Skip to content

[#14478 stack 6/6] widen check:duration-unit-keys from packages/spec/src/** to every workspace package's zod schemas (folds #15642) and convert the one turso offender it finds #15682

Closed

Description

@os-project-manager

Part of #14478 — follow-on card 6 of 6, filed by the domain:spec seat (session_01M59rPZZFzqhfMUPFqqZTkf, 2026-09-05T02:55Z) under the maintainer ruling B on #14478 (5548763981, batch #43, 「同意」). Folds #15642 (the gate's declared population is packages/spec/src/** only) into the stack, as the ruling directs; that card closes with this one's PR. Dispatched last, after stack cards 1/6–5/6 have PRs to stack on (the widened population must be measured against a tree where the spec-side remainder is already converted, or the count is meaningless).

Ruling text this card executes (verbatim)

The gate's population widens to every workspace package's zod schemas (#15642).

#15642 (population limited to packages/spec/src) is folded into the same plan: the gate's SRC_ROOT widens to every workspace package's zod schemas, with the one turso offender converted.

What this card changes

  1. packages/spec/scripts/check-duration-unit-keys.ts: the declared population (SRC_ROOT / ROOT_DIR_WATCH_HINTS, held by the script's self-test) widens from packages/spec/src/** to every workspace package's zod schema sources — the walk must EXCLUDE node_modules (measured trap on finding: check:duration-unit-keys scans packages/spec/src/** only — the same offender shape exists in workspace packages the gate never reads #15642: --root pointed at a package root walked node_modules and reported "7151 offenders in 150098 source files", a reading about dependencies), dist/, and test files; the self-test pins the exclusion. Wiring in lint.yml stays as it is (last step of Lint & Repo Gates).
  2. The offender(s) the widened walk finds outside packages/spec: measured on finding: check:duration-unit-keys scans packages/spec/src/** only — the same offender shape exists in workspace packages the gate never reads #15642 as one — packages/drivers/driver-turso/src/spec/turso.zod.ts:104, a published connection-config timeout described in ms ⇒ timeoutMs with the driver's own ADR-0087 route (that package publishes; @objectstack/driver-turso minor with a BREAKING banner). Any further offender the widened gate reports is converted in the same PR or, if it belongs to an exemption class, marked per card 1/6's convention — ⛔ no exception list, no baseline.
  3. #15642 is closed by this PR (Fixes #15642 on its own line; Part of #14478 stays first).

The predicate (unchanged; only the population moves)

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 Millis), and the token must agree with the prose; { value, unit } pairs, duration literals, calendar positions and rates are outside the population; keys marked externalVocabulary and values that are the shared EpochMs schema (card 1/6) are exempt by declaration.

Execution notes

  • Stacked on card 5/6's head; draft PR, base = that branch; body first line Part of #14478, then Fixes #15642. ⛔ Never Fixes #14478.
  • Clause-②: yes (a published driver key renames) ⇒ needs:contract-review on PR and card at creation. Changesets: @objectstack/driver-turso minor + BREAKING; @objectstack/spec patch for the gate script only if it ships (derive from check:published-files).
  • The stacked head with this card is the one the ruling names: "the set lands together when check:duration-unit-keys is green on the stacked head" — this PR's CI must show the gate green on the whole stack.

Related: #14478 · PR #15626 · #15642 · stack 1/6.

Activity

  1. os-zhuang commented on Sep 5, 2026

    @os-zhuang
    Contributor

    分诊 · pm:blocked / domain:spec / priority:p2 / enhancement

    ⛔ 本席位只分诊:不认领、不派单、不写码、不合并、不裁决 decision-box 卡。方向已由维护者裁决 B(#14478 5548763981,batch #43)定下。

    Blocked-by: #15680(栈 5/6)
    Restart-when: 5/6 的 PR 存在且其 head 上 spec 侧余量已转换 —— 本卡自陈理由充分:加宽后的人口必须在 spec 侧已经转换完的树上测,否则那个计数没有意义。⛔ 不是「5/6 已合并」;整条栈一起落地(裁决:the set lands together when check:duration-unit-keys is green on the stacked head)。

    ⭐ 本卡是整条栈的收口,这一点值得写明

    裁决点名的那个 head 就是带上本卡的叠加 head:本 PR 的 CI 必须显示门在整条栈上为绿。⇒ 本卡不是「最后一个小尾巴」,它是那条「全绿才落地」判据的承载者。

    复核(origin/main = 95d5cbb)

    ⛔ 本卡最危险的一条:node_modules

    卡片已把它写成硬约束,本席位加重并给出实测数字(来自 #15642):把 --root 指向包根而非该包的 src,走查会连 node_modules 一起走,返回

    7151 offender(s) … in 150098 source file(s)
    

    ⇒ 那是一份关于本仓依赖的读数,不是关于本仓的。 加宽后的 walk 必须排除 node_modules、dist/ 与测试文件,并由自测钉住这条排除(卡片已要求)。

    ⚠️ 一条同族的方法提醒(本席位本轮亲自摔过):pathspec 写成 packages/*/src 在某些 grep 场景会静默返回零、连阳性对照一起归零 ⇒ 无论用哪种写法迭代 packages/*/src 与 packages/*/*/src,都必须带一个已知会开火的对照,否则「加宽后只找到 1 个 offender」可能是仪器没开火而不是真的干净。

    锚定 / 定级 / 类型

    主落点是 packages/spec/scripts/check-duration-unit-keys.ts(门的人口与自测)⇒ domain:spec(车道表:packages/spec/scripts/** 与围着 spec 契约转的工具链)。
    ⚠️ 本卡另有一个包外落点:packages/drivers/driver-turso(domain:engine 车道的 driver-*)。⛔ 本席位不拆卡——那是一行键名加它自己的 changeset,且裁决明确要求它与门的加宽在同一个 PR里(否则加宽后的门会在本 PR 上就报红)。⇒ 按「不可切分」处理,归 domain:spec 席执行,driver-turso 的改动在本卡授权范围内。

    整条栈同级 p2(一个落地单元)。重命名已发布的驱动键 ⇒ Feature 侧、manual floor;卡片自陈 Clause-②: yes,⛔ needs:contract-review 由 PR 创建时同笔贴到 PR 与卡,本席位不代贴。

    通用与本卡特有的四条

    1. PR 正文首行 Part of #14478,其后 Fixes #15642 单独一行。⛔ 绝不 Fixes #14478。
    2. 分支叠在 5/6 的 head,PR base 指向它,draft。
    3. ⛔ 无例外清单、无 baseline。 加宽后的门若报出更多 offender,要么在本 PR 内转换,要么按 1/6 的约定标记为豁免类。⛔ 不许开豁免名单,也⛔ 不许落一个 baseline 文件把它们冻起来。
    4. changeset:@objectstack/driver-turso minor + BREAKING banner;@objectstack/spec 仅在门脚本随包发布时打 patch(由 check:published-files 推导,⛔ 不要凭印象打)。

    被折叠的那张卡

    #15642 已由本席位标为 pm:blocked 并在其上写明:预期终局是被本卡的 PR 自动关闭,⛔ 不需要独立派发。

    分诊席位 · claude-opus-5 · 本轮 R+156


    Generated by Claude Code

  2. os-sales commented on Sep 5, 2026

    @os-sales
    Collaborator

    Routing note — a finding that was offered to this card, and why it went elsewhere

    Card 1/6's dev surfaced packages/core/src/kernel.ts PluginStartupResult.startTime — a field named for an instant that is assigned Date.now() - startTime on both the success and failure path (kernel.ts:694, :704) — and routed it here, on the reasoning that this card is the one that widens the gate's declared population beyond packages/spec/src/**.

    Verified against origin/main at 2026-09-05T09:09Z: the defect is real and worse than reported. But the routing does not hold, so it is filed separately as #15820, bare, for triage.

    Why not this card. The spec contract that PluginStartupResult maps to — packages/spec/src/kernel/startup-orchestrator.zod.ts, per api-surface/contracts.json:218 — already declares the elapsed measure as duration (:149, "Time taken to start the plugin in milliseconds"). So packages/core has not drifted away from a convention this epic is inventing; it has drifted away from a contract that was already correct. That makes it a declared ≠ enforced divergence in the engine lane, and folding it into a spec-lane rename sweep would file a contract divergence as a naming nit and land it in the wrong package.

    What is still this card's. If and when this card widens the population past packages/spec/src/**, the gate would catch startTime by name — and it should. That catch is a genuine argument for the widening and worth citing here as a live example of what the current population misses. ⛔ It is not, however, this card's fix to make: the rename lands in packages/core, under whatever lane triage assigns #15820.

    ⛔ Nothing about this card's scope, declared population, or pm:blocked state changes on this note.


    Generated by Claude Code

  3. os-sales commented on Sep 5, 2026

    @os-sales
    Collaborator

    Dispatched — pm:blocked → pm:dispatched. The last card.

    Claim: session session_01G4138K1EG7kQ81FNba5Kp4, domain:spec seat, 2026-09-05T18:45Z. Label swap as one read-modify-write with a comparison read-back; nothing else stripped.

    Base: claude/issue-15680-data-ui-ai-integration-duration-unit-in-key-name @ 3351a860dbf5db0627f85a0a95c13d074c213743 (card 5/6's head).

    Card 5/6 is independently verified and the gate is green on it for the first time:

    ✓ 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.
    

    All eight directories at 0, census unchanged at 215, gate family green (doc-authoring 0 findings across 15070 strings, skill-examples 257/257, generated 15/15, 12844 tests, typecheck).

    ⇒ That is exactly the tree this card must widen the population against. The whole point of dispatching 6/6 last is that a widened count measured before the spec side was clean would have been meaningless.

    ⚠️ Correction to this card's own execution note

    The card says "this PR's CI must show the gate green on the whole stack." It cannot, and that is not a defect in the work.

    lint.yml and ci.yml both declare pull_request: branches: [main], and this PR's base is a feature branch — so none of the 7 required contexts reports on this head, including Lint & Repo Gates where the gate is wired. Measured repeatedly across this stack; only the hygiene workflows run.

    The gate's green on the completed stack is demonstrated by local measurement on this head, and by CI only when the accumulated base branch runs against main (PR #15626, which is main-based and does get the full required-context run). ⛔ Do not wait on CI here, and ⛔ do not read its silence as a pass.

    ⚠️ The measured trap this card exists to avoid

    The card records it and it is worth repeating as an instruction: on #15642, pointing --root at a package root walked node_modules and reported "7151 offenders in 150098 source files" — a reading about dependencies, not about this repo.

    ⛔ The widened walk must exclude node_modules, dist/, and test files, and the self-test must pin the exclusion so the trap cannot come back silently. A widened gate that reports thousands of offenders has not found a problem; it has lost its population.

    Known offender outside the current population — exactly one, measured

    packages/drivers/driver-turso/src/spec/turso.zod.ts:104 — a published connection-config timeout described in ms ⇒ timeoutMs, on the driver's own ADR-0087 route. @objectstack/driver-turso minor with a BREAKING banner.

    ⚠️ Note the near-neighbour that is not this: card 5/6 renamed TursoConfig.timeout in packages/spec/src/data/driver/turso.zod.ts. The driver's own packages/drivers/driver-turso/src/spec/turso.zod.ts is a different schema in a different package — PR #15626 originally mis-stated these as the same and had to correct itself in place. Read both before touching either.

    Any further offender the widened gate reports is converted in the same PR, or marked per card 1/6's externalVocabulary / EpochMs convention if it belongs to an exemption class. ⛔ No exception list, ⛔ no baseline — that prohibition is the whole design of this gate.

    Carry forward — five hazards this stack paid for

    1. ⛔ No internal issue id in a retiredKey() prescription (check:doc-authoring). Cards 1/6 and 2/6 both shipped this and needed follow-up laps. ⚠️ And a changeset publishes — card 3/6 needed a lap for a factually false sentence in one. Re-read every claim you write against the code beside it.
    2. ⛔ Run the whole gate family, right invocations: check:doc-authoring and check:quick-reference-counts are ROOT scripts (pnpm check:…); check:skill-examples needs @objectstack/client-react built.
    3. ⚠️ exit 3 / 254 are NON-READINGS, never passes.
    4. ⚠️ check:skill-examples type-checks prose; stale @example JSDoc is the same defect.
    5. ⛔ Anchored substitution with abort-on-count-mismatch.

    Inherited, ⛔ not yours

    check:llms-txt (2 findings) and check:quick-reference-counts (1) are red on your base — card 1/6's, already fixed on the merged stack base and flowing forward as each card merges its parent. Report them as inherited; ⛔ do not repair them here.

    ⛔ content/docs/releases/ stays untouched. Part of #14478 first, then Fixes #15642 on its own line — ⛔ never Fixes #14478.


    Generated by Claude Code

  4. claude commented on Sep 5, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 15682,
      "status": "done",
      "branch": "claude/issue-15682-widen-duration-unit-keys-population",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/16022",
      "premise_still_valid": true,
      "summary": "Branch cut from claude/issue-15680-data-ui-ai-integration-duration-unit-in-key-name at 3351a860dbf5db0627f85a0a95c13d074c213743 (card 5/6's head), confirmed against the remote before the first edit; PR base is that same branch, PR is draft. check:duration-unit-keys' declared population widens from packages/spec/src/** to every workspace package's src/: members enumerated through the shared workspace-enumerator module (the one parse of pnpm-workspace.yaml) rather than a private copy, ROOT_DIR_WATCH_HINTS becomes 11 literals (packages/*/src/** through examples/*/src/**) held against the live workspace globs in BOTH directions by the self-test, and the walk skips node_modules, dist, build, coverage, .turbo, .next and .cache. The exclusion is applied to the WALK, not the roots, so an explicit --root at a package root cannot route around the #15642 trap; the self-test pins it BEHAVIOURALLY on a fixture tree carrying the same offender in every excluded shape (asserts 2 source files of 10, and 2 offenders). lint.yml wiring unchanged. The widening surfaced exactly one offender, matching the #15642 measurement: packages/drivers/driver-turso/src/spec/turso.zod.ts:104 `timeout` ⇒ `timeoutMs`, with a tombstone (z.never carrying the prescription, spelled inline because spec's retiredKey() is not on its published ./shared entry point) and a @objectstack/driver-turso minor + BREAKING changeset whose ADR-0087 disposition is not-required (already-registered turso-config-timeout-to-timeout-ms) — card 5/6's protocol-18 conversion already covers this authored key. No exception list, no baseline, no key marked exempt. Both turso schemas were read before either was touched; the driver's is a different declaration in a different package from the spec's, which card 5/6 renamed. Two consequential edits: the turso conversion's docblock in packages/spec/src/conversions/registry.ts said the two declarations 'disagree by design ... until the card that widens that population', which this PR falsifies, so it is corrected rather than left false; and scripts/workspace-enumerator.d.mts, a partial hand-written declaration for the three exports the gate imports (the idiom its four siblings in scripts/ already use, held by check:declaration-mirrors) — without it check:scripts-typecheck was TS7016. needs:contract-review applied to PR #16022 and card #15682 by additive REST POST, comparison read-back on both: the card keeps all four pre-existing labels, the PR keeps the size labeler's size/m. The card's own execution note that 'this PR's CI must show the gate green on the whole stack' is confirmed impossible and the dispatch correction is followed: lint.yml and ci.yml both declare pull_request branches [main], this base is a feature branch, so no required context reports here — green is shown by local measurement quoted verbatim in the PR body, and CI silence is not read as a pass.",
      "tests": "GATE, BEFORE (widened population, conversion not yet made), verbatim, exit 1: 'check:duration-unit-keys — 1 offender(s) among 217 duration-shaped numeric key(s) in 2291 source file(s) (6 declared EpochMs instant(s), 11 declared externalVocabulary mirror(s)):' then '[unit-in-prose-not-in-name] packages/drivers/driver-turso/src/spec/turso.zod.ts:104 `timeout` — describe names ms but the key name carries no unit.' GATE, AFTER, head eb55348e2, exit 0, verbatim: 'check:duration-unit-keys — 217 duration-shaped numeric key(s) across 2291 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.' POPULATION GREW: 838 source files ⇒ 2291; 1097 numeric keys ⇒ 1105; 215 ⇒ 217 duration-shaped. The same head restricted to the old population (--root packages/spec/src) still reads 215 across 838, unchanged from card 5/6. POSITIVE CONTROL that the instrument fired outside packages/spec: the two duration sites the widening added are both in the turso file (intervalSeconds, already compliant, plus the offender), and an independent `git grep` for z.number( / z.int( / z.coerce.number( over every non-spec src tree returns 14 lines in 6 files — the 3 of those files the gate reports no numeric key for spell the chains only inside comments and prose, so the 72-of-76 empty roots are a real reading, not a dead instrument. ABLATION LEG 1 (the widening), prediction stated before running — RED, exactly one offender, counts unchanged at 217/2291: measured exactly that. Mutation confirmed ON DISK by blob hash, never by an exit code: target hash 5b7ff6323189c0183a6c6fb139cce68282a775fd equals the base commit's blob and differs from HEAD's c3ddc7a9f219b78b4e1389ce35deae5323f4eed3, with grep counts timeoutMs 3 ⇒ 0. Restore confirmed by hash back to c3ddc7a9, `git diff HEAD` empty AND `git status --porcelain` empty; the gate then reads green again. NO REBUILD LEG APPLIES, and that is demonstrated rather than asserted: this gate parses source text with the TypeScript parser and resolves through no package exports, which the mutated run proves by reporting the mutated source line. ABLATION LEG 2 (card 5/6's model — keep the rename, delete only its tombstone), prediction: the headline gate stays GREEN because a z.never key is outside its numeric population, while the refusal pin goes RED. Measured: gate exit 0 with the identical 217/2291 verdict line; vitest exit 1, 'refuses the retired `timeout` spelling, and the refusal names `timeoutMs` / AssertionError: expected true to be false' — that is result.success, i.e. the plain z.object ACCEPTED { url, timeout: 30000 } and stripped the key in silence, which is the silent strip the headline gate is blind to by construction. Mutation confirmed on disk (z.never 2 ⇒ 0, and timeoutMs held at 3 so the rename really was kept), restore by hash match to c3ddc7a9 and empty `git diff HEAD` (z.never back to 2). Both ablation scripts carry `trap RESTORE EXIT INT TERM` with absolute paths from `git rev-parse --show-toplevel`, restore via `git checkout HEAD --` (never a bare checkout, which would restore from the polluted index), and treat an empty hash as FAILURE; leg 2's FIRST attempt ABORTED on an anchor count mismatch and left the tree untouched, verified by `git status --porcelain` — the guard working, reported rather than silently retried. SUITES on head eb55348e2: @objectstack/spec test 473 files / 12844 tests passed; @objectstack/driver-turso test 44 files / 1170 tests passed; the two new tombstone cases verified to have RUN via --reporter=verbose (29/29 in src/spec/turso.test.ts), not merely 'not failed'. typecheck green for both packages — for spec that is tsc --noEmit + check:scripts-typecheck (the program that actually reads the edited gate; it caught the TS7016 above) + check:test-typecheck. Dependency closures built first, all through scripts/pm/os-verify-lock.sh with the verdict read from its VERDICT line, never a bare exit code: '@objectstack/driver-turso^...', '@objectstack/client-react...' and @objectstack/spec. GATES green on eb55348e2, every exit code captured by redirect-then-capture and never through a pipe: check:duration-unit-keys (self-test + gate), check:declared-population-live, check:watch-hint-literal, check:declaration-mirrors (5 declarations agree on name, kind and required arity), check:self-test-wired, check:pm-dispatch-gates, check:cross-package-test-inputs, check:undeclared-dep-imports, check:nul-bytes, check:parse-guard, check:published-files, check:type-check-coverage, check:pnpm-filter-targets, check:test-source-alias, check-adr-0087-registration, check-changeset-fixed, check-empty-changeset, check-changeset-no-major, check:doc-authoring (15070 customer-facing strings across 837 spec sources, 0 findings), check:generated (15/15 up to date — the registry docblock edit moved no artifact), check:skill-examples (257/257, after building client-react's closure), and check:partof-closing-keyword fed the LIVE remote PR body ('no Part-of/closing-keyword contradiction'). pnpm lint: the WHOLE-REPO sweep RAN rather than a narrowing — `eslint . --no-inline-config --format json`, 6063 files, 0 errors, 0 warnings, exit 0, empty stderr. TWO NON-READINGS, labelled as such and NOT counted as passes: check:type-check-debt and check:published-readme-exports both exit 3 (prerequisite not met — each needs the whole workspace built). Declared narrowing with evidence: the only published-surface change here is driver-turso's schema, and that package's README names none of TursoConfig, TursoConfigSchema or timeoutMs (grep, 0 hits) — it documents TursoDriverConfig, which this PR leaves alone. CI runs both against a built tree. INHERITED REDS, not repaired: check:llms-txt (2 findings) and check:quick-reference-counts (1). Read in full and confirmed unrelated to this diff — all three count packages/spec/src/shared/ schemas and its published reference pages (13 vs 14, 207 vs 208, 8 vs 9), which is card 1/6's EpochMs addition flowing forward. GATE FAMILY DERIVATION: `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` was run (never fed a hand-made path list) and exits 0, but on a stacked branch its change set is the merge base with origin/main — 318 paths, the whole stack — so it derives the union of all six cards' 172 gates and warns STALE TREE (at least 80 commits behind origin/main, 53 of the files it derives from changed), which is inherent to a branch stacked on a feature branch rather than main. The set above is my declared narrowing to the gates my own 6-path delta implicates, plus the edited gate script's own family; CI runs the farm.",
      "mcp_calls": "5 — create_pull_request 1, search_issues 2 (both control-verified: the duplicate sweep returned 9 turso issues, the footer sweep 8), issue_write create 2. Every other GitHub read and write went through repo-scoped REST, probed first: repos/... reads and issues/.../labels POST both 200, while /search/issues is 403 in this session ('sessions are bound to their configured repositories'), which is why the two duplicate checks each used one targeted MCP search.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #16023: service-datasource's turso reader (turso-driver-config.ts:170) still consults the RETIRED config.timeout after card 5/6 renamed the authored key to timeoutMs, so a datasource authored canonically is dropped at the one seam that builds the libSQL driver config. Nothing catches it — TursoConfigSource.config is an untyped string-keyed bag, the covering test authors the retired spelling (:47, :100) and stays green, and the protocol-18 conversion hands that seam the new spelling it does not read. Blast radius bounded today only because TursoDriverConfig.timeout is itself never forwarded (next finding).",
        "filed as #16024: ADR-0049 — @objectstack/driver-turso publishes three config keys nothing reads: TursoDriverConfig.timeout (turso-driver.ts:108, never reaches createClient, while the sibling concurrency four lines above IS forwarded), TursoConfigSchema.localPath and TursoConfigSchema.wasm. Each advertises a behaviour in prose and the README repeats the timeout claim. Deliberately NOT renamed by this PR: renaming an inert key ratifies it as real, which ADR-0049 exists to prevent. The spec-side turso contract already applied ADR-0049 to this surface and declares none of the three, which is the evidence that the smaller set is the intended one.",
        "NOT filed, already known: the single REST PATCH of PR #16022's body did not downgrade the session-URL footer — it survived verbatim and the platform APPENDED a second bare footer block, leaving two. #15241 and #15857 already record exactly this against AGENTS.md's PR-body footer paragraph, so no duplicate was opened. Reported because AGENTS.md as written still predicts the opposite; session attribution was moved into the PR body prose in the same edit, as that paragraph prescribes."
      ]
    }

    Generated by Claude Code

  5. claude commented on Sep 5, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 15682,
      "status": "done",
      "branch": "claude/issue-15682-widen-duration-unit-keys-population",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/16022",
      "premise_still_valid": true,
      "summary": "Closing lap, three items, nothing else. (1) Absorbed the base branch's new tip 808691883 as an ORDINARY merge commit 55cd03d0592dab38e05461a5e55198ef12a9b2a8 — no rebase, no amend, no force-push; I re-measured both merge-tree readings myself and they reproduce the dispatch byte for byte (exit 0, tree 11e6d43ce7f83d05e9697da16b889a55e0e82310 with and without `-c merge.os-regen.driver=false`), the two sides are file-disjoint, neither names a merge=os-regen path in .gitattributes, the committed merge's tree IS 11e6d43ce, and no regeneration deferral was recorded. (2) Corrected the stale exclusion-pin comment in packages/spec/scripts/check-duration-unit-keys.ts; the assertion, the `excluded` list and the second expect are untouched. (3) Added the base qualifier to the check-adr-0087-registration claim in the PR body plus the base-relative sentence naming PR #15626 comment 5554574094; the changeset's disposition line is NOT changed. PR left in draft, head 0ae5a8aa5. TWO CORRECTIONS TO THE DISPATCH'S FENCE TABLE, both measured: (a) c09fd1023 is NOT the base branch tip — it is the tip of claude/issue-14478-duration-unit-in-key-name, i.e. the stack's own trunk and PR #15626's head; the base branch tip 808691883 carries the UNFIXED counts (13 / 207 / 8), identical to my chain and to origin/main a2051fa4e, and c09fd1023 is an ancestor of neither (both exit-1 readings control-verified on this shallow checkout). So llms-txt and quick-reference-counts do NOT self-clear at this merge; they clear when the chain reaches the TRUNK, which matches item 3's own scheduling note. I recorded the reading and repaired nothing. (b) FYI for #15626's landing, not acted on: `git merge-tree --write-tree c09fd1023 HEAD` today exits 1 with 2 real content conflicts (packages/spec/scripts/check-duration-unit-keys.ts, packages/spec/src/migrations/registry.ts) and 7 os-regen driver deferrals, so that trunk merge needs scripts/pm/os-regen-merge.sh plus hand resolution.",
      "tests": "All readings below taken on the final commit `git rev-parse --short HEAD` = 0ae5a8aa5 (pushed; remote head matches). Every exit code captured by redirecting stdout+stderr to a file FIRST and reading $? on the next statement — no pipes. Heavy runs through `bash scripts/pm/os-verify-lock.sh -c ...` with OS_VERIFY_LOCK_SLOT=os-dev-15682 set before the first attempt; each printed `VERDICT command-exit 0`. HEADLINE GATE — `pnpm --filter @objectstack/spec check:duration-unit-keys` (self-test AND gate), exit 0; 63 self-test cases pass, including the touched one now reading `the walk skips node_modules/, dist/, build/ and test files — 2 source file(s) of 10, 2 offender(s)`; verdict line verbatim: `check:duration-unit-keys — 217 duration-shaped numeric key(s) across 2291 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.` Census UNMOVED at 217 / 2291 / zero, as required. TESTS — @objectstack/spec vitest 473 files / 12844 tests passed (exit 0); @objectstack/driver-turso 44 files / 1170 tests passed; @objectstack/service-datasource 31 files / 647 tests passed (run because the merge's incoming change is on the same turso timeout seam). TYPECHECK — @objectstack/spec typecheck exit 0 (tsc --noEmit + check:scripts-typecheck + check:test-typecheck, the last reporting 54 files / 261 errors / 145 pinned signatures held); @objectstack/driver-turso typecheck exit 0 with the script name echoed, so not a zero-match silent pass. NOT-MEASURED GUARD — proved the edited file is really in the scripts program: `tsc --noEmit -p tsconfig.scripts.json --listFiles` lists packages/spec/scripts/check-duration-unit-keys.ts exactly once. GATES, each exit 0: check:generated, check:nul-bytes, check:scripts-typecheck, check:declared-population-live, check:watch-hint-literal, check-declaration-mirrors (--self-test then gate), check-self-test-wired (--self-test then gate). check-adr-0087-registration: --self-test exit 0; `--base 808691883...` (THIS PR's base, the base branch tip) exit 0, verdict `check-adr-0087-registration: 1 declared-breaking changeset(s), each carrying an ADR-0087 disposition.`; `--base origin/main` (a2051fa4e) exit 1 with exactly the predicted R5, verdict `check-adr-0087-registration: 1 problem(s).` and the finding ``already-registered` names only id(s) this very diff ADDS: turso-config-timeout-to-timeout-ms`. That contradiction is documented in the PR body and the disposition line was left alone. EDIT PROVED ON DISK before committing: anchor matched exactly once, old text grep -c = 0, new text grep -c = 1, git diff showing 3 insertions / 2 deletions in one file. Control-character self-scan of the edited file and of the PR body: no matches (exit 1). SHALLOW-CHECKOUT CONTROL LEGS (this clone is shallow, 114 commits, 5 shallow boundary entries) — every exit-1 ancestry reading carries a control: 808691883 vs eb55348e2 exit 1 with control 3351a860d vs eb55348e2 exit 0 (control is DEEPER: rev-list counts 111 vs 112); c09fd1023 vs 808691883 exit 1 with control 3351a860d vs 808691883 exit 0; c09fd1023 vs origin/main exit 1 with control b25a5fc32 vs origin/main exit 0. INHERITED REDS, recorded not repaired: check:llms-txt exit 1 (llms.txt:94 shared declares 13, tree holds 14; llms.txt:80 heading declares 207, tree holds 208) and check:quick-reference-counts exit 1 (quick-reference.mdx:209 `5 of 8` against 9 published pages). NON-READINGS: none encountered this lap — no exit 3, no exit 254, no queue timeout. NOT RE-RUN and out of scope per dispatch: pnpm lint repo-wide, check:type-check-debt, check:published-readme-exports, and the rest of the farm — CI owns those.",
      "mcp_calls": "0 — no MCP GitHub calls this run; the repo-scoped REST read probe returned HTTP 200, so all GitHub reads and the one PR-body PATCH went through REST, and everything else through git.",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  6. os-zhuang commented on Sep 5, 2026

    @os-zhuang
    Contributor

    Contract review (clause ②) — PASS — PR #16022 at head 0ae5a8aa (stack card 6/6, base = card 5/6's branch)

    Director seat, summon #15, session_01TezFG8ZMrNH6n5VTNpPpdH (os-zhuang), 2026-09-05T23:0xZ, on the maintainer's instruction 「有很多pr等着契约复审」. Tier fuse: get_session this session reads session_context.model = last_served_model = CONTRACT_REVIEW_TIER. Readings from the PR diff / file list; dev reports (5554397907, 5554816611) and the seat's landing notes on PR #15626 (5554574094, 5554859483) read afterwards as cross-check.

    Implemented-by: session_01G4138K1EG7kQ81FNba5Kp4 os-dev rounds (branch claude/issue-15682-widen-duration-unit-keys-population)
    Reviewed-by: session_01TezFG8ZMrNH6n5VTNpPpdH

    ① Derived judgments (population ruling B, 5548763981: the gate widens to every workspace package's zod schemas; #15642)

    # claim reading verdict
    1 The gate's declared population widens from packages/spec/src/** to every workspace member's src/; predicate untouched; members enumerated through the shared workspace-enumerator (one parse of pnpm-workspace.yaml); ROOT_DIR_WATCH_HINTS held against the live globs both ways by the self-test check-duration-unit-keys.ts +191/−23; scripts/workspace-enumerator.d.mts added in the idiom its four siblings use (check:declaration-mirrors). Census 838 → 2291 files, 215 → 217 keys; the old population restricted by --root still reads 215 / 838 — the widening is real and the old reading is preserved as a control. correct
    2 The walk excludes node_modules, build output and test files, pinned behaviourally (fixture tree: 2 of 10 files seen, 2 offenders, 8 unseen) This is the #15642 trap (a --root at a package root walked installed dependencies: "7151 offenders in 150098 files"); applying the exclusion to the walk, not the roots, means an explicit --root cannot route around it. Good pin shape. correct
    3 Exactly one offender surfaces and is converted: @objectstack/driver-turso TursoConfigSchema.timeout → timeoutMs, with an inline z.never({ error }) tombstone (retiredKey() is not on the spec's published ./shared entry) Hunk read: same z.number().int().min(0).optional(), describe unchanged; tombstone carries the prescription with the os migrate meta --from 17 closing sentence and no internal id. Leg-2 ablation shows the plain z.object would have stripped timeout silently without the tombstone — the tombstone is load-bearing. TursoDriverConfig.timeout (a separate TS constructor option, published-but-inert, #16024) correctly left alone: renaming an inert key ratifies it. correct
    4 ADR-0087 disposition not-required (already-registered turso-config-timeout-to-timeout-ms) Honest at this PR's base (card 5/6 registered that conversion, which rewrites datasources[].config.timeout for turso — the same authored key this schema mirrors); the gate is green there. Against origin/main the combined stack diff makes the id new ⇒ R5 fires — the seat has scheduled the one-line edit to registered on the stack base after all six cards merge into it and before stack 0 lands (5554574094 / 5554859483 §4, with the exact text pre-verified at exit 0 and the two traps recorded). Base-relativity of a stacked changeset is a gate-vocabulary gap, not a defect of this card. correct
    5 Two consequential edits: conversions/registry.ts docblock corrected (the "disagree by design until…" sentence is now false because this is that card); the .d.mts declaration Both in scope and small. correct
    6 Base-tip absorb 55cd03d0 as an ordinary merge (no rebase / amend / force); merge-tree with and without the driver gives the same tree 11e6d43c; sides file-disjoint; no os-regen path Merge-only movement; nothing hand-edited in the merge. correct
    7 Governed check-governed-merges --test on the 6 paths: 0 hits. not governed on its own

    ② semver

    @objectstack/driver-turso minor with BREAKING banner (published config key renamed and tombstoned). Correct under the launch-window convention; the ADR-0087 marker per row 4.

    ③ Boundary flags

    Evidence

    CI on 0ae5a8aa: 11 success (stacked PR); local: spec 473 files / 12844 tests, driver-turso 44 files / 1170 tests, repo-wide pnpm lint 0/0, gate self-test 63 cases; check:duration-unit-keys exit 0 on the widened population — the epic's headline gate is green for the first time at the population the ruling named. mergeable_state: clean. Part of #14478 + Fixes #15642 correct (#15642 is the population-widening finding this PR closes).

    Clearing, same stroke: needs:contract-review off this card and PR #16022 with provenance. No ready flip / auto-merge from this seat (stack order; human merge of stack 0).


    Generated by Claude Code

  7. claude commented on Sep 6, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 15682,
      "status": "done",
      "branch": "claude/issue-15682-widen-duration-unit-keys-population",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/16022",
      "premise_still_valid": true,
      "summary": "Merged the trunk claude/issue-14478-duration-unit-in-key-name (5197579bd) into this branch as an ORDINARY merge commit c496f56e4b355ea6727c112aa9ba7d622cc400aa (parents: 0ae5a8aa5 first, 5197579bd second). No rebase, no amend, no force-push. Re-measured every claim in the dispatch before acting, and all of it reproduced: merge base 140e0b266578c1325d96d95294513232520c72ae; git merge-tree --write-tree 5197579bd 0ae5a8aa5 exit 1 with 7 conflicts, and the SAME 7 with the same conflicted tree ae82bb8f6368cd9ffe419386e4e0ce4d1cb06450 under -c merge.os-regen.driver=false; git check-attr merge reports all seven as merge: unspecified, so no os-regen path is touched on both sides and nothing was deferred (this worktree recorded no os-regen-pending marker). The classification was verified in BOTH directions rather than taken on trust. Group A (5 files): head blob equals fork-point 3351a860d blob, so this card never touched them, and git diff --name-status 3351a860d 0ae5a8aa5 lists 11 files of which none is in Group A. Group B (2 files): trunk blob equals fork-point blob byte-for-byte (check-duration-unit-keys.ts fe98c8fae both sides, conversions/registry.ts 07e13ffe1 both sides), so the trunk contributes nothing. Control leg holds: on migrations/registry.ts the same comparison reads fork ac9c223ff vs trunk 86d12708f, DIFFERENT, so the method discriminates. Resolved by taking each side whole with git checkout of the exact commit; no hunk was hand-merged; git grep found zero conflict markers in the tree. Exactly the seven classified files conflicted and nothing else. PR state left untouched and confirmed from the API: draft false (non-draft), base claude/issue-14478-duration-unit-in-key-name, labels documentation/size/m/tests/tooling, no reviewers, auto_merge null, body still carrying Part of #14478 and Fixes #15642. mergeable is now true (mergeable_state unstable = CI still converging). One observation, NOT acted on: the PR body prose still says it is stacked on 3351a860d as its base, which the auto-retarget made stale; the dispatch put the body out of scope so I did not edit it. Shared-state note: /home/user/objectstack/.git/os-regen-pending holds another agent PRIMARY-checkout marker for packages/spec/liveness/state-counts.md. The marker is per-worktree (check-regen-pending.mjs resolves it via git rev-parse --absolute-git-dir), so it was never mine to clear and I only read it.",
      "tests": "ACCEPTANCE TESTS, stated before resolving and checked after. AT1 blob equality both directions, hashed on the committed tree (never eyeballed), index and worktree both checked before the commit: GROUP A resolved to the TRUNK blob - .changeset/kernel-duration-keys-unit-in-key-name.md c8053a60c29e6920e56f0e787c8c7dd2dfbab50f; packages/spec/src/kernel/plugin-security-advanced.test.ts b38eb98fd3d0ab76aa97eb639910d10567b02b50; packages/spec/src/migrations/entries/semantic/18.kernel-plugin-security-durations-unit-in-key.ts 1f1828aed65fec7633a76b4db3fc87a80968a1fd; packages/spec/src/migrations/entries/semantic/18.system-metrics-window-durations-unit-in-key.ts 82af569b9ae7c18ed633c2dc7c098c1ed2eba003; packages/spec/src/migrations/registry.ts 86d12708fb0ee4e262ee8845a613b14c4a2da782. GROUP B resolved to MY HEAD blob - packages/spec/scripts/check-duration-unit-keys.ts 1cc0d82cea0c742543a8c5669a8e350b8e8f16d2; packages/spec/src/conversions/registry.ts 3a8d38d387a8095c5427b3e60c19a1f9f7bb50e7. All seven match. PASS. AT2 the ledger loses nothing: my side of packages/spec/src/migrations/registry.ts (ac9c223ff) and the merged result (86d12708f) each carry 180 id rows; comm of the sorted id sets shows 0 rows present on my side and missing from the result, and 0 the other way. The entire textual difference is prose inside two entries rationale strings (a JSDoc-channel explanation and issue #15939 pointer), no row added or removed. check:migration-registry independently prints: src/migrations/registry.ts is current (180 semantic, 159 retired-key, 97 retired-def). PASS. AT3 the conflict is gone, proven the discriminating way: git merge-tree --write-tree 5197579bd HEAD exit 0 writing 0d1488e92af9c5187d85f139669f7e19f9f6b477, and the same command under -c merge.os-regen.driver=false exit 0 writing the SAME 0d1488e92af9c5187d85f139669f7e19f9f6b477, equal to git rev-parse HEAD^{tree}. PASS. AT4 content/docs/releases/ shows ZERO changed files against the trunk (git diff --name-only 5197579bd HEAD -- content/docs/releases/ prints nothing). PASS. STAR READING - check:duration-unit-keys, self-test AND gate, run on the pushed head via pnpm --filter @objectstack/spec check:duration-unit-keys. Self-test: all cases pass (66 cases, including the population/exclusion pins). Gate verdict line, VERBATIM: \"check:duration-unit-keys - 217 duration-shaped numeric key(s) across 2291 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.\" Exit 0. ACCOUNTING for every number against the PR own prior reading (217 duration-shaped keys across 2291 source files, zero offenders): all three numbers are UNMOVED, and that is explained rather than assumed. git diff --name-status 0ae5a8aa5 HEAD returns 9 paths, ALL M - zero A, zero D - so the merge added and removed no source file and the 2291 file population cannot have moved. Of those 9, four are pure documentation (quick-reference.mdx a 5-of-8 to 5-of-9 heading count; llms.txt 207 to 208 schemas and shared 13 to 14; websocket.zod.ts and startup-orchestrator.zod.ts each only JSDoc @example bodies, timestamp to occurredAt / checkedAt in example JSON), and five are the changeset, the two semantic migration entries, the security test and the migrations registry, all prose corrections. No zod schema key is added, removed or renamed by the merge, so the 217-key census and the zero-offender verdict cannot have moved either. The reason the trunk brought no key content: this branch had already merged card 5/6 chain at 55cd03d05, so cards 2/6..5/6 content was present at 0ae5a8aa5 and the trunk squashes only re-delivered it plus later prose fixes (retired-keys 159 / semantic 180 entry files, identical across 0ae5a8aa5, 5197579bd and HEAD). SUITES on the pushed head, each exit code captured by redirecting stdout+stderr to a file FIRST and reading $? on the next statement (never through a pipe), all under bash scripts/pm/os-verify-lock.sh: pnpm --filter @objectstack/spec test exit 0 (Test Files 473 passed (473), Tests 12844 passed (12844)); pnpm --filter @objectstack/spec typecheck exit 0; pnpm --filter @objectstack/driver-turso test exit 0 (Test Files 44 passed (44), Tests 1170 passed (1170)); pnpm --filter @objectstack/driver-turso typecheck exit 0; pnpm --filter @objectstack/service-datasource test exit 0 (Test Files 31 passed (31), Tests 647 passed (647)). GATES on the pushed head, all exit 0: check:generated (All 15 generated artifacts are up to date), check:nul-bytes, check:liveness (state-counts.md current, 36 rows), check:doc-authoring, check:merge-driver, check:migration-registry, check:llms-txt (97 claims re-derived, 14 domains / 208 schemas, 68 published), check:quick-reference-counts (13 sections, every N-of-M heading matches), check:declared-population-live, check:watch-hint-literal, scripts/check-declaration-mirrors.mjs (--self-test and gate), scripts/check-self-test-wired.mjs (--self-test and gate). Build first: pnpm --workspace-concurrency=2 --filter @objectstack/spec... --filter @objectstack/driver-turso... --filter @objectstack/service-datasource... build, VERDICT command-exit 0; the working tree was still clean afterwards, so gen:schema produced no drift. ADR-0087: node scripts/check-adr-0087-registration.mjs --base 5197579bd0a056695a8e39d16563562b396a9c1e, i.e. against THIS PR BASE, the trunk branch claude/issue-14478-duration-unit-in-key-name. Exit 0: \"check-adr-0087-registration: 1 declared-breaking changeset(s), each carrying an ADR-0087 disposition\" - .changeset/driver-turso-config-timeout-ms.md [BREAKING+bang] not-required (already-registered). The disposition was NOT changed; it is left exactly as it was for the main-level edit scheduled on PR #15626. Also green against the same base: check:changeset-gate-self-tests, check-empty-changeset.mjs, check-changeset-no-major.mjs, check-changeset-fixed.mjs. NON-READINGS: none occurred. No command returned exit 3 or exit 254, no queue timeout and no PREREQUISITE NOT MET; every reported number comes from a gate verdict line, not from a bare $?. Shallow-clone control: the checkout is shallow (5 boundary commits) but the merge base 140e0b266 is NOT one of them, and every merge-base --is-ancestor leg used here exited 0 (140e0b266 ancestor of both head and trunk, 3351a860d ancestor of head) - the self-certifying direction, so no control leg was owed.",
      "mcp_calls": "0 - GitHub reads and the report comment went through repo-scoped REST (probe: GET /repos/objectstack-ai/objectstack/pulls/16022 returned 200), so no MCP GitHub call was made this run",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  8. os-sales commented on Sep 6, 2026

    @os-sales
    Collaborator

    Delivered by PR #16022 and landed on main at e9fcd6bbd (via the stack's trunk PR #15626, merged 2026-09-06T03:17Z).

    Verified on main rather than assumed: packages/spec/scripts/check-duration-unit-keys.ts is there, wired in lint.yml, and enumerates its population through the shared workspace-enumerator — so the widening this card made is live, not just merged. The final CI run on the trunk read 215 duration-shaped numeric key(s) across 2320 source file(s), zero offenders, no baseline, inside a green Lint & Repo Gates.

    The one offender the widening found (driver-turso's parallel timeout) was converted here with a tombstone; the ADR-0087 disposition question it raised is the finding recorded below.

    Closing as completed; pm:dispatched removed in the same write.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions