Skip to content

fix(spec): DeleteMetaItemResponseSchema declares seq and projectionApplied, matching its two sibling verbs - #13208

Queued
os-trump wants to merge 6 commits into
mainfrom
claude/issue-13155-delete-response-parity
Queued

fix(spec): DeleteMetaItemResponseSchema declares seq and projectionApplied, matching its two sibling verbs#13208
os-trump wants to merge 6 commits into
mainfrom
claude/issue-13155-delete-response-parity

Conversation

@os-trump

Copy link
Copy Markdown
Collaborator

Fixes #13155

DeleteMetaItemResponseSchema now declares seq and projectionApplied — the two keys its own branch has always sent, and the two keys both sibling verbs on the same metadata door already declare. This carries the ruling in #5745 (「[#5563 附带裁决] SaveMetaItemResponseSchema 补齐实现实际返回的字段(version / seq / state / projectionApplied)」) across to the third verb: that decision was made for saveMetaItem, extended to publishMetaItem by #7294, and never carried here. Declaration only — zero runtime behaviour changes, and nothing here claims the wire was wrong. The wire is right, the two siblings agree with it, and the third declaration was short.

Premise check, measured on origin/main before any edit

The card's premise — the delete branch's sent set is expressible in the siblings' shapeholds. Readings:

reading result
Sole producer ObjectStackProtocolImplementation.deleteMetaItem (packages/metadata-protocol/src/protocol.ts:18999)
REST door const result = await p.deleteMetaItem(deleteRequest); res.json(result); (rest-server.ts:5941) — verbatim, no route-level mutation, so the protocol return is the wire body
Producer's own return type already declares seq?: number and projectionApplied?: MutationProjectionOutcome (:19013-:19020)
projectionApplied shape interface MutationProjectionOutcome { success: boolean; error?: string } (:3758) — byte-identical to the inline object both siblings declare
seq source DeleteResult { seq: number } (packages/metadata-core/src/types.ts:225) — the same integer sequence the siblings declare

