@substrat-run/kernel 0.127.0 → 0.128.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"scope-repoint.d.ts","sourceRoot":"","sources":["../src/scope-repoint.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAEpD,0FAA0F;AAC1F,MAAM,WAAW,aAAa;IAC5B,4CAA4C;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,aAAa,GAAG,IAAI,CA2BpG"}
1
+ {"version":3,"file":"scope-repoint.d.ts","sourceRoot":"","sources":["../src/scope-repoint.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAEpD,0FAA0F;AAC1F,MAAM,WAAW,aAAa;IAC5B,4CAA4C;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgEG;AACH,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,SAAS,EACd,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,aAAa,GAAG,SAAS;AACjC,sFAAsF;AACtF,GAAG,EAAE,MAAM,GACV,IAAI,CA0BN"}
@@ -1,4 +1,5 @@
1
1
  import { scopeId, substratError } from '@substrat-run/contracts';
2
+ import { liveTupleSql } from './permission-eval.js';
2
3
  /**
3
4
  * Re-point a dump's scope-level grants at the scope it lands in, the step every restore, fork
4
5
  * and preview carry ends with (#1869). One definition for both adapters' `importDump`, so the
@@ -33,19 +34,48 @@ import { scopeId, substratError } from '@substrat-run/contracts';
33
34
  * `WHERE`: the update rewrites the very rows the probe looks for, and the answer must not
34
35
  * change part-way through.
35
36
  *
36
- * `COLLATE BINARY` is written out because the tuples table's DDL comes from the dump, and a
37
- * column declared `COLLATE NOCASE` there would make a bare `=` fold case again.
37
+ * **Which row wins a collision (#1882).** `(subject, relation, object)` is the primary key,
38
+ * so a moved row can land on a row the dump already holds for the destination. That used to
39
+ * be `UPDATE OR REPLACE`, so the moved row always won, `expires_at` and `revoked_at` with it:
40
+ * a revoked or expired source row could replace a live grant on the destination. Now each
41
+ * row is ranked, and the higher rank is kept:
42
+ *
43
+ * 1. **live**: not revoked, and not expired at `now`;
44
+ * 2. **revoked**: a K-21 tombstone, the evidence of a revocation (#1659's reconcile never
45
+ * re-seats a grant it finds tombstoned, so losing one would un-revoke it);
46
+ * 3. **expired**: not revoked, but past its `expires_at`, which is evidence of nothing.
47
+ *
48
+ * | moved row \ destination row | live | revoked | expired |
49
+ * |-----------------------------|-------------|-------------|-------------|
50
+ * | live | destination | moved | moved |
51
+ * | revoked | destination | destination | moved |
52
+ * | expired | destination | destination | destination |
53
+ *
54
+ * On a tie the destination row stays. Two moved rows that would land on the same key (only
55
+ * the fallback moves more than one object) are settled by the same rank, and on a tie by the
56
+ * lower `object`, before either meets the destination. Whichever live row is kept takes the
57
+ * EARLIEST expiry among the live rows for its key (no expiry counting as the latest), so a
58
+ * restore never widens a grant's life, whichever side it came from. The loser is deleted: it cannot stay under its old object, because a row naming another scope
59
+ * is exactly what a later restore of this scope refuses. Everything runs inside the load's
60
+ * transaction, and the final `UPDATE` is a plain one, so a collision this missed fails the
61
+ * restore instead of replacing a row.
62
+ *
63
+ * `COLLATE BINARY` is written out on every comparison. Since #1883 the tuples table is always
64
+ * built from the kernel's DDL, never the dump's, so its columns are BINARY already; the
65
+ * collation stays as a second guard, so this function never depends on the table it is
66
+ * handed having the right one.
38
67
  */
39
- export function repointScopeGrants(sql, destScopeId, source) {
68
+ export function repointScopeGrants(sql, destScopeId, source,
69
+ /** The instant `expires_at` is judged against, as the adapter's checker judges it. */
70
+ now) {
40
71
  // `exact` is a claim about a named source; with none named it would silently take the fallback.
41
72
  if (source !== undefined && !source.scopeId) {
42
73
  throw substratError('validation_failed', 'restore refused: `exact` needs the scope the dump came from');
43
74
  }
44
75
  const dest = `scope:${destScopeId}`;
45
- const update = 'UPDATE OR REPLACE _substrat_tuples SET object = ? WHERE object <> ? COLLATE BINARY AND';
46
76
  const from = source === undefined ? undefined : `scope:${source.scopeId}`;
47
77
  if (from === undefined || (!source?.exact && sql.all(SOURCE_PROBE, from).length === 0)) {
48
- sql.run(`${update} substr(object, 1, 6) = 'scope:' COLLATE BINARY`, dest, dest);
78
+ moveOnto(sql, dest, now, undefined);
49
79
  return;
50
80
  }
51
81
  const strays = source.exact
@@ -59,7 +89,50 @@ export function repointScopeGrants(sql, destScopeId, source) {
59
89
  `(${source.scopeId}) and this scope (${destScopeId}), which a restore cannot re-point: ` +
60
90
  `${strays.slice(0, 3).join(', ')}${strays.length > 3 ? ', …' : ''}`);
61
91
  }
62
- sql.run(`${update} object = ? COLLATE BINARY`, dest, dest, from);
92
+ moveOnto(sql, dest, now, from);
93
+ }
94
+ /**
95
+ * Move onto `dest` every row naming `from` (the exact rule), or every `scope:` row when `from`
96
+ * is undefined (the fallback), settling each key collision by the rule in
97
+ * `repointScopeGrants`'s header. A row already on `dest` is never moved.
98
+ */
99
+ function moveOnto(sql, dest, now, from) {
100
+ // Whether the row under `alias` moves. Binds `moved` (below), in that order.
101
+ const isMoved = (alias) => `(${from === undefined ? `substr(${alias}.object, 1, 6) = 'scope:'` : `${alias}.object = ?`} COLLATE BINARY ` +
102
+ `AND ${alias}.object <> ? COLLATE BINARY)`;
103
+ const moved = from === undefined ? [dest] : [from, dest];
104
+ // The rank in the header: 2 live (the checker's own definition, so a restore keeps the row a
105
+ // check would honour), 1 revoked, 0 expired. Binds `now`.
106
+ const rank = (alias) => `(CASE WHEN ${liveTupleSql(alias)} THEN 2 WHEN ${alias}.revoked_at IS NOT NULL THEN 1 ELSE 0 END)`;
107
+ // A row competing for `dest`: moved there, or already on it. Binds `moved`, then `dest`.
108
+ const competes = (alias) => `(${isMoved(alias)} OR ${alias}.object = ? COLLATE BINARY)`;
109
+ const competing = [...moved, dest];
110
+ const live = (alias) => `(${liveTupleSql(alias)})`;
111
+ const sameKey = (a, b) => `${a}.subject = ${b}.subject COLLATE BINARY AND ${a}.relation = ${b}.relation COLLATE BINARY`;
112
+ // `_substrat_tuples` is the row being judged; SQLite takes no alias on a DELETE's target.
113
+ const T = '_substrat_tuples';
114
+ // 0. Every live row that meets another live one, carrying an earlier expiry, for its key
115
+ // takes the earliest, so the row kept below never outlives any of them. MIN skips NULL,
116
+ // which is "no expiry", the latest.
117
+ sql.run(`UPDATE ${T} SET expires_at = (
118
+ SELECT MIN(o.expires_at) FROM ${T} o WHERE ${sameKey('o', T)} AND ${competes('o')} AND ${live('o')})
119
+ WHERE ${live(T)} AND ${competes(T)} AND EXISTS (
120
+ SELECT 1 FROM ${T} o WHERE ${sameKey('o', T)} AND o.object <> ${T}.object COLLATE BINARY
121
+ AND ${competes('o')} AND ${live('o')} AND o.expires_at IS NOT NULL
122
+ AND (${T}.expires_at IS NULL OR o.expires_at < ${T}.expires_at))`, ...competing, now, now, ...competing, ...competing, now);
123
+ // 1. Between two moved rows for one key: the higher rank, then the lower object.
124
+ sql.run(`DELETE FROM ${T} WHERE ${isMoved(T)} AND EXISTS (
125
+ SELECT 1 FROM ${T} o WHERE ${sameKey('o', T)} AND ${isMoved('o')} AND o.object <> ${T}.object COLLATE BINARY
126
+ AND (${rank('o')} > ${rank(T)} OR (${rank('o')} = ${rank(T)} AND o.object < ${T}.object COLLATE BINARY)))`, ...moved, ...moved, now, now, now, now);
127
+ // 2. A moved row loses to the destination row unless it ranks higher.
128
+ sql.run(`DELETE FROM ${T} WHERE ${isMoved(T)} AND EXISTS (
129
+ SELECT 1 FROM ${T} d WHERE ${sameKey('d', T)} AND d.object = ? COLLATE BINARY
130
+ AND ${rank('d')} >= ${rank(T)})`, ...moved, dest, now, now);
131
+ // 3. Every moved row still standing beat the destination row for its key, which goes.
132
+ sql.run(`DELETE FROM ${T} WHERE object = ? COLLATE BINARY AND EXISTS (
133
+ SELECT 1 FROM ${T} o WHERE ${sameKey('o', T)} AND ${isMoved('o')})`, dest, ...moved);
134
+ // 4. No key collides now. A plain UPDATE, so one that still did would fail the restore.
135
+ sql.run(`UPDATE ${T} SET object = ? WHERE ${isMoved(T)}`, dest, ...moved);
63
136
  }
64
137
  const SOURCE_PROBE = 'SELECT 1 FROM _substrat_tuples WHERE object = ? COLLATE BINARY LIMIT 1';
65
138
  // Live rows only: a revoked grant (K-21 tombstone) on a third scope authorizes nothing anywhere.
@@ -1 +1 @@
1
- {"version":3,"file":"scope-repoint.js","sourceRoot":"","sources":["../src/scope-repoint.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAejE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAAc,EAAE,WAAmB,EAAE,MAAsB;IAC5F,gGAAgG;IAChG,IAAI,MAAM,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QAC5C,MAAM,aAAa,CAAC,mBAAmB,EAAE,6DAA6D,CAAC,CAAC;IAC1G,CAAC;IACD,MAAM,IAAI,GAAG,SAAS,WAAW,EAAE,CAAC;IACpC,MAAM,MAAM,GAAG,wFAAwF,CAAC;IACxG,MAAM,IAAI,GAAG,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,MAAM,CAAC,OAAO,EAAE,CAAC;IAC1E,IAAI,IAAI,KAAK,SAAS,IAAI,CAAC,CAAC,MAAM,EAAE,KAAK,IAAI,GAAG,CAAC,GAAG,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,EAAE,CAAC;QACvF,GAAG,CAAC,GAAG,CAAC,GAAG,MAAM,iDAAiD,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;QAChF,OAAO;IACT,CAAC;IACD,MAAM,MAAM,GAAG,MAAO,CAAC,KAAK;QAC1B,CAAC,CAAC,EAAE;QACJ,CAAC,CAAC,GAAG;aACA,GAAG,CAAC,YAAY,EAAE,IAAI,EAAE,IAAI,CAAC;aAC7B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;aAC5B,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IAC1E,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,MAAM,aAAa,CACjB,mBAAmB,EACnB,6CAA6C,MAAM,CAAC,MAAM,kCAAkC;YAC1F,IAAI,MAAO,CAAC,OAAO,qBAAqB,WAAW,sCAAsC;YACzF,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CACtE,CAAC;IACJ,CAAC;IACD,GAAG,CAAC,GAAG,CAAC,GAAG,MAAM,4BAA4B,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;AACnE,CAAC;AAED,MAAM,YAAY,GAAG,wEAAwE,CAAC;AAC9F,iGAAiG;AACjG,MAAM,YAAY,GAAG;;;kBAGH,CAAC"}
1
+ {"version":3,"file":"scope-repoint.js","sourceRoot":"","sources":["../src/scope-repoint.ts"],"names":[],"mappings":"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"}
@@ -64,9 +64,13 @@ export declare function seatScopeTuple(subject: string, relation: string, object
64
64
  * So: a non-revoked, unexpired role tuple JOINED to a non-revoked definition of the same
65
65
  * key for this tenant. Expiry is compared as ISO text, as every tuple walk here does.
66
66
  * Returns one row, `effective` 0 or 1.
67
+ *
68
+ * `subject` narrows the same question to one holder (#1665): does THIS principal hold a role
69
+ * the scope can expand? An owner hand-over asks it of the successor, so the scope is not
70
+ * handed to a principal whose member role was taken back.
67
71
  */
68
- export declare function effectiveRoleGrantQuery(tenantId: string, now: string): {
72
+ export declare function effectiveRoleGrantQuery(tenantId: string, now: string, subject?: string): {
69
73
  sql: string;
70
- params: [string, string, string, string];
74
+ params: string[];
71
75
  };
72
76
  //# sourceMappingURL=scope-tuple-seat.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"scope-tuple-seat.d.ts","sourceRoot":"","sources":["../src/scope-tuple-seat.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,GAAG,IAAI,GACvB;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,CAAC,CAAA;CAAE,CAQ1E;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,GACV;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CAkB3D"}
1
+ {"version":3,"file":"scope-tuple-seat.d.ts","sourceRoot":"","sources":["../src/scope-tuple-seat.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,GAAG,IAAI,GACvB;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,CAAC,CAAA;CAAE,CAQ1E;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,MAAM,GACf;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAoBnC"}
@@ -70,24 +70,30 @@ export function seatScopeTuple(subject, relation, object, expiresAt) {
70
70
  * So: a non-revoked, unexpired role tuple JOINED to a non-revoked definition of the same
71
71
  * key for this tenant. Expiry is compared as ISO text, as every tuple walk here does.
72
72
  * Returns one row, `effective` 0 or 1.
73
+ *
74
+ * `subject` narrows the same question to one holder (#1665): does THIS principal hold a role
75
+ * the scope can expand? An owner hand-over asks it of the successor, so the scope is not
76
+ * handed to a principal whose member role was taken back.
73
77
  */
74
- export function effectiveRoleGrantQuery(tenantId, now) {
78
+ export function effectiveRoleGrantQuery(tenantId, now, subject) {
79
+ const only = subject === undefined ? '' : ' AND subject = ?';
80
+ const who = subject === undefined ? [] : [subject];
75
81
  return {
76
82
  sql: `SELECT (
77
83
  EXISTS (
78
84
  SELECT 1 FROM _substrat_tuples t
79
85
  JOIN _substrat_roles r
80
86
  ON r.tenant_id = ? AND r.revoked_at IS NULL AND t.relation = 'role:' || r.role_key
81
- WHERE t.revoked_at IS NULL AND (t.expires_at IS NULL OR t.expires_at > ?)
87
+ WHERE t.revoked_at IS NULL AND (t.expires_at IS NULL OR t.expires_at > ?)${only.replace('subject', 't.subject')}
82
88
  )
83
89
  OR EXISTS (
84
90
  SELECT 1 FROM _substrat_tenant_tuples tt
85
91
  JOIN _substrat_roles r
86
92
  ON r.tenant_id = tt.tenant_id AND r.revoked_at IS NULL AND tt.relation = 'role:' || r.role_key
87
- WHERE tt.tenant_id = ? AND tt.revoked_at IS NULL AND (tt.expires_at IS NULL OR tt.expires_at > ?)
93
+ WHERE tt.tenant_id = ? AND tt.revoked_at IS NULL AND (tt.expires_at IS NULL OR tt.expires_at > ?)${only.replace('subject', 'tt.subject')}
88
94
  )
89
95
  ) AS effective`,
90
- params: [tenantId, now, tenantId, now],
96
+ params: [tenantId, now, ...who, tenantId, now, ...who],
91
97
  };
92
98
  }
93
99
  //# sourceMappingURL=scope-tuple-seat.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"scope-tuple-seat.js","sourceRoot":"","sources":["../src/scope-tuple-seat.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,MAAM,oBAAoB,CAAC;AAEjE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,MAAM,UAAU,cAAc,CAC5B,OAAe,EACf,QAAgB,EAChB,MAAc,EACd,SAAwB;IAExB,OAAO;QACL,GAAG,EAAE;yCACgC,2BAA2B;;+CAErB;QAC3C,MAAM,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC;KACxD,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,uBAAuB,CACrC,QAAgB,EAChB,GAAW;IAEX,OAAO;QACL,GAAG,EAAE;;;;;;;;;;;;;qBAaY;QACjB,MAAM,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,CAAC;KACvC,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"scope-tuple-seat.js","sourceRoot":"","sources":["../src/scope-tuple-seat.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,MAAM,oBAAoB,CAAC;AAEjE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,MAAM,UAAU,cAAc,CAC5B,OAAe,EACf,QAAgB,EAChB,MAAc,EACd,SAAwB;IAExB,OAAO;QACL,GAAG,EAAE;yCACgC,2BAA2B;;+CAErB;QAC3C,MAAM,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC;KACxD,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,uBAAuB,CACrC,QAAgB,EAChB,GAAW,EACX,OAAgB;IAEhB,MAAM,IAAI,GAAG,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,kBAAkB,CAAC;IAC7D,MAAM,GAAG,GAAG,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IACnD,OAAO;QACL,GAAG,EAAE;;;;;sFAK6E,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,WAAW,CAAC;;;;;;8GAMZ,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,YAAY,CAAC;;qBAE9H;QACjB,MAAM,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE,GAAG,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,GAAG,GAAG,CAAC;KACvD,CAAC;AACJ,CAAC"}
@@ -1,4 +1,10 @@
1
1
  import type { ScopedSql } from './scope-host.js';
2
+ /**
3
+ * The platform spine's table prefix — the same one `isSystemTable` groups on, and the one a
4
+ * restore builds from the kernel's DDL (`isSpineTable`, #1883). Compared lowercased: SQLite
5
+ * matches table names without regard to case, so `_SUBSTRAT_TUPLES` is the tuples table.
6
+ */
7
+ export declare const SPINE_PREFIX = "_substrat";
2
8
  /**
3
9
  * Refuse a statement whose write target is a `_substrat_*` table.
4
10
  *
@@ -1 +1 @@
1
- {"version":3,"file":"spine-guard.d.ts","sourceRoot":"","sources":["../src/spine-guard.ts"],"names":[],"mappings":"AAqCA,OAAO,KAAK,EAAE,SAAS,EAAY,MAAM,iBAAiB,CAAC;AA8K3D;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAqBpD;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,GAAG,SAAS,CAWtD"}
1
+ {"version":3,"file":"spine-guard.d.ts","sourceRoot":"","sources":["../src/spine-guard.ts"],"names":[],"mappings":"AAqCA,OAAO,KAAK,EAAE,SAAS,EAAY,MAAM,iBAAiB,CAAC;AAE3D;;;;GAIG;AACH,eAAO,MAAM,YAAY,cAAc,CAAC;AA2KxC;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAqBpD;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,GAAG,SAAS,CAWtD"}
@@ -35,8 +35,12 @@
35
35
  * them, so a forge chained after a legitimate write must not slip past.
36
36
  */
37
37
  import { substratError } from '@substrat-run/contracts';
38
- /** The platform spine's table prefix — the same one `isSystemTable` groups on. */
39
- const SPINE_PREFIX = '_substrat';
38
+ /**
39
+ * The platform spine's table prefix — the same one `isSystemTable` groups on, and the one a
40
+ * restore builds from the kernel's DDL (`isSpineTable`, #1883). Compared lowercased: SQLite
41
+ * matches table names without regard to case, so `_SUBSTRAT_TUPLES` is the tuples table.
42
+ */
43
+ export const SPINE_PREFIX = '_substrat';
40
44
  /**
41
45
  * Tokens that may stand between a write verb and the table it names. The first
42
46
  * following token that is NOT one of these (or that is quoted) is the target.
@@ -1 +1 @@
1
- {"version":3,"file":"spine-guard.js","sourceRoot":"","sources":["../src/spine-guard.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"spine-guard.js","sourceRoot":"","sources":["../src/spine-guard.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * How a restore, fork or carry loads a dump's `_substrat_*` spine tables (#1883). One
3
+ * definition for both adapters' loaders, so the DO and the pure host build the same tables
4
+ * from the same dump.
5
+ *
6
+ * A dump used to be replayed table by table, `CREATE TABLE` from the dump and then its rows,
7
+ * spine included; the kernel's `CREATE TABLE IF NOT EXISTS` afterwards filled only the gaps.
8
+ * So a dump could decide the schema of the tables the permission checker reads. A dump that
9
+ * declared `_substrat_tuples.object` as `COLLATE NOCASE` made every `=` the checker and the
10
+ * spine SQL wrote on it ignore case: a grant on `aiTurn:x` answered for `aiturn:x`.
11
+ *
12
+ * Now the loader builds every spine table from its own kernel DDL, and the dump contributes
13
+ * rows only, inserted by column name:
14
+ * - a column the kernel's table does not have (a dump from a newer kernel) is added as a plain
15
+ * untyped column, unless it is named for SQLite's rowid (refused, since it would shadow it), with no type, collation, constraint or default, and keeps its values
16
+ * (`spineColumnAdditions`). It is not one the checker reads, and it carries nothing that
17
+ * could change how the columns the checker does read compare. When a later kernel adds the
18
+ * column for real, its additive ALTER meets it and tolerates it as a duplicate;
19
+ * - a column the dump lacks takes the kernel's default (NULL for every additive column), so
20
+ * a dump taken before a column existed still restores. The one column that is not additive,
21
+ * `_substrat_schedule_state.kind` (#1288, part of the key), is derived from the row the way
22
+ * the #1288 rebuild derives it;
23
+ * - a spine table the kernel does not build is refused (`assertSpineTablesBuilt`), every such
24
+ * table named at once.
25
+ *
26
+ * Tables outside the spine keep the dump's own DDL: a vertical's schema is the dump's to say.
27
+ */
28
+ /**
29
+ * A kernel-owned table, which a restore builds from the kernel's DDL rather than the dump's.
30
+ * The prefix is the spine guard's, compared the way SQLite compares table names, without case:
31
+ * a dump's `_Substrat_tuples` IS the tuples table, and read as a vertical one its DDL would be
32
+ * replayed and the kernel's `IF NOT EXISTS` would skip its own.
33
+ */
34
+ export declare function isSpineTable(name: string): boolean;
35
+ /**
36
+ * Refuse a dump carrying `_substrat_*` tables this host's kernel does not build, naming every
37
+ * one of them. Called before any spine row goes in, inside the load's transaction; `columnsOf`
38
+ * is the same lookup `dumpRowsInsert` takes, `undefined` for a table the kernel did not build.
39
+ *
40
+ * The usual cause is a dump from the other kind of host: a Durable Object's scope builds spine
41
+ * tables (`_substrat_roles`, `_substrat_tenant_tuples`, …) that a node scope keeps in its
42
+ * directory instead. Loading one anyway would mean dropping its rows, which a restore does not
43
+ * do silently.
44
+ */
45
+ export declare function assertSpineTablesBuilt(names: readonly string[], columnsOf: KernelColumnsOf): void;
46
+ /** A table's columns as the kernel built it here, or `undefined` when it built no such table. */
47
+ export type KernelColumnsOf = (name: string) => readonly string[] | undefined;
48
+ /**
49
+ * The `INSERT` that loads one dumped table's rows, one row of positional parameters per
50
+ * execution, in the dump's column order: `spineRowsInsert` for a spine table, and the dump's
51
+ * own columns for a vertical one. The names are quoted as given: the loader has already passed
52
+ * the dump through `assertReplayableDump`.
53
+ */
54
+ export declare function dumpRowsInsert(table: {
55
+ name: string;
56
+ columns: readonly string[];
57
+ }, columnsOf: KernelColumnsOf): string;
58
+ /**
59
+ * The statements that add to a kernel-built spine table each column the dump carries and the
60
+ * kernel does not, as a plain untyped column: `ADD COLUMN "<name>"`, lowercased, and nothing after it, so it
61
+ * has no type, collation, constraint or default. Run after `assertSpineTablesBuilt` and before
62
+ * the table's rows go in, inside the load's transaction. The names are the dump's, which
63
+ * `assertReplayableDump` has already held to the identifier rule. Empty for a table the kernel
64
+ * did not build, which `assertSpineTablesBuilt` has refused.
65
+ */
66
+ export declare function spineColumnAdditions(table: {
67
+ name: string;
68
+ columns: readonly string[];
69
+ }, kernelColumns: readonly string[] | undefined): string[];
70
+ /**
71
+ * The `INSERT` that loads one dumped spine table's rows into the table the kernel built.
72
+ *
73
+ * `kernelColumns` is the kernel table's column list as the loader reads it back after
74
+ * `spineColumnAdditions`, or `undefined` when the kernel built no such table. Either refusal
75
+ * below is a backstop the loader's order makes unreachable, and throws `validation_failed`
76
+ * inside the load's transaction, so the target keeps what it held.
77
+ */
78
+ export declare function spineRowsInsert(table: {
79
+ name: string;
80
+ columns: readonly string[];
81
+ }, kernelColumns: readonly string[] | undefined): string;
82
+ //# sourceMappingURL=spine-restore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spine-restore.d.ts","sourceRoot":"","sources":["../src/spine-restore.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAIlD;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,SAAS,EAAE,eAAe,GAAG,IAAI,CAGjG;AAED,iGAAiG;AACjG,MAAM,MAAM,eAAe,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;AAmB9E;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,EAAE,SAAS,EAAE,eAAe,GAAG,MAAM,CAEtH;AASD;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,EACnD,aAAa,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,GAC3C,MAAM,EAAE,CAkBV;AAKD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,EACnD,aAAa,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,GAC3C,MAAM,CAyBR"}
@@ -0,0 +1,138 @@
1
+ import { substratError } from '@substrat-run/contracts';
2
+ import { SCHEDULE_STATE_KIND_OF_OP } from './platform-sweep.js';
3
+ import { isSearchIndexTable } from './search-index.js';
4
+ import { SPINE_PREFIX } from './spine-guard.js';
5
+ /**
6
+ * How a restore, fork or carry loads a dump's `_substrat_*` spine tables (#1883). One
7
+ * definition for both adapters' loaders, so the DO and the pure host build the same tables
8
+ * from the same dump.
9
+ *
10
+ * A dump used to be replayed table by table, `CREATE TABLE` from the dump and then its rows,
11
+ * spine included; the kernel's `CREATE TABLE IF NOT EXISTS` afterwards filled only the gaps.
12
+ * So a dump could decide the schema of the tables the permission checker reads. A dump that
13
+ * declared `_substrat_tuples.object` as `COLLATE NOCASE` made every `=` the checker and the
14
+ * spine SQL wrote on it ignore case: a grant on `aiTurn:x` answered for `aiturn:x`.
15
+ *
16
+ * Now the loader builds every spine table from its own kernel DDL, and the dump contributes
17
+ * rows only, inserted by column name:
18
+ * - a column the kernel's table does not have (a dump from a newer kernel) is added as a plain
19
+ * untyped column, unless it is named for SQLite's rowid (refused, since it would shadow it), with no type, collation, constraint or default, and keeps its values
20
+ * (`spineColumnAdditions`). It is not one the checker reads, and it carries nothing that
21
+ * could change how the columns the checker does read compare. When a later kernel adds the
22
+ * column for real, its additive ALTER meets it and tolerates it as a duplicate;
23
+ * - a column the dump lacks takes the kernel's default (NULL for every additive column), so
24
+ * a dump taken before a column existed still restores. The one column that is not additive,
25
+ * `_substrat_schedule_state.kind` (#1288, part of the key), is derived from the row the way
26
+ * the #1288 rebuild derives it;
27
+ * - a spine table the kernel does not build is refused (`assertSpineTablesBuilt`), every such
28
+ * table named at once.
29
+ *
30
+ * Tables outside the spine keep the dump's own DDL: a vertical's schema is the dump's to say.
31
+ */
32
+ /**
33
+ * A kernel-owned table, which a restore builds from the kernel's DDL rather than the dump's.
34
+ * The prefix is the spine guard's, compared the way SQLite compares table names, without case:
35
+ * a dump's `_Substrat_tuples` IS the tuples table, and read as a vertical one its DDL would be
36
+ * replayed and the kernel's `IF NOT EXISTS` would skip its own.
37
+ */
38
+ export function isSpineTable(name) {
39
+ const lower = name.toLowerCase();
40
+ // The search index is spine-prefixed too, but derived: a loader rebuilds it, never loads it.
41
+ return lower.startsWith(SPINE_PREFIX) && !isSearchIndexTable(lower);
42
+ }
43
+ /**
44
+ * Refuse a dump carrying `_substrat_*` tables this host's kernel does not build, naming every
45
+ * one of them. Called before any spine row goes in, inside the load's transaction; `columnsOf`
46
+ * is the same lookup `dumpRowsInsert` takes, `undefined` for a table the kernel did not build.
47
+ *
48
+ * The usual cause is a dump from the other kind of host: a Durable Object's scope builds spine
49
+ * tables (`_substrat_roles`, `_substrat_tenant_tuples`, …) that a node scope keeps in its
50
+ * directory instead. Loading one anyway would mean dropping its rows, which a restore does not
51
+ * do silently.
52
+ */
53
+ export function assertSpineTablesBuilt(names, columnsOf) {
54
+ const missing = names.filter((n) => isSpineTable(n) && columnsOf(n) === undefined);
55
+ if (missing.length > 0)
56
+ throw unbuiltSpineTables(missing);
57
+ }
58
+ const unbuiltSpineTables = (names) => substratError('validation_failed', `restore refused: the dump carries spine table(s) this host's kernel does not build: ${names.join(', ')}. ` +
59
+ 'It was exported by a different kind of host (a Durable Object scope holds spine tables a node scope ' +
60
+ 'keeps in its directory) or by a newer kernel, and its rows would have nowhere to go. Nothing was changed.');
61
+ /**
62
+ * Columns the kernel's table requires that a dump taken before them lacks, with the
63
+ * expression that derives each from the dumped row. Every other spine column is additive
64
+ * and defaults, so it needs no entry.
65
+ */
66
+ const DERIVED_COLUMNS = {
67
+ _substrat_schedule_state: { kind: SCHEDULE_STATE_KIND_OF_OP },
68
+ };
69
+ /**
70
+ * The `INSERT` that loads one dumped table's rows, one row of positional parameters per
71
+ * execution, in the dump's column order: `spineRowsInsert` for a spine table, and the dump's
72
+ * own columns for a vertical one. The names are quoted as given: the loader has already passed
73
+ * the dump through `assertReplayableDump`.
74
+ */
75
+ export function dumpRowsInsert(table, columnsOf) {
76
+ return isSpineTable(table.name) ? spineRowsInsert(table, columnsOf(table.name)) : plainInsert(table);
77
+ }
78
+ const plainInsert = (table) => `INSERT INTO "${table.name}" (${table.columns.map((c) => `"${c}"`).join(', ')}) ` +
79
+ `VALUES (${table.columns.map(() => '?').join(', ')})`;
80
+ /** Column names as SQLite resolves them, without case. */
81
+ const lowered = (columns) => new Set(columns.map((c) => c.toLowerCase()));
82
+ /**
83
+ * The statements that add to a kernel-built spine table each column the dump carries and the
84
+ * kernel does not, as a plain untyped column: `ADD COLUMN "<name>"`, lowercased, and nothing after it, so it
85
+ * has no type, collation, constraint or default. Run after `assertSpineTablesBuilt` and before
86
+ * the table's rows go in, inside the load's transaction. The names are the dump's, which
87
+ * `assertReplayableDump` has already held to the identifier rule. Empty for a table the kernel
88
+ * did not build, which `assertSpineTablesBuilt` has refused.
89
+ */
90
+ export function spineColumnAdditions(table, kernelColumns) {
91
+ if (kernelColumns === undefined)
92
+ return [];
93
+ const known = lowered(kernelColumns);
94
+ const unknown = table.columns.filter((c) => !known.has(c.toLowerCase()));
95
+ // A real column named for the rowid shadows SQLite's alias. On the outbox, `rowid` is the mark
96
+ // the #1705 and #1746 since-queries read (`OUTBOX_MARK_SQL`), so a dump's value would silence
97
+ // them from then on, and survive every later export.
98
+ const aliased = unknown.filter((c) => ROWID_ALIASES.has(c.toLowerCase()));
99
+ if (aliased.length > 0) {
100
+ throw substratError('validation_failed', `restore refused: the dump's ${table.name} has column(s) named for SQLite's rowid: ${aliased.join(', ')}. ` +
101
+ 'A real column by that name would shadow the rowid the kernel reads. Nothing was changed.');
102
+ }
103
+ // Lowercased, as every kernel column is spelled: a later kernel adding the column for real
104
+ // then meets the name it expects, whatever case the dump used.
105
+ return unknown.map((c) => `ALTER TABLE "${table.name}" ADD COLUMN "${c.toLowerCase()}"`);
106
+ }
107
+ /** The names SQLite resolves to a table's rowid, unless a real column takes one. */
108
+ const ROWID_ALIASES = new Set(['rowid', 'oid', '_rowid_']);
109
+ /**
110
+ * The `INSERT` that loads one dumped spine table's rows into the table the kernel built.
111
+ *
112
+ * `kernelColumns` is the kernel table's column list as the loader reads it back after
113
+ * `spineColumnAdditions`, or `undefined` when the kernel built no such table. Either refusal
114
+ * below is a backstop the loader's order makes unreachable, and throws `validation_failed`
115
+ * inside the load's transaction, so the target keeps what it held.
116
+ */
117
+ export function spineRowsInsert(table, kernelColumns) {
118
+ // `assertSpineTablesBuilt` has refused this already, naming every such table.
119
+ if (kernelColumns === undefined)
120
+ throw unbuiltSpineTables([table.name]);
121
+ const known = lowered(kernelColumns);
122
+ // `spineColumnAdditions` has added every such column already.
123
+ const unknown = table.columns.filter((c) => !known.has(c.toLowerCase()));
124
+ if (unknown.length > 0) {
125
+ throw substratError('validation_failed', `restore refused: the dump's ${table.name} has column(s) that were not added to this host's table: ` +
126
+ `${unknown.join(', ')}. Nothing was changed.`);
127
+ }
128
+ const dumped = lowered(table.columns);
129
+ const derived = Object.entries(DERIVED_COLUMNS[table.name.toLowerCase()] ?? {}).filter(([c]) => known.has(c) && !dumped.has(c));
130
+ if (derived.length === 0)
131
+ return plainInsert(table);
132
+ const quoted = table.columns.map((c) => `"${c}"`);
133
+ // The derivation reads the dumped row by name, so the row is a one-row SELECT to read from.
134
+ const row = table.columns.map((c, i) => `? AS ${quoted[i]}`).join(', ');
135
+ return (`INSERT INTO "${table.name}" (${[...quoted, ...derived.map(([c]) => `"${c}"`)].join(', ')}) ` +
136
+ `SELECT ${[...quoted, ...derived.map(([, expr]) => expr)].join(', ')} FROM (SELECT ${row})`);
137
+ }
138
+ //# sourceMappingURL=spine-restore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spine-restore.js","sourceRoot":"","sources":["../src/spine-restore.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@substrat-run/kernel",
3
- "version": "0.127.0",
3
+ "version": "0.128.0",
4
4
  "description": "Substrat kernel contracts and services — pure TypeScript, no platform imports (D-14)",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {
@@ -22,7 +22,7 @@
22
22
  "dist"
23
23
  ],
24
24
  "dependencies": {
25
- "@substrat-run/contracts": "^0.127.0"
25
+ "@substrat-run/contracts": "^0.128.0"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@types/node": "^22.20.4",