Skip to content

Commit 559041d

Browse files
os-billclaude
andauthored
feat(spec): govern connector + analytics_cube in the liveness ledger — PENDING_GOVERNANCE reaches empty (#18609)
Fixes #18582 Clause-②: no `PENDING_GOVERNANCE` reaches **empty**. `connector` and `analytics_cube` — the two debts left on this card after `sharing_rule` was paid by PR #18587 — move into `GOVERNED` with a ledger each, so every authorable metadata type in the denominator #18133 widened now has one. ## What was measured **`connector` — 74 properties: 20 `live`, 1 `planned`, 53 `dead`.** - **Which schema the walker really resolves** (the seat's open question 1): `getMetadataTypeSchema('connector')` returns `DeclarativeConnectorEntrySchema`, and that schema is `ConnectorSchema.superRefine(...)`. In Zod 4 a `superRefine` attaches a check to the **same object def** rather than wrapping it, so `shapeOf()` returns `ConnectorSchema`'s shape unchanged: the walked key set is byte-identical to the base's, tombstones included. **The gate cannot tell the two schemas apart.** What the entry schema buys is refusals, which are invisible to the walk and show up only on the three rows where they are the whole verdict. That difference is recorded in the ledger's `_note` and in the README row. - **One schema, two doors** is the shape fact behind the split. The ledger's denominator entry exists for the AUTHORING doors (`defineStack({ connectors })`, `PUT /meta/connector/:name`), while the same `ConnectorSchema` is what `AutomationEngine.registerConnector` parses for a def a plugin or an ADR-0097 provider factory builds in code. So a key can have a real consumer and still do nothing when a metadata author writes it — every row says which door its consumer is fed from, and every `live` row carries a `producer` (#4837). - The keys an authored entry can reach are exactly the `ConnectorProviderContext` fields plus `name` and `enabled`. `type` and `icon` reach that context and are dropped by **all three** shipped provider factories (`ctx.icon` census: zero reads across `packages/connectors`, with `ctx.label` — four hits — as the lit control). `authentication` is the ledger's one `planned`: refused outright by ADR-0097 §3 (#7990), never ignored. - The 53 `dead` are four declared subsystems with no engine (`syncConfig` 7, `fieldMappings` 7, `retryConfig` 8, `health` 14), `triggers` (6 — the schema's own docblock already said so, #3197), the connector's nested `webhooks`, `status`, `metadata`, both timeouts, `actions.description` / `.outputSchema`, and four `retiredKey` tombstones whose rows stay because the key stays in the walked shape (the `rls.priority` precedent). **`analytics_cube` — 29 properties: 17 `live`, 12 `dead`.** - **An honest `dead` was the outcome on 12 rows** (the seat's open question 2), and none was inflated to green the gate. `cube-registry.ts` names three producers into one registry — authored cubes, compiled datasets (ADR-0021) and ad-hoc query inference — and only the first is the door this ledger governs, so a key whose only reader sits on the compiled-dataset path is not live for an authored cube. That is `dimensions.granularities` (read by `dataset-executor#granularityOf`, whose argument is a `CompiledDataset` an authored cube never becomes) and `measures.format`. - **Whether ADR-0049 wants a retirement is answered per row, and mostly the answer is no** — the ledger says so explicitly so the enforce-or-remove channel does not act on the word `dead`. `granularities` and `measures.format` are the dataset compiler's own output channel on a shared shape: deleting them breaks a live internal write. `joins[].sql` is REQUIRED and documented as the ON clause while the strategies synthesise an FK equality and never consult it — a decision, not a sweep. `public` is an access-control flag that gates nothing (three sites write `false`, nothing reads it): a knob that was never wired, not a hole that was opened. `refreshKey.every` / `.sql` are the only rows where retirement is the obvious shape, and even there the showcase example authors them. - **#10238 is not prejudged.** Whether cube authoring is live end to end remains its own measurement; this ledger answers the per-key question only, and says so in the `_note`. **Two prior in-repo claims were falsified by this measurement and are corrected in the ledgers** (not in their source files — that is out of scope here, and both are filed below): 1. `packages/spec/src/conversions/registry.ts` states `retryConfig` "and the timeouts beside it are untouched — they are live". The word `retryConfig` does not occur anywhere in `packages/` or `examples/` outside `packages/spec`, and every `connectionTimeoutMs` / `requestTimeoutMs` occurrence is a WRITE of the literal 30000 so a def satisfies the post-parse type. 2. `bootstrapDeclaredWebhooks` documents itself as materializing each "stack/connector-authored webhook", while its source is `readDeclared(…, 'webhook')` — metadata items the decomposition registers from the top-level `webhooks:` collection, which a connector's nested array never becomes. ## The gate, red before and green after Both ledgers in place and both types in `GOVERNED`, before the README / counts caught up — `pnpm --filter @objectstack/spec check:liveness`, **exit 1**: ``` ✗ 2 governed type(s) with NO row in the README state table: connector analytics_cube ✗ 1 README state-table heading error(s): heading says 37 governed types, GOVERNED has 39 ✗ the generated count artifact is not current: packages/spec/liveness/state-counts.md is STALE — it does not match what the gate measures right now. first difference at line 67: - | **total** | **878** | **5** | **1** | **96** | **11** | **991** | + | `connector` | 20 | 0 | 0 | 53 | 1 | 74 | ✗ 2 row(s) where README.md and state-counts.md disagree: connector — counted in state-counts.md, no row in the README table analytics_cube — counted in state-counts.md, no row in the README table ✗ 1 UNDECLARED container inheritance — a blanket verdict covers keys nothing classified: connector/webhooks — one verdict covers 21 unclassified child key(s): … ``` That run is also the answer to the seat's warning about `liveness/README.md`: `check-liveness.mts` declares `readmeMissingRows` for exactly this, so the README rows, the heading count and `state-counts.md` are not optional extras — the gate reverse-requires them. After the README rows + heading (37 → 39), `gen:liveness-counts`, and the `connector/webhooks` row in `undrilled-containers.baseline.json` — **exit 0**: ``` governance denominator: 30 authorable type(s) — 26 registered kind(s) + 4 unregistered-kind stack collection(s) (analytics_cube, connector, sharing_rule, webhook); 30 governed, 0 awaiting a ledger. (+ 9 type(s) governed from OUTSIDE the denominator via SPEC_ONLY_SCHEMAS — 39 governed in total.) ✓ every governed-type property, at every depth the ledger drills, is classified, every authorable type — registered kind or unregistered-kind stack collection — is governed or explicitly pending, … and the README state table carries a row for each of the 39 governed type(s) it claims to index. ✓ packages/spec/liveness/state-counts.md is current — the same 39 row(s). ``` `connector/webhooks` is RECORDED in the undrilled baseline rather than deferred or drilled, and the ledger row says why: `WebhookConfigSchema` is `WebhookSchema.extend({ events, signatureAlgorithm })`, so a `deferred` row to the governed `webhook` type would be refused by the gate's key-set EQUALITY check — correctly — and drilling would mean writing 21 child rows of which 8 are the ADR-0010 protection envelope this gate auto-classifies `live` everywhere else. ## Verification | Command | Result | |---|---| | `pnpm --filter @objectstack/spec check:liveness` | exit 0 — `PENDING_GOVERNANCE` empty, 39 governed | | `pnpm --filter @objectstack/spec check:generated` | exit 0 — all 15 generated artifacts up to date | | `pnpm --filter @objectstack/spec check:authorable-surface` | exit 0 — 1536 schemas generated | | `pnpm --filter @objectstack/spec check:api-surface` | exit 0 — public API surface unchanged | | `pnpm --filter @objectstack/spec check:docs` | exit 0 — 223 generated files in sync | | `pnpm --filter @objectstack/spec typecheck` + `check:scripts-typecheck` | exit 0 | | `pnpm --filter @objectstack/spec exec vitest run scripts/liveness/` | 11 files, 315 tests passed | | `pnpm check:platform-checklist` | exit 0 — 38 kinds mapped, 1 waived | | `pnpm check:nul-bytes`, `check:published-files`, `check:merge-driver`, `check:doc-authoring`, the three changeset gates + their self-tests, `check:keyed-text-bounds`, `check:comment-mask-*`, `check:closing-keyword-parity`, `check:pm-*` | exit 0 (22 families) | `packages/spec` has no `lint` script; the repo runs one root `eslint . --no-inline-config`, so the ESLint reading here is a **declared narrowing** with its three pieces of evidence: (1) the universe comes from the config itself — the only config object with a `files` glob for source is `**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}`, and the seven non-`.mts` paths in this diff are `.json` / `.md`, confirmed by running ESLint on `liveness/connector.json` and getting `File ignored because no matching configuration was supplied`; (2) `--format json` on the one file this diff adds to that universe reports **1 file, 0 errors, 0 warnings**; (3) `eslint.config.mjs`'s own header states this repo "never enables type-aware linting (no `parserOptions.project`, no typed `@typescript-eslint` rules) for ANY file", so nothing in this diff can move the verdict on a file it does not touch. The whole-farm run is CI's. Both figures are read at `a442b583fb`. **Widening tells.** `node scripts/pm/check-widening-tells.mjs --declaration no --diff DIFFPATH (this PR's diff)` exits **0 with no tell**, matching PR #18587 — but its own output is the honest reading and it is not "clean": `8 changed file(s) — 0 judged against a declared surface (no widening tell), 8 NOT MEASURED`, because no declared surface covers ledger JSON, a changeset, a checklist map or a gate script. Reported as NOT MEASURED rather than as a pass. ## File surface | Path | Why | |---|---| | `packages/spec/liveness/connector.json` | new ledger (23 top-level rows, 7 drilled containers) | | `packages/spec/liveness/analytics_cube.json` | new ledger (9 top-level rows, 4 drilled containers) | | `packages/spec/scripts/liveness/check-liveness.mts` | both types into `GOVERNED`; `PENDING_GOVERNANCE` emptied; its `[#18582]` note rewritten (it said "two left") | | `packages/spec/liveness/README.md` | from the pre-declared OPEN set — two state-table rows and the heading count 37 → 39, both reverse-required by `readmeMissingRows` / `readmeHeadingErrors`; the closing `PENDING_GOVERNANCE` paragraph rewritten | | `packages/spec/liveness/state-counts.md` | OPEN set — regenerated with `gen:liveness-counts`, never hand-edited | | `packages/spec/scripts/liveness/undrilled-containers.baseline.json` | one recorded row, `connector/webhooks`, reverse-required by the container-coverage leg (see above) | | `docs/qa/platform-checklist/coverage.json` | OPEN set — two entries; the map is keyed by ledger name and `check:platform-checklist` reds on an unmapped kind. Existing key order left as it was | | `.changeset/18582-connector-analytics-cube-liveness-ledgers.md` | OPEN set — `patch`, because `liveness` is in this package's published `files[]`, so both ledgers ship in the tarball | Neither `packages/spec/src/ui/view.zod.ts` (PR #18561) nor `packages/spec/scripts/check-generated.ts` (#17735) is touched. ## Acceptance notes Seen and deliberately not fixed here — three are findings this seat asks the dispatching seat to file, the rest are noted only: - **to file (contract violation; dedupe words: `retryConfig` live claim, connector timeouts, conversions registry comment):** `packages/spec/src/conversions/registry.ts`'s `connector-rate-limit-config-removed` entry asserts `retryConfig` "and the timeouts beside it are untouched — they are live". Measured false; the comment is what a later reader will trust. - **to file (contract violation; dedupe words: `bootstrapDeclaredWebhooks` docblock, connector-authored webhook, sys_webhook source):** the materializer's docblock claims it materializes each "stack/connector-authored webhook"; its source is `readDeclared(…, 'webhook')`, which a connector's nested array never reaches. - **to file (metadata-authoring trap; dedupe words: `analytics_cube` joins sql ON clause, synthesised FK equality, cube join relationship):** `Cube.joins[].sql` is REQUIRED and documented as the join's ON clause, and both strategies synthesise `ON "parent"."seg" = "alias"."id"` without reading it, so a non-FK join condition returns a 200 carrying different arithmetic than the author declared. `joins[].relationship` is the same shape one key over. - **noted, not filed:** `packages/spec/docs/SYNC_ARCHITECTURE.md` still ticks "✅ Monitoring: Health checks, metrics, logging" and "✅ Conflict Resolution: Multiple strategies" at L3, both unbacked on this surface — the `health` and `syncConfig` subtrees are dead. Carrier: the next PR that acts on the `syncConfig` / `health` ADR-0049 decision; that file is the one an author reads before writing either block. - **noted, not filed:** `analytics_cube.public` is an access-control key that gates nothing. Not filed separately because the ledger row IS the record and the remedy is the ADR-0049 decision the `dead` verdict opens. Carrier: the enforce-or-remove sweep that reads this ledger. - **noted, not filed:** `Metric.name` / `Dimension.name` are required inner fields shadowed by their record key, so a disagreement is silently resolved in the key's favour. Carrier: none — no PR and no person is near these files today; recorded here so a later sweep does not have to re-derive it. --- _Generated by [Claude Code](https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 684146b commit 559041d

8 files changed

Lines changed: 620 additions & 31 deletions

File tree

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
"@objectstack/spec": patch
3+
---
4+
5+
`liveness/connector.json` and `liveness/analytics_cube.json` — the last two governance debts the liveness ratchet declared are paid, so `PENDING_GOVERNANCE` is empty and every authorable metadata type now has a ledger (#18582).
6+
7+
The ledgers ship inside this package, so these are the files an upgrading reader greps to learn whether a key they are about to author does anything. Both types are authored through real doors — `defineStack({ connectors })` / `defineStack({ analyticsCubes })` and `PUT /api/v1/meta/{connector,analytics_cube}/:name` — and neither had ever been walked: they were in neither `GOVERNED` nor `PENDING_GOVERNANCE` until #18133 widened the denominator, so their silence read as "nothing to report".
8+
9+
- **`connector` — 74 properties: 20 `live`, 1 `planned`, 53 `dead`.** One schema, two doors: the ledger's entry exists for the AUTHORING doors, while the same `ConnectorSchema` is what `AutomationEngine.registerConnector` parses for a def a plugin or an ADR-0097 provider factory builds in code. The keys an authored entry can actually reach are the `ConnectorProviderContext` fields plus `name` and `enabled`; `type` and `icon` reach that context and are dropped by all three shipped provider factories. The 53 dead are four declared subsystems with no engine — `syncConfig`, `fieldMappings`, `retryConfig`, `health` — plus `triggers` (the schema's own docblock already said so, #3197), the connector's nested `webhooks`, `status`, both timeouts, and four `retiredKey` tombstones. `authentication` is `planned`: refused outright by ADR-0097 §3, never ignored.
10+
- **`analytics_cube` — 29 properties: 17 `live`, 12 `dead`.** The query path is genuinely consumed (`sql` is both the FROM table and the object whose RLS read scope is injected; `measures.type` picks the aggregate; `joins[].name` the joined table). What is not: the caching block (`refreshKey`), the `public` access flag that gates nothing, `joins[].relationship` and the REQUIRED `joins[].sql` — the ON clause is synthesised as a foreign-key equality and an authored one is never consulted — and the inner `name` on each of `measures`/`dimensions`, where the record key is the identity. #10238 (is cube authoring live end to end?) is a separate measurement and is not prejudged here.
11+
- **Two prior in-repo claims were falsified and are corrected in the ledgers.** A comment in `src/conversions/registry.ts` says `retryConfig` "and the timeouts beside it are untouched — they are live"; the word does not occur outside `packages/spec` at all. And `bootstrapDeclaredWebhooks` documents itself as materializing each "stack/connector-authored webhook", while its source is `readDeclared(…, 'webhook')` — metadata items the decomposition registers from the top-level `webhooks:` collection, which a connector's nested array never becomes.
12+
13+
No schema changed and no verdict moved on an existing ledger: `check:liveness` walks two more types and reports the same 583 repo-local evidence paths resolving, with 39 governed types indexed by the README table.
14+
15+
Clause-②: no

‎docs/qa/platform-checklist/coverage.json‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@
1919
"ai.console-ai-surface-gating"
2020
]
2121
},
22+
"analytics_cube": {
23+
"items": [
24+
"dashboards.cube-query"
25+
]
26+
},
2227
"api": {
2328
"items": [
2429
"api-backend.declarative-endpoint-execution",
@@ -50,6 +55,15 @@
5055
"platform-core.docs-portal-render"
5156
]
5257
},
58+
"connector": {
59+
"items": [
60+
"integration-system.connector-declarative-boot",
61+
"integration-system.connector-auth-kind-application",
62+
"integration-system.connector-descriptor-audit",
63+
"integration-system.connector-degraded-recovery",
64+
"automation.connector-dispatch-matrix"
65+
]
66+
},
5367
"crud_endpoints": {
5468
"items": [
5569
"api-backend.rest-crud-config-contract",

‎packages/spec/liveness/README.md‎

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -816,7 +816,7 @@ The governed set is `GOVERNED` at the top of `check-liveness.mts`. To add a type
816816
RecordDetailView had been gating the History tab on it the whole time (#2707).
817817
4. Add the type to `GOVERNED`; confirm the gate is green.
818818

819-
## Current state — 37 governed types (complete registry coverage)
819+
## Current state — 39 governed types (complete registry coverage)
820820

821821
> **This heading is now checked** (#7257). `check:liveness` reconciles the table
822822
> against `GOVERNED` in both directions — a governed type with no row fails, a row
@@ -938,6 +938,8 @@ marker where the Notes cell goes, never a guess at what belongs there.
938938
| route_generation | seeded 2026-09-02 (#14369) — one of the FOUR `RestServerConfig` sub-objects, and the family that made the `SPEC_ONLY_SCHEMAS` boundary explicit: SERVER CONFIGURATION. An author writes `RestServerConfigSchema` (`packages/spec/src/api/rest-server.zod.ts`) as the REST server's construction argument — not a metadata item, not a request body, not a manifest — so no registry has ever held it and no ratchet rooted in one could ask who reads it. Rooted on the four sub-schemas rather than on the whole config on purpose: the walk drilled exactly ONE level when this was rooted (it recurses as of #17424; the rooting stands), so with `RestServerConfigSchema` as the root the sub-objects would BE the drilled level and `metadata.endpoints.schema` / `batch.operations.upsertMany` would have no row of their own, their container's blanket `live` silently covering a dead key — #4956's shape, in the ledger written to end it. `RestApiConfigSchema` (the fifth sub-object, `api`) is deliberately not enrolled: its consumption seam is still validate-only and is the subject of its own card, so a census of it would record a half that is about to move. **What #11984 settled and what it did not**: that PR made `RestServer.normalizeConfig` PARSE and CONSUME these four instead of casting them, so an out-of-enum or out-of-range value is now refused at construction — accept/reject. Executing a declared contract does not give a key a consumer, and this family is that distinction's worked example. Dead 6 = every key it has, and that is the finding: `routes` is parsed, defaulted and normalized into `this.config.routes`, and nothing ever reads it back. `excludeObjects: ['sys_log']` excludes nothing, `nameTransform: 'plural'` still mounts every route under the raw object name, and the per-object `overrides` record (drilled to `enabled` / `basePath` / `operations`) turns nothing on or off. ⚠️ The `overrides` hits in `packages/rest/src` are a REQUEST BODY and a test builder — different keys with the same name. This is the one member of the family with a customer-visible limb: `RestServerConfigSchema`'s own `@example` advertises `routes: { excludeObjects: ['system_log'] }`, so the published prose promises a capability the runtime does not deliver (Prime Directive #10). Fixing that example belongs to whichever enforce-or-remove limb the key lands on — `routes.*` reads as designed-but-never-wired, so enforcing it is real work in route generation that changes the mounted surface, and no dev agent decides that **#14691 RETIRED all six (2026-09-03, ADR-0049)**: every key is now a `retiredKey()` tombstone and the sub-object is tombstones-only; the rows stay `dead` with a REMOVED note (non-strict schema) and the three `overrides.*` child rows collapse into the one `overrides` row. Triage held `overrides` open as an ENFORCE candidate; the measurement closed it as REMOVE because the capability already exists at its proper seat — per-object exposure is the object's own `enable.apiEnabled` / `enable.apiMethods`, enforced by rest-server.ts#enforceApiAccess (404 / 405) — and `basePath` / `nameTransform` would contradict the one deployment-wide data base and Prime Directive #6 (the object name IS the REST path segment). The `@example` limb is corrected in the same change. `evidenceScope` widened to `cross-repo` (#14796) |
939939
| realtime_subscription | seeded 2026-09-04 (#14446) — a TRANSPORT-PROTOCOL surface, the fifth category the `SPEC_ONLY_SCHEMAS` override has had to reach. `SubscriptionSchema` (`packages/spec/src/api/realtime.zod.ts`) is what a client declares to open a realtime subscription: the item type of `RealtimeConfigSchema.subscriptions` and the `Subscription` the generated API reference publishes. Like `query` it is a request surface rather than stored metadata, and like `query` that is exactly why it went unasked — no registry holds it, `RealtimeConfigSchema` is `.passthrough()` so nothing downstream even refuses an unknown key, and the whole vocabulary sat outside the denominator while the reference kept publishing it. Rooted on `SubscriptionSchema` rather than on `RealtimeConfigSchema` for the reason the four `RestServerConfig` sub-objects document one row up: the walk drilled exactly ONE level when this was rooted (it recurses as of #17424; the rooting stands), so with the config as the root `events[].type` and `events[].filters` would inherit a container verdict instead of carrying rows of their own — #4956's shape. **Dead 6 = every key it has, and the CONTAINER is the finding**: nothing outside `packages/spec` imports `SubscriptionSchema`, `SubscriptionEventSchema` or `RealtimeConfigSchema` at all, so no key beneath them can be read (the `manifest.contributes` reasoning). The two keys the card measured are the sharp ones. `events[].type` accepts `RealtimeEventType`, whose four members (`record.created` / `record.updated` / `record.deleted` / `field.changed`) are DISJOINT from what the engine publishes (`DataEventType`'s `data.record.*`, live emitter in `service-knowledge`), so an author who writes the enum's own `record.created` gets a subscription that silently never fires — and the enum is what the API reference shows them. Its direction is settled by the 2026-09-02 triage and quoted verbatim in the row: enforce means REPOINTING THE ENUM, never changing what the runtime publishes. `field.changed` is the same spelling the sibling `DataEventType` REMOVED in 17.0.0 (#4673, PR #4685) for having no producer; it survives here only because this enum was never in a ratchet's denominator. `events[].filters` is `z.unknown().optional()` — the textbook ADR-0049 fourth state, no shape and no reader, failing in the permissive direction (a subscriber who filters receives every event). ⚠️ Three spellings of a realtime subscription exist and only the third is executed: this one, `websocket.zod.ts#EventSubscriptionSchema`, and the plain interface `contracts/realtime-service.ts#RealtimeSubscriptionOptions` that `in-memory-realtime-adapter.ts#matchesSubscription` actually reads. The file note names the same-name-different-shape traps so the next census does not mistake one for a consumer. Zero live |
940940
| sharing_rule | seeded 2026-09-17 (#18582) — the second of the three `PENDING_GOVERNANCE` debts #18133 declared, and the first one PAID (`connector` and `analytics_cube` are still owed on that card). Not a registered kind: it is bound in `UNREGISTERED_KIND_SCHEMAS` (#6245) and reaches the walk through `getMetadataTypeSchema`'s unregistered-kind fallback, so this ledger governs a type `listMetadataTypeSchemaTypes()` still does not enumerate. One shape fact decides every row: the AUTHORING shape is not the ENFORCED shape. ADR-0057 D6 makes the `sys_sharing_rule` row canonical (`object_name` + `criteria_json` + `recipient_type`/`recipient_id` + `access_level`) and `bootstrapDeclaredSharingRules` translates each authored key into it at boot — nothing re-parses `SharingRuleSchema` at enforcement time — so every consumer cited reads a COLUMN and every row carries the `producer` (#4837) that populates it, which is the `seed.env` lesson applied to a whole type rather than to one key. Preview read points ENUMERATED per the #7131 rule and the answer recorded rather than skipped: `registerBuiltinPreviews()` (objectui @dda8f381) registers twenty types and `sharing_rule` is not one of them; what objectui does consume is the whole shape, on the CREATE door only (`AUTHOR_SHAPE_ONLY_TYPES` — the EDIT door is deliberately ungated because a served body carries the `_diagnostics` decoration this `.strict()` schema rejects). The single non-`live` row is `type`, the `SharingRuleType` discriminator: one member, `criteria`, whose only reader is a defensive `=== 'owner'` comparison that is unreachable for every value the schema admits. `planned` on the `action.operation` precedent (a one-member discriminator held `planned` until a runtime half dispatched on it, #15080), and deliberately NOT an enforce-or-remove candidate: the key is required, so removing it would break every authored rule to delete nothing. |
941+
| connector | seeded 2026-09-17 (#18582) — the second of the three `PENDING_GOVERNANCE` debts #18133 declared, paid in the same diff as `analytics_cube`, which empties that map. Not a registered kind: bound in `UNREGISTERED_KIND_SCHEMAS` (#6245) and reached through `getMetadataTypeSchema`'s unregistered-kind fallback. **What the walk actually resolves, measured:** the binding names `DeclarativeConnectorEntrySchema`, and that schema is `ConnectorSchema.superRefine(...)` — in Zod 4 a `superRefine` attaches a check to the same object def rather than wrapping it, so the walked key set is byte-identical to the base's. The gate cannot tell the two schemas apart; what the entry schema buys is REFUSALS, invisible to the walk and visible only in the three rows where they are the whole verdict. **ONE SCHEMA, TWO DOORS** is the shape fact behind the 20/1/53 split: the ledger's denominator entry exists for the AUTHORING doors (`defineStack({ connectors })`, `PUT /meta/connector/:name`), while the same `ConnectorSchema` is what `AutomationEngine.registerConnector` parses for a def a PLUGIN or an ADR-0097 provider factory builds in code — so a key can have a real consumer and still do nothing when a metadata author writes it. The keys an authored entry can reach are exactly the `ConnectorProviderContext` fields plus `name` and `enabled`; `type` and `icon` reach that context and are dropped by all three shipped factories, and each says so on its own row. `authentication` is the ledger's `planned`: refused outright by ADR-0097 §3 (#7990) rather than ignored. The 53 `dead` are four declared subsystems with no engine — `syncConfig` (7), `fieldMappings` (7), `retryConfig` (8), `health` (14, both sub-blocks) — plus `triggers` (6, and the schema's own docblock says so: #3197), the connector's nested `webhooks` (one blanket verdict, recorded in the undrilled baseline), `status`, `metadata`, the two timeouts, `actions.description`/`.outputSchema`, and four `retiredKey` tombstones that keep their rows because the key stays in the walked shape (the `rls.priority` precedent). **A prior in-repo claim was falsified here**: the conversion registry's note that `retryConfig` "and the timeouts beside it are untouched — they are live" is corrected on those rows — the word does not occur outside `packages/spec` at all |
942+
| analytics_cube | seeded 2026-09-17 (#18582) — the third debt, paid in the same diff as `connector`. Not a registered kind either: bound in `UNREGISTERED_KIND_SCHEMAS` by #10194 and reached through the same unregistered-kind fallback. **ONE Cube shape, THREE producers, one registry** is what decides every row: `cube-registry.ts` names them itself — authored cubes (`analyticsCubes[]` / `defineCube()`, threaded by the CLI into `AnalyticsServiceConfig.cubes`), COMPILED DATASETS (ADR-0021, where `dataset-compiler` mints a Cube), and ad-hoc query inference. Only the first is the authoring door governed here, so a key whose only reader sits on the compiled-dataset path is not live for an authored cube however busy that reader is — the #4837 producer rule on a shape with three producers. That is `dimensions.granularities` (read by `dataset-executor#granularityOf`, whose argument is a `CompiledDataset` an authored cube never becomes) and `measures.format` (written by the compiler, threaded to the wire from the DATASET measure instead). The query path is genuinely live: `sql` is the FROM table AND the object whose RLS read scope is injected, `measures.type` picks the aggregate, `measures.sql`/`dimensions.sql` the column, `joins[].name` the joined table. The 12 `dead` are the caching block (`refreshKey.every`/`.sql` — no refresh scheduler exists anywhere), the access-control flag (`public` — three sites write `false`, nothing reads it: a knob that was never wired, not a hole that was opened), `joins[].relationship` and the REQUIRED `joins[].sql` (the ON clause is SYNTHESISED as an FK equality and the authored one is never consulted), the three `description`s, and the inner `name` on each of `measures`/`dimensions`, where the record KEY is the identity. **#10238 is not prejudged**: whether cube authoring is live end to end is still its own measurement — this ledger answers the per-key question only |
941943

942944
The `dead` set across types is the enforce-or-remove worklist (ADR-0049); every
943945
misleading entry carries `authorWarn` so authors hear about it at compile time
@@ -953,14 +955,17 @@ precisely how the gap survived for a year: prose cannot fail a build. Now the ga
953955
compares `GOVERNED` against the denominator in both directions (an ungoverned
954956
authorable type fails; so does a stale pending row whose debt is already paid).
955957

956-
⚠️ **The map is no longer empty, and that is #18133's finding rather than a
957-
regression.** Widening the denominator from the registered kinds to the authorable
958+
⚠️ **The map went from empty to three rows and back to empty, and neither move was
959+
a regression.** Widening the denominator from the registered kinds to the authorable
958960
set (see the ⛔ note under *Source of truth* above) made three types visible that
959961
had been in **neither** `GOVERNED` **nor** `PENDING_GOVERNANCE` — `connector`,
960962
`sharing_rule` and `analytics_cube` — and therefore produced no row in any of the
961-
gate's lists while the report read complete. They are now declared debts with a
962-
reason and an issue number apiece, which is the state this ratchet exists to
963-
produce; the direction of travel is out of that map and into `GOVERNED`, exactly
964-
as it was for the nine. ⛔ Their presence is not a licence to leave them there.
965-
**One of the three is paid**: `sharing_rule` is governed as of #18582, and the map
966-
now holds `connector` and `analytics_cube`.
963+
gate's lists while the report read complete (#18133). Declaring them as debts with a
964+
reason and an issue number apiece is what a ratchet is for; paying them is the
965+
direction of travel, exactly as it was for the nine. **All three are now paid**:
966+
`sharing_rule` (PR #18587), then `connector` and `analytics_cube` together, each with
967+
a ledger beside this file — so `PENDING_GOVERNANCE` is empty again, this time with
968+
nothing hidden behind the emptiness. ⛔ The map stays in the gate: it is how the next
969+
authorable type that arrives ungoverned gets a declared debt instead of silence, and
970+
an entry added to it is a debt with an issue number, never a way to silence the gate
971+
on a type somebody has just registered.

0 commit comments

Comments
 (0)