From 032d1746a16aa27058e9b1b473cca3becc570625 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 13 Sep 2026 08:45:58 +0000 Subject: [PATCH 1/3] wip(spec): rename RuntimeConfig.resourceLimits.timeout to timeoutMs Co-authored-by: Claude Claude-Session: https://claude.ai/code/session_015c5G6TmpMKgnusmTpD7Ntt --- .../kernel/plugin-security-advanced.test.ts | 79 ++++++++++++++++--- .../kernel/plugin-security-advanced.zod.ts | 31 +++++++- ...__RuntimeConfig__resourceLimits.timeout.ts | 17 ++++ ...rnel-runtime-config-timeout-unit-in-key.ts | 62 +++++++++++++++ 4 files changed, 176 insertions(+), 13 deletions(-) create mode 100644 packages/spec/src/migrations/entries/retired-keys/18.kernel__RuntimeConfig__resourceLimits.timeout.ts create mode 100644 packages/spec/src/migrations/entries/semantic/18.kernel-runtime-config-timeout-unit-in-key.ts diff --git a/packages/spec/src/kernel/plugin-security-advanced.test.ts b/packages/spec/src/kernel/plugin-security-advanced.test.ts index b38eb98fd3..f284c46b6d 100644 --- a/packages/spec/src/kernel/plugin-security-advanced.test.ts +++ b/packages/spec/src/kernel/plugin-security-advanced.test.ts @@ -30,7 +30,7 @@ describe('Plugin Security Advanced Schemas', () => { resourceLimits: { maxMemory: 16777216, // 16MB maxCpu: 50, - timeout: 30000, + timeoutMs: 30000, }, }; const result = RuntimeConfigSchema.parse(config); @@ -67,7 +67,7 @@ describe('Plugin Security Advanced Schemas', () => { resourceLimits: { maxMemory: 1073741824, // 1GB maxCpu: 100, - timeout: 60000, + timeoutMs: 60000, }, }; const result = RuntimeConfigSchema.parse(config); @@ -392,17 +392,76 @@ describe('Plugin security durations carry their unit (#15678)', () => { expect(parsed.vulnerabilityDisclosure?.responseTimeHours).toBe(24); expect(parsed.vulnerabilityDisclosure?.bugBounty).toBe(true); }); +}); - // A NEGATIVE control on the same file: this key names its unit only in the - // JSDoc above it, a channel the gate does not read (it reads `.describe()` and - // `.meta({ description })`) — so the gate lists it without judging it, and it - // is outside this rename. The JSDoc-channel gap is #15939. Without this test, a - // later sweep reads the four renames above as "every timeout on this file". - it('leaves `RuntimeConfig.resourceLimits.timeout` bare — its describe names no unit', () => { - const parsed = RuntimeConfigSchema.parse({ +// THE FIFTH DURATION, closed by #15939 ruling A — what this slot used to pin. +// +// Until this card, this slot held a NEGATIVE control: `resourceLimits.timeout` +// named its unit only in the JSDoc above it, a channel `check:duration-unit-keys` +// does not read (it reads `.describe()` and `.meta({ description })`), so the +// gate listed the key in its census without judging it and #15678 deliberately +// left it alone. The control existed so a later sweep could not read the four +// renames above as "every timeout on this file". +// +// #15939 IS that sweep, and it is the reason the control was written. Director +// seat ruling A (2026-09-11, maintainer 「同意」, decision batch #115) remediates +// the JSDoc-channel population per file, so the key is renamed here and this +// slot now pins the OPPOSITE fact: the bare spelling is refused with the rename +// prescription, and the suffixed spelling parses at the same magnitude. The +// guard succeeded by failing — ⛔ it was not deleted, weakened or skipped. +describe('RuntimeConfig.resourceLimits.timeout → timeoutMs (#15939 ruling A, #14478)', () => { + it('REFUSES the retired `resourceLimits.timeout` with the rename to `timeoutMs`', () => { + const result = RuntimeConfigSchema.safeParse({ engine: 'process' as const, resourceLimits: { maxMemory: 1073741824, timeout: 60000 }, }); - expect(parsed.resourceLimits?.timeout).toBe(60000); + expect(result.success).toBe(false); + const issue = result.error!.issues.find( + (i) => i.path.join('.') === 'resourceLimits.timeout', + ); + expect(issue).toBeDefined(); + expect(issue!.code).not.toBe('unrecognized_keys'); + expect(issue!.message).toMatch( + /`RuntimeConfig\.resourceLimits\.timeout` was renamed.*Rename the key to `timeoutMs`/s, + ); + expect(issue!.message).toContain('the value (milliseconds) is unchanged'); + }); + + it('accepts `timeoutMs` at the magnitude the retired key carried, beside its siblings', () => { + const parsed = RuntimeConfigSchema.parse({ + engine: 'process' as const, + resourceLimits: { maxMemory: 1073741824, maxCpu: 100, timeoutMs: 60000 }, + }); + expect(parsed.resourceLimits?.timeoutMs).toBe(60000); + expect(parsed.resourceLimits).not.toHaveProperty('timeout'); + expect(parsed.resourceLimits?.maxMemory).toBe(1073741824); + expect(parsed.resourceLimits?.maxCpu).toBe(100); + }); + + // The channel the whole of #15939 is about: the describe is what + // `content/docs/references/**` renders, and it now names the unit. + it('publishes the unit in the describe — the text the reference pages render', () => { + const limits = RuntimeConfigSchema.shape.resourceLimits.unwrap(); + expect(limits.shape.timeoutMs.description).toBe('Maximum execution time in milliseconds'); + }); + + // The two `timeout` keys on this file are DIFFERENT keys on different + // shapes, and both are now retired to the same `timeoutMs` token. Each + // refusal must name its own shape, or an upgrading author edits the wrong + // block — the confusion `SandboxConfig.process.timeout`'s own tombstone + // comment warned about while this key was still bare. + it('names its own shape, not the sandbox one, in the prescription', () => { + const runtime = RuntimeConfigSchema.safeParse({ + resourceLimits: { timeout: 60000 }, + }); + const sandbox = SandboxConfigSchema.safeParse({ process: { timeout: 30000 } }); + const runtimeMsg = runtime.error!.issues + .find((i) => i.path.join('.') === 'resourceLimits.timeout')!.message; + const sandboxMsg = sandbox.error!.issues + .find((i) => i.path.join('.') === 'process.timeout')!.message; + expect(runtimeMsg).toContain('`RuntimeConfig.resourceLimits.timeout`'); + expect(runtimeMsg).not.toContain('`SandboxConfig.process.timeout`'); + expect(sandboxMsg).toContain('`SandboxConfig.process.timeout`'); + expect(sandboxMsg).not.toContain('`RuntimeConfig.resourceLimits.timeout`'); }); }); diff --git a/packages/spec/src/kernel/plugin-security-advanced.zod.ts b/packages/spec/src/kernel/plugin-security-advanced.zod.ts index af629b3427..160ab1868d 100644 --- a/packages/spec/src/kernel/plugin-security-advanced.zod.ts +++ b/packages/spec/src/kernel/plugin-security-advanced.zod.ts @@ -176,6 +176,20 @@ export const PluginPermissionSetSchema = lazySchema(() => z.object({ * Runtime Configuration * Defines the execution environment for plugin isolation */ +// Declared ABOVE its consumer on purpose: `gen:schema` and +// `check:authorable-surface` run with `OS_EAGER_SCHEMAS=1`, which makes +// `lazySchema` evaluate the factory at module load, so a const declared after +// `RuntimeConfigSchema` would be read from its temporal dead zone. The four +// tombstone strings further down sit after their schemas for the same reason — +// every one of them is declared before the block that reads it. +const RUNTIME_RESOURCE_LIMITS_TIMEOUT_RETIRED = + '`RuntimeConfig.resourceLimits.timeout` was renamed to `timeoutMs` in @objectstack/spec 17 — ' + + 'the unit of a duration-shaped number lives in the key name, not only in the describe prose. ' + + 'Its unit (milliseconds) lived in a source JSDoc only and the published description read ' + + '"Maximum execution time", naming no unit at all, so a reader of the reference page could ' + + 'not tell 60000 milliseconds from 60000 seconds. Rename the key to `timeoutMs`; the value ' + + '(milliseconds) is unchanged.'; + export const RuntimeConfigSchema = lazySchema(() => z.object({ /** * Runtime engine type @@ -289,10 +303,21 @@ export const RuntimeConfigSchema = lazySchema(() => z.object({ .describe('Maximum CPU usage percentage'), /** - * Execution timeout in milliseconds + * Execution timeout in milliseconds. + * + * Renamed from `timeout` (#15939 ruling A, executing #14478 ruling B): the + * unit lived in this JSDoc only, and `.describe()` — the text + * `content/docs/references/kernel/plugin-security-advanced.mdx` publishes — + * read "Maximum execution time" and named none. Spelled `Ms`, the same + * token `SandboxConfig.process.timeoutMs` on this file already carries. + * Tombstoned rather than deleted because this nested `resourceLimits` + * object is not `.strict()`. */ - timeout: z.number().int().min(0).optional() - .describe('Maximum execution time'), + timeoutMs: z.number().int().min(0).optional() + .describe('Maximum execution time in milliseconds'), + + /** Tombstone for the rename above (#15939 ruling A, executing #14478). */ + timeout: retiredKey(RUNTIME_RESOURCE_LIMITS_TIMEOUT_RETIRED), }).optional(), })); diff --git a/packages/spec/src/migrations/entries/retired-keys/18.kernel__RuntimeConfig__resourceLimits.timeout.ts b/packages/spec/src/migrations/entries/retired-keys/18.kernel__RuntimeConfig__resourceLimits.timeout.ts new file mode 100644 index 0000000000..f410479509 --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-keys/18.kernel__RuntimeConfig__resourceLimits.timeout.ts @@ -0,0 +1,17 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #15939 ruling A (per-file remediation of #14478 ruling B). This is the fifth +// duration on `kernel/plugin-security-advanced.zod.ts` and the one #15678 +// deliberately left alone: `resourceLimits.timeout` said "Execution timeout in +// milliseconds" in a source JSDoc and "Maximum execution time" in the +// `.describe()` the reference pages publish, so the published channel named no +// unit at all and the gate listed the key in its census without judging it. +// Renamed to `timeoutMs`, the same token `SandboxConfig.process.timeoutMs` on +// this file already carries. The value is unchanged. Tombstoned with +// `retiredKey()`: the nested `resourceLimits` object is not strict, so a bare +// deletion would silently strip the key. No D2 conversion: a `RuntimeConfig` is +// the engine block of the `SandboxConfig` a host or a plugin security manifest +// constructs, never a stack collection member or a stored row — the same +// reading `kernel-plugin-security-durations-unit-in-key` recorded for the four +// keys it renamed. See `kernel-runtime-config-timeout-unit-in-key`. +export const entry = 'kernel/RuntimeConfig:resourceLimits.timeout'; diff --git a/packages/spec/src/migrations/entries/semantic/18.kernel-runtime-config-timeout-unit-in-key.ts b/packages/spec/src/migrations/entries/semantic/18.kernel-runtime-config-timeout-unit-in-key.ts new file mode 100644 index 0000000000..98f6291cd5 --- /dev/null +++ b/packages/spec/src/migrations/entries/semantic/18.kernel-runtime-config-timeout-unit-in-key.ts @@ -0,0 +1,62 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +import type { SemanticMigration } from '../../types.js'; + +export const entry: SemanticMigration = { + id: 'kernel-runtime-config-timeout-unit-in-key', + // No backticks in `surface` — build-upgrade-guide.ts renders it inside a + // code span AND a table cell. + surface: 'RuntimeConfig resourceLimits.timeout (kernel/plugin-security-advanced.zod.ts)', + replacement: 'resourceLimits.timeoutMs — rename the key; the value (milliseconds) is unchanged', + reason: + 'This entry COMPLETES what #15678 deliberately left alone, and the two are meant to be read ' + + 'as a sequence. #15678 renamed the four plugin-security durations on this same file ' + + '(`kernel-plugin-security-durations-unit-in-key`) and recorded, accurately, that one key was ' + + 'out of its scope: RuntimeConfig.resourceLimits.timeout named its unit only in the JSDoc ' + + 'above it ("Execution timeout in milliseconds"), a channel check:duration-unit-keys does not ' + + 'read — it reads `.describe()` and `.meta({ description })` — and that key\'s describe ' + + '("Maximum execution time") named none, so the gate listed it among the duration-shaped keys ' + + 'without judging it, neither an offender nor an exemption. That JSDoc-channel gap was filed ' + + 'as #15939, and #15678\'s statement about its own scope stays true. #15939 is now ruled and ' + + 'this is its remediation: director-seat ruling A, 2026-09-11, carrying the maintainer\'s ' + + '「同意」 (decision batch #115), which remediates the 21-row JSDoc-channel population per file ' + + 'and lands the widened gate (#17635) last, into a tree already clean. So the reader who most ' + + 'needs the unit — the reader of the published reference page, who never sees the source ' + + 'JSDoc — got a bare integer on ' + + 'content/docs/references/kernel/plugin-security-advanced.mdx and could not tell 60000 ' + + 'milliseconds from 60000 seconds. The key is renamed and the describe is corrected in the ' + + 'same stroke, because under the #14478 rule moving the unit into the describe alone is ' + + 'itself a violation (unit in prose, none in the name). Spelled Ms, the same token ' + + 'SandboxConfig.process.timeoutMs on this very file already carries: counted on this tree, ' + + 'the suffixed family spells it that way in every member (29 key-position `timeoutMs` ' + + 'declarations across packages/spec/src/**/*.zod.ts, 40 distinct *Ms keys) and there is no ' + + 'timeoutMillis, timeout_ms or timeoutMS variant anywhere in packages/spec/src. Tombstoned ' + + 'with retiredKey() because the nested resourceLimits object is not strict, so a bare ' + + 'deletion would silently strip the key. Why a semantic entry and not a D2 conversion: a ' + + 'RuntimeConfig is the engine block of the SandboxConfig a host or a plugin security manifest ' + + 'constructs — stack.zod.ts declares no sandbox, security-policy or runtime-config collection ' + + 'and it is not a stored sys_metadata row — so the conversion chain has no seam that runs on ' + + 'it; the same reading #15678 recorded for the four keys it renamed. Measured on 146c291943: ' + + 'no in-repo runtime reads the key — packages/core/src/security/sandbox-runtime.ts, the one ' + + 'consumer of this shape, reads resourceLimits.maxCpu (3 occurrences of resourceLimits) and ' + + 'spells timeout 0 times; outside the zod file and its test the only live occurrences are the ' + + 'generated rows in content/docs/references/kernel/plugin-security-advanced.mdx, which this ' + + 'rename regenerates. The pinned objectui checkout — .objectui-sha = ' + + '53ded82bf7a494f54e344e19099dbf00854b8694 — spells resourceLimits.timeout 0 times across ' + + '6409 tracked files, against lit controls timeout 832, RuntimeConfig 236 and resourceLimits ' + + '2 on the same corpus; both resourceLimits hits are prose in packages/app-shell recording ' + + 'that objectui\'s own AppShellRuntimeConfig shares not one key with the spec\'s ' + + 'RuntimeConfig, so nothing there authors this key and no pin bump is owed. #15939, #15678, ' + + '#14478, ADR-0087.', + acceptanceCriteria: + 'Every RuntimeConfigSchema.parse(…) site, and every literal handed to a plugin sandbox as its ' + + 'runtime block, spells resourceLimits.timeoutMs; authoring resourceLimits.timeout fails to ' + + 'compile (input type `never`) and fails to parse with the rename prescription naming ' + + 'timeoutMs and the shape it belongs to. Behaviour is unchanged: a runtime given ' + + 'timeoutMs: 60000 aborts execution after sixty seconds exactly as timeout: 60000 did, and ' + + 'the min(0) integer bound rides along with the renamed key. The published describe reads ' + + '"Maximum execution time in milliseconds". Verify the two same-named keys on this one file ' + + 'apart: RuntimeConfig.resourceLimits.timeout and SandboxConfig.process.timeout both retire ' + + 'to a key spelled timeoutMs, and each refusal names its own shape so an upgrading author ' + + 'edits the right block.', +}; From 8657656ba3a37e6ea39f06b3e98ba2cdee404034 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 13 Sep 2026 08:56:52 +0000 Subject: [PATCH 2/3] feat(spec)!: RuntimeConfig.resourceLimits.timeout carries its unit in the key name Rename `RuntimeConfig.resourceLimits.timeout` to `timeoutMs` on `kernel/plugin-security-advanced.zod.ts`, the fifth duration on this file and the one #15678 deliberately left alone because its unit lived in a source JSDoc that `check:duration-unit-keys` does not read. - `retiredKey()` tombstone on the old spelling (the nested `resourceLimits` object is not strict, so a bare deletion would silently strip the key) - ADR-0087 D3 semantic entry `kernel-runtime-config-timeout-unit-in-key` and the `RETIRED_KEYS_BY_MAJOR[18]` row, registry regenerated by `gen:migration-registry` - the pin test that asserted the key stays bare is replaced, not removed - reference page regenerated by `gen:docs` - `minor` changeset with the FROM to TO mapping and the ADR-0087 disposition Co-authored-by: Claude Claude-Session: https://claude.ai/code/session_015c5G6TmpMKgnusmTpD7Ntt --- ...ntime-config-resource-limits-timeout-ms.md | 78 +++++++++++++++++++ .../kernel/plugin-security-advanced.mdx | 7 +- packages/spec/src/migrations/registry.ts | 73 +++++++++++++++++ 3 files changed, 155 insertions(+), 3 deletions(-) create mode 100644 .changeset/17781-runtime-config-resource-limits-timeout-ms.md diff --git a/.changeset/17781-runtime-config-resource-limits-timeout-ms.md b/.changeset/17781-runtime-config-resource-limits-timeout-ms.md new file mode 100644 index 0000000000..8cc7780ac5 --- /dev/null +++ b/.changeset/17781-runtime-config-resource-limits-timeout-ms.md @@ -0,0 +1,78 @@ +--- +"@objectstack/spec": minor +--- + +feat(spec)!: the fifth `kernel/plugin-security-advanced.zod.ts` duration — `RuntimeConfig.resourceLimits.timeout` — carries its unit in the key name (#17781, ruling A on #15939) + + + +**BREAKING** — the execution timeout on a plugin sandbox's runtime block carries its unit in the +key name. + +| | before | after | +|:--|:--|:--| +| authored key | `resourceLimits.timeout: 60000` | `resourceLimits.timeoutMs: 60000` | +| published describe | `Maximum execution time` | `Maximum execution time in milliseconds` | +| value + bound | milliseconds, `int().min(0)` | **unchanged** | + +## Migration + +```diff + resourceLimits: { + maxMemory: 1073741824, +- timeout: 60000, ++ timeoutMs: 60000, + } +``` + +Rename the key. The value is the same number of milliseconds it always was and the `int().min(0)` +bound rides along with it; nothing else on `RuntimeConfig` moves. + +## Why + +This is the key #15678 deliberately left alone, and this changeset closes it. `#15678` renamed the +four other plugin-security durations on this same file and recorded, accurately, that this one was +out of its scope: `resourceLimits.timeout` named its unit only in the JSDoc above it — "Execution +timeout in milliseconds" — a channel `check:duration-unit-keys` does not read (it reads +`.describe()` and `.meta({ description })`), and its describe said "Maximum execution time" and +named no unit at all. So the gate listed the key among the duration-shaped keys without judging it, +neither an offender nor an exemption, and the reader who most needs the unit — the reader of +`content/docs/references/kernel/plugin-security-advanced.mdx`, who never sees the source JSDoc — +got a bare integer and could not tell 60000 milliseconds from 60000 seconds. That JSDoc-channel gap +was filed as #15939 and is now ruled: director-seat **ruling A** (2026-09-11, maintainer 「同意」, +decision batch #115) remediates the population per file. Under the #14478 rule, moving the unit +into the describe alone is itself a violation — unit in prose, none in the name — so the key is +renamed and the describe is corrected in one stroke. + +Spelled `Ms`, the same token `SandboxConfig.process.timeoutMs` on this very file already carries: +counted on this tree, the suffixed family spells it that way in every member (29 key-position +`timeoutMs` declarations across `packages/spec/src/**/*.zod.ts`, 40 distinct `*Ms` keys), and no +`timeoutMillis`, `timeout_ms` or `timeoutMS` variant exists anywhere in `packages/spec/src`. + +⚠️ Two keys on this one file spelled `timeout` and both now retire to a key spelled `timeoutMs`: +`RuntimeConfig.resourceLimits.timeout` (this one) and `SandboxConfig.process.timeout` (#15678). +They are different keys on different shapes, so each refusal names its own shape — check which +block you are editing. + +## The kit + +- a `retiredKey()` tombstone on the old spelling, so `tsc` types it `never` and a value reaching + the parse raises the rename prescription instead of being silently stripped (the nested + `resourceLimits` object is not `.strict()`) +- the ADR-0087 D3 semantic entry `kernel-runtime-config-timeout-unit-in-key`, which states + explicitly that it completes what #15678 left alone so the two read as a sequence, and the + `RETIRED_KEYS_BY_MAJOR[18]` row `kernel/RuntimeConfig:resourceLimits.timeout`. No D2 conversion: + a `RuntimeConfig` is the engine block of the `SandboxConfig` a host or a plugin security manifest + constructs, `stack.zod.ts` declares no sandbox, security-policy or runtime-config collection, and + it is not a stored `sys_metadata` row — so the chain has no seam that runs on it. That is the + same reading #15678 recorded for the four keys it renamed. +- the pin test that asserted this key stays bare is **replaced, not removed**: it now pins that the + bare spelling is refused with the rename prescription, that `timeoutMs` parses at the same + magnitude beside its siblings, that the describe publishes the unit, and that the two same-named + `timeout` retirements on this file name their own shapes apart +- `content/docs/references/kernel/plugin-security-advanced.mdx` regenerated by `gen:docs`: three + rows move and the tombstone prescription renders in place of the old describe +- no authorable-surface row moves — that ratchet records top-level keys per def, and this key is + nested under `resourceLimits` (measured: `kernel/RuntimeConfig:` carries exactly + `engine`, `engineConfig` and `resourceLimits` across `authorable-surface/` and + `authorable-surface.base.json`, and `check:authorable-surface` is green without regeneration) diff --git a/content/docs/references/kernel/plugin-security-advanced.mdx b/content/docs/references/kernel/plugin-security-advanced.mdx index 10a4294e67..19b190eef1 100644 --- a/content/docs/references/kernel/plugin-security-advanced.mdx +++ b/content/docs/references/kernel/plugin-security-advanced.mdx @@ -359,7 +359,7 @@ Type of resource being accessed | :--- | :--- | :--- | :--- | | **engine** | `Enum<'v8-isolate' \| 'wasm' \| 'container' \| 'process'>` | optional (default: `"v8-isolate"`) | Execution environment engine | | **engineConfig** | `{ wasm?: object; container?: object; v8Isolate?: object }` | optional | | -| **resourceLimits** | `{ maxMemory?: integer; maxCpu?: number; timeout?: integer }` | optional | | +| **resourceLimits** | `{ maxMemory?: integer; maxCpu?: number; timeoutMs?: integer }` | optional | | ### Nested Shape: `RuntimeConfig.resourceLimits` @@ -367,7 +367,8 @@ Type of resource being accessed | :--- | :--- | :--- | :--- | | **maxMemory** | `integer` | optional | Maximum memory allocation | | **maxCpu** | `number` | optional | Maximum CPU usage percentage | -| **timeout** | `integer` | optional | Maximum execution time | +| **timeoutMs** | `integer` | optional | Maximum execution time in milliseconds | +| **timeout** | `never` | optional | [REMOVED] `RuntimeConfig.resourceLimits.timeout` was renamed to `timeoutMs` in @objectstack/spec 17 — the unit of a duration-shaped number lives in the key name, not only in the describe prose. Its unit (milliseconds) lived in a source JSDoc only and the published description read "Maximum execution time", naming no unit at all, so a reader of the reference page could not tell 60000 milliseconds from 60000 seconds. Rename the key to `timeoutMs`; the value (milliseconds) is unchanged. | --- @@ -394,7 +395,7 @@ Type of resource being accessed | :--- | :--- | :--- | :--- | | **engine** | `Enum<'v8-isolate' \| 'wasm' \| 'container' \| 'process'>` | optional (default: `"v8-isolate"`) | Execution environment engine | | **engineConfig** | `{ wasm?: object; container?: object; v8Isolate?: object }` | optional | | -| **resourceLimits** | `{ maxMemory?: integer; maxCpu?: number; timeout?: integer }` | optional | | +| **resourceLimits** | `{ maxMemory?: integer; maxCpu?: number; timeoutMs?: integer }` | optional | | ### Nested Shape: `SandboxConfig.filesystem` diff --git a/packages/spec/src/migrations/registry.ts b/packages/spec/src/migrations/registry.ts index 4ab46c7fb5..fa6c0adc40 100644 --- a/packages/spec/src/migrations/registry.ts +++ b/packages/spec/src/migrations/registry.ts @@ -8683,6 +8683,64 @@ const step18: MigrationStep = { + 'codebase must now read responseTimeHours and responseTimeMs respectively, and neither ' + 'accepts the bare name.', }, + { + id: 'kernel-runtime-config-timeout-unit-in-key', + // No backticks in `surface` — build-upgrade-guide.ts renders it inside a + // code span AND a table cell. + surface: 'RuntimeConfig resourceLimits.timeout (kernel/plugin-security-advanced.zod.ts)', + replacement: 'resourceLimits.timeoutMs — rename the key; the value (milliseconds) is unchanged', + reason: + 'This entry COMPLETES what #15678 deliberately left alone, and the two are meant to be read ' + + 'as a sequence. #15678 renamed the four plugin-security durations on this same file ' + + '(`kernel-plugin-security-durations-unit-in-key`) and recorded, accurately, that one key was ' + + 'out of its scope: RuntimeConfig.resourceLimits.timeout named its unit only in the JSDoc ' + + 'above it ("Execution timeout in milliseconds"), a channel check:duration-unit-keys does not ' + + 'read — it reads `.describe()` and `.meta({ description })` — and that key\'s describe ' + + '("Maximum execution time") named none, so the gate listed it among the duration-shaped keys ' + + 'without judging it, neither an offender nor an exemption. That JSDoc-channel gap was filed ' + + 'as #15939, and #15678\'s statement about its own scope stays true. #15939 is now ruled and ' + + 'this is its remediation: director-seat ruling A, 2026-09-11, carrying the maintainer\'s ' + + '「同意」 (decision batch #115), which remediates the 21-row JSDoc-channel population per file ' + + 'and lands the widened gate (#17635) last, into a tree already clean. So the reader who most ' + + 'needs the unit — the reader of the published reference page, who never sees the source ' + + 'JSDoc — got a bare integer on ' + + 'content/docs/references/kernel/plugin-security-advanced.mdx and could not tell 60000 ' + + 'milliseconds from 60000 seconds. The key is renamed and the describe is corrected in the ' + + 'same stroke, because under the #14478 rule moving the unit into the describe alone is ' + + 'itself a violation (unit in prose, none in the name). Spelled Ms, the same token ' + + 'SandboxConfig.process.timeoutMs on this very file already carries: counted on this tree, ' + + 'the suffixed family spells it that way in every member (29 key-position `timeoutMs` ' + + 'declarations across packages/spec/src/**/*.zod.ts, 40 distinct *Ms keys) and there is no ' + + 'timeoutMillis, timeout_ms or timeoutMS variant anywhere in packages/spec/src. Tombstoned ' + + 'with retiredKey() because the nested resourceLimits object is not strict, so a bare ' + + 'deletion would silently strip the key. Why a semantic entry and not a D2 conversion: a ' + + 'RuntimeConfig is the engine block of the SandboxConfig a host or a plugin security manifest ' + + 'constructs — stack.zod.ts declares no sandbox, security-policy or runtime-config collection ' + + 'and it is not a stored sys_metadata row — so the conversion chain has no seam that runs on ' + + 'it; the same reading #15678 recorded for the four keys it renamed. Measured on 146c291943: ' + + 'no in-repo runtime reads the key — packages/core/src/security/sandbox-runtime.ts, the one ' + + 'consumer of this shape, reads resourceLimits.maxCpu (3 occurrences of resourceLimits) and ' + + 'spells timeout 0 times; outside the zod file and its test the only live occurrences are the ' + + 'generated rows in content/docs/references/kernel/plugin-security-advanced.mdx, which this ' + + 'rename regenerates. The pinned objectui checkout — .objectui-sha = ' + + '53ded82bf7a494f54e344e19099dbf00854b8694 — spells resourceLimits.timeout 0 times across ' + + '6409 tracked files, against lit controls timeout 832, RuntimeConfig 236 and resourceLimits ' + + '2 on the same corpus; both resourceLimits hits are prose in packages/app-shell recording ' + + 'that objectui\'s own AppShellRuntimeConfig shares not one key with the spec\'s ' + + 'RuntimeConfig, so nothing there authors this key and no pin bump is owed. #15939, #15678, ' + + '#14478, ADR-0087.', + acceptanceCriteria: + 'Every RuntimeConfigSchema.parse(…) site, and every literal handed to a plugin sandbox as its ' + + 'runtime block, spells resourceLimits.timeoutMs; authoring resourceLimits.timeout fails to ' + + 'compile (input type `never`) and fails to parse with the rename prescription naming ' + + 'timeoutMs and the shape it belongs to. Behaviour is unchanged: a runtime given ' + + 'timeoutMs: 60000 aborts execution after sixty seconds exactly as timeout: 60000 did, and ' + + 'the min(0) integer bound rides along with the renamed key. The published describe reads ' + + '"Maximum execution time in milliseconds". Verify the two same-named keys on this one file ' + + 'apart: RuntimeConfig.resourceLimits.timeout and SandboxConfig.process.timeout both retire ' + + 'to a key spelled timeoutMs, and each refusal names its own shape so an upgrading author ' + + 'edits the right block.', + }, { id: 'kernel-startup-orchestrator-durations-unit-in-key', // No backticks in `surface` — build-upgrade-guide.ts renders it inside a @@ -12714,6 +12772,21 @@ export const RETIRED_KEYS_BY_MAJOR: Readonly> // this rename, and the divergence between the two shapes is filed separately. // See `kernel-startup-orchestrator-durations-unit-in-key`. 'kernel/PluginStartupResult:duration', + // #15939 ruling A (per-file remediation of #14478 ruling B). This is the fifth + // duration on `kernel/plugin-security-advanced.zod.ts` and the one #15678 + // deliberately left alone: `resourceLimits.timeout` said "Execution timeout in + // milliseconds" in a source JSDoc and "Maximum execution time" in the + // `.describe()` the reference pages publish, so the published channel named no + // unit at all and the gate listed the key in its census without judging it. + // Renamed to `timeoutMs`, the same token `SandboxConfig.process.timeoutMs` on + // this file already carries. The value is unchanged. Tombstoned with + // `retiredKey()`: the nested `resourceLimits` object is not strict, so a bare + // deletion would silently strip the key. No D2 conversion: a `RuntimeConfig` is + // the engine block of the `SandboxConfig` a host or a plugin security manifest + // constructs, never a stack collection member or a stored row — the same + // reading `kernel-plugin-security-durations-unit-in-key` recorded for the four + // keys it renamed. See `kernel-runtime-config-timeout-unit-in-key`. + 'kernel/RuntimeConfig:resourceLimits.timeout', // #15678 (stack card 3/6 of #14478) — ruling B. `SandboxConfig.process.timeout` // said "Process timeout in ms" in prose and nothing else. Renamed to // `timeoutMs`; the value is unchanged. Tombstoned with `retiredKey()` inside From 65dae07b9053953d606db247d1b6f70dffd2309c Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 13 Sep 2026 09:26:05 +0000 Subject: [PATCH 3/3] fix(spec): cite the objectui pin in the asserting spelling the gate recognises MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `check:objectui-pin-citations` accepts exactly two spellings and a third is a hard red. The semantic entry's citation broke across a string concatenation, so the sha landed on a source line the mention could not reach — unrecognised, and therefore outside every check. The reading was taken against the sha read out of this tree's `.objectui-sha`, i.e. the pin we build against, so the asserting `=` form is the true one. Co-authored-by: Claude Claude-Session: https://claude.ai/code/session_015c5G6TmpMKgnusmTpD7Ntt --- .../semantic/18.kernel-runtime-config-timeout-unit-in-key.ts | 5 +++-- packages/spec/src/migrations/registry.ts | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/spec/src/migrations/entries/semantic/18.kernel-runtime-config-timeout-unit-in-key.ts b/packages/spec/src/migrations/entries/semantic/18.kernel-runtime-config-timeout-unit-in-key.ts index 98f6291cd5..de3b9cbd8e 100644 --- a/packages/spec/src/migrations/entries/semantic/18.kernel-runtime-config-timeout-unit-in-key.ts +++ b/packages/spec/src/migrations/entries/semantic/18.kernel-runtime-config-timeout-unit-in-key.ts @@ -41,8 +41,9 @@ export const entry: SemanticMigration = { + 'consumer of this shape, reads resourceLimits.maxCpu (3 occurrences of resourceLimits) and ' + 'spells timeout 0 times; outside the zod file and its test the only live occurrences are the ' + 'generated rows in content/docs/references/kernel/plugin-security-advanced.mdx, which this ' - + 'rename regenerates. The pinned objectui checkout — .objectui-sha = ' - + '53ded82bf7a494f54e344e19099dbf00854b8694 — spells resourceLimits.timeout 0 times across ' + + 'rename regenerates. The pinned objectui checkout — this is the pin we build against, ' + + '`.objectui-sha` = `53ded82bf7a494f54e344e19099dbf00854b8694`, re-read from this tree — ' + + 'spells resourceLimits.timeout 0 times across ' + '6409 tracked files, against lit controls timeout 832, RuntimeConfig 236 and resourceLimits ' + '2 on the same corpus; both resourceLimits hits are prose in packages/app-shell recording ' + 'that objectui\'s own AppShellRuntimeConfig shares not one key with the spec\'s ' diff --git a/packages/spec/src/migrations/registry.ts b/packages/spec/src/migrations/registry.ts index fa6c0adc40..8ee939f6fe 100644 --- a/packages/spec/src/migrations/registry.ts +++ b/packages/spec/src/migrations/registry.ts @@ -8722,8 +8722,9 @@ const step18: MigrationStep = { + 'consumer of this shape, reads resourceLimits.maxCpu (3 occurrences of resourceLimits) and ' + 'spells timeout 0 times; outside the zod file and its test the only live occurrences are the ' + 'generated rows in content/docs/references/kernel/plugin-security-advanced.mdx, which this ' - + 'rename regenerates. The pinned objectui checkout — .objectui-sha = ' - + '53ded82bf7a494f54e344e19099dbf00854b8694 — spells resourceLimits.timeout 0 times across ' + + 'rename regenerates. The pinned objectui checkout — this is the pin we build against, ' + + '`.objectui-sha` = `53ded82bf7a494f54e344e19099dbf00854b8694`, re-read from this tree — ' + + 'spells resourceLimits.timeout 0 times across ' + '6409 tracked files, against lit controls timeout 832, RuntimeConfig 236 and resourceLimits ' + '2 on the same corpus; both resourceLimits hits are prose in packages/app-shell recording ' + 'that objectui\'s own AppShellRuntimeConfig shares not one key with the spec\'s '