Repository navigation
spec(lint): honour the datasource runtime-create declaration at the publish door — the reading says group C, dispatched and silent - #19596
Conversation
… publish door `DEFAULT_METADATA_TYPE_REGISTRY` declares `datasource` with `allowRuntimeCreate: true`, and no rule named it in `runtimeTypes`, so a runtime datasource write built no snapshot and dispatched no rule at all. The type was outside the ten the ADR-0049 ruling graded, so its group is measured here rather than inherited. Retirement is refuted (a real stack collection exists to create into, and the ADR-0015 Addendum create path is live). The `skill` hold-out is refuted too: its bridge resolved into two collections the door does not carry, while this one judges each written item's own keys against that type's liveness ledger and resolves into nothing. So the honest group is the ledger-driven one: `lintLivenessProperties` gains `datasource` beside `email_template` / `mapping`, with the matching `TYPE_TO_STACK_KEY` row. The shipped datasource ledger carries 0 warn keys, so the rule is dispatched and silent today — pinned as such, with a lit control, and under the same no-ledger-population fence. Claude-Session: https://claude.ai/code/session_01AmH9bKvGoLjiY86Q4Z3og2 Co-authored-by: Claude <noreply@anthropic.com>
…red, and add the changeset The registry entry is the one place an author meets `allowRuntimeCreate: true` for this type, and it said nothing about which door judges such a write. It now names the rule, states that the rule is dispatched and silent by ledger, and records that the multi-line entry shape is what hid the type from a line-wise census. Claude-Session: https://claude.ai/code/session_01AmH9bKvGoLjiY86Q4Z3og2 Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 2 package(s): 5 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 136 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 94e91bc14065d883fad13c9799c0a265dfc77f4f && git checkout 94e91bc14065d883fad13c9799c0a265dfc77f4f
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 1f69917c5c76c065256a9bbaac3afc587279b9ba a2596caebf45a9c3f8e6713fc04188c7c6dada75 && git checkout -B drift-repro 1f69917c5c76c065256a9bbaac3afc587279b9ba && git merge --no-ff a2596caebf45a9c3f8e6713fc04188c7c6dada75
node scripts/docs-audit/affected-docs.mjs --json 1f69917c5c76c065256a9bbaac3afc587279b9ba
|
Contract reviewServed-tier: ① Derived judgmentsRe-taken in my own worktree at the head, exit codes captured before any pipe. ⛔ Nothing below is adopted from the 1 · The census — a different instrument, not a re-run of the dev's. I read
Exact on every leg. At the head the undeclared set is 5. 2 · 「Same value at both doors」 — driven through the REAL gate, not through the seam. The PR pins this with Door and whole stack returned the identical two findings, non-vacuously: Ledger restored; Why it holds structurally — which matters more than the fixture: 3 · The stack-key proof — reproduced, then pushed past where the dev stopped. Baseline green: 45 cases across the two files.
Each: anchor asserted unique before writing, on-disk blob hash change proven, restore proven byte-identical to 4 · The silence, and its control. And the claim the report only asserted — 「the day a property earns a row the door lights up with no second edit」 — is confirmed behaviourally by leg 2: two rows added to the shipped ledger, two advisories at the door, zero lines of code changed. 5 · Blast radius — run, not skipped. 6 · Gates. Green here: ② Semver level
The contract-surface question, answered mechanically rather than by reading the diff. I printed the Can this refuse a write that previously passed? No, and I tried to make it. The rule is ③ Boundary flagsOn the arm choice — the central question — the chain holds, and I could not break it. Group D is refuted on its own criterion: So: 「dispatched and silent」 is an honest discharge here, not an unenforced declaration in a new coat — but the reason is narrower than 「it matches group C」, and it is worth stating in the terms that would distinguish the two. What makes it honest is that the circuit is live and load-bearing, and that is now measured rather than asserted: adding two rows to the shipped ledger lit two advisories at the real door with no code change. An unenforced declaration in a new coat would not have lit. The switch is off; the wiring is not decorative. Flags, none blocking:
What I tried that did NOT break it: the four ablations above; the door-vs-stack comparison through the real gate on a lit shipped ledger, with a dotted child path and every omitted collection populated; three malformed bodies against the throw-catcher; a caller-supplied Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #19568
Clause-②: yes
The reading, first — and it is neither of the two arms the card offered
The card asks which group
datasourcebelongs to: A (wire the rule) or D (retire the declaration). Measured against the ruling's own group criteria, it is neither — it is group C, the ledger-driven arm, which that same ruling folds into group A's card. Both offered arms are refuted, and the second refutation is the one that decided the shape of this PR.Group D is refuted by its own criterion. That arm is for a declaration where 「no stack collection exists to create into, so the declaration is a promise nothing can keep」.
ObjectStackDefinitionSchema.datasourcesis a first-class stack collection (packages/spec/src/stack.zod.ts:275), the type has a live runtime create path (ADR-0015 Addendum,origin: 'runtime'), andRUNTIME_CREATE_ALLOWED_TYPESinpackages/metadata-protocol/src/protocol.tsis derived straight from the registry entry, soPUT /api/v1/meta/datasource/NAMEreally does mint one. Retiring the flag would withdraw a capability the platform ships.The
skillhold-out is refuted, and this is the precedent the dispatch asked me to test against.skillstayed out of the sibling landing #19542 becausevalidateAiToolReferencesreads a skill's tool references againststack.toolsandstack.actions— collections the runtime door's snapshot does not carry — so the door produced that rule'sunresolvedfinding while the same rule over the whole stack produced none. Wiring it would have shipped a false advisory into Studio; the hold-out is written up on card #19527.datasource's only candidate rule reaches into no collection at all:lintLivenessPropertiesjudges each written item's own top-level keys against that type's liveness ledger, so the door's verdict and the whole-stack verdict are the same value by construction. That is asserted in this PR as a comparison, not argued: the same written datasource judged once in the shape a per-write snapshot has and once with every collection the door omits present and populated, with an anti-vacuity leg so two empty lists cannot pass for agreement.⇒ wiring is honest here, and it lands as the
email_template/mappingshape: dispatched and silent.Evidence, re-measured on
origin/main1f69917c5c(after PR #19517 landed), not adopted from the cardallowRuntimeCreate: truedatasource, atmetadata-plugin.zod.ts:930. The card's mechanism holds at this commit.allowRuntimeCreate: truetypes named by no liveruntimeTypesdeclaration inpackages/lint/srcdatasource,external_catalog,translation,doc,tool,skillobject: 12zzznotatype: 0datasourceanywhere inpackages/lint/srclint-liveness-properties.ts:502, the row{ type: 'datasource', key: 'datasources' }, carried since #4487packages/spec/liveness/datasource.jsonauthorWarnrows (the three textual hits are prose inside notes)object.jsonwarns onexternalSharingModelOf the six,
datasourcewas the only one with no carrier: the others are the ruling's group B readings, its group D retirement, and the #19527 hold-out.What landed
lintLivenessPropertiesdeclaresdatasourceinruntimeTypes, besideemail_templateandmapping.TYPE_TO_STACK_KEYgainsdatasource: 'datasources'— ⛔ not a mapping ahead of its rule: that rule has readstack.datasourcessince datasource 是注册的 metadata type,却不在 liveness 账本的 GOVERNED 里 —— 两个 PR 手工挖出 6 个死键,正是因为没有闸门看着它 #4487.runtime-gate.datasource-writes.test.tscarries the reading in executable form.allowRuntimeCreate: trueis honoured, that the honouring rule is silent by ledger, and that the entry's multi-line shape is what hid it from a line-wise census.One place this proof is LARGER than its two siblings'. Group C's stack keys rest on a single string assertion, because with an empty warn map no behavioural case can tell
'mappings'from a'mapping'typo. This file closes that gap for its own row: it drives the real rule through its ledger-directory seam over a stack built atstackKeyForType('datasource')itself, with the wrong-key leg asserted beside it. Ablated (ablation-replace, on-disk blob change proven, restore proven byte-identical to HEAD):runtimeTypesloses'datasource'datasource: 'datasources'becomesdatasource: 'datasource'What this does NOT reach, stated rather than left to be discovered
The Setup wizard's own route —
POST /api/v1/datasources— persists throughDatasourceAdminService.createDatasource, which writes viametadata.registerplus a directsys_metadatarow, and never reachessaveMetaItem. So this crossing honours the/metadoor (REST, MCP, an AI author), not the wizard's dedicated route. The two doors enforce disjoint check sets today; that asymmetry is real, is outside this card's file surface, and is written up under Acceptance notes rather than repaired here.Verification
pnpm --filter @objectstack/lint test— 108 files, 4079 passed, 5 skipped.pnpm --filter @objectstack/lint typecheck— green, test layer included.pnpm --filter @objectstack/spec test— 509 files, 14895 passed.pnpm --filter @objectstack/metadata-protocol test(185 passed, 3 skipped),pnpm --filter @objectstack/objectql test(303 files, 5050 passed),pnpm --filter @objectstack/rest test(194 files, 3254 passed, 1 skipped) — the packages that drivesaveMetaItemand declare no dependency on@objectstack/lint.pnpm --filter @objectstack/spec build && pnpm --filter @objectstack/spec check:generated— all 15 generated artifacts up to date; the registry comment moved none.pnpm lint(repo-wide,eslint . --no-inline-config) — exit 0.scripts/pm/dispatch-gates.mjs, reconciled with--ran): 86 derived, 83 run green, 3 NOT MEASURED, 0 unrun. The three arecheck:dual-build-cjs-loads,check:i18nandcheck:type-check-debt, each exiting 3 — PREREQUISITE NOT MET, a whole-repo build this run did not have. ⛔ Not failures and ⛔ not passes; CI builds first and runs all three. All measured ata2596caebf.Acceptance notes
datasourceenforce disjoint check sets.assertDatasourcePoolSupported's own docblock says it is 「Called at every door apoolblock can come in through — the Setup wizard's create/update, the boot-time auto-connect pre-pass, and the driver factory itself」, and the/metawrite door is a door it does not list: a datasource minted there is Zod-parsed and gated by the authoring rules, but never seesassertValidConfig,assertDatasourcePoolSupportedor the code-origin collision refusal thatcreateDatasourceperforms before persisting. The record lands insys_metadataand is met at connect time instead, which is the outcomecreateDatasource's own comment says it exists to prevent. Outside this card's file surface (packages/services/service-datasource,packages/metadata-protocol) and a different defect class, so it is reported for filing rather than repaired here.docandexternal_catalogbecause 「no stack collection exists to create into」. At1f69917c5cthere is noexternalCatalogscollection, so that holds forexternal_catalog— butObjectStackDefinitionSchema.docsexists (stack.zod.ts:431). Whoever takes the group D retirement should re-read the premise fordocbefore flipping the flag. Carrier: the group D retirement card. Noted, not filed.lintLivenessPropertiesre-reads the ledger directory on every call —resolveLivenessDir()plus onereadFileSyncper governed type, memoised only within a call, and the gate runs its rules twice per write. Bounded today to the three types that dispatch it, so it is an observation, not a card.Generated by Claude Code