Four success returns, not one — this is the one place the mirror is a shape mirror rather than a member-list copy:

  1. repository path, row deleted (:19260) — seq always; projectionApplied when a projector is registered. The only branch carrying either key.
  2. repository path, no row (:19169) — success/no-op, appends no history event.
  3. legacy raw-engine path, row deleted (:19409) — reachable in control-plane bootstrap for a code-only type (saveMetaItem 的 legacy raw-engine 分支在 #5086 的门之后已不可达 —— 一段 grep 找得到、运行时永不执行的代码 #5264, deliberately alive). Writes no history row and emits no watch event, so no seq even though a row really went away; its receipt message omits the [seq=…] suffix for the same reason.
  4. legacy raw-engine path, no row (:19377) — the same no-op as (2).

So both keys land .optional() here while seq is required on both siblings. Declaring it required would make three of the producer's own returns fail their own contract — the #5563 defect in mirror image. seq absent means "this branch appended no history event", never "nothing was deleted"; reset is the key that answers that.

Two sibling keys are deliberately not declared, because this branch provably never sends them: version (a delete mints no new content hash, so there is no ADR-0008 OCC token to echo) and advisories (the #4463 authoring gate runs on the two write doors by D1 — a delete submits no body for it to judge). Pinned directly, so a later "finish the mirror" cannot add a contract for bytes no producer emits.

What is pinned

  • Spec side (packages/spec/src/api/protocol.test.ts) — the widened keys parse; seq stays an integer and rejects a fractional one; projectionApplied.success is required once the key is present; the three no-seq branches parse green; the unknown-key posture is preserved (version/advisories are still stripped). Two exported module-scope type pins state the card's cost as a compile-time fact: both keys are now reachable from a DeleteMetaItemResponse without an as any. Verified real rather than phantom — tsc -p tsconfig.test.json --listFiles lists this file, and check:test-typecheck compiles the layer.
  • Producer side (packages/objectql/src/delete-meta-response-conformance.test.ts, new) — the sibling gates' exact shape, driving the real protocol against a real ObjectQL engine: nothing is stripped, projectionApplied is carried through when a projector throws, and seq really advances past the save's event rather than merely being present.

Reverse verification (ablation)

The objectqlspec pair is registered unaliased in KNOWN_UNALIASED_TEST_IMPORTS, i.e. it resolves through dist/, so both legs were rebuilt and both were proved on disk.

Attempt 1 was a false green, and is reported because it is the interesting result. Narrowing the schema alone is a bare deletion against the regenerated authorable-surface/api.json, so gen:schema's deletion guard failed the build; the marker never reached dist/, and the suite ran the pre-mutation build and passed 5/5. ablation-dist-preflight --absent/present is what caught it. Recorded as a finding in its own right: the declaration is now guarded by two independent mechanisms, so a future bare deletion of either key cannot land quietly.

Attempt 2 reverted the baseline lines in the same mutation so the build agreed with itself:

injected marker = 1 · removed describe = 0 · baseline seq lines = 0 · baseline proj lines = 0
build exit = 0
✓ marker present in 2 built files -- the ablation is live in the artifact the suite consumes
ABLATED_TEST_EXIT=1 → Tests  4 failed | 1 passed (5)
  AssertionError: expected [ 'seq' ] to deeply equal []
  AssertionError: expected [ 'seq', 'projectionApplied' ] to deeply equal []

The one case that stayed green under ablation is the "no overlay row" no-op, which carries neither key — so nothing is stripped there either way. That is the same property the siblings record about their own conditional keys, and it is why the no-op branch needs the direct seq === undefined assertion it has rather than relying on the strip check.

Restore leg, symmetric: sources restored byte-identical (git diff HEAD empty; blob hashes equal to their HEAD blobs), spec rebuilt, ablation-dist-preflight … --absent✓ marker absent from all 215 built files, suite green again 5/5.

Generated artifacts

Regenerated with the repo's own tooling, never by hand — check:generated --fix on exactly the two it proved stale (gen:docs, gen:strictness-ledger); authorable-surface/api.json was rewritten by gen:schema during the build, which is the expected product. Deltas are minimal and mechanical: two api/DeleteMetaItemResponse:* lines, api/ strictness sites 444 → 445, and nine lines of reference docs. check:api-surface and check:export-origins were already green — the change adds no export.

type-alias-convention.pin.test.ts is not touched (PR #13186 owns it): the widening mints no new pin, because .optional() widens input and output identically — the precedent that file already records for advisories on SaveMetaItemResponseSchema. Confirmed mechanically rather than by reading: check:spec-parsed-alias reports 837 pinned isomorphic, exactly the count that file asserts.

Nothing in packages/client changes and #13023 is not re-opened — PR #13148 binds the type, so it inherits this fix for free.

One in-scope fix inside my own new file: check:objectql-double-limit failed it as a NEW limit-blind find double (the two sibling conformance files predate that gate and sit in its shrink-only baseline). Repaired per the gate's own prescription — the bound is applied after the filter, by presence.

Verification

Run after the final commit, on c2b36e4a3 (current origin/main merged in first — api-surface/export-origins are hot files, and packages/spec moved on the incoming side, so the closure was reinstalled and rebuilt before any of this was read). Exit codes captured before any pipe; each line quotes the gate's own verdict.

check verdict line
check:generated (all 14 spec artifacts) ✓ All 14 generated artifacts are up to date.
pnpm --filter @objectstack/spec typecheck check:test-typecheck: OK — @objectstack/spec's test layer compiles under packages/spec/tsconfig.test.json
pnpm --filter @objectstack/spec test Test Files 443 passed (443) · Tests 11775 passed (11775)
three door conformance suites (objectql) Test Files 3 passed (3) · Tests 28 passed (28)
pnpm --filter @objectstack/client test Test Files 30 passed (30) · Tests 402 passed (402) — the corroborating run; nothing in it should change, and nothing did
pnpm lint (whole repo, eslint . --no-inline-config) exit 0, no output
check:objectql-double-limit OK ObjectQL double limit conformance holds · baseline key set verified against c25bfb3: no files added.
check:engine-double-contract OK — 705 pinned, 134 in the DEBT ledger, 3 exempt.
check:where-matcher 315 matcher(s) discovered, 315 answer the combinator battery correctly or refuse it loudly · none new
check:spec-parsed-alias 1528 bare z.input aliases, 837 pinned isomorphic, 691 paired with an XParsed. OK
check:cross-package-test-inputs OK: 23 package(s) read outside themselves, all declared
check:test-source-alias OK — 72 packages with tests scanned
check:type-check-coverage, check:query-options-erasure, check:doc-authoring, check:merge-driver, check:published-files all exit 0
changeset family (check:empty-changeset, check:changeset-no-major, check:adr-0087-registration, check:changeset-gate-self-tests, check:objectui-changeset, check:pm-half-states) all exit 0; 1 declaring changeset(s) added, no major bump

The gate family was derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (no hand-fed paths — the script takes the changeset from the merge base itself).

One thing is NOT MEASURED and is reported as such rather than as a pass: packages/objectql's typecheck is tsc --noEmit against a tsconfig.json whose exclude carries **/*.test.ts, and the package has no sibling test-layer program or test-typecheck-debt.json. tsc --listFiles returns 0 hits for the new conformance file, so a green objectql typecheck says nothing about it; its types are exercised by the vitest run above and by CI, not by a static program. That is a pre-existing property of the package, not something this PR changes, and it is stated so the green above is not read as covering more than it does.

Tier

The implementation tier was opus under the recorded quota-exhaustion exemption (maintainer 2026-08-13, 「fable 如果用完了,可以用 opus」), with needs:contract-review on this PR as the compensating control.

This PR was generated by Claude Code in session session_01LpRNHxWZgSUgVnFT9mQQo4 (https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4) — carried as prose because a body PATCH normalises the footer link (#12886).


Generated by Claude Code

@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling labels Aug 29, 2026
@github-actions

github-actions Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 8 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/spec/authorable-surface/api.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

16 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json e452ad54238164dd514e04704b3c1df6dd9c11a5.

2 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/spec/authorable-surface/api.json) — pages documenting those are invisible to this run
  • 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 — 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 e452ad54238164dd514e04704b3c1df6dd9c11a5packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json e452ad54238164dd514e04704b3c1df6dd9c11a5

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

claude added 2 commits August 29, 2026 13:16
The `api/` bucket moved on both sides of the merge — main widened the
analytics and automation route response schemas (+4), this branch widened
DeleteMetaItemResponseSchema (+1). The counts file carries a `merge=os-regen`
driver, so the merge left it un-text-merged; this is the regenerated value,
produced by `pnpm --filter @objectstack/spec gen:strictness-ledger`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4
@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Aug 29, 2026
@os-trump os-trump added needs:contract-review and removed documentation Improvements or additions to documentation tests tooling labels Aug 29, 2026 — with Claude
@os-elon
os-elon added this pull request to the merge queue Aug 29, 2026
Any commits made after this event will not be merged.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

3 participants