feat(spec): govern connector + analytics_cube in the liveness ledger — PENDING_GOVERNANCE reaches empty - #18609
Merged
os-bill merged 3 commits intoSep 17, 2026
Conversation
Claude-Session: https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3 Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3 Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3 Co-authored-by: Claude <noreply@anthropic.com>
Contributor
📓 Docs Drift Check
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): |
This was referenced Sep 17, 2026
os-bill
marked this pull request as ready for review
September 17, 2026 09:06
os-bill
enabled auto-merge
September 17, 2026 09:06
This was referenced Sep 17, 2026
os-bill
deleted the
claude/issue-18582-connector-analytics-cube-ledgers
branch
September 17, 2026 09:27
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #18582
Clause-②: no
PENDING_GOVERNANCEreaches empty.connectorandanalytics_cube— the two debts left on this card aftersharing_rulewas paid by PR #18587 — move intoGOVERNEDwith a ledger each, so every authorable metadata type in the denominator #18133 widened now has one.What was measured
connector— 74 properties: 20live, 1planned, 53dead.getMetadataTypeSchema('connector')returnsDeclarativeConnectorEntrySchema, and that schema isConnectorSchema.superRefine(...). In Zod 4 asuperRefineattaches a check to the same object def rather than wrapping it, soshapeOf()returnsConnectorSchema'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_noteand in the README row.defineStack({ connectors }),PUT /meta/connector/:name), while the sameConnectorSchemais whatAutomationEngine.registerConnectorparses 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 everyliverow carries aproducer(liveness 台账把「消费端存在读取代码」当作 live 的证据,会漏掉「没有任何生产者传值」的死键(Seed.env 即如此) #4837).ConnectorProviderContextfields plusnameandenabled.typeandiconreach that context and are dropped by all three shipped provider factories (ctx.iconcensus: zero reads acrosspackages/connectors, withctx.label— four hits — as the lit control).authenticationis the ledger's oneplanned: refused outright by ADR-0097 §3 ([security]sys_metadata.metadatais a general cleartext sink: any authored artefact whose schema permits an inline credential lands it there (datasourceconfig.password, connectorauthentication) #7990), never ignored.deadare four declared subsystems with no engine (syncConfig7,fieldMappings7,retryConfig8,health14),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 nestedwebhooks,status,metadata, both timeouts,actions.description/.outputSchema, and fourretiredKeytombstones whose rows stay because the key stays in the walked shape (therls.priorityprecedent).analytics_cube— 29 properties: 17live, 12dead.deadwas the outcome on 12 rows (the seat's open question 2), and none was inflated to green the gate.cube-registry.tsnames 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 isdimensions.granularities(read bydataset-executor#granularityOf, whose argument is aCompiledDatasetan authored cube never becomes) andmeasures.format.dead.granularitiesandmeasures.formatare the dataset compiler's own output channel on a shared shape: deleting them breaks a live internal write.joins[].sqlis REQUIRED and documented as the ON clause while the strategies synthesise an FK equality and never consult it — a decision, not a sweep.publicis an access-control flag that gates nothing (three sites writefalse, nothing reads it): a knob that was never wired, not a hole that was opened.refreshKey.every/.sqlare the only rows where retirement is the obvious shape, and even there the showcase example authors them.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):
packages/spec/src/conversions/registry.tsstatesretryConfig"and the timeouts beside it are untouched — they are live". The wordretryConfigdoes not occur anywhere inpackages/orexamples/outsidepackages/spec, and everyconnectionTimeoutMs/requestTimeoutMsoccurrence is a WRITE of the literal 30000 so a def satisfies the post-parse type.bootstrapDeclaredWebhooksdocuments itself as materializing each "stack/connector-authored webhook", while its source isreadDeclared(…, 'webhook')— metadata items the decomposition registers from the top-levelwebhooks: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:That run is also the answer to the seat's warning about
liveness/README.md:check-liveness.mtsdeclaresreadmeMissingRowsfor exactly this, so the README rows, the heading count andstate-counts.mdare not optional extras — the gate reverse-requires them. After the README rows + heading (37 → 39),gen:liveness-counts, and theconnector/webhooksrow inundrilled-containers.baseline.json— exit 0:connector/webhooksis RECORDED in the undrilled baseline rather than deferred or drilled, and the ledger row says why:WebhookConfigSchemaisWebhookSchema.extend({ events, signatureAlgorithm }), so adeferredrow to the governedwebhooktype 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-classifiesliveeverywhere else.Verification
pnpm --filter @objectstack/spec check:livenessPENDING_GOVERNANCEempty, 39 governedpnpm --filter @objectstack/spec check:generatedpnpm --filter @objectstack/spec check:authorable-surfacepnpm --filter @objectstack/spec check:api-surfacepnpm --filter @objectstack/spec check:docspnpm --filter @objectstack/spec typecheck+check:scripts-typecheckpnpm --filter @objectstack/spec exec vitest run scripts/liveness/pnpm check:platform-checklistpnpm 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-*packages/spechas nolintscript; the repo runs one rooteslint . --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 afilesglob for source is**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}, and the seven non-.mtspaths in this diff are.json/.md, confirmed by running ESLint onliveness/connector.jsonand gettingFile ignored because no matching configuration was supplied; (2)--format jsonon 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 (noparserOptions.project, no typed@typescript-eslintrules) 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 ata442b583fb.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
packages/spec/liveness/connector.jsonpackages/spec/liveness/analytics_cube.jsonpackages/spec/scripts/liveness/check-liveness.mtsGOVERNED;PENDING_GOVERNANCEemptied; its[#18582]note rewritten (it said "two left")packages/spec/liveness/README.mdreadmeMissingRows/readmeHeadingErrors; the closingPENDING_GOVERNANCEparagraph rewrittenpackages/spec/liveness/state-counts.mdgen:liveness-counts, never hand-editedpackages/spec/scripts/liveness/undrilled-containers.baseline.jsonconnector/webhooks, reverse-required by the container-coverage leg (see above)docs/qa/platform-checklist/coverage.jsoncheck:platform-checklistreds on an unmapped kind. Existing key order left as it was.changeset/18582-connector-analytics-cube-liveness-ledgers.mdpatch, becauselivenessis in this package's publishedfiles[], so both ledgers ship in the tarballNeither
packages/spec/src/ui/view.zod.ts(PR #18561) norpackages/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:
retryConfiglive claim, connector timeouts, conversions registry comment):packages/spec/src/conversions/registry.ts'sconnector-rate-limit-config-removedentry assertsretryConfig"and the timeouts beside it are untouched — they are live". Measured false; the comment is what a later reader will trust.bootstrapDeclaredWebhooksdocblock, connector-authored webhook, sys_webhook source): the materializer's docblock claims it materializes each "stack/connector-authored webhook"; its source isreadDeclared(…, 'webhook'), which a connector's nested array never reaches.analytics_cubejoins sql ON clause, synthesised FK equality, cube join relationship):Cube.joins[].sqlis REQUIRED and documented as the join's ON clause, and both strategies synthesiseON "parent"."seg" = "alias"."id"without reading it, so a non-FK join condition returns a 200 carrying different arithmetic than the author declared.joins[].relationshipis the same shape one key over.packages/spec/docs/SYNC_ARCHITECTURE.mdstill ticks "✅ Monitoring: Health checks, metrics, logging" and "✅ Conflict Resolution: Multiple strategies" at L3, both unbacked on this surface — thehealthandsyncConfigsubtrees are dead. Carrier: the next PR that acts on thesyncConfig/healthADR-0049 decision; that file is the one an author reads before writing either block.analytics_cube.publicis 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 thedeadverdict opens. Carrier: the enforce-or-remove sweep that reads this ledger.Metric.name/Dimension.nameare 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