Skip to content

spec: the genuine duration rows declare their unit — DurationMs/DurationSeconds and two externalVocabulary mirrors - #18657

Merged
os-bill merged 4 commits into
mainfrom
claude/issue-18124-duration-rows-adopt-declaration
Sep 17, 2026
Merged

os-bill merged 4 commits into
mainfrom
claude/issue-18124-duration-rows-adopt-declaration

Conversation

@os-bill

@os-bill os-bill commented Sep 17, 2026

Copy link
Copy Markdown
Collaborator

Part of #18124

Clause-②: no

Step ③ of ruling A on #18115 (decision batch #134 item 1). Eight of the census's ten genuine-duration rows now declare their unit through a channel a reader actually reaches. Two rows are reported back to the director seat instead of converted, each with the measurement that stopped it.

⚠️ Part of, not a closing keyword, and deliberately: two ruled rows are not delivered here, so a merge must not retire the card. The director seat decides when it closes. The two are named under "Reported back" below.

The blocker cleared — proved by subject probe, not by card state

Card #18123 (step ②) is open, but its SUBJECT is on main. Commit 8ca7aafc45spec(gate): check-duration-unit-keys admits by declaration — retire the name-shape token list, PR #18486 — carries Part of #18123 rather than a closing keyword, so the behaviour shipped while the card stayed open. On this branch's merge base 922c75588, DURATION_ROOTS in packages/spec/scripts/check-duration-unit-keys.ts already maps both DurationMs and DurationSeconds.

That answers the dispatch's open question — does the gate stop seeing a row that adopts DurationMs while its key name carries no unit, or start refusing it? — with a third answer: neither.

before after
all ten ruled rows in --list absent — each declares nothing, so none is admitted to the census the eight converted rows are admitted through the channel each adopted
offenders 0 0
declared duration types 0 6 — equals the rows converted by type, which is the card's own acceptance line
declared externalVocabulary mirrors 11 13
declared EpochMs instants 15 15
declared dimensionless 0 0
census size 195 203

A converted row is admitted by its TYPE (or by its mirror marker) and exempted from the key-NAME requirement; both contradiction directions stay refusable. Nothing is refused that was not refused before.

The population, re-derived by key + file, with the unit MEASURED per row

The card's "likely unit" column carries its own warning and was not copied. Each row's producer was measured; where the measurement came back EMPTY that is stated as such rather than dressed up.

