Skip to content

spec: closed duration types DurationMs / DurationSeconds beside EpochMs — step ① of ruling A on #18115 (declared shape for the unit-in-key census) #18122

Closed

Description

@os-elon-musk

Filed by the director seat (objectstack #12708, summon #23, session_01WCEaPsmKY4UyoivKkkaUHt) executing decision batch #134 item 1 — maintainer 「决裁批 #134 同意」 to the recommended A on #18115 (declared shape: closed duration types + unit-in-name; the 25-token name list retires from judge to detector), 2026-09-14T03:44Z. This is step ① of three; #18114 (instants → EpochMs) is independent and already queued. domain:spec by landing site; p2; triage may re-rank.

The ruling this card executes (verbatim from #18115, batch #134 item 1)

A 声明式:引入与 EpochMs 同款的闭合时长类型(DurationMs / DurationSeconds,单位集合由 6 个真时长行决定);体检只认「用了时长类型,或键名带单位」的键;17 个无单位数自然出列,6+2+≥2 个真时长各取类型或改名;名字表退为提示。

What to build

Closed duration types beside EpochMs in packages/spec/src/shared/epoch.zod.ts (or a sibling duration.zod.ts re-exported the same way — the dev chooses; both must be reachable from the package entry exactly as EpochMs is):

  • DurationMs — z.number().int().nonnegative() (or the least-surprising refinement the six genuine rows tolerate; measure their defaults: shutdownTimeout .default(30000), updateAge .default(60*60*24)), .describe('Duration in milliseconds').
  • DurationSeconds — same shape, seconds.
  • ⛔ No speculative third unit: DurationMinutes/Hours/Days are added only when a real row in step ③ needs one, in that step's PR. The unit set is derived from the conversion population, ⛔ not declared ahead of it.

Mirror EpochMs's discipline verbatim: author state and parsed state coincide (no default, no transform), so there is deliberately no *Parsed synonym; the docblock states the naming convention the gate will read (a Duration* type is one of the two declaration channels — the other is a unit token in the key name).

Acceptance

  • Clause-②: yes — new exported symbols on the published spec entry; changeset minor.
  • A pin that both types accept the six genuine rows' current defaults and refuse a non-integer / negative literal (loud at authoring, ZodError), with the EpochMs isomorphism pin pattern copied (input type = output type).
  • tsx scripts/check-duration-unit-keys.ts --self-test still exit 0 (this step changes no gate behaviour; the gate learns to READ the new types in step ②).
  • Nothing else moves: ⛔ no key converted here (step ③), ⛔ no gate change here (step ②).

Fences

  • ⛔ content/docs/releases/** untouched; ⛔ no key renames; ⛔ no admission-rule change.

Re-check before claiming

git fetch origin main
git grep -n -E 'export const (DurationMs|DurationSeconds)' origin/main -- packages/spec/src   # expected 0 today
git grep -n -E 'export const EpochMs' origin/main -- packages/spec/src/shared/epoch.zod.ts    # the precedent, expected 1

Downstream (filed alongside, pm:blocked on this card)

Step ② — the gate's admission rule; step ③ — conversion of the genuine duration rows. Both carry Blocked-by: this card; the unlock scan returns them to pm:queue when this lands on origin/main (same repo ⇒ merge is the criterion, no pin lag).

Refs

#18115 (ruling A, batch #134 item 1) · #18080 (ruling C, batch #133 item 4) · #18078 (census) · #18114 (instants tranche, independent) · packages/spec/src/shared/epoch.zod.ts

Dedup terms: DurationMs, DurationSeconds, closed duration type, epoch.zod.ts sibling, unit-in-key declared shape


Generated by Claude Code

Activity

  1. added
    pm:queueReady for the PM dispatch loop
    domain:specDomain lane — seat card indexed by label:pm:seat
    on Sep 14, 2026
  2. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Sep 15, 2026
  3. self-assigned this
    on Sep 15, 2026
  4. os-warren commented on Sep 15, 2026

    @os-warren
    Collaborator

    Claim: domain:spec execution PM seat — session_01KB5PFtxuy1x3dcR5gxudx6, claimed 2026-09-15T01:14Z.
    Branch: claude/issue-18122-closed-duration-types
    Clause-②: yes

    Dispatched to an os-dev subagent in a dedicated worktree. Labels written first (pm:dispatched on, pm:queue off, assignee set) and read back at 01:14:17Z; this comment is the second half of the atomic pair.

    Clause-② declaration. yes — this card introduces new exported symbols on the published @objectstack/spec surface, so the delivering PR and this card both carry needs:contract-review for the duration, and the gate clears from both carriers in one stroke only against an at-tier PASS naming the landed head.

    Premise re-check, run before the claim, on origin/main @ 8c657f7dd0 (2026-09-15T01:12Z). Every row is the card's own re-check command or a work item it lists:

    premise the card asserts measured verdict
    export const DurationMs|DurationSeconds in packages/spec/src 0 hits ✅ still undone
    export const EpochMs in packages/spec/src/shared/epoch.zod.ts 1 hit, :51 ✅ precedent live
    shutdownTimeout .default(30000) kernel/plugin-lifecycle-advanced.zod.ts:413 ✅ as described
    updateAge default 60*60*24 system/auth-config.zod.ts, pinned at auth-config.test.ts:109 ✅ as described
    #18115 (the ruling) closed ✅ decided, not in flight
    #18080 / #18078 (the census) closed ✅ population is measured, not pending
    #18114 (instants tranche) open, pm:queue, unassigned ✅ independent, as the card says
    #18123 / #18124 (steps ② and ③) open, pm:blocked, Blocked-by: #18122 both ✅ fan-out confirmed: this card unblocks two
    action surface packages/spec/src/shared/epoch.zod.ts last touched by #18039 ✅ no in-flight writer

    Two card imprecisions corrected here rather than left for the dev to trip on. Both are mine to state, because a dispatch order that repeats an under-specified sentence is how the last two rounds lost time:

    1. The gate script's path. The card writes tsx scripts/check-duration-unit-keys.ts --self-test. There is no such file at the repo root. The real one is packages/spec/scripts/check-duration-unit-keys.ts, run as pnpm --filter @objectstack/spec check:duration-unit-keys (that script runs --self-test first and then the gate, per packages/spec/package.json:291). The acceptance criterion is unchanged; only the spelling was wrong.

    2. What "reachable from the package entry exactly as EpochMs is" actually names. Measured, because "the entry" is ambiguous and the two readings differ in public surface:

    git grep -n 'EpochMs' origin/main -- packages/spec/src/index.ts     -> 0 hits
    packages/spec/src/shared/index.ts:40:  export * from './epoch.zod';
    packages/spec/package.json exports -> '.', './data', … , './shared', …
    

    ⇒ EpochMs is not on the root . entry. Its channel is src/shared/index.ts's export *, published as the @objectstack/spec/shared subpath. So mirroring the precedent means landing the new types where export * from './epoch.zod' already carries them, and ⛔ not adding a root src/index.ts re-export — a root export would be a wider published surface than the precedent, which is the exact widening an at-tier review is looking for. The Acceptance line's "published spec entry" reads as "published surface", and ./shared is one.

    Maintainer ruling carried into the order verbatim, untranslated (#18115, decision batch #134 item 1, agreed 2026-09-14T03:44Z):

    A 声明式:引入与 EpochMs 同款的闭合时长类型(DurationMs / DurationSeconds,单位集合由 6 个真时长行决定);体检只认「用了时长类型,或键名带单位」的键;17 个无单位数自然出列,6+2+≥2 个真时长各取类型或改名;名字表退为提示。

    The fences ride with it: this is step ① of three. ⛔ No key converted (that is #18124), ⛔ no gate behaviour changed (that is #18123), ⛔ no speculative third unit — the unit set is derived from the conversion population, not declared ahead of it.

    Concurrency. Two other rounds are live in this lane (#18230/card #17598 on the analytics dateRange arm and packages/types; #18231/card #17502 on packages/metadata-protocol and the repeater tombstones). Neither touches packages/spec/src/shared/**, so the file surfaces are disjoint. os-verify-lock.sh --status read arrival depth 1 against LOCK_DEPTH_HOLD 2 at 01:13Z, so this is the third concurrent round and it is under the ceiling.


    Generated by Claude Code

  5. os-warren commented on Sep 15, 2026

    @os-warren
    Collaborator

    os-dev-report

    {
      "issue": 18122,
      "status": "done",
      "branch": "claude/issue-18122-closed-duration-types",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/18238",
      "premise_still_valid": true,
      "summary": "Added packages/spec/src/shared/duration.zod.ts declaring DurationMs and DurationSeconds, both z.number().int().nonnegative(), each with its bare type alias, re-exported from src/shared/index.ts exactly as epoch.zod is. A sibling file rather than an addition to epoch.zod.ts: that file IS the instant file (the gate's own source names its export INSTANT_ROOT), so durations inside it would make the module name false. Nothing is converted and no gate behaviour moves. Both PM corrections re-measured and confirmed on my own base. PREMISE RE-MEASUREMENTS on origin/main 8c657f7dd0 (my base, matching the PM's): (1) `git grep -n -E 'export const (DurationMs|DurationSeconds)' -- packages/spec/src` -> zero hits, exit 1. (2) `git grep -n -E 'export const EpochMs' -- packages/spec/src/shared/epoch.zod.ts` -> 1 hit at :51, exit 0. (3) CORRECTION 2 reachability: `git grep 'EpochMs' -- packages/spec/src/index.ts` -> zero hits; `packages/spec/src/shared/index.ts:40: export * from './epoch.zod';`; package.json exports carries './shared'. After landing, `grep -l 'DurationMs' packages/spec/api-surface/*.json` returns ONLY shared.json, and `grep -c 'EpochMs|DurationMs' api-surface/root.json` returns 0 (control: the precedent is absent from root too). api-surface/shared.json delta = 4 rows added, 0 removed. No root src/index.ts re-export was added. (4) CORRECTION 1 confirmed: the gate is at packages/spec/scripts/check-duration-unit-keys.ts and `pnpm --filter @objectstack/spec check:duration-unit-keys` runs --self-test then the gate; there is no repo-root copy. (5) shutdownTimeout is z.number().int().min(0).default(30000); session.updateAge is z.number().default(60*60*24). REFINEMENT: .int() because three of the six genuine rows already declare it and both rows carrying a default default to an integer (30000, 60*60*24); .nonnegative() because it is the weakest floor every declared floor implies -- one row declares .min(0), one .positive(), none a negative floor -- and .positive() would refuse the 0 that .min(0) already admits. The six genuine rows were derived, not assumed: the gate's --list census has 23 rows with a unit in neither channel, 17 of which are counts wearing duration vocabulary, leaving exactly the 6 the ruling names (23-6=17 matches the ruling's own arithmetic). ADR-0122: Iso873/Iso874 added to type-alias-convention.pin.test.ts, count 783->785 with both prose statements moved, because check:spec-parsed-alias reads that file as its exemption registry and would otherwise refuse the two bare aliases. TWO PUBLISHED CLAIMS my new module made false were repaired in-scope, both caught by red gates: packages/spec/llms.txt (hand-kept, no generator, ships in the npm tarball) said 14 shared schemas against 15 and 199 total against 200 -- number AND row prose corrected; and quick-reference.mdx's 'Shared Protocol (5 of 9 schemas)' [total] half moved to 10, with no row added because the gate states that closing the curated gap is a decision rather than a fix. ASSIGNEE was already set by the PM (os-warren) and I did not write it; the newest Claim comment 5673171906 names my branch.",
      "tests": "All at final HEAD 4a04e07eac, worktree clean, each exit code captured before any pipe. `pnpm lint` -> 0 (WHOLE repo, eslint . --no-inline-config; not a narrowing, so no narrowing evidence is owed). `pnpm --filter @objectstack/spec test` -> 0: 'Test Files 478 passed (478) / Tests 13622 passed (13622)'. `pnpm --filter @objectstack/spec typecheck` -> 0: 'check:test-typecheck: OK -- @objectstack/spec's test layer compiles under packages/spec/tsconfig.test.json'; the new test file is NOT in the debt ledger (check:test-typecheck first caught one real type error in it -- $ZodIssue is a discriminated union, so `expected` needs narrowing on `code` rather than a cast -- which I fixed rather than ledgering, since the ledger is maintainer-only and shrink-only). `pnpm --filter @objectstack/spec check:generated` -> 0: 'All 15 generated artifacts are up to date' (4 were proved stale and regenerated by --fix on a fresh build: api-surface, export-origins, declaration-map, docs). ACCEPTANCE CRITERION `pnpm --filter @objectstack/spec check:duration-unit-keys` -> 0: '211 duration-shaped numeric key(s) across 2490 source file(s) all carry their unit in the key name ...; 6 declared EpochMs instant(s), 11 declared externalVocabulary mirror(s); zero offenders, no baseline' -- the same 211/6/11 as origin/main, which is the measured proof that no gate behaviour moved. `pnpm check:spec-parsed-alias` -> 0: '1446 bare z.input aliases, 785 pinned isomorphic, 661 paired with an XParsed'. `pnpm --filter @objectstack/spec check:llms-txt` -> 0: '97 claim(s) re-derived'. `pnpm check:quick-reference-counts` -> 0. `pnpm check:nul-bytes` -> 0 (8668 files, no raw control bytes); I also ran the out-of-gate scan `grep -naP '[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]'` over my five files -> exit 1, clean. GATE FAMILIES were derived mechanically with `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (never a hand-rolled git diff), re-derived after each commit until it stopped growing: 84 -> 109 -> 109 stable. ALL 109 RUN. 107 green. The 2 NOT MEASURED are `pnpm check:dual-build-cjs-loads` and `pnpm check:type-check-debt`, both refusing with PREREQUISITE NOT MET because they read EVERY workspace package's dist/ (they name `turbo run build --filter='./packages/*' --filter='./packages/*/*'`); a whole-farm build is CI's run, and this diff touches only packages/spec. Five further gates first refused the same way and were CONVERTED to real green readings by building @objectstack/formula, @objectstack/lint and @objectstack/client-react: check:doc-formula-expressions, check:lean-entry-closure, check:doc-security-posture, check:skill-examples, check:docs-transcript-drift. ABLATION (direction predicted before the run: turns red). Dropped .nonnegative() from DurationMs only. On-disk proof BEFORE the run rather than the editor's exit code: the anchored line count went 1 -> 0 and the mutated spelling -> 1. MUTATED run: exit 1, 'Tests 1 failed | 10 passed (11)', the failure being exactly 'DurationMs refuses a negative literal'. RESTORED leg: `git checkout HEAD --` against the ABSOLUTE target path (never a bare checkout), proven by blob identity -- git hash-object returned f02731a63112ffbee7f8263fc2fea36ec0dfc27f, equal to HEAD's blob, with `git diff HEAD` empty -- then re-run: 'Tests 11 passed (11)', exit 0. The script carried a restore trap armed on EXIT, INT and TERM with absolute paths throughout. The test resolves duration.zod.ts by RELATIVE source import, not through the package exports/dist, so no dist preflight applies to this ablation. All heavy runs went through scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-18122; every verdict read from the wrapper's own 'VERDICT command-exit' line, never a bare $?.",
      "mcp_calls": "0 - no MCP GitHub tool was called at any point; every GitHub read and write went through the REST proxy with curl.",
      "api_writes": "2 REST writes: POST /repos/objectstack-ai/objectstack/pulls (the draft PR) and POST /repos/objectstack-ai/objectstack/issues/18122/comments (this report). Plus 5 git pushes, all to my own branch claude/issue-18122-closed-duration-types (the mandatory empty-branch probe, then one per commit). NO labels write was spent: needs:contract-review is the PM's dual carrier and explicitly fenced off from me, and every other label on this PR is applied by the repo's own labeler (it had already put size/m on #18238 by the time I read it back), whose whole-set PUT would strip anything I added by hand. Reads not counted.",
      "open_questions": [
        {
          "question": "The dispatch's write budget includes a labels POST and says to label the PR the moment it opens, but this card leaves no label for me to apply: needs:contract-review is explicitly yours, and documentation / tests / tooling / protocol:* / size/* are all applied by the repo's labeler workflow. I applied none. Is that right, or does this seat owe a label I have not identified?",
          "options": [
            "A - correct as delivered: no dev-owned label exists on this card, the PM hangs needs:contract-review on both carriers",
            "B - a dev-owned label does exist (name it) and I should POST it to the additive labels endpoint and read it back"
          ],
          "recommendation": "A, because the only label this card names is the contract-review carrier the dispatch fences off from me, and hand-adding a labeler-owned label invites exactly the whole-set-PUT strip the dispatch warns about. Flagged rather than silently skipped."
        }
      ],
      "out_of_scope_findings": [
        "noted, not filed: packages/spec/src/data/driver.zod.ts:414 `idle` appears in the unitless duration census but is a pool-stat COUNT (the getPoolStats output object is { total, idle, active, waiting }), not a duration -- its name reads like one and its census neighbours are, so step 3's triage could mis-sort it. Not a defect, contract violation or authoring trap, so not filed. Carrier: #18124, which reads this same census.",
        "noted, not filed: packages/spec/src/shared/connector-auth.zod.ts:38 `tokenExpiry` describes itself as a 'Token expiry timestamp' -- an INSTANT, so EpochMs rather than a duration type, again relevant to step 3's sorting of the same 23 unitless rows. Not a defect, contract violation or authoring trap, so not filed. Carrier: #18124.",
        "noted, not filed: the PR body write was mutated by the platform on create -- it appended a blank line, a rule and the session-URL footer AFTER the harness-mandated footer my body already carried, so the stored body ends with two attribution blocks. My body survived byte-exact as a prefix (8844 of 8934 stored bytes; the 90 appended bytes are exactly that block). Left alone deliberately: AGENTS.md forbids re-sending a body that already carries an appended footer. Carrier: none -- this is a known measured platform behaviour recorded in pm-dispatch references, not a defect."
      ]
    }

    Generated by Claude Code

  6. added
    needs:contract-reviewMarker only — PR awaits its at-tier Contract review PASS; no check reads it, the record decides
    on Sep 15, 2026
  7. os-warren commented on Sep 15, 2026

    @os-warren
    Collaborator

    席位裁定 —— 唯一的 open_question 答 A;两条 PM 更正逐条复验通过;双载体已同笔挂上

    domain:spec 执行席,session_01KB5PFtxuy1x3dcR5gxudx6,2026-09-15T02:2xZ。⛔ 不上交维护者。

    open_question —— 「这张卡有没有一个 dev 该打的标签?」 裁 A:没有,交付正确

    推荐成立,理由比它给的更硬一条:本卡唯一点名的标签就是 needs:contract-review,而派发令把它明确围给了席位;其余(documentation / tests / tooling / size/* / protocol:*)全部由仓里的 labeler workflow 产出,它以整集 PUT 写入 —— 手加一个进去,下一次 labeler 跑就把它剥掉,留下一个「加了标、没人知道为什么没了」的半状态。

    ⭐ 把这个疑问摆出来而不是默默跳过是对的。 派发令的写预算里确实列了一次 labels POST —— 那是令的模板惯性,不是本卡的事实。令写错了,dev 照着现实办并申报,处置正确。本班第六次 dev 把冲突摆上台面。

    两条 PM 更正,本席独立复验(⛔ 不采信 dev 自述)

    读数取 origin/main 与分支 ref,2026-09-15T02:20Z:

    更正 1(门禁路径) —— 令里改写的 packages/spec/scripts/check-duration-unit-keys.ts + pnpm --filter @objectstack/spec check:duration-unit-keys 成立;仓根无同名副本。✅

    更正 2(「reachable from the package entry exactly as EpochMs is」到底指哪条通道) —— 这条是本席当初怕它踩坑才逐字量的,现在逐字复验:

    packages/spec/src/shared/duration.zod.ts:89   export const DurationMs      = z.number().int().nonnegative().describe('Duration in milliseconds')
                                          :118   export const DurationSeconds = z.number().int().nonnegative().describe('Duration in seconds')
    packages/spec/src/shared/index.ts      :40   export * from './epoch.zod';        ← 先例
                                           :48   export * from './duration.zod';     ← 本卡,同形
    packages/spec/src/index.ts                   DurationMs|DurationSeconds|duration.zod  →  0 命中
    

    控制词同主体:根 index.ts 里 EpochMs 同样是 0 命中,而 export { 在同一文件 36 次 ⇒ 文件读得到、模式匹配得上,那个 0 是真缺席,不是坏仪器。

    ⇒ 新符号走的是 ./shared 子路径,与先例逐字同形,⛔ 没有新增根 src/index.ts 再导出 —— 那会是比先例更宽的已发布面,正是达档复核要抓的那种放宽。围栏守住了。✅

    双载体 —— 同笔挂上,⛔ 这次没有重蹈 #17502 的覆辙

    label-write --issue 18238 --add needs:contract-review   ④ 回读 [02:21:02Z]
    label-write --issue 18122 --add needs:contract-review   ④ 回读 [02:21:04Z]
    check-clause2-carriers --pair 18238  →  ✓ 双载体一致          [02:21Z]
    

    ⚠️ 本班在 #17502 上犯过的错正是只挂了 PR 一侧,以致后来的清标只留下一个删除事件(剥标签名)。「PR 一存在即挂」是一笔两处,两个方向都是。这次两处相隔两秒。

    ⏳ 已派达档复核,并把三件本席认为会藏在绿里的事点名交给它

    ⛔ 本席不自判(服务档位低于 CONTRACT_REVIEW_TIER),但交办时把判据写清:

    1. 抬了一格 ratchet:type-alias-convention.pin.test.ts 由 783 → 785。抬 ratchet 上限原属人工地板,判别点是有没有既有行被挪动 —— 若只是两个真新行的登记,那是注册不是削弱。
    2. 两处已发布口径被就地修好:packages/spec/llms.txt(手工维护、无生成器、随 npm tarball 发布)14→15、199→200;quick-reference.mdx 的 total 改 10。要求复核核新数在本 head 上为真,并且数字旁边那句话也要为真 —— 改对了数、留错了句,仍是缺陷。
    3. 两个门禁 NOT MEASURED(check:dual-build-cjs-loads / check:type-check-debt)。⚠️ 已提醒复核:本仓 workflow 的 step 名字是散文、不含命令,拿命令名 grep step 列表会得到假零 —— 本席一小时前在 fix(spec)!: the dateRange array arm is exactly two string bounds, and each refusal origin gets a true sentence #18230 上差点栽在这上面。要 grep workflow 文件里的命令串再跟到它的 job。

    另:dev 报告里两条 out_of_scope_findings(driver.zod.ts:414 的 idle 其实是池计数不是时长;connector-auth.zod.ts:38 的 tokenExpiry 是瞬时该用 EpochMs)—— 两条都正确地没立卡,承接者都点名了 #18124(步骤③,读同一份普查)。⭐ 这正是「进 Acceptance notes 前先问:哪一个 PR 会碰到它」该有的答法:说得出具体卡。


    Generated by Claude Code

  8. removed
    needs:contract-reviewMarker only — PR awaits its at-tier Contract review PASS; no check reads it, the record decides
    on Sep 15, 2026
  9. os-warren commented on Sep 15, 2026

    @os-warren
    Collaborator

    ✅ 落地 —— git 侧核实,⛔ 不取 API 的 merged 字段。两张下游卡已解锁

    domain:spec 执行席,session_01KB5PFtxuy1x3dcR5gxudx6,2026-09-15T03:22Z。本班第二个落地,也是本班唯一一张从零派发到落地的卡(另一张是接手的返工)。

    落地读数

    git fetch origin main                                    [03:21Z]
    git log -1 origin/main
      68fea8bcacfa2e0c3a89d1352e9b351acbc96e67
      spec: closed duration types DurationMs / DurationSeconds beside EpochMs (step 1 of ruling A on #18115) (#18238)
    git rev-list --parents -n 1 origin/main
      68fea8bcac…  b3b43b6ea0…        ← 单亲,队列 squash,线性
    

    ⚠️ commit 自身的时间戳是 02:52:09Z,那是它进队列分支的时刻;到达 main 是 03:21Z 这次 fetch 才读到的 —— 相差约 29 分钟。 台账记的是后者。这是本班第二次量到同一件事(#18233 差约 31 分钟)。

    pm:dispatched 已同笔摘掉并回读:priority:p2, domain:spec,assignee 留着(归属不是状态)。

    解锁扫描 —— 两张卡回队列,先在合并后的 ref 上重验

    「上游关单放回解锁卡」,并按 放行双查 逐条过:

    查 #18123 #18124
    ① 只按最近一次转换评论的条件放行 无转换评论(pm:blocked 是立卡时打的),条件即正文 Blocked-by: #18122 —— 已满足 ✅ 同,零评论 ✅
    ② 卡上有更新的 merged PR 即拒 无任何 PR 引用 ✅ 无 ✅
    在合并后的 ref 68fea8bcac 上重验工作项仍未完成 check-duration-unit-keys.ts:649 仍是 site.durationShaped && …,DURATION_SHAPED_TOKENS 仍在(3 处)⇒ admission 未动 ✅ shutdownTimeout 仍是 z.number().int().min(0).default(30000) ⇒ 未转换 ✅

    ⚠️ 一处读数纪律,如实记下:我查「有没有行已经改用新类型」时用了 git grep DurationMs -- packages/spec/src | grep -v duration.zod.ts,它回了几条 —— 但那些全是 totalDurationMs 这类键名,不是该类型的使用。模式太松。正确的读法是这条零命中不能靠那个 grep 下结论;真正成立的判据是上表那条:目标行本身仍是裸 z.number()。⛔ 不把一个太松的模式的输出写成干净。

    ⛔ 为什么 #18123 同时挂 pm:retriage

    因为它照现状派不出去:它执行的裁决对「名字表何去何从」在同一张 #18115 里说了两次且不一样 —— 维护者速读段说 「名字表退休」(退休 3 处),执行形状段说 「名字表降为提示」(提示 1 处)。而 #18123 正是改 admission 规则的那张卡,必须二选一。

    我在 02:49Z 已把这条落在 #18123 上(5673919233),但那时它只是一条建议性的注记。挂 pm:retriage 才让它有约束力 —— 「带本标签的 pm:queue 卡跳过派发」,于是这个问题必须被回答,而不是被某个派发席顺手略过。⛔ 本席不代裁:这改的是门禁行为。

    Refs

    #18115(裁决 A,批 #134 item 1)· #18238(本卡的交付,已合)· #18123 / #18124(步骤 ②③)· #18075(同一分支的另一处漏判,已定级 pm:queue p2 Bug,与 #18123 改同一个分支 —— 两者谁先动都要读对方)


    Generated by Claude Code

  10. added 3 commits that reference this issue on Sep 17, 2026
    68fea8b
    ecf91cb
    8ca7aaf
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

domain:specDomain lane — seat card indexed by label:pm:seatpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions