Skip to content

Commit 32da13c

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-20492-uninstall-refuse-before-mutate
2 parents eca5d38 + 9e9bb46 commit 32da13c

39 files changed

Lines changed: 527 additions & 344 deletions

File tree

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
fix(spec): `os migrate meta` guidance for the `datasource-*`, `filter-*`, `action-*`, `data-*` and `element-*` migration entries states each lesson in words instead of citing tracker numbers
6+
7+
Clause-②: no
8+
9+
The ADR-0087 semantic entries of the `datasource-*` family (the publish-time credential,
10+
placeholder and URL refusals, and the bound-secret pairs a mongo datasource cannot use),
11+
the `filter-*` family (the retired `$regex`, the `$between` endpoint refusals and the
12+
comparand shapes the save door now refuses), the `action-*` family (the retired
13+
descriptor key, the `resumeAuthority` default flip, the action-session rename, the bulk
14+
dispatch contract and the engine facade's query envelope), the `data-*` family (the
15+
retired driver and engine contract members, the retired field-changed event and two
16+
duration keys renamed with their unit) and the `element-*` family (the filter rule array
17+
at the page binding and the element and block doors) are printed by `os migrate meta` as
18+
the header, `why:` and `verify:` lines of a manual change. Their text sent the reader to
19+
issue-tracker, decision-batch and ruling-record numbers — some of which no longer
20+
resolve, and some in other repositories — for what a ruling, measurement or fix had
21+
decided; it now says what was decided, in the sentence being read. ADR ids are kept.
22+
23+
Text only: no entry id, `surface`, `from` / `to`, conversion or matching logic changes,
24+
and the chain rewrites exactly what it rewrote before. The generated migration registry,
25+
`spec-changes.json` and the protocol upgrade guide carry the same text.

‎docs/protocol-upgrade-guide.md‎

Lines changed: 12 additions & 12 deletions
Large diffs are not rendered by default.

‎packages/cli/test/migrate-meta-engine-guidance.test.ts‎

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
/**
44
* `os migrate meta` — the guidance it prints for the ADR-0087 semantic entries
55
* of the COVERED families (`engine-*`, `ui-*`, `plugin-*`, `driver-*`,
6-
* `kernel-*`, `system-*`) states each lesson in words and carries no tracker
7-
* number.
6+
* `kernel-*`, `system-*`, `datasource-*`, `filter-*`, `action-*`, `data-*`,
7+
* `element-*`) states each lesson in words and carries no tracker number.
88
*
99
* ## What this pins
1010
*
@@ -73,26 +73,62 @@ const TSX = resolve(HERE, '../../../node_modules/.bin/tsx');
7373
const TRACKER_ID = /#\d{4,5}\b/;
7474

7575
/** The families this pin holds, selected by entry-id prefix. */
76-
const COVERED_PREFIXES = ['engine-', 'ui-', 'plugin-', 'driver-', 'kernel-', 'system-'];
76+
const COVERED_PREFIXES = [
77+
'engine-', 'ui-', 'plugin-', 'driver-', 'kernel-', 'system-',
78+
'datasource-', 'filter-', 'action-', 'data-', 'element-',
79+
];
7780

