Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
e22957d
feat(spec): declare the two duration-rule exemptions on the schema (#…
claude Sep 5, 2026
414e515
feat(spec)!: move the six epoch instants onto EpochMs and mark the ex…
claude Sep 5, 2026
3f95444
feat(spec): publish the externalVocabulary standard on the reference …
claude Sep 5, 2026
884646a
docs(changeset): the two duration-rule exemptions (#15676)
claude Sep 5, 2026
c1949b9
chore(spec): regenerate the derived artifacts and fix the consumers t…
claude Sep 5, 2026
796f24f
wip(spec): rename the 12 api/ duration keys, tombstones on the old sp…
claude Sep 5, 2026
7e870b1
wip(spec): readers, ADR-0087 registrations, ledger row for the api/ r…
claude Sep 5, 2026
37fc158
wip(spec): tombstone refusal tests, alias retarget, regenerated artif…
claude Sep 5, 2026
8cd4d8c
docs(changeset): the twelve api/ duration renames (#15677)
claude Sep 5, 2026
d7ebd6c
fix(docs-audit): declare the conversion-replay exclusion kind (b) rel…
claude Sep 5, 2026
dc3b847
docs: move the hand-written pages onto the renamed keys, and strip th…
claude Sep 5, 2026
9605808
docs(spec): drop the internal issue id from the four epoch-instant to…
claude Sep 5, 2026
19de17a
Merge remote-tracking branch 'origin/claude/issue-15676-epoch-ms-and-…
claude Sep 5, 2026
b463390
chore(spec): regenerate the reference page the merge deferred (#15677)
claude Sep 5, 2026
ecbc4be
wip(spec): rename the 14 kernel/ duration keys, tombstones on the old…
claude Sep 5, 2026
cf5c834
wip(spec): readers and ADR-0087 registrations for the kernel/ renames…
claude Sep 5, 2026
6bf1119
wip(spec): tombstone refusal tests and regenerated reference pages (#…
claude Sep 5, 2026
8f2b8f3
docs(changeset): the fourteen kernel/ duration renames (#15678)
claude Sep 5, 2026
b5f2a84
wip(spec): rename the 15 system/ duration keys, tombstones on the old…
claude Sep 5, 2026
28f60ba
wip(spec): readers, tombstone refusal tests and ADR-0087 registration…
claude Sep 5, 2026
dc67a94
wip(spec): regenerated artifacts and reference pages for the system/ …
claude Sep 5, 2026
d1d63f3
test(spec): fix the three new metrics pins — required label, and asse…
claude Sep 5, 2026
45a3589
docs(changeset): the fifteen system/ duration renames (#15679)
claude Sep 5, 2026
e5f0bb2
Merge trunk claude/issue-14478-duration-unit-in-key-name into the sys…
claude Sep 5, 2026
bcf0412
feat(spec)!: the last seven data/ ui/ ai/ integration/ duration keys …
claude[bot] Sep 5, 2026
c01fdcf
chore(spec): discharge the os-regen deferral — regeneration is a no-o…
claude Sep 5, 2026
9509c6d
Merge card 5/6 (squashed onto this branch as bcf0412a9) back into the…
claude Sep 5, 2026
599c3df
docs(spec): correct why the burn-rate window is left unrenamed
claude Sep 6, 2026
c3720bd
style(spec): re-wrap the corrected clause so the phrase is greppable …
claude Sep 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changeset/cli-explain-dashboard-refresh-interval-seconds.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"@objectstack/cli": patch
---

fix(cli): `explain` names the renamed `dashboard.refreshIntervalSeconds` (#14478)

The dashboard key catalogue `os explain` prints lists
`refreshIntervalSeconds` instead of `refreshInterval`, following the
`@objectstack/spec` rename of the authored key (the unit now lives in the key
name). Same key, same seconds; no other command output and no public surface of
this package changes.
138 changes: 138 additions & 0 deletions .changeset/data-ui-ai-integration-duration-keys-unit-in-key-name.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
---
"@objectstack/spec": minor
---

feat(spec)!: the last seven `data/` · `ui/` · `ai/` · `integration/` duration keys carry their unit in the key name (#15680, ruling B on #14478)

<!-- adr-0087: registered dashboard-refresh-interval-to-refresh-interval-seconds, connector-health-and-trigger-durations-unit-in-key, memory-persistence-auto-save-interval-to-ms, turso-config-timeout-to-timeout-ms, ai-conversation-analytics-duration-unit-in-key, data-nosql-query-options-timeout-unit-in-key -->

**BREAKING** — eight published duration keys are renamed and tombstoned. Shipped
as `minor` under the repo's launch-window convention for breaking changes; the
hand-migration prescriptions are registered under protocol major 18. Maintainer
ruling B on #14478 (2026-09-02, decision batch #43, 「同意」).

`check:duration-unit-keys` makes a duration-shaped `z.number()` carry its unit in
the key NAME, never only in its `.describe()` prose, and grandfathers no existing
offender. Card 1/6 (#15676) landed the rule's two structural exemptions, card 2/6
(#15677) cleared `api/`, card 3/6 (#15678) cleared `kernel/` and card 4/6
(#15679) cleared `system/`. This card clears the remainder, and is the first
where the gate itself reads **`zero offenders`** and exits `0`.

⚠️ That is green **for the gate's currently declared population**
(`packages/spec/src/**`), not for the epic. Card 6/6 widens the population and has
already measured an offender outside this subtree, so the gate is expected to go
red again by design. This changeset does not claim #14478 is finished.

## FROM → TO

| key | replacement | unit |
|:--|:--|:--|
| `dashboard.refreshInterval` | `refreshIntervalSeconds` | seconds |
| `CircuitBreakerConfig.monitoringWindow` | `monitoringWindowMs` | milliseconds |
| `ConnectorTrigger.interval` | `intervalSeconds` | seconds |
| `FilePersistenceConfig.autoSaveInterval` | `autoSaveIntervalMs` | milliseconds |
| `AutoPersistenceConfig.autoSaveInterval` | `autoSaveIntervalMs` | milliseconds |
| `TursoConfig.timeout` | `timeoutMs` | milliseconds |
| `NoSQLQueryOptions.timeout` | `timeoutMs` | milliseconds |
| `ConversationAnalytics.duration` | `durationSeconds` | seconds |

**Every value is unchanged** — only key names move. The two keys that carried a
default keep it (`CircuitBreakerConfig.monitoringWindowMs` still defaults to
60000, `FilePersistenceConfig.autoSaveIntervalMs` to 2000); the other six declare
none. Bounds move with their keys, so `autoSaveIntervalMs` still refuses anything
under 100 on both persistence arms, `NoSQLQueryOptions.timeoutMs` and
`TursoConfig.timeoutMs` still refuse a zero or negative integer, and
`ConversationAnalytics.durationSeconds` still refuses a negative length. Every old
spelling is a `retiredKey()` tombstone, so it fails `tsc` at the authoring site
(input type `never`) and fails the parse with the rename prescription rather than
a bare unrecognized-key error.

`dashboard`'s three rename-hint aliases — `refresh`, `autoRefresh`, `pollInterval`
— were repointed to `refreshIntervalSeconds` in the same edit. A hint left naming
the tombstone would have prescribed a key the shape refuses, which is the one
failure this rename could have introduced silently; a pin asserts all three.

## ⚠️ `dashboard.refreshInterval` crosses a repository boundary

This is the only rename in the whole stack whose consumer is in **another
repository**, so its reader could not move in this PR the way every other reader
in this card did. objectui's dashboard renderer reads the key, multiplies by
1000 to drive a `setInterval`, and republishes it as an authoring input the
console offers. Those sites move in a follow-up objectui card, sequenced behind
a release that actually ships this rename.

Until that lands the renderer sees an absent key and simply does not start its
refresh timer — a dashboard still renders, and still refreshes when the user
asks. The ADR-0087 conversion in this changeset is what keeps stored dashboards
and `os migrate meta` correct in the meantime.

## ⚠️ An eighth key moves that the gate did not list

`AutoPersistenceConfig.autoSaveInterval` is not a gate offender: its `.describe()`
named no unit at all, and the predicate judges prose against name.

It moves anyway because it is not a second key. `persistence: { type: 'auto' }`
resolves to the same Node.js file adapter as `type: 'file'`, and this value is
forwarded to the same `FileSystemPersistenceAdapter` field, in the same
milliseconds, under the same `min(100)` bound. Renaming one arm and not the other
would have left one value with two spellings across sibling arms of one union,
and the driver reading both — the consumer-side dialect Prime Directive #12
forbids. Its describe now names the unit too, and a pin asserts the refusal on
the arm the gate never listed, so a later reader cannot "restore" the bare
spelling as an over-application of the rule.

## Dispositions — four D2 conversions, two semantic entries

Judged per key from `stack.zod.ts`'s collection roots rather than defaulted, and
unlike card 4/6 this card's answer is split.

**D2 conversions** (six keys). `dashboards:`, `connectors:` and `datasources:`
are each a stack collection whose members are stored whole as `sys_metadata`
rows, so the conversion chain has a seam that sees them:
`dashboard-refresh-interval-to-refresh-interval-seconds`,
`connector-health-and-trigger-durations-unit-in-key` (both connector keys in one
pass, emitting separately),
`memory-persistence-auto-save-interval-to-ms` (both persistence arms) and
`turso-config-timeout-to-timeout-ms`. The two datasource conversions are
driver-aware for the reason `datasource-config-driver-key-aliases` records: a
bare `config.timeout` under another driver is that driver's own key and must not
be touched.

**Semantic entries** (two keys). `ConversationAnalytics` is computed at runtime
and handed to a consumer, and `NoSQLQueryOptions` is a per-call driver argument
reached only through `AggregationPipeline.options`. Neither is a stack collection
member or a stored row, so the chain has no seam — the disposition every
runtime-emitted measurement in this stack has taken.

All eight are registered by exact key in `RETIRED_KEYS_BY_MAJOR`.

## A retirement tombstone is no longer read as a secret

`refusedCredentialKeys` derives a driver's refused inline credentials by finding
`z.never()` keys in its config contract. A `retiredKey()` tombstone is also a
`z.never()`, and until this card no driver contract carried one — so "never ⇒
credential" held by accident of population rather than by construction. The first
tombstone to arrive (`TursoConfig.timeout`) made the derivation answer that a
millisecond budget was a secret: it was redacted off the datasource read path and
dragged a non-credential name into the fallback list every unrecognised driver is
scrubbed by.

The derivation now skips keys carrying the `[REMOVED] ` prefix `retiredKey()`
itself stamps. The exclusion is deliberately **negative** — skip declared
tombstones — rather than positive (keep only keys marked `format: 'password'`),
even though every credential slot in every builtin contract does carry that
marker today: under-redacting is the dangerous direction, so a future credential
key whose author forgets the marker is still scrubbed, and only a key that has
explicitly declared itself retired may drop out. Both directions are pinned.

## Keys deliberately left alone

`TursoConfig.sync.intervalSeconds` and `CircuitBreakerConfig.resetTimeoutMs`
already carried their unit — they are the same-shape neighbours that made the
bare `timeout` and `monitoringWindow` collisions visible, and pins assert they
did not move. `NoSQLQueryOptions.batchSize` is a COUNT of documents and every
number on `ConversationAnalytics` other than the duration is a count of messages,
tokens or events: a count has no unit to carry. The turso schema shipped by
`@objectstack/driver-turso` is a separate declaration outside this gate's
declared population and is not touched here; card 6/6 owns it, so the two
declarations disagree by design until that lands.
28 changes: 28 additions & 0 deletions .changeset/driver-memory-auto-save-interval-ms.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
"@objectstack/driver-memory": minor
---

feat(driver-memory)!: the file-persistence auto-save interval names its unit (#15680, ruling B on #14478)

<!-- adr-0087: registered memory-persistence-auto-save-interval-to-ms -->

**BREAKING** — `InMemoryDriverOptions.persistence.autoSaveInterval` and
`FileSystemPersistenceAdapter`'s `autoSaveInterval` constructor option are both
renamed to **`autoSaveIntervalMs`**, following the `@objectstack/spec` rename of
the authored keys on both persistence arms.

Same value, same milliseconds, same 2000 default, same `setInterval` cadence. The
option was always milliseconds — it is passed straight to `setInterval` — and the
spec's `min(100)` bound is what made the bare name dangerous rather than untidy:
100 reads as a plausible number of seconds, so an author who guessed the unit
wrong cleared the bound, was refused nowhere, and saved a thousand times more
often than intended.

Both persistence arms move together: `type: 'auto'` resolves to this same file
adapter and forwards the same field, so this package reads exactly one spelling
rather than two.

```diff
- new InMemoryDriver({ persistence: { type: 'file', autoSaveInterval: 5000 } })
+ new InMemoryDriver({ persistence: { type: 'file', autoSaveIntervalMs: 5000 } })
```
12 changes: 12 additions & 0 deletions .changeset/platform-objects-dashboard-refresh-interval-seconds.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"@objectstack/platform-objects": patch
---

fix(platform-objects): the dashboard metadata-form bundles follow the `refreshIntervalSeconds` rename (#14478)

The `metadataForms.dashboard` translation bundles key the auto-refresh field as
`refreshIntervalSeconds`, following the `@objectstack/spec` rename of the
authored key. Regenerated with `node scripts/check-i18n-bundles.mjs --write`; the
hand-written `zh-CN` / `ja-JP` / `es-ES` label and help text were carried across
the rename unchanged, because the field still means what it meant and each help
text already named the unit.
57 changes: 57 additions & 0 deletions .changeset/service-datasource-turso-timeout-ms-reader.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
---
"@objectstack/service-datasource": patch
---

fix(service-datasource): the shared libSQL config builder reads the canonical `config.timeoutMs` (#16023, follow-up on #15680)

`buildTursoDriverConfig` — the ONE seam both libSQL loaders go through (#7314) —
still consulted `config.timeout` after #15680 renamed that authored key to
`timeoutMs` and tombstoned the old spelling. A turso datasource authored the
canonical way therefore reached the seam, matched nothing, and had its timeout
**silently dropped**: no diagnostic in any channel.

The reader now consults `config.timeoutMs`. The DRIVER key it lands on is
unchanged and still spelled `timeout` — `TursoDriverConfig.timeout` is
published-but-inert (#16024), and renaming an inert key would ratify it as real,
which is what ADR-0049 exists to prevent. So this seam is the one place the
authored and driver spellings differ, and it now says so.

## No fallback arm for the retired spelling — the seam's own precedent

Both sibling arms in `default-datasource-driver-factory.ts` already answer this
in the same words: sqlite's "`filename` is the whole contract … so no `??`
tolerance survives here", mongo's "`url` is the one spelling". A renamed
datasource config key reaches a reader already canonical from two directions —
authoring refuses the retired spelling at the door (`retiredKey()`: `tsc`
`never` plus a parse-time prescription), and a stored `sys_metadata` row replays
the full ADR-0087 chain including `retiredFromLoadPath` entries at
`loadDatasourceRows` / `loadDatasourceRow`, so the D2 conversion
`turso-config-timeout-to-timeout-ms` has rewritten the key before this table
sees it. A `??` arm would be a consumer-side dialect (Prime Directive #12) for a
spelling both doors have closed.

`authToken`'s legacy arm is not a counter-precedent: it is kept for a LIVE route
(host boot translating `OS_DATABASE_AUTH_TOKEN` into a config it constructs
itself, which never meets the authoring schema), not for a retired spelling.

## Why the covering test did not catch it, and what replaces it

`TursoConfigSource.config` is a bare string-keyed bag, so `tsc` cannot see a
rename through it — the tombstone's type channel, which caught the alias tables
elsewhere in this stack, does not reach here. And the covering test authored the
**retired** spelling at all three of its turso `config` sites, so it was green
for exactly the behaviour that had become wrong. A test that pins the retired
spelling cannot notice this class of bug.

The three sites now author the canonical spelling, and the file gains cases
DERIVED from the authoring contract rather than written against today's key
list: they read `TursoConfigSchema`'s own `retiredKey()` tombstones and assert
that (a) every canonical replacement is consulted by some reader, and (b) no
retired spelling is — probed at every JS type a reader could type-test, with a
vacuity guard so a mis-derived empty list fails instead of passing. They hold
for the next rename without being edited.

The two sibling pins that author the same spec — `packages/cli`'s driver
correspondence check and `packages/runtime`'s cross-loader convergence check —
move to the canonical spelling with it; their assertions read driver keys and
are unchanged.
Loading