Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
'@objectstack/spec': patch
---

fix(spec): `os migrate meta` guidance for the `field-*`, `export-*`, `api-*`, `dataset-*`, `hook-*` and `metadata-*` migration entries states each lesson in words instead of citing tracker numbers

Clause-②: no

The ADR-0087 semantic entries of the `field-*` family (the runtime `field` write door, the
`maxLength` / `minLength` / `scale` / `precision` refusals, `scale` on a currency field,
`multiple` on a type that holds one value, and predicates that read through a reference),
the `export-*` family (the export permission axis, the eight constraint keys retired from
`ExportFieldMeta` and the retired export-job API family), the `api-*` family (the runtime `api` write door,
the split API entry and two duration keys renamed with their unit), the `dataset-*` family
(the aggregate × field-type refusals and the nested-relation list refused at save), the
`hook-*` family (the retired hook-session `roles` and the two `registerHook` refusals) and
the `metadata-*` family (the retired customization protocol, the re-partitioned endpoint
switches, the metadata-manager cache keys and the retired `additionalTypes`) are printed by
`os migrate meta` as the header, `why:` and `verify:` lines of a manual change. Their text
sent the reader to issue-tracker, decision-batch and ruling-record numbers — some of which
no longer resolve, and some in another repository — for what a ruling, measurement or fix
had decided; it now says what was decided, in the sentence being read. ADR ids are kept.

Text only: no entry id, `from` / `to`, conversion or matching logic changes, and the chain
rewrites exactly what it rewrote before. One entry's `surface` (the header line of
`dataset-measure-aggregate-field-type-refused`) drops the two tracker numbers it carried and
names nothing else differently. The generated migration registry, `spec-changes.json` and
the protocol upgrade guide carry the same text.
20 changes: 10 additions & 10 deletions docs/protocol-upgrade-guide.md

Large diffs are not rendered by default.

29 changes: 28 additions & 1 deletion packages/cli/test/migrate-meta-engine-guidance.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
* `os migrate meta` — the guidance it prints for the ADR-0087 semantic entries
* of the COVERED families (`engine-*`, `ui-*`, `plugin-*`, `driver-*`,
* `kernel-*`, `system-*`, `datasource-*`, `filter-*`, `action-*`, `data-*`,
* `element-*`) states each lesson in words and carries no tracker number.
* `element-*`, `field-*`, `export-*`, `api-*`, `dataset-*`, `hook-*`,
* `metadata-*`) states each lesson in words and carries no tracker number.
*
* ## What this pins
*
Expand Down Expand Up @@ -76,6 +77,7 @@ const TRACKER_ID = /#\d{4,5}\b/;
const COVERED_PREFIXES = [
'engine-', 'ui-', 'plugin-', 'driver-', 'kernel-', 'system-',
'datasource-', 'filter-', 'action-', 'data-', 'element-',
'field-', 'export-', 'api-', 'dataset-', 'hook-', 'metadata-',
];

