feat(spec)!: the last seven data/ ui/ ai/ integration/ duration keys carry their unit in the key name — the gate reaches zero offenders (#15680) - #15988
Conversation
…bstones on the old spellings (#15680) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4138K1EG7kQ81FNba5Kp4
…for the 7 renames (#15680) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4138K1EG7kQ81FNba5Kp4
…al derivation so a retirement tombstone is not read as a secret (#15680) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4138K1EG7kQ81FNba5Kp4
…ey rename, translations carried over (#15680) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4138K1EG7kQ81FNba5Kp4
…s, plus the auto-arm sibling (#15680) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4138K1EG7kQ81FNba5Kp4
…y rename (#15680) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4138K1EG7kQ81FNba5Kp4
📓 Docs Drift CheckThis PR changes 5 package(s): 14 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 3 release-owned page(s) also name something this change touched. These are read-only:
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): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 9cd53009156bdc29e68140f04a8f95675e1db599 && git checkout 9cd53009156bdc29e68140f04a8f95675e1db599
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 45a35896c8ae9f93aeecce3ab604f4a0b43f3f87 808691883023a04d844f5360bfaae7361a01b61f && git checkout -B drift-repro 45a35896c8ae9f93aeecce3ab604f4a0b43f3f87 && git merge --no-ff 808691883023a04d844f5360bfaae7361a01b61f
node scripts/docs-audit/affected-docs.mjs --json 45a35896c8ae9f93aeecce3ab604f4a0b43f3f87
|
Seat ruling on both open questions — A and A⭐ First, the headline this card earns: Q1 — the eighth key (
|
…at the shared libSQL seam `buildTursoDriverConfig` still consulted `config.timeout` after that authored key was renamed to `timeoutMs` and tombstoned, so a datasource authored the canonical way had its timeout silently dropped. `TursoConfigSource.config` is an untyped string-keyed bag, so tsc could not see the rename through it, and the covering test authored the retired spelling at all three of its sites and stayed green over the defect. The reader now reads `config.timeoutMs`; the driver key it lands on stays `timeout` (published-but-inert, must not be ratified by a rename). No fallback arm for the retired spelling — the sqlite `filename` and mongo `url` arms in `default-datasource-driver-factory.ts` set that precedent, and both authoring and stored-row rehydration deliver the canonical key already. The covering test moves to the canonical spelling and gains contract-derived cases that read the schema's own tombstones, so they hold for the next rename without being edited. The two sibling pins that author the same spec move with it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4138K1EG7kQ81FNba5Kp4
Follow-up fix on this branch: the libSQL seam now reads the canonical
|
| renamed key | untyped / string-keyed readers found |
|---|---|
TursoConfig.timeout |
1 — the defect. service-datasource/src/turso-driver-config.ts:170. Fixed here. |
dashboard.refreshInterval |
0 in this repo. The only reader is objectui's, already documented in this PR body and sequenced behind a release. In-repo sites (cli/src/commands/explain.ts:241, the four generated *.metadata-forms.generated.ts) already say refreshIntervalSeconds. |
CircuitBreakerConfig.monitoringWindow |
0 — no runtime reader at all. Outside packages/spec the name appears only in the generated authorable-surface / authorable-defaults artifacts. Declared-only surface. |
ConnectorTrigger.interval |
0. ConnectorTrigger is imported nowhere; the only .triggers read is connector.zod.ts:996's own superRefine, which reads the array, never interval. |
FilePersistenceConfig.autoSaveInterval |
0 stale. driver-memory reads autoSaveIntervalMs throughout (memory-driver.ts:2296,2304, persistence/file-adapter.ts:21,23,70) — moved with the rename in this PR. |
AutoPersistenceConfig.autoSaveInterval |
0 stale. Same adapter, same field, same call sites as the file arm. |
NoSQLQueryOptions.timeout |
0 — zero consumers anywhere. NoSQLQueryOptions appears only inside packages/spec. Nothing outside reads it, typed or untyped. |
ConversationAnalytics.duration |
0 — zero consumers anywhere. Same: ConversationAnalytics appears only inside packages/spec. |
The measured empty results are the finding: seven of the eight renamed keys had no
untyped reader to leave behind, and TursoConfig.timeout was the only one with a live
string-keyed consumer. The near-misses that a name-grep surfaces and that are NOT this
class, each checked and dismissed: core/src/health-monitor.ts config.timeout /
config.interval (typed PluginHealthCheckParsed, and PluginHealthCheck is not among
the eight); plugin-email/src/transports/smtp.ts:149 this.opts.timeout (SMTP transport
options); cli/src/commands/* flags.timeout (CLI flags);
metadata-fs/src/repository.ts interval (its own watcher option);
core/src/qa/http-adapter.ts:226 action.payload?.duration and
service-automation's wait-node config.duration (neither key is one of the eight).
⛔ packages/drivers/driver-turso/src/spec/turso.zod.ts:104 — the driver package's own
parallel timeout — is not touched. It is card 6/6's, exactly as this PR body says.
One adjacent observation, NOT filed and NOT touched
cloud-connection/src/cloud-connection-plugin.ts:324 reads json.interval off the cloud
device-code response and re-emits it, while DeviceRequestResponse.interval was renamed
to intervalSeconds by card 2/6 (#15677). Different card's key, and it crosses a
network boundary to another repo, so it is not this sweep's to judge — flagging it for
whoever owns card 2/6's reader audit rather than filing over it.
Verification — union run at 808691883, the final commit
Every exit code was captured by redirecting stdout and stderr to a file FIRST and
reading $? on the next statement. Nothing was read through a pipe.
| command | exit |
|---|---|
pnpm --filter @objectstack/spec check:duration-unit-keys |
0 — unchanged census, verdict below |
pnpm --filter @objectstack/spec test |
0 — Test Files 473 passed (473), Tests 12844 passed (12844) |
pnpm --filter @objectstack/spec typecheck |
0 |
pnpm --filter @objectstack/service-datasource test |
0 — Test Files 31 passed (31), Tests 647 passed (647) |
pnpm --filter @objectstack/service-datasource typecheck |
0 |
pnpm --filter @objectstack/cli exec vitest run src/utils/storage-driver.test.ts |
0 — 36 passed |
pnpm --filter @objectstack/runtime exec vitest run src/turso-driver-factory.convergence.test.ts |
0 — 7 passed |
pnpm --filter @objectstack/cli typecheck · @objectstack/runtime typecheck |
0 |
pnpm --filter @objectstack/spec check:generated |
0 — All 15 generated artifacts are up to date |
pnpm check:doc-authoring |
0 — 0 findings |
pnpm check:nul-bytes |
0 — 7709 files scanned |
pnpm check:test-source-alias · check:cross-package-test-inputs |
0 |
The required gate, verbatim:
✓ check:duration-unit-keys — 215 duration-shaped numeric key(s) across 838 source file(s)
all carry their unit in the key name (or in a sibling `unit`, or under a declared
exemption: 6 declared `EpochMs` instant(s), 11 declared `externalVocabulary` mirror(s));
zero offenders, no baseline.
215 among 838, zero offenders, exit 0 — identical to this PR's own reading. A reader
fix did not move it, as required.
typecheck really does cover the edited test file: tsc --noEmit --listFiles lists
src/__tests__/turso-driver-config.test.ts (1 hit), so "typecheck is clean" is a
statement about the new cases and not a vacuous one.
Declared narrowing. The whole-repo pnpm lint sweep was not run. Instead
eslint --no-inline-config --format json ran on the 4 edited source files: 4 files
linted, 0 errors, 0 warnings. That narrowing is a measurement, not a skip, because this
repo runs one eslint.config.mjs which — by its own header, measured with a positive
control — never enables type-aware linting for ANY file, so a 4-file diff cannot move the
verdict on an untouched file. CI runs the farm once regardless.
pnpm check:generated at the repo root exits 254 — command not found (it is a
packages/spec script; scoped correctly it exits 0), and the first reproduction run
exited 1 with ERR_MODULE_NOT_FOUND on a relative import path, which is a broken
invocation and not a red.
Inherited red, independently re-confirmed — still not repaired here
pnpm check:quick-reference-counts exits 1: [total] section "Shared Protocol" declares "of 8 schemas" but content/docs/references/shared/ publishes 9. This PR body
already records it as inherited. Re-proved from my side rather than taken on trust: my
commit changes 0 files under content/docs/ (git diff 3351a860db..HEAD --name-only -- content/docs/ is empty), and content/docs/references/shared/epoch.mdx is added by
this branch relative to origin/main, which is what takes that category from 8 to 9.
Pre-existing, out of my scope, and flagged to the PM as an open blocker on this PR.
Generated by Claude Code
Card 5/6 verified — and the "inherited blocker" recorded on this PR is measured to be no blocker
1. The seam fix — read, not relayed
⭐ The gate's census could not have moved, and that is structural rather than a matter of trust: none of the five files sits under 2. The renamed-key list, derived independentlyNot taken from the report — enumerated from the ADR-0087 retired-key entries this card adds: Eight entries, matching the sweep's eight rows exactly. Independently confirmed that four of the owning types ( 3. The sweep re-run independently — same two live sites, no thirdRe-swept every retired spelling repo-wide by name, by bag-shaped read and by string literal. It reproduces the table, and turns up nothing the report missed. The near-misses land the same way: Two sites outside the eight, both already owned:
⭐ That second one is worth a measurement rather than a shrug, because the gate reading it lives in a required context. const SDUI_OBJECT_BLOCK_TYPES = Object.keys(ComponentPropsMap)
.filter((t) => t.startsWith('object-'))and 4. ⭐ The
|
| tree | quick-reference.mdx:209 |
llms.txt shared row |
llms.txt heading |
|---|---|---|---|
base branch tip c09fd1023 |
(5 of 9 schemas) |
| shared | 14 | |
(208 schemas) |
this head 808691883 |
(5 of 8 schemas) |
| shared | 13 | |
(207 schemas) |
git merge-tree of this head into the base branch |
(5 of 9 schemas) |
| shared | 14 | |
(208 schemas) |
The third row is the one that decides it, and it is the tree that will actually exist. ⛔ So this is not to be repaired on this card — repairing it here would conflict with the fix already on the base branch.
Verdict
Verified. This card touches skills/objectstack-ui/rules/dashboards.md, so it is governed surface ⇒ human merge, after card 4/6 lands. Merge into the base branch is measured clean in both the driver-enabled and driver-disabled readings, which agree byte for byte.
Generated by Claude Code
|
Contract review (clause ②) PASS at head ⛔ Governed ( Implemented-by: Generated by Claude Code |
bcf0412
into
claude/issue-15679-system-duration-unit-in-key-name
Part of #14478
Stack card 5/6 — issue #15680, implemented here and deliberately left open (card 6/6 still owes the population widening).
Branch base:
claude/issue-15679-system-duration-unit-in-key-name@45a35896c8ae9f93aeecce3ab604f4a0b43f3f87— card 4/6's head, notmain.git merge-base --is-ancestoranswered exit 0 against that branch before the first edit (a positive ancestry read is self-certifying on a shallow checkout; only a negative would have needed a control leg).⭐ This is the card that turns the headline gate green
pnpm --filter @objectstack/spec check:duration-unit-keys, verbatim, exit code captured before any pipe:BEFORE (on the base,
45a35896c) —EXIT=1:AFTER (on
3351a860d) —EXIT=0:The census is unchanged at 215 — the renamed keys stay in the population and are now compliant. The file count moves 828 → 838: ten new migration-entry files.
packages/spec/src/**), not for the epic. Card 6/6 widens that population and #15642 has already measured an offender outside this subtree (packages/drivers/driver-turso/src/spec/turso.zod.ts:104). The gate is expected to go red again by design. #14478 is not finished by this PR.The 7 renames — re-derived from the gate on this branch's own head before editing
src/ai/conversation.zod.ts:308durationdurationSecondssrc/data/driver-nosql.zod.ts:310timeouttimeoutMssrc/data/driver/memory.zod.ts:112autoSaveIntervalautoSaveIntervalMssrc/data/driver/turso.zod.ts:215timeouttimeoutMssrc/integration/connector.zod.ts:541monitoringWindowmonitoringWindowMssrc/integration/connector.zod.ts:675intervalintervalSecondssrc/ui/dashboard.zod.ts:906refreshIntervalrefreshIntervalSecondsRow for row with the card's table. ⛔
packages/drivers/driver-turso/src/spec/turso.zod.ts:104— the driver's own parallel schema — is not touched; card 6/6 owns it, so the two declarations disagree by design until that lands.Every rename was an anchored substitution with abort-on-count-mismatch: each edit asserted the exact occurrence count of its anchor and refused the whole file otherwise.
AutoPersistenceConfig.autoSaveInterval(src/data/driver/memory.zod.ts:189on the base) is not a gate offender: its.describe()named no unit at all, so the predicate never judged it.It moves anyway, and the boundary scan is this:
persistence: { type: 'auto' }resolves to the same Node.js file adapter astype: 'file', and this value is forwarded to the sameFileSystemPersistenceAdapterconstructor field, in the same milliseconds, under the samemin(100)bound, straight into the samesetInterval. Renaming one arm and not the other would have left one value with two spellings across sibling arms of one union, withpackages/drivers/driver-memoryreading both — the consumer-side dialect Prime Directive #12 forbids. Its.describe()now names the unit too, so name and prose agree.A pin asserts the refusal on the arm the gate never listed, so a later reader cannot "restore" the bare spelling as an over-application of the rule. If the PM would rather this were a separate card, it is one commit to lift out.
Dispositions — decided from
stack.zod.ts, not defaultedFour D2 conversions, because
dashboards:,connectors:anddatasources:are each declared as a stack collection instack.zod.tsand their members are stored whole assys_metadatarows:dashboard-refresh-interval-to-refresh-interval-secondsconnector-health-and-trigger-durations-unit-in-key— both connector keys in one pass, emitting separatelymemory-persistence-auto-save-interval-to-ms— both persistence armsturso-config-timeout-to-timeout-msBoth datasource conversions are driver-aware, for the reason
datasource-config-driver-key-aliasesrecords: a bareconfig.timeoutunder another driver is that driver's own key and must not be rewritten. Each fixture carries a member that never authored the key, so copy-on-write identity is pinned too.Two semantic entries, because neither surface is a collection member or a stored row and the chain has no seam that would ever see one:
ai-conversation-analytics-duration-unit-in-key— analytics are computed at runtime and handed to a consumerdata-nosql-query-options-timeout-unit-in-key— a per-call driver argument, reached only throughAggregationPipeline.optionsAll eight keys are
retiredKey()tombstones with aRETIRED_KEYS_BY_MAJORrow. ⛔ No internal issue id appears in any prescription string —check:doc-authoringreads 0 findings across 15070 customer-facing strings and 46 published skill files; the ids live in//and/** */comments only.dashboard's three rename-hint aliases —refresh,autoRefresh,pollInterval— were repointed torefreshIntervalSecondsin the same edit. A hint left naming the tombstone would prescribe a key the shape refuses; a pin asserts all three, in both directions.dashboard.refreshIntervalis the only rename in this whole stack whose consumer lives in another repository, so its reader could not move in this PR the way every other reader here did.In-repo evidence —
sdui.manifest.jsonis tracked at the repo root (last written by6b66ec749) and is objectui's own registry-inputs dump, produced by driving a real browser over objectui built at the.objectui-shapin:sdui.manifest.json:805— componentdashboard(namespaceview) declares an input namedrefreshInterval, described "Auto-refresh period in seconds. Zero or a negative value disables the timer, and it only runs when the host wires an onRefresh handler."check:react-declaration-paritydoes not cover this. Run withMANIFESTpointed at the committed dump it exits 0 and reports90 spec-only divergences, 0 blocks missing from the registry— its scope isObjectForm/ListView/ObjectChartplus the SDUIobject-*blocks, and thedashboardcomponent is in none of them. So this divergence is invisible to that gate in both directions.Reader sites at the exact pin — read from the sibling
objectuicheckout ata472b07167a39e55491109e864bb5a54027dcfbd, which is the sha.objectui-shanames (that checkout's own HEAD is1ec291c0d, so every path below was read at the pinned commit, not at its tip). ⛔ Nothing in objectui was modified.Runtime readers — these are the ones that break:
packages/plugin-dashboard/src/DashboardRenderer.tsx:448-453—schema.refreshInterval, guarded<= 0, thensetInterval(handleRefresh, schema.refreshInterval * 1000)packages/plugin-dashboard/src/DashboardGridLayout.tsx:146-151— the same read and the same* 1000Declaration / registry (this is what produces
sdui.manifest.json:805):packages/plugin-dashboard/src/index.tsx:94— the published input declarationpackages/plugin-dashboard/src/DashboardConfigPanel.tsx:108— the designer config-panel field keyobjectui's own parallel type declarations:
packages/types/src/zod/complex.zod.ts:740—refreshInterval: z.number().min(0).optional().describe('Auto-refresh interval in seconds')packages/types/src/complex.ts:1132,packages/types/src/designer.ts:585,packages/types/src/data.ts:1358,packages/types/src/ai.ts:453i18n:
packages/app-shell/src/views/metadata-admin/metadata-form-i18n.ts:91— the zh-CN label keyed by the field namePins that will go red against a bumped spec:
packages/types/src/__tests__/page-app-dashboard-spec-parity.test.ts:168-170— assertsDashboarddeclaresrefreshInterval; this is the explicit spec-parity pinpackages/plugin-dashboard/src/__tests__/dashboardAuthoredInputs.test.tsx:159,170,221,333,353— includingexpect(block).toContain('refreshInterval?: number;')packages/types/src/__tests__/dashboard-config.test.ts:37,122packages/plugin-dashboard/src/__tests__/ConfigPanel.i18nWiring.test.tsx:242Docs:
content/docs/plugins/plugin-dashboard.mdx:54,content/docs/core/report-schema.mdx:392,content/docs/api/schema-reference.md:941,987content/docs/api/schema-reference.md:987documents the key as "Auto-refresh interval in milliseconds" while every other objectui site and the spec say seconds. That is an objectui-side doc error that predates this PR — noted, not touched.Not a reader of this key:
packages/plugin-report/src/ReportViewer.tsx:230readsreport.refreshInterval, which is objectui's own report schema (packages/types/src/reports.zod.ts:167), a different declaration.Behaviour until the follow-up lands: the renderer sees an absent key and does not start its refresh timer. A dashboard still renders and still refreshes on demand. The D2 conversion in this PR is what keeps stored dashboards and
os migrate metacorrect in the meantime.A retirement tombstone is no longer read as a secret
refusedCredentialKeysderives a driver's refused inline credentials by findingz.never()keys in its config contract. AretiredKey()tombstone is also az.never(), and until this card no driver contract carried one — so "never implies credential" held by accident of population rather than by construction. The first tombstone to arrive (TursoConfig.timeout) made the derivation answer that a millisecond budget was a secret: three pins indatasource-credential-redaction.test.tswent red,refusedCredentialKeys('turso')returned['authToken', 'timeout'], andtimeoutwas redacted off the datasource read path.Measured before choosing the fix — every genuine credential slot in every builtin driver contract (
postgres,mysql,mongodb,turso) carriesformat: 'password'; the tombstone was the onlyz.never()that did not.The derivation now skips keys carrying the
[REMOVED]prefixretiredKey()itself stamps. The exclusion is deliberately negative — skip declared tombstones — rather than positive (keep only keys markedformat: 'password'), even though the positive form would work today: under-redacting is the dangerous direction, so a future credential key whose author forgets the marker is still scrubbed, and only a key that has explicitly declared itself retired may drop out. Both directions are pinned, and the pin also asserts the tombstone really is in the shape — without that leg it would pass equally on a key deleted outright.Reverse verification — two ablation legs, prediction stated before running
No build is involved in either leg and none was needed: the gate is a source scanner, the vitest suites import
./…zodby relative source path, and the probe imported the source file directly. There is nodist/resolution in play, so no stale-artifact hazard applies. Each leg restored withgit checkout HEAD -- ABSOLUTE_PATHunder atrap … EXIT INT TERM, with the restore proved bygit diff HEADempty and a blob-hash match — never by an exit code.Leg A — revert one rename (the dashboard key), keep everything else.
Predicted: gate RED, exit 1, exactly one offender,
[unit-in-prose-not-in-name] src/ui/dashboard.zod.ts refreshInterval, census still 215.Observed: exactly that.
Leg B — card 4/6's model: keep the rename, delete ONLY the tombstone, on the non-strict
CircuitBreakerConfigso the silent strip is demonstrable.Predicted: (1) the headline gate stays GREEN and exit 0 — it judges the canonical key's name against its prose and is structurally blind to a missing tombstone; (2) the refusal pin goes RED; (3) the parse succeeds and the authored value vanishes, falling back to the 60000 default; (4)
check:authorable-surfacegoes RED as the[RETIRED]row disappears.Observed: all four.
Leg B is the whole argument for tombstones on non-strict shapes: the headline gate cannot see the defect, and the only thing standing between an author and a silently halved monitoring window is the refusal pin.
Verification — union run AFTER the final commit, at
3351a860dpnpm --filter @objectstack/spec testTest Files 473 passed (473),Tests 12844 passed (12844)pnpm --filter @objectstack/spec typecheckpnpm --filter @objectstack/spec check:duration-unit-keyspnpm --filter @objectstack/spec check:generatedAll 15 generated artifacts are up to datepnpm --filter @objectstack/spec check:skill-examples257 prose examples type-check across 3 surface(s)pnpm --filter @objectstack/spec check:livenesspnpm --filter @objectstack/spec check:empty-statepnpm check:doc-authoringpnpm check:i18n9 package(s) — all bundles in syncpnpm check:i18n-stale-fillpnpm check:nul-bytespnpm check:pm-skill-ratchetnode scripts/check-skills-token-ratchet.mjsnode scripts/check-adr-0087-registration.mjs --base origin/mainnode scripts/check-changeset-no-major.mjs,check-empty-changeset.mjs,pnpm check:changeset-gate-self-testspnpm check:stack-collection-maps·check:spec-parsed-alias·check:dispatcher-error-vocabulary·check:error-code-casingpnpm check:skill-frame-sync·check:skill-compatibility·check:skill-identifier-livenesspnpm check:docs-single-h1·check:doc-anchors·check:cross-package-test-inputs·check:test-source-aliasnode scripts/check-published-list-mirrors.mjspnpm --filter @objectstack/driver-memory test+typecheckTest Files 41 passed,Tests 1058 passedpnpm --filter @objectstack/cli typecheckcheck:generatedwas re-run after the final rebuild: its first run reportedcheck:api-surfacestale, and the gate's own text named the cause —dist/**/*.d.tsolder thansrc. Rebuilt, re-ran, green. No artifact was regenerated on that evidence.Declared narrowing.
pnpm lint(the whole-repo eslint sweep) was not run here; CI runs the farm exactly once on this diff. Everything above is the cheap half.pnpm check:published-list-mirrorsreturns 254 — command not found, a NON-READING, not a pass and not a failure. The script isnode scripts/check-published-list-mirrors.mjs, which exits 0. Likewise the firstnode scripts/check-i18n-bundles.mjs --writereturned exit 3 — PREREQUISITE NOT MET (the CLI closure was not built); the reading below is from the run after that closure was built.Every exit code above was captured with
cmd > file 2>&1; EXIT=$?— redirect first, then read$?. Nothing was read through a pipe.⛔ Inherited reds — NOT from this PR
Both were red on the base and are proven inherited by blob identity, not by argument:
pnpm --filter @objectstack/spec check:llms-txt— 2 findings, both countingsrc/shared/.packages/spec/llms.txt(74bba77c…), the gate script (916a9990…) and thesrc/shared/tree (3c002da6…) are byte-identical between45a35896cand3351a860d, and the population the gate counts —*.zod.tsfiles underpackages/spec/src/— is 209 on both sides. My ten new files are migration entries, not schemas.pnpm check:quick-reference-counts— 1 finding,[total] section "Shared Protocol" declares "of 8 schemas" but content/docs/references/shared/ publishes 9.quick-reference.mdx(f5488e77…), thecontent/docs/references/sharedtree (e8c0bcc9…) and the gate script (ce2b2de3…) are all byte-identical between base and head.Already fixed on card 1/6's branch. ⛔ Not repaired here.
skills/**readings — this PR is governed, and the file staysOne published skill file is touched, so the PR is governed and the file is deliberately kept rather than dropped to escape governance.
skills/objectstack-ui/rules/dashboards.md— linesskills/objectstack-ui/rules/dashboards.md— tokens (gate convention,ceil(utf8 bytes / 4))skills/)check-skills-token-ratchetconfirms it independently:skills/objectstack-ui/rules/dashboards.md is 6089 tokens (ceiling 6090; headroom 1). No content was bought — the change is one key name and a shortened trailing comment.Out-of-scope
Nothing was filed. The one thing that looked like a candidate —
AutoPersistenceConfig.autoSaveInterval— is handled inline above with its boundary scan, because leaving it would have shipped one value under two spellings.⛔
api/,kernel/,system/,shared/, the gate's declared population, other PRs' diffs andcontent/docs/releases/are untouched.Generated by Claude Code