Skip to content

docs(supabase): document the wasm-inline entry in the two files that ship - #951

Merged
tobyhede merged 7 commits into
mainfrom
docs/supabase-wasm-inline-shipping-docs
Aug 31, 2026
Merged

docs(supabase): document the wasm-inline entry in the two files that ship#951
tobyhede merged 7 commits into
mainfrom
docs/supabase-wasm-inline-shipping-docs

Conversation

@tobyhede

@tobyhede tobyhede commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Summary

CipherStash Stack encrypts individual database columns. @cipherstash/stack-supabase is the Supabase integration, and since #912 it has shipped two entry points: the package root, which reads your column types straight out of Postgres and therefore needs Node; and @cipherstash/stack-supabase/wasm-inline, which takes the tables you declare instead and therefore runs on Deno, Supabase Edge Functions and Cloudflare Workers.

The documents that ship to customers were never updated. They still said the wrapper cannot run in a Worker, which stopped being true nine days ago. This PR corrects them and documents the edge entry, which the README did not mention even once.

This matters most for the readers who need the edge entry: server code on managed AI platforms (Lovable, v0, Bolt, Replit) runs on an edge runtime. An agent that reads "cannot run in a Worker" does not file a bug — it concludes the product does not support the platform and stops.

Writing the correct version turned up a set of claims in the same neighbourhood that were also wrong, and a type that enforced less than the docs said it did. Those are fixed here too, and three new test files stop each one drifting back.

The six commits

Rebased onto main after #953 merged; the hashes below are post-rebase.

e36f1a06 Document the wasm-inline entry in packages/stack-supabase/README.md and skills/stash-supabase/SKILL.md
d35c792f Correct four false edge-entry claims found while writing the above, and guard the schema import
0abd7be8 Adversarial re-check of d35c792f's own corrections — the select('*') refusal is not a read backstop
88264b79 Review response: refuse databaseUrl in the type, and carry 0abd7be8 across to the copy it missed
3979948b Review response: attribute the Node-only restriction to the engine, not to introspection
1a7db492 Enrol the README and the Supabase skill in #952's runtime-claims guard
4d717468 Audit follow-up: the undeclared-column remedy was the option that entry refuses

Changes

skills/stash-supabase/SKILL.md — ships inside the stash and @cipherstash/wizard npm packages; stash init copies it into the customer's repository, where their coding agent reads it as instruction.

  • The setup section's claim is scoped to the package root entry, and points forward to the new step 5.
  • New "### 5. Edge runtimes — the wasm-inline entry": an entry-point comparison table, the full call shape, and the four ways that entry differs.
  • The managed-platforms callout and the Edge Function credentials callout both point at step 5. The second previously sent a Supabase reader to @cipherstash/stack/wasm-inline, which is the right entry for encrypting without the wrapper and the wrong one for using it.

packages/stack-supabase/README.md — renders on the npm package page. Same correction to the introspection paragraph, plus a new "Edge runtimes" section with the same table and call shape. The file contained zero occurrences of the word "wasm" before this.

skills/stash-edge/SKILL.md and skills/stash-managed-platforms/SKILL.md — corrected where they were wrong about this surface; see below.

packages/stack-supabase/src/wasm-inline.tsdatabaseUrl?: never on EncryptedSupabaseWasmOptions. The only non-documentation change in the PR.

.changeset/supabase-docs-wasm-inline-entry.md@cipherstash/stack-supabase, stash and @cipherstash/wizard, all patch.

Corrections beyond the headline

Each was checked against source rather than restated.

  1. stash-managed-platforms shipped a snippet that does not compile. It authored schemas from @cipherstash/stack/wasm-inline and handed them to encryptedSupabase. The adapter types schemas from @cipherstash/stack/eql/v3, and the two entries ship independent declarations of the column classes whose private columnName TypeScript compares nominally — tsc rejects it while the code runs perfectly, which is why nobody noticed.
  2. stash-edge is what produced that snippet. Its "Schema Modules Do Not Cross Entries" section had no carve-out for the adapter. The real rule is "author against the entry whose client type consumes the schema".
  3. The wrong failure mode for a missing declaration. Omitting schemas on the edge entry cannot produce a no-column client; the hazard is an undeclared column on a declared table, treated as plaintext.
  4. select('*') being refused is not a read backstop. A query awaited with no .select() call at all takes the raw-* branch (query-builder.ts:703-725) and decryptResults passes it through on !hasSelect (query-results.ts:126-138), returning every column undecrypted — declared or not, no error. Writes have a backstop (the eql_v3_* domain CHECK, though a NULL passes); reads have none.
  5. databaseUrl was only refused for callers who wrote the options inline. Leaving the field off the interface is policed by excess-property checking, which fires on fresh object literals alone — an options object built as a const and passed by variable type-checked clean and hit the runtime throw instead, from documents claiming the type checker enforced it. Now databaseUrl?: never, the same gap and fix as WasmClientConfig.eqlVersion?: never one package along.
  6. "Everything after construction is the same wrapper" was false in the first way a reader hits it. The README said it twenty-five lines under a paragraph telling the same reader select('*') just works, immediately above the snippet it tells you to port into an Edge Function. Both it and stash-supabase now name the two exceptions.
  7. A diagnostic that cannot fire. stash-managed-platforms promised an ambient DATABASE_URL is ignored "with a warning that the declaration is unverified", in a section about wasm-inline. Both the ambient read (create.ts:304) and the warning (create.ts:330) are gated on introspector, which is null on that build.
  8. "Introspection needs a connection, so this entry runs on Node only" inverts the cause. Declaring schemas really does skip introspection, and the entry is Node-only anyway — it binds the native engine, and its emitted bundle carries an import("pg") specifier a bundler resolves at build time. A reader who takes the stated cause at face value reaches for schemas expecting an edge-capable client. Both the README and stash-supabase now attribute the restriction to the engine, keeping introspection as the reason for the pg peer dependency, which is what it actually explains. This is the same defect fix(supabase): document the second entry point, and pin the runtime to the engine #952 corrects in this package's .d.ts.
  9. The undeclared-column remedy was the option that entry refuses. stash-managed-platforms' ⚠️ callout answered "you cannot guarantee your declaration is complete" with "pass databaseUrl so introspection fills the gaps" — under ## encryptedSupabase in a Worker, four lines above the sentence saying databaseUrl is refused there. Same shape as 7, in the adjacent paragraph, and it lands on this skill's actual readers: an agent on Lovable, v0, Bolt or Replit has only the edge entry, so following the remedy gets a construction throw.

New tests

Nothing type-checks a SKILL.md or a README, and these are shipped text — the drift lands in someone else's repository, not ours. Three files close that:

  • scripts/__tests__/skills-supabase-edge-schema-entry.test.mjs — fails if any shipped document pairs encryptedSupabase with a schema authored from @cipherstash/stack/wasm-inline. Per fenced block, not per file.
  • scripts/__tests__/skills-select-star-not-a-read-backstop.test.mjs — fails if any shipped document states the select('*') refusal without the caveat, in the same markdown section. This one exists because the correction in 578783ad landed in one of two copies and the copy left behind then looked reviewed. It scans per section so wording can move, blanks fenced code so a snippet is not read as a claim, and carries a liveness test against the canonical skill — a decayed regex is a guard that always passes.
  • packages/stack-supabase/__tests__/supabase-wasm-config.test-d.ts — type-level: the edge config accepts both auth arms and rejects clientId + clientKey alone; databaseUrl is rejected inline and by variable, on both call forms, with a positive control so a never that poisoned the options type cannot masquerade as passing expect-errors.
  • packages/stack-supabase/__tests__/supabase-declared-mode.test.ts — pins the real hazard at runtime: an undeclared column on a declared table reaches PostgREST as plaintext on insert, update and filter, and is absent from the decrypt call.

Both doc guards were confirmed red before the fixes: the new one against the unmodified stash-managed-platforms, and again against a README with the caveat clause deleted.

Correction 8 is now mechanically enforced too, in this PR rather than a follow-up. #952's scripts/__tests__/supabase-runtime-claims.test.mjs shipped with a note in its own GUARDED list deferring packages/stack-supabase/README.md until this branch stopped rewriting those lines. 1a7db492 adds it, and adds skills/stash-supabase/SKILL.md beside it — the two copies that reach a customer, one as the npm package page and one copied into their repository by stash init. Enrolling the skill needed one more sentence fixed ("Removing the pg dependency would unblock Workers, not browsers" left "Workers" unqualified, and its opener made introspection the thing keeping the entry out of a browser; the browser is ruled out by the workspace clientKey, #804, either way). skills/stash-managed-platforms/SKILL.md is deliberately not enrolled, with the reason recorded in the docblock: its causal claims are correct, but it trips the unqualified-"Worker" detector twice inside its YAML frontmatter description, where a reword changes what the skill matches on — its own change, with its own review.

Verification

Source for every claim about the edge entry:

Claim Source
schemas is required packages/stack-supabase/src/create.ts:307-313
config is required and carries all four CS_* values packages/stack-supabase/src/wasm-inline.ts:18-30
databaseUrl throws at construction packages/stack-supabase/src/create.ts:358-362
databaseUrl is rejected by the type packages/stack-supabase/src/wasm-inline.ts (databaseUrl?: never)
.withLockContext() / .audit() throw packages/stack-supabase/src/wasm-client-adapter.ts:40-66
Bare await returns undecrypted rows query-builder.ts:703-725, query-results.ts:126-138
The unverified-declaration warning is introspector-gated packages/stack-supabase/src/create.ts:304,330

Test runs, re-run at 4d717468 after the rebase:

pnpm --filter @cipherstash/stack-supabase test (570 passed at becdebb2) was not re-run here: this worktree has no built index.node, so the runtime suite fails on the missing native binding. Every commit since is documentation, a .mjs guard and that guard's file list — nothing it covers. CI builds the binding and runs it.

Rebuilt stash, @cipherstash/wizard and @cipherstash/stack-supabase. grep "cannot run in a Worker" over packages/cli/dist/skills/ and packages/wizard/dist/skills/ returns nothing; before the rebuild both carried it at line 269, so an npm pack shipped it.

Why @cipherstash/wizard is bumped

skills/ ships in that tarball as well as stash's — packages/wizard/tsup.config.ts:24-25 copies it into dist/skills, and package.json lists that under files. Without the bump the published wizard keeps shipping the old text until an unrelated change moves its version. The recent skills-editing changesets were stash-only; the skills-adding ones bumped both.

Related

Closes #950
Refs #912 (added the second entry point), #804 (the browser claim, which is correct and stays), #797 (.withLockContext() on the WASM entry, which throws today).

Review notes

The #953 conflict is resolved. That PR merged first, and this branch is rebased onto it. The conflict landed in skills/stash-edge/SKILL.md's frontmatter description rather than the predicted paragraph: #953 added the browser clause there while this branch corrected "the four mandatory CS_* variables" to "which CS_* variables are mandatory". Both are kept. The two changes are complementary as expected — that PR explains why the browser is still ruled out, this one why the Worker no longer is.

Three files carry the same stale sentence and are deliberately untouched: packages/stack/CHANGELOG.md, packages/stack-supabase/CHANGELOG.md, and docs/superpowers/specs/2026-07-09-supabase-v3-introspection-design.md. They are historical records and were accurate on the dates they were written. docs/reference/supabase-sdk.md carries it too, and is fixed in #952 instead — it is internal reference documentation and ships in no package, so it takes no changeset and did not belong in this one.

Two things found and deliberately left as follow-ups:

  • e2e/wasm/supabase-declared.test.ts pairs encryptedSupabase with a schema from @cipherstash/stack/wasm-inline — the exact pairing this PR documents as non-compiling — hidden by deno test --no-check. It is internal and ships to nobody, and at lines 255-284 one table object feeds both a raw wasm-inline Encryption and the adapter, so the fix needs two schema objects rather than a swapped import.
  • The introspector-gated-warning claim is left unguarded by a test. "A warning is logged" has no stable phrasing to key on, and "introspector" appears freely in any section discussing introspection, so every detector for it either misses rewordings or fires on unrelated prose. The guard's header comment records this.

https://claude.ai/code/session_01TvoE76yuuLn2Bmdaz8yFYy

@changeset-bot

changeset-bot Bot commented Aug 27, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 4d71746

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 11 packages
Name Type
@cipherstash/stack-supabase Patch
stash Patch
@cipherstash/wizard Patch
@cipherstash/basic-example Patch
@cipherstash/e2e Patch
@cipherstash/stack Patch
@cipherstash/stack-drizzle Patch
@cipherstash/stack-prisma Patch
@cipherstash/bench Patch
@cipherstash/test-kit Patch
@cipherstash/prisma-example Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@tobyhede
tobyhede marked this pull request as ready for review August 30, 2026 23:26
@tobyhede
tobyhede requested a review from a team as a code owner August 30, 2026 23:26
tobyhede added a commit that referenced this pull request Aug 30, 2026
Review of #951 found four wrong statements in files that ship to customers.
Verified each against source before fixing; one was misattributed and the real
defect turned out to be older and elsewhere.