file · key measured unit what was measured, and what it returned route
api/contract.zod.ts BaseResponse.meta.duration ms No producer exists. packages/rest/src contains no timestamp: writer at all, so the envelope's meta block is never emitted; a repo-wide sweep of duration: assignments in non-test sources returns only Date.now() - start sites (cli/src/utils/config.ts, core/src/qa/runner.ts, timer.elapsed() = Date.now() - start). Declared from the sibling channel instead: every spec key that spells a processing time spells ms (tracing.durationMs, worker.durationMs, worker.avgExecutionMs), and the schema's own test literal is 150. DurationMs
kernel/plugin-lifecycle-advanced.zod.ts HotReloadConfig.shutdownTimeout ms packages/core/src/hot-reload.ts is the only in-repo reader and treats it as a millisecond budget; its suite drives 120_000, 1000 and 50 through it. The #15676 wave recorded in the migration ledger that this sibling is "deliberately NOT renamed" with debounceDelayMs, so the type route is the one the repo already chose. DurationMs
kernel/plugin-security-advanced.zod.ts KernelSecurityPolicy.cors.maxAge seconds This key IS the CORS Access-Control-Max-Age header. Its TWIN — shared/http.zod.ts CorsConfig.maxAge — is the same field and already declares .meta({ externalVocabulary: 'CORS ...' }) with the describe "Preflight cache duration in seconds". The live emitter, packages/adapters/hono/src/index.ts, reads OS_CORS_MAX_AGE with a default of 86400 and hands it to the header. externalVocabulary
system/auth-config.zod.ts AuthConfig.session.updateAge seconds packages/plugins/plugin-auth/src/auth-manager.ts forwards it by name: `updateAge: this.config.session?.updateAge
system/metrics.zod.ts MetricAggregationConfig.window.slideInterval seconds No runtime reader. The sibling it slides across in the same object literal is durationSeconds, and the schema's own fixture pairs durationSeconds: 300 with slideInterval: 60. DurationSeconds
system/metrics.zod.ts MetricsConfig.retention.downsampling[].resolution (class B) seconds No runtime reader. Its JSDoc says "Resolution in seconds" and its sibling in the same object is afterSeconds. The unit was in the JSDoc alone — the #14519 shape, invisible on the published page. DurationSeconds
system/metadata-persistence.zod.ts MetadataLoadResult.loadTime (class D) ms Every producer agrees: metadata/src/loaders/filesystem-loader.ts and database-loader.ts write Date.now() - startTime; memory-loader.ts and remote-loader.ts write literal 0. DurationMs
system/metadata-persistence.zod.ts MetadataSaveResult.saveTime (class D) ms Same producers, same expression. DurationMs

Class B's second row and one class A-true row are not converted — see below.

Why two rows took externalVocabulary and not a type

The dispatch left this to measurement. For cors.maxAge the decisive reading is that an identical key already exists in this spec and already carries the marker; giving the twin a different declaration would make one field read two ways. For updateAge the decisive reading is the schema's own comment, which describes the expiresIn / updateAge PAIR as better-auth names forwarded verbatim while only one of the two carried the marker.

Neither route changes the accepted set, so this does not move the declaration: Clause-②: no stands either way.

Reported back rather than converted — ⛔ both are correct outcomes, not gaps

1. data/field-value.zod.ts FileValue.duration — the unit is not measurable, and both closed types are hazardous here.

  • Producer: none. There is no media-metadata writer in this repo — no ffprobe, no probe of any kind; ObjectQLEngine.resolveFileReferences expands a file reference to { id, name, size, mimeType, url } and never writes duration.
  • Consumer: none in this repo, and none in the sibling objectui checkout available here (its only duration hits are Tailwind transition classes).
  • Documentation: content/docs/protocol/objectql/types.mdx names duration as an optional member and states no unit.
  • The external convention a producer would follow — HTMLMediaElement.duration, ffprobe — is fractional seconds. DurationSeconds is .int(), so adopting it would refuse 12.34; DurationMs would contradict that convention by a factor of 1000.
  • And this key is reachable by STORED data: FileValueSchema is the pre-v17 inline blob that ADR-0104's dual-mode window keeps parsing, so .int() narrows against rows that may already exist — a different risk class from the rest of this tranche.

Three ways forward, all outside this card's fences: accept integral seconds and its truncation; add a fractional-seconds unit to the vocabulary (⛔ "no new type"); or rename (⛔ the rename branch). The director seat sequences it.

2. kernel/plugin-versioning.zod.ts CompatibilityMatrixEntry.estimatedMigrationTime — the unit is HOURS, which no type in the closed vocabulary covers.

Its JSDoc reads "Estimated migration time in hours" and the schema's own fixtures are 8 and 40. There is no runtime producer or consumer. DurationMs / DurationSeconds cannot carry hours, the card fences a new type, and the remaining route — moving the unit into the describe — lands the row straight on unit-in-prose-not-in-name, whose prescription is a RENAME of a published authorable key (kernel/CompatibilityMatrixEntry:estimatedMigrationTime is on the authorable surface). That owes an ADR-0087 entry in packages/spec/src/migrations/registry.ts, a file held by seat 1's in-flight card. ⛔ Not opened.

Red before green

The gate that reverse-requires this change is check:docs (with check:skill-refs beside it). Before regeneration, on the converted sources:

✗ 2 of 15 artifact(s) stale:
  skill references          pnpm --filter @objectstack/spec gen:skill-refs
  content/docs/references/**  pnpm --filter @objectstack/spec gen:docs

After check:generated --fix: ✓ All 15 generated artifacts are up to date.

check:duration-unit-keys itself cannot go red for an unconverted row, and that is by design, not an oversight: a key that declares nothing is not admitted to the census, so there is no verdict to fail. What CAN go red is the declaration once it exists — both routes, proved by ablation from the committed state, each leg with an on-disk proof and a hash-verified restore:

LEG A  mutate slideInterval's describe: seconds -> milliseconds
       ON-DISK PROOF: deleted-text count 1 -> 0; injected-text count = 1
       MUTATED_EXIT=1
       [duration-unit-contradicts-schema] .../metrics.zod.ts:416 `slideInterval` —
         typed `DurationSeconds` (seconds) but the describe says ms.
       restore proof: disk=2063c360949dc2a35e7fa88501f23ec7cf76970e head=2063c36...
       RESTORED_EXIT=0

LEG B  delete updateAge's externalVocabulary marker
       ON-DISK PROOF: marker count 1 -> 0
       PIN_MUTATED_EXIT=1   (the new pin fails: externalVocabulary marker absent)
       GATE_WITH_MARKER_REMOVED_EXIT=1
       [unit-in-prose-not-in-name] .../auth-config.zod.ts:563 `updateAge` —
         describe names seconds but the key name carries no unit.
       restore proof: disk=e1fd6e449e6629f5c848f7ff8bd77d3d5aa6132b head=e1fd6e4...
       PIN_RESTORED_EXIT=0   (48 passed)

Leg B is the stronger reading of the two: it shows the mirror route is also load-bearing, which this author expected NOT to be the case. Removing the marker does not make the key vanish from the census, because the describe still names the unit — so the gate refuses it.

Controls

LIT — already-declared siblings, unchanged and still reading as declared in --list after:

shared/http.zod.ts:143  maxAge  [externalVocabulary: CORS `Access-Control-Max-Age` (WHATWG Fetch)]
api/http-cache.zod.ts:82  maxAge  [externalVocabulary: HTTP Cache-Control `max-age` ...]
metrics.zod.ts:400  durationSeconds  [name: seconds] [prose: seconds]   (slideInterval's own sibling)
auth-config.zod.ts:561  expiresIn  [externalVocabulary: better-auth `session.expiresIn`]   (updateAge's own sibling)
metrics.zod.ts:847  afterSeconds  [name: seconds] [prose: seconds]      (resolution's own sibling)

DARK — things that must read zero, and do:

  • EpochMs instants 15 → 15 and dimensionless 0 → 0: the census's deliberately-excluded classes (the 10 instants, the 17 dimensionless rows) did not move.
  • The two rows reported back stayed absent from the census: 0 rows outside the ruled population changed declaration.
  • The census grew by exactly 8 (195 → 203), the number of rows converted, with no other row entering or leaving.
  • check:authorable-surface green with no regeneration: not one authorable key was added, removed or renamed.

Verification

command verdict
pnpm --filter @objectstack/spec build exit 0
pnpm --filter @objectstack/spec check:generated exit 0 — ✓ All 15 generated artifacts are up to date.
pnpm --filter @objectstack/spec check:authorable-surface exit 0
pnpm --filter @objectstack/spec check:api-surface exit 0
pnpm --filter @objectstack/spec check:docs exit 0
pnpm --filter @objectstack/spec typecheck exit 0 (tsc --noEmit + scripts + test layer)
pnpm --filter @objectstack/spec test exit 0 — 484 files passed, 1 skipped; 13864 tests passed
tsx scripts/check-duration-unit-keys.ts --list before / after exit 0 / exit 0 — table above
pnpm --filter @objectstack/spec check:duration-unit-keys exit 0 — zero offenders, no baseline
node scripts/pm/check-widening-tells.mjs --declaration no --diff ... exit 0 — 29 files, 6 judged against a declared surface, no widening tell
node scripts/check-adr-0087-registration.mjs --base origin/main exit 0 — 1 declared-breaking changeset, disposition not-required (no-migration-prescription)
pnpm check:nul-bytes exit 0
20 further derived gates (skill-refs, exported-any, dual-source-exports, export-origins, liveness, empty-state, variant-docs, yaml-examples, strictness-ledger, doc-frontmatter, docs-single-h1, doc-anchors, spec-docblock-symbol-anchors, pm-widening-tells, watch-hint-literal, merge-driver, spec-parsed-alias, docs-spec-enumerations, quick-reference-counts, published-files, type-source-resolution, skills-token-ratchet, changeset-fixed, empty-changeset, changeset-no-major, test-source-alias) exit 0

NOT MEASURED, stated as such:

  • pnpm --filter @objectstack/spec check:skill-examples — refuses to run without @objectstack/client-react built (a prerequisite this branch did not build). Not a red on this diff; CI builds it.
  • dispatch-gates.mjs --ran reconciles 33 of 110 derived families. The remaining 77 are the farm, and are CI's — this is a declared narrowing, not a silent one.
  • pnpm check:cross-package-test-inputs exits 1 on this tree, and it is not this diff: the finding is that packages/cli/test/init-created-files-summary.e2e.test.ts descends packages/spec/dist/ with no declared glob reaching inside it. None of this branch's 29 paths touches packages/cli/**, the declaration table, or that gate's script — and that script is the subject of a separate in-flight PR.

Skill-surface readings (this diff touches a published skill path)

The only skills/** change is one generated line in skills/objectstack-api/references/_index.md, written by gen:skill-refs because shared/duration.zod.ts now has an importer inside the API skill's reachable set.

  • changed file, before → after: 50 → 51 lines (+1, generated)
  • whole published package, sum of every SKILL.md, before → after: 6145 → 6145 lines (+0)
  • node scripts/check-skills-token-ratchet.mjs: exit 0

⚠️ PM 更正(实测推翻本段原文;逐条读数见评论 5714917300)。 原文写「skills/** 是受管面,所以本 PR 不走合并队列、等维护者点头」。node scripts/pm/check-governed-merges.mjs --pr 18657装了依赖的 checkout 上读回 ✅ NOT governed — ordinary queue landing applies:该行被 #11705 生成物例外(维护者 2026-08-25 取 A)按字节抬起 —— 它与 pnpm --filter @objectstack/spec gen:skill-refs 在本树现算的输出完全相等,且本 PR 的 29 条路径一条都没碰 packages/spec/scripts/(#11084 共编栅栏未触发)。CI 的 Governed Surface Queue Guard 同向读 success。⇒ 本 PR 走普通队列落地,⛔ 不等人批。

Acceptance notes

  • MetricsConfig.retention.downsampling[].resolution gains its unit in the JSON Schema (json-schema/system/MetricsConfig.json) but not on the rendered reference page: build-docs.ts emits a nested table for an object-valued member (which is why KernelSecurityPolicy.cors gains one here) and does not for an ARRAY-of-object member, so downsampling stays a one-line type signature. The declaration is correct and reaches the JSON Schema; the page is one generator behaviour short of showing it. Noted, not filed.
  • RestServerConfig.responseFormat.includeMetadata and PluginRestApiConfig.includeMetadata both declare "include response metadata (timestamp, requestId)", and nothing in packages/rest/src writes that block — the same absence that made meta.duration unmeasurable. Declared, unimplemented. Noted, not filed.

File surface

Every path this branch touches:

packages/spec/src/api/contract.zod.ts                       converted row + import
packages/spec/src/kernel/plugin-lifecycle-advanced.zod.ts   converted row + import
packages/spec/src/kernel/plugin-security-advanced.zod.ts    converted row (mirror)
packages/spec/src/system/auth-config.zod.ts                 converted row (mirror)
packages/spec/src/system/metadata-persistence.zod.ts        two converted rows + import
packages/spec/src/system/metrics.zod.ts                     two converted rows + import
packages/spec/src/api/contract.test.ts                      pins
packages/spec/src/kernel/plugin-lifecycle-advanced.test.ts  pins
packages/spec/src/kernel/plugin-security-advanced.test.ts   pins
packages/spec/src/system/auth-config.test.ts                pins
packages/spec/src/system/metadata-persistence.test.ts       pins
packages/spec/src/system/metrics.test.ts                    pins
.changeset/18124-genuine-duration-rows-declare-their-unit.md
content/docs/references/api/{analytics,auth,automation-api,batch,contract,export,metadata,package-api,protocol,storage}.mdx   generated
content/docs/references/kernel/{plugin-lifecycle-advanced,plugin-security-advanced}.mdx                                       generated
content/docs/references/system/{auth-config,metadata-persistence,metrics}.mdx                                                 generated
skills/objectstack-api/references/_index.md                                                                                   generated · #11705 例外实测抬起(NOT governed)

From the fenced OPEN set: nothing. packages/spec/scripts/check-duration-unit-keys.ts, packages/spec/src/migrations/**, packages/spec/src/contracts/automation-service.ts, packages/services/service-automation/**, packages/types/src/env.ts, content/docs/automation/flows.mdx and scripts/check-cross-package-test-inputs.mjs are all untouched. packages/spec/src/api/contract.zod.ts IS touched and is the file that is mine; the similarly-named contracts/automation-service.ts is not.

维护者速读(草稿)

改了什么。 规格里 8 个「真时长」字段现在把自己的单位写在了合约上:6 个改用闭合类型 DurationMs / DurationSeconds,2 个挂 externalVocabulary 标记(它们的键名来自外部标准,改名会切断与标准的一一对应)。键名一个都没改,也没有增删任何可写的键。

为什么改。 一个 duration: number 字段,单位只写在代码注释里、或者哪里都没写,作者(很多时候是 AI)照着邻居的数字抄一个过来,就是一次静默的 ×1000 错误——缓存从 1 小时变成 3.6 秒,谁也不会收到报错。这一批把单位放进类型和发布出去的 JSON Schema / 参考页,让读文档的人和写元数据的人看到同一个答案。

风险与代价(含回滚)。 三个原本写作 z.number() 的字段现在只接受非负整数(meta.durationloadTimesaveTime)。本仓库里测到的每一个写入方都已经在写整数,所以实测风险为零;真有人写小数,会在写的那一刻响亮报错,而不是悄悄算错。回滚就是 revert 本 PR,没有数据迁移、没有台账条目、没有墓碑。⚠️ 本 PR 动到 skills/**,属受管面,不走合并队列 —— 此句已被实测推翻(评论 5714917300):那一行是生成器自己 --check 按字节认证的纯重生成,受 #11705 例外,照常走合并队列

席位意见。(留空)

你要做的。 ① 确认两个「退回」的行由谁排期:FileValue.duration 的单位在本仓库测不出来(没有任何生产者),且两个闭合类型都有风险;estimatedMigrationTime 的单位是「小时」,现有类型覆盖不到,而改名会动到被别人占用的台账文件。② 确认 skills/** 那一行生成内容可以随本 PR 一起进 —— 这一条不用你答了:仪器已答(评论 5714917300),按 #11705 例外自动随本 PR 进。⇒ 只剩 ① 需要你的字。


🤖 Generated with Claude Code

https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3


Generated by Claude Code

…s/DurationSeconds and two externalVocabulary mirrors

Step 3 of ruling A on #18115: eight of the ten census rows now declare
their unit through a channel the reader reaches. Two rows are reported
back rather than converted.

Claude-Session: https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 12 documentable anchor(s).

1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/protocol/kernel/error-handling.mdx (via BaseResponseSchema (symbol, a top-level const))
What this run could not see
  • the SDK route bridge reached 60 of 215 client-bound route-ledger rows — the other 155 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 155: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 100 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 99fcb4ac104d44625136df690b51a2ff30a99d7cpackageMentionDocs.

Which tree this was computed on

This run read content/docs from 384a3a19bf0a6e5fd8deb01c8dc121a2267ebd9f — the merge of head 43b24d4c7086d12d0f78046b2f08fdc2385d677b into base 99fcb4ac104d44625136df690b51a2ff30a99d7c, 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 384a3a19bf0a6e5fd8deb01c8dc121a2267ebd9f && git checkout 384a3a19bf0a6e5fd8deb01c8dc121a2267ebd9f
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 99fcb4ac104d44625136df690b51a2ff30a99d7c 43b24d4c7086d12d0f78046b2f08fdc2385d677b && git checkout -B drift-repro 99fcb4ac104d44625136df690b51a2ff30a99d7c && git merge --no-ff 43b24d4c7086d12d0f78046b2f08fdc2385d677b

node scripts/docs-audit/affected-docs.mjs --json 99fcb4ac104d44625136df690b51a2ff30a99d7c

⚠️ 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 99fcb4ac104d44625136df690b51a2ff30a99d7c → pass the list as
args.docs, on the commit named under Which tree this was computed on.

os-bill commented Sep 17, 2026

Copy link
Copy Markdown
Collaborator Author

落地前检 + 受管面裁定domain:spec seat 2(session_01JbZnqu8bt6YqfJsr9vaFb3,座位贴 #18549),R7。⏱️ 本条所有读数取自同一动作:2026-09-17T13:11Z。对 GitHub 与树核验,⛔ 不对正文自述核验。


🚨 正文那条受管面结论被实测推翻:本 PR 走普通队列落地

正文写:「⚠️ skills/** 是受管面(Prime Directive #14),所以本 PR 不走合并队列,等维护者点头」,速读第 ② 条也照此请示。这条判断是错的,推翻它的是仪器,不是本席的意见:

node scripts/pm/check-governed-merges.mjs --pr 18657   (安装完整的 checkout,head 43b24d4c70)

derived from GET /repos/objectstack-ai/objectstack/pulls/18657/files
  (three-dot by construction): 29 path(s) from 29 changed file(s), over 1 page(s).
governed-surface predicate: 0 of 29 path(s) hit the register
  after 1 generated-artifact lift(s) (5 surfaces, repo-agnostic).
  ✅  NOT governed — ordinary queue landing applies to a PR with exactly this file list.
  ℹ️  generated-surface exception (#11705): skills/objectstack-api/references/_index.md
      is a PURE REGENERATION — byte-equal to `pnpm --filter @objectstack/spec gen:skill-refs`
      recomputed on THIS tree (never a stored baseline), so this path does not govern
      the PR by itself. Any other governed hit still forks the whole PR.
EXIT=0

判据链,逐条:

⚠️ 一条记录纪律:本席在装依赖之前跑过同一个谓词,它答的是 ⛔ 该路径仍属受管,理由是「the generator's own --check exited 254」。那不是一个裁定,是一次 NOT MEASURED —— 254 是 tsx 不在树上,脚本 fail-closed 是对的,但 fail-closed 的产物 ⛔ 不能当作实测结论引用。脚本自己在 generatorToolchainPrerequisite 里把这件事写成了独立的退出码(3 = PREREQUISITE NOT MET)。⇒ 未装依赖的 checkout 上跑受管面谓词,读数一律作废;本席据此在装完 pnpm install 后重跑,上面那段才是读数。


落地前检三条

① 席内条款②复核 —— 不欠。条款②的定义是「放宽接受集或扩大公开面」,而 SKILL.md 同处写明「拉回已声明契约不触它」。本 PR 的方向是收窄:三个原本 z.number() 的键现在只收非负整数;check:authorable-surface 无重生成即绿、check:api-surface 绿 ⇒ 无新键、无新导出符号 ⇒ 机械地板的两条触发项都不命中。

② 双载体机读 —— exit 0。

node scripts/pm/check-clause2-carriers.mjs --pair 18657
✓ PR #18657 / card #18124 — the clause-② declaration is readable in the fixed spelling
  and both carriers agree, and its diff carries no widening tell.

③ CI 全绿 —— 35 个 check-run:33 success · 2 skipped · 0 其它,且 check-expected-skips --pr 18657 exit 0,两个 skip 逐个在名册上(Console Pin Gate 无 console 面变更;Packed-tarball smokeneeds:pack-smoke 标)。⛔ 非 required 子集,是全量。


merge=os-regen 面:A 节四步序实测无事可做,⛔ 不是跳过

本 PR 碰 16 条 os-regen 路径(content/docs/references/** ×15 + skills/*/references/_index.md ×1,清单当场读 grep os-regen .gitattributes)。合并基 922c755888:

读数
分支改过、且 main 在窗口内改过的 os-regen 路径(驱动器唯一能动手的集合) 0
main 自合并基以来动过的 os-regen 路径(全部,不限本分支) 0
⭐ 亮控 — main 自合并基以来的提交数 6
⭐ 亮控 — main 自合并基以来动过的 packages/spec/src 文件数 2(contracts/automation-service.ts + 其 test,来自 #18635)

⇒ 静默吞边的危险按构造不存在:git 只在两侧都改过一条路径时才调驱动器,而那个集合读 0。两条亮控都非零,所以这个 0 ⛔ 不是「窗口里什么都没动」的平凡真 —— 本班上一次就栽在亮控自身为零上,这次先验了。⇒ 直接入队;落地后仍按纪律重验生成物(队列自己的 merge 也跑同一个驱动器)。

Docs Drift 咨询已作答,读数为零

机器人点名 content/docs/protocol/kernel/error-handling.mdx(经由 BaseResponseSchema)。逐字查:该页对 BaseResponseSchema 只有一处链接,全篇 0 处提 duration0 处陈述任何单位 ⇒ 本 PR 没有让它变假。咨询已答,⛔ 无需人工复读。


⚠️ 一条如实记录的档位事实(⛔ 不掩,也 ⛔ 不当作入队闸)

本卡认领时的文件面读回 no path-derived mandate(逐字:the surface hits none of the 3 declared glob(s)),⇒ Container & model: model: default judgement tier 在认领那一刻是对的

实际 diff 的文件面读回:

Model tier — MANDATORY: claude-fable-5-1 (derived from the file surface, not recalled).
  - skills/objectstack-api/references/_index.md ⇢ 'skills/**' — clause ①
    … and one such path closes the exit for the whole card

差别整个来自认领评论里那条预先申报的开放臂:「任何门禁反向要求的派生物」。生成器把一行写进了 skills/**,于是实际面带上了一条强制 fable 的路径,而派发是按默认档跑的。

本席的判断,连同它的边界:这一行是生成器自己 --check 按字节认证的纯重生成,clause ① 要防的危险(手写错的 skill 内容原样发运给第三方)在这里结构性不存在;fable 档的判断力 ⛔ 加不到一个字节相等证明上。⇒ 不重派、不阻队。但这条「预申报的派生物臂能把 skills/** 悄悄拉进面、从而同时欠一个档位与一份净增行数预算(SKILL.md 546)」是协议层的真缺口,本席另立卡,⛔ 不在本 PR 处理。

净增行数本身 dev 已经量了并且很小:改动文件 50 → 51 行(+1,生成);整包每个 SKILL.md 之和 6145 → 6145(+0);check-skills-token-ratchet exit 0。


下一步

转 ready + 入队。⚠️ 首行是 Part of #18124、全文零闭合关键词 ⇒ 合并 ⛔ 不关卡;落地后由本席摘 pm:dispatched、把 #18124 放回队列并写清「交付八行、剩两行各欠什么裁决」。


Generated by Claude Code

@os-bill
os-bill marked this pull request as ready for review September 17, 2026 13:16
@os-bill
os-bill enabled auto-merge September 17, 2026 13:16
@os-bill
os-bill added this pull request to the merge queue Sep 17, 2026
Merged via the queue into main with commit c049e74 Sep 17, 2026
44 checks passed
@os-bill
os-bill deleted the claude/issue-18124-duration-rows-adopt-declaration branch September 17, 2026 13:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:system size/xl tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants