Skip to content

Commit ec7bb50

Browse files
committed
fix(spec): stage-6 migration guidance states each lesson in words, not tracker numbers
The reason / replacement / acceptanceCriteria text (and one surface) of the rest-, analytics-, view-, package-, object-, sharing-, audit-, flow- and http- ADR-0087 semantic entries, plus the two carry-overs from stage 5 (api-error-retry-after-unit-in-key names its 2026-09-05 population ruling; inline-grid-column-currency-scale-refused drops the ruling-record ids and batch numbers its field sibling already dropped). Text only. Claude-Session: https://claude.ai/code/session_01ARcDurZ5j34RdqsGgc4jgH Co-authored-by: Claude <noreply@anthropic.com>
1 parent fb38607 commit ec7bb50

35 files changed

Lines changed: 189 additions & 132 deletions

File tree

‎packages/spec/src/migrations/entries/semantic/17.analytics-query-request-envelope-retired.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ export const entry: SemanticMigration = {
88
replacement: 'bare AnalyticsQuery body (top-level cube/measures/dimensions/where/...)',
99
reason:
1010
'The { cube, query: {...} } envelope was an HTTP-wire dialect of the retired degraded ' +
11-
'analytics shim (#3891), never stored in stack metadata — there is no source for the ' +
11+
'analytics shim (the fallback that answered /analytics/query when no analytics service ' +
12+
'was installed, and dropped the caller\'s identity and its `where` filter at the door), ' +
13+
'never stored in stack metadata — there is no source for the ' +
1214
'chain to rewrite. Callers of POST /analytics/query and /analytics/sql must move the ' +
1315
'query.* fields to the body top level themselves.',
1416
acceptanceCriteria:

‎packages/spec/src/migrations/entries/semantic/17.audit-log-action-enum-retired.ts‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ export const entry: SemanticMigration = {
2323
+ 'on every deployment, and still is — what changed is that the contract no longer '
2424
+ 'promises otherwise',
2525
reason:
26-
'Maintainer ruling 2026-08-12 (#7675), the retirement half of a two-half verdict: the '
27-
+ 'cheap writers get built (#8144 login/logout, #8145 config_change) and the enum '
26+
'Maintainer ruling 2026-08-12 on the audit log\'s writerless actions, the retirement half '
27+
+ 'of a two-half verdict: the cheap writers get built (`login` / `logout` on the auth '
28+
+ 'session hooks, `config_change` from the settings service) and the enum '
2829
+ 'values with no feature behind them are retired. 原则记录:空 widget + 永远查不到东西的'
2930
+ '过滤器是可见产品缺陷;审计面宁窄勿谎. '
3031
+ 'The defect was false compliance on a COMPLIANCE surface, which is the sharpest form '
@@ -54,7 +55,7 @@ export const entry: SemanticMigration = {
5455
+ 'on this object at all (`validateRecord` skips `readonly` fields, and every field '
5556
+ 'here is readonly), so nothing rejects stored history and no backfill is required or '
5657
+ 'wanted. Deleting audit history to satisfy a schema narrowing would be the one '
57-
+ 'genuinely destructive reading of this change. ADR-0049 / ADR-0087, #8147.',
58+
+ 'genuinely destructive reading of this change. ADR-0049 / ADR-0087.',
5859
acceptanceCriteria:
5960
'No consumer filters `sys_audit_log` on `action = "export"` or '
6061
+ '`action = "permission_change"` expecting rows: both were empty everywhere before '

‎packages/spec/src/migrations/entries/semantic/17.audit-log-action-restore-retired.ts‎

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,11 @@ export const entry: SemanticMigration = {
2424
+ '`sys_audit_log` on this value was reading an empty result set on every deployment, '
2525
+ 'and still is — what changed is that the contract no longer promises otherwise. If '
2626
+ 'you were counting on a restore trail, the capability itself is the missing piece '
27-
+ '(#1883, #3146), not this enum row',
27+
+ '(an undelete / purge permission lifecycle and a soft-delete recycle bin, neither built '
28+
+ 'yet), not this enum row',
2829
reason:
2930
'The same maintainer ruling as `audit-log-action-enum-retired`, carried to the one '
30-
+ "value #7675's own survey did not name (#8315, triage 2026-08-13). 原则记录:空 "
31+
+ "value that ruling's own survey did not name (triage 2026-08-13). 原则记录:空 "
3132
+ 'widget + 永远查不到东西的过滤器是可见产品缺陷;审计面宁窄勿谎. '
3233
+ '`restore` is the least ambiguous member of the family: the record-level writer '
3334
+ "could not have produced it even by accident, because `actionFor()` in "
@@ -37,7 +38,8 @@ export const entry: SemanticMigration = {
3738
+ 'asserted the opposite, so a declaration-reading audit scored the action as '
3839
+ 'covered: the `writes_only` list view offered it as a filter value, and the module '
3940
+ 'docblock of auth-event-audit.ts named it among the actions the writer emits. The '
40-
+ 'comment is the ADR-0049 declared-≠-enforced shape in its purest form (#8011) — a '
41+
+ 'comment is the ADR-0049 declared-≠-enforced shape in its purest form (the shape a '
42+
+ 'credential-storage audit had to settle by re-measuring two "hashed at rest" comments) — a '
4143
+ 'sentence next to a mechanism, contradicted by the type signature of that very '
4244
+ 'mechanism, with nothing in CI able to tell. Both declarations are corrected in one '
4345
+ 'change, and the invariant behind the comment (every declared action has a writer) '
@@ -50,16 +52,16 @@ export const entry: SemanticMigration = {
5052
+ 'field is `readonly: true`, so nobody authors an audit row and nobody authors this '
5153
+ 'enum. '
5254
+ '⚠️ This is a statement about the WRITER, not a product stance against undelete. '
53-
+ 'Soft delete/restore is parked, not rejected (#1883 pm:on-hold, #3146 '
54-
+ 'status:parked). If that capability lands, this value returns WITH its writer — the '
55+
+ 'Soft delete/restore is parked, not rejected: the undelete / purge lifecycle and the '
56+
+ 'recycle bin are both held open, not declined. If that capability lands, this value '
57+
+ 'returns WITH its writer — the '
5558
+ 'emission point, its tests, and the view that surfaces it — never as a bare enum '
5659
+ 'row again. '
5760
+ '⚠️ Historical ROWS are deliberately untouched, exactly as for the sibling entry: '
5861
+ 'the enum is not enforced on this object at all (`validateRecord` skips `readonly` '
5962
+ 'fields), so any stored row keeps parsing and reading back, and no backfill is '
6063
+ 'required or wanted. Deleting audit history to satisfy a schema narrowing would be '
61-
+ 'the one genuinely destructive reading of this change. ADR-0049 / ADR-0087, #8315, '
62-
+ '#7675, #8147.',
64+
+ 'the one genuinely destructive reading of this change. ADR-0049 / ADR-0087.',
6365
acceptanceCriteria:
6466
'No consumer filters `sys_audit_log` on `action = "restore"` expecting rows: it was '
6567
+ 'empty on every deployment before this change and behaves identically after it. '

‎packages/spec/src/migrations/entries/semantic/17.flow-retry-max-retries-required.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export const entry: SemanticMigration = {
1919
reason:
2020
'maxRetries had two defaults — FlowSchema `.default(0)` and the engine\'s ' +
2121
'`maxRetries ?? 3` — so an unstated count retried 0 times through the schema and 3 ' +
22-
'times through a hand-built definition (#4247). With the engine\'s copy removed the ' +
22+
'times through a hand-built definition. With the engine\'s copy removed the ' +
2323
'unstated count is unambiguously 0, and retrying zero times is exactly ' +
2424
"`strategy: 'fail'`, so the schema now refuses the combination instead of it silently " +
2525
'doing nothing. There is no lossless rewrite: 0 preserves the behaviour a parsed flow ' +

‎packages/spec/src/migrations/entries/semantic/17.http-request-errors-total-retired.ts‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ export const entry: SemanticMigration = {
2121
+ '`@objectstack/runtime`\'s `instrumentRouteHandler`, applied only by the dispatcher\'s '
2222
+ 'own route Proxy — so the series never saw auth\'s `getRawApp()` mount, the REST data '
2323
+ 'API via `RouteManager`, or any other inbound surface. Its two siblings in the same '
24-
+ 'family were moved to the transport seam (#9650/#9835 for the counter, #9834/#10004 '
25-
+ 'for the histogram) and this one could not follow: `HttpResponseObservation` carries '
24+
+ 'family were moved to the transport seam (the request counter, then the latency '
25+
+ 'histogram, both through the response-observing hook the transport was given) and this '
26+
+ 'one could not follow: `HttpResponseObservation` carries '
2627
+ '`{method, routePattern, status, elapsedMs}` and NO throw signal of any kind, so every '
2728
+ 'transport-side shape would have counted a DIFFERENT population rather than the same '
2829
+ 'one more widely. The divergence was measured in both directions — the dispatcher '
@@ -38,8 +39,8 @@ export const entry: SemanticMigration = {
3839
+ 'the series in its own dashboard or alert file, outside this repo. That is exactly why '
3940
+ 'this entry exists: for an operator whose Grafana keys on the string, the ledger is the '
4041
+ 'only notification channel there is. Same disposition, and the same reason, as '
41-
+ '`runtime-httpserver-wrapper-retired` (#5122) and `enhanced-api-error-field-errors-renamed` '
42-
+ '(#3977). ADR-0049 / ADR-0087, #9834.',
42+
+ '`runtime-httpserver-wrapper-retired` and `enhanced-api-error-field-errors-renamed`. '
43+
+ 'ADR-0049 / ADR-0087.',
4344
acceptanceCriteria:
4445
'No dashboard, alert rule or exporter config names `http_request_errors_total`: the '
4546
+ 'series stops receiving samples the moment 17.2.0 is deployed, so a panel keyed on it '

‎packages/spec/src/migrations/entries/semantic/17.http-server-runtime-vocabulary-retired.ts‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ export const entry: SemanticMigration = {
1818
+ 'record can only disagree with them. Server-level configuration that IS authorable '
1919
+ 'lives on `defineStack({ server })` / `StackServerConfigSchema`, which is unaffected)',
2020
reason:
21-
'The second and final ADR-0049 pass over `system/http-server.zod.ts`. #4938 removed the '
22-
+ 'CONFIG half (`HttpServerConfigSchema`, nine keys, zero readers, zero authoring '
21+
'The second and final ADR-0049 pass over `system/http-server.zod.ts`. The first removed '
22+
+ 'the CONFIG half (`HttpServerConfigSchema`, nine keys, zero readers, zero authoring '
2323
+ 'entry); this removes the RUNTIME half — a 7-member lifecycle event union with a '
2424
+ 'timestamped envelope, an eight-boolean capability report, and a five-state status '
2525
+ 'record with connection and request counters. Nothing ever emitted, consumed or '
@@ -39,10 +39,12 @@ export const entry: SemanticMigration = {
3939
+ 'this file when there was one. '
4040
+ 'With no carrier key there is nothing to tombstone, and with no author there is no '
4141
+ 'source or `sys_metadata` row for a D2 conversion to rewrite: RETIRED_DEFS_BY_MAJOR '
42-
+ 'plus this entry are the declaration — route 3, the same shape as #4938 in this very '
43-
+ 'file, #4834, #4988 and #5055. If host-implementer conformance becomes a real '
42+
+ 'plus this entry are the declaration — route 3, the same shape as the config half\'s '
43+
+ 'removal in this very file and the earlier removals of the dynamic plugin-loading family, '
44+
+ 'the `ui/` interaction configs and the widget / i18n shapes. If host-implementer '
45+
+ 'conformance becomes a real '
4446
+ 'requirement it returns through the ENFORCE route: an adapter contract with a checker '
45-
+ 'behind it, vocabulary second. ADR-0049, #5295.',
47+
+ 'behind it, vocabulary second. ADR-0049.',
4648
acceptanceCriteria:
4749
'No source imports `ServerEvent`, `ServerEventType`, `ServerEventSchema`, '
4850
+ '`ServerCapabilities`, `ServerCapabilitiesSchema`, `ServerCapabilitiesParsed`, '

‎packages/spec/src/migrations/entries/semantic/17.package-uninstall-explicit-all-tenants.ts‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ export const entry: SemanticMigration = {
88
replacement: 'explicit `allTenants: true` for a cross-tenant uninstall, or an `organizationId` to scope it',
99
reason:
1010
'An uninstall that named no organization matched EVERY organization\'s rows — measured '
11-
+ 'at 5 of 5 deleted, including a foreign org\'s (#7705, #7780). That width was never '
11+
+ 'at 5 of 5 deleted, including a foreign org\'s, while uninstall\'s orphaned-row defect was '
12+
+ 'being repaired. That width was never '
1213
+ 'chosen; it fell out of a missing argument, and the two transports of the same route '
1314
+ 'disagreed because of it. In protocol 17 the call is REFUSED instead: neither '
1415
+ '`organizationId` nor `allTenants: true` answers 400 `TENANT_SCOPE_REQUIRED` and '
@@ -19,7 +20,7 @@ export const entry: SemanticMigration = {
1920
+ 'at the call site — which is the whole reason the parameter had to become explicit '
2021
+ 'rather than conventional. Nothing in authored metadata spells this: it is a runtime '
2122
+ 'call-site contract, so it is one semantic TODO for operators and API callers rather '
22-
+ 'than a stack conversion — the same disposition `rest-requireauth-default-flip` (#12) '
23+
+ 'than a stack conversion — the same disposition `rest-requireauth-default-flip` (protocol 12) '
2324
+ 'takes for its own default flip.',
2425
acceptanceCriteria:
2526
'Every caller of `deletePackage` states its tenant scope. A caller that intends an '
@@ -31,5 +32,5 @@ export const entry: SemanticMigration = {
3132
+ 'that script was relying on the cross-tenant reading and must now say so on purpose. '
3233
+ 'The org-scoped path is unchanged — an uninstall carrying an `organizationId` still '
3334
+ 'removes that org\'s rows AND the environment-wide (`organization_id IS NULL`) rows, '
34-
+ 'exactly as #7705 left it.',
35+
+ 'exactly as the orphaned-row repair left it.',
3536
};

‎packages/spec/src/migrations/entries/semantic/17.rest-server-openapi31-block-removed.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,14 @@ export const entry: SemanticMigration = {
1818
+ 'only `api`/`crud`/`metadata`/`batch`/`routes`, and the served /openapi.json is the '
1919
+ 'pre-generated @objectstack/spec contract enriched with the live server URL and the '
2020
+ 'registered objects — a webhook declared here never appeared in any served document '
21-
+ '(ADR-0049; the #3197 connector-webhook shape one layer up). There is no behaviour '
21+
+ '(ADR-0049; the declared-but-unconsumed shape an earlier audit found in the connector '
22+
+ 'webhook and event enums one layer up). There is no behaviour '
2223
+ 'to preserve and nothing stored to rewrite: `RestServerConfig` is plugin TS '
2324
+ 'configuration (REST plugin constructor / `plugin-hono-server` `restConfig`), never '
2425
+ "a `sys_metadata` shape — the stack tree's `api` block declares only its four "
2526
+ 'scoping/auth knobs. The three schemas are removed with the key (zero import-level '
2627
+ 'consumers in objectstack / cloud / objectui); the key itself is tombstoned because '
27-
+ 'the schema is not `.strict()` and a plain delete would strip it silently. #4579.',
28+
+ 'the schema is not `.strict()` and a plain delete would strip it silently.',
2829
acceptanceCriteria:
2930
'No `RestServerConfig` value passed to the REST plugin (or `plugin-hono-server` '
3031
+ '`restConfig`) carries `openApi31` — a config that includes it now fails the parse '

‎packages/spec/src/migrations/entries/semantic/17.sharing-execution-context-retired.ts‎

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,29 @@ export const entry: SemanticMigration = {
1212
+ '`isSystem`) that sharing, approval and report enforcement signatures used to name',
1313
replacement:
1414
'`ExecutionContext` from `@objectstack/spec` — the complete '
15-
+ '`resolveAuthzContext` envelope the contracts have declared since #6523. Every one '
15+
+ '`resolveAuthzContext` envelope the sharing, approval and report contracts have declared '
16+
+ 'since they converged onto it. Every one '
1617
+ 'of the retired type\'s six fields exists on it under the same name and type, so a '
1718
+ 'value that satisfied the old type already satisfies the envelope: only the '
1819
+ 'annotation is rewritten, never the value',
1920
reason:
20-
'ADR-0049 enforce-or-remove, completing the #6206 ruling (2026-08-07: enforcement '
21-
+ 'adjudicates on the WHOLE envelope, never a per-site subset). This type was the '
21+
'ADR-0049 enforce-or-remove, completing the maintainer\'s ruling of 2026-08-07 on the '
22+
+ 'share-link context (enforcement adjudicates on the WHOLE envelope, never a per-site '
23+
+ 'subset). This type was the '
2224
+ 'declared context parameter of 36 signatures across three contracts — '
2325
+ '`ISharingService` / `ISharingRuleService`, `IApprovalService`, `IReportService` — '
2426
+ 'and it omitted four fields those gates need: `accessible_org_ids` (under the '
2527
+ '`group` tenancy posture this IS the Layer 0 wall, ADR-0105 D2), `org_user_ids`, '
2628
+ '`posture` (ADR-0095 D2) and `tabPermissions`. Its damage ran in the MIRROR '
27-
+ 'direction of the share-link twin (#6430 / PR #6511): nothing trimmed the VALUES — '
29+
+ 'direction of the share-link twin, which that same ruling moved onto the whole context: '
30+
+ 'nothing trimmed the VALUES — '
2831
+ "the engine middleware always handed the whole context down — it was the declared "
2932
+ 'TYPE that was narrow, so an implementation could not READ what it had been given '
3033
+ 'without casting out of its own contract (`const posture = (context as any).posture` '
31-
+ "in plugin-approvals' privileged-override gate). #6523 / PR #7068 converged the "
32-
+ 'contracts, PR #7140 and PR #7206 re-annotated the four implementations, and this '
33-
+ 'card removes the now-unreferenced declaration (#7070, #7218). '
34+
+ "in plugin-approvals' privileged-override gate). One change converged the "
35+
+ 'contracts, two more re-annotated the four implementations (sharing and audit, then '
36+
+ 'approvals and reports), and this change removes the now-unreferenced declaration, the '
37+
+ 'deletion that split had deferred. '
3438
+ 'Why this needs a ledger entry despite nothing in-repo referencing it: it is the '
3539
+ '`export-field-meta-constraints-retired` / `hook-context-session-roles-retired` '
3640
+ 'disposition — a PUBLISHED TypeScript surface with no spec schema, so there is no '
@@ -39,7 +43,7 @@ export const entry: SemanticMigration = {
3943
+ 'Why D3 semantic and not a D2 conversion: nothing authored or stored changes shape. '
4044
+ 'The name is only ever spelled inside a consumer\'s own TypeScript, so no '
4145
+ '`objectstack migrate meta` transform can reach it, and no `sys_metadata` row '
42-
+ 'carries it. ADR-0049 / ADR-0087, #7218.',
46+
+ 'carries it. ADR-0049 / ADR-0087.',
4347
acceptanceCriteria:
4448
'No source of yours imports `SharingExecutionContext` from `@objectstack/spec` or '
4549
+ '`@objectstack/plugin-sharing`; each such import becomes `ExecutionContext` from '

‎packages/spec/src/migrations/entries/semantic/17.sharing-rule-recipient-reconcile.ts‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,11 @@ export const entry: SemanticMigration = {
3333
+ 'runtime-reserved and deliberately non-authorable (there is no `sys_queue` yet). Note '
3434
+ 'the two neighbouring conversions cover DIFFERENT faces of this schema and not this '
3535
+ 'one: `sharing-recipient-role-to-position` is the ADR-0090 role → position rename and '
36-
+ '`sharing-rule-access-level-full-to-edit` is the access-level vocabulary. Registered by '
37-
+ 'the #6350 stock reconciliation. ADR-0078 / ADR-0090 D3 / ADR-0087, #1878 (backfilled '
38-
+ '#6350).',
36+
+ '`sharing-rule-access-level-full-to-edit` is the access-level vocabulary. The change came '
37+
+ 'out of the metadata property liveness audit, which found security properties parsed but '
38+
+ 'never enforced, and was registered late, by the stock reconciliation that compared the '
39+
+ 'breaking changesets already on the v17 release train against this ledger. ADR-0078 / '
40+
+ 'ADR-0090 D3 / ADR-0087.',
3941
acceptanceCriteria:
4042
'No sharing rule names `group` or `guest`, and none carries `type: owner`; stale '
4143
+ 'definitions now FAIL parse with the valid options listed, so the sweep is "fix until '

0 commit comments

Comments
 (0)