/**
Expand All @@ -89,12 +91,19 @@ const REWRITTEN = [
'action-descriptor-resume-authority-default-flip',
'action-engine-facade-find-query-envelope',
'action-session-roles-to-positions',
'api-assembled-entry-split',
'api-error-retry-after-unit-in-key',
'api-runtime-config-durations-unit-in-key',
'api-runtime-create-withdrawn',
'data-driver-find-stream-retired',
'data-driver-query-omit-object',
'data-engine-batch-retired',
'data-field-changed-event-retired',
'data-file-value-duration-unit-in-key',
'data-nosql-query-options-timeout-unit-in-key',
'dataset-filter-nested-relation-equality-array-refused-at-save',
'dataset-measure-aggregate-field-type-refused',
'dataset-measure-selecting-aggregate-field-type-refused',
'datasource-config-inline-credential-refused',
'datasource-config-mongo-options-credential-refused',
'datasource-config-placeholder-refused',
Expand All @@ -118,6 +127,16 @@ const REWRITTEN = [
'engine-find-formula-filter-refused',
'engine-find-formula-order-by-refused',
'engine-update-upsert-retired',
'export-axis-opt-in',
'export-field-meta-constraints-retired',
'export-job-family-retired',
'field-currency-scale-refused',
'field-max-length-malformed-or-misplaced-refused',
'field-min-length-malformed-or-misplaced-refused',
'field-multiple-non-capable-type-refused',
'field-predicate-reference-traversal-refused',
'field-runtime-create-withdrawn',
'field-scale-precision-integer-refused',
'filter-between-blank-endpoint-refused',
'filter-between-field-reference-endpoint-refused',
'filter-comparand-types-and-widget-nested-slots-refused-at-save',
Expand All @@ -129,6 +148,9 @@ const REWRITTEN = [
'filter-query-face-comparands-refused-at-save',
'filter-regex-options-retired',
'filter-text-operator-declared-type-refused',
'hook-context-session-roles-retired',
'hook-register-empty-object-target-refused',
'hook-register-undispatched-lifecycle-event-refused',
'kernel-compatibility-matrix-estimated-migration-time-unit-in-key',
'kernel-context-preview-mode-retired',
'kernel-event-bus-retention-unit-in-key',
Expand All @@ -138,6 +160,11 @@ const REWRITTEN = [
'kernel-plugin-security-durations-unit-in-key',
'kernel-runtime-config-timeout-unit-in-key',
'kernel-startup-orchestrator-durations-unit-in-key',
'metadata-customization-protocol-retired',
'metadata-endpoints-switch-radius-repartitioned',
'metadata-manager-config-cache-ttl-unit-in-key',
'metadata-manager-config-inert-cache-keys-retired',
'metadata-plugin-additional-types-retired',
'plugin-activation-events-retired',
'plugin-auto-restart-never-reinitialised',
'plugin-manifest-contributes-dead-members-retired',
Expand Down
24 changes: 12 additions & 12 deletions packages/spec/spec-changes.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const entry: SemanticMigration = {
+ 'and ship it through `publishPackage`',
reason:
'The `api` registry entry declared `allowRuntimeCreate: true` and the runtime never '
+ 'honoured it. Measured on a real showcase boot (#5488): `PUT /api/v1/meta/api/'
+ 'honoured it. Measured on a real showcase boot: `PUT /api/v1/meta/api/'
+ 'e8_backdoor` answered 200 with `{"success":true,…,"message":"Saved …"}`, and the '
+ 'declared route then answered 404 forever — with NO `[EndpointMatcher] … EXCLUDED` '
+ 'line, because the endpoint was never in the index to be excluded from. The serving '
Expand All @@ -24,19 +24,22 @@ export const entry: SemanticMigration = {
+ 'making the matcher read `sys_metadata` re-opens cache, invalidation, tenancy and '
+ "the ADR-0110 D3 miss-vs-outage distinction on a new read path, and there is no "
+ 'business pull for Studio-authored endpoints today (zero `.api.*` artifacts author '
+ 'them at runtime; showcase uses the artifact route, #5040 E8 LIVE). '
+ 'them at runtime; showcase uses the artifact route, and its declared endpoints serve '
+ 'live). '
+ 'There is NO D2 conversion, for the reason this list exists: nothing in an authored '
+ 'source spells this key. `allowRuntimeCreate` is a PLATFORM registry value, not an '
+ 'authorable one, and the artifact route it points authors toward is untouched — a '
+ '`**/*.api.ts` file valid before this change is valid after it, byte for byte. What '
+ 'changed is a runtime HTTP verdict, so it is one semantic TODO for operators and '
+ 'Studio callers rather than a stack conversion — the same disposition '
+ '`BatchOptions.validateOnly` (#4052) takes. Consequently `gateApiDraftsForPublish` '
+ '(PR #5279) is retired with it: it gated a promotion into a state the matcher can '
+ '`BatchOptions.validateOnly` takes. Consequently `gateApiDraftsForPublish` '
+ 'is retired with it: it gated a promotion into a state the matcher can '
+ 'never read, and with the inlet closed no `api` draft can exist for it to judge. '
+ 'Re-entry is recorded in the ruling: if #2657 Part B promotes `apis` to a registered '
+ 'type WITH A REAL CONSUMPTION PATH, the flag flips back then — implementation first, '
+ 'declaration second. ADR-0049 / ADR-0121, #5488 (subsumes #5311).',
+ 'Re-entry is recorded in the ruling: if the Studio metadata-coverage work promotes `apis` '
+ 'to a registered type WITH A REAL CONSUMPTION PATH, the flag flips back then — '
+ 'implementation first, '
+ 'declaration second. The same refusal closes the direct-active write too, which had been a '
+ 'third path past the endpoint namespace and duplicate-path gates. ADR-0049 / ADR-0121.',
acceptanceCriteria:
'No caller creates or updates an `api` item through the runtime metadata API. '
+ '`PUT /api/v1/meta/api/{name}` answers 403 with `code: "NOT_CREATABLE"` and a body '
Expand All @@ -45,8 +48,8 @@ export const entry: SemanticMigration = {
+ 'as well as direct-active, because the gate runs before the draft/publish branch and '
+ 'does not read `mode`. ⚠️ Verify the artifact route is UNAFFECTED, which is the whole '
+ 'point of the change: a stack declaring `apis:` still compiles, still passes '
+ '`validateApiEndpointDeclarations` at publish (`publishPackage`, #5189) and at load '
+ '(`buildEndpointIndex`, PR #5203), and its endpoints still SERVE — that route was '
+ '`validateApiEndpointDeclarations` at publish (`publishPackage`) and at load '
+ '(`buildEndpointIndex`), and its endpoints still SERVE — that route was '
+ 'always the only one that served. An operator who genuinely needs the runtime door '
+ 'back on one deployment sets `OS_METADATA_WRITABLE=api`, the same single escape '
+ 'hatch `job` / `agent` / `capability` use; note that this unlocks the WRITE only, and '
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,10 @@ export const entry: SemanticMigration = {
+ 'and `false` is authoring intent rather than a veto, because permission sets are '
+ 'additive capability containers (ADR-0090). The super-user bits no longer confer it: '
+ '`viewAllRecords` / `modifyAllRecords` are "may see all data", not "may take a bulk '
+ 'copy". Registered by the #6350 stock reconciliation; #3544 / #3710 predate the #6148 '
+ 'completeness gate. ADR-0087, #3544 / #3710 (backfilled #6350).',
+ 'copy". Registered (backfilled) by the stock reconciliation that compared the breaking '
+ 'changesets already on the v17 release train against this ledger: the export axis, and its '
+ 'extension to the CSV attachments scheduled reports mail out, both predate the gate that makes '
+ 'a breaking changeset state its ADR-0087 disposition. ADR-0087.',
acceptanceCriteria:
'Every environment-authored permission set has been READ and decided, not just parsed: '
+ 'each object entry whose holders should keep exporting carries `allowExport: true`, and '
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ export const entry: SemanticMigration = {
+ 'passed in, so the map carried a second copy of facts the caller already held. '
+ "They existed for exactly one consumer — the import dry run's hand-copied "
+ 'pre-check mirror (`firstMissingRequiredField` / `firstConstraintViolation`, '
+ 'framework#3956) — and #4633 ruling D retired that mirror (PR #6532): the dry run '
+ 'added when the dry run was found skipping the field-level validation the real write ran) '
+ '— and the maintainer\'s 2026-08-06 ruling D (a validate-only protocol operation, so the '
+ 'dry run\'s prediction is the engine\'s verdict by construction) retired that mirror: the dry run '
+ "now asks `DataProtocol.validateData` for the engine's verdict, which reads the "
+ "object's own schema. That left all eight computed on every import and read by "
+ 'NOTHING, which is the declared-and-unread shape ADR-0049 exists for; a constraint '
Expand All @@ -30,8 +32,8 @@ export const entry: SemanticMigration = {
+ "verify, plugin-auth, plugin-dev) and the `objectui` sibling; plugin-auth's "
+ 'identity import forwards `prepared.metaMap` into `runImport` but reads only the '
+ 'presentation keys through `coerceRow`. '
+ 'Why this needs a ledger entry despite that sweep: it is the `findStream` (#4484) / '
+ '`IStorageService.list` (#5540) / `actor-user-roles-to-positions` (#6011) '
+ 'Why this needs a ledger entry despite that sweep: it is the `findStream` / '
+ '`IStorageService.list` / `actor-user-roles-to-positions` '
+ 'disposition — a published TS surface with NO spec schema, so there is no '
+ '`retiredKey()` tombstone and no parse rejection that could carry a prescription, '
+ 'and the ledger is the only channel that reaches an upgrader. It is if anything '
Expand All @@ -45,7 +47,8 @@ export const entry: SemanticMigration = {
+ 'remain fully authorable on a field definition and fully enforced by the engine, '
+ 'which is where they always lived. The only place these eight are ever spelled is '
+ "inside a consumer's own TypeScript, so no `objectstack migrate meta` transform can "
+ 'reach them. ADR-0049 / ADR-0087, #6536 (the sweep PR #6532 deliberately deferred).',
+ 'reach them. ADR-0049 / ADR-0087; this is the removal the dry-run change deliberately '
+ 'deferred to a sweep of its own.',
acceptanceCriteria:
'No code of yours reads any of the eight off a `buildFieldMetaMap` / '
+ '`prepareImportRequest` result. Grep your sources for `.required` / `.hasDefault` / '
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const entry: SemanticMigration = {
reason:
'The `field` registry entry declared `allowRuntimeCreate: true` and the platform never '
+ 'built a read path for it. Measured end-to-end through the real HttpDispatcher -> '
+ 'ObjectStackProtocolImplementation -> SysMetadataRepository (#7893): '
+ 'ObjectStackProtocolImplementation -> SysMetadataRepository: '
+ "`PUT /api/v1/meta/field/showcase_task.zz_probe` answered 200 with "
+ '{"success":true,"state":"active","message":"Saved field …"}, the row persisted, and '
+ '`GET /api/v1/meta/object/showcase_task` then listed fields = [title, status] with '
Expand All @@ -29,7 +29,8 @@ export const entry: SemanticMigration = {
+ '(a composition step that does not exist, ~20 `gate.fields` call sites, physical '
+ 'schema/migrations, and cold boot via `loadMetaFromDb`); if ever wanted it is a '
+ 'separate card — implementation first, declaration second. '
+ '⚠️ This is NOT the #5488 (`api`) rationale reused: that ruling rested on "zero '
+ '⚠️ This is NOT the `api` withdrawal\'s rationale reused (`api-runtime-create-withdrawn`): '
+ 'that ruling rested on "zero '
+ 'business pull", and "add a field" is the opposite — a core Studio/CRM operation. The '
+ 'justification here is that the operation REMAINS AVAILABLE on the route that actually '
+ 'composes: `object` keeps `allowRuntimeCreate: true`, so what is withdrawn is a second, '
Expand All @@ -39,30 +40,30 @@ export const entry: SemanticMigration = {
+ 'authorable one, and no authored source changes — an `**/*.object.ts` file valid before '
+ 'this change is valid after it, byte for byte. What changed is a runtime HTTP verdict, '
+ 'so it is one semantic TODO for operators and Studio callers rather than a stack '
+ 'conversion — the same disposition `api` (#5488) and `BatchOptions.validateOnly` '
+ '(#4052) take. ADR-0049 / ADR-0087, #7893 (split from #7743).',
+ 'conversion — the same disposition `api` and `BatchOptions.validateOnly` take. '
+ 'ADR-0049 / ADR-0087.',
acceptanceCriteria:
'No caller creates a standalone `field` item through the runtime metadata API. '
+ '`PUT /api/v1/meta/field/{object}.{name}` answers 403 with `code: "NOT_CREATABLE"` and '
+ 'a body naming both flags (`allowRuntimeCreate=false, allowOrgOverride=false`) and the '
+ 'prescription `PUT /api/v1/meta/object/:object with the new field in `fields``. The '
+ 'plural spelling `PUT /api/v1/meta/fields/{object}.{name}` folds onto the singular '
+ '(#7894) and earns the same refusal — verify it, because it was a separate door until '
+ 'and earns the same refusal — verify it, because it was a separate door until '
+ '2026-08-12. ⚠️ Verify the OBJECT route is UNAFFECTED, which is the whole point of the '
+ 'change: `PUT /api/v1/meta/object/{name}` with a new entry in `fields` still answers '
+ '200, and `GET /api/v1/meta/object/{name}` READS THE NEW FIELD BACK (assert on '
+ '`body.data.item.fields`, not `body.item`, which is undefined and makes an empty read '
+ 'look like a pass). Assert a DECLARED field is present in the same response, so a dead '
+ 'read cannot be what makes the check pass. '
+ '⚠️ #7743\'s overlay refusal is untouched and must stay: overwriting a field a code '
+ '⚠️ The field overlay refusal is untouched and must stay: overwriting a field a code '
+ 'package ships is still 403 `NOT_OVERRIDABLE`, a different gate for a different '
+ 'question — making field OVERRIDES legal was never part of this decision. '
+ 'DISPOSITION OF EXISTING ROWS: `field` rows already written through the retired channel '
+ 'stay in `sys_metadata` and are INERT — they were inert before this change too, since '
+ 'no read path ever composed them into an object, so nothing that used to work stops '
+ 'working and no data is silently reinterpreted. They remain self-readable by name and '
+ 'still report `_diagnostics.valid: true`, which asserts only that the isolated document '
+ 'is well-formed (see #8169 — the envelope has no "in effect" axis). They may be deleted '
+ 'is well-formed (the envelope has no "in effect" axis). They may be deleted '
+ 'at leisure: `deleteMetaItem` is deliberately NOT gated by this refusal, so repair stays '
+ 'possible. An operator who needs the write door back on one deployment sets '
+ '`OS_METADATA_WRITABLE=field`; note this unlocks the WRITE only — the field still will '
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,18 @@ export const entry: SemanticMigration = {
+ 'different untyped object that does carry `roles`, tracked apart and unaffected). '
+ 'Both branches were therefore dead on '
+ 'every real engine path — an authorization decision in shape only, and a second admin '
+ 'dialect competing with the one ADR-0090 D3 / ADR-0095 D3 sanction. #4839 (PR #5049) '
+ 'removed the readers; this removes the declaration, per ADR-0049 enforce-or-remove. '
+ 'dialect competing with the one ADR-0090 D3 / ADR-0095 D3 sanction. An earlier fix removed '
+ 'both readers, returning the record lock and the delegation guard to the one permission '
+ 'vocabulary; this removes the declaration, per ADR-0049 enforce-or-remove. '
+ 'This is a RUNTIME context, not stored metadata: the engine builds a HookContext per '
+ 'operation and nothing persists one, so no `sys_metadata` row, example or template '
+ 'can carry the key and there is no source for the D2 chain to rewrite — the '
+ '`openApi31` (#4579) / `activationEvents` (#4657) shape, one semantic TODO rather '
+ '`openApi31` / `activationEvents` shape, one semantic TODO rather '
+ 'than a stack conversion. The key IS tombstoned (`HookContextSchema` is deliberately '
+ 'not `.strict()` — a plain delete would strip it silently, #3733 / ADR-0104), so a '
+ 'not `.strict()` — a plain delete would strip it silently, as a removed field key was '
+ 'measured to be, ADR-0104), so a '
+ 'consumer that parses a context it was handed still meets the prescription. '
+ 'ADR-0049, #5050.',
+ 'ADR-0049.',
acceptanceCriteria:
'No hook reads `ctx.session.roles`; caller gating uses `ctx.session.userId` / '
+ '`ctx.session.isSystem`, and privilege comes from the security service '
Expand Down
Loading
Loading