Skip to content

docs(verify): record the #12981 determination that the audience-gate seed is not a durability swallow - #13884

Merged
os-steve merged 2 commits into
mainfrom
claude/issue-12981-batch8-claim-to-persist-determination
Aug 31, 2026
Merged

docs(verify): record the #12981 determination that the audience-gate seed is not a durability swallow#13884
os-steve merged 2 commits into
mainfrom
claude/issue-12981-batch8-claim-to-persist-determination

Conversation

@claude

@claude claude Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Part of #12981 — batch 8. A determination, not a repair.

Batches 1–7 repaired every DARK site that was a repair. This batch answers the one question left, per site:

Are the swallows in plugin-sharing/src/share-link-service.ts and verify/src/harness.ts claims-to-persist at all?

Two different answers, both backed by a reading of what the caller actually sees.

packages/verify/src/harness.ts :: inviteForAudienceGate — NOT a claim-to-persist ⇒ annotated

Recorded in the file, in the shape packages/runtime/src/domains/keys.ts established. The whole diff is that annotation — comment-only, no behaviour change.

Caller-visible evidence, not an adjective:

  1. Nothing claims to have persisted. The helper answers a Promise of void (harness.ts:694) — no return value, no counter, no report out of which any caller could read a landed row.
  2. One caller, and it verifies the row one line later. harness.ts:727 (signUp) is the only call site in the repo. Its very next statement POSTs /auth/sign-up/email — the operation the row is a precondition for. Under the default invite_only posture a missing invitation makes that POST refuse and signUp throws verify signUp failed: ${res.status} ${await res.text()}, carrying the audience gate's real status and body. Under open / email_domain sign-up succeeds on its own merits and the row was never needed. Those are the only two branches — which is what the seam's own "the gate answers either way" means, now stated so it can be checked.
  3. The row is write-only. org_verify_audience_gate / usr_verify_audience_gate appear at exactly two sites in the repo, both of them the write in this function. Nothing reads them back.
  4. The case the catch was written for is "there is nothing to persist into" — an app under verification carrying no sys_invitation object.
  5. Posture. @objectstack/verify boots in-process against in-memory SQLite; the file header: "This is a verification harness — it never touches a real database or production data." There is no deployed plane that could go on looking healthy.

