Skip to content

feat(spec): govern connector + analytics_cube in the liveness ledger — PENDING_GOVERNANCE reaches empty - #18609

Merged
os-bill merged 3 commits into
mainfrom
claude/issue-18582-connector-analytics-cube-ledgers
Sep 17, 2026
Merged

os-bill merged 3 commits into
mainfrom
claude/issue-18582-connector-analytics-cube-ledgers

Conversation

@os-bill

@os-bill os-bill commented Sep 17, 2026

Copy link
Copy Markdown
Collaborator

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 (liveness 台账把「消费端存在读取代码」当作 live 的证据,会漏掉「没有任何生产者传值」的死键(Seed.env 即如此) #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 ([security] sys_metadata.metadata is a general cleartext sink: any authored artefact whose schema permits an inline credential lands it there (datasource config.password, connector authentication) #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, Audit: several event/subscription/connector enums are schema-only (declared, no runtime consumer) #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.
  • [finding] defineStack({ analyticsCubes }) is authorable and undocumented — measure whether CubeSchema is live before anyone writes a page for it #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.jsonexit 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

@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tooling labels Sep 17, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️ 4 changed file(s) yielded no anchor (packages/spec/liveness/README.md, packages/spec/liveness/analytics_cube.json, packages/spec/liveness/connector.json, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 4 changed file(s) yielded no anchor (packages/spec/liveness/README.md, packages/spec/liveness/analytics_cube.json, packages/spec/liveness/connector.json, …) — pages documenting those are invisible to this run
  • 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 — 136 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 d93400f42ea69215b3de22bfe3ca057385ff0eeapackageMentionDocs.

@os-bill
os-bill marked this pull request as ready for review September 17, 2026 09:06
@os-bill
os-bill enabled auto-merge September 17, 2026 09:06
@os-bill
os-bill added this pull request to the merge queue Sep 17, 2026
Merged via the queue into main with commit 559041d Sep 17, 2026
37 checks passed
@os-bill
os-bill deleted the claude/issue-18582-connector-analytics-cube-ledgers branch September 17, 2026 09:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

spec/liveness: seed ledgers for the three PENDING_GOVERNANCE debts #18133 declared — connector, sharing_rule, analytics_cube

2 participants