chore(spec): govern the realtime SubscriptionSchema surface in the liveness ledger - #15242
Merged
Merged
Conversation
…veness ledger (#14446) A new ledger file classifies all six authorable properties of SubscriptionSchema — the item type of RealtimeConfigSchema.subscriptions and the `Subscription` the generated API reference publishes — enrolled through the gate's SPEC_ONLY_SCHEMAS override, the route query / qa / manifest and the four RestServerConfig sub-objects already take. All six are dead, and the container is the finding: nothing outside packages/spec imports SubscriptionSchema, SubscriptionEventSchema or RealtimeConfigSchema, so no key beneath them can be read. events[].type accepts RealtimeEventType, whose four members are disjoint from the emitted DataEventType vocabulary; events[].filters is z.unknown().optional() with no shape and no consumer. No key is enforced, removed, deprecated, re-described or repointed; api/realtime.zod.ts and api/events.zod.ts are not edited. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H2oQebDDxYKfWZusyd8GXk
…altime-liveness-ledger
Contributor
📓 Docs Drift Check
What this run could not see
Coarse fallback — 128 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
This was referenced Sep 4, 2026
os-justin
marked this pull request as ready for review
September 4, 2026 07:27
os-justin
enabled auto-merge
September 4, 2026 07: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 #14446
Governs the realtime
SubscriptionSchemasurface in the spec liveness ledger. Ledger rows and their evidence only: no key is enforced, removed, deprecated, re-described or repointed, andpackages/spec/src/api/realtime.zod.tsandpackages/spec/src/api/events.zod.tsare not edited.What the triage ruled, and what this PR is
Triage 5506262367 (2026-09-02), verbatim where operative:
That direction narrowing is quoted verbatim inside the
events[].typerow'snote, which is where the next reader will look.Premise re-verified at the base ref, and one correction
Everything the card measured holds at
5f5511f0, with one attribution error in the card and its triage. Evidence, with refs:5f5511f0RealtimeEventTypespellsrecord.*realtime.zod.ts:33-38—record.created/record.updated/record.deleted/field.changeddata.record.*events.zod.ts#DataEventType—data.record.created/updated/deleted; emitter branches on those strings inpackages/services/service-knowledge/src/knowledge-service-plugin.tsfiltersisz.unknown().optional()realtime.zod.ts:49—filters: z.unknown().optional().describe('Filter conditions')packages/spec/liveness/*.json— zero hits forSubscription/Realtimeoutsidewebhook.jsonproseSubscriptionSchemaSubscriptionEventSchema(realtime.zod.ts:46-50).SubscriptionSchema(:57-62) declaresid/events/transport/channelThe cited lines (
:47,:49) and the quoted shapes are exact; only the owning symbol was misattributed, and this package's own authorable-surface census already spells the two apart —packages/spec/authorable-surface/api.jsonlistsapi/SubscriptionEvent:typeandapi/SubscriptionEvent:filtersbesideapi/Subscription:id/:events/:transport/:channel. Reached from the enrolled root the two keys are theeventscontainer's children, so they land asevents[].typeandevents[].filters.Enrolment decision, and the authority for it
The triage names
api.jsonby family, not by measurement:packages/spec/liveness/api.jsongovernsApiEndpointSchema(metadata typeapi, thirteen endpoint properties) and has no relationship toSubscriptionSchema. Writing subscription rows into it would have produced rows the gate reports asORPHAN.The authority on the correct landing is the gate's own header (
packages/spec/scripts/liveness/check-liveness.mts): the walk is rooted in the metadata-type registry, and a schema no registry holds is enrolled through theSPEC_ONLY_SCHEMASoverride — the routewebhook,query,validation,qa,manifestand the fourRestServerConfigsub-objects (PR #14638) already take. This PR follows that shape end to end: import,GOVERNEDentry, override entry with its rationale, a new ledger file, the README table row and heading count, and the regenerated counts artifact.Rooted on
SubscriptionSchema, notRealtimeConfigSchema— measurement, not taste, and the same reasoning PR #14638 recorded for rooting on four sub-objects instead of the whole config. The walk drills exactly one level. WithRealtimeConfigSchemaas the root,subscriptionswould be the drilled level andevents[].type/events[].filterswould sit one deeper with no row of their own, inheriting a container verdict — #4956's shape, in the ledger written to end it.--dump realtime_subscriptionconfirms the walk auto-expandseventsinto exactly the three keys, so drillingeventsputs both measured keys at a falsifiable depth and adds no new undrilled-container baseline entry.RealtimeConfigSchema's own three keys are deliberately not enrolled, the wayRestApiConfigSchemawas left out of the sibling family: whether enabling realtime does anything is a different question with no census behind it.The enrolment's own rule: every authorable property classified
The
RestServerConfigprecedent classified every authorable property of each enrolled schema, and the gate's ratchet requires it. All six are classified, and all six aredead.iddeadevents[].typedeadRealtimeEventType, whose four members are disjoint from the emitteddata.record.*vocabulary. Carries the settled direction verbatim.events[].objectdeadRealtimeSubscriptionOptions.objecton a different type.events[].filtersdeadz.unknown().optional()— no shape, no consumer; fails in the permissive direction.transportdeadchanneldeadThe container is the finding. Nothing outside
packages/specimportsSubscriptionSchema,SubscriptionEventSchemaorRealtimeConfigSchemaat all, so no key beneath them can be read — themanifest.contributesreasoning. Census at5f5511f0:git grep -nE 'SubscriptionEventSchema|\bSubscriptionSchema\b|RealtimeConfigSchema'over the whole repo excluding CHANGELOGs hits exactly eleven files, every one of them spec's own tree, a generated artifact or published prose. TheSubscription/SubscriptionEvent/RealtimeConfigtype names were swept separately across*.ts/*.tsxoutsidepackages/specand produced only file-local interfaces of the same name. objectui@24e027e: one import ofRealtimeConfigSchema, in a divergence pin asserting that objectui's ownRealtimeSubscriptionConfigis not a dialect of the spec's; zero hits for the other symbols. The closed cloud runtime was not reachable from this container, soevidenceScopestaysin-reporather than claiming a sweep that was not run.Two traps the file note records so the next census does not repeat them: three spellings of a realtime subscription exist and only the third is executed (this schema,
websocket.zod.ts#EventSubscriptionSchema, and the plain interfacecontracts/realtime-service.ts#RealtimeSubscriptionOptionsthatin-memory-realtime-adapter.ts#matchesSubscriptionactually reads); and theSubscriptionidentifiers inin-memory-realtime-adapter.tsandservice-cluster/src/memory/pubsub.tsare file-local interfaces with the same name and a different shape.field.changedis worth naming separately. The identical spellingdata.field.changedwas removed from the siblingDataEventTypein 17.0.0 under ADR-0049 (#4673, PR #4685 — route 3 of the retirement playbook), for having no producer;events.zod.tsrecords that removal in its own header. The same spelling survives here only because this enum has never been in a ratchet's denominator, which is what this PR changes. No removal is taken here.No entry carries
authorWarn: the CLI lint walks stack collections and a realtime subscription is not part of a stack at all (stack.zod.tshas norealtimekey,TYPE_COLLECTIONSno subscription entry), so a warning here could never be emitted. The file note records that as_authorWarnSkipped, the conventionqa.jsonand theRestServerConfigfiles use.Gates
Derived after the last commit with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands(no path args — it reads the change set itself), on a tree atorigin/mainwith no staleness warning. All runs below are pinned to head779b0736; exits were captured before any pipe.pnpm --filter @objectstack/spec check:liveness— exit 0,realtime_subscription 6 classified (dead 6), and✓ … the README state table carries a row for each of the 36 governed type(s) it claims to index/✓ packages/spec/liveness/state-counts.md is current — the same 36 row(s).pnpm --filter @objectstack/spec check:generated— exit 0,✓ All 15 generated artifacts are up to date. First run saidapi-surface/was stale; that was a phantom of an unbuiltdist/— the gate reads built output and says so. Afterpnpm --filter @objectstack/spec buildit is green, and nothing was regenerated.pnpm check:published-files,pnpm check:doc-authoring,pnpm check:nul-bytes,node scripts/check-system-context-census.mjs(+--self-test),pnpm check:changeset-gate-self-tests,node scripts/check-empty-changeset.mjs --self-test— all exit 0.pnpm check:dual-build-cjs-loads— exit 3,PREREQUISITE NOT MET — this gate reads built output, and some package has no dist/, with its own⛔ This is NOT a pass: nothing was measured.It needs a whole-monorepopnpm build; recorded as not measured, not as green, and left to CI.vitest run scripts/liveness/{check-liveness,elsewhere,evidence,readme-table}.test.ts scripts/zod-graph.test.ts src/system/metadata-form-zod-reconciliation.test.ts—Test Files 6 passed (6),Tests 210 passed (210)— pluscheck:scripts-typecheckexit 0.check-changeset-fixed,check:spec-changes,check:authz-resolver,check:error-code-casing,check:filter-alias-parity,check:swallow-census-controls— all exit 0.pnpm lint(repo-wideeslint . --no-inline-config) — exit 0 in 76s. No narrowing claimed.Changeset
patchfor@objectstack/spec:liveness/**is named in the package'sfiles, so this is a published-data change even though no runtime behaviour moves. No BREAKING banner and no ADR-0087 marker — nothing authorable is removed, renamed or re-described, and the marker gate applies to changesets a PR newly declares breaking.Generated by Claude Code