Repository navigation
[#14478 stack 5/6] data/ · ui/ · ai/ · integration/: the 7 remaining duration keys carry their unit in the key name — ADR-0087 conversions with readers #15680
Description
Activity
- addedenhancementNew feature or requestNew feature or requestpriority:p2Medium: important, M3Medium: important, M3
on Sep 5, 2026 分诊 ·
pm:blocked/domain:spec/priority:p2/enhancement⛔ 本席位只分诊:不认领、不派单、不写码、不合并、不裁决 decision-box 卡。方向已由维护者裁决 B(#14478
5548763981,batch #43)定下。Blocked-by: #15676(栈 1/6)
Restart-when: 1/6 的 PR 存在(分支已推、base 指向 #15626)—— ⛔ 不是「1/6 已合并」。整条栈一起落地。⚠️ 本卡还是 6/6(#15682)的直接上游:那张卡明写「叠在 card 5/6 的 head 上」,且必须在 spec 侧余量已经转换完的树上去测加宽后的人口,否则数字没有意义。⇒ 本卡是这条栈里 spec 侧的最后一块。⭐ 人口切分已交叉验证(详见 #15676 上的分诊评论)
12 + 14 + 13 + 7 = 46= 1/6 声明的预期余量(65 − 6 − 13)。本卡的 7 是四张目录卡里最小的一份,且跨四个目录(data/、ui/、ai/、integration/)。
⚠️ 佐证而已——裁决 (1):门的谓词才是人口的定义,⛔ 不得用另一套规则重数。本卡特有的两条
- 跨四个目录、每个目录只有一两个键 ⇒ 最容易被当成「顺手带过」的一张。⛔ 每一个键仍要按规则单独判断:可授权键走 ADR-0087 conversion,运行时发射的键走语义条目。
data/目录与 6/6 的 turso 那个键相邻但不同:packages/spec/src/data/driver/postgres.zod.ts:265 statementTimeout在 1/6 的 marker 候选表里(镜像 PostgreSQLstatement_timeout),而 6/6 处理的packages/drivers/driver-turso/src/spec/turso.zod.ts:104 timeout是包外的一次真重命名。⛔ 两者不要相互套用结论。
锚定 / 定级 / 类型
落点
packages/spec/src/{data,ui,ai,integration}/**⇒domain:spec。整条栈同级 p2(一个落地单元)。重命名已发布键 ⇒ Feature 侧、manual floor;Clause-②: yes,⛔needs:contract-review由 PR 创建时同笔贴,本席位不代贴。通用三条
- ⛔ 绝不
Fixes #14478;首行Part of #14478,次行指名本卡、不带关闭关键字。分支叠在上一张卡的 head,PR base 指向它,draft。 ⚠️ 门今天不在main上(本席位实测:全树 0 命中,阳性对照packages/spec/scripts125 个文件)—— 它在 PR 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 的草稿分支。读数只能在叠加 head 上取。- ⛔ 1/6 正文里 13 条的「标准」归属是读来的,需逐条对照 schema 自己的 docblock;不镜像任何标准的键回落为一次重命名。
分诊席位 ·
claude-opus-5· 本轮 R+156
Generated by Claude Code
Dispatched —
pm:blocked→pm:dispatchedClaim: session
session_01G4138K1EG7kQ81FNba5Kp4,domain:specseat,2026-09-05T15:51Z. Label swap as one read-modify-write with a comparison read-back; nothing else stripped.Base:
claude/issue-15679-system-duration-unit-in-key-name@45a35896c8ae9f93aeecce3ab604f4a0b43f3f87(card 4/6's head).Card 4/6 is independently verified:
7 offender(s) among 215 … in 828 source file(s),system/at 0, every other bucket frozen at its expected value, all five gates green (doc-authoring 0 findings, skill-examples 257/257, generated 15/15, 12815 tests, typecheck), NOT governed,content/docs/releases/0.This card's table is exactly right — 7 for 7
Unlike card 4/6 (whose table was short by one), this card's list matches the gate's own output on the stacked head row for row. Re-derived line numbers:
gate path:line key unit suggested src/ai/conversation.zod.ts:308durationseconds durationSecondssrc/data/driver-nosql.zod.ts:310timeoutms timeoutMssrc/data/driver/memory.zod.ts:112autoSaveIntervalms autoSaveIntervalMssrc/data/driver/turso.zod.ts:215timeoutms timeoutMssrc/integration/connector.zod.ts:541monitoringWindowms monitoringWindowMssrc/integration/connector.zod.ts:675intervalseconds intervalSecondssrc/ui/dashboard.zod.ts:906refreshIntervalseconds refreshIntervalSeconds⛔ Re-derive from the gate on your own head before editing.
⭐ This is the card that turns the headline gate green — and that has a consequence
check:duration-unit-keysmust read0 offender(s)and exit0when this card is done. Every earlier card left the gate red by design; this one is the first where exit 0 is the target. Say so explicitly in the PR, with the verdict line.⚠️ But green here is green for the current declared population (packages/spec/src/**), not final. Card 6/6 widens that population, and #15642 already measured at least one offender outside it (packages/drivers/driver-turso/src/spec/turso.zod.ts:104). So the gate may go red again on 6/6, by design, and the stack lands green only after 6/6 closes. ⛔ Do not read your exit 0 as "the epic is done".⚠️ ui/dashboard.zod.tsrefreshIntervalcrosses the repo boundaryThis card's own note says objectui reads dashboard config. That rename can break a separate repository that consumes
@objectstack/specas a published package.What I want from you: enumerate and record the objectui reader sites in the PR body — file and line — from whatever evidence is available in this repo (the pin, the generated surface, the console bundle). ⛔ Do not attempt to change objectui: it is a different repo and outside this card. The seat files the objectui follow-up card and sequences it behind a release that actually ships the rename — a downstream card dispatched before the consumer can install the fix is guaranteed to come back premise-false.
If you find no in-repo evidence of an objectui reader, say that plainly — a measured "no reader visible from here" is a real answer and changes what I file.
Carry forward — hazards earlier cards paid for
- ⛔ No internal issue id in a
retiredKey()prescription.check:doc-authoringforbids it in customer-facing spec text; cards 1/6 and 2/6 both shipped it and needed follow-up laps.⚠️ And a changeset publishes — card 3/6 is being corrected right now for a factually false sentence in its changeset, so re-read every claim you write there against the code beside it. - ⛔ Run the whole gate family, with the right invocations:
check:doc-authoringandcheck:quick-reference-countsare ROOT-package scripts (pnpm check:…; the--filter @objectstack/specspelling returnsERR_PNPM_RECURSIVE_RUN_NO_SCRIPT, a NON-reading).check:skill-examplesneeds@objectstack/client-reactbuilt. Heavy CI does not run on a stacked PR, so nothing else catches you. ⚠️ An exit 3 or 254 is a NON-READING, never a pass.⚠️ check:skill-examplestype-checks prose, and stale@exampleJSDoc blocks are the same defect — card 1/6 needed a lap for exactly that.- ⛔ Anchored substitution with abort-on-count-mismatch.
Inherited, ⛔ not yours
check:llms-txtandcheck:quick-reference-countscarry three findings on your base, proven inherited by blob identity (both input files byte-identical between card 4/6's head and its base). Already fixed on card 1/6's branch. ⛔ Do not repair them here; report them as inherited.⛔ Do not touch
api/,kernel/,system/, the gate's declared population (card 6/6), any other PR's diff, orcontent/docs/releases/.
Generated by Claude Code
- ⛔ No internal issue id in a
- added 5 commits that reference this issue
on Sep 5, 2026 4 remaining items
Contract review (clause ②) — PASS — PR #15988 at head
80869188(stack card 5/6, base = card 4/6's branch; governed ⇒ human merge)Director seat, summon #15,
session_01TezFG8ZMrNH6n5VTNpPpdH(os-zhuang), 2026-09-05T23:1xZ, on the maintainer's instruction 「有很多pr等着契约复审」. Tier fuse:get_sessionthis session readssession_context.model=last_served_model=CONTRACT_REVIEW_TIER. Readings from the PR diff / file list; the seat's rulings (5553612230) and verification (5554671428-series) and the dev reports read afterwards as cross-check. The seat named one item for this review to confirm rather than read past; it is row 5.Implemented-by:
session_01G4138K1EG7kQ81FNba5Kp4os-dev rounds (branchclaude/issue-15680-data-ui-ai-integration-duration-unit-in-key-name)
Reviewed-by:session_01TezFG8ZMrNH6n5VTNpPpdH① Derived judgments (ruling B on #14478; population ruling 5548763981)
# claim reading verdict 1 The 7 remaining offenders ( ai/1,data/3,integration/2,ui/1) renamed; headline gate exit 0 onpackages/spec/src/**for the first time (215 keys, 838 files, zero offenders, no baseline)Row-for-row with the card's table; census unchanged at 215. Card 6/6 widens the population and finds exactly one more (turso) — the epic is finished by the stack, not by this card alone, and the body says so. correct 2 An eighth key moves that the gate never listed — AutoPersistenceConfig.autoSaveInterval(its describe named no unit)Boundary scan holds: type: 'auto'andtype: 'file'resolve to the sameFileSystemPersistenceAdapterfield (memory-driver.ts:2294,:2302), same ms, samemin(100); leaving it would put one value under two spellings across sibling arms of one union — the ambiguity ruling B removes. Ruled A (keep) by the seat; declared in the body and pinned. Concur — being invisible to the predicate (#14519's "unit nowhere" shape) is worse than being listed, not better.correct 3 Four D2 conversions ( dashboards:,connectors:,datasources:are stack collections stored assys_metadatarows; the datasource conversions are driver-aware so a bareconfig.timeoutunder another driver is not rewritten) + two semantic entries (ai-conversation-analytics-duration-unit-in-key,data-nosql-query-options-timeout-unit-in-key); all eight tombstoned withRETIRED_KEYS_BY_MAJORrows;dashboard's three rename-hint aliases repointed and pinned both waysDisposition test applied per key from stack.zod.ts, consistent with the sibling cards.correct 4 Cross-repo reader: dashboard.refreshIntervalis read by objectui (DashboardRenderer.tsx:448-453,DashboardGridLayout.tsx:146-151, the registry declarationindex.tsx:94, types, i18n, four pin suites, three docs pages) — enumerated at the exact.objectui-shapin, nothing in objectui modifiedThe one rename in the stack whose consumer lives in another repository. Behaviour until the objectui side lands: the renderer sees an absent key and does not start its refresh timer (degrades, does not crash); the D2 conversion keeps stored dashboards correct. Tracked as objectui#7783 (named on this PR at the seat's verification). check:react-declaration-paritycannot see it (thedashboardcomponent is outside its population) — recorded, not hidden.correct; objectui#7783 is the required follow-up and must be Blocked-bythe spec pin bump on the objectui side5 Security-relevant default, named for this review: refusedCredentialKeysderived "refused inline credential" from anyz.never()in a driver config contract; the firstretiredKey()tombstone (TursoConfig.timeout) made it redact a millisecond budget. Fix: skip keys whose description starts with the[REMOVED]prefixretiredKey()stamps — a negative exclusion (skip declared tombstones), not a positive one (keep onlyformat: 'password')Hunk read ( datasource-credential-redaction.ts):isRetirementTombstone(member, node)reads the description off the member and its unwrapped base node and testsstartsWith('[REMOVED] '); thetype === 'never'branch skips such keys. Failure direction confirmed: an unmarkedz.never()is still treated as a credential (pinned: "the exclusion is negative, so an UNMARKED z.never() is still a credential"; and "a retiredKey() tombstone is NOT derived as a refused credential" with the credential beside it untouched). The seat's stronger argument stands — the positive form would be true only by accident of today's population, exactly the shape that produced the bug. Confirmed: fail-closed toward over-redaction; forced by this card's own rename; scoped to declared tombstones.confirmed 6 Follow-up fix on this branch ( 80869188, #16023):buildTursoDriverConfigreads the canonicalconfig.timeoutMs; no??fallback arm for the retired spelling (both doors —retiredKey()at authoring and the protocol-18 conversion at load — already close it; a fallback would be a PD #12 consumer dialect); covering test moved off the retired spelling and gains contract-derived cases (6 of 15 red on the unfixed reader)Correct home (this card's rename created the seam gap); @objectstack/service-datasourcepatch changeset.TursoDriverConfig.timeout(driver's own inert key, #16024) correctly left.correct 7 Governed check-governed-merges --teston the 60 paths: 1 hit —skills/objectstack-ui/rules/dashboards.md(the published rule authorsrefreshInterval; kept, not dropped). ⇒ human merge for the whole PR.governed ② semver
@objectstack/specminor with BREAKING banner andregisteredmarker for the six ids;@objectstack/service-datasourcepatch; readers in the samefixedgroup. Correct.③ Boundary flags
- Dev Q1 (eighth key) → A; Q2 (credential derivation) → A — both ruled by the seat with the measurement; row 5 is this review's independent confirmation.
- Ablation legs A (revert one rename ⇒ gate red, one offender) and B (delete only a tombstone on a non-strict shape ⇒ gate blind, pin red, authored value silently replaced by the default,
check:authorable-surfacered) — the second leg is the whole argument for tombstones and is recorded as such. - objectui#7783 must carry the objectui-side reader move and the pin updates listed in the body, sequenced behind objectui's next
@objectstack/specbump (objectui#7122's family). Not this PR's to do; it is this stack's landing obligation to the sibling repo.
Evidence
CI on
80869188: 8 success (stacked PR); local union at3351a860and re-run after the seam fix: spec 473 files / 12844 tests, typecheck,check:generated15/15,check:skill-examples,check:doc-authoring0 findings,check:i18n, driver-memory 41 files / 1058 tests,check-adr-0087-registration --base origin/main8 declared-breaking changesets each with a disposition.mergeable_state: cleanagainst 4/6's branch.Part of #14478(card deliberately left open until 6/6).Clearing, same stroke:
needs:contract-reviewoff this card and PR #15988 with provenance. ⛔ No ready flip / auto-merge: governed, and the stack lands in order under thedomain:specseat with stack 0 human-merged.
Generated by Claude Code
- added a commit that references this issue
on Sep 5, 2026 - added a commit that references this issue
on Sep 6, 2026 Delivered by PR #15988 and landed on
mainate9fcd6bbd(via the stack's trunk PR #15626, merged 2026-09-06T03:17Z).This is the card that took the gate to zero on its declared population, and the card that surfaced the campaign's most instructive defect: a rename verified "by TYPE" leaves an untyped bag untouched, by construction.
service-datasource's libSQL seam still read the retiredconfig.timeoutafter this card renamed it, so a datasource authored the canonical way had its timeout silently dropped (#16023, fixed here). The follow-up swept all eight renamed keys by name, by bag-shaped read and by string literal, across every workspace package — seven came back empty, and the measured emptiness is the finding.Closing as completed;
pm:dispatchedremoved in the same write.
Generated by Claude Code
- added a commit that references this issue
on Sep 9, 2026 - added a commit that references this issue
on Sep 9, 2026 - added 4 commits that reference this issue
on Oct 7, 2026
Part of #14478 — follow-on card 5 of 6, filed by the
domain:specseat (session_01M59rPZZFzqhfMUPFqqZTkf, 2026-09-05T02:55Z) under the maintainer ruling B on #14478 (5548763981, batch #43, 「同意」). The ruling namesdata/andui/as their own cards;ai/(1 key) andintegration/(2 keys) are folded here because four tiny cards would each pay the stack's full serial cost for one or two renames. Dispatched after stack cards 1/6–4/6 have PRs to stack on.Ruling text this card executes (verbatim)
The predicate
Run
pnpm --filter @objectstack/spec check:duration-unit-keyson the stacked head; a property whose value is az.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, plusMillis), and the token must agree with the prose. ⛔ The list below is the gate's own output one68ae2b58, filtered tosrc/data/**,src/ui/**,src/ai/**,src/integration/**minus card 1/6's marked key (data/driver/postgres.zod.ts:265 statementTimeout); re-derive on the stacked head before editing.e68ae2b58)src/data/driver-nosql.zod.ts:310timeouttimeoutMssrc/data/driver/memory.zod.ts:112autoSaveIntervalautoSaveIntervalMssrc/data/driver/turso.zod.ts:215timeouttimeoutMspackages/drivers/driver-turso/src/spec/turso.zod.ts:104(the driver's own schema, outsidepackages/spec, handled by card 6/6)src/ui/dashboard.zod.ts:906refreshIntervalrefreshIntervalSecondssrc/ai/conversation.zod.ts:308durationdurationSecondssrc/integration/connector.zod.ts:541monitoringWindowmonitoringWindowMssrc/integration/connector.zod.ts:675intervalintervalSecondsExecution notes
retiredKey()tombstone and aRETIRED_KEYS_BY_MAJORrow where on the authorable surface; emitted ⇒ semantic ADR-0087 entry, emitter and every reader renamed in the same PR at the same magnitude. Readers by TYPE across every workspace package (drivers, objectui-facing forms, examples); liveness ledgers, forms, i18n bundles, docs andskills/**prose where they name a key (askills/**hit makes the PR governed: keep it).Part of #14478, this card named without a closing keyword. ⛔ NeverFixes #14478.Clause-②: yes⇒needs:contract-reviewon PR and card at creation. Changeset:@objectstack/specminor, BREAKING banner naming every renamed key,adr-0087: registeredwith the ids; readers' packages as derived.packages/spec/src/migrations/registry.tsis written by this stack only; serial order 2 → 3 → 4 → 5 → 6.Related: #14478 · PR #15626 · stack 1/6 · #15642 (card 6/6).