- `skills/stash-managed-platforms/SKILL.md` shipped a snippet that does not
  compile: it authored `schemas` from `@cipherstash/stack/wasm-inline` and
  handed them to `encryptedSupabase`, whose `schemas` is typed from
  `@cipherstash/stack/eql/v3`. The two entries ship independent declarations of
  the column classes, whose private `columnName` field TypeScript compares
  nominally, so `tsc` rejects it while the code runs fine. Nothing caught it —
  `column-map.ts` probes structurally by design, and `e2e/wasm/deno.json` runs
  `deno test --no-check`.
- `skills/stash-edge/SKILL.md` is what produced that snippet: it told edge
  projects to author schemas from the WASM entry with no carve-out. The rule is
  really "author against the entry whose client type consumes the schema".
- `skills/stash-supabase/SKILL.md` named the wrong hazard. An undeclared table
  throws and a missing `schemas` cannot construct; what is silently treated as
  plaintext is an undeclared *column* on a declared table.
- "Undeclared tables behave exactly as with no `schemas` at all" was false on
  the native entry too — introspection is gated on a resolved database URL, not
  on the absence of `schemas`.
- "config must carry all four `CS_*` values" was wrong in five places.
  `WasmClientConfig` is a union: only `clientId` and `clientKey` are always
  required, and the `authStrategy` arm makes `workspaceCrn` optional. OIDC
  federation does work on the edge; `.withLockContext()` is what does not.
  `stash-edge` already contradicted itself here — its own example passes two.

Three tests keep these honest, each confirmed to fail when its property is
violated:

- `scripts/__tests__/skills-supabase-edge-schema-entry.test.mjs` fails if any
  shipped document pairs the adapter's edge entry with a WASM-entry schema.
- `supabase-wasm-config.test-d.ts` pins the config union at the type level.
- `supabase-declared-mode.test.ts` pins the undeclared-column plaintext path
  across insert, update, filter, select and decrypt.

Documentation and tests only — no runtime behaviour changes.

Claude-Session: https://claude.ai/code/session_01UqnsDeKs8N9n9TStPAqzzw
tobyhede added a commit that referenced this pull request Aug 31, 2026
The reference doc and three TSDoc blocks all derived the default entry's
runtime from schema discovery: introspection needs Postgres, therefore the
entry cannot run on an edge runtime. That inference is false in both
directions. Declaring `schemas` removes the Postgres dependency entirely
(create.ts:303-306, :364-367) and the entry is still Node-only; and the
entry would be Node-only with no introspection code in it at all.

What actually pins it is the import: `Encryption` from `@cipherstash/stack`
pulls a module graph that statically imports `@cipherstash/auth`, whose Node
entry resolves its platform binding at module evaluation, and the emitted
bundle carries an `import("pg")` specifier a bundler resolves at build time.
Neither moves when you declare schemas.

The default entry's doc also named `@cipherstash/protect-ffi` as the binary
loaded on import. It is the one package in that graph that deliberately does
not: `packages/protect-ffi/src/index.cts` uses `import native = require(...)`
specifically so `__importStar` cannot force the neon proxy to resolve, and
`nativeLoading.test.ts` guards it.

Two smaller corrections in the same pass: bare "a Worker" is ambiguous and
false under the Node `worker_threads` reading — the native entry runs fine
there — so the edge runtimes are now named, as the table already named them;
and the browser prohibition is restored to the native entry, which the
previous revision moved onto the edge entry, leaving the native paragraph
implying the browser was fine.

Guarded by scripts/__tests__/supabase-runtime-claims.test.mjs (three
detectors, unit-tested in both directions, applied to the four prose
sources), and by three new assertions in wasm-entry-edge-safety.test.ts that
tie the corrected prose to the emitted bundles — its header comment repeated
the protect-ffi misattribution and would otherwise have contradicted them.

Not touched, to avoid conflicting with open PRs: skills/stash-supabase and
packages/stack-supabase/README.md carry defect 1 verbatim but are being
rewritten on #951 at those exact lines, and the browser-capability claims in
examples/ and packages/stack/tsup.config.ts belong to #953. The README path
is recorded in the guard's GUARDED list comment so it is added when #951
lands.

Claude-Session: https://claude.ai/code/session_01FVKXa6GjUHN5xvJq2912KA
@tobyhede

Copy link
Copy Markdown
Contributor Author

Flagging one sentence here from #952, which corrects this same explanation elsewhere.

packages/stack-supabase/README.md on this branch (around line 90):

Introspection needs a direct Postgres connection (DATABASE_URL), so pg is an optional peer dependency and this entry runs on Node only

The so chains two consequences off introspection, and the second one does not follow. Introspection is not what makes this entry Node-only — passing schemas skips introspection entirely (no connection, no pg, no databaseUrl; src/create.ts:303-306 and :364-367) and the entry is still Node-only. What actually pins it is the engine: the import pulls a module graph that statically imports @cipherstash/auth, whose Node entry resolves its platform binding at module evaluation, and the emitted bundle carries an import("pg") specifier a bundler resolves at build time. Neither moves when you declare schemas.

The reason to raise it now rather than after merge: #952 adds a prose guard (scripts/__tests__/supabase-runtime-claims.test.mjs) that detects exactly this shape. The README is not in its GUARDED list yet — deliberately, because this branch is rewriting those lines and editing them from two branches would conflict — but the list carries a note to add the path once this lands. Cheaper to fix the sentence here than to land it and have the guard bounce it.

Worth noting the neighbouring sentence in skills/stash-supabase/SKILL.md on this branch already gets it right:

Introspection needs a direct Postgres connection (...), and the engine is a native module, so this entry runs on Node only

That names the engine as a cause, which is the fix. The README just needs the same treatment. Something like:

Introspection needs a direct Postgres connection (DATABASE_URL), so pg is an optional peer dependency. The engine is a native module, so this entry runs on Node only — construct it in your server-side code, not in a browser. For an edge runtime, see the second entry point below.

That also restores the browser half, which the current rewrite drops.