7881
/**
7982
* The entries rewritten when each family was brought to this line — the
8083
* anti-vacuity floor. A covered entry that carried no tracker id to begin with
8184
* is held by its prefix and needs no row here.
8285
*/
8386
const REWRITTEN = [
87+
'action-bulk-dispatch-contract-undeclared',
88+
'action-descriptor-is-async-retired',
89+
'action-descriptor-resume-authority-default-flip',
90+
'action-engine-facade-find-query-envelope',
91+
'action-session-roles-to-positions',
92+
'data-driver-find-stream-retired',
93+
'data-driver-query-omit-object',
94+
'data-engine-batch-retired',
95+
'data-field-changed-event-retired',
96+
'data-file-value-duration-unit-in-key',
97+
'data-nosql-query-options-timeout-unit-in-key',
98+
'datasource-config-inline-credential-refused',
99+
'datasource-config-mongo-options-credential-refused',
100+
'datasource-config-placeholder-refused',
101+
'datasource-config-postgres-url-unparseable-refused',
102+
'datasource-config-url-query-credential-refused',
103+
'datasource-config-url-userinfo-refused',
104+
'datasource-credentialsref-mongo-composed-no-username-refused',
105+
'datasource-credentialsref-mongo-url-no-user-refused',
84106
'driver-aggregate-undeclared-key-aliases-removed',
85107
'driver-capabilities-inert-bits-removed',
86108
'driver-options-timeout-to-timeout-ms',
87109
'driver-sql-distinct-bare-filter-typed',
88110
'driver-sql-unresolvable-where-column-refused',
89111
'driver-sql-upsert-cross-row-identity-merge-refused',
90112
'driver-turso-config-local-path-wasm-retired',
113+
'element-data-source-and-object-block-filter-rule-array',
114+
'element-number-filter-rule-array',
115+
'element-record-picker-filter-rule-array',
91116
'engine-dotted-filter-refused',
92117
'engine-dotted-projection-refused',
93118
'engine-find-formula-filter-refused',
94119
'engine-find-formula-order-by-refused',
95120
'engine-update-upsert-retired',
121+
'filter-between-blank-endpoint-refused',
122+
'filter-between-field-reference-endpoint-refused',
123+
'filter-comparand-types-and-widget-nested-slots-refused-at-save',
124+
'filter-equality-array-comparand-refused',
125+
'filter-equality-array-comparand-refused-at-save',
126+
'filter-icontains-comparand-refused-at-parse',
127+
'filter-ne-array-comparand-refused',
128+
'filter-preset-ordering-comparand-refused',
129+
'filter-query-face-comparands-refused-at-save',
130+
'filter-regex-options-retired',
131+
'filter-text-operator-declared-type-refused',
96132
'kernel-compatibility-matrix-estimated-migration-time-unit-in-key',
97133
'kernel-context-preview-mode-retired',
98134
'kernel-event-bus-retention-unit-in-key',

‎packages/spec/spec-changes.json‎

Lines changed: 22 additions & 22 deletions
Large diffs are not rendered by default.

‎packages/spec/src/migrations/entries/semantic/17.action-descriptor-is-async-retired.ts‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,16 @@ export const entry: SemanticMigration = {
1212
reason:
1313
'ADR-0049 enforce-or-remove. `isAsync` declared "this action suspends the flow '
1414
+ 'awaiting an external reply" and NOTHING read it: a fresh three-repo measurement '
15-
+ '(#6748, re-run at pickup) found zero property reads across objectstack, objectui '
16-
+ 'and cloud — every hit was the declaration itself, a generated baseline, one of '
15+
+ '(taken when the key was filed for retirement, and re-run at pickup) found zero '
16+
+ 'property reads across objectstack, objectui and cloud — every hit was the declaration itself, a generated baseline, one of '
1717
+ 'five shipped descriptors WRITING it, a test fixture pinning the shape, or prose. '
1818
+ 'So declaring it never made a node suspend and omitting it never stopped one, '
1919
+ 'which is the silently-inert declaration ADR-0049 exists to end. It was always a '
2020
+ 'second, weaker spelling of the capability `supportsPause` states, and the two '
2121
+ 'diverged in exactly the way a duplicated declaration does: `screen` declared '
2222
+ 'both, `map` and `wait` declared `isAsync` alongside `supportsPause`, and nothing '
23-
+ 'anywhere reconciled them. The sibling took the ENFORCE leg of the same ruling in '
24-
+ '#6667 — `AutomationEngine` now refuses a suspension whose type does not declare '
23+
+ 'anywhere reconciled them. The sibling took the ENFORCE leg of the same ruling — '
24+
+ '`AutomationEngine` now refuses a suspension whose type does not declare '
2525
+ '`supportsPause: true` — so the capability this key gestured at is now a real, '
2626
+ 'enforced fact under one name. This one had no consumer to grow into and takes '
2727
+ 'the remove leg. '

‎packages/spec/src/migrations/entries/semantic/17.action-descriptor-resume-authority-default-flip.ts‎

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,13 @@ export const entry: SemanticMigration = {
2222
'A SECURE-DEFAULT FLIP with no metadata shape to rewrite — the same category as '
2323
+ "protocol 12's `rest-requireauth-default-flip`, and it is registered here for the "
2424
+ 'same reason: whether a given pause is genuinely open to the generic route is a '
25-
+ 'trust judgment no transform can make. The #3801 resume gate keys on the SUSPENDED '
25+
+ 'trust judgment no transform can make. The generic resume route\'s authorization gate '
26+
+ 'keys on the SUSPENDED '
2627
+ "NODE, and `ActionDescriptor.resumeAuthority` used to default to `'any'`, so a "
2728
+ 'pausing node type shipped raw-resumable unless its author remembered the field. '
2829
+ "It now resolves to `'service'` when absent: an unclaimed pause is refused on the "
2930
+ 'generic route with `PERMISSION_DENIED` / 403 until its descriptor states who may '
30-
+ 'continue it. #3823 is the incident that decided the direction — ADR-0044 pointed '
31+
+ 'continue it. The revise-window incident decided the direction — ADR-0044 pointed '
3132
+ "an approval's revise edge at a generic `wait`, `wait` is legitimately `'any'`, and "
3233
+ 'the pause standing in a service-owned position inherited a fail-open value nobody '
3334
+ 'chose; the demonstrated cost was an unaudited resubmit plus a destroyed remote '
@@ -36,8 +37,8 @@ export const entry: SemanticMigration = {
3637
+ "guessing `'service'` returns a refusal naming the missing field. ⚠️ The surface "
3738
+ 'is a DESCRIPTOR FIELD set in plugin CODE, never stack metadata, so there is no '
3839
+ 'source for a D2 conversion to rewrite and deliberately no schema tombstone — the '
39-
+ 'disposition `data-driver-find-stream-retired` (#4484), `storage-service-list-retired` '
40-
+ '(#5540) and `actor-user-roles-to-positions` (#6011) already carry. It differs from '
40+
+ 'disposition `data-driver-find-stream-retired`, `storage-service-list-retired` '
41+
+ 'and `actor-user-roles-to-positions` already carry. It differs from '
4142
+ 'those in one way a reader should not have to infer: nothing is REMOVED, so tsc '
4243
+ 'reports nothing at all — the field was already optional after step one and an '
4344
+ 'omission still compiles. The enforced channels are all run-time: a registration '
@@ -47,7 +48,8 @@ export const entry: SemanticMigration = {
4748
+ 'channel that arrives BEFORE a user hits a run that will not continue. In-tree the '
4849
+ 'flip moves nothing: all six shipped pausing types (screen, wait, subflow, map, '
4950
+ 'approval, approval_revise) declare their authority explicitly. ADR-0044 amendment '
50-
+ '(2026-07-28) and its 2026-08-08 landing section, ADR-0019 #3801 addendum, #5561.',
51+
+ '(2026-07-28) and its 2026-08-08 landing section, and the 2026-07-28 resume-seam '
52+
+ 'addendum to ADR-0019 (approval as a flow node).',
5153
acceptanceCriteria:
5254
'Every action descriptor your plugin registers for a node type that can suspend '
5355
+ 'declares `resumeAuthority`. Booting the stack logs no `declares supportsPause but '
@@ -56,12 +58,12 @@ export const entry: SemanticMigration = {
5658
+ "through the generic route succeeds for the ones you declared `'any'`, and answers "
5759
+ "403 (`PERMISSION_DENIED`) for the ones you declared `'service'`, which continue "
5860
+ 'through your own service API instead. ⚠️ `supportsPause` is no longer the '
59-
+ 'declaration nothing enforced (#5703, closed by #6667): an executor whose '
61+
+ 'declaration nothing enforced: an executor whose '
6062
+ '`execute()` returns `suspend: true` while leaving `supportsPause` false is still '
6163
+ 'warned about by neither warning channel, but '
6264
+ '`AutomationEngine.refuseUndeclaredSuspension` now refuses that suspension at the '
6365
+ 'one seam every suspension passes through — a guard-class failure no `fault` edge '
6466
+ 'routes — so it needs no hand-check. The residue that does: an executor registering '
6567
+ 'NO descriptor declares nothing for either warning or the refusal to read, so its '
66-
+ 'pauses are still created and refused only later, on the resume route (#5561).',
68+
+ 'pauses are still created and refused only later, on the resume route.',
6769
};

‎packages/spec/src/migrations/entries/semantic/17.action-session-roles-to-positions.ts‎

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,25 @@ export const entry: SemanticMigration = {
99
reason:
1010
'The MIRROR-IMAGE sibling of `actor-user-roles-to-positions`, and the reason both are in this '
1111
+ 'step: the hook `ctx.session` carried `roles` declared-and-never-produced (removed '
12-
+ 'outright, #5050), while the ACTION body\'s `ctx.session` carries it '
12+
+ 'outright), while the ACTION body\'s `ctx.session` carries it '
1313
+ 'produced-and-really-populated. `buildActionSession()` '
1414
+ '(`packages/runtime/src/action-execution.ts`) copies `ExecutionContext.positions` '
1515
+ 'into a key spelled `roles` — the ADR-0090 D3 vocabulary handed to the author under '
1616
+ 'the one spelling that ADR bans — so a body author met two different answers to one '
1717
+ 'key name on one platform: rejected in a hook, live and full of values in an action. '
18-
+ '#5613 ruled contract-first (maintainer, 2026-08-06: "C skeleton + A semantics"): '
19-
+ 'phase 1 (#5697) declared the previously undeclared shape as `ActionSessionSchema`, '
20-
+ 'and phase 2 renames the key. `positions` is now the canonical key on that schema '
21-
+ 'and `roles` a deprecated alias of it (#5779); the producer emits both for one '
22-
+ 'deprecation window (#5613 runtime half), after which `roles` is removed on the path '
23-
+ 'the v16 session-alias removal already walked (#3280 deprecated → #3290 removed). '
18+
+ 'The maintainer ruled contract-first on 2026-08-06 ("C skeleton + A semantics": declare '
19+
+ 'the shape as it stands first, then rename on the typed face): phase 1 declared the '
20+
+ 'previously undeclared shape as `ActionSessionSchema`, and phase 2 renames the key. '
21+
+ '`positions` is now the canonical key on that schema and `roles` a deprecated alias of '
22+
+ 'it; the producer emits both for one deprecation window (the runtime half of the same '
23+
+ 'ruling), after which `roles` is removed on the path the v16 session-alias removal '
24+
+ 'already walked (the hook session\'s `tenantId` alias: deprecated first, removed in the '
25+
+ 'next major). '
2426
+ 'Why this is a D3 semantic TODO and not a D2 conversion, on two independent grounds: '
2527
+ 'FIRST, there is no source to convert — an action `ctx.session` is constructed per '
2628
+ 'dispatch and never persisted, so no `sys_metadata` row, example or template can '
27-
+ 'carry the key — the `openApi31` (#4579) / `activationEvents` (#4657) / '
28-
+ '`hook-context-session-roles-retired` (#5050) shape. SECOND, the only place the key '
29+
+ 'carry the key — the `openApi31` / `activationEvents` / '
30+
+ '`hook-context-session-roles-retired` shape. SECOND, the only place the key '
2931
+ 'is ever SPELLED is inside an action body: author-written JS/TS, or a sandboxed '
3032
+ 'script whose `ScriptContext.session` is still `unknown`. A declarative transform '
3133
+ 'cannot safely rewrite an identifier inside free-form code — exactly the reason the '
@@ -38,7 +40,7 @@ export const entry: SemanticMigration = {
3840
+ 'authorable-surface ratchet adjudicates) belongs to the release that closes the '
3941
+ 'window. Until then this entry IS the channel: `spec-changes.json` and the generated '
4042
+ 'upgrade guide are how a reader learns the rename before the removal reaches them. '
41-
+ 'ADR-0090 D3, ADR-0087, #5613 / #5779.',
43+
+ 'ADR-0090 D3, ADR-0087.',
4244
acceptanceCriteria:
4345
'No action body reads `ctx.session.roles`; every such read is `ctx.session.positions` '
4446
+ 'and observes the same array (the rename is a rename — the VALUE is '

‎packages/spec/src/migrations/entries/semantic/17.data-driver-find-stream-retired.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export const entry: SemanticMigration = {
3030
+ 'no schema tombstone either: nothing ever ran a driver object through '
3131
+ '`DriverInterfaceSchema.parse()`, so a prescription there would have no one to '
3232
+ 'reach. The enforced channel is tsc, and it points at callers. ADR-0049 / '
33-
+ 'ADR-0078, #4484.',
33+
+ 'ADR-0078.',
3434
acceptanceCriteria:
3535
'No code calls `driver.findStream(...)`; large reads page through `find()` with '
3636
+ '`limit`/`offset` (which guarantees a total order across the whole walk) or go '

‎packages/spec/src/migrations/entries/semantic/17.data-driver-query-omit-object.ts‎

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@ export const entry: SemanticMigration = {
1919
+ 'layer spends a named 400 (`QUERY_OBJECT_MISMATCH`) refusing the inconsistency. The '
2020
+ 'driver side paid in blanket casts: a direct caller holding only a `where` could not '
2121
+ 'name the type, wrote `as any`, and switched off checking for `where` / `orderBy` / '
22-
+ '`fields` along with it — 20 such sites measured in cloud#1053, and cloud#1030\'s '
23-
+ '`$like` reached runtime through exactly that hole. This is a TS contract surface with '
22+
+ '`fields` along with it — 20 such sites were measured in the downstream cloud codebase, '
23+
+ 'and a `$like` the type layer would have caught reached runtime there through exactly '
24+
+ 'that hole. This is a TS contract surface with '
2425
+ 'no authored source for the chain to rewrite, which is why it is a semantic entry and '
2526
+ 'not a D2 conversion; for a typed caller the compiler names every site (TS2353 '
2627
+ '`\'object\' does not exist in type \'DriverQuery\'`), and for an untyped JS caller '
@@ -29,14 +30,15 @@ export const entry: SemanticMigration = {
2930
+ 'unchanged (excess properties are only rejected on fresh literals), and an '
3031
+ 'implementation still declaring `query: QueryAST` keeps compiling under parameter '
3132
+ 'bivariance. What an implementation may no longer do is READ `query.object` — callers '
32-
+ 'are now entitled to omit it. Registered by the #6350 stock reconciliation. #5181 was '
33-
+ 'the audit\'s CONTROL sample, drawn to show that not every flagged candidate is an '
33+
+ 'are now entitled to omit it. Registered by the stock reconciliation that compared the '
34+
+ 'breaking changesets already on the v17 release train against this ledger. This change '
35+
+ 'was that audit\'s CONTROL sample, drawn to show that not every flagged candidate is an '
3436
+ 'omission, and it was one: the seven `IDataDriver` hits in the ledger are all prose '
3537
+ 'inside other entries, and the only subject-level hit on this interface is '
3638
+ '`data-driver-find-stream-retired` — a DIFFERENT member. Two later, smaller driver '
37-
+ 'call-parameter changes (#6321, #6083) both registered, and both cite #5181 as '
38-
+ 'background; the larger sibling they derive from never got its own entry. ADR-0087, '
39-
+ '#5181 (backfilled #6350).',
39+
+ 'call-parameter changes both registered, and both cite this narrowing as '
40+
+ 'background; the larger sibling they derive from never got its own entry. ADR-0087 '
41+
+ '(backfilled by that reconciliation).',
4042
acceptanceCriteria:
4143
'No `IDataDriver` call site passes an inline literal carrying `object:` — `driver.find('
4244
+ '"account", { object: "account", where: … })` becomes `driver.find("account", { where: '

‎packages/spec/src/migrations/entries/semantic/17.data-engine-batch-retired.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export const entry: SemanticMigration = {
3636
+ '`DataEngineBatchRequestSchema`, so a `retiredKey()` prescription would have no one '
3737
+ 'to reach; its three `authorable-surface.json` baseline lines and its '
3838
+ '`json-schema.manifest.json` entry are dropped in the same change, deliberately. '
39-
+ 'The enforced channel is tsc. ADR-0049 / ADR-0078, #4618.',
39+
+ 'The enforced channel is tsc. ADR-0049 / ADR-0078.',
4040
acceptanceCriteria:
4141
'No code calls `engine.batch(...)` and no type references `DataEngineBatchRequest`; '
4242
+ 'in-process multi-write atomicity goes through `IObjectQLEngine.transaction(cb)`, a '

0 commit comments

Comments
 (0)