@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.
- package/dist/capability.d.ts +220 -0
- package/dist/capability.d.ts.map +1 -0
- package/dist/capability.js +537 -0
- package/dist/capability.js.map +1 -0
- package/dist/check-key.d.ts +30 -0
- package/dist/check-key.d.ts.map +1 -0
- package/dist/check-key.js +37 -0
- package/dist/check-key.js.map +1 -0
- package/dist/denial-query.d.ts +32 -1
- package/dist/denial-query.d.ts.map +1 -1
- package/dist/denial-query.js +66 -28
- package/dist/denial-query.js.map +1 -1
- package/dist/index.d.ts +19 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -3
- package/dist/index.js.map +1 -1
- package/dist/outbox-event.d.ts +131 -0
- package/dist/outbox-event.d.ts.map +1 -0
- package/dist/outbox-event.js +185 -0
- package/dist/outbox-event.js.map +1 -0
- package/dist/permission-checker.d.ts +8 -1
- package/dist/permission-checker.d.ts.map +1 -1
- package/dist/permission-checker.js +18 -0
- package/dist/permission-checker.js.map +1 -1
- package/dist/permission-eval.d.ts +8 -0
- package/dist/permission-eval.d.ts.map +1 -1
- package/dist/permission-eval.js +164 -91
- package/dist/permission-eval.js.map +1 -1
- package/dist/platform-request-query.d.ts +58 -1
- package/dist/platform-request-query.d.ts.map +1 -1
- package/dist/platform-request-query.js +61 -1
- package/dist/platform-request-query.js.map +1 -1
- package/dist/platform-sweep.d.ts +123 -8
- package/dist/platform-sweep.d.ts.map +1 -1
- package/dist/platform-sweep.js +158 -34
- package/dist/platform-sweep.js.map +1 -1
- package/dist/row-decode.d.ts +107 -0
- package/dist/row-decode.d.ts.map +1 -0
- package/dist/row-decode.js +93 -0
- package/dist/row-decode.js.map +1 -0
- package/dist/scope-host.d.ts +211 -9
- package/dist/scope-host.d.ts.map +1 -1
- package/dist/scope-host.js +33 -0
- package/dist/scope-host.js.map +1 -1
- package/dist/scope-tuple-seat.d.ts +72 -0
- package/dist/scope-tuple-seat.d.ts.map +1 -0
- package/dist/scope-tuple-seat.js +93 -0
- package/dist/scope-tuple-seat.js.map +1 -0
- package/dist/subject-redaction.d.ts +160 -0
- package/dist/subject-redaction.d.ts.map +1 -0
- package/dist/subject-redaction.js +210 -0
- package/dist/subject-redaction.js.map +1 -0
- package/dist/system-switch.d.ts +108 -0
- package/dist/system-switch.d.ts.map +1 -0
- package/dist/system-switch.js +145 -0
- package/dist/system-switch.js.map +1 -0
- package/dist/timeline.d.ts.map +1 -1
- package/dist/timeline.js +109 -57
- package/dist/timeline.js.map +1 -1
- 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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"}
|
package/dist/timeline.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"timeline.d.ts","sourceRoot":"","sources":["../src/timeline.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
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"}
|