⇒ AGENTS.md's third legal answer. The annotation also records why a FAILURE_PROPAGATION_SITES entry is not the right declaration here: that one asserts every path out of the catch delivers, and this catch returns normally — the delivery is the caller's next call. (It would also go red as STALE today, exactly as keys.ts's annotation records, since DURABILITY_CRITICAL_CALLEES has no insert.)

packages/plugins/plugin-sharing/src/share-link-service.ts :: resolveToken — IS a claim-to-persist ⇒ handed back, not repaired

This reverses batch 6's "probably not / usage telemetry" reading. Per the dispatch's hard boundary, nothing in this file is touched — the tier call belongs to its own repair batch.

Caller-visible evidence:

  • The write is real and lands in normal operation. use_count and last_used_at are declared readonly: true on sys_share_link, and packages/objectql/src/readonly-strict-errors.ts:130 records that { context: { isSystem: true } } "exempts statically 'readonly' fields" — so SYSTEM_CTX (share-link-service.ts:44) is exactly what makes this update legal rather than a silent no-op strip.
  • The public caller is told nothing — both HTTP callers project the link through a 9-field allowlist that excludes both counters (share-link-routes.ts:276-286, runtime/src/domains/share-links.ts:176-186).
  • The admin caller IS told, and told something false. objects/sys-share-link.object.ts:60 puts use_count and last_used_at in the shipped active_links grid; the object header says admins inspect via the audit grid and rows are "preserved for audit". The field declarations state the invariant, shipped in four locales: use_count"Incremented by resolveToken on every successful resolution"; last_used_at"Stamped by resolveToken; used by the dashboard to highlight active links".
  • So after the catch: HTTP 200 with the record, and a grid asserting a count the system's own field declaration defines as the number of successful resolutions — now wrong, with no indication. That is AGENTS.md's durability clause verbatim: "persisted state and runtime state disagree… Nothing looks broken; the loss surfaces a release later."
  • Neither of the two exits applies. The failure does not propagate (the catch returns normally and resolveToken returns its result), and there is a write.
  • Not decoration. These counters are the only trace that an identity-less capability token is being exercised. The tree argues this itself, in the other direction, at share-link-service.test.ts:280-284: a stamp that ticks wrongly is "a bad signal for anyone auditing a leaked link." Measured: use_count is never read for enforcement anywhere — there is no max-uses gate that would fail closed and surface the loss.
  • Not functional, either: AGENTS.md's functional class is "visibly smaller… the next person to use the missing thing finds out." The admin reading the grid does not find out; they get a plausible wrong number.

Census — measured before and after, both readings byte-identical

Instrument: scripts/measure-durability-swallow-family.mjs, self-test green before every reading (4 positive controls at their declared tier, 3 negative yield none, 2 regression controls stay clear, 56 member sites total).

tier before after
MEMBERS 56 sites / 37 files 56 sites / 37 files
[1] DARK 5 / 5 5 / 5
[2] carries-error 24 / 19 24 / 19
[3] channelled 27 / 14 27 / 14
QUIET (adjacent) 98 98

Both readings taken with the instrument's self-test green, and the after-reading re-taken on the final commit a4603a4c4.

⚠️ Unchanged by construction, and that is the honest reading. Census membership is decided on three mechanical conjuncts; a comment is trivia to the AST, so an annotation can never move a site out of DARK. keys.ts demonstrates the same thing — it has carried its determination for rounds and is still listed. The annotation moves a site off the human worklist, not off the instrument's count. Measured, not assumed: the base tree was restored under a trap and re-run, and the two census outputs diff to zero.

Gates

node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack from this worktree, on the actual diff (1 path). Both output sections read whole. 19 families — 9 pnpm, 10 direct node; no .mdx in the diff, so no re-derivation was owed. Exit codes captured before any pipe; every verdict quoted from the gate's own line.

  • Green (17 of 19): check:cross-package-test-inputs · check:doc-authoring · check:logger-receiver-detach · check:page-declaration-shape · check:published-files · check:slot-lookup · check:test-source-alias · check:type-source-resolution · check-ci-filter-parity · check-comment-mask-adoption · check-cross-package-test-inputs · check-keyed-text-bounds · check-plugin-teardown-shape · check-shard-attestation · check-undeclared-dep-imports · docs-audit/check-affected-docs · docs-audit/check-drift-comment. Plus check:nul-bytes ("scanned 7608 text file(s) … no raw ASCII control bytes") for the edit itself.
  • pnpm lint — WHOLE REPO, run in full, no narrowing to declare: eslint . --no-inline-config, command-exit 0, re-run on the final commit a4603a4c4 (60s under the shared verify lock).
  • NOT MEASURED (2):
    • node scripts/check-test-completeness.mjs — exit 3 = PREREQUISITE NOT MET. It grades a saved turbo run test log; CI tees a real one.
    • pnpm check:durability-log-level on this family — measured both directions, byte-identical: "✓ durability-degradation log levels: 29 durability-critical catch seam(s), all loud, rethrowing or propagating to the caller (4 propagating, declared); 2 counter-guarded summary report(s) outside a catch" before and after. Its green is never "level approved", in either direction — and it sees no seam in inviteForAudienceGate at all, since its vocabulary carries no insert.
    • pnpm check:dual-build-cjs-loads — not run locally; it demands a full workspace build, and the diff is comment-only. CI runs it.

Typecheck — a real reading, not a zero-match exit 0. @objectstack/verify does declare typecheck: tsc --noEmit. The first run reported 19 TS2307 Cannot find module '@objectstack/…' — a pure build prerequisite. Cleared exactly the closure it named (turbo run build --filter='@objectstack/verify^...' --concurrency=2, 32 tasks successful) and re-ran on a4603a4c4: 0 errors, with --listFiles confirming harness.ts really is in the program (1 hit).

Tests. The two suites that actually exercise signUpinviteForAudienceGatesrc/erasure-transaction-authorization.test.ts and src/harness.app-default-profile.test.ts — run green: 2 files, 10 tests passed.

Before/after was measured, not asserted. The annotation was committed first (b1aa36a35; final head a4603a4c4) so the restore leg had a real reference. The mutation script carries trap … EXIT INT TERM with absolute paths; mutation and restore were both confirmed on disk by blob hash, never by an editor's exit code — PRE 5dca167b (asserted equal to the HEAD blob), POST c2c448f7 (asserted equal to the base blob and different from PRE; an empty hash treated as FAILURE), marker [#12981] 1 → 0 → 1. Restore proved by state: git diff HEAD 0 lines, git status --porcelain 0 entries, worktree hash back at the HEAD blob.

Ratchet ledgers. scripts/durability-degradation.baseline.json — not in the diff, still empty, zero rows added. engine-double-contract.pinned.json — not in the diff, and unmoved. scripts/slot-lookup-baseline.json — not in the diff; check:slot-lookup reports "106 unswept site(s) in 25 file(s), none new… baseline key set verified against add6a1b: no files added."

Changeset

skip-changeset, applied to this PR as an additive POST and read back. The diff is one comment-only hunk in one file: it publishes nothing, changes no behaviour, and adds no user-visible change for a release note to carry.

Remaining DARK after this batch

auth-manager.ts                → census false member (batch 6 determination) — do not repair
ensure-default-organization.ts → fence lifted (PR #13685 merged); not an outstanding repair
runtime/src/domains/keys.ts    → closed by its own [#12981] annotation; its remaining action
                                 belongs to the programme's LAST step
verify/src/harness.ts          → THIS BATCH: determined NOT a claim-to-persist, annotated
share-link-service.ts          → ⬅ IS a claim-to-persist. The one outstanding repair. Batch 9.

Session: https://claude.ai/code/session_016ZC5rNQj3WEet5HAmmAkMs

Generated by Claude Code


Generated by Claude Code

claude added 2 commits August 31, 2026 15:43
…seed is not a durability swallow

`scripts/measure-durability-swallow-family.mjs` reports
`harness.ts::inviteForAudienceGate` as tier-1 DARK on three mechanical
conjuncts and leaves the fourth -- "and the caller still reports success"
-- to a person. This records that answer in the file, the way
`packages/runtime/src/domains/keys.ts` does, so the next census reading
is legible instead of re-derived.

The answer is NO: the helper returns `Promise<void>` and has exactly one
caller, whose next statement performs the operation the row is a
precondition for -- a lost row makes `signUp` throw with the audience
gate's real status and body. Comment-only; no behaviour changes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ZC5rNQj3WEet5HAmmAkMs
…bracket fragments

Two type/format fragments in the annotation prose were spelled with angle
brackets. The GitHub body sanitizer eats short tag-shaped fragments, even
inside backticks, so an annotation meant to be quoted verbatim into the
card would arrive corrupted. Spelled out instead, with no loss of
precision. Comment-only; no behaviour changes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ZC5rNQj3WEet5HAmmAkMs
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 31, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/verify, touching 1 documentable anchor(s).

1 release-owned page(s) name something this change touched. These are read-only:

  • content/docs/releases/v17.mdx (via bootStack (symbol, a top-level function))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 2 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 9b21037d4198cd14105abd354b019e9323b4fe57packageMentionDocs.

Which tree this was computed on

This run read content/docs from db5498252feef666c382ca7b01911dd6644e665c — the merge of head a4603a4c48ea9ac90578b22db6bd8afa786ac209 into base 9b21037d4198cd14105abd354b019e9323b4fe57, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin db5498252feef666c382ca7b01911dd6644e665c && git checkout db5498252feef666c382ca7b01911dd6644e665c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 9b21037d4198cd14105abd354b019e9323b4fe57 a4603a4c48ea9ac90578b22db6bd8afa786ac209 && git checkout -B drift-repro 9b21037d4198cd14105abd354b019e9323b4fe57 && git merge --no-ff a4603a4c48ea9ac90578b22db6bd8afa786ac209

node scripts/docs-audit/affected-docs.mjs --json 9b21037d4198cd14105abd354b019e9323b4fe57

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 9b21037d4198cd14105abd354b019e9323b4fe57 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@os-steve
os-steve marked this pull request as ready for review August 31, 2026 17:10
@os-steve
os-steve enabled auto-merge August 31, 2026 17:10
@os-steve
os-steve added this pull request to the merge queue Aug 31, 2026
Merged via the queue into main with commit 5887805 Aug 31, 2026
37 checks passed
@os-steve
os-steve deleted the claude/issue-12981-batch8-claim-to-persist-determination branch August 31, 2026 17:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants