@substrat-run/kernel 0.116.0 → 0.117.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 (60) hide show
  1. package/dist/capability.d.ts +220 -0
  2. package/dist/capability.d.ts.map +1 -0
  3. package/dist/capability.js +537 -0
  4. package/dist/capability.js.map +1 -0
  5. package/dist/check-key.d.ts +30 -0
  6. package/dist/check-key.d.ts.map +1 -0
  7. package/dist/check-key.js +37 -0
  8. package/dist/check-key.js.map +1 -0
  9. package/dist/denial-query.d.ts +32 -1
  10. package/dist/denial-query.d.ts.map +1 -1
  11. package/dist/denial-query.js +66 -28
  12. package/dist/denial-query.js.map +1 -1
  13. package/dist/index.d.ts +19 -4
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +14 -3
  16. package/dist/index.js.map +1 -1
  17. package/dist/outbox-event.d.ts +131 -0
  18. package/dist/outbox-event.d.ts.map +1 -0
  19. package/dist/outbox-event.js +185 -0
  20. package/dist/outbox-event.js.map +1 -0
  21. package/dist/permission-checker.d.ts +8 -1
  22. package/dist/permission-checker.d.ts.map +1 -1
  23. package/dist/permission-checker.js +18 -0
  24. package/dist/permission-checker.js.map +1 -1
  25. package/dist/permission-eval.d.ts +8 -0
  26. package/dist/permission-eval.d.ts.map +1 -1
  27. package/dist/permission-eval.js +164 -91
  28. package/dist/permission-eval.js.map +1 -1
  29. package/dist/platform-request-query.d.ts +58 -1
  30. package/dist/platform-request-query.d.ts.map +1 -1
  31. package/dist/platform-request-query.js +61 -1
  32. package/dist/platform-request-query.js.map +1 -1
  33. package/dist/platform-sweep.d.ts +123 -8
  34. package/dist/platform-sweep.d.ts.map +1 -1
  35. package/dist/platform-sweep.js +158 -34
  36. package/dist/platform-sweep.js.map +1 -1
  37. package/dist/row-decode.d.ts +107 -0
  38. package/dist/row-decode.d.ts.map +1 -0
  39. package/dist/row-decode.js +93 -0
  40. package/dist/row-decode.js.map +1 -0
  41. package/dist/scope-host.d.ts +211 -9
  42. package/dist/scope-host.d.ts.map +1 -1
  43. package/dist/scope-host.js +33 -0
  44. package/dist/scope-host.js.map +1 -1
  45. package/dist/scope-tuple-seat.d.ts +72 -0
  46. package/dist/scope-tuple-seat.d.ts.map +1 -0
  47. package/dist/scope-tuple-seat.js +93 -0
  48. package/dist/scope-tuple-seat.js.map +1 -0
  49. package/dist/subject-redaction.d.ts +160 -0
  50. package/dist/subject-redaction.d.ts.map +1 -0
  51. package/dist/subject-redaction.js +210 -0
  52. package/dist/subject-redaction.js.map +1 -0
  53. package/dist/system-switch.d.ts +108 -0
  54. package/dist/system-switch.d.ts.map +1 -0
  55. package/dist/system-switch.js +145 -0
  56. package/dist/system-switch.js.map +1 -0
  57. package/dist/timeline.d.ts.map +1 -1
  58. package/dist/timeline.js +109 -57
  59. package/dist/timeline.js.map +1 -1
  60. package/package.json +2 -2
