Skip to content

Commit 45a3589

Browse files
committed
docs(changeset): the fifteen system/ duration renames (#15679)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4138K1EG7kQ81FNba5Kp4
1 parent d1d63f3 commit 45a3589

1 file changed

Lines changed: 110 additions & 0 deletions

File tree

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
---
2+
"@objectstack/spec": minor
3+
---
4+
5+
feat(spec)!: the fifteen `system/` duration keys carry their unit in the key name (#15679, ruling B on #14478)
6+
7+
<!-- adr-0087: registered system-cache-durations-unit-in-key, system-collaboration-durations-unit-in-key, system-failover-health-check-interval-unit-in-key, system-metrics-window-durations-unit-in-key, system-object-storage-durations-unit-in-key, system-registry-config-durations-unit-in-key, system-tracing-span-duration-unit-in-key, system-worker-queue-rate-limit-duration-unit-in-key -->
8+
9+
**BREAKING** — fifteen published `system/` duration keys are renamed and
10+
tombstoned. Shipped as `minor` under the repo's launch-window convention for
11+
breaking changes; the hand-migration prescriptions are registered under protocol
12+
major 18. Maintainer ruling B on #14478 (2026-09-02, decision batch #43,
13+
「同意」).
14+
15+
`check:duration-unit-keys` makes a duration-shaped `z.number()` carry its unit
16+
in the key NAME, never only in its `.describe()` prose, and grandfathers no
17+
existing offender. Stack card 1/6 (#15676) landed the rule's two structural
18+
exemptions, card 2/6 (#15677) cleared `api/` and card 3/6 (#15678) cleared
19+
`kernel/`; this card clears `system/`. Measured with the gate itself:
20+
`src/system/**` goes from 15 offenders to **0**, and the whole-tree count falls
21+
**22 → 7**.
22+
23+
## FROM → TO
24+
25+
| key | replacement | unit |
26+
|:--|:--|:--|
27+
| `CacheTier.ttl` | `ttlSeconds` | seconds |
28+
| `CacheAvalanchePrevention.circuitBreaker.resetTimeout` | `resetTimeoutSeconds` | seconds |
29+
| `CollaborationSessionConfig.idleTimeout` | `idleTimeoutMs` | milliseconds |
30+
| `CollaborationSessionConfig.snapshot.interval` | `intervalMs` | milliseconds |
31+
| `FailoverConfig.healthCheckInterval` | `healthCheckIntervalSeconds` | seconds |
32+
| `MetricAggregationConfig.window.size` | `durationSeconds` | seconds |
33+
| `ServiceLevelIndicator.window.size` | `durationSeconds` | seconds |
34+
| `ServiceLevelObjective.period.duration` | `durationSeconds` | seconds |
35+
| `AccessControlConfig.maxAge` | `maxAgeSeconds` | seconds |
36+
| `StorageConnection.timeout` | `timeoutMs` | milliseconds |
37+
| `RegistryUpstream.syncInterval` | `syncIntervalSeconds` | seconds |
38+
| `RegistryUpstream.timeout` | `timeoutMs` | milliseconds |
39+
| `RegistryConfig.cache.ttl` | `ttlSeconds` | seconds |
40+
| `Span.duration` | `durationMs` | milliseconds |
41+
| `QueueConfig.rateLimit.duration` | `durationMs` | milliseconds |
42+
43+
**Every value is unchanged** — only key names move, and every default moves with
44+
its key (`CacheTier` still defaults to 300, `CollaborationSessionConfig` to
45+
300000, `FailoverConfig` to 30, `RegistryUpstream.timeoutMs` to 30000,
46+
`RegistryConfig.cache.ttlSeconds` to 3600). Bounds move with their keys too, so
47+
`syncIntervalSeconds` still refuses anything under 60 and `timeoutMs` anything
48+
under 1000. Every old spelling is a `retiredKey()` tombstone, so it fails `tsc`
49+
at the authoring site (input type `never`) and fails the parse with the rename
50+
prescription rather than a bare unrecognized-key error.
51+
52+
## ⚠️ Two `maxAge` keys, opposite sides of the line — do not harmonise them
53+
54+
`AccessControlConfig.maxAge` (bucket CORS) is **renamed** to `maxAgeSeconds`.
55+
Its twin `shared/CorsConfig.maxAge` (HTTP CORS) is **not**, and keeps its bare
56+
name under an `externalVocabulary` marker.
57+
58+
The asymmetry is the whole point. Every bucket-CORS standard the first value is
59+
forwarded to already spells the unit — S3 `MaxAgeSeconds`, GCS `maxAgeSeconds`,
60+
Azure `MaxAgeInSeconds` — so marking that key would have exempted a *deviation
61+
from* the cited standard rather than a mirror of it. The Fetch response header
62+
the second mirrors, `Access-Control-Max-Age`, genuinely carries no unit token.
63+
A find-and-replace across both leaves no gate red: the marker exempts the twin
64+
either way. A pin test in `object-storage.test.ts` is the only guard.
65+
66+
## ⚠️ `window.size` becomes `durationSeconds`, not the mechanical `sizeSeconds`
67+
68+
The gate prints `sizeSeconds` for the two `window.size` keys, and that name is
69+
wrong on its face. `size` means a byte or row count everywhere else in this spec
70+
`CacheTier.maxSize` is megabytes, `RegistryConfig.cache.maxSize` is bytes, and
71+
`MetricExportConfig.batch.size` on the very same file is a record count — so
72+
`sizeSeconds` would have kept the misleading half of the name and bolted a unit
73+
onto it. `windowSeconds` was rejected for a plainer reason: the parent key is
74+
already `window`, so it would read `window.windowSeconds`.
75+
76+
`durationSeconds` names what the number is, and the file supplied its own
77+
precedent: `ServiceLevelObjective.period.duration` already called a period
78+
length a duration. After the rename all three read alike. The prescription says
79+
so explicitly, so the next author does not read the departure as a slip and
80+
"correct" it back to the mechanical name.
81+
82+
## Dispositions — eight semantic entries, no D2 conversion
83+
84+
Justified per key rather than defaulted, and this card's answer is uniform:
85+
**none of the fifteen gets an ADR-0087 D2 conversion.** A D2 conversion runs
86+
over a stack document, and `stack.zod.ts` declares no `cache`, `collaboration`,
87+
`disasterRecovery`, `metrics`, `objectStorage`, `registry`, `tracing` or
88+
`worker` root — none of these twelve defs is a stack collection member or a
89+
registered metadata kind stored as a `sys_metadata` row, so the conversion chain
90+
has no seam that would see one. They are host configuration (`CacheTier`,
91+
`FailoverConfig`, `StorageConnection`, `RegistryUpstream`, `RegistryConfig`,
92+
`QueueConfig`), call arguments (`CollaborationSessionConfig`) and
93+
runtime-emitted measurements (`Span`). Each therefore carries a **semantic**
94+
entry, which is what ruling B prescribes for a key that is not authorable stack
95+
metadata. All fifteen are registered by exact key in `RETIRED_KEYS_BY_MAJOR`,
96+
nested spellings included.
97+
98+
## Keys deliberately left alone
99+
100+
`FailoverConfig.dns.ttl` is a declared `externalVocabulary` mirror of the DNS
101+
resource-record TTL field (RFC 1035 §4.1.3) and keeps its bare name.
102+
`CacheAvalanchePrevention.lockout.lockTimeoutMs` was already correct — and it is
103+
milliseconds where its `resetTimeoutSeconds` sibling is seconds, so the two must
104+
not be migrated as if they were one unit. `MetricExportConfig.batch.size` is a
105+
record count and `QueueConfig.rateLimit.max` is a task count: neither is a
106+
duration, so neither has a unit to carry. `ServiceLevelObjective.errorBudget`'s
107+
burn-rate `window` and the OpenTelemetry exporter `timeout` name no unit
108+
anywhere in their prose, so both are outside the gate's population entirely.
109+
Pin tests assert each of these, so a later sweep cannot read this card as
110+
"every duration-shaped number on these files".

0 commit comments

Comments
 (0)