feat(spec): error-code provenance gate + four adjudicated ledger rows - #14091
Conversation
…gate (WIP) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Mciyv38maJ6HYVMiaM26T1
…xtures Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Mciyv38maJ6HYVMiaM26T1
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Mciyv38maJ6HYVMiaM26T1
📓 Docs Drift CheckThis PR changes 1 package(s): 27 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 1 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 128 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 6c82d5a915df19d1dde5ec9ee7f4cbab8d2ab30c && git checkout 6c82d5a915df19d1dde5ec9ee7f4cbab8d2ab30c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin def3b7c43425a5a7b3ea14afaacb50b4e9439491 da672bb561d2e5dba9631eeca4d2a2835a9cbbbf && git checkout -B drift-repro def3b7c43425a5a7b3ea14afaacb50b4e9439491 && git merge --no-ff da672bb561d2e5dba9631eeca4d2a2835a9cbbbf
node scripts/docs-audit/affected-docs.mjs --json def3b7c43425a5a7b3ea14afaacb50b4e9439491
|
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 33472997997 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
|
Queue-failure triage (run 33472997997,
Per the merge-queue triage checklist (branch 2/3): one re-queue now. If the rebuild fails again with the same runtime-timeout signature, the second failure is treated as real and gets root-caused, not re-queued. Generated by Claude Code |
Fixes #13353
Session: https://claude.ai/code/session_01Mciyv38maJ6HYVMiaM26T1 (dev seat; durable attribution — the footer form is create-only).
Clause-②: no— provenance rows change no accept or reject verdict: the registered union is proven byte-identical (248 codes before and after, measured on both trees),ErrorCodeparses exactly what it did, and the ledger's admission rules are untouched. The new exports (PROVENANCE_WAIVERS,ProvenanceWaiverSchema,ProvenanceWaiver) are additive data tables read by the new gate. The path limb (packages/spec/src/**) binds regardless, soneeds:contract-reviewis on this PR and the card.What this delivers — the gate first, the rows as its adjudicated data
Per the triage on #13353 (comment 5478816074, binding): the gate is the deliverable, the rows are the easy half.
1.
check:error-code-provenance(packages/spec/scripts/check-error-code-provenance.ts, wired as a spec package script + onelint.ymlstep): every stamp site of a ledger-REGISTERED code inpackages/non-test source must be listed under the stamping package's own owner key, or carry a recordedPROVENANCE_WAIVERSentry. Scan method is the card's own (three patterns: object-literalcode:stamp,.code =assignment,*_CODEconstant initializer), sharing the source-text idiom and the comment mask ofcheck:dispatcher-error-vocabulary— populations are disjoint by construction (that gate reports codes OUTSIDE the registered union; this one reports registered codes stamped by a non-listing package). Declared blindness is printed on every run and inherits the sibling gate's: constants not named*_CODE, helper/shorthand indirections, templates, class fields. Waivers are held live in three directions:registeredUndermust still list the code; the waived package must still NOT list it; the scan must still find a site for the pair.2. The waiver mechanism lives in the ledger file itself, matching the
STANDARD_SYNONYM_WAIVERSidiom (schema + recorded reason + stale-guard), because the triage's predicted condition held: the FIRST adjudication below proved a genuine door case, and the two ledger-recorded counter-examples (UPDATE_ID_MISMATCH,FLOW_DISABLEDfamily) needed machine-readable records for the gate to be green at all. 8 entries, all live.3. Four adjudicated provenance rows (of the card's five candidates). Union unchanged; each row's comment in the ledger records the wire path.
Per-row adjudication (site read, wire path traced, verdict + reason)
Adjudication order per triage:
service-datasourcefirst, because its verdict decides the gate design. Line numbers are fromorigin/mainat 8dc22d6 (the worktree base).1.
@objectstack/service-datasource/EXTERNAL_IMPORT_ERROR— DOOR CASE, waiver, NO row. Site:external-datasource-service.ts:333,importNameRefusedErrorsetserr.code/err.status. Wire path traced:importObjecthas exactly one door in the tree — rest'sPOST .../external/tables/:remote/import(packages/rest/src/external-datasource-routes.ts, the route's catch), which stampsEXTERNAL_IMPORT_ERRORitself viasendError(res, 400, ...)for EVERYimportObjectthrow and never reads the producer's code;service-datasource/admin-routes.tsdoes not serveimportObjectat all (grep: zero call sites outside rest). The rest module's own header records the decision: the code is registered under rest "because a refused federated import is specific to it". The producer's stamp is the declaration shape (resolveThrownHttpErrorreads it) agreeing with the door by construction — not a second wire emitter. This is exactly theFLOW_DISABLED/UPDATE_ID_MISMATCHclass, so per the triage this verdict forced the waiver mechanism into the gate's design.2.
@objectstack/plugin-webhooks/INVALID_REQUEST— ROW (new owner key). Site:webhook-outbox-plugin.ts:399. Wire path: the plugin mountsPOST /api/v1/webhooks/redeliverDIRECTLY on the raw Hono app (registerAdminRoutes) and writes the refusal envelope itself — the door IS the stamping package, mounted whenever the http-server service is up (a live server; the #8035 shape). Same raw-app plugin-route door the ledger already accepted forUNIQUE_SCOPE_CONFIRMATION_REQUIRED. Sibling refusals on the route are standard-catalog members needing no row.3.
@objectstack/cloud-connection/FORBIDDEN— ROW (existing key). Site:marketplace-install-local-plugin.ts:1722(base-tree 1719; drift is line-only),requireInstallCapabilityanswers 403 byc.jsonon the plugin's own four install/uninstall/reseed/purge routes. Door = producer. The spelling is the #8211-waived synonym: the waiver admits the (code, shadows) pair regardless of package, so the admission gate stays green; the waiver's recorded reason gained one clause naming this row, and the reverse pin inerror-code-ledger.test.tsnow lists four packages.4.
@objectstack/cli/ENVIRONMENT_NOT_FOUND— ROW (new owner key). Site:commands/serve.ts:4798, the unknown-hostname guard's JSON limb answers 404 with the code by the CLI's OWN middleware on a serving host. Door = producer. Second emitter of a code cloud-connection already registers — provenance, not identity, per the ledger header.5.
@objectstack/trigger-api/INVALID_REQUEST— ROW (existing key). Sites:api-trigger.ts:195and:199,handleRequest's malformed-body 400s. Wire path: the same package'splugin.tsserves the returned body verbatim (c.json(out.body, out.status)) on the raw-app hooks route — the handler whose sibling refusals (ENQUEUE_FAILED,INVALID_SIGNATURE) this key already registers. Door = producer.Tree drift since the card's scan (base 881f8d8), triaged the same way:
service-automation/FLOW_INPUT_SCHEMA_INVALID(engine.ts — the #10025 producer has since landed; the runtime row's comment already records "registered HERE, not under the engine's package" — waiver), secondFLOW_DISABLED/FLOW_NO_START_NODEsites inretryExecution(same pairs, covered by the same waivers),@objectstack/spec/ITEM_LOCKED(metadata-protection.zod.tsshared lock evaluators; the protocol layer is the registered emitter — waiver), and@objectstack/types/VALIDATION_FAILED(validation-failure.ts, the #8016 shared constructor; served under the emitting doors' own registrations — waiver).Out of scope, per triage: the
@objectstack/client/UPLOAD_SESSION_EXPIREDclient-synthesised hit. #13353 remains the record of that open ledger-scope question; here it is a waiver entry whose reason states the question is unruled — deliberately NOT a sixth row, and no new card was filed (the triage already routed it: a separate card only if the spec seat wants it answered).Edits beyond the claim's file surface, each ordered by a gate
packages/spec/src/api/error-code-ledger.test.ts— the reverse-direction pin enumerates the packages registeringFORBIDDEN; the adjudicated row adds cloud-connection to that list (admission RULES untouched).packages/spec/src/type-alias-convention.pin.test.ts—check:spec-parsed-alias(ADR-0122) went red on the new schema and its own failure text ordered the fix:Iso865isomorphism pin (input equals infer — four constrained strings, no default/transform), count 835 to 836 with the receipt comment.packages/spec/scripts/check-generated.ts— the reconciliation ledger requires every newcheck:script classified; added as NO_GENERATOR (pure source audit, nothing to regenerate)..github/workflows/lint.yml— flagged loudly per dispatch: one added step (Error-code provenance guard, beside its sibling gate, deliberately in the job with no paths filter). The workflows touch is unavoidable, verified three ways: no aggregation auto-runs new spec package check scripts (each has an explicit step;check:generateddeliberately does NOT run NO_GENERATOR audits), and hosting the repo-wide scan in the spec vitest suite is structurally unsound (turbo hashes per-package inputs, so a cached spec test run stays green on exactly the PR that adds an unlisted stamper elsewhere — thecheck:cross-package-test-inputsfailure class). If enqueue hits token-permission trouble, this file is why.check:generated --fix, only the proved-stale set): api-surface, export-origins, declaration-map, docs references, strictness-ledger counts, authorable-surface/json-schema manifests — the diff is exactly the three new exports plus one countedz.object.Verification (union pass at head da672bb, after the final commit)
pnpm --filter @objectstack/spec check:generated— "All 15 generated artifacts are up to date."pnpm --filter @objectstack/spec check:error-code-provenance— self-test "3 patterns and every waiver direction pinned"; real run "OK — every registered-code stamp site is listed under its own owner key or carries a recorded waiver (8 waiver(s), all live)". Scan population at this head: 1959 files, 300 registered-code stamp sites — 285 listed, 15 waived, 0 violations.pnpm check:dispatcher-error-vocabularyat da672bb — "OK — 52 unregistered code-stamping site(s), all classified; 1 awaiting a ledger entry (spec: register the dispatcher conformance gate's reported error codes in ERROR_CODE_LEDGER (spec half of the #8087 ruling) #8846)" (stays green, as the triage required).pnpm --filter @objectstack/spec typecheckexit 0 (scripts program covers both new files — proven by listFiles, 2 hits);check:test-typecheck"OK" after the pin edit.webhook-outbox-plugin.ts:399; removing theEXTERNAL_IMPORT_ERRORwaiver reddened it naming exactlyexternal-datasource-service.ts:333; both mutations proven on disk by anchored grep counts (8 to 7, 4 to 3), both restores proven by emptygit diff HEADplus blob hash equal to HEAD; restored tree green. Direction observed: red, as predicted.check:error-code-casing,check:cross-package-test-inputs,check-comment-mask-adoption,check-self-test-wired,check-self-test-workflow-commands,check-ci-filter-parity,check:required-contexts,check:pnpm-filter-targets,check:test-source-alias,check:changeset-gate-self-testsplus the four changeset gates individually,check:merge-driver,check:published-files(the script does not ship; the ledger rows do, viasrc/**/*.zod.ts),check:type-check-coverage,check:spec-parsed-alias("836 pinned isomorphic ... OK"), nul-bytes, and the docs/pm families derived bydispatch-gates.mjs.check-test-completenessandcheck:type-check-debtexit 3 = NOT MEASURED by their own declared contract (they need a CI turbo log / a re-measure input) — recorded as such, not as green or red.Changeset: patch for
@objectstack/spec(rows + additive exports ship in the package; the gate script does not). Not breaking, so no ADR-0087 marker is owed —check-adr-0087-registrationgreen.Generated by Claude Code
Generated by Claude Code