@@ -0,0 +1,210 @@
1
+ /**
2
+ * The intent journal's half of a subject erasure (#1600).
3
+ *
4
+ * `shredSubject` holds K-37's Tier-1 line — *the payload goes, the envelope stays* —
5
+ * and for a year that line reached exactly one table, `_substrat_outbox`. It is not the
6
+ * only place the spine keeps an event's payload. A CP-less host cannot run a connector
7
+ * (no directory, no credentials, no sanctioned egress), so each connector delivery
8
+ * becomes a `connector:<provider>` platform intent whose payload is the WHOLE
9
+ * `DomainEvent` — fat by design, because "the platform's handler needs everything the
10
+ * in-process handler would have been handed" (`connectorDispatchPayload`). That copy
11
+ * lands in `_substrat_platform_requests`, which nothing ever deletes: the retention is
12
+ * deliberate, `listPlatformRequestHistory` exists precisely so a settled row stays
13
+ * readable. So a name the redaction did not reach there survived in the live scope
14
+ * database, and in every export, backup and PITR window taken from it afterwards.
15
+ *
16
+ * **The rule this module implements is narrow on purpose:** an intent that carries a
17
+ * COPY OF AN EVENT is redacted exactly when the erasure redacts that event. Not "any
18
+ * payload mentioning the subject" — the outbox spares a `piiClass: 'none'` event even
19
+ * when it names the subject, and a copy judged more harshly than its original is an
20
+ * incoherent rule, not a stricter one. So the predicate below is the outbox's own
21
+ * predicate (`subject_id = ? AND pii_class != 'none'`) applied to whatever spine
22
+ * envelope the payload embeds, at whatever depth. `subjectId` and `piiClass` occur
23
+ * together only on `domainEventShape`, which is what makes the walk kind-agnostic
24
+ * rather than a guess about `connector:*`.
25
+ *
26
+ * What it therefore does NOT reach is an intent kind that carries a subject's PII
27
+ * WITHOUT carrying the classified event — an intent payload has no `piiClass` of its
28
+ * own, so the kernel has nothing to read. No kind does that today (kernel-design.md
29
+ * §13.1 limit 7 says so and names the walk), and a kind that started to would be
30
+ * inventing an unclassified PII store inside the spine.
31
+ *
32
+ * This is a kernel module rather than two copies of the same SQL for
33
+ * `platformRequestHistoryQuery`'s reason, sharpened: three surfaces answer one question
34
+ * from this table, and a privacy guarantee that holds on one adapter is not a guarantee.
35
+ */
36
+ /**
37
+ * The one key a redacted intent payload carries, and nothing else in the system does.
38
+ *
39
+ * Underscore-prefixed on the `_substrat_*` habit: the marker is the platform's, not a
40
+ * field some vertical's payload could plausibly own.
41
+ */
42
+ export const REDACTED_INTENT_MARKER = '_substratRedacted';
43
+ /**
44
+ * What replaces a redacted intent's payload.
45
+ *
46
+ * The column is `payload TEXT NOT NULL` on both adapters, so the outbox's
47
+ * `SET payload = NULL` cannot transfer and *what a redacted intent looks like* is a
48
+ * shape to choose rather than a null to write. Three things decide this one:
49
+ *
50
+ * - **Obviously redacted, never plausible data.** A reader who has the row in front of
51
+ * them must not have to know the kind's schema to see that the content is gone.
52
+ * - **It fails every handler's parse.** Each drain handler opens with
53
+ * `<kind>Payload.parse(request.payload)`, and no kind's schema admits an object whose
54
+ * only key is this marker. So a drain that reaches the row after the redaction settles
55
+ * loudly instead of executing a tombstone as if it were an instruction. What this does
56
+ * NOT close, and should not be read as closing, is the drain that had already READ the
57
+ * payload when the erasure landed: it DELIVERS what it read. That window is the one the
58
+ * outbox redaction has too — a consumer mid-dispatch holds the payload it was handed —
59
+ * and closing it wants a lock across the drain hop rather than a shape here. Its
60
+ * WRITEBACK is closed separately and is not part of that residue: `settlePlatformRequest`
61
+ * is a compare-and-set on `status = 'pending'` on both adapters, so a stale pass cannot
62
+ * undo the redaction or put a provider's reply — which can quote the person — back into
63
+ * `last_error`. The two halves were one sentence in the first cut of this file, which
64
+ * made an avoidable write look as unavoidable as the delivery.
65
+ * - **It keeps the pseudonymous key, exactly as the outbox row does.** The redacted
66
+ * outbox row still carries `subject_id`; §5.3's "pseudonymous keys and transaction
67
+ * facts remain" is the same sentence here. A row that is blank for no stated reason
68
+ * reads as corruption; this one says what happened to it and when.
69
+ */
70
+ export function redactedIntentPayload(subjectId, at) {
71
+ return JSON.stringify({
72
+ [REDACTED_INTENT_MARKER]: { reason: 'subject-erasure', subjectId, at },
73
+ });
74
+ }
75
+ /**
76
+ * The note a redaction leaves on an intent that had already settled.
77
+ *
78
+ * `last_error` is overwritten rather than kept, and that is deliberate: it is free text
79
+ * a third party wrote about the delivery of this person's data — the surface #618 built
80
+ * precisely so a provider's own sentence ("…requires valid personal number field") is
81
+ * readable — so it is content about the subject, not envelope. An erasure that emptied
82
+ * `payload` and left a provider quoting the name two columns over would be the same bug
83
+ * this fixes, one column to the right.
84
+ */
85
+ export const REDACTED_INTENT_NOTE = 'redacted by subject erasure (#37) — what this intent said is gone; that it happened, and when, is not';
86
+ /**
87
+ * The note a redaction leaves on an intent that was still `pending`.
88
+ *
89
+ * A pending row is redacted like any other AND settled `failed` in the same statement,
90
+ * because the two halves of the alternative are both wrong: leaving it pending-and-intact
91
+ * keeps the name (the defect), and leaving it pending-and-redacted hands the drain a
92
+ * tombstone to execute. `failed` is the truthful terminal state — the delivery did not
93
+ * happen and now cannot — and the executor delivery behind it was already journaled as
94
+ * routed, so nothing re-routes the event to replace this row.
95
+ */
96
+ export const CANCELLED_INTENT_NOTE = 'cancelled by subject erasure (#37) — the payload was redacted before the drain reached it, so this intent never ran';
97
+ /**
98
+ * The candidate read: every intent whose stored payload TEXT contains the subject id.
99
+ *
100
+ * A prefilter, not the decision — `intentPayloadCarriesSubject` decides. `instr` rather
101
+ * than `LIKE` because it is a literal substring search with no wildcard to escape, and
102
+ * the needle is the subject id as `JSON.stringify` would have written it into the
103
+ * payload (identical to the raw id for a `dataSubjectId`, which is a ULID and so has
104
+ * nothing to escape — spelled out anyway, since the host contract types the parameter
105
+ * `string`).
106
+ *
107
+ * Unindexed, and that is affordable: this runs once per staff-triggered erasure over one
108
+ * scope's journal, where the alternative is parsing every row's JSON in the host.
109
+ */
110
+ export function platformRequestRedactionQuery(subjectId) {
111
+ return {
112
+ sql: 'SELECT id, payload FROM _substrat_platform_requests WHERE instr(payload, ?) > 0',
113
+ // Strip the quotes JSON.stringify adds and keep the escaped body — the exact run of
114
+ // characters the serialized payload holds.
115
+ params: [JSON.stringify(subjectId).slice(1, -1)],
116
+ };
117
+ }
118
+ /**
119
+ * The write. One statement per redacted row, so the payload, the note and the pending
120
+ * row's settlement are one atomic change rather than three that can half-land.
121
+ *
122
+ * Every SET expression is evaluated against the ORIGINAL row in SQLite, so both `CASE
123
+ * WHEN status = 'pending'` arms read the status as it was before this statement touched
124
+ * it — which is what lets one statement both re-word the note and change the status it
125
+ * branched on. `settled_at` is COALESCEd so a row that already settled keeps the instant
126
+ * it settled at; only a cancelled pending row is stamped now.
127
+ *
128
+ * Params, in order: payload, cancelled-note, redacted-note, at, id.
129
+ */
130
+ export const PLATFORM_REQUEST_REDACTION_SQL = `UPDATE _substrat_platform_requests
131
+ SET payload = ?,
132
+ last_error = CASE WHEN status = 'pending' THEN ? ELSE ? END,
133
+ settled_at = COALESCE(settled_at, ?),
134
+ status = CASE WHEN status = 'pending' THEN 'failed' ELSE status END
135
+ WHERE id = ?`;
136
+ /** The bound parameters for `PLATFORM_REQUEST_REDACTION_SQL`, in its declared order. */
137
+ export function platformRequestRedactionParams(id, subjectId, at) {
138
+ return [redactedIntentPayload(subjectId, at), CANCELLED_INTENT_NOTE, REDACTED_INTENT_NOTE, at, id];
139
+ }
140
+ /**
141
+ * Does this stored payload carry a copy of an event the erasure redacts?
142
+ *
143
+ * The outbox's predicate, walked over the payload's structure: an object carrying BOTH
144
+ * `subjectId` equal to this subject AND a `piiClass` other than `'none'` IS a spine
145
+ * envelope — those two field names occur together nowhere else — so wherever the spine
146
+ * has copied one into an intent, the copy inherits the original's redaction.
147
+ *
148
+ * Returns false for a payload that IS already a tombstone (so a re-run after a crash
149
+ * converges rather than re-stamping) and false for a payload that is not JSON at all,
150
+ * which nothing in the kernel can write but which a stricter answer would have to invent
151
+ * a meaning for.
152
+ */
153
+ export function intentPayloadCarriesSubject(payloadText, subjectId) {
154
+ let parsed;
155
+ try {
156
+ parsed = JSON.parse(payloadText);
157
+ }
158
+ catch {
159
+ return false;
160
+ }
161
+ if (isRedactedPayload(parsed))
162
+ return false;
163
+ return carriesSubject(parsed, subjectId);
164
+ }
165
+ /**
166
+ * Is this payload one we already redacted?
167
+ *
168
+ * **Asked of the WHOLE payload, once, and never inside the walk (#1600 review).** The
169
+ * first cut short-circuited the walk at any object carrying the marker key, which made
170
+ * `{ _substratRedacted: false, event: <a real envelope> }` a payload the erasure stepped
171
+ * straight past, PII and all — and the comment beside it claimed the check was redundant
172
+ * belt-and-braces because a tombstone has no `piiClass`. That was true of the tombstone
173
+ * and false of everything else wearing its key. An intent payload is `unknown` and module
174
+ * code chooses it, so a marker that means "stop looking" must not be something a payload
175
+ * can merely CONTAIN.
176
+ *
177
+ * The invariant asked instead is un-forgeable in the only way that matters: *a payload
178
+ * that is nothing but a redaction tombstone is already redacted*. One top-level key, and
179
+ * that key's value says why. Anything beside the marker is not this, gets walked, and is
180
+ * judged on its own contents — which is exactly right, because something beside the
181
+ * marker is something the erasure might need to reach. Inner fields beyond `reason` are
182
+ * deliberately not pinned: a later tombstone that carries more would still be nothing but
183
+ * a tombstone, and hard-coding its shape here is how idempotency would quietly regress.
184
+ */
185
+ function isRedactedPayload(parsed) {
186
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed))
187
+ return false;
188
+ const keys = Object.keys(parsed);
189
+ if (keys.length !== 1 || keys[0] !== REDACTED_INTENT_MARKER)
190
+ return false;
191
+ const marker = parsed[REDACTED_INTENT_MARKER];
192
+ return (typeof marker === 'object' &&
193
+ marker !== null &&
194
+ !Array.isArray(marker) &&
195
+ marker['reason'] === 'subject-erasure');
196
+ }
197
+ function carriesSubject(node, subjectId) {
198
+ if (Array.isArray(node))
199
+ return node.some((child) => carriesSubject(child, subjectId));
200
+ if (node === null || typeof node !== 'object')
201
+ return false;
202
+ const obj = node;
203
+ if (obj['subjectId'] === subjectId &&
204
+ typeof obj['piiClass'] === 'string' &&
205
+ obj['piiClass'] !== 'none') {
206
+ return true;
207
+ }
208
+ return Object.values(obj).some((child) => carriesSubject(child, subjectId));
209
+ }
210
+ //# sourceMappingURL=subject-redaction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subject-redaction.js","sourceRoot":"","sources":["../src/subject-redaction.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,108 @@
1
+ /**
2
+ * The schedule kill switch (#1666), shared by both adapters so the gate that decides
3
+ * whether a module's schedules run and the lever that moves it cannot disagree.
4
+ *
5
+ * A scope runs a module's schedules only while it holds a live `system:<module>` grant
6
+ * (#383) — "the grant IS the switch". That sentence was true of the gate and had no
7
+ * lever: nothing could revoke the grant. This file is the lever, and it has to be more
8
+ * than a revoke, for two reasons found while building it:
9
+ *
10
+ * 1. **A reconcile seats grants.** Since #1659 a reconcile leaves a revoked grant revoked,
11
+ * but it still CREATES a missing one. A vertical whose next version declares a schedule
12
+ * with a new permission gets that grant seated live on every install — and a gate that
13
+ * reads "any live grant" is open again, on a scope an operator switched off.
14
+ * 2. **An explicit grant clears a tombstone.** `grantToSystem` is `INSERT OR REPLACE` by
15
+ * design (#1659's re-grant guarantee), so a stray grant of one permission would bring
16
+ * that tuple back — and with it, under the old gate, the whole module.
17
+ *
18
+ * So the OFF position is a tuple of its own: a live `system:<module>` / `switch:off` /
19
+ * `scope:<id>` marker. The gate is OFF while that marker is live, whatever grants are
20
+ * live beside it. **Restore is the lever; a grant is not.** The marker is an ordinary
21
+ * K-21 tuple: switching back on tombstones it, so the row stays as evidence of when the
22
+ * switch was last pulled. The permission walk reads only `role:` and `granted:` relations
23
+ * (`permission-eval.ts`), so the marker authorizes nothing and denies nothing by itself.
24
+ *
25
+ * Switching off ALSO tombstones every live `granted:` tuple the module holds on the scope.
26
+ * That is what makes it a kill switch rather than a schedule pause: anything that acts
27
+ * with the module's system authority — a resumable job run (#1577), a `getSystemScope`
28
+ * invoke — is denied by its own `ctx.check` while the switch is off.
29
+ *
30
+ * **And nothing may write a new one while it is off**, which is what makes that denial
31
+ * hold. The checker is deliberately NOT marker-aware; the switch is closed on the WRITE
32
+ * side instead, at the only two writers of a scope-level `system:` grant:
33
+ * - `grantToSystem` refuses while the module is switched off on that scope (the adapters
34
+ * ask `systemSwitchedOff` in the same unit as the write);
35
+ * - provisioning's seat (`seatScopeTuple`) seats nothing for a subject whose marker is
36
+ * live, so a reconcile cannot create a grant a newer version declares.
37
+ *
38
+ * **Restore returns exactly what OFF took, never more.** Each grant OFF tombstones gets a
39
+ * `switched:<permission>` record, live for as long as the switch holds it. ON restores
40
+ * only grants with a live record, then tombstones the records — so a grant that was
41
+ * revoked independently BEFORE the switch was pulled stays revoked through OFF and ON.
42
+ *
43
+ * Params and results are plain SQL over `_substrat_tuples`, run through `SwitchSql`,
44
+ * which each adapter implements over its own handle inside one transaction.
45
+ */
46
+ /** The relation of the OFF marker. Not `granted:` and not `role:`, so no check reads it. */
47
+ export declare const SYSTEM_SWITCH_OFF_RELATION = "switch:off";
48
+ /** The two statement shapes the switch needs, over either adapter's SQLite handle. */
49
+ export interface SwitchSql {
50
+ all(sql: string, ...params: (string | null)[]): Record<string, unknown>[];
51
+ run(sql: string, ...params: (string | null)[]): void;
52
+ }
53
+ /**
54
+ * Where one module's schedules stand on one scope.
55
+ *
56
+ * - `on`: a live grant and no live OFF marker — due schedules fire.
57
+ * - `off`: a live OFF marker. The schedules do not fire, and each is reported `skipped`
58
+ * with `switchedOff: true` — the sweep reached the scope and chose not to run, which
59
+ * is not the same fact as a sweep that never reached it.
60
+ * - `ungranted`: no live grant and no marker. A foreign vertical's scope on a CP-full host
61
+ * (the module is registered and this scope never ran it), or a grant removed by a raw
62
+ * write. Quiet, exactly as before the switch existed: no run, no skip, no error.
63
+ */
64
+ export type SystemScheduleState = 'on' | 'off' | 'ungranted';
65
+ /**
66
+ * "Is this subject's OFF marker live?", as a SQL predicate over ONE bound parameter (the
67
+ * subject). The one spelling the gate, the grant refusal and the provisioning seat share,
68
+ * so the three cannot disagree about what "switched off" means.
69
+ */
70
+ export declare const SYSTEM_SWITCH_OFF_PREDICATE = "EXISTS (SELECT 1 FROM _substrat_tuples\n WHERE subject = ? AND relation = 'switch:off' AND revoked_at IS NULL)";
71
+ /**
72
+ * The gate, one statement: is the OFF marker live, and is any `granted:` tuple live?
73
+ * `substr` rather than `LIKE`: `LIKE` is case-insensitive in SQLite and a Durable
74
+ * Object caps its patterns (#1655); an exact prefix compare is neither.
75
+ */
76
+ export declare function systemScheduleState(db: SwitchSql, moduleId: string, now: string): SystemScheduleState;
77
+ /** Is this module switched off on the scope `db` is? What `grantToSystem` refuses on. */
78
+ export declare function systemSwitchedOff(db: SwitchSql, moduleId: string): boolean;
79
+ /** The refusal `grantToSystem` throws while the switch is off — one wording, both adapters. */
80
+ export declare function systemSwitchedOffMessage(moduleId: string, scopeId: string): string;
81
+ /** What one switch call did in the scope's own storage — `SystemSwitchOutcome`'s shape. */
82
+ export interface SwitchOutcome {
83
+ held: boolean;
84
+ changed: boolean;
85
+ permissions: string[];
86
+ }
87
+ /**
88
+ * Move one module's switch on one scope. Idempotent: a repeat changes nothing and says so.
89
+ *
90
+ * OFF tombstones every live `granted:` tuple the module holds at `scope:<id>`, records each
91
+ * as `switched:<permission>`, and makes the marker live. ON un-tombstones exactly the
92
+ * grants with a live `switched:` record, tombstones those records, and tombstones the
93
+ * marker. A repeated OFF re-asserts the whole position: a grant that became live meanwhile
94
+ * (only a raw write can do that now) is tombstoned and recorded too.
95
+ *
96
+ * `held: false`, with nothing written, when the scope holds neither a grant nor a marker
97
+ * for the module: the caller named something this scope never ran, and turning "nothing"
98
+ * off must not write a marker that silently disables a module the day it is installed.
99
+ *
100
+ * Run it inside one transaction; the reads and writes here are meant to be one unit.
101
+ */
102
+ export declare function switchSystemSchedules(db: SwitchSql, input: {
103
+ moduleId: string;
104
+ scopeId: string;
105
+ to: 'on' | 'off';
106
+ at: string;
107
+ }): SwitchOutcome;
108
+ //# sourceMappingURL=system-switch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"system-switch.d.ts","sourceRoot":"","sources":["../src/system-switch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAEH,4FAA4F;AAC5F,eAAO,MAAM,0BAA0B,eAAe,CAAC;AAKvD,sFAAsF;AACtF,MAAM,WAAW,SAAS;IACxB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IAC1E,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC;CACtD;AAED;;;;;;;;;;GAUG;AACH,MAAM,MAAM,mBAAmB,GAAG,IAAI,GAAG,KAAK,GAAG,WAAW,CAAC;AAI7D;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,oHACmD,CAAC;AAE5F;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,mBAAmB,CAcrG;AAED,yFAAyF;AACzF,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAK1E;AAED,+FAA+F;AAC/F,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAMlF;AAED,2FAA2F;AAC3F,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,qBAAqB,CACnC,EAAE,EAAE,SAAS,EACb,KAAK,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,IAAI,GAAG,KAAK,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GACzE,aAAa,CA4Ff"}
@@ -0,0 +1,145 @@
1
+ /**
2
+ * The schedule kill switch (#1666), shared by both adapters so the gate that decides
3
+ * whether a module's schedules run and the lever that moves it cannot disagree.
4
+ *
5
+ * A scope runs a module's schedules only while it holds a live `system:<module>` grant
6
+ * (#383) — "the grant IS the switch". That sentence was true of the gate and had no
7
+ * lever: nothing could revoke the grant. This file is the lever, and it has to be more
8
+ * than a revoke, for two reasons found while building it:
9
+ *
10
+ * 1. **A reconcile seats grants.** Since #1659 a reconcile leaves a revoked grant revoked,
11
+ * but it still CREATES a missing one. A vertical whose next version declares a schedule
12
+ * with a new permission gets that grant seated live on every install — and a gate that
13
+ * reads "any live grant" is open again, on a scope an operator switched off.
14
+ * 2. **An explicit grant clears a tombstone.** `grantToSystem` is `INSERT OR REPLACE` by
15
+ * design (#1659's re-grant guarantee), so a stray grant of one permission would bring
16
+ * that tuple back — and with it, under the old gate, the whole module.
17
+ *
18
+ * So the OFF position is a tuple of its own: a live `system:<module>` / `switch:off` /
19
+ * `scope:<id>` marker. The gate is OFF while that marker is live, whatever grants are
20
+ * live beside it. **Restore is the lever; a grant is not.** The marker is an ordinary
21
+ * K-21 tuple: switching back on tombstones it, so the row stays as evidence of when the
22
+ * switch was last pulled. The permission walk reads only `role:` and `granted:` relations
23
+ * (`permission-eval.ts`), so the marker authorizes nothing and denies nothing by itself.
24
+ *
25
+ * Switching off ALSO tombstones every live `granted:` tuple the module holds on the scope.
26
+ * That is what makes it a kill switch rather than a schedule pause: anything that acts
27
+ * with the module's system authority — a resumable job run (#1577), a `getSystemScope`
28
+ * invoke — is denied by its own `ctx.check` while the switch is off.
29
+ *
30
+ * **And nothing may write a new one while it is off**, which is what makes that denial
31
+ * hold. The checker is deliberately NOT marker-aware; the switch is closed on the WRITE
32
+ * side instead, at the only two writers of a scope-level `system:` grant:
33
+ * - `grantToSystem` refuses while the module is switched off on that scope (the adapters
34
+ * ask `systemSwitchedOff` in the same unit as the write);
35
+ * - provisioning's seat (`seatScopeTuple`) seats nothing for a subject whose marker is
36
+ * live, so a reconcile cannot create a grant a newer version declares.
37
+ *
38
+ * **Restore returns exactly what OFF took, never more.** Each grant OFF tombstones gets a
39
+ * `switched:<permission>` record, live for as long as the switch holds it. ON restores
40
+ * only grants with a live record, then tombstones the records — so a grant that was
41
+ * revoked independently BEFORE the switch was pulled stays revoked through OFF and ON.
42
+ *
43
+ * Params and results are plain SQL over `_substrat_tuples`, run through `SwitchSql`,
44
+ * which each adapter implements over its own handle inside one transaction.
45
+ */
46
+ /** The relation of the OFF marker. Not `granted:` and not `role:`, so no check reads it. */
47
+ export const SYSTEM_SWITCH_OFF_RELATION = 'switch:off';
48
+ /** `switched:<permission>` — "the switch revoked this grant, and ON gives it back". */
49
+ const SWITCHED_PREFIX = 'switched:';
50
+ const subjectOf = (moduleId) => `system:${moduleId}`;
51
+ /**
52
+ * "Is this subject's OFF marker live?", as a SQL predicate over ONE bound parameter (the
53
+ * subject). The one spelling the gate, the grant refusal and the provisioning seat share,
54
+ * so the three cannot disagree about what "switched off" means.
55
+ */
56
+ export const SYSTEM_SWITCH_OFF_PREDICATE = `EXISTS (SELECT 1 FROM _substrat_tuples
57
+ WHERE subject = ? AND relation = '${SYSTEM_SWITCH_OFF_RELATION}' AND revoked_at IS NULL)`;
58
+ /**
59
+ * The gate, one statement: is the OFF marker live, and is any `granted:` tuple live?
60
+ * `substr` rather than `LIKE`: `LIKE` is case-insensitive in SQLite and a Durable
61
+ * Object caps its patterns (#1655); an exact prefix compare is neither.
62
+ */
63
+ export function systemScheduleState(db, moduleId, now) {
64
+ const subject = subjectOf(moduleId);
65
+ const row = db.all(`SELECT
66
+ ${SYSTEM_SWITCH_OFF_PREDICATE} AS off,
67
+ EXISTS (SELECT 1 FROM _substrat_tuples
68
+ WHERE subject = ? AND substr(relation, 1, 8) = 'granted:'
69
+ AND revoked_at IS NULL AND (expires_at IS NULL OR expires_at > ?)) AS granted`, subject, subject, now)[0];
70
+ if (Number(row?.off) === 1)
71
+ return 'off';
72
+ return Number(row?.granted) === 1 ? 'on' : 'ungranted';
73
+ }
74
+ /** Is this module switched off on the scope `db` is? What `grantToSystem` refuses on. */
75
+ export function systemSwitchedOff(db, moduleId) {
76
+ const row = db.all(`SELECT ${SYSTEM_SWITCH_OFF_PREDICATE} AS off`, subjectOf(moduleId))[0];
77
+ return Number(row?.off) === 1;
78
+ }
79
+ /** The refusal `grantToSystem` throws while the switch is off — one wording, both adapters. */
80
+ export function systemSwitchedOffMessage(moduleId, scopeId) {
81
+ return (`module '${moduleId}' is switched off on scope ${scopeId} (#1666) — restore it first ` +
82
+ `(restoreToSystem). A grant is not the lever: granting while it is off would hand the ` +
83
+ `module's system authority back to anything but its schedules.`);
84
+ }
85
+ /**
86
+ * Move one module's switch on one scope. Idempotent: a repeat changes nothing and says so.
87
+ *
88
+ * OFF tombstones every live `granted:` tuple the module holds at `scope:<id>`, records each
89
+ * as `switched:<permission>`, and makes the marker live. ON un-tombstones exactly the
90
+ * grants with a live `switched:` record, tombstones those records, and tombstones the
91
+ * marker. A repeated OFF re-asserts the whole position: a grant that became live meanwhile
92
+ * (only a raw write can do that now) is tombstoned and recorded too.
93
+ *
94
+ * `held: false`, with nothing written, when the scope holds neither a grant nor a marker
95
+ * for the module: the caller named something this scope never ran, and turning "nothing"
96
+ * off must not write a marker that silently disables a module the day it is installed.
97
+ *
98
+ * Run it inside one transaction; the reads and writes here are meant to be one unit.
99
+ */
100
+ export function switchSystemSchedules(db, input) {
101
+ const subject = subjectOf(input.moduleId);
102
+ const object = `scope:${input.scopeId}`;
103
+ const grants = db.all(`SELECT relation, revoked_at FROM _substrat_tuples
104
+ WHERE subject = ? AND object = ? AND substr(relation, 1, 8) = 'granted:'
105
+ ORDER BY relation`, subject, object);
106
+ const marker = db.all(`SELECT revoked_at FROM _substrat_tuples WHERE subject = ? AND relation = ? AND object = ?`, subject, SYSTEM_SWITCH_OFF_RELATION, object)[0];
107
+ if (grants.length === 0 && !marker)
108
+ return { held: false, changed: false, permissions: [] };
109
+ const permissionOf = (relation) => relation.slice('granted:'.length);
110
+ if (input.to === 'off') {
111
+ const live = grants.filter((g) => g.revoked_at === null).map((g) => permissionOf(g.relation));
112
+ for (const permission of live) {
113
+ db.run(`UPDATE _substrat_tuples SET revoked_at = ? WHERE subject = ? AND relation = ? AND object = ?`, input.at, subject, `granted:${permission}`, object);
114
+ // What ON may give back — and nothing else. INSERT OR REPLACE, so a record a past
115
+ // OFF/ON cycle left tombstoned is live again for this one.
116
+ db.run(`INSERT OR REPLACE INTO _substrat_tuples (subject, relation, object, expires_at, revoked_at)
117
+ VALUES (?, ?, ?, NULL, NULL)`, subject, `${SWITCHED_PREFIX}${permission}`, object);
118
+ }
119
+ const markerMoved = !marker || marker.revoked_at !== null;
120
+ if (markerMoved) {
121
+ db.run(`INSERT OR REPLACE INTO _substrat_tuples (subject, relation, object, expires_at, revoked_at)
122
+ VALUES (?, ?, ?, NULL, NULL)`, subject, SYSTEM_SWITCH_OFF_RELATION, object);
123
+ }
124
+ return { held: true, changed: markerMoved || live.length > 0, permissions: live };
125
+ }
126
+ const records = db.all(`SELECT relation FROM _substrat_tuples
127
+ WHERE subject = ? AND object = ? AND substr(relation, 1, 9) = ? AND revoked_at IS NULL
128
+ ORDER BY relation`, subject, object, SWITCHED_PREFIX);
129
+ const restored = [];
130
+ for (const { relation } of records) {
131
+ const permission = relation.slice(SWITCHED_PREFIX.length);
132
+ const revoked = grants.find((g) => g.relation === `granted:${permission}` && g.revoked_at !== null);
133
+ if (revoked) {
134
+ db.run(`UPDATE _substrat_tuples SET revoked_at = NULL WHERE subject = ? AND relation = ? AND object = ?`, subject, `granted:${permission}`, object);
135
+ restored.push(permission);
136
+ }
137
+ db.run(`UPDATE _substrat_tuples SET revoked_at = ? WHERE subject = ? AND relation = ? AND object = ?`, input.at, subject, relation, object);
138
+ }
139
+ const markerMoved = marker !== undefined && marker.revoked_at === null;
140
+ if (markerMoved) {
141
+ db.run(`UPDATE _substrat_tuples SET revoked_at = ? WHERE subject = ? AND relation = ? AND object = ?`, input.at, subject, SYSTEM_SWITCH_OFF_RELATION, object);
142
+ }
143
+ return { held: true, changed: markerMoved || restored.length > 0, permissions: restored };
144
+ }
145
+ //# sourceMappingURL=system-switch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"system-switch.js","sourceRoot":"","sources":["../src/system-switch.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"timeline.d.ts","sourceRoot":"","sources":["../src/timeline.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,SAAS,EAEd,KAAK,OAAO,EACZ,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,KAAK,WAAW,EAEhB,KAAK,gBAAgB,EACrB,KAAK,UAAU,EAOf,KAAK,QAAQ,EACb,KAAK,IAAI,EAET,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACtB,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,SAAS,EAAY,MAAM,iBAAiB,CAAC;AAE3D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AAEH,oFAAoF;AACpF,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;CACxC;AAmHD;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAC1B,GAAG,EAAE,cAAc,EACnB,MAAM,EAAE,SAAS,EACjB,IAAI,CAAC,EAAE,QAAQ,GACd,IAAI,CAAC,aAAa,CAAC,CAKrB;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,WAAW,CACzB,GAAG,EAAE,cAAc,EACnB,MAAM,EAAE,SAAS,EACjB,IAAI,CAAC,EAAE,QAAQ,GACd,IAAI,CAAC,YAAY,CAAC,CAKpB;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,cAAc,EACnB,OAAO,EAAE,OAAO,EAChB,QAAQ,SAAK,GACZ,UAAU,CAuCZ;AAwCD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,cAAc,EACnB,OAAO,EAAE,OAAO,EAChB,QAAQ,SAAK,GACZ,WAAW,CAoFb;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,cAAc,EACnB,YAAY,EAAE,MAAM,EACpB,KAAK,SAAM,GACV,gBAAgB,CAOlB;AAqBD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,QAAQ,EAAE,OAAO,GAAG,QAAQ,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,CA6ChH;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,eAAe,GAAG,gBAAgB,CAqEzF"}
1
+ {"version":3,"file":"timeline.d.ts","sourceRoot":"","sources":["../src/timeline.ts"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,SAAS,EACd,KAAK,OAAO,EACZ,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,KAAK,WAAW,EAEhB,KAAK,gBAAgB,EACrB,KAAK,UAAU,EAMf,KAAK,QAAQ,EACb,KAAK,IAAI,EACT,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACtB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,KAAK,EAAE,SAAS,EAAY,MAAM,iBAAiB,CAAC;AAE3D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AAEH,oFAAoF;AACpF,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;CACxC;AAmID;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAC1B,GAAG,EAAE,cAAc,EACnB,MAAM,EAAE,SAAS,EACjB,IAAI,CAAC,EAAE,QAAQ,GACd,IAAI,CAAC,aAAa,CAAC,CAKrB;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,WAAW,CACzB,GAAG,EAAE,cAAc,EACnB,MAAM,EAAE,SAAS,EACjB,IAAI,CAAC,EAAE,QAAQ,GACd,IAAI,CAAC,YAAY,CAAC,CAKpB;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,cAAc,EACnB,OAAO,EAAE,OAAO,EAChB,QAAQ,SAAK,GACZ,UAAU,CA+CZ;AAkDD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,cAAc,EACnB,OAAO,EAAE,OAAO,EAChB,QAAQ,SAAK,GACZ,WAAW,CAoFb;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,cAAc,EACnB,YAAY,EAAE,MAAM,EACpB,KAAK,SAAM,GACV,gBAAgB,CAOlB;AA+DD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,QAAQ,EAAE,OAAO,GAAG,QAAQ,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,CA4BhH;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,eAAe,GAAG,gBAAgB,CAqEzF"}