@freshtonic freshtonic left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes for one narrowly-scoped reason; everything else in this PR verified cleanly and is approve-quality. Details below.

The blocker: the README's corrected sentence still carries the false causal claim

packages/stack-supabase/README.md (~line 90) now reads:

Introspection needs a direct Postgres connection (DATABASE_URL), so pg is an optional peer dependency and this entry runs on Node only

The "so" still derives runs on Node only from introspection — the precise causal inversion #952 exists to correct, and which its changeset corrects in this same package's .d.ts hover text. The two shipping surfaces would disagree about causality after both merge. The harm case is the one #952 documents: a reader who believes the causal version passes schemas (which really does remove introspection) expecting an edge-capable client, and gets a build failure — the entry is Node-only because it binds the native engine, schemas or not.

This is also pre-announced churn: #952's GUARDED comment defers adding this README to the falseRuntimeCause guard until after this PR lands, at which point the guard will flag this exact sentence and force a third PR to rewrite it. Cheaper to land it right here. Suggested wording:

Introspection needs a direct Postgres connection (DATABASE_URL), which is why pg is an optional peer dependency. This entry runs on Node only either way — it binds the native engine, and declaring schemas doesn't move that — so construct it in your server-side code. For an edge runtime, see the second entry point below.

Two smaller instances of the same shape, recommended but not blocking:

  • skills/stash-supabase/SKILL.md step 3: "Introspection needs a direct Postgres connection (…), and the engine is a native module, so this entry runs on Node only". This one at least names the true cause, but still lists introspection as co-cause of the runtime restriction. Lead with the engine, keep introspection as the reason for the connection, not the runtime.
  • The managed-platforms callout's "encryptedSupabase can be constructed inside a Worker" sentence has no edge-runtime qualifier in the sentence itself — the reading worker_threads makes it ambiguous per #952's third defect. One word ("edge Worker" → name the runtimes) fixes it.

