Skip to content

feat(spec)!: duration-shaped number keys carry their unit in the key name — no-baseline gate + seven ADR-0087 renames (timeoutMs, ttlSeconds/ttlMs, *TimeoutSeconds) - #15626

Draft
claude[bot] wants to merge 5 commits into
mainfrom
claude/issue-14478-duration-unit-in-key-name
Draft

feat(spec)!: duration-shaped number keys carry their unit in the key name — no-baseline gate + seven ADR-0087 renames (timeoutMs, ttlSeconds/ttlMs, *TimeoutSeconds)#15626
claude[bot] wants to merge 5 commits into
mainfrom
claude/issue-14478-duration-unit-in-key-name

Conversation

@claude

@claude claude Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Part of #14478
Fixes #14519

Executes the maintainer ruling recorded on #14478 at comment 5518649320ruled B, verbatim 「14461 你不处理,其他同意」, on the standing rules 「不考虑存量」 and 「项目在创业阶段,用户也很少,短期不考虑渐进。」: a spec-source gate for duration-shaped z.number() keys with no grandfathered baseline, plus an ADR-0087 conversion of every offender the ruling named, in one PR. Dispatched by the domain:spec seat (claim 5547289696), CONTRACT_REVIEW_TIER.

Clause-②: yes — seven published authorable keys are renamed. needs:contract-review is carried on the card and on this PR; the isolated contract-tier review is the seat's to dispatch, and the gate stays on both carriers until it clears.

⛔ Landing: this PR is GOVERNED — draft is its finished state, a human merges it

