feat(spec)!: the four system/logging.zod.ts duration keys carry their unit in the key name (#17782, ruling A on #15939) - #17999
Conversation
Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015c5G6TmpMKgnusmTpD7Ntt
… unit in the key name Renames HttpDestinationConfig batch.flushInterval / retry.initialDelay / timeout and LoggingConfig buffer.flushInterval to their *Ms spellings, each with a retiredKey() tombstone, one ADR-0087 semantic entry and four RETIRED_KEYS_BY_MAJOR rows. Values and defaults are unchanged. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015c5G6TmpMKgnusmTpD7Ntt
📓 Docs Drift Check8 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. 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 a3d98401307629df9d91471e7fea64497e18e174 && git checkout a3d98401307629df9d91471e7fea64497e18e174
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin c45ea8c3964f2d9b6d414d9ee7b7fd6c5ba4cf78 a5976809c2b11a90c68c5ad9f66436cba7366590 && git checkout -B drift-repro c45ea8c3964f2d9b6d414d9ee7b7fd6c5ba4cf78 && git merge --no-ff a5976809c2b11a90c68c5ad9f66436cba7366590
node scripts/docs-audit/affected-docs.mjs --json c45ea8c3964f2d9b6d414d9ee7b7fd6c5ba4cf78 |
Docs Drift Check answered by hand — the gap it declares is real, and it is empty hereEpic PM for #15939 · readings on Docs Drift reads 8 anchors, no hand-written page naming any of them. Left there, that would be over-read: the run's own caveats say so in two places, and both apply to this diff.
So the check was done by hand rather than inferred. Hand-written docs only —
Lit controls on that same corpus: The single file hit is
An index row naming the def and the source file, and no key name on it ( ⇒ no hand-written page goes stale on this diff. The one page that renders these keys is Recorded rather than left implicit because this PR is the first in the #15939 epic where Docs Drift named nothing — sibling PR #17986 named six pages, five of them false positives off a generic ⛔ No change requested. Nothing here is a defect and nothing in this PR moves because of it. Generated by Claude Code |
…gging-duration-units
The red
|
…gging-duration-units
Contract review
Isolated at-tier review from a fresh worktree at the head sha ( ① Derived judgments
② Semver level
③ Boundary flags
Cross-file staleness sweep (whole tree at head, including Clause ②: Ablation: reproduced and it proves what it claims. From the committed state, under an Readings I re-measuredAll in
Taken on the body's word (not re-measured): the Implemented-by: branch claude/issue-17782-logging-duration-units VERDICT: PASS Generated by Claude Code |
Carriers cleared — provenance
Full acceptance, including the disposition of every review flag, is on the card: #17782 comment
⇒ Not blocking and deliberately not pushed: the conclusion it supports is unaffected —
⛔ No approval is given or implied. This is a governed surface: the clause-② review clears the queue gate, it is not a review approval, and this seat neither approves nor merges. Generated by Claude Code |
Second lander on packages/spec/authorable-surface/system.json and authorable-defaults/system.json after #17999 (#17782, logging). Both shards were merged by the os-regen driver, so step 2 took main's side and this commit regenerates them from the merged source — the only way both cards' rows are proven present rather than assumed. Claude-Session: https://claude.ai/code/session_015c5G6TmpMKgnusmTpD7Ntt Co-authored-by: Claude <noreply@anthropic.com>
…t in the key name (objectstack-ai#17783) (objectstack-ai#18007) Fixes objectstack-ai#17783 `Clause-②: yes` Executes director-seat **ruling A on objectstack-ai#15939** (2026-09-11, maintainer 「同意」, decision batch objectstack-ai#115) for `packages/spec/src/system/metrics.zod.ts` — the sixth of the seven per-file remediation cards. PR objectstack-ai#17635 (the gate) lands last, into a tree these cards have cleaned. ## The five rows, re-located by symbol path Line numbers from objectstack-ai#17635's enumeration rot; each row was re-located by symbol and its JSDoc read. | symbol path | JSDoc said | describe said | after | ratchet | |:--|:--|:--|:--|:--| | `MetricDefinition.summary.maxAge` | "Max age of observations in seconds" | *(none)* | `maxAgeSeconds` | nested — moves nothing | | `ServiceLevelObjective.errorBudget.burnRateWindows[].window` | "Window size in seconds" | "Window size" | `durationSeconds` | nested, array element — moves nothing | | `MetricExportConfig.interval` | "Export interval in seconds" | *(none)* | `intervalSeconds` | **top level — moves** | | `MetricsConfig.collectionInterval` | "Collection interval in seconds" | *(none)* | `collectionIntervalSeconds` | **top level — moves** | | `MetricsConfig.retention.period` | "Retention period in seconds" | *(none)* | `durationSeconds` | nested — moves nothing | Every value is seconds as before; every default (600, 60, 15, 604800) is unchanged. Four of the five carried **no `.describe()` at all**, so the reference page published a bare integer. ## Decoys — measured, and none of them moved Measured at `fc28c1d38`, occurrences via `grep -o | wc -l` (`grep -c` counts lines): | key | key-position declarations | of which decoys | |:--|--:|:--| | `window` | 3 | `:346` `MetricAggregationConfig.window` and `:442` `ServiceLevelIndicator.window`, both `z.object({` | | `period` | 2 | `:509` `ServiceLevelObjective.period`, a `z.object({` | | `maxAge` · `interval` · `collectionInterval` | 1 each | — | Lit control on that file `z.number` = 38; dark control (fabricated token) = 0. All three decoys are objects that already hold a `durationSeconds` of their own from objectstack-ai#15679, and a pin in this PR asserts none of them moved. ## Suffix: `Seconds`, and why three of the five are not the mechanical name Counted in key position across `packages/spec/src/**/*.zod.ts` at `fc28c1d38`: **`Seconds` 40 · `Sec` 1 · `S` 0**. The single `Sec` is `maxExecutionTimeSec`; the two bare `*S:` hits on the corpus are `maxCommitTimeMS` (a millisecond spelling) and `enableRLS` (a boolean), so as a *seconds* suffix `S` reads 0. At this PR's head `Seconds` is 45 — the five added here. This file already has a **stated, reasoned naming convention** from objectstack-ai#15679, recorded in its two tombstone entries, and counting alone cannot see it: - **`burnRateWindows[].window` → `durationSeconds`**, not `windowSeconds`. It is the *fourth* window length on this file; objectstack-ai#15679 renamed the other three to `durationSeconds` precisely "so the three measurements now read alike", and rejected `windowSeconds` because the parent key was already `window`. The same stutter applies here against the enclosing `burnRateWindows` array. Independently: on this tree `windowSeconds` is **not an authorable key at all** — its only key-position occurrence is `system/stack-server.zod.ts:88`, an entry in `ServerRateLimitConfigSchema`'s `aliases` map that maps the spelling **away** to `windowMs`. - **`retention.period` → `durationSeconds`**, not `periodSeconds`. `period` is calendar vocabulary elsewhere in this spec (`ServiceLevelObjective.period.type` selects rolling or calendar; `PluginRegistryEntry.pricing.billingPeriod` is monthly or yearly), so `periodSeconds` keeps the ambiguous half of the name — the objection objectstack-ai#15679 raised against `sizeSeconds`. `periodSeconds` is attested 0 times on this tree; `durationSeconds` 4. - **`collectionInterval` → `collectionIntervalSeconds`**, keeping the qualifier, because `MetricExportConfig.intervalSeconds` is a different cadence one def over that this same PR creates. The qualifier-plus-`IntervalSeconds` compound is attested: `syncIntervalSeconds`, `refreshIntervalSeconds`, `healthCheckIntervalSeconds`. The two mechanical spellings are attested rather than invented: - **`maxAgeSeconds`** — `AccessControlConfig.maxAgeSeconds` on `system/object-storage.zod.ts` is the landed rename of a `maxAge` under this same rule, tombstone and all. It also keeps the `age` stem that the sibling key `ageBuckets` counts buckets of; `durationSeconds` would have orphaned that pair. - **`intervalSeconds`** — 4 key-position occurrences, every one a seconds-valued cadence. ## The pin this card was written to trip `metrics.test.ts` carried `it('leaves the two non-duration keys on this file alone')`, holding the burn-rate `window` bare because it is "outside the gate population entirely". **This card is the sweep that guard was written to catch, so it succeeds by failing.** It bundled two subjects; the exporter batch `size` half is a COUNT of records, still true, and survives untouched under a header narrowed to name only it. The `window` half is replaced by a refusal pin, an acceptance pin at the same 3600, and a describe pin, in a block whose own header stays true. ⛔ Nothing deleted, skipped or loosened.⚠️ Note for whoever rebases **PR objectstack-ai#17635**: its diff patches exactly those comment lines (`- outside the gate population entirely` → `+ inside the gate's census and outside its verdict`), and its own text says the pin "must be re-read, not trusted, when the rename lands". That hunk has no subject after this PR; expect a conflict there and drop that half of its repair. ## Kit - `retiredKey()` tombstone per key (⛔ none of the five enclosing shapes is `.strict()` — measured: 0 `.strict()` on this file — so a bare deletion would silently strip). Every tombstone const is declared **above** the schema that reads it: `gen:schema` and `check:authorable-surface` run with `OS_EAGER_SCHEMAS=1`, which makes `lazySchema` evaluate at module load, and a const below its reader is a TDZ read (the trap PR objectstack-ai#17983 hit). - One ADR-0087 D3 semantic entry `system-metrics-jsdoc-durations-unit-in-key` + five `RETIRED_KEYS_BY_MAJOR[18]` rows. It opens by stating how it relates to objectstack-ai#15679's `system-metrics-window-durations-unit-in-key` rather than rewriting it. - **objectstack-ai#15679's entry gains a pointer clause** (contract review, `bcfdd35609`). Its `acceptanceCriteria` ended "Both keep their names." — and that field's contract is *how the consumer proves the hand-migration correct*, rendering as "Done when:" in the guide and "verify:" in `migrate meta`, so a normative sentence saying the burn-rate window keeps its name instructs a future reviewer that this correct sweep was an error. Every existing word is left in place and a clause is appended naming the successor (Prime Directive objectstack-ai#13 pointer, not a rewrite). This is not amending published history: the protocol-18 step is unreleased and still assembling — `PROTOCOL_VERSION` is `17.0.0`, `docs/protocol-upgrade-guide.md` ends at "Protocol 16 → 17", and **both** entry ids render 0 times in the guide, re-measured on this head. - No D2 conversion: `stack.zod.ts` declares no metrics collection and none of these defs is a stored `sys_metadata` row — the reading objectstack-ai#15679 already recorded for this file. - `minor` changeset with the `**BREAKING**` banner, FROM → TO for all five, and `adr-0087: registered`. ## Verification | reading | exit | note | |:--|--:|:--| | `pnpm --filter @objectstack/spec build` | 0 | via `os-verify-lock.sh`, `VERDICT command-exit 0` (latest on head `84e62ed712`) | | `pnpm --filter @objectstack/spec test` | 0 | 476 files, 13536 tests | | `pnpm --filter @objectstack/spec typecheck` | 0 | | | `check:generated` | 0 | 15/15 artifacts current, re-run on head `84e62ed712` after the pointer commit | | `dispatch-gates` derived families | 109/109 exit 0 | derived and run on `bcfdd35609`; the only change since is the objectstack-ai#15679 pointer clause and its `registry.ts` mirror | | consumers: `core` · `verify` · `cli` · `rest` typecheck | 0 each | on a **built** closure | | consumers: `observability` · `downstream-contract` · `http-conformance` test | 0 each | | | pinned objectui at `.objectui-sha` | 0 hits | all six def names + both distinctive keys, 6409 tracked files; lit `window` 2710 · `timeout` 832 · `period` 160 · `interval` 156 · `metrics` 301; dark 0 ⇒ **no pin bump owed** | **Consumers.** Outside `packages/spec`, every occurrence of every distinctive key on these shapes (`burnRateWindows`, `errorBudget`, `downsampling`, `collectionInterval`, `cardinalityLimits`, `maxLabelCombinations`, `ageBuckets`) is in the generated `content/docs/references/system/metrics.mdx`, which this PR regenerates — lit control `defineStack` 1195 on the same corpus, dark 0. Zero in-repo code consumers, confirming the dispatch's own measurement. **Ablation** (both hardest pins, `EXIT INT TERM` trap, byte-identity proven): | leg | anchor before → after | mutated blob ≠ HEAD | tests | restore | |:--|:--|:--|:--|:--| | delete `window: retiredKey(SLO_BURN_RATE_WINDOW_RETIRED),` | 1 → 0 | `8768749…` ≠ `a747965…` | **exit 1**, 1 failed / 45 passed | blob back to `a747965…`, `git diff HEAD` empty, `git status --porcelain` empty | | delete `period: retiredKey(RETENTION_PERIOD_RETIRED),` | 1 → 0 | `8a84628…` ≠ `a747965…` | **exit 1**, 1 failed / 45 passed | same | | control (unmutated) | — | — | **exit 0**, 46 passed | — | **The two legs fail differently, and prove different things** — corrected here after the contract review caught the body claiming they proved the same one: - **Leg 1** reads `AssertionError: expected undefined to be defined` at `metrics.test.ts:622`. `result.success` was still `false`, but no issue landed at `errorBudget.burnRateWindows.0.window`: the sibling `durationSeconds` on that array element is **required**, so with the tombstone gone the parse is refused anyway for a missing required key. What leg 1 proves is the **lost prescription** — the author gets a bare "required" refusal instead of the FROM → TO rename message the tombstone carries. ⛔ It is not a silent-strip demonstration. - **Leg 2** reads `AssertionError: expected true to be false` at `metrics.test.ts:659` — `result.success` was `true`. `retention.durationSeconds` is `.optional().default(604800)`, so with the tombstone gone the unknown `period` key is accepted and stripped and the parse succeeds. **This is the ADR-0049 silent strip**, live. Both legs are valid evidence that their pin can fail, which is what an ablation is for. Leg 1 also answers Zone 2.4's first question by test rather than assumption: `retiredKey()` on an **array-element** object refuses exactly as it does on a plain nested object, at path `errorBudget.burnRateWindows.0.window`. **Second-lander merge.** `bash scripts/pm/os-regen-merge.sh` (⛔ never a hand-merge) after objectstack-ai#17999 landed. Step 2 took main's side of both shards; the regeneration commit rebuilt them from the merged source. Set-difference over the whole registry across the merge: retired-key rows **175 → 182, 0 lost, 7 gained**; semantic ids **210 → 213, 0 lost, 3 gained**. Those totals are `gen:migration-registry`'s own printed line, not a hand-rolled census — an earlier revision of this body carried 177 → 184 and 209 → 212 from a regex over `registry.ts`, which miscounts; the review could not reproduce them and was right. The **deltas** were identical under both methods, and are corroborated a third way by file count: `entries/retired-keys` holds 177 files on `main` @ `8261ff7171` + this card's 5 = 182, and `entries/semantic` 212 + 1 = 213 — so nothing was dropped. objectstack-ai#17782's four logging rows and its `logging-durations-unit-in-key` id re-assert at 4 / 2 / 2 / 4 / 10 — matching the pre-merge baseline exactly — with lit control `kernel-runtime-config-timeout-unit-in-key` 4 and dark control 0. **Array path notation** — `system/ServiceLevelObjective:errorBudget.burnRateWindows.window` uses plain dots with no bracket token. Settled by the contract review, which closed the evidence gap I had declared: the two `change-management.zod.ts` precedents do cross an array element with plain dots (reachable at `bf1054a4c0` despite the shallow clone), and there is a **live** precedent I had missed — `kernel/Manifest:contributes.kinds.globs`, where `kinds: z.array(strictObject({` contains `globs: retiredKey(`. ## Acceptance notes - **`check-widening-tells` T1 on `retiredKey()` lines** — the known inverted false positive objectstack-ai#17955. Not reshaped, not weakened. In this run the whole derived family exited 0. - **`resolution` is a sixth JSDoc-only duration on this file, and no gate will ever say so.** `MetricsConfig.retention.downsampling[].resolution` — JSDoc "Resolution in seconds", describe "Downsampled resolution" — is outside `check:duration-unit-keys` on the **name** axis (`resolution` is not in `DURATION_SHAPED_TOKENS`), so unlike objectstack-ai#15939's prose axis, PR objectstack-ai#17635's widening does not reach it. Not folded in here: triage certified this batch as exactly 21 rows with "⛔ 无第八张". Filed as objectstack-ai#18030. ⛔ Draft. Not ready, not enqueued, no auto-merge. The in-seat clause-② contract review is owed first and landing is the PM's step. --- _Generated by [Claude Code](https://claude.ai/code)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Fixes #17782
Clause-②: yes
Ruling A on #15939 (director seat 2026-09-11, maintainer 「同意」, decision batch #115), per-file
remediation of the #14478 rule. This is the
system/logging.zod.tscard — 4 rows of the 21-rowdelta enumerated in PR #17635, which lands LAST.
What changed
HttpDestinationConfigbatch.flushInterval: 5000batch.flushIntervalMsbatchHttpDestinationConfigretry.initialDelay: 1000retry.initialDelayMsretryHttpDestinationConfigtimeout: 30000timeoutMsLoggingConfigbuffer.flushInterval: 1000buffer.flushIntervalMsbufferEvery value, default and bound is unchanged. Each old spelling stays as a
retiredKey()tombstone;none of the four enclosing objects is
.strict(), so a bare deletion would have stripped the valuein silence.
The JSDoc I read on each key, and what the published channel said
All four named milliseconds in a source JSDoc — "Flush interval in milliseconds", "Initial retry
delay in milliseconds", "Timeout in milliseconds" — and none carried a
.describe()at all.The
check:duration-unit-keyscensus on this tree read all four as[name: -] [prose: -]beforethe change, so
content/docs/references/system/logging.mdxpublished a bare 5000 / 1000 / 30000 /1000 with nothing on the page to decide milliseconds from seconds. Each key is renamed and given
the describe it never had: under the #14478 rule, fixing only the describe would itself be a
violation.
The two traps this card carried
flushIntervalis declared twice, in two different defs, with two different defaults — 5000 onthe HTTP destination's
batch, 1000 on the loggingbuffer. They are two keys. Each gets its owntombstone, its own
RETIRED_KEYS_BY_MAJOR[18]row, and a prescription that names its def so anauthor who lands on one is not sent to the other. Located by def, not by key name.
Mixed nesting, so the ratchet moves for exactly one key.
authorable-surfacerecords top-levelkeys per def (
build-schemas.tsreadsschema.propertiesone level deep).HttpDestinationConfig.timeoutis the only top-level key of the four, so it is the only one whose rows move:
system/HttpDestinationConfig:timeoutbecomes[RETIRED]beside a newsystem/HttpDestinationConfig:timeoutMs, and theauthorable-defaults/row is renamed with it. Thethree nested keys move neither file — correct, not an omission.
authorable-surface.base.jsonisuntouched (it is written only by
gen:authorable-surface-base).Naming
Ms, derived from the family on this tree rather than from the card: 272 key-position*Ms:declarations in
packages/spec/srcagainst 75*Seconds:. The only competing unit spellings are3
*MS:and 9*Millis:, and every one mirrors a name fixed outside this repo — MongoDB'smaxCommitTimeMS/connectTimeoutMS, node-postgres'sidleTimeoutMillis/connectionTimeoutMillisonPoolConfigSchema— so unlike theTtl-versus-TTLquestion asibling round settled, there is no in-repo alternative to choose between. All three target spellings
were already attested as key-position
*.zod.tsdeclarations:flushIntervalMs1 (onkernel/events/integrations.zod.ts, at the same 1000 default),initialDelayMs5,timeoutMs30.Changeset level
.changeset/17782-logging-duration-units.mddeclares"@objectstack/spec": minor, afeat(spec)!summary, a
**BREAKING**banner and theadr-0087: registered logging-durations-unit-in-keydisposition. Ruling A wrote
patch; that half of the clause is falsified by the precedent the samesentence names — every
#15678/#15679entry inpackages/spec/CHANGELOG.mdunder## 17.4.0is
feat(spec)!at minor — and the correction is recorded publicly on #15939 (5652120294). Thedeclaration is
Clause-②: yesfor the same mechanical reason: a rename puts a new authorablespelling on a published payload, and the floor in
references/contract-review.mdmakes that amandatory affirmative whatever the round's size.
Registration
One ADR-0087 D3 semantic entry
logging-durations-unit-in-keyplus fourRETIRED_KEYS_BY_MAJOR[18]rows, one per key — the shape settled for the four remaining rename cards on #15939. No D2
conversion:
stack.zod.tsdeclares no logging collection and neitherLoggingConfigSchemanorHttpDestinationConfigSchemais referenced anywhere inpackages/spec/srcoutsidesystem/logging.zod.ts, so the chain has no rehydration seam that runs on an authored loggingdocument.
Registry regenerated with
gen:migration-registry, never hand-merged. Set-difference over everyregistry row (every
id:line plus every lone quoted array element), measured three ways:os-regen-merge.shsyncs ofmain, pre → post regenerationmainbrought (#17784, #17781)ae1b258889→ headExact-name occurrence counts on the regenerated registry after the second sync, guarding the silent
drop the
os-regendriver can produce: sibling #17781'skernel-runtime-config-timeout-unit-in-key1 and its row
kernel/RuntimeConfig:resourceLimits.timeout1; sibling #17784'stenant-schema-cache-ttl-unit-in-key1 andsystem/SchemaLevelIsolationStrategy:performance.schemaCacheTTL1; my own id and four rows 1 each; dark control 0. Both siblings' implementation bodies survive too
(
timeoutMs7 inkernel/plugin-security-advanced.zod.ts,schemaCacheTtlSeconds3 insystem/tenant.zod.ts).Cross-repo
The pinned objectui checkout at
.objectui-shapin53ded82bf7a494f54e344e19099dbf00854b8694spells
flushInterval0 times,initialDelay0,HttpDestinationConfig0 andLoggingConfig0across its 6409 tracked files, against lit controls
useState2304 andtimeout702 on the samecorpus. Nothing in the sibling reads these keys.
Tests
All readings below are from head
a5976809c2, the head this PR now carries, re-run after bothos-regen-merge.shsyncs ofmain.pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 src/system/logging.test.ts—46 passed. Three existing pins asserted the retired spellings (
config.timeout30000,batch.flushInterval10000,buffer.flushInterval2000); each was moved to the new key, itscomment rewritten to say why, and none was deleted or weakened.
pnpm --filter @objectstack/spec typecheck && pnpm --filter @objectstack/spec test— VERDICTcommand-exit 0, 476 files / 13533 tests passed.
pnpm --filter @objectstack/spec check:generated— 15 of 15 artifacts up to date on thishead, with the working tree clean, after
gen:migration-registry, the package build andgen:docs.node scripts/pm/dispatch-gates.mjs --commandsderives 109 families for these 12 paths;--ranwith a recorded exit code per command reconciles 109 accounted, 107 run green, 2NOT MEASURED, 0 unrun. The two are
check:dual-build-cjs-loadsandcheck:type-check-debt,both exit 3
PREREQUISITE NOT MET— they need every workspace package built, which CI'sBuild Coresupplies and a spec-only worktree does not. ⛔ Neither is a pass.pnpm --filter @objectstack/spec check:duration-unit-keys— 0 offenders; the four rows leave thecensus's unit-nowhere population.
pnpm --filter @objectstack/spec check:objectui-pin-citations— exit 0: 16 assertingcitations match
.objectui-sha(53ded82bf), 26 historical recorded and not checked. The asserting citation sits on ONE physical source line in both its sites (thesemantic entry and its generated registry mirror), which is the failure three sibling PRs hit.
node scripts/pm/check-widening-tells.mjs --declaration yes— exit 0. Under a hypothetical--declaration noit exits 4 with 4 T1 tells, one perretiredKey()line (:326 :351 :371:759) — the inversion already filed as [finding] check-widening-tells fires T1 on a retiredKey() tombstone line, so every ADR-0087 key retirement reads as a clause-2 widening for the one reason the accept set shrank #17955, and the four newly writable
*Mskeys are not whatit flags. ⛔ The diff is not reshaped and the checker is not weakened.
EXIT INT TERMrestore trap. All fourretiredKey(...)calls replaced by a marker comment: occurrence counts 1/1/1/1 → 0/0/0/0 and theinjected marker 0 → 4,
git hash-object9768a4bb…→40095a54…(the mutation proven on disk,not inferred from an editor exit code). The suite then read 4 failed | 42 passed — exactly the
four
REFUSES the retired spellingpins, no others. Restored withgit checkout HEAD --on anabsolute path:
git hash-objectback to9768a4bb…andgit diff HEADempty. That blob is stillthe one at this head, so the reading stands for the PR as it is.
Landing order, for whoever lands after me
This diff edits
packages/spec/authorable-surface/system.json(+2/−1) andpackages/spec/authorable-defaults/system.json(±1), and card #17783 (system/metrics.zod.ts,two top-level rows) is in flight against the same two shards. Both paths are routed
merge=os-regenin.gitattributes, so the local driver defers them andpre-commitcollects thedebt — the merge queue rebuilds server-side where no driver runs, so the shard is textually shared
and a conflict there is expected, not a defect. The only correct resolution is
bash scripts/pm/os-regen-merge.sh(commit the merge FIRST, then regenerate on the merged base),⛔ never a hand-merge of the JSON: these rows are a sorted set, a dropped line is silent, and
gen:schemarecomputes the whole shard from the merged Zod graph anyway. One thing measured herethat the later lander can rely on: the ratchet moved for exactly one of my four keys, so the
regenerated shard differs from
mainby three lines and nothing else on this file is mine.Acceptance notes
packages/spec/src/system/logging.zod.ts:298backoffMultiplieralso reads[name: -] [prose: -]in the census, but it is a ratio, not a duration, and is not one of this card's fourrows. Noted, not filed.
PREREQUISITE NOT METon aworktree that has not built the whole repo; they are named in the report comment as NOT MEASURED
rather than passed. CI's
Build Coresupplies what they need.⛔ Not ready for review by this seat: the in-seat clause-② review at tier is the PM's step. Draft,
not enqueued, no auto-merge.
🤖 Generated with Claude Code
https://claude.ai/code/session_015c5G6TmpMKgnusmTpD7Ntt
Generated by Claude Code