What I verified (all checks out)

  • The WasmClientConfig union claims are exactly right. packages/stack/src/wasm-inline.ts:221-270: clientId/clientKey on the base; the access-key arm requires workspaceCrn + accessKey with authStrategy?: never; the strategy arm makes workspaceCrn optional with accessKey: never. The README/skill prose, the type test's positive and negative cases, and the "mixing rejected" case all match the type as written.
  • The eql/v3-vs-wasm-inline schema-entry correction is real. schema-builder.ts:3,7 types schemas as Record<string, AnyV3Table> from @cipherstash/stack/eql/v3; column-map.ts documents the structural (non-instanceof) probe that let the broken snippet run at runtime; e2e/wasm/deno.json runs --no-check, confirming nothing in-repo would have caught the shipped snippet. The managed-platforms skill really did ship a snippet that fails tsc.
  • The declared-mode hazard rewrite matches the code. create.ts:285-304: the ambient DATABASE_URL read is gated on introspector, so "undeclared tables behave exactly as with no schemas" was indeed false without databaseUrl passed alongside. The select('*') refusal (query-builder.ts:162) and the raw-* no-.select() branch (:724) are both as the new skill text describes — including the important point that the refusal is not a read backstop.
  • The new wire-level tests pin the real hazard (undeclared column: plaintext on insert/update/filter, no ::jsonb cast, absent from the decrypt table) at the observable-payload level, and the type test carries a positive control so the @ts-expect-errors can't go vacuous.
  • The new guard runs and can't go silently empty: lib/package-readmes.mjs exists on main, the shipped-file set is asserted non-trivial, and per-block (not per-file) matching correctly permits the raw-client snippet and the adapter snippet in the same document.
  • Changeset scope follows repo precedent: pending skills changesets (supabase-skill-eql-305, skills-eql-source-and-index-cascade, prisma-skill-upgrade-replan) all bump stash only, so stash + @cipherstash/stack-supabase is right here. (Wizard also ships skills/ in its tarball and conventionally gets no bump — if that convention is wrong it's wrong repo-wide, not in this PR.)
  • CI is green; the #953 conflict note is accurate and the merge-second-rebases plan is fine.

Fix the README sentence (and ideally the two skill instances) and this is an approve — the rest of the PR is careful, verified work, and the four corrected claims in the changeset are each genuinely load-bearing for edge users.

@coderdan

Copy link
Copy Markdown
Contributor

Review

The correction this PR makes is right and overdue, and the discipline in commit 3 — noticing that the select('*') refusal is not a read backstop, and saying so — is the good kind of self-correction. Five findings, two medium. Both mediums are the same shape: a claim that is true of the package root being carried onto the edge entry, or a correction landed in one skill and not the sibling this PR also edits.

Medium

1. packages/stack-supabase/README.md:112 — "everything after construction is the same wrapper" is false in the first way a reader will hit. The line says from(), the filters and the response shape are identical. The edge entry is always in declared mode, and declared mode refuses select('*')expandStarOrThrow (query-builder.ts:159-166) throws when allColumns is empty. Two paragraphs above, README:87 tells the same reader "select('*') just works".

Concretely: someone ports the Quick-start snippet into a Supabase Edge Function exactly as the new section instructs, and gets encryptedSupabase does not support select('*') at runtime — from a document that just told them nothing after construction changes. from() on an undeclared table throwing is a second such difference. Step 5 of the skill at least names the select('*') refusal; the README names neither.

2. skills/stash-managed-platforms/SKILL.md:181-183 — commit 3's correction did not propagate here, and this PR edits this file. Commit 3 established that the select('*') refusal is not a read backstop: a query awaited with no .select() call at all takes the raw-* branch (query-builder.ts:720-724) and decryptResults passes it straight through on !hasSelect (query-results.ts:127-130), returning every column undecrypted. That landed in stash-supabase/SKILL.md and not here, where the text still reads:

What declared mode gives up, it gives up loudly rather than silently:

  • select('*') and bare select() are refused.

That is precisely the inference commit 3 identified as wrong. An agent on Lovable writes await supabase.from('users').eq('id', 1), gets raw EQL payloads back as data with no error, and this skill told it the loud-failure guarantee covered reads. The ⚠️ callout below it covers the undeclared column case but not this one. Worth carrying commit 3's wording across.

Low

3. README.md:132, skills/stash-supabase/SKILL.md:363 and :395 — "three of them enforced by the type checker" overstates what the type does. Omitting databaseUrl from EncryptedSupabaseWasmOptions only trips excess-property checking, which fires on fresh object literals alone. Verified: a temporary .test-d.ts passing { schemas, config, databaseUrl: 'postgres://…' } through a const variable into encryptedSupabase type-checks clean under --typecheck.only, and the caller then hits the runtime throw at create.ts:359-361.

This is the failure mode the repo already defends against one file over — WasmClientConfig.eqlVersion?: never exists precisely because "a shared config const, which is what a v2 → v3 migration actually holds, therefore type-checked clean and then hit the runtime guard". Same fix here: databaseUrl?: never on the options type. Otherwise the count is two, not three.

4. skills/stash-managed-platforms/SKILL.md:189 — a diagnostic that cannot fire on this entry. "An ambient DATABASE_URL … is ignored when schemas are passed, with a warning that the declaration is unverified." This section's subject is wasm-inline (the paragraph immediately below it is about that entry refusing databaseUrl), and there the warning never fires: both the ambient read (create.ts:304) and the warning (create.ts:330) are gated on introspector, which is null for that build. This is the same correction the PR already made in stash-supabase/SKILL.md, not carried across.

5. .changeset/supabase-docs-wasm-inline-entry.md:1-3 — no @cipherstash/wizard bump. The changeset covers stash and @cipherstash/stack-supabase. skills/ also ships in the wizard tarball: packages/wizard/tsup.config.ts:24-25 copies ../../skills into dist/skills, and packages/wizard/package.json lists it under files. Without a wizard patch, the published wizard keeps shipping the "cannot run in a Worker" text until some unrelated change bumps it. This matches existing convention (ec0c5a75's skills-only changeset was stash-only), so it may be a pre-existing gap rather than this PR's omission — but AGENTS.md is explicit that both tarballs carry skills/.

Housekeeping

The PR body describes only the first commit. "Changes" lists three files; the diff is nine. Commits 981f86a3 and 578783ad added ~440 lines of new tests (supabase-declared-mode.test.ts, supabase-wasm-config.test-d.ts, scripts/__tests__/skills-supabase-edge-schema-entry.test.mjs), edits to skills/stash-edge/SKILL.md and skills/stash-managed-platforms/SKILL.md, and grew the changeset from ~40 to 108 lines. The "Verification" section's test runs predate all of it. Worth refreshing before merge — the new guard test is the most reviewable thing here and the body does not mention it exists.

Verified, not findings

  • pnpm --filter @cipherstash/stack-supabase test — 570 passed, 16 files. --typecheck.only — 51 passed; the new .test-d.ts is genuinely reached, and tests.yml:149 runs it in CI. skills-supabase-edge-schema-entry.test.mjs — 35 passed. skill-supabase-apply.test.ts — 29 passed.
  • Every substantive doc claim cross-checked against source: WasmClientConfig's three arms (stack/src/wasm-inline.ts:221-287), the databaseUrl and schemas throws (create.ts:303-361), the !hasSelect passthrough, expandStarOrThrow, the chainer throws (wasm-client-adapter.ts:40-66), the domain CHECK and its NULL hole, V3Schemas typed from @cipherstash/stack/eql/v3. All correct as written.
  • src/wasm-inline.ts is a doc-comment change only — no behaviour.
  • supabase-wasm-config.test-d.ts:135 relies on Parameters<> picking the last overload of EncryptedSupabaseWasmFactory. Reordering the two overloads would make [1]['config'] resolve against supabaseKey: string and fail confusingly. Brittle, not wrong.
  • The e2e/wasm/supabase-declared.test.ts counterexample and the guard's markdown-only scope are deferred explicitly in commit 3's message; not re-raised.
  • Four scripts/__tests__ files fail locally for environment reasons unrelated to this diff (missing node_modules for the EQL subtree; release-gate reaching npm).

@coderdan coderdan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes on one item. Full findings are in #951 (comment) — this review just marks which of them block.

Blocking — skills/stash-managed-platforms/SKILL.md:181-183. Commit 3 of this PR established that the select('*') refusal is not a read backstop: a query awaited with no .select() call takes the raw-* branch (query-builder.ts:720-724) and decryptResults passes it through on !hasSelect (query-results.ts:127-130), returning every column undecrypted. That correction landed in stash-supabase/SKILL.md and not in this file, which the same PR edits. It still reads "What declared mode gives up, it gives up loudly rather than silently" over a bullet saying select('*') and bare select() are refused.

This skill ships into customer repositories and is read as instruction by an agent on Lovable, v0, Bolt or Replit. The failure it mispromises is silent — raw EQL payloads returned as data, no error. Shipping the correction to one of two copies is worse than shipping neither, because the copy left behind now looks reviewed. Carrying commit 3's wording across is the whole fix.

Worth fixing in the same pass, not blocking — packages/stack-supabase/README.md:112. "Everything after construction is the same wrapper" contradicts README:87 twenty-five lines above, and the edge entry refuses select('*') in exactly the Quick-start snippet the new section tells readers to port. One sentence, and it renders on the npm package page.

Findings 3, 4 and 5 in the comment are fine as follow-ups. Finding 3 has a real one-line fix (databaseUrl?: never) if you would rather the "three of them enforced by the type checker" claim become true than be softened.

The central correction here is right and the new guard test is real — this is a short round trip, not a rework. Please also refresh the PR body: it describes only the first of three commits, and the "Verification" runs predate the ~440 lines of tests added since.

tobyhede added a commit that referenced this pull request Aug 31, 2026
…ead-backstop correction across

Review of #951 found four things. One is a change to a published type; three
are corrections to shipped text, two of them in the copy the previous commit
did not touch.

`EncryptedSupabaseWasmOptions` enforced less than three documents claimed it
did. Leaving `databaseUrl` off the interface is policed by excess-property
checking, which fires on FRESH object literals alone — so an options object
assembled as a `const` and passed by variable, which is what a Node-to-edge
port actually holds, type-checked clean and reached the construction-time
throw in `create.ts` instead. The README, `stash-supabase` and the interface's
own doc comment all said the type checker enforced it. The field is now
declared `databaseUrl?: never`, which is the same gap and the same fix as
`WasmClientConfig.eqlVersion?: never` one package along, whose comment
describes this exact shared-config-const path. The runtime throw stays: it is
the only thing a plain JS caller meets. New type tests cover both the inline
and the by-variable shapes on both call forms, and a positive control asserts
the same options object still compiles once the field is dropped — without it,
a `never` that poisoned the options type would read as four passing
expect-errors.

`578783ad` corrected the `select('*')` claim in `stash-supabase` and left the
same claim standing in `stash-managed-platforms`, which the same PR edited.
That skill still framed declared mode as giving things up "loudly rather than
silently" over a bullet naming the refusal — precisely the inference the
correction exists to kill. Its audience is an agent on Lovable, v0, Bolt or
Replit reading it as instruction, and the failure it mispromised is silent:
`await supabase.from('users').eq('id', 1)` with no `.select()` takes the
raw-`*` branch (`query-builder.ts:703-725`), `decryptResults` returns it
untouched on `!hasSelect` (`query-results.ts:126-138`), and every column comes
back as a raw EQL payload with no error. Shipping a correction to one of two
copies is worse than shipping neither, because the copy left behind now looks
reviewed — so `scripts/__tests__/skills-select-star-not-a-read-backstop.test.mjs`
now fails if any shipped document states the refusal without the caveat in the
same markdown section. It scans per section rather than per sentence, so the
wording can move; it blanks fenced code, so a snippet is not read as a claim;
and it carries a liveness test against the canonical skill, because a decayed
regex is a guard that always passes.

"Everything after construction is the same wrapper" was false in the first way
a reader hits it, in both the README and `stash-supabase`. The edge entry is
always in declared mode, where `select('*')` is refused and `from()` on an
undeclared table throws — and the README said it twenty-five lines under a
paragraph telling the same reader `select('*')` just works, immediately above
the quick-start snippet it tells you to port into an Edge Function. Both
sentences now name the two exceptions.

`stash-managed-platforms` also promised a diagnostic that cannot fire on the
entry its section is about: an ambient `DATABASE_URL` ignored "with a warning
that the declaration is unverified". Both the ambient read (`create.ts:304`)
and the warning (`create.ts:330`) are gated on `introspector`, which is `null`
for the wasm-inline build. Scoped to Node, with the edge case stated. This is
the same correction `stash-supabase` already carried and this file did not.

The changeset gains `@cipherstash/wizard`: `skills/` ships in that tarball too
(`packages/wizard/tsup.config.ts` copies it to `dist/skills`, listed under
`files`), so without the bump the published wizard keeps the old text until an
unrelated change moves its version. The recent skills-editing changesets were
`stash`-only; the skills-adding ones bumped both.

The ambient-warning claim is deliberately left unguarded — "a warning is
logged" has no stable phrasing to key on and "introspector" appears freely in
any section about introspection, so every detector for it either misses
rewordings or fires on unrelated prose. The header comment says so.

Verification: `@cipherstash/stack-supabase` 570 tests + 56 type tests, 0 type
errors; `pnpm run test:scripts` 887 passed (51 files), including both doc
guards; `skill-supabase-apply` 29 passed; `biome check` 0 errors. The new
guard was confirmed red against the unmodified `stash-managed-platforms` and
against a README with the caveat clause removed, before either was fixed.

Claude-Session: https://claude.ai/code/session_01TvoE76yuuLn2Bmdaz8yFYy

@freshtonic freshtonic left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed at becdebb. Still requesting changes, for the same single item as my last review — it is the one thing the new commit did not touch.

Still blocking — packages/stack-supabase/README.md (~line 90):

Introspection needs a direct Postgres connection (DATABASE_URL), so pg is an optional peer dependency and this entry runs on Node only

The "so" still derives runs on Node only from introspection — the causal inversion #952 (now merged) corrects in this same package's .d.ts, and the sentence #952's falseRuntimeCause guard will mechanically flag the moment this README is added to its GUARDED list, which this PR's own comments schedule. One clause fixes it; suggested wording from my previous review still applies:

Introspection needs a direct Postgres connection (DATABASE_URL), which is why pg is an optional peer dependency. This entry runs on Node only either way — it binds the native engine, and declaring schemas doesn't move that — so construct it in your server-side code. For an edge runtime, see the second entry point below.

The two smaller instances I flagged as non-blocking are also still present (skills/stash-supabase/SKILL.md:271's "and the engine is a native module, so…" co-cause form, and the line-29 bare "Worker") — still non-blocking, still worth the one-word fixes while in the file.

The new commit itself is good, verified:

  • databaseUrl?: never on EncryptedSupabaseWasmOptions is a real fix, and the rationale is exactly right: omission is policed by excess-property checking, which fires on fresh literals only, so the ported-from-Node const options object type-checked clean and hit the runtime throw. The new type-test block pins the variable-passing case — the one that actually bites.
  • Dan's blocker (the read-backstop correction missing from stash-managed-platforms) is carried across, and the new skills-select-star-not-a-read-backstop.test.mjs guard means the "gives up loudly" phrasing can't drift back into any shipped document.
  • The @cipherstash/wizard: patch addition to the changeset is correct and goes further than repo precedent required — wizard ships skills/ in its own tarball, so this is the more honest bump set.
  • CI is green at HEAD.

Fix the one README sentence and I'll approve on sight.

…ship

`@cipherstash/stack-supabase` has had two entry points since #912. The package
root introspects the database and runs on Node; `/wasm-inline` carries the WASM
engine, takes declared `schemas` instead of introspecting, and runs on Deno,
Supabase Edge Functions and Cloudflare Workers. Introspection was the only
thing needing a Postgres socket, so that entry does run in a Worker.

Two shipping documents were never updated and still describe the state before
that change:

- `packages/stack-supabase/README.md` said the factory "cannot run in an edge
  Worker or the browser", and contained no occurrence of the word "wasm" at
  all. This file renders on the npm package page.
- `skills/stash-supabase/SKILL.md` said the same in its setup section. The
  skill ships inside the `stash` tarball and `stash init` copies it into the
  customer's repo, where their agent reads it as instruction. The file's one
  correct mention of the edge entry sat in a callout the setup steps never
  pointed at, so a reader following the steps never learned it existed.

The readers this misled hardest are the ones who need the edge entry most:
server code on Lovable, v0, Bolt and Replit runs on an edge runtime, which is
exactly what `/wasm-inline` was built for. The failure is silent — an agent
that reads "cannot run in a Worker" concludes the product does not support the
platform and stops.

Both files now carry an entry-point table and the edge call shape, and name the
four ways that entry differs, each checked against the source rather than
restated: `schemas` is required (`create.ts:307-313`), `config` is required and
carries all four `CS_*` values (`wasm-inline.ts:18-30`), `databaseUrl` throws at
construction (`create.ts:359-361`), and `.withLockContext()` / `.audit()` throw
rather than silently dropping an identity claim (`wasm-client-adapter.ts:40-66`,
#797).

The browser half of the old sentence was correct and is kept, with its reason
named: the WASM client requires a workspace `clientKey` on every authentication
path, so a browser build would ship the key with it (#804).

Deliberately untouched: `packages/stack/CHANGELOG.md`,
`packages/stack-supabase/CHANGELOG.md` and the superpowers design spec carry the
same sentence and are historical records, accurate for their dates.
`docs/reference/supabase-sdk.md` carries it too and is fixed separately — it is
internal and ships in no package.

Changeset: `stash` patch for the skill, `@cipherstash/stack-supabase` patch for
the README.

Closes #950
Refs #912, #804, #797

Claude-Session: https://claude.ai/code/session_01E1J2nVGJWVkqvLepDfinRf
Review of #951 found four wrong statements in files that ship to customers.
Verified each against source before fixing; one was misattributed and the real
defect turned out to be older and elsewhere.

- `skills/stash-managed-platforms/SKILL.md` shipped a snippet that does not
  compile: it authored `schemas` from `@cipherstash/stack/wasm-inline` and
  handed them to `encryptedSupabase`, whose `schemas` is typed from
  `@cipherstash/stack/eql/v3`. The two entries ship independent declarations of
  the column classes, whose private `columnName` field TypeScript compares
  nominally, so `tsc` rejects it while the code runs fine. Nothing caught it —
  `column-map.ts` probes structurally by design, and `e2e/wasm/deno.json` runs
  `deno test --no-check`.
- `skills/stash-edge/SKILL.md` is what produced that snippet: it told edge
  projects to author schemas from the WASM entry with no carve-out. The rule is
  really "author against the entry whose client type consumes the schema".
- `skills/stash-supabase/SKILL.md` named the wrong hazard. An undeclared table
  throws and a missing `schemas` cannot construct; what is silently treated as
  plaintext is an undeclared *column* on a declared table.
- "Undeclared tables behave exactly as with no `schemas` at all" was false on
  the native entry too — introspection is gated on a resolved database URL, not
  on the absence of `schemas`.
- "config must carry all four `CS_*` values" was wrong in five places.
  `WasmClientConfig` is a union: only `clientId` and `clientKey` are always
  required, and the `authStrategy` arm makes `workspaceCrn` optional. OIDC
  federation does work on the edge; `.withLockContext()` is what does not.
  `stash-edge` already contradicted itself here — its own example passes two.

Three tests keep these honest, each confirmed to fail when its property is
violated:

- `scripts/__tests__/skills-supabase-edge-schema-entry.test.mjs` fails if any
  shipped document pairs the adapter's edge entry with a WASM-entry schema.
- `supabase-wasm-config.test-d.ts` pins the config union at the type level.
- `supabase-declared-mode.test.ts` pins the undeclared-column plaintext path
  across insert, update, filter, select and decrypt.

Documentation and tests only — no runtime behaviour changes.

Claude-Session: https://claude.ai/code/session_01UqnsDeKs8N9n9TStPAqzzw
Adversarial verification of the previous commit found four problems in its own
corrections. Three are fixed here; the fourth is pre-existing and noted below.

The worst was a mitigation that does not hold. The bullet claimed `select('*')`
being refused in declared mode means "ciphertext only comes back if you name
the column yourself". The refusal is real and unconditional, but it guards the
wrong door: a read awaited with no `.select()` call at all takes the raw-`*`
branch in `query-builder.ts` and `decryptResults` passes it through on
`!hasSelect`, so every column returns undecrypted — declared or not.
`query-builder.ts:703-720` documents this as long-standing behaviour
deliberately left alone; the docs just did not reflect it. So writes have a
backstop (the domain CHECK, which a NULL still passes) and reads have none.
Corrected in the skill, in the new test's header comment, and in the changeset.

Also: `WasmClientConfig` has three arms, not two — the third is the deprecated
`strategy` alias. Naming it in user docs would advertise what we are removing,
so the fix is to stop counting rather than to document it. And `stash-edge`'s
new exception section said the failure came "from the other direction", which
contradicts the section above it that already establishes assignment is
rejected both ways; it is the same rejection reported one level up, at
`AnyV3Table` rather than at the column.

Not fixed here, worth a follow-up: `e2e/wasm/supabase-declared.test.ts` pairs
`encryptedSupabase` with a schema from `@cipherstash/stack/wasm-inline` — the
exact pairing this PR documents as non-compiling — hidden by `deno test
--no-check`. It is a genuine counterexample, but it is an internal test that
ships to nobody, and at lines 255-284 one table object feeds both a raw
wasm-inline `Encryption` and the adapter, so the fix needs two schema objects
rather than a swapped import. The new guard cannot catch it either: its
extractor reads fenced markdown blocks, so covering `.ts` files is a different
test with its own scope question.

Claude-Session: https://claude.ai/code/session_01UqnsDeKs8N9n9TStPAqzzw
…ead-backstop correction across

Review of #951 found four things. One is a change to a published type; three
are corrections to shipped text, two of them in the copy the previous commit
did not touch.

`EncryptedSupabaseWasmOptions` enforced less than three documents claimed it
did. Leaving `databaseUrl` off the interface is policed by excess-property
checking, which fires on FRESH object literals alone — so an options object
assembled as a `const` and passed by variable, which is what a Node-to-edge
port actually holds, type-checked clean and reached the construction-time
throw in `create.ts` instead. The README, `stash-supabase` and the interface's
own doc comment all said the type checker enforced it. The field is now
declared `databaseUrl?: never`, which is the same gap and the same fix as
`WasmClientConfig.eqlVersion?: never` one package along, whose comment
describes this exact shared-config-const path. The runtime throw stays: it is
the only thing a plain JS caller meets. New type tests cover both the inline
and the by-variable shapes on both call forms, and a positive control asserts
the same options object still compiles once the field is dropped — without it,
a `never` that poisoned the options type would read as four passing
expect-errors.

`578783ad` corrected the `select('*')` claim in `stash-supabase` and left the
same claim standing in `stash-managed-platforms`, which the same PR edited.
That skill still framed declared mode as giving things up "loudly rather than
silently" over a bullet naming the refusal — precisely the inference the
correction exists to kill. Its audience is an agent on Lovable, v0, Bolt or
Replit reading it as instruction, and the failure it mispromised is silent:
`await supabase.from('users').eq('id', 1)` with no `.select()` takes the
raw-`*` branch (`query-builder.ts:703-725`), `decryptResults` returns it
untouched on `!hasSelect` (`query-results.ts:126-138`), and every column comes
back as a raw EQL payload with no error. Shipping a correction to one of two
copies is worse than shipping neither, because the copy left behind now looks
reviewed — so `scripts/__tests__/skills-select-star-not-a-read-backstop.test.mjs`
now fails if any shipped document states the refusal without the caveat in the
same markdown section. It scans per section rather than per sentence, so the
wording can move; it blanks fenced code, so a snippet is not read as a claim;
and it carries a liveness test against the canonical skill, because a decayed
regex is a guard that always passes.

"Everything after construction is the same wrapper" was false in the first way
a reader hits it, in both the README and `stash-supabase`. The edge entry is
always in declared mode, where `select('*')` is refused and `from()` on an
undeclared table throws — and the README said it twenty-five lines under a
paragraph telling the same reader `select('*')` just works, immediately above
the quick-start snippet it tells you to port into an Edge Function. Both
sentences now name the two exceptions.

`stash-managed-platforms` also promised a diagnostic that cannot fire on the
entry its section is about: an ambient `DATABASE_URL` ignored "with a warning
that the declaration is unverified". Both the ambient read (`create.ts:304`)
and the warning (`create.ts:330`) are gated on `introspector`, which is `null`
for the wasm-inline build. Scoped to Node, with the edge case stated. This is
the same correction `stash-supabase` already carried and this file did not.

The changeset gains `@cipherstash/wizard`: `skills/` ships in that tarball too
(`packages/wizard/tsup.config.ts` copies it to `dist/skills`, listed under
`files`), so without the bump the published wizard keeps the old text until an
unrelated change moves its version. The recent skills-editing changesets were
`stash`-only; the skills-adding ones bumped both.

The ambient-warning claim is deliberately left unguarded — "a warning is
logged" has no stable phrasing to key on and "introspector" appears freely in
any section about introspection, so every detector for it either misses
rewordings or fires on unrelated prose. The header comment says so.

Verification: `@cipherstash/stack-supabase` 570 tests + 56 type tests, 0 type
errors; `pnpm run test:scripts` 887 passed (51 files), including both doc
guards; `skill-supabase-apply` 29 passed; `biome check` 0 errors. The new
guard was confirmed red against the unmodified `stash-managed-platforms` and
against a README with the caveat clause removed, before either was fixed.

Claude-Session: https://claude.ai/code/session_01TvoE76yuuLn2Bmdaz8yFYy
…t introspection

The README and the Supabase skill both wrote "introspection needs a direct
Postgres connection, so ... this entry runs on Node only". Introspection is not
what makes the entry Node-only — it binds the native engine, and declaring
schemas does not move that. The stated cause invites the conclusion that
declaring tables makes the root entry edge-capable, which is the wrong turn the
wasm-inline entry exists to prevent. This is the same causal inversion #952
corrected in the package's .d.ts.

Two smaller instances in skills/stash-supabase/SKILL.md go with it: a bare
"Worker" where the surrounding paragraph means any edge runtime, and the
co-cause "and the engine is a native module, so ..." split into its own
sentence so the engine carries the claim alone.

Claude-Session: https://claude.ai/code/session_01133kxi8qXu6UJPiKJDwDt6
…e-claims guard

supabase-runtime-claims.test.mjs shipped with its own instruction: add
packages/stack-supabase/README.md when #951 lands, held off only because this
branch was rewriting the same lines. Both sentences now attribute the Node-only
restriction to the engine, so the README goes on the list, and so does
skills/stash-supabase/SKILL.md — the two copies that reach a customer, one as
the npm package page and one copied into their repo by stash init.

Enrolling the skill needed one sentence changed: "Removing the pg dependency
would unblock Workers, not browsers" left "Workers" unqualified, and its opener
made introspection the thing keeping the entry out of a browser. Neither is
true of the browser, which is blocked by the workspace clientKey (#804)
regardless.

stash-managed-platforms is left off, with the reason recorded in the docblock:
its causal claims are correct, but it trips the unqualified-"Worker" detector in
its frontmatter description, where a reword changes what the skill matches on.

Claude-Session: https://claude.ai/code/session_01133kxi8qXu6UJPiKJDwDt6
@tobyhede
tobyhede force-pushed the docs/supabase-wasm-inline-shipping-docs branch from becdebb to 1a7db49 Compare August 31, 2026 02:41

@freshtonic freshtonic left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving at 1a7db49 — the blocker from my last two reviews is resolved, and better than asked: the README sentence now attributes Node-only to the engine with introspection kept to what it actually explains (the pg peer dep), the skill's step-3 sentence carries the same correct causality, and rather than deferring guard enrolment to a follow-up, this commit adds both the README and the Supabase skill to the #952 runtime-claims guard now — so the false-cause phrasing is mechanically unwritable from here on. CI green. Nice work on this whole family of PRs.

…refuses

stash-managed-platforms told readers to pass databaseUrl "so introspection fills
the gaps" when they cannot guarantee their schemas are complete. That sentence
sits under "## encryptedSupabase in a Worker", four lines above the sentence
saying databaseUrl is refused outright on that entry.

Same fault as the ambient-DATABASE_URL sentence corrected in 981f86a, in the
adjacent paragraph, and it lands on the readers this skill is written for: an
agent on Lovable, v0, Bolt or Replit has only the edge entry, so following the
remedy gets a construction throw. It now says introspection is unavailable
there, that the declaration is the only thing between a schema change and a
silent plaintext write, and that the Node entry is where databaseUrl covers you.

Claude-Session: https://claude.ai/code/session_01133kxi8qXu6UJPiKJDwDt6
@tobyhede
tobyhede requested a review from coderdan August 31, 2026 04:44

@freshtonic freshtonic left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-approved at 4d71746 — the follow-up commit removes another self-contradiction (stash-managed-platforms recommended passing databaseUrl under the Worker heading, four lines from the sentence saying the edge entry refuses it) and keeps the changeset in step. Consistent with everything already approved.

@freshtonic freshtonic mentioned this pull request Aug 31, 2026
@tobyhede
tobyhede merged commit ad059f3 into main Aug 31, 2026
27 checks passed
@tobyhede
tobyhede deleted the docs/supabase-wasm-inline-shipping-docs branch August 31, 2026 23:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

stash-supabase skill and README tell customers Workers are impossible — supported since #912

3 participants