Skip to content

docs(spec): widen DELIVERY_NOT_ELIGIBLE's ledger gloss to both delivery surfaces - #12089

Merged
os-litant merged 1 commit into
mainfrom
claude/issue-11892-delivery-not-eligible-gloss
Aug 25, 2026
Merged

docs(spec): widen DELIVERY_NOT_ELIGIBLE's ledger gloss to both delivery surfaces#12089
os-litant merged 1 commit into
mainfrom
claude/issue-11892-delivery-not-eligible-gloss

Conversation

@os-litant

@os-litantos-litant commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

Fixes#11892

ERROR_CODE_LEDGER's entry for DELIVERY_NOT_ELIGIBLE glossed the code as "delivery row is in a non-terminal state". The code is now raised on two delivery surfaces, and they refuse opposite halves of the state space — so the old wording was not merely incomplete, it was backwards for half the code's uses: it describes terminal rows as the acceptable ones, while INotificationOutbox.ack refuses exactly those.

Comment only. No code is registered or removed, no wire value changes, and no acceptance or refusal behaviour moves. The reuse itself is the ruled shape (PR #11858's contract-review PASS ruled option B; a second near-synonym code was rejected for the vocabulary sprawl ADR-0112 exists to prevent) — only the comment lagged.

What the gloss now says, and how the refusal set was derived

The card named two surfaces. A repo-wide git grep -rn "DELIVERY_NOT_ELIGIBLE" -- 'packages/**/*.ts' over the real throw sites found six, in two families — the gloss covers the measured set, not the card's reading:

SurfaceRefusalSite
IHttpOutbox.redeliver (HttpRedeliverError)row is not terminal (redeliver wants success/failed/dead)http-outbox.tsassertHttpRedeliverable
producer's RedeliverGuard returned a refusalhttp-outbox.tsassertRedeliverAllowed
RedeliverGuard itself threw — fail-closed, "we could not check" must never read as "allowed"http-outbox.tsassertRedeliverAllowed
terminal re-check at the write missed: a dispatcher tick re-claimed the row mid-callsql-http-outbox.ts, memory-http-outbox.ts
INotificationOutbox.ack (NotificationAckError)row is not in_flight — covers an unclaimed pending row (the ack-as-cancel trap) and an already-terminal onesql-outbox.ts, memory-outbox.ts
compare-and-set read-back shows the claim was lost mid-ack (slow send outran claimTtlMs)sql-outbox.ts

The two guard-based redeliver refusals and the two compare-and-set misses are not state predicates at all, which is the second reason no single-clause gloss covers this code. The entry keeps the neighbouring DELIVERY_NEVER_SENT "Distinct from…" contrast, now pointed both ways.

Premise check against origin/main

The card's premise holds; two details in the dispatch reading did not survive verification, and neither changes the fix:

  • The card cites error-code-ledger.zod.ts:511; the entry is at :513 on f7b25c5 (line drift).
  • The ledger has one gloss for this code, not two. The file's second textual occurrence is the cross-reference insideDELIVERY_NEVER_SENT's gloss on the line above, and both sit in the same @objectstack/service-messaging section — so there is one place to write, not a per-section sweep.

Generated ledger docs: correctly unchanged

content/docs/references/api/error-code-ledger.mdx is an auto-generated artifact, but the generator emits only the code-name list — it never carries the inline glosses (grep for the old wording in the page returns nothing). So this change produces no docs delta, and no generated file belongs in this PR. Proven rather than assumed, after gen:schema supplied the required json-schema tree:

pnpm --filter @objectstack/spec check:docs
✅ 229 generated files in sync with packages/spec

Verification — union re-run on the final commit 285254d

Gate families derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack against the real change set (26 families; broader than the dispatch list, which predated the changeset file). Re-run on the committed HEAD, quoting each gate's own verdict line:

  • check:error-code-casing✓ no unlisted lowercase error codes in 4660 scanned file(s) (ADR-0112).
  • check:dispatcher-error-vocabularyOK — 21 unregistered code-stamping site(s), all classified; 1 awaiting a ledger entry (#8846).
  • @objectstack/spec check:docs✅ 229 generated files in sync with packages/spec
  • @objectstack/spec check:liveness✓ … the README state table carries a row for each of the 31 governed type(s) it claims to index.
  • @objectstack/spec check:empty-state✓ all classified (1 closed, 2 open, 4 output, 9 scope)
  • @objectstack/spec check:strictness-ledger✓ strictness ledger: 60 file(s) across 5 triaged director(ies) …
  • @objectstack/spec typecheckcheck:test-typecheck: OK — @objectstack/spec's test layer compiles …
  • @objectstack/spec ledger suite — Test Files 1 passed (1) · Tests 17 passed (17)

Green earlier in the same tree: check:changeset-gate-self-tests, check:cross-package-test-inputs, check:merge-driver, check:objectui-changeset, check:published-files, check:slot-lookup, check:spec-parsed-alias, check:test-source-alias, check:type-source-resolution, check:variant-docs, check:doc-formula-expressions (✓ … 22 record-scoped formula example(s) across 421 files / 1449 TS blocks judged clean, after building @objectstack/formula and @objectstack/lint, whose absence had made it exit before measuring anything), plus check-adr-0087-registration, check-changeset-no-major, check-ci-filter-parity, check-cross-package-test-inputs, check-empty-changeset, check-plugin-teardown-shape, check-affected-docs, check-drift-comment, release-rehearsal-clone --self-test, check-nul-bytes.

Declared narrowings

  • check-dev-prereqs.mjs was not run to a verdict. Its precondition is a built workspace (it reported 67 of 67 packages missing their dist/ entry point) and it measured nothing. A full pnpm build is a repo-scale run CI owns and performs before this gate.
  • Repo-wide pnpm lint narrowed to the changed files, and the narrowing is measured, not assumed. ESLint's own config declines the changeset (File ignored because no matching configuration was supplied.), so the linted population of this diff is the single .ts file; --format json reports 2 entries / 0 errors, 0 real warnings. The narrowing excludes nothing because this repo runs one eslint.config.mjs that never enables type-aware linting for any file (no parserOptions.project, no typed @typescript-eslint rules — stated in the config with a measured positive control), so a two-file comment diff cannot move the verdict of any untouched file.

Scope

Clause ② is no — prose only; the set of legal metadata and every accept/reject decision is byte-for-byte identical before and after. The diff touches packages/spec/src/**, so it still routes through needs:contract-review before enqueue. Draft, and staying draft.

…ry surfaces
The ERROR_CODE_LEDGER entry glossed the code as "delivery row is in a
non-terminal state" — one refusal on one surface. The code is now raised on
two, and the two refuse OPPOSITE halves of the state space, so the old wording
was not merely incomplete: it was backwards for half the uses, describing
terminal rows as the acceptable ones while INotificationOutbox.ack refuses
exactly those.
The gloss is now stated per surface, and names every refusal family a repo-wide
grep of the code finds, not only the two the card named:
- IHttpOutbox.redeliver (HttpRedeliverError): a row that is NOT terminal;
the producer's RedeliverGuard refusing or itself throwing (fail-closed);
and the terminal re-check at the write missing after a dispatcher tick
re-claimed the row.
- INotificationOutbox.ack (NotificationAckError): a row that is not
in_flight — both an unclaimed pending row and an already-terminal one —
plus SqlNotificationOutbox's compare-and-set read-back showing the claim
was lost mid-ack.
Comment only. No code is registered or removed, no wire value changes, and no
acceptance or refusal behaviour moves. packages/spec publishes
src/**/*.zod.ts, so the corrected gloss ships to consumers; patch changeset.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NDGG54XF5gbTLdQzCtnaVV
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/error-catalog.mdx(via ERROR_CODE_LEDGER (symbol))
  • content/docs/api/error-handling-server.mdx(via ERROR_CODE_LEDGER (symbol))
  • content/docs/kernel/contracts/data-engine.mdx(via ERROR_CODE_LEDGER (symbol))

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

  • content/docs/releases/v17.mdx(via ERROR_CODE_LEDGER (symbol))

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
  • 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 — 126 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 8450eeacb64a0d831b4d569d9bd43dca4c2abc4apackageMentionDocs.

Which tree this was computed on

This run read content/docs from d19a6dd555ea87f6b01628d09f0c1f3d6bf46755 — the merge of head 285254d25a7918a3364cdf11c0a7275ee477fa9b into base 8450eeacb64a0d831b4d569d9bd43dca4c2abc4a, 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 d19a6dd555ea87f6b01628d09f0c1f3d6bf46755 && git checkout d19a6dd555ea87f6b01628d09f0c1f3d6bf46755
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 8450eeacb64a0d831b4d569d9bd43dca4c2abc4a 285254d25a7918a3364cdf11c0a7275ee477fa9b && git checkout -B drift-repro 8450eeacb64a0d831b4d569d9bd43dca4c2abc4a && git merge --no-ff 285254d25a7918a3364cdf11c0a7275ee477fa9b
node scripts/docs-audit/affected-docs.mjs --json 8450eeacb64a0d831b4d569d9bd43dca4c2abc4a

⚠️ 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 8450eeacb64a0d831b4d569d9bd43dca4c2abc4a → pass the list as
args.docs, on the commit named under Which tree this was computed on.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/stooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DELIVERY_NOT_ELIGIBLE's ledger gloss describes only the redeliver refusal, and is now narrower than the code's uses

2 participants

@os-litant@claude