@substrat-run/kernel 0.120.0 → 0.121.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.
Files changed (39) hide show
  1. package/dist/index.d.ts +9 -5
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +7 -4
  4. package/dist/index.js.map +1 -1
  5. package/dist/list-index.d.ts.map +1 -1
  6. package/dist/list-index.js +5 -2
  7. package/dist/list-index.js.map +1 -1
  8. package/dist/module-migrations.d.ts +26 -0
  9. package/dist/module-migrations.d.ts.map +1 -0
  10. package/dist/module-migrations.js +21 -0
  11. package/dist/module-migrations.js.map +1 -0
  12. package/dist/platform-call.d.ts +20 -0
  13. package/dist/platform-call.d.ts.map +1 -1
  14. package/dist/platform-call.js +24 -0
  15. package/dist/platform-call.js.map +1 -1
  16. package/dist/platform-sweep.d.ts +157 -4
  17. package/dist/platform-sweep.d.ts.map +1 -1
  18. package/dist/platform-sweep.js +574 -45
  19. package/dist/platform-sweep.js.map +1 -1
  20. package/dist/scope-host.d.ts +105 -2
  21. package/dist/scope-host.d.ts.map +1 -1
  22. package/dist/scope-host.js.map +1 -1
  23. package/dist/sql-limits.d.ts +70 -0
  24. package/dist/sql-limits.d.ts.map +1 -0
  25. package/dist/sql-limits.js +197 -0
  26. package/dist/sql-limits.js.map +1 -0
  27. package/dist/system-switch-record.d.ts +158 -0
  28. package/dist/system-switch-record.d.ts.map +1 -0
  29. package/dist/system-switch-record.js +227 -0
  30. package/dist/system-switch-record.js.map +1 -0
  31. package/dist/ulid.d.ts +6 -0
  32. package/dist/ulid.d.ts.map +1 -1
  33. package/dist/ulid.js +21 -7
  34. package/dist/ulid.js.map +1 -1
  35. package/dist/vertical-events.d.ts +96 -2
  36. package/dist/vertical-events.d.ts.map +1 -1
  37. package/dist/vertical-events.js +230 -1
  38. package/dist/vertical-events.js.map +1 -1
  39. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"scope-host.js","sourceRoot":"","sources":["../src/scope-host.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"scope-host.js","sourceRoot":"","sources":["../src/scope-host.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * The SQLite limits a Durable Object enforces and node does not (#1741).
3
+ *
4
+ * A vertical's `ctx.sql` runs on `better-sqlite3` in every local suite and on a Durable
5
+ * Object's SQLite when it is deployed, and the DO's build sets several limits far below
6
+ * stock SQLite's. A statement over one passed every node suite and failed only on a
7
+ * deployed scope — `customer/get` in an adopter vertical counted rows with a 29-term
8
+ * `UNION ALL` and broke on the first hosted call. #1655 closed the same gap for a `LIKE`
9
+ * pattern (`tools/vitest/like-pattern-limit.cjs`, which has to patch the driver because
10
+ * `better-sqlite3` exposes no `sqlite3_limit`). Three of the four limits below can be judged
11
+ * from the statement text, so the adapter judges them itself, and a vertical's OWN suite sees
12
+ * what a Durable Object sees. The pattern limit cannot: a pattern is often built at run time,
13
+ * and judging it exactly means replacing `like()` on every connection, a JavaScript call per
14
+ * row that also switches off SQLite's `LIKE` prefix index optimisation. That cost is fine in a
15
+ * test preload and not in an adapter self-hosters run, so it stays a preload's job.
16
+ *
17
+ * The values are MEASURED, not read from documentation:
18
+ * `packages/adapter-cloudflare/test/do-sql-limits.test.ts` drives a real Durable Object's
19
+ * SQL to each limit and pins the value and the refusal message it gave. If workerd moves
20
+ * one, that test goes red first.
21
+ *
22
+ * | limit | value | DO's refusal |
23
+ * |-------------------------------|----------------|-----------------------------------------------------|
24
+ * | terms in one compound SELECT | 5 | `too many terms in compound SELECT` |
25
+ * | bound parameters | 100 | `too many SQL variables at offset N` |
26
+ * | statement length | 100 000 bytes | `statement too long` (`SQLITE_TOOBIG`) |
27
+ * | `LIKE`/`GLOB` pattern length | 50 bytes | `LIKE or GLOB pattern too complex` (not judged here: preload only) |
28
+ *
29
+ * What is NOT a limit, measured: a multi-row `VALUES` list — SQLite does not count its rows
30
+ * as compound terms; 5 000 rows ran on the DO. So `INSERT … VALUES (…),(…),…` is bounded by
31
+ * the parameter count and the statement length only.
32
+ *
33
+ * Judged here, from the text, exactly as SQLite counts:
34
+ * - **compound terms** — `UNION`, `UNION ALL`, `INTERSECT` and `EXCEPT` at one parenthesis
35
+ * level chain their operands into one compound; the limit is on the operands (operators + 1).
36
+ * A subquery, a CTE body and a view body each start their own count.
37
+ * - **bound parameters** — SQLite's own numbering: each `?` takes the next number, `?N`
38
+ * takes N, a `:name`/`@name`/`$name` takes the next number the first time it is seen. The
39
+ * limit is on the highest number, not on how many are written or how many are bound.
40
+ * - **statement length** — the whole string the caller passed, in UTF-8 bytes (a multi-
41
+ * statement string counts once, as on the DO).
42
+ *
43
+ * The messages are the DO's own, suffix included, so a suite that matches on one matches
44
+ * both adapters. Only MODULE-facing SQL is wrapped: platform-internal statements are the
45
+ * platform's own responsibility, and they already run on a real DO in the workerd suites.
46
+ */
47
+ import type { ScopedSql } from './scope-host.js';
48
+ /** The limits a Durable Object's SQLite enforces — measured, see the file header. */
49
+ export declare const DO_SQL_LIMITS: {
50
+ /** Terms (operands) in one compound `SELECT`. */
51
+ readonly compoundTerms: 5;
52
+ /** Bound parameters — the highest parameter number — in one statement. */
53
+ readonly boundParameters: 100;
54
+ /** Statement length, in UTF-8 bytes, of the whole string handed to `sql.exec`. */
55
+ readonly statementBytes: 100000;
56
+ /** `LIKE`/`GLOB` pattern length, in UTF-8 bytes. Enforced by `tools/vitest/like-pattern-limit.cjs`. */
57
+ readonly likePatternBytes: 50;
58
+ };
59
+ /**
60
+ * Refuse a statement a Durable Object's SQLite would refuse for its length, its compound
61
+ * `SELECT` terms, or its bound parameters. Throws the DO's own message.
62
+ */
63
+ export declare function assertWithinSqlLimits(sql: string): void;
64
+ /**
65
+ * Wrap a module-facing `ScopedSql` so every statement passes `assertWithinSqlLimits` first.
66
+ * The node adapter wraps `ctx.sql` in it; the DO needs no wrapper — its SQLite is the
67
+ * source of these limits.
68
+ */
69
+ export declare function guardSqlLimits(inner: ScopedSql): ScopedSql;
70
+ //# sourceMappingURL=sql-limits.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sql-limits.d.ts","sourceRoot":"","sources":["../src/sql-limits.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,OAAO,KAAK,EAAE,SAAS,EAAY,MAAM,iBAAiB,CAAC;AAE3D,qFAAqF;AACrF,eAAO,MAAM,aAAa;IACxB,iDAAiD;aACjD,aAAa,EAAE,CAAC;IAChB,0EAA0E;aAC1E,eAAe,EAAE,GAAG;IACpB,kFAAkF;aAClF,cAAc,EAAE,MAAO;IACvB,uGAAuG;aACvG,gBAAgB,EAAE,EAAE;CACZ,CAAC;AA4CX;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAsHvD;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,SAAS,GAAG,SAAS,CAW1D"}
@@ -0,0 +1,197 @@
1
+ /** The limits a Durable Object's SQLite enforces — measured, see the file header. */
2
+ export const DO_SQL_LIMITS = {
3
+ /** Terms (operands) in one compound `SELECT`. */
4
+ compoundTerms: 5,
5
+ /** Bound parameters — the highest parameter number — in one statement. */
6
+ boundParameters: 100,
7
+ /** Statement length, in UTF-8 bytes, of the whole string handed to `sql.exec`. */
8
+ statementBytes: 100_000,
9
+ /** `LIKE`/`GLOB` pattern length, in UTF-8 bytes. Enforced by `tools/vitest/like-pattern-limit.cjs`. */
10
+ likePatternBytes: 50,
11
+ };
12
+ const utf8 = new TextEncoder();
13
+ const byteLength = (s) => utf8.encode(s).length;
14
+ /** The offset SQLite reports is in BYTES from the start of the statement. */
15
+ const byteOffset = (sql, index) => byteLength(sql.slice(0, index));
16
+ const COMPOUND_OPERATORS = new Set(['union', 'intersect', 'except']);
17
+ const isWord = (c) => /[A-Za-z0-9_$\u0080-￿]/.test(c);
18
+ /**
19
+ * Where a `:name`, `@name` or `$name` parameter token ends — SQLite's own tokenizer rule
20
+ * (`sqlite3GetToken`, the variable case), which is wider than an identifier:
21
+ * - identifier characters (letters, digits, `_`, `$`, anything non-ASCII) continue it;
22
+ * - `::` continues it, so `$a::b` is ONE parameter (the pair adds nothing to the name);
23
+ * - a `(` starts a Tcl-style suffix that runs to the next `)` or whitespace, and ends the
24
+ * token: `$a(b c)` is not one parameter but `$a(b` and, past the space, other words.
25
+ * A scanner that stopped at the first `:` or `(` counted the rest as more parameters, so a
26
+ * valid statement could be refused for holding over 100 variables.
27
+ */
28
+ function endOfNamedParameter(sql, from) {
29
+ const n = sql.length;
30
+ let i = from + 1;
31
+ while (i < n) {
32
+ const c = sql[i];
33
+ if (isWord(c)) {
34
+ i += 1;
35
+ }
36
+ else if (c === '(') {
37
+ i += 1;
38
+ while (i < n && !/\s/.test(sql[i]) && sql[i] !== ')')
39
+ i += 1;
40
+ if (sql[i] === ')')
41
+ i += 1;
42
+ break;
43
+ }
44
+ else if (c === ':' && sql[i + 1] === ':') {
45
+ i += 2;
46
+ }
47
+ else {
48
+ break;
49
+ }
50
+ }
51
+ return i;
52
+ }
53
+ /**
54
+ * Refuse a statement a Durable Object's SQLite would refuse for its length, its compound
55
+ * `SELECT` terms, or its bound parameters. Throws the DO's own message.
56
+ */
57
+ export function assertWithinSqlLimits(sql) {
58
+ // Cheap first: UTF-16 length is at most the byte length, so a string under
59
+ // a third of the limit cannot be over it, and most statements skip the encode.
60
+ if (sql.length * 3 > DO_SQL_LIMITS.statementBytes && byteLength(sql) > DO_SQL_LIMITS.statementBytes) {
61
+ throw new Error('statement too long: SQLITE_TOOBIG');
62
+ }
63
+ const n = sql.length;
64
+ // Operators seen at each open parenthesis level of the statement being scanned.
65
+ let operators = [0];
66
+ let nVar = 0;
67
+ const named = new Map();
68
+ const tooManyVariables = (at) => new Error(`too many SQL variables at offset ${byteOffset(sql, at)}: SQLITE_ERROR`);
69
+ const bindOne = (at, number_) => {
70
+ if (number_ > DO_SQL_LIMITS.boundParameters)
71
+ throw tooManyVariables(at);
72
+ };
73
+ let i = 0;
74
+ while (i < n) {
75
+ const c = sql[i];
76
+ // Comments are whitespace to SQLite.
77
+ if (c === '-' && sql[i + 1] === '-') {
78
+ while (i < n && sql[i] !== '\n')
79
+ i += 1;
80
+ continue;
81
+ }
82
+ if (c === '/' && sql[i + 1] === '*') {
83
+ i += 2;
84
+ while (i < n && !(sql[i] === '*' && sql[i + 1] === '/'))
85
+ i += 1;
86
+ i += 2;
87
+ continue;
88
+ }
89
+ // String literals and quoted identifiers; a doubled quote is an escaped one.
90
+ if (c === "'" || c === '"' || c === '`') {
91
+ i += 1;
92
+ while (i < n) {
93
+ if (sql[i] === c) {
94
+ if (sql[i + 1] === c) {
95
+ i += 2;
96
+ continue;
97
+ }
98
+ break;
99
+ }
100
+ i += 1;
101
+ }
102
+ i += 1;
103
+ continue;
104
+ }
105
+ if (c === '[') {
106
+ while (i < n && sql[i] !== ']')
107
+ i += 1;
108
+ i += 1;
109
+ continue;
110
+ }
111
+ if (c === '(') {
112
+ operators.push(0);
113
+ i += 1;
114
+ continue;
115
+ }
116
+ if (c === ')') {
117
+ if (operators.length > 1)
118
+ operators.pop();
119
+ i += 1;
120
+ continue;
121
+ }
122
+ if (c === ';') {
123
+ // A new statement: its own compound chain and its own parameter numbering.
124
+ operators = [0];
125
+ nVar = 0;
126
+ named.clear();
127
+ i += 1;
128
+ continue;
129
+ }
130
+ if (c === '?') {
131
+ const start = i;
132
+ i += 1;
133
+ let digits = '';
134
+ while (i < n && sql[i] >= '0' && sql[i] <= '9')
135
+ digits += sql[i++];
136
+ if (digits === '') {
137
+ nVar += 1;
138
+ bindOne(start, nVar);
139
+ }
140
+ else {
141
+ const number_ = Number(digits);
142
+ if (number_ < 1 || number_ > DO_SQL_LIMITS.boundParameters) {
143
+ throw new Error(`variable number must be between ?1 and ?${DO_SQL_LIMITS.boundParameters} at offset ${byteOffset(sql, start)}: SQLITE_ERROR`);
144
+ }
145
+ if (number_ > nVar)
146
+ nVar = number_;
147
+ }
148
+ continue;
149
+ }
150
+ if (c === ':' || c === '@' || c === '$') {
151
+ const start = i;
152
+ i = endOfNamedParameter(sql, i);
153
+ // SQLite keys a slot on the WHOLE token, sigil included: `:a` and `@a` are two variables.
154
+ const token = sql.slice(start, i);
155
+ if (token.length > 1 && !named.has(token)) {
156
+ nVar += 1;
157
+ named.set(token, nVar);
158
+ bindOne(start, nVar);
159
+ }
160
+ continue;
161
+ }
162
+ if (isWord(c)) {
163
+ const start = i;
164
+ while (i < n && isWord(sql[i]))
165
+ i += 1;
166
+ // `t.union` is a column, not an operator; a reserved word cannot be a bare name otherwise.
167
+ const word = sql.slice(start, i).toLowerCase();
168
+ if (COMPOUND_OPERATORS.has(word) && sql[start - 1] !== '.') {
169
+ const level = operators.length - 1;
170
+ operators[level] = operators[level] + 1;
171
+ if (operators[level] + 1 > DO_SQL_LIMITS.compoundTerms) {
172
+ throw new Error('too many terms in compound SELECT: SQLITE_ERROR');
173
+ }
174
+ }
175
+ continue;
176
+ }
177
+ i += 1;
178
+ }
179
+ }
180
+ /**
181
+ * Wrap a module-facing `ScopedSql` so every statement passes `assertWithinSqlLimits` first.
182
+ * The node adapter wraps `ctx.sql` in it; the DO needs no wrapper — its SQLite is the
183
+ * source of these limits.
184
+ */
185
+ export function guardSqlLimits(inner) {
186
+ return {
187
+ query: (sql, params) => {
188
+ assertWithinSqlLimits(sql);
189
+ return inner.query(sql, params);
190
+ },
191
+ exec: (sql, params) => {
192
+ assertWithinSqlLimits(sql);
193
+ return inner.exec(sql, params);
194
+ },
195
+ };
196
+ }
197
+ //# sourceMappingURL=sql-limits.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sql-limits.js","sourceRoot":"","sources":["../src/sql-limits.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,158 @@
1
+ /**
2
+ * The directory's record of the schedule kill switch (#1674), shared by both adapters.
3
+ *
4
+ * The switch itself (`system-switch.ts`) lives in the scope's own storage: a `switch:off`
5
+ * marker beside the module's grants, which is what the runner gates on. That is the right
6
+ * place for the gate, and the wrong place for the only copy. A scope whose storage is wiped
7
+ * and re-provisioned (#321/#332), or restored from a dump taken before the switch was pulled,
8
+ * loses the marker, and the next reconcile seats the grants live again. The switch is then
9
+ * silently on, on exactly the scopes an operator switched off.
10
+ *
11
+ * So the directory keeps one row per (tenant, scope, module): the position the switch was
12
+ * last moved to, by whom, why, and when. It is the model `_substrat_connection_grants` set
13
+ * (#592): the enforcement stays where it is checked, and the directory holds what a
14
+ * reconcile re-asserts FROM. It also answers the fleet question the scope cannot: "which
15
+ * scopes have a module switched off", without walking every scope's store.
16
+ *
17
+ * **OFF wins from either side, and a record never turns anything on.**
18
+ * - A record of `off` and a scope that has lost its marker: the record wins, and
19
+ * `HostAdmin.reassertSystemSwitches` switches it off again, after the seat, so the grants
20
+ * the seat recreated are the ones OFF tombstones and a later ON gives back.
21
+ * - A record of `on` (or none) and a live marker: the marker wins, and it stays off.
22
+ * Restore is the lever (#1666), and a reconcile that could turn a kill switch back on
23
+ * would be a second one.
24
+ *
25
+ * The admin log is still the history. This is only the current position.
26
+ */
27
+ import type { ListPage } from '@substrat-run/contracts';
28
+ import type { SwitchSql, SystemScheduleState } from './system-switch.js';
29
+ /**
30
+ * The table. Interpolated into both adapters' directory DDL, so `lint:spine-ddl` sees the
31
+ * one spelling on each side. No CHECK on `position`: the drift gate does not compare
32
+ * constraints, and the writers below are the only thing that sets it.
33
+ */
34
+ export declare const SYSTEM_SWITCHES_DDL = "\n CREATE TABLE IF NOT EXISTS _substrat_system_switches (\n tenant_id TEXT NOT NULL,\n scope_id TEXT NOT NULL,\n module_id TEXT NOT NULL,\n -- 'on' or 'off'. A row exists only for a module some switch call actually held.\n position TEXT NOT NULL,\n actor TEXT NOT NULL,\n reason TEXT NOT NULL,\n -- The switch call's own id, the one its admin-log intent and outcome rows carry.\n -- A ULID, so it is also the fleet read's chronological keyset cursor.\n operation_id TEXT NOT NULL,\n switched_at TEXT NOT NULL,\n PRIMARY KEY (tenant_id, scope_id, module_id)\n );\n CREATE INDEX IF NOT EXISTS _substrat_system_switches_position\n ON _substrat_system_switches (position, operation_id);\n CREATE INDEX IF NOT EXISTS _substrat_system_switches_operation\n ON _substrat_system_switches (operation_id);\n";
35
+ /**
36
+ * The one-time backfill from the admin log: every switch moved before the record existed.
37
+ * Without it, those are exactly the switches a wipe would still lose.
38
+ *
39
+ * It reads the admin-log shape `revokeFromSystem` / `restoreToSystem` have written since
40
+ * #1666, two rows per call sharing `after.operationId`: an `intent` row carrying
41
+ * `after.moduleId` and `after.reason`, then an outcome row whose `after.phase` is
42
+ * `applied`, `refused` or `failed`. Only calls whose outcome is `applied` count, because a
43
+ * refused call moved nothing and a failed one may not have. Of those, only the LATEST per
44
+ * (tenant, scope, module) is taken, by the intent's ULID id. `at` is the intent's, the
45
+ * same instant the status read's who/why join reports. A key whose calls include no
46
+ * applied OFF gets no row, even when its latest applied call is an ON: a live
47
+ * `restoreToSystem` of a module never switched off writes none either
48
+ * (`recordSystemSwitchedOn`), and the backfill must not record what the live path would not.
49
+ *
50
+ * `INSERT OR IGNORE`, so a row the switch wrote itself is never overwritten by history.
51
+ * The adapters run it only on the construction that creates the table, so it runs once.
52
+ */
53
+ export declare const SYSTEM_SWITCHES_BACKFILL_SQL = "\n WITH applied AS (\n SELECT DISTINCT action, json_extract(after, '$.operationId') AS operation_id\n FROM _substrat_admin_log\n WHERE action IN ('revokeFromSystem', 'restoreToSystem')\n AND json_extract(after, '$.phase') = 'applied'\n ),\n intents AS (\n SELECT i.id AS id, i.tenant_id AS tenant_id, i.scope_id AS scope_id,\n json_extract(i.after, '$.moduleId') AS module_id,\n CASE i.action WHEN 'revokeFromSystem' THEN 'off' ELSE 'on' END AS position,\n i.actor AS actor,\n json_extract(i.after, '$.reason') AS reason,\n json_extract(i.after, '$.operationId') AS operation_id,\n i.at AS at\n FROM _substrat_admin_log i\n JOIN applied a\n ON a.action = i.action AND a.operation_id = json_extract(i.after, '$.operationId')\n WHERE i.action IN ('revokeFromSystem', 'restoreToSystem')\n AND i.tenant_id IS NOT NULL AND i.scope_id IS NOT NULL\n AND json_extract(i.after, '$.phase') = 'intent'\n AND json_extract(i.after, '$.moduleId') IS NOT NULL\n AND json_extract(i.after, '$.reason') IS NOT NULL\n ),\n ranked AS (\n SELECT *,\n ROW_NUMBER() OVER (PARTITION BY tenant_id, scope_id, module_id ORDER BY id DESC) AS latest,\n MAX(position = 'off') OVER (PARTITION BY tenant_id, scope_id, module_id) AS ever_off\n FROM intents\n )\n INSERT OR IGNORE INTO _substrat_system_switches\n (tenant_id, scope_id, module_id, position, actor, reason, operation_id, switched_at)\n SELECT tenant_id, scope_id, module_id, position, actor, reason, operation_id, at\n FROM ranked\n WHERE latest = 1 AND ever_off = 1\n";
54
+ /** "Does the record table exist yet?" — asked BEFORE the DDL, so the backfill runs once. */
55
+ export declare function systemSwitchesTableExists(db: SwitchSql): boolean;
56
+ /** One record row as the directory holds it, with the scope's binding joined on. */
57
+ export interface SystemSwitchRecordRow {
58
+ tenantId: string;
59
+ scopeId: string;
60
+ moduleId: string;
61
+ vertical: string | null;
62
+ position: 'on' | 'off';
63
+ actor: string;
64
+ reason: string;
65
+ operationId: string;
66
+ at: string;
67
+ }
68
+ /**
69
+ * Filter for the fleet read. Unset `position` means both; the HTTP route defaults it to
70
+ * `off`. Paged by `operationId` (the exclusive cursor), oldest first unless `order: 'desc'`.
71
+ */
72
+ export interface SystemSwitchRecordFilter extends ListPage {
73
+ position?: 'on' | 'off';
74
+ tenantId?: string;
75
+ scopeId?: string;
76
+ moduleId?: string;
77
+ vertical?: string;
78
+ }
79
+ /**
80
+ * The fleet read (`GET /system-switches`): records, keyset-paged by `operation_id`.
81
+ *
82
+ * The key is the call that last moved the switch, so a row whose switch moves during a walk
83
+ * is re-keyed to a newer id and jumps to the end of the order. Ascending (the default) never
84
+ * skips a row for it; at worst it is seen twice, once per position. A DESCENDING walk can
85
+ * miss a row that moved after the walk started, so use ascending for "every switch".
86
+ */
87
+ export declare function listSystemSwitchRecords(db: SwitchSql, filter?: SystemSwitchRecordFilter): SystemSwitchRecordRow[];
88
+ /** One scope's record rows by module — what the per-scope status read joins as `recorded`. */
89
+ export declare function systemSwitchRecordsOf(db: SwitchSql, tenantId: string, scopeId: string): Map<string, 'on' | 'off'>;
90
+ /**
91
+ * The per-scope status read's rows with the record joined on. Each module the scope
92
+ * reports gets its recorded position (or null). A module the record holds that the scope
93
+ * no longer reports at all is ADDED, as `ungranted`: that is the scope a wipe emptied, and
94
+ * the drift is the whole reason to show it. Sorted by module id, as the scope's rows are.
95
+ */
96
+ export declare function withRecorded(states: {
97
+ moduleId: string;
98
+ schedules: SystemScheduleState;
99
+ }[], recorded: Map<string, 'on' | 'off'>): {
100
+ moduleId: string;
101
+ schedules: SystemScheduleState;
102
+ recorded: 'on' | 'off' | null;
103
+ }[];
104
+ /** The modules a reconcile must re-assert OFF on this scope. */
105
+ export declare function switchedOffModulesOf(db: SwitchSql, tenantId: string, scopeId: string): string[];
106
+ /** What one switch call writes into the record. */
107
+ export interface SystemSwitchRecordWrite {
108
+ tenantId: string;
109
+ scopeId: string;
110
+ moduleId: string;
111
+ actor: string;
112
+ reason: string;
113
+ operationId: string;
114
+ at: string;
115
+ }
116
+ /** The row as it stood before a write, so a failed switch can put it back. */
117
+ export type SystemSwitchRecordPrior = Pick<SystemSwitchRecordRow, 'position' | 'actor' | 'reason' | 'operationId' | 'at'> | null;
118
+ /** What a re-assert did for one recorded-off module — `HostAdmin.reassertSystemSwitches`' answer. */
119
+ export interface SystemSwitchReassert {
120
+ moduleId: string;
121
+ held: boolean;
122
+ changed: boolean;
123
+ }
124
+ /**
125
+ * OFF's write, made AFTER the scope's switch held. An upsert: a repeat OFF refreshes the
126
+ * actor, reason and time, as the status read reports the latest reason. Never made for a
127
+ * call that held nothing, because a row saying `off` for a module the scope never ran
128
+ * would switch it off on the day it is installed (the kernel's `held` check, for the same
129
+ * reason).
130
+ */
131
+ export declare function recordSystemSwitchedOff(db: SwitchSql, row: SystemSwitchRecordWrite): void;
132
+ /**
133
+ * ON's write, made BEFORE the scope's switch moves, and only to a row that exists. Before,
134
+ * because the other order fails unsafely: a scope switched on whose record write then
135
+ * failed would still read `off`, and the next reconcile would switch the operator's
136
+ * restore back off. This order fails toward a record of `on` beside a live marker, which
137
+ * the marker wins. Answers the row as it was, for `restoreSystemSwitchRecord`.
138
+ */
139
+ export declare function recordSystemSwitchedOn(db: SwitchSql, row: SystemSwitchRecordWrite): SystemSwitchRecordPrior;
140
+ /**
141
+ * Put a row back as `recordSystemSwitchedOn` found it — the ON that wrote it threw, or held
142
+ * nothing. A compare-and-set on the ON's own `operationId`: if another switch call has
143
+ * written the row since (a concurrent OFF), the row is left as that call wrote it, so an
144
+ * ON's undo can never clobber a newer position.
145
+ */
146
+ export declare function restoreSystemSwitchRecord(db: SwitchSql, on: {
147
+ tenantId: string;
148
+ scopeId: string;
149
+ moduleId: string;
150
+ operationId: string;
151
+ }, prior: SystemSwitchRecordPrior): void;
152
+ /**
153
+ * Forget one scope's switch records — the scope's directory row is going too (a reaped
154
+ * preview or fork, a reaped scope). Without this the fleet read keeps listing a switch on
155
+ * a scope that no longer exists. The admin log keeps the history, as it keeps the scope's.
156
+ */
157
+ export declare function forgetSystemSwitchesOf(db: SwitchSql, scopeId: string): void;
158
+ //# sourceMappingURL=system-switch-record.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"system-switch-record.d.ts","sourceRoot":"","sources":["../src/system-switch-record.ts"],"names":[],"mappings":"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"}