Draft is not "unfinished" here. One path in this diff is on the governed-surface register, and one hit governs the whole PR — 「混合 diff 一条命中即整 PR 分叉」 (maintainer, 2026-08-18; AGENTS.md Prime Directive #14). Measured, not assumed: node scripts/pm/check-governed-merges.mjs --test skills/objectstack-data/references/data-hooks.md packages/spec/src/data/hook.zod.tsexit 3, skills/** ×1 — the published skills catalog: skills/objectstack-data/references/data-hooks.md, with the spec path listed as not on the register; the control leg (--test packages/spec/src/data/hook.zod.ts alone) exits 0. The other 62 files are ordinary.

The skills/** edit is kept on purpose and must not be dropped to escape governance: on origin/main that reference names timeout at :109-110 (an authored example, timeout: 5000,) and in the prose lists at :871 and :885; landing the rename without it would have the published skills catalog teach a spelling the schema now refuses — a defect strictly worse than a human merge. So: ⛔ no seat flips this PR ready, enqueues it, arms auto-merge, or approves it (an agent-operated approver account counts as a seat). Review is requested from hotlong (a governed approver); the human merge is the review record. Nothing about the change itself is in question — this section is about how it lands.

⚠️ Read this first: the ruling's offender list is 7; the ruling's rule finds 70

The ruling was adopted on a measured radius of five keys (plus the two #14519 keys), three readers and one in-repo author. The card's own inventory said that of 178 unit-naming describes "for most of them the unit is also in the key name". That premise is false by an order of magnitude. Deriving the offender set mechanically from packages/spec/src/** with the ruling's own rule — a z.number() chain whose .describe() names a time unit and whose key name carries none — finds 70 offenders on ca46f8f12 (six more were detector false positives, since removed: ordinal "second", min as minimum). The ruling's seven are among them. After this PR converts those seven, 65 remain, in four classes (full list in the gate's own output, pnpm --filter @objectstack/spec check:duration-unit-keys):

class count examples
authored config durations — the ruling's class, unnamed by the ruling ~30 RestApiEndpoint.timeout / cacheTtl, WebSocketConfig.pingInterval / heartbeatInterval / timeout, CollaborationSessionConfig.idleTimeout (ms — the same name the tenant key had in seconds), EventQueueConfig.retention (days), RegistryConfig.syncInterval / ttl, DashboardConfig.refreshInterval
keys whose spelling mirrors an external standard ~12 HttpCacheConfig.maxAge / staleWhileRevalidate / staleIfError (Cache-Control directives), CORS maxAge twice, better-auth expiresIn three times, S3 presign expiresIn, pg statementTimeout, DNS ttl, OAuth device-flow interval
runtime-emitted measurements, never authored ~16 StartupResult.duration / totalDuration, PluginHealth.uptime / responseTime, TraceSpan.duration, ApiError.retryAfter (the wire envelope)
instants — "Unix timestamp in milliseconds" 6 timestamp, lastSeen, startTime, registeredAt, createdAt

I stopped at the ruling's seven and did not convert the other 65. Converting them is ten times the radius the maintainer measured when adopting B, includes keys whose spelling is fixed by HTTP, OAuth, AWS, DNS and better-auth, and includes runtime-emitted shapes for which an ADR-0087 conversion has no seam — decisions the ruling did not make. Under the four-axis frame the rule stands; what is undecided is the population: literal (convert all 65), or the rule minus instants and external-vocabulary mirrors (declared in-schema, never as a gate ledger), or the rule restricted to the authorable metadata-type surface. That is put to the maintainer in the report as needs_decision.

Consequence: this PR is red on its own gate, by design. The gate is wired into lint.yml as the last step of the Lint & Repo Gates job so it masks nothing behind it, and it prints the 65 remaining offenders. ⛔ It has no baseline and no exception list — the dispatch forbade a gate that passes only because its exceptions were enumerated. It goes green the day the remainder is converted or the population is narrowed by decision.

What this PR does

1. The gate — packages/spec/scripts/check-duration-unit-keys.ts

pnpm --filter @objectstack/spec check:duration-unit-keys (self-test first, wired as check:self-test-wired requires; classified NO_GENERATOR in check-generated.ts; declared population packages/spec/src/** via the ROOT_DIR_WATCH_HINTS idiom, held against the scan root in the self-test).

The rule, one direction each way: a property whose value is a z.number() / z.int() / z.coerce.number() chain and whose .describe() names a time unit must carry that unit as a token of its key name (Ms / Seconds / Minutes / Hours / Days, plus the knex-inherited Millis), and the token must agree with the prosettlMs described "in seconds" is refused too. { value, unit } pairs are recognised structurally by the sibling unit key; duration literals ('14d') are strings and outside the population. Calendar positions ("day of the month (1-31)") and rates ("requests per second") are skipped. Singular prose forms count only with a number in front ("1 second"), which is what keeps the ordinal "second pass" and min as minimum out.

Why packages/spec/scripts/ and not packages/lint: @objectstack/lint validates a customer's metadata graph at build time — pure (stack) => Issue[] functions the CLI and AI authoring share. This gate reads this package's own source and judges how a schema is declared; it has no stack to validate and nothing a customer could run it on. That is the shape of every other spec source audit (check-exported-any, check-dual-source-exports, check-error-code-provenance).

Why the name-only rule is a census row and not a verdict: judged by name alone ("a key called sessionTimeout with no unit anywhere") the rule fired 44 times on ca46f8f12, and most were counts wearing a duration's vocabulary — contextWindow, slidingWindowSize, snapshotInterval ("every N events"), reflectionInterval ("every N interactions"), backoffMultiplier, staleKeys. A rule that cannot tell a window of tokens from a window of seconds would either grandfather those by name or teach authors to append Ms to a count. --list still prints the ~25 genuine unit-nowhere keys (the #14519 shape: logging.flushInterval, tracing.exportTimeout, tenant.schemaCacheTTL, plugin-lifecycle-advanced.shutdownTimeout, …) so the population stays visible.

2. The seven conversions — one ADR-0087 entry each, ⛔ no alias, no transition window

schema before → after route ADR-0087
HookSchema (hooks[]) timeouttimeoutMs retiredKey() tombstone on the strict shape (carries the rename; tsc never + parse); alias timeoutms → timeout removed D2 hook-timeout-to-timeout-ms (retired from the load path) + step 18
JobSchema (jobs[]) timeouttimeoutMs tombstone; alias timeoutMs → timeout removed; system/Job:timeout registered D2 job-timeout-to-timeout-ms + step 18
DriverOptionsSchema timeouttimeoutMs tombstone (non-strict); data/DriverOptions:timeout registered semantic driver-options-timeout-to-timeout-ms (a per-call options object has no stack seam)
MetadataManagerConfigSchema cache.ttlcache.ttlSeconds; cache.databaseLoader.ttlcache.databaseLoader.ttlMs two tombstones (non-strict nested objects) semantic metadata-manager-config-cache-ttl-unit-in-key
DatabaseLevelIsolationStrategySchema / TenantSecurityPolicySchema connectionPool.idleTimeoutidleTimeoutSeconds; accessControl.sessionTimeoutsessionTimeoutSeconds; describes now say "in seconds" two tombstones semantic tenant-timeouts-unit-in-key

Every old spelling is refused with a prescription naming the new key (pinned per schema in hook.test.ts, job.test.ts, driver.test.ts, metadata-loader.test.ts, tenant.test.ts, each with a tsc-channel case). Hook is not on the authorable surface (its handler is a function), so it has no RETIRED_KEYS_BY_MAJOR row; the two nested ttl keys and the tenant keys are not surface rows either.

#14519 is genuinely completed and carried as Fixes: both tenant keys carry their unit in the name and their .describe() now says "in seconds" — pinned, because .describe() is what content/docs/references/** publishes and the JSDoc above a key is not, so the reference-page reader was the one reader who never saw the unit. #14519's own proposed fix (add the unit to the describe only) is exactly what the new gate refuses, which is why the keys were renamed instead.

3. Readers, in the same PR

reader before → after
packages/metadata/src/loaders/database-loader.ts (:136, :252) DatabaseLoaderCacheOptions.ttlttlMs; ttl: cacheOpts?.ttlMs ?? 60_000 (same magnitude)
packages/objectql/src/hook-wrappers.ts:358 meta.timeoutmeta.timeoutMs
packages/spec/src/contracts/job-service.ts JobScheduleOptions.timeouttimeoutMs — renamed in lockstep: a contract key that re-spelled the value without its unit would reintroduce one layer down exactly the ambiguity the rename removed
packages/runtime/src/app-plugin.ts:1098 threads { retryPolicy, timeoutMs: job.timeoutMs }
packages/services/service-job/src/run-with-policy.ts:127, db-job-adapter.ts options?.timeoutMs; withoutPolicy strips timeoutMs
examples/app-showcase/src/automation/jobs/index.ts:23 timeoutMs: 300000
packages/spec/src/data/hook.form.ts:71 form field timeoutMs (i18n bundles regenerated with node scripts/check-i18n-bundles.mjs --write)
liveness ledgers hook.json / job.json timeoutMs live rows with the same anchors; timeout rows kept as dead tombstone rows (the retiredKey route keeps the key in the walked shape)
docs: content/docs/automation/jobs.mdx, content/docs/protocol/kernel/metadata-service.mdx, packages/metadata/README.md:180 new spellings
skills/objectstack-data/references/data-hooks.md (governed) timeoutMs in the example and the two prose lists

The README candidate is in scope, and was changed: it sits in the package whose reader is renamed here, it demonstrates the exact spelling the schema now refuses, and check:skill-examples-style example rot is the failure this rule exists for. Cost one line. The bare-key sweep was not done: playwright.config.ts, sqlite-occupancy.ts, serve-process.ts, the compose file, SMTP transport timeout, RegistryConfig.cache.ttl (its own schema, in the remainder) are different keys on different schemas and were left alone — every hit was disambiguated to its declaring schema first.

CORRECTION — fix lap, 2026-09-05, head e68ae2b5. The list above originally also named turso timeout among the "different keys on different schemas". That was wrong. It is corrected in place rather than deleted, because the mistake names its own failure mode: turso carries two timeout spellings and the disambiguation collapsed them into one.

  • TursoDriverConfig.timeout (packages/drivers/driver-turso/src/turso-driver.ts:108) and its zod twin packages/drivers/driver-turso/src/spec/turso.zod.ts:104 — the driver's OWN connection config. It lives outside packages/spec/src/**, which is this gate's entire declared population (ROOT_DIR_WATCH_HINTS, held by the script's self-test). Genuinely a different key on a different schema, and still untouched — correctly.
  • packages/drivers/driver-turso/src/turso-driver-options-door.test.ts:119 — a literal typed by the Parameters lookup on argument 3 of TursoDriver.update. That argument is DriverOptions, so the key written there was exactly the one this PR renames. Missed.

The miss was an UNDER-collection — the mirror image of the over-collection the disambiguation was guarding against — and it is what turned Type Check · workspace red (run 33932953271, job 101215162163, head 99999540a): TS2322: Type 'number' is not assignable to type 'undefined' at :119, TS18048 at :121. Both are the retiredKey tombstone type doing precisely what it is for. Fixed by moving the literal to timeoutMs at the same magnitude — milliseconds in and out, no value conversion.

Seat coordinates re-measured and confirmed: database-loader.ts:252 (not :204), hook-wrappers.ts:358 (not :357), run-with-policy.ts:127, jobs/index.ts:23. One seat assertion the tree contradicts: the card says the two tenant keys are "on the authorable surface"; authorable-surface/system.json carries no Tenant* row at all (the only tenant rows are cloud/ProvisionTenantRequest:*), so no RETIRED_KEYS_BY_MAJOR entry exists for them and none is owed.

4. Changesets — level derived from the repo's rule, not from the dispatch

scripts/check-changeset-no-major.mjs (header: breaking changes ship as minor during the launch window; the BREAKING banner and the ADR-0087 disposition are the carriers) + pr-automation.yml "WHICH LEVEL" + precedent in packages/spec/CHANGELOG.md (**BREAKING** … shipped as minor):

  • @objectstack/spec minor, BREAKING banner, adr-0087: registered naming the five ids;
  • @objectstack/metadata minor, BREAKING (DatabaseLoaderOptions.cache.ttlttlMs is an exported interface member), adr-0087: registered metadata-manager-config-cache-ttl-unit-in-key (the gate refused already-registered for an id this diff adds — measured, corrected);
  • @objectstack/objectql, @objectstack/service-job, @objectstack/runtime patch — they read the renamed key; no public surface of their own moves.

node scripts/check-adr-0087-registration.mjs --base origin/main✓ check-adr-0087-registration: 2 declared-breaking changeset(s), each carrying an ADR-0087 disposition.

5. Skills line readings (the skills/** diff)

skills/objectstack-data/references/data-hooks.md: 979 → 979 lines. Whole package skills/objectstack-data/**/*.md: 3736 → 3736. All skills/**/SKILL.md: 6835 → 6835. Net zero; no re-wrap, no content bought.

Out-of-scope finding, filed (not ridden along)

Verification (final head 99999540a)

  • pnpm --filter @objectstack/spec buildVERDICT command-exit 0 (161s, then 196s after the tombstone text edit); check:generated --fix on 14 of 15, the 15th (check:react-declaration-parity) needs objectui's manifest and cannot run here.
  • pnpm --filter @objectstack/spec typecheck (src + scripts + test-typecheck) → VERDICT command-exit 0.
  • pnpm --filter @objectstack/metadata --filter @objectstack/objectql --filter @objectstack/service-job --filter @objectstack/runtime typecheckVERDICT command-exit 0.
  • vitest, spec: hook, job, driver, tenant, metadata-loader, conversions/, migrations/, alias-integrity, retired-key-migrate-sentence, strict-object, hook-body, hook-form, check-generated-ledgerTest Files 16 passed, Tests 719 passed (after the last three job fixtures were renamed).
  • vitest, readers: metadata database-loader.test.ts 86 passed; objectql hook-binder + hook-metrics 32 passed; service-job db-job-adapter.timeout + cron-job-adapter + interval-job-adapter 40 passed.
  • check:liveness✓ every governed-type property … is classified (hook 22 classified, live 19 dead 3; job 16, live 15 dead 1); check:i18n → 0 after regeneration; check:skill-examples → 0 (needs client-react built first — measured: a stale-dist refusal until then).
  • Gate families derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (147 commands; exit codes captured before any pipe): all green on the final head except — check:duration-unit-keys 1 (the 65 remaining offenders, by design); check:dual-build-cjs-loads 3 PREREQUISITE NOT MET (needs a full pnpm build; NOT MEASURED); check-engine-split-ratio --days 90 2 (shallow clone refusal; NOT MEASURED); check-required-contexts --verify-required-set 2 in the farm (HTTP 401 without the proxy) and 0 re-run as NODE_OPTIONS=--use-env-proxy; check:pm-dispatch-gates run detached per its own header → ✓ dispatch-gates self-test: 1445 cases pass. (exit code not captured — detached).
  • check:docs-audit-scope red once mid-farm on an earlier head (self-test case "every contract declaration admitted is a packages/spec API declaration") and green on every re-run, including the pristine base worktree. Not new and not this PR's: check:skill-examples leaves packages/spec/.examples-build/ behind, and check:docs-audit-scope then fails its own self-test in the same working tree #15446 records exactly this sequence — check:skill-examples leaves packages/spec/.examples-build/ behind and the docs-audit self-test then admits it — and the farm had run check:skill-examples immediately before. Duplicate-searched before concluding (control query answered spec: duration-shaped number keys carry their unit in describe prose only — two ttl keys with different units in one block, bare timeout keys, unit-less tenant timeouts #14478).

Ablation — the gate measures something, and the refusals fire

Script kept in the session scratchpad; every leg confirmed on disk before its reading, restored with a trap and proved by blob hash (5d7306265c5c… before and after) plus git diff HEAD empty:

  • A — inject one offender (src/zz-ablation-offender.zod.ts, cooldown "in seconds"): 65 → 66 offenders, the injected site named; file removed → 65.
  • B — --root on scratch trees: a tree of three compliant keys (ttlMs, retentionDays, a { value, unit } pair) → exit 0, "zero offenders"; the same tree plus one ttl "in seconds" → exit 1, 1 offender.
  • C — mutate the detector (prose regex made unmatchable, grep -c 1 on the marker, 0 on the original anchor): self-test 7 cases red, production count 65 → 2 — the self-test is the instrument watching the rule, as check-self-test-wired requires.
  • ADR-0087 refusals: each old spelling is pinned to fail with the rename prescription (see the five test files), and the D2 fixtures replay in conversions.test.ts / migrations.test.ts.

Fix lap — workspace type check restored (head e68ae2b5)

One file changed since 99999540a: packages/drivers/driver-turso/src/turso-driver-options-door.test.ts, timeout to timeoutMs. Scope was the type check and nothing else — the gate's 65 remaining offenders are untouched and Lint & Repo Gates stays red by design.

  • Swept by TYPE, not by name. Every file naming DriverOptions outside node_modules (73 of them) was read for the retired spelling, and separately every bare timeout: / ttl: key literal and every .timeout property read under packages/**, apps/** and examples/**. One site: the turso door test. The empty results are part of the reading — memory, mongodb, sql and sqlite-wasm each carry their own door / conformance tests and none writes the renamed key; NoSQLQueryOptionsSchema.timeout, DataEngineExecuteRequest.options (a free-form z.record), LRUCache.ttl, lifecycle.ttl, the SMTP transport timeout and HealthCheckConfig.timeout are all other schemas, and the surviving ttl readers already spell ttlMs / ttlSeconds.
  • Reproduced before fixing. pnpm --filter @objectstack/driver-turso typecheck on 99999540a reproduced the two CI errors verbatim — VERDICT command-exit 2.
  • Green after. The same command plus pnpm --filter @objectstack/driver-turso testVERDICT command-exit 0, Test Files 44 passed (44), Tests 1159 passed (1159).
  • The CI job's own command, re-run on e68ae2b5: pnpm exec turbo run typecheck --concurrency=2 --filter='./packages/*' --filter='./packages/*/*' --filter='./apps/*' gives Tasks: 135 successful, 135 total — 70 of them typecheck tasks, all five driver packages among them, zero error TS, VERDICT command-exit 0. That is the answer to "is a second driver hiding behind the first": turbo tore down 21 tasks after turso failed on 99999540a, and here every one of them ran.
  • No changeset owed — derived, not guessed. The one changed file is a *.test.ts. @objectstack/driver-turso publishes files: ["dist","README.md","CHANGELOG.md"] built from a src/index.ts entry, and check:published-files re-confirms every publishable package "admits no test": the edit releases nothing, which is case 2 of pr-automation.yml "WHICH LEVEL". No public surface of that package moves — the type that moved is DriverOptions, and @objectstack/spec already carries the BREAKING changeset for it. The skip-changeset label is not the remedy either: that is for a PR releasing nothing at all, and this one releases plenty.
  • Gates re-run on e68ae2b5, each exit code captured by redirecting to a file before any pipe: check:nul-bytes 0, check:cross-package-test-inputs 0, check:test-source-alias 0, check:type-source-resolution 0, check:published-files 0. check:duration-unit-keys 1, printing ✗ check:duration-unit-keys — 65 offender(s) among 215 duration-shaped numeric key(s) in 762 source file(s) — the same 65 as before this lap, so the held decision is untouched.
  • Filed, not ridden along: finding: check:duration-unit-keys scans packages/spec/src/** only — the same offender shape exists in workspace packages the gate never reads #15642 — the gate's declared population is packages/spec/src/** only, and the same offender shape exists outside it (measured: --root ../drivers/driver-turso/src reports 1 offender, src/spec/turso.zod.ts:104). A second axis of the same open population question, so it is recorded rather than answered.

🤖 Generated with Claude Code

https://claude.ai/code/session_01G4138K1EG7kQ81FNba5Kp4


Generated by Claude Code


Generated by Claude Code

@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 6 package(s): @objectstack/metadata, @objectstack/objectql, @objectstack/platform-objects, @objectstack/runtime, @objectstack/service-job, @objectstack/spec, touching 39 documentable anchor(s). ⚠️ 9 changed file(s) yielded no anchor (packages/metadata/README.md, packages/objectql/src/hook-binder.ts, packages/spec/authorable-surface/data.json, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

22 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json d30ccb9bd963942d33072443f680a32664087346.

4 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 9 changed file(s) yielded no anchor (packages/metadata/README.md, packages/objectql/src/hook-binder.ts, packages/spec/authorable-surface/data.json, …) — pages documenting those are invisible to this run
  • 15 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 61 of 219 client-bound route-ledger rows — the other 158 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 158: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • 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 d30ccb9bd963942d33072443f680a32664087346packageMentionDocs.

Which tree this was computed on

This run read content/docs from 849565670761f6dc23342a33ce614fc1a1959759 — the merge of head e68ae2b5822f7358f181df6c54bbd94f2e9d9ad3 into base d30ccb9bd963942d33072443f680a32664087346, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 849565670761f6dc23342a33ce614fc1a1959759 && git checkout 849565670761f6dc23342a33ce614fc1a1959759
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin d30ccb9bd963942d33072443f680a32664087346 e68ae2b5822f7358f181df6c54bbd94f2e9d9ad3 && git checkout -B drift-repro d30ccb9bd963942d33072443f680a32664087346 && git merge --no-ff e68ae2b5822f7358f181df6c54bbd94f2e9d9ad3

node scripts/docs-audit/affected-docs.mjs --json d30ccb9bd963942d33072443f680a32664087346

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs d30ccb9bd963942d33072443f680a32664087346 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

`turso-driver-options-door.test.ts` builds a `Parameters<TursoDriver['update']>[3]`
literal — that argument IS `DriverOptions`, so the `timeout` key it wrote is the
one renamed to `timeoutMs` here, not a driver-local key. Same magnitude
(milliseconds), no value conversion. `TursoDriverConfig.timeout` in
`turso-driver.ts` and the `timeout` in `src/spec/turso.zod.ts` are a different
key on the driver's own connection schema and stay as they are.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G4138K1EG7kQ81FNba5Kp4

Copy link
Copy Markdown
Collaborator

Director audit note — both "blockers" relayed to the maintainer on this PR are already settled on the record; one measured fact added on mergeability

Director seat (objectstack #12708), summon #14, session session_01LsEjuNMPitCHwEfYftZ1um, 2026-09-05T04:2xZ. Audit only — this seat does not drive this PR.

1. The population boundary is ruled, not open. Batch #43 ruled B on #14478 at 5548763981 (2026-09-05T02:32Z): the rule governs every authored and every runtime-emitted duration; epoch instants move to a shared EpochMs schema and external-vocabulary keys carry a .meta({ externalVocabulary }) marker, both declared on the schema and never in a gate ledger; the gate's population widens to every workspace package (folding #15642). The domain:spec seat session_01M59rPZZFzqhfMUPFqqZTkf took it into execution at 5548885230 (02:56Z): six stacked sub-issues #15676#15682, this PR resumed under that seat as the stack's base. A message stating "人口边界未裁" was written before, or without re-reading, that ruling.

2. Mergeability — measured now, not relayed. Fetched refs/pull/15626/head (e68ae2b5) and origin/main (1c142140; the branch is 53 behind / 5 ahead, merge-base ca46f8f1):

git merge-tree --write-tree --name-only origin/main refs/pr/15626   → exit 1
CONFLICT (content): packages/spec/liveness/state-counts.md

One conflict, in a generated liveness-count file; the eight other touched files (lint.yml, the platform-objects translation bundles) auto-merge. So GitHub's dirty is true on today's main; a "clean" three-way reading was true of an older main. The repair is the repo's own regenerator for that file (the liveness ledger tooling this PR already ran), not a hand edit — then a push, which is the only thing that makes GitHub recompute.

3. Whose trip it is. Per the ruling's execution rule (2) and the seat's own comment, this PR is driven by session_01M59rPZZFzqhfMUPFqqZTkf; the merge-main-and-regenerate push belongs to that seat, and it belongs before sub-issue 1/6 (#15676) is cut off this branch — a stack built on a conflicted base carries the conflict into every layer. ⛔ Still governed (skills/**): draft until the stacked head is green on check:duration-unit-keys, review from hotlong, human merge.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cd dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation needs:contract-review protocol:data protocol:system size/xl tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

spec: tenant.zod.ts idleTimeout / sessionTimeout publish a describe with no unit, while the JSDoc one line above says seconds

3 participants