Skip to content

docs(spec): describe ack()'s post-#11859 refusal set in the DELIVERY_NOT_ELIGIBLE ledger gloss - #13508

Merged
os-project-manager merged 2 commits into
mainfrom
claude/issue-12283-ack-bullet-refusal-set
Aug 31, 2026
Merged

os-project-manager merged 2 commits into
mainfrom
claude/issue-12283-ack-bullet-refusal-set

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes #12283

⛔ Parked DRAFT on purpose — do not flip ready, do not enqueue, do not arm auto-merge. The diff lands on the contract-face path (packages/spec/src/**), so the clause-② PATH limb fires: needs:contract-review is on both this PR and the card, and the review chain owns enqueue.

What changed

Exactly one bullet of prose in packages/spec/src/api/error-code-ledger.zod.ts — the DELIVERY_NOT_ELIGIBLE → INotificationOutbox.ack gloss — plus its changeset. No schema, shape, or code change; no new error code; no behaviour moves.

The ledger states DELIVERY_NOT_ELIGIBLE's refusal cases per surface, by enumeration. #11859 (PR #12282, ruled option C — the claim credential rides the record claim() returns and ack() takes it back) widened that set on the notification surface, and two statements in the bullet stopped being the whole truth.

Before

After

Premise re-verified against the merged code, not the card

The card's line numbers were stale (it measured at ~:530-536; the bullet sits at :572 on this branch head), so the bullet was re-located and every claim re-checked against the merged #11859 tree:

  • Ownership joined the predicate, and the credential is the pair. INotificationOutbox.ack's interface contract (packages/services/service-messaging/src/outbox.ts) states the precondition as two tests re-stated in the conditional write: the row is in_flight, AND its (claimed_by, claimed_at) pair equals the credential on the record handed back. MemoryNotificationOutbox.ack spells it as r.claimedBy !== claimed.claimedBy || r.claimedAt !== claimed.claimedAt, so a later claim's claimedAt refuses the earlier claim's ack — including the same node's own re-claim.
  • The no-credential refusal is real and separate. notificationAckNoCredentialMessage is raised before any read or write when the record carries no (claimedBy, claimedAt) pair.
  • Both backends, not just SQL. The three refusal messages (notificationAckNotClaimedMessage, notificationAckLostClaimMessage, notificationAckNoCredentialMessage) are defined once in outbox.ts and raised by both memory-outbox.ts and sql-outbox.ts.
  • The pinning test the card names exists and covers both. packages/services/service-messaging/src/outbox-ack-claim-ownership.integration.test.ts runs describe.each([memoryBackend(), sqlBackend()]) over MemoryNotificationOutbox and SqlNotificationOutbox, including the case "the credential is the CLAIM, not the node: a stale ack loses to the same node's own re-claim."

Same class and same route as PR #12089, which repaired the previous iteration of this exact bullet; this PR mirrors its shape (one ledger edit + one changeset, prose only).

Verification

All gates re-run on the final merged head 453ac8d3 (after git merge origin/main, deps refreshed, spec rebuilt), each read from the gate's own printed verdict line — never a bare $? behind a pipe:

  • pnpm --filter @objectstack/spec check:generated — ✓ All 14 generated artifacts are up to date.
  • pnpm check:error-code-casing — ✓ no unlisted lowercase error codes in 5036 scanned file(s) (ADR-0112).
  • pnpm check:dispatcher-error-vocabulary — check-dispatcher-error-vocabulary: OK — 51 unregistered code-stamping site(s), all classified; 1 awaiting a ledger entry (#8846).
  • pnpm --filter @objectstack/spec typecheck — check:test-typecheck: OK (test layer compiles; 54 files / 262 errors held in the shrink-only debt ledger, unchanged by this diff)
  • pnpm --filter @objectstack/spec test — Test Files 443 passed (443) · Tests 11811 passed (11811)

Gate family derived mechanically with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (187 families discovered across 28 workflow files; change set read from the merge base, not a hand-built diff). The remaining derived families are repo-wide scans CI runs in full regardless; the ledger-specific ones are named above and were run here.

Merged origin/main immediately before finalizing, as the ledger is a cross-lane conflict magnet — six commits came in, none touching this file, and the branch's delta vs main is still exactly the two intended files (41 insertions, 7 deletions).


Generated by Claude Code

claude added 2 commits August 30, 2026 14:39
…NOT_ELIGIBLE ledger gloss

The INotificationOutbox.ack bullet enumerated a refusal set that #11859
widened: ownership joined the predicate (the (claimed_by, claimed_at) pair
rides the record claim() returns), and both backends -- not only
SqlNotificationOutbox -- raise the lost-claim refusal.

Comment only. No code registered or removed, no wire value changes, no
acceptance or refusal behaviour moves.

Part of #12283
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation tooling labels Aug 30, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

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

  • content/docs/api/client-sdk.mdx (via ERROR_CODE_LEDGER (symbol))
  • 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 0b9ad00256bfeba4dd78fa10553990cb06088ffc → packageMentionDocs.

Which tree this was computed on

This run read content/docs from b1b2421f296993d2863f232b1df7d8310766d256 — the merge of head 453ac8d3f95b7fc58a0b1d6c3744c7f86778fd7b into base 0b9ad00256bfeba4dd78fa10553990cb06088ffc, 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 b1b2421f296993d2863f232b1df7d8310766d256 && git checkout b1b2421f296993d2863f232b1df7d8310766d256
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 0b9ad00256bfeba4dd78fa10553990cb06088ffc 453ac8d3f95b7fc58a0b1d6c3744c7f86778fd7b && git checkout -B drift-repro 0b9ad00256bfeba4dd78fa10553990cb06088ffc && git merge --no-ff 453ac8d3f95b7fc58a0b1d6c3744c7f86778fd7b

node scripts/docs-audit/affected-docs.mjs --json 0b9ad00256bfeba4dd78fa10553990cb06088ffc

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

os-project-manager commented Aug 30, 2026 •

Copy link
Copy Markdown
Collaborator Author

Docs Drift Check — hand-checked, no drift. No docs edit needed.

The four pages are listed via the broad ERROR_CODE_LEDGER symbol anchor: each cites the ledger symbol generically (1–2 mentions) without restating this bullet's content. Grepped all four for DELIVERY_NOT_ELIGIBLE, NotificationAckError, INotificationOutbox, SqlNotificationOutbox, in_flight and claimTtl:

page matches
content/docs/api/client-sdk.mdx 0
content/docs/api/error-catalog.mdx 0
content/docs/api/error-handling-server.mdx 0
content/docs/kernel/contracts/data-engine.mdx 0

None restates the falsified claim — neither the "not in_flight" formulation nor the "SqlNotificationOutbox only" attribution appears in any of them. So there is nothing to correct, and nothing was edited: the anchor fired on the symbol, not on the prose this PR changes.

content/docs/releases/v17.mdx was read only, never edited (release-owned). It carries no mention of DELIVERY_NOT_ELIGIBLE or NotificationAckError either, so there is no finding to record against it.


Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

Contract review (fable seat, maintainer-directed): PASS.

The rewritten gloss states the post-#11859 refusal set exactly: the ownership predicate with the (claimed_by, claimed_at) credential pair, the no-credential branch, and both backends attributed — with the shared-message non-drift property and the pinning test named. Comment-only, two files, no code or wire movement; the changeset says so honestly.

Releasing per the maintainer's authorization (2026-08-31): marking ready and arming auto-merge; needs:contract-review comes off both carriers.


Generated by Claude Code

@os-project-manager
os-project-manager marked this pull request as ready for review August 31, 2026 00:47
@os-project-manager
os-project-manager added this pull request to the merge queue Aug 31, 2026
Merged via the queue into main with commit ae8edd2 Aug 31, 2026
41 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-12283-ack-bullet-refusal-set branch August 31, 2026 01:11
os-tesla pushed a commit that referenced this pull request Oct 25, 2026
…ectui#12724) (#13508)

<!-- claude-projects-attribution: {"github_login":"os-zhuang"} -->
_Requested by **Sam** · [project
thread](https://claude.ai/code/project/chan_01GsBogozeZG5sdpAARrN7HP?thread=cmsg_01GsBogozeZG5sdpAARrN7HPPdgXnLU8SfcQmac3AzRouL)_

Clause-②: yes (the exported `ProposedPlanCard` gains the optional
`posture`)

Closes #12724

## What

objectstack-ai/cloud declared `planCard.posture` on
`propose_blueprint`'s envelope (cloud#2924 ruling A, cloud PR #3111,
`f0ecef3a`): `{ mode: 'publish' | 'draft', liveRecords, line }`.
objectui drew nothing from it yet. The plan card now draws
`posture.line` (for example 「首次搭建直接上线;之后的修改先存草稿,由你发布」) as the **last
item of the assumptions list**, where the AiBuild artboard draws it. A
plan card without the key draws as before. If a plan has a posture but
no assumptions, the section still renders and holds the posture line
alone.

- `mapMessages.ts`: `ProposedPlanCard.posture?: { mode; line }`, lifted
in `liftPlanCard` only when `mode` is a declared value and `line` is
non-empty. `liveRecords` is already in the line, so it is not lifted.
- `ChatbotEnhanced.tsx`: one `<li data-testid="proposed-plan-posture"
data-posture={mode}>` after the assumptions.
- `fixtures/build-cards.schema.ts`: re-vendored verbatim at cloud
`f0ecef3a`. The only change from the `f7ab9cc8` copy is
`planCard.posture`, and cloud `main` `36b07fdf` has the same schema byte
for byte.
- `buildCards.fixtures.ts`: `planEnvelope(lang, { posture: 'firstBuild'
| 'draft' })`, built with cloud's own `postureFirstBuild` /
`postureIntoLiveApp` wording.
- The docs paragraph in `content/docs/plugins/plugin-chatbot.mdx`, and a
changeset (`@object-ui/plugin-chatbot` minor).

## Why this closes #12724

The card's two remaining artboard rows have no producer key at cloud
`main` `36b07fdf`, and no cloud card plans one:
- the plan card's 「确认后约 1 分钟搭好」 and 「也可以直接回复:…」
- the result card's subtitle 「应用已进启动台 · 包 … · 以下每一条都按方案核对过」

The maintainer ruled in the project thread on 2026-10-25 to drop both
from the artboard rather than wait for cloud. This posture row was the
last declared key still undrawn.

## Tests

- `pnpm exec vitest run packages/plugin-chatbot/`: 83 files, 795 tests,
all passed, including the new
`ChatbotEnhanced.planCardPosture-12724.test.tsx`. It covers zh and en
first-build lines, the draft line, posture without assumptions, and two
controls: no key, and an undeclared mode.
- `buildCards.contract-12724.test.ts` checks both new fixture payloads
against the vendored schema.
- `node scripts/check-changeset-presence.mjs` ✅. `pnpm
check:new-line-citations`: 0 new.

- [x] Adds no product-specific widget, dialog, hook or string (see
AGENTS.md › Product-neutral renderer)

---
_Generated by [Claude
Code](https://claude.ai/code/session_01R3ARF6ZEyBWW8EBT9we3eX)_

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants