Skip to content

Commit f77b806

Browse files
os-steveclaude
andauthored
spec(lint): honour the datasource runtime-create declaration at the publish door — the reading says group C, dispatched and silent (#19596)
Fixes #19568 Clause-②: yes ## The reading, first — and it is neither of the two arms the card offered The card asks which group `datasource` belongs 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.datasources` is 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'`), and `RUNTIME_CREATE_ALLOWED_TYPES` in `packages/metadata-protocol/src/protocol.ts` is derived straight from the registry entry, so `PUT /api/v1/meta/datasource/NAME` really does mint one. Retiring the flag would withdraw a capability the platform ships. **The `skill` hold-out is refuted, and this is the precedent the dispatch asked me to test against.** `skill` stayed out of the sibling landing #19542 because `validateAiToolReferences` reads a skill's tool references against `stack.tools` and `stack.actions` — collections the runtime door's snapshot does not carry — so the door produced that rule's `unresolved` finding 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: `lintLivenessProperties` judges 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` / `mapping` shape: dispatched and silent.** ## Evidence, re-measured on `origin/main` `1f69917c5c` (after PR #19517 landed), not adopted from the card | leg | reading | | --- | --- | | registry entries parsed with a multi-line-aware scanner | 27 entries · **22** carry `allowRuntimeCreate: true` | | of those 22, entries written MULTI-LINE | **1** — `datasource`, at `metadata-plugin.zod.ts:930`. The card's mechanism holds at this commit. | | `allowRuntimeCreate: true` types named by **no** live `runtimeTypes` declaration in `packages/lint/src` | **6** — `datasource`, `external_catalog`, `translation`, `doc`, `tool`, `skill` | | lit control, same scan | declarations naming `object`: **12** | | dark control, same scan | declarations naming `zzznotatype`: **0** | | candidate rules naming `datasource` anywhere in `packages/lint/src` | **1** — `lint-liveness-properties.ts:502`, the row `{ type: 'datasource', key: 'datasources' }`, carried since #4487 | | `packages/spec/liveness/datasource.json` | 12 props, **0** `authorWarn` rows (the three textual hits are prose inside notes) | | lit control, same instrument | `object.json` warns on `externalSharingModel` | Of the six, `datasource` was 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 - `lintLivenessProperties` declares `datasource` in `runtimeTypes`, beside `email_template` and `mapping`. - `TYPE_TO_STACK_KEY` gains `datasource: 'datasources'` — ⛔ not a mapping ahead of its rule: that rule has read `stack.datasources` since #4487. - `runtime-gate.datasource-writes.test.ts` carries the reading in executable form. - The registry entry itself now says where its `allowRuntimeCreate: true` is 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. **⚠️ Dispatched and silent, on purpose.** With 0 warn keys no datasource document can be advised at this door today, so the behavioural acceptance the sibling card used — a real write refused, a good write passing — is **not available for this type**, exactly as it was not for group C. The same fence applies: the wiring is the whole deliverable and ⛔ no ledger-population work rides with it. The silence is pinned beside a lit control on the same instrument in the same process, so it can never be read as a broken dispatch or an unresolvable ledger directory. **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 at `stackKeyForType('datasource')` itself, with the wrong-key leg asserted beside it. Ablated (`ablation-replace`, on-disk blob change proven, restore proven byte-identical to HEAD): | mutation | result | | --- | --- | | `runtimeTypes` loses `'datasource'` | 3 red — the dispatch pin, the door pin, the roster pin | | `datasource: 'datasources'` becomes `datasource: 'datasource'` | 2 red — including the behavioural stack-key case, which is what group C could not manage | ## What this does NOT reach, stated rather than left to be discovered The Setup wizard's own route — `POST /api/v1/datasources` — persists through `DatasourceAdminService.createDatasource`, which writes via `metadata.register` plus a direct `sys_metadata` row, and never reaches `saveMetaItem`. So this crossing honours the `/meta` door (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. - **The publish-gate blast radius, not the rule registry's package graph**: `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 drive `saveMetaItem` and 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. - Derived gate families (`scripts/pm/dispatch-gates.mjs`, reconciled with `--ran`): **86 derived, 83 run green, 3 NOT MEASURED, 0 unrun**. The three are `check:dual-build-cjs-loads`, `check:i18n` and `check: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 at `a2596caebf`. ## Acceptance notes - **The two runtime-create doors for `datasource` enforce disjoint check sets.** `assertDatasourcePoolSupported`'s own docblock says it is 「Called at every door a `pool` block can come in through — the Setup wizard's create/update, the boot-time auto-connect pre-pass, and the driver factory itself」, and the `/meta` write door is a door it does not list: a datasource minted there is Zod-parsed and gated by the authoring rules, but never sees `assertValidConfig`, `assertDatasourcePoolSupported` or the code-origin collision refusal that `createDatasource` performs before persisting. The record lands in `sys_metadata` and is met at connect time instead, which is the outcome `createDatasource`'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. - **Group D's criterion is measurably false for one of its two members.** The ruling retires `doc` and `external_catalog` because 「no stack collection exists to create into」. At `1f69917c5c` there is no `externalCatalogs` collection, so that holds for `external_catalog` — but `ObjectStackDefinitionSchema.docs` exists (`stack.zod.ts:431`). Whoever takes the group D retirement should re-read the premise for `doc` before flipping the flag. Carrier: the group D retirement card. Noted, not filed. - **`lintLivenessProperties` re-reads the ledger directory on every call** — `resolveLivenessDir()` plus one `readFileSync` per 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](https://claude.ai/code/session_01AmH9bKvGoLjiY86Q4Z3og2)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 0e658fb commit f77b806

6 files changed

Lines changed: 457 additions & 3 deletions

File tree

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
"@objectstack/lint": minor
3+
---
4+
5+
The runtime publish gate now judges `datasource` writes: `lintLivenessProperties` declares the type in `runtimeTypes` and `TYPE_TO_STACK_KEY` maps it onto `datasources`, so a datasource minted through Studio, REST `/meta` or an MCP/AI author reaches an authoring rule for the first time.
6+
7+
Clause-②: yes (widening) — one metadata type joins an existing rule's declared roster. No schema key, export or closed-set member is added, nothing previously admitted is refused, and no authored document changes meaning.
8+
9+
`DEFAULT_METADATA_TYPE_REGISTRY` has declared `datasource` with `allowRuntimeCreate: true` since ADR-0015's Addendum, and no rule named it in `runtimeTypes` — so the gate filtered a datasource write out before it consulted the stack-key table, and the write built no snapshot and ran no rule at all. It is the ADR-0049 declared-not-enforced shape, missed by the census that graded ten sibling types because its registry entry is the only multi-line one and a single-line reader of the registry cannot see it.
10+
11+
- **The group was measured, not inherited**, because this type sat outside the ten the ruling graded. **Retirement is refuted**: that arm is for a declaration with no stack collection to create into, and `datasources` is a first-class collection with a live runtime create path. **The `skill` hold-out is refuted too**, which is the half that decided it — `skill` stayed out because its bridge resolves references into `stack.tools` / `stack.actions`, collections the door's snapshot does not carry, so the door reached a verdict the whole stack does not share. This rule resolves into nothing: it judges each written item's own top-level keys against that type's liveness ledger, and the door's verdict and the whole-stack verdict are pinned as the identical value.
12+
- **⚠️ Dispatched and silent, on purpose.** `packages/spec/liveness/datasource.json` carries 0 warn keys, so no datasource document can be advised at this door today — the `email_template` / `mapping` end state exactly, under the same fence: the wiring is the whole deliverable and ⛔ no ledger-population work rides with it. The day a datasource property earns an `authorWarn` row the door lights up with no second edit. The silence is pinned beside a lit control on the same instrument in the same process, so it can never be read as a broken dispatch or an unresolvable ledger directory.
13+
- **The stack key is proved behaviourally**, which its two ledger-driven siblings could not manage: `runtime-gate.datasource-writes.test.ts` drives the real rule through its ledger-directory seam over a stack built at `stackKeyForType('datasource')` itself, with the wrong-key leg asserted beside it, so the `seed: 'data'` failure shape — a mapping onto a collection nothing reads — reds a case here rather than riding on one string assertion.
14+
- **Nothing else widened.** A datasource write reaches this one rule and no reference-integrity member; `translation`, `tool`, `doc`, `external_catalog` and `skill` keep their empty rosters, each awaiting its own reading or retirement.

‎packages/lint/src/authoring-rules.ts‎

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1414,8 +1414,30 @@ export const AUTHORING_RULES: readonly AuthoringRule[] = [
14141414
// the whole deliverable»). `runtime-gate.inert-type-writes.test.ts` pins
14151415
// both halves — that the rule is dispatched, and that it is silent — so
14161416
// the day a ledger row lands the door lights up with no second edit here.
1417+
//
1418+
// [#19568] `datasource` joins them, on a reading rather than by
1419+
// inheritance: it was outside the ten types the ADR-0049 ruling graded, so
1420+
// its group had to be measured. Both arms the card offered were tested
1421+
// against the ruling's own criteria. RETIREMENT is refuted — that arm is
1422+
// for a declaration 「no stack collection exists to create into」, and
1423+
// `ObjectStackDefinitionSchema.datasources` is a first-class collection a
1424+
// Studio/REST/MCP author really mints into (ADR-0015 Addendum,
1425+
// `origin: 'runtime'`). The `skill` HOLD-OUT is refuted too, and that is
1426+
// the one worth stating: `skill` was held back because
1427+
// `validateAiToolReferences` resolves into `stack.tools` / `stack.actions`
1428+
// — collections the door's snapshot does not carry — so the door's verdict
1429+
// differed from the whole-stack verdict and wiring it would have shipped a
1430+
// FALSE advisory into Studio. This rule resolves into nothing: it judges
1431+
// each written item's own top-level keys against its type's ledger, so the
1432+
// door's verdict and the whole-stack verdict are the same value by
1433+
// construction (pinned as a door-vs-stack comparison, not as an argument).
1434+
//
1435+
// ⚠️ MEASURED, same instrument, same run as the two above:
1436+
// `packages/spec/liveness/datasource.json` is 12 props / 0 warn keys, so
1437+
// this third type is dispatched and silent exactly as they are. The fence
1438+
// is inherited with the shape: ⛔ no ledger-population work, zero pull.
14171439
surfaces: CLI_AND_RUNTIME,
1418-
runtimeTypes: ['email_template', 'mapping'],
1440+
runtimeTypes: ['email_template', 'mapping', 'datasource'],
14191441
run: (stack) =>
14201442
lintLivenessProperties(stack).map((f) => ({
14211443
severity: 'warning' as const,

0 commit comments

Comments
 (0)