@substrat-run/kernel 0.117.0 → 0.119.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 +22 -1
- package/dist/capability.d.ts.map +1 -1
- package/dist/capability.js +20 -0
- package/dist/capability.js.map +1 -1
- package/dist/connector-calls.d.ts +205 -0
- package/dist/connector-calls.d.ts.map +1 -0
- package/dist/connector-calls.js +192 -0
- package/dist/connector-calls.js.map +1 -0
- package/dist/idempotency.d.ts +7 -1
- package/dist/idempotency.d.ts.map +1 -1
- package/dist/idempotency.js +8 -2
- package/dist/idempotency.js.map +1 -1
- package/dist/index.d.ts +14 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -5
- package/dist/index.js.map +1 -1
- package/dist/job-run.d.ts +28 -0
- package/dist/job-run.d.ts.map +1 -1
- package/dist/job-run.js +37 -0
- package/dist/job-run.js.map +1 -1
- package/dist/peer.d.ts +132 -0
- package/dist/peer.d.ts.map +1 -0
- package/dist/peer.js +177 -0
- package/dist/peer.js.map +1 -0
- package/dist/permission-checker.d.ts +1 -1
- package/dist/permission-checker.d.ts.map +1 -1
- package/dist/permission-checker.js +4 -1
- package/dist/permission-checker.js.map +1 -1
- package/dist/platform-sweep.d.ts +116 -1
- package/dist/platform-sweep.d.ts.map +1 -1
- package/dist/platform-sweep.js +209 -0
- package/dist/platform-sweep.js.map +1 -1
- package/dist/scope-host.d.ts +228 -7
- package/dist/scope-host.d.ts.map +1 -1
- package/dist/scope-host.js.map +1 -1
- package/dist/subject-redaction.d.ts +120 -7
- package/dist/subject-redaction.d.ts.map +1 -1
- package/dist/subject-redaction.js +155 -3
- package/dist/subject-redaction.js.map +1 -1
- package/dist/system-switch.d.ts +38 -0
- package/dist/system-switch.d.ts.map +1 -1
- package/dist/system-switch.js +50 -3
- package/dist/system-switch.js.map +1 -1
- package/dist/timeline.d.ts.map +1 -1
- package/dist/timeline.js +30 -3
- package/dist/timeline.js.map +1 -1
- package/dist/vertical-events.d.ts +162 -0
- package/dist/vertical-events.d.ts.map +1 -0
- package/dist/vertical-events.js +348 -0
- package/dist/vertical-events.js.map +1 -0
- package/package.json +2 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The intent journal's half of a subject erasure (#1600)
|
|
2
|
+
* The intent journal's half of a subject erasure (#1600) — and, by the same link, the
|
|
3
|
+
* job-run tables' (#1632, at the end of this file).
|
|
3
4
|
*
|
|
4
5
|
* `shredSubject` holds K-37's Tier-1 line — *the payload goes, the envelope stays* —
|
|
5
6
|
* and for a year that line reached exactly one table, `_substrat_outbox`. It is not the
|
|
@@ -98,9 +99,10 @@ export interface PlatformRequestRedactionCandidate {
|
|
|
98
99
|
/**
|
|
99
100
|
* What one spine redaction moved, per table.
|
|
100
101
|
*
|
|
101
|
-
*
|
|
102
|
-
* said about a person
|
|
103
|
-
* different facts, and a DSAR answer that folded them
|
|
102
|
+
* Separate numbers rather than one sum, because the receipt reports them apart: how much
|
|
103
|
+
* was said about a person, how many copies of it were queued for a third party, and how
|
|
104
|
+
* many long-running jobs held one are different facts, and a DSAR answer that folded them
|
|
105
|
+
* together could not say any of them.
|
|
104
106
|
*
|
|
105
107
|
* It lives here rather than in an adapter because it is what the Durable-Object adapter
|
|
106
108
|
* hands back across its RPC, and the coordinator that reads it deliberately does not
|
|
@@ -110,7 +112,20 @@ export interface PlatformRequestRedactionCandidate {
|
|
|
110
112
|
export interface SubjectRedactionCounts {
|
|
111
113
|
events: number;
|
|
112
114
|
intents: number;
|
|
115
|
+
/**
|
|
116
|
+
* Job runs (#1632) whose record held a copy of a redacted event — in the run row, or in
|
|
117
|
+
* a step of its ledger. Counted per RUN, once, however many of its columns and steps
|
|
118
|
+
* were rewritten: "how many long-running jobs had this person's data" is the fact, and
|
|
119
|
+
* a count of rewritten cells would read larger than anything a DSAR answer could say.
|
|
120
|
+
*/
|
|
121
|
+
jobRuns: number;
|
|
113
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* What a ScopeDO from after #1600 and before #1632 answers — the job-run count absent,
|
|
125
|
+
* because that host never looked at the job tables. The coordinator refuses it rather than
|
|
126
|
+
* reading the absence as zero (see `redactSubject`'s callers).
|
|
127
|
+
*/
|
|
128
|
+
export type LegacySubjectRedactionCounts = Omit<SubjectRedactionCounts, 'jobRuns'>;
|
|
114
129
|
/**
|
|
115
130
|
* The candidate read: every intent whose stored payload TEXT contains the subject id.
|
|
116
131
|
*
|
|
@@ -138,11 +153,22 @@ export declare function platformRequestRedactionQuery(subjectId: string): {
|
|
|
138
153
|
* branched on. `settled_at` is COALESCEd so a row that already settled keeps the instant
|
|
139
154
|
* it settled at; only a cancelled pending row is stamped now.
|
|
140
155
|
*
|
|
141
|
-
*
|
|
156
|
+
* **`result` and `last_failure` go too (#1632).** The first cut spared `result` as
|
|
157
|
+
* envelope, reasoning from a routed dispatch whose result is `{ eventId }`. But `result` is
|
|
158
|
+
* whatever the drain's handler returned, and a connector handler's return is the
|
|
159
|
+
* provider's answer about delivering THIS person's data — `last_error`'s reason, in the
|
|
160
|
+
* column beside it. So a result that exists becomes the same tombstone the payload does;
|
|
161
|
+
* a NULL result stays NULL, because a tombstone there would claim an answer nobody gave.
|
|
162
|
+
* `last_failure` carries no free text (`{origin, code, permission}`), and is nulled for a
|
|
163
|
+
* different reason: it is the ATTRIBUTION of `last_error`, and once `last_error` is our
|
|
164
|
+
* note, a kept `origin: 'provider'` would caption the platform's sentence as the
|
|
165
|
+
* provider's words. NULL is the contract's "unrecorded", which is now the truth.
|
|
166
|
+
*
|
|
167
|
+
* Params, in order: payload, cancelled-note, redacted-note, result-tombstone, at, id.
|
|
142
168
|
*/
|
|
143
|
-
export declare const PLATFORM_REQUEST_REDACTION_SQL = "UPDATE _substrat_platform_requests\n SET payload = ?,\n last_error = CASE WHEN status = 'pending' THEN ? ELSE ? END,\n settled_at = COALESCE(settled_at, ?),\n status = CASE WHEN status = 'pending' THEN 'failed' ELSE status END\n WHERE id = ?";
|
|
169
|
+
export declare const PLATFORM_REQUEST_REDACTION_SQL = "UPDATE _substrat_platform_requests\n SET payload = ?,\n last_error = CASE WHEN status = 'pending' THEN ? ELSE ? END,\n last_failure = NULL,\n result = CASE WHEN result IS NULL THEN NULL ELSE ? END,\n settled_at = COALESCE(settled_at, ?),\n status = CASE WHEN status = 'pending' THEN 'failed' ELSE status END\n WHERE id = ?";
|
|
144
170
|
/** The bound parameters for `PLATFORM_REQUEST_REDACTION_SQL`, in its declared order. */
|
|
145
|
-
export declare function platformRequestRedactionParams(id: string, subjectId: string, at: string): [string, string, string, string, string];
|
|
171
|
+
export declare function platformRequestRedactionParams(id: string, subjectId: string, at: string): [string, string, string, string, string, string];
|
|
146
172
|
/**
|
|
147
173
|
* Does this stored payload carry a copy of an event the erasure redacts?
|
|
148
174
|
*
|
|
@@ -157,4 +183,91 @@ export declare function platformRequestRedactionParams(id: string, subjectId: st
|
|
|
157
183
|
* a meaning for.
|
|
158
184
|
*/
|
|
159
185
|
export declare function intentPayloadCarriesSubject(payloadText: string, subjectId: string): boolean;
|
|
186
|
+
/**
|
|
187
|
+
* The note a redaction leaves on a delivery of a redacted event that had failed.
|
|
188
|
+
*
|
|
189
|
+
* `_substrat_deliveries.error` is a consumer's or executor's own sentence about handling
|
|
190
|
+
* THIS event — a throw that quoted the payload it choked on reads exactly like the
|
|
191
|
+
* provider's `last_error` on an intent. Unlike every other copy in this file it needs no
|
|
192
|
+
* walk: the row is keyed by `event_id`, so the outbox's own predicate names it exactly.
|
|
193
|
+
*/
|
|
194
|
+
export declare const REDACTED_DELIVERY_NOTE = "redacted by subject erasure (#37) \u2014 what this delivery reported is gone; that it failed, and when, is not";
|
|
195
|
+
/**
|
|
196
|
+
* One statement: every failed delivery of an event the erasure redacts. Only a NON-NULL
|
|
197
|
+
* error is rewritten, because a non-null error is what MEANS dead-lettered or retrying
|
|
198
|
+
* (`deliveryState`); writing the note onto a delivered row would turn it into a dead one.
|
|
199
|
+
* Not counted on the receipt: it is text about an event `eventsRedacted` already counts,
|
|
200
|
+
* not another copy of it. Idempotent — a note is not rewritten with itself.
|
|
201
|
+
*
|
|
202
|
+
* Params: note, note, subject id.
|
|
203
|
+
*/
|
|
204
|
+
export declare const DELIVERY_ERROR_REDACTION_SQL = "UPDATE _substrat_deliveries\n SET error = ?\n WHERE error IS NOT NULL AND error != ?\n AND event_id IN (SELECT id FROM _substrat_outbox WHERE subject_id = ? AND pii_class != 'none')";
|
|
205
|
+
/**
|
|
206
|
+
* The note a redaction leaves on a job run, or on a step of its ledger, that had finished.
|
|
207
|
+
*
|
|
208
|
+
* Same reasoning as `REDACTED_INTENT_NOTE`: `last_error` is a sentence an external system
|
|
209
|
+
* wrote about work done on this person's data, so it is content, not envelope.
|
|
210
|
+
*/
|
|
211
|
+
export declare const REDACTED_JOB_NOTE = "redacted by subject erasure (#37) \u2014 what this job run held about the subject is gone; that it ran, and when, is not";
|
|
212
|
+
/**
|
|
213
|
+
* The note a redaction leaves on a job run that was still `running`.
|
|
214
|
+
*
|
|
215
|
+
* A running run is redacted AND settled `failed` in the same statement, for the pending
|
|
216
|
+
* intent's reason and one of its own. A run resumes from its payload, its cursor and the
|
|
217
|
+
* memo of its completed steps; once any of those is a tombstone, the next pass would be
|
|
218
|
+
* handed the tombstone AS its input — a step's memo is returned to the handler without
|
|
219
|
+
* running anything — and walk on from nonsense. `failed` is what the run now is.
|
|
220
|
+
*/
|
|
221
|
+
export declare const CANCELLED_JOB_NOTE = "stopped by subject erasure (#37) \u2014 this run held a copy of an erased subject's event, so it cannot resume from it";
|
|
222
|
+
/**
|
|
223
|
+
* How the job-run half talks to a scope database — one shape both adapters can satisfy
|
|
224
|
+
* in a line (`db.prepare(sql)` / `this.sql.exec(sql, …)`), so the reads, the predicate
|
|
225
|
+
* and the writes below are ONE implementation rather than two ports of it.
|
|
226
|
+
*/
|
|
227
|
+
export type RedactionSql = (sql: string, params: readonly (string | number | null)[]) => unknown[];
|
|
228
|
+
/**
|
|
229
|
+
* The job-run half of an erasure (#1632), shared by both adapters.
|
|
230
|
+
*
|
|
231
|
+
* **What decides membership is #1600's predicate, and nothing else.** A job run carries no
|
|
232
|
+
* `subject_id`: its payload is "ids and configuration", its cursor is opaque, and a step's
|
|
233
|
+
* result is whatever a HOST handler got back from an external system. The one link the
|
|
234
|
+
* kernel can read reliably is the one the intent journal uses — a spine envelope embedded
|
|
235
|
+
* at any depth, carrying this subject and a `piiClass` other than `none` — so a job-run
|
|
236
|
+
* copy is redacted exactly when the erasure redacts the event it copies. The candidate
|
|
237
|
+
* reads use `instr` over the text, as the intent read does; `intentPayloadCarriesSubject`
|
|
238
|
+
* decides.
|
|
239
|
+
*
|
|
240
|
+
* **What it therefore does NOT reach** is external output naming the person with no
|
|
241
|
+
* classified envelope around it — a step that returns a provider's contact record, a
|
|
242
|
+
* `last_error` quoting a name. There is nothing in such a row that says whose it is, and a
|
|
243
|
+
* substring match on the id would both erase on coincidence and miss the name itself.
|
|
244
|
+
* kernel-design.md §13.1 limit 8 states it; a declared subject on the run is the open
|
|
245
|
+
* design that would close it (#1632).
|
|
246
|
+
*
|
|
247
|
+
* Per column, for a matching run: `payload` and `cursor` become the tombstone where THEY
|
|
248
|
+
* match (the payload is `NOT NULL`, the cursor may be `NULL` and stays so), `last_error`
|
|
249
|
+
* becomes the note, and a `running` run is settled `failed`. For a matching step: `result`
|
|
250
|
+
* becomes the tombstone, `last_error` the note — and its RUN is settled too, because the
|
|
251
|
+
* memo would otherwise hand the tombstone to the next pass. Idempotent: a tombstone no
|
|
252
|
+
* longer matches, so a second erasure finds nothing.
|
|
253
|
+
*
|
|
254
|
+
* Returns the number of distinct runs touched.
|
|
255
|
+
*/
|
|
256
|
+
export declare function redactSubjectJobRuns(sql: RedactionSql, subjectId: string, at: string): number;
|
|
257
|
+
/**
|
|
258
|
+
* One matched step: its result becomes the tombstone, its error the note.
|
|
259
|
+
*
|
|
260
|
+
* Params: tombstone, note, run_id, step.
|
|
261
|
+
*/
|
|
262
|
+
export declare const JOB_STEP_REDACTION_SQL = "UPDATE _substrat_job_steps\n SET result = ?, last_error = ?\n WHERE run_id = ? AND step = ?";
|
|
263
|
+
/**
|
|
264
|
+
* One matched run. Every `CASE WHEN status = 'running'` reads the status as it was before
|
|
265
|
+
* this statement — SQLite evaluates each SET against the original row — which is what
|
|
266
|
+
* lets one statement pick the note and settle the status it branched on. `ended_at` is
|
|
267
|
+
* COALESCEd so a run that had already ended keeps the instant it ended.
|
|
268
|
+
*
|
|
269
|
+
* Params: payload-hit (0/1), tombstone, cursor-hit (0/1), tombstone, cancelled-note,
|
|
270
|
+
* redacted-note, at (ended_at), at (updated_at), id.
|
|
271
|
+
*/
|
|
272
|
+
export declare const JOB_RUN_REDACTION_SQL = "UPDATE _substrat_job_runs\n SET payload = CASE WHEN ? = 1 THEN ? ELSE payload END,\n cursor = CASE WHEN ? = 1 THEN ? ELSE cursor END,\n last_error = CASE WHEN status = 'running' THEN ? ELSE ? END,\n next_attempt_at = CASE WHEN status = 'running' THEN NULL ELSE next_attempt_at END,\n ended_at = COALESCE(ended_at, ?),\n updated_at = ?,\n status = CASE WHEN status = 'running' THEN 'failed' ELSE status END\n WHERE id = ?";
|
|
160
273
|
//# sourceMappingURL=subject-redaction.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"subject-redaction.d.ts","sourceRoot":"","sources":["../src/subject-redaction.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"subject-redaction.d.ts","sourceRoot":"","sources":["../src/subject-redaction.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,sBAAsB,CAAC;AAE1D;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAI3E;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,oBAAoB,+GACwE,CAAC;AAE1G;;;;;;;;;GASG;AACH,eAAO,MAAM,qBAAqB,6HACqF,CAAC;AAExH,mDAAmD;AACnD,MAAM,WAAW,iCAAiC;IAChD,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,MAAM,MAAM,4BAA4B,GAAG,IAAI,CAAC,sBAAsB,EAAE,SAAS,CAAC,CAAC;AAEnF;;;;;;;;;;;;GAYG;AACH,wBAAgB,6BAA6B,CAAC,SAAS,EAAE,MAAM,GAAG;IAChE,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAOA;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,8BAA8B,qXAO3B,CAAC;AAEjB,wFAAwF;AACxF,wBAAgB,8BAA8B,CAC5C,EAAE,EAAE,MAAM,EACV,SAAS,EAAE,MAAM,EACjB,EAAE,EAAE,MAAM,GACT,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAGlD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,2BAA2B,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAS3F;AAmDD;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,mHAC0E,CAAC;AAE9G;;;;;;;;GAQG;AACH,eAAO,MAAM,4BAA4B,oMAG2D,CAAC;AAIrG;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,6HACyF,CAAC;AAExH;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,2HACuF,CAAC;AAEvH;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC,EAAE,KAAK,OAAO,EAAE,CAAC;AAEnG;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CA8C7F;AAED;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,sGAEF,CAAC;AAElC;;;;;;;;GAQG;AACH,eAAO,MAAM,qBAAqB,+dAQlB,CAAC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The intent journal's half of a subject erasure (#1600)
|
|
2
|
+
* The intent journal's half of a subject erasure (#1600) — and, by the same link, the
|
|
3
|
+
* job-run tables' (#1632, at the end of this file).
|
|
3
4
|
*
|
|
4
5
|
* `shredSubject` holds K-37's Tier-1 line — *the payload goes, the envelope stays* —
|
|
5
6
|
* and for a year that line reached exactly one table, `_substrat_outbox`. It is not the
|
|
@@ -125,17 +126,31 @@ export function platformRequestRedactionQuery(subjectId) {
|
|
|
125
126
|
* branched on. `settled_at` is COALESCEd so a row that already settled keeps the instant
|
|
126
127
|
* it settled at; only a cancelled pending row is stamped now.
|
|
127
128
|
*
|
|
128
|
-
*
|
|
129
|
+
* **`result` and `last_failure` go too (#1632).** The first cut spared `result` as
|
|
130
|
+
* envelope, reasoning from a routed dispatch whose result is `{ eventId }`. But `result` is
|
|
131
|
+
* whatever the drain's handler returned, and a connector handler's return is the
|
|
132
|
+
* provider's answer about delivering THIS person's data — `last_error`'s reason, in the
|
|
133
|
+
* column beside it. So a result that exists becomes the same tombstone the payload does;
|
|
134
|
+
* a NULL result stays NULL, because a tombstone there would claim an answer nobody gave.
|
|
135
|
+
* `last_failure` carries no free text (`{origin, code, permission}`), and is nulled for a
|
|
136
|
+
* different reason: it is the ATTRIBUTION of `last_error`, and once `last_error` is our
|
|
137
|
+
* note, a kept `origin: 'provider'` would caption the platform's sentence as the
|
|
138
|
+
* provider's words. NULL is the contract's "unrecorded", which is now the truth.
|
|
139
|
+
*
|
|
140
|
+
* Params, in order: payload, cancelled-note, redacted-note, result-tombstone, at, id.
|
|
129
141
|
*/
|
|
130
142
|
export const PLATFORM_REQUEST_REDACTION_SQL = `UPDATE _substrat_platform_requests
|
|
131
143
|
SET payload = ?,
|
|
132
144
|
last_error = CASE WHEN status = 'pending' THEN ? ELSE ? END,
|
|
145
|
+
last_failure = NULL,
|
|
146
|
+
result = CASE WHEN result IS NULL THEN NULL ELSE ? END,
|
|
133
147
|
settled_at = COALESCE(settled_at, ?),
|
|
134
148
|
status = CASE WHEN status = 'pending' THEN 'failed' ELSE status END
|
|
135
149
|
WHERE id = ?`;
|
|
136
150
|
/** The bound parameters for `PLATFORM_REQUEST_REDACTION_SQL`, in its declared order. */
|
|
137
151
|
export function platformRequestRedactionParams(id, subjectId, at) {
|
|
138
|
-
|
|
152
|
+
const tombstone = redactedIntentPayload(subjectId, at);
|
|
153
|
+
return [tombstone, CANCELLED_INTENT_NOTE, REDACTED_INTENT_NOTE, tombstone, at, id];
|
|
139
154
|
}
|
|
140
155
|
/**
|
|
141
156
|
* Does this stored payload carry a copy of an event the erasure redacts?
|
|
@@ -207,4 +222,141 @@ function carriesSubject(node, subjectId) {
|
|
|
207
222
|
}
|
|
208
223
|
return Object.values(obj).some((child) => carriesSubject(child, subjectId));
|
|
209
224
|
}
|
|
225
|
+
// -- the delivery journal's error text (#1632) ---------------------------------------
|
|
226
|
+
/**
|
|
227
|
+
* The note a redaction leaves on a delivery of a redacted event that had failed.
|
|
228
|
+
*
|
|
229
|
+
* `_substrat_deliveries.error` is a consumer's or executor's own sentence about handling
|
|
230
|
+
* THIS event — a throw that quoted the payload it choked on reads exactly like the
|
|
231
|
+
* provider's `last_error` on an intent. Unlike every other copy in this file it needs no
|
|
232
|
+
* walk: the row is keyed by `event_id`, so the outbox's own predicate names it exactly.
|
|
233
|
+
*/
|
|
234
|
+
export const REDACTED_DELIVERY_NOTE = 'redacted by subject erasure (#37) — what this delivery reported is gone; that it failed, and when, is not';
|
|
235
|
+
/**
|
|
236
|
+
* One statement: every failed delivery of an event the erasure redacts. Only a NON-NULL
|
|
237
|
+
* error is rewritten, because a non-null error is what MEANS dead-lettered or retrying
|
|
238
|
+
* (`deliveryState`); writing the note onto a delivered row would turn it into a dead one.
|
|
239
|
+
* Not counted on the receipt: it is text about an event `eventsRedacted` already counts,
|
|
240
|
+
* not another copy of it. Idempotent — a note is not rewritten with itself.
|
|
241
|
+
*
|
|
242
|
+
* Params: note, note, subject id.
|
|
243
|
+
*/
|
|
244
|
+
export const DELIVERY_ERROR_REDACTION_SQL = `UPDATE _substrat_deliveries
|
|
245
|
+
SET error = ?
|
|
246
|
+
WHERE error IS NOT NULL AND error != ?
|
|
247
|
+
AND event_id IN (SELECT id FROM _substrat_outbox WHERE subject_id = ? AND pii_class != 'none')`;
|
|
248
|
+
// -- the job-run tables (#1632) ------------------------------------------------------
|
|
249
|
+
/**
|
|
250
|
+
* The note a redaction leaves on a job run, or on a step of its ledger, that had finished.
|
|
251
|
+
*
|
|
252
|
+
* Same reasoning as `REDACTED_INTENT_NOTE`: `last_error` is a sentence an external system
|
|
253
|
+
* wrote about work done on this person's data, so it is content, not envelope.
|
|
254
|
+
*/
|
|
255
|
+
export const REDACTED_JOB_NOTE = 'redacted by subject erasure (#37) — what this job run held about the subject is gone; that it ran, and when, is not';
|
|
256
|
+
/**
|
|
257
|
+
* The note a redaction leaves on a job run that was still `running`.
|
|
258
|
+
*
|
|
259
|
+
* A running run is redacted AND settled `failed` in the same statement, for the pending
|
|
260
|
+
* intent's reason and one of its own. A run resumes from its payload, its cursor and the
|
|
261
|
+
* memo of its completed steps; once any of those is a tombstone, the next pass would be
|
|
262
|
+
* handed the tombstone AS its input — a step's memo is returned to the handler without
|
|
263
|
+
* running anything — and walk on from nonsense. `failed` is what the run now is.
|
|
264
|
+
*/
|
|
265
|
+
export const CANCELLED_JOB_NOTE = 'stopped by subject erasure (#37) — this run held a copy of an erased subject\'s event, so it cannot resume from it';
|
|
266
|
+
/**
|
|
267
|
+
* The job-run half of an erasure (#1632), shared by both adapters.
|
|
268
|
+
*
|
|
269
|
+
* **What decides membership is #1600's predicate, and nothing else.** A job run carries no
|
|
270
|
+
* `subject_id`: its payload is "ids and configuration", its cursor is opaque, and a step's
|
|
271
|
+
* result is whatever a HOST handler got back from an external system. The one link the
|
|
272
|
+
* kernel can read reliably is the one the intent journal uses — a spine envelope embedded
|
|
273
|
+
* at any depth, carrying this subject and a `piiClass` other than `none` — so a job-run
|
|
274
|
+
* copy is redacted exactly when the erasure redacts the event it copies. The candidate
|
|
275
|
+
* reads use `instr` over the text, as the intent read does; `intentPayloadCarriesSubject`
|
|
276
|
+
* decides.
|
|
277
|
+
*
|
|
278
|
+
* **What it therefore does NOT reach** is external output naming the person with no
|
|
279
|
+
* classified envelope around it — a step that returns a provider's contact record, a
|
|
280
|
+
* `last_error` quoting a name. There is nothing in such a row that says whose it is, and a
|
|
281
|
+
* substring match on the id would both erase on coincidence and miss the name itself.
|
|
282
|
+
* kernel-design.md §13.1 limit 8 states it; a declared subject on the run is the open
|
|
283
|
+
* design that would close it (#1632).
|
|
284
|
+
*
|
|
285
|
+
* Per column, for a matching run: `payload` and `cursor` become the tombstone where THEY
|
|
286
|
+
* match (the payload is `NOT NULL`, the cursor may be `NULL` and stays so), `last_error`
|
|
287
|
+
* becomes the note, and a `running` run is settled `failed`. For a matching step: `result`
|
|
288
|
+
* becomes the tombstone, `last_error` the note — and its RUN is settled too, because the
|
|
289
|
+
* memo would otherwise hand the tombstone to the next pass. Idempotent: a tombstone no
|
|
290
|
+
* longer matches, so a second erasure finds nothing.
|
|
291
|
+
*
|
|
292
|
+
* Returns the number of distinct runs touched.
|
|
293
|
+
*/
|
|
294
|
+
export function redactSubjectJobRuns(sql, subjectId, at) {
|
|
295
|
+
// The needle, spelled as `platformRequestRedactionQuery` spells it.
|
|
296
|
+
const needle = JSON.stringify(subjectId).slice(1, -1);
|
|
297
|
+
const tombstone = redactedIntentPayload(subjectId, at);
|
|
298
|
+
const runs = new Set();
|
|
299
|
+
const steps = sql('SELECT run_id, step, result FROM _substrat_job_steps WHERE instr(result, ?) > 0', [needle]);
|
|
300
|
+
for (const s of steps) {
|
|
301
|
+
if (!intentPayloadCarriesSubject(s.result, subjectId))
|
|
302
|
+
continue;
|
|
303
|
+
sql(JOB_STEP_REDACTION_SQL, [tombstone, REDACTED_JOB_NOTE, s.run_id, s.step]);
|
|
304
|
+
runs.add(s.run_id);
|
|
305
|
+
}
|
|
306
|
+
const candidates = sql(`SELECT id, payload, cursor FROM _substrat_job_runs
|
|
307
|
+
WHERE instr(payload, ?) > 0 OR instr(cursor, ?) > 0`, [needle, needle]);
|
|
308
|
+
const hits = new Map();
|
|
309
|
+
for (const r of candidates) {
|
|
310
|
+
const payload = intentPayloadCarriesSubject(r.payload, subjectId);
|
|
311
|
+
const cursor = r.cursor !== null && intentPayloadCarriesSubject(r.cursor, subjectId);
|
|
312
|
+
if (payload || cursor)
|
|
313
|
+
hits.set(r.id, { payload, cursor });
|
|
314
|
+
}
|
|
315
|
+
// A run reached only through a step is still rewritten: its note and its status are
|
|
316
|
+
// what stop the tombstoned memo being replayed.
|
|
317
|
+
for (const id of runs)
|
|
318
|
+
if (!hits.has(id))
|
|
319
|
+
hits.set(id, { payload: false, cursor: false });
|
|
320
|
+
for (const [id, hit] of hits) {
|
|
321
|
+
sql(JOB_RUN_REDACTION_SQL, [
|
|
322
|
+
hit.payload ? 1 : 0,
|
|
323
|
+
tombstone,
|
|
324
|
+
hit.cursor ? 1 : 0,
|
|
325
|
+
tombstone,
|
|
326
|
+
CANCELLED_JOB_NOTE,
|
|
327
|
+
REDACTED_JOB_NOTE,
|
|
328
|
+
at,
|
|
329
|
+
at,
|
|
330
|
+
id,
|
|
331
|
+
]);
|
|
332
|
+
runs.add(id);
|
|
333
|
+
}
|
|
334
|
+
return runs.size;
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* One matched step: its result becomes the tombstone, its error the note.
|
|
338
|
+
*
|
|
339
|
+
* Params: tombstone, note, run_id, step.
|
|
340
|
+
*/
|
|
341
|
+
export const JOB_STEP_REDACTION_SQL = `UPDATE _substrat_job_steps
|
|
342
|
+
SET result = ?, last_error = ?
|
|
343
|
+
WHERE run_id = ? AND step = ?`;
|
|
344
|
+
/**
|
|
345
|
+
* One matched run. Every `CASE WHEN status = 'running'` reads the status as it was before
|
|
346
|
+
* this statement — SQLite evaluates each SET against the original row — which is what
|
|
347
|
+
* lets one statement pick the note and settle the status it branched on. `ended_at` is
|
|
348
|
+
* COALESCEd so a run that had already ended keeps the instant it ended.
|
|
349
|
+
*
|
|
350
|
+
* Params: payload-hit (0/1), tombstone, cursor-hit (0/1), tombstone, cancelled-note,
|
|
351
|
+
* redacted-note, at (ended_at), at (updated_at), id.
|
|
352
|
+
*/
|
|
353
|
+
export const JOB_RUN_REDACTION_SQL = `UPDATE _substrat_job_runs
|
|
354
|
+
SET payload = CASE WHEN ? = 1 THEN ? ELSE payload END,
|
|
355
|
+
cursor = CASE WHEN ? = 1 THEN ? ELSE cursor END,
|
|
356
|
+
last_error = CASE WHEN status = 'running' THEN ? ELSE ? END,
|
|
357
|
+
next_attempt_at = CASE WHEN status = 'running' THEN NULL ELSE next_attempt_at END,
|
|
358
|
+
ended_at = COALESCE(ended_at, ?),
|
|
359
|
+
updated_at = ?,
|
|
360
|
+
status = CASE WHEN status = 'running' THEN 'failed' ELSE status END
|
|
361
|
+
WHERE id = ?`;
|
|
210
362
|
//# sourceMappingURL=subject-redaction.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"subject-redaction.js","sourceRoot":"","sources":["../src/subject-redaction.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"subject-redaction.js","sourceRoot":"","sources":["../src/subject-redaction.ts"],"names":[],"mappings":"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"}
|
package/dist/system-switch.d.ts
CHANGED
|
@@ -74,8 +74,31 @@ export declare const SYSTEM_SWITCH_OFF_PREDICATE = "EXISTS (SELECT 1 FROM _subst
|
|
|
74
74
|
* Object caps its patterns (#1655); an exact prefix compare is neither.
|
|
75
75
|
*/
|
|
76
76
|
export declare function systemScheduleState(db: SwitchSql, moduleId: string, now: string): SystemScheduleState;
|
|
77
|
+
/**
|
|
78
|
+
* The same gate for ANY switched subject (#1706) — `system:<module>` above, `vertical:<slug>`
|
|
79
|
+
* for a peer. One statement, one spelling: the peer switch's status read must not be able to
|
|
80
|
+
* disagree with the schedule switch's about what "off" and "ungranted" mean, and the surest
|
|
81
|
+
* way to hold that is for there to be one predicate rather than two that look alike.
|
|
82
|
+
*/
|
|
83
|
+
export declare function subjectGrantState(db: SwitchSql, subject: string, now: string): SystemScheduleState;
|
|
84
|
+
/** One module's position, as `systemGrantsStatus` below enumerates them. */
|
|
85
|
+
export interface SystemGrantsEntry {
|
|
86
|
+
moduleId: string;
|
|
87
|
+
schedules: SystemScheduleState;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Every module this scope holds or has ever held system authority for, and where each
|
|
91
|
+
* stands (#1674) — the per-scope status read (`GET .../system-grants`). One SELECT
|
|
92
|
+
* enumerating the `system:<moduleId>` subjects this scope's storage has a live `granted:`
|
|
93
|
+
* tuple or OFF marker for (a module with neither has never run here, and has nothing to
|
|
94
|
+
* report — not even `ungranted`), then `systemScheduleState` per one: the SAME predicate
|
|
95
|
+
* `runDueSchedules` gates on, so the read and the runner cannot disagree.
|
|
96
|
+
*/
|
|
97
|
+
export declare function systemGrantsStatus(db: SwitchSql, now: string): SystemGrantsEntry[];
|
|
77
98
|
/** Is this module switched off on the scope `db` is? What `grantToSystem` refuses on. */
|
|
78
99
|
export declare function systemSwitchedOff(db: SwitchSql, moduleId: string): boolean;
|
|
100
|
+
/** Is this subject's OFF marker live on the scope `db` is? Any subject — see `switchSubjectGrants`. */
|
|
101
|
+
export declare function subjectSwitchedOff(db: SwitchSql, subject: string): boolean;
|
|
79
102
|
/** The refusal `grantToSystem` throws while the switch is off — one wording, both adapters. */
|
|
80
103
|
export declare function systemSwitchedOffMessage(moduleId: string, scopeId: string): string;
|
|
81
104
|
/** What one switch call did in the scope's own storage — `SystemSwitchOutcome`'s shape. */
|
|
@@ -105,4 +128,19 @@ export declare function switchSystemSchedules(db: SwitchSql, input: {
|
|
|
105
128
|
to: 'on' | 'off';
|
|
106
129
|
at: string;
|
|
107
130
|
}): SwitchOutcome;
|
|
131
|
+
/**
|
|
132
|
+
* The switch itself, for ANY non-person subject's scope-level grants — `switchSystemSchedules`
|
|
133
|
+
* for a schedule (`system:<module>`), and the peer kill switch for another vertical
|
|
134
|
+
* (`vertical:<slug>`, #1706). One body, so the two levers cannot disagree about what OFF
|
|
135
|
+
* tombstones, what ON gives back, or when a call held nothing. Everything said above
|
|
136
|
+
* `switchSystemSchedules` holds for every subject: the marker is `SYSTEM_SWITCH_OFF_RELATION`,
|
|
137
|
+
* and `seatScopeTuple`'s predicate is already bound per subject, so a marker blocks the seat
|
|
138
|
+
* for exactly the subject it names.
|
|
139
|
+
*/
|
|
140
|
+
export declare function switchSubjectGrants(db: SwitchSql, input: {
|
|
141
|
+
subject: string;
|
|
142
|
+
scopeId: string;
|
|
143
|
+
to: 'on' | 'off';
|
|
144
|
+
at: string;
|
|
145
|
+
}): SwitchOutcome;
|
|
108
146
|
//# sourceMappingURL=system-switch.d.ts.map
|
|
@@ -1 +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,
|
|
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,CAErG;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,mBAAmB,CAalG;AAED,4EAA4E;AAC5E,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,mBAAmB,CAAC;CAChC;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAWlF;AAED,yFAAyF;AACzF,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAE1E;AAED,uGAAuG;AACvG,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,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,CAOf;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CACjC,EAAE,EAAE,SAAS,EACb,KAAK,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,IAAI,GAAG,KAAK,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GACxE,aAAa,CA4Ff"}
|
package/dist/system-switch.js
CHANGED
|
@@ -61,7 +61,15 @@ export const SYSTEM_SWITCH_OFF_PREDICATE = `EXISTS (SELECT 1 FROM _substrat_tupl
|
|
|
61
61
|
* Object caps its patterns (#1655); an exact prefix compare is neither.
|
|
62
62
|
*/
|
|
63
63
|
export function systemScheduleState(db, moduleId, now) {
|
|
64
|
-
|
|
64
|
+
return subjectGrantState(db, subjectOf(moduleId), now);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The same gate for ANY switched subject (#1706) — `system:<module>` above, `vertical:<slug>`
|
|
68
|
+
* for a peer. One statement, one spelling: the peer switch's status read must not be able to
|
|
69
|
+
* disagree with the schedule switch's about what "off" and "ungranted" mean, and the surest
|
|
70
|
+
* way to hold that is for there to be one predicate rather than two that look alike.
|
|
71
|
+
*/
|
|
72
|
+
export function subjectGrantState(db, subject, now) {
|
|
65
73
|
const row = db.all(`SELECT
|
|
66
74
|
${SYSTEM_SWITCH_OFF_PREDICATE} AS off,
|
|
67
75
|
EXISTS (SELECT 1 FROM _substrat_tuples
|
|
@@ -71,9 +79,31 @@ export function systemScheduleState(db, moduleId, now) {
|
|
|
71
79
|
return 'off';
|
|
72
80
|
return Number(row?.granted) === 1 ? 'on' : 'ungranted';
|
|
73
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* Every module this scope holds or has ever held system authority for, and where each
|
|
84
|
+
* stands (#1674) — the per-scope status read (`GET .../system-grants`). One SELECT
|
|
85
|
+
* enumerating the `system:<moduleId>` subjects this scope's storage has a live `granted:`
|
|
86
|
+
* tuple or OFF marker for (a module with neither has never run here, and has nothing to
|
|
87
|
+
* report — not even `ungranted`), then `systemScheduleState` per one: the SAME predicate
|
|
88
|
+
* `runDueSchedules` gates on, so the read and the runner cannot disagree.
|
|
89
|
+
*/
|
|
90
|
+
export function systemGrantsStatus(db, now) {
|
|
91
|
+
const rows = db.all(`SELECT DISTINCT subject FROM _substrat_tuples
|
|
92
|
+
WHERE substr(subject, 1, 7) = 'system:'
|
|
93
|
+
AND (substr(relation, 1, 8) = 'granted:' OR relation = '${SYSTEM_SWITCH_OFF_RELATION}')
|
|
94
|
+
ORDER BY subject`);
|
|
95
|
+
return rows.map((row) => {
|
|
96
|
+
const moduleId = row.subject.slice('system:'.length);
|
|
97
|
+
return { moduleId, schedules: systemScheduleState(db, moduleId, now) };
|
|
98
|
+
});
|
|
99
|
+
}
|
|
74
100
|
/** Is this module switched off on the scope `db` is? What `grantToSystem` refuses on. */
|
|
75
101
|
export function systemSwitchedOff(db, moduleId) {
|
|
76
|
-
|
|
102
|
+
return subjectSwitchedOff(db, subjectOf(moduleId));
|
|
103
|
+
}
|
|
104
|
+
/** Is this subject's OFF marker live on the scope `db` is? Any subject — see `switchSubjectGrants`. */
|
|
105
|
+
export function subjectSwitchedOff(db, subject) {
|
|
106
|
+
const row = db.all(`SELECT ${SYSTEM_SWITCH_OFF_PREDICATE} AS off`, subject)[0];
|
|
77
107
|
return Number(row?.off) === 1;
|
|
78
108
|
}
|
|
79
109
|
/** The refusal `grantToSystem` throws while the switch is off — one wording, both adapters. */
|
|
@@ -98,7 +128,24 @@ export function systemSwitchedOffMessage(moduleId, scopeId) {
|
|
|
98
128
|
* Run it inside one transaction; the reads and writes here are meant to be one unit.
|
|
99
129
|
*/
|
|
100
130
|
export function switchSystemSchedules(db, input) {
|
|
101
|
-
|
|
131
|
+
return switchSubjectGrants(db, {
|
|
132
|
+
subject: subjectOf(input.moduleId),
|
|
133
|
+
scopeId: input.scopeId,
|
|
134
|
+
to: input.to,
|
|
135
|
+
at: input.at,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* The switch itself, for ANY non-person subject's scope-level grants — `switchSystemSchedules`
|
|
140
|
+
* for a schedule (`system:<module>`), and the peer kill switch for another vertical
|
|
141
|
+
* (`vertical:<slug>`, #1706). One body, so the two levers cannot disagree about what OFF
|
|
142
|
+
* tombstones, what ON gives back, or when a call held nothing. Everything said above
|
|
143
|
+
* `switchSystemSchedules` holds for every subject: the marker is `SYSTEM_SWITCH_OFF_RELATION`,
|
|
144
|
+
* and `seatScopeTuple`'s predicate is already bound per subject, so a marker blocks the seat
|
|
145
|
+
* for exactly the subject it names.
|
|
146
|
+
*/
|
|
147
|
+
export function switchSubjectGrants(db, input) {
|
|
148
|
+
const subject = input.subject;
|
|
102
149
|
const object = `scope:${input.scopeId}`;
|
|
103
150
|
const grants = db.all(`SELECT relation, revoked_at FROM _substrat_tuples
|
|
104
151
|
WHERE subject = ? AND object = ? AND substr(relation, 1, 8) = 'granted:'
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"system-switch.js","sourceRoot":"","sources":["../src/system-switch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAEH,4FAA4F;AAC5F,MAAM,CAAC,MAAM,0BAA0B,GAAG,YAAY,CAAC;AAEvD,uFAAuF;AACvF,MAAM,eAAe,GAAG,WAAW,CAAC;AAqBpC,MAAM,SAAS,GAAG,CAAC,QAAgB,EAAU,EAAE,CAAC,UAAU,QAAQ,EAAE,CAAC;AAErE;;;;GAIG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG;sCACL,0BAA0B,2BAA2B,CAAC;AAE5F;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,EAAa,EAAE,QAAgB,EAAE,GAAW;IAC9E,
|
|
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,EAOL,KAAK,SAAS,EACd,KAAK,OAAO,
|
|
1
|
+
{"version":3,"file":"timeline.d.ts","sourceRoot":"","sources":["../src/timeline.ts"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,SAAS,EACd,KAAK,OAAO,EAEZ,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,CAiEZ;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,CAwChH;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,eAAe,GAAG,gBAAgB,CAqEzF"}
|
package/dist/timeline.js
CHANGED
|
@@ -184,6 +184,21 @@ export function walkEventCause(ctx, eventId, maxDepth = 25) {
|
|
|
184
184
|
const rows = ctx.sql.query(`SELECT ${HISTORY_COLUMNS} FROM _substrat_outbox WHERE id = ? LIMIT 1`, [next]);
|
|
185
185
|
const row = rows[0];
|
|
186
186
|
if (row === undefined) {
|
|
187
|
+
// #1705: not in the outbox, but RECEIVED: the cause is another vertical's exported
|
|
188
|
+
// event. The trail leaves this scope rather than breaking, so the walk says where it
|
|
189
|
+
// went and does not raise the integrity alarm that 'missing' is.
|
|
190
|
+
const imported = ctx.sql.query('SELECT source_vertical, source_scope_id FROM _substrat_imports WHERE event_id = ? LIMIT 1', [next])[0];
|
|
191
|
+
if (imported !== undefined) {
|
|
192
|
+
return {
|
|
193
|
+
chain,
|
|
194
|
+
terminal: 'imported',
|
|
195
|
+
imported: {
|
|
196
|
+
eventId: next,
|
|
197
|
+
vertical: imported.source_vertical,
|
|
198
|
+
scopeId: imported.source_scope_id,
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
}
|
|
187
202
|
// The FIRST id missing means the caller named an event this scope does not
|
|
188
203
|
// hold; a later one means the spine lost a row it should still have. Both are
|
|
189
204
|
// 'missing' — the view says the trail cannot be followed further, and does not
|
|
@@ -427,12 +442,24 @@ export function readDeadLetters(ctx, page) {
|
|
|
427
442
|
params.push(event, event, consumer);
|
|
428
443
|
}
|
|
429
444
|
params.push(limit);
|
|
445
|
+
// #1705: a delivery's event is in this scope's outbox, OR it was received from another
|
|
446
|
+
// vertical and its envelope is in `_substrat_imports`. An import that gave up, or one the
|
|
447
|
+
// producer withheld, is a dead letter this scope's operator must see, so the read takes
|
|
448
|
+
// both. Two LEFT JOINs rather than a UNION: each seeks its table's key, where a UNION ALL
|
|
449
|
+
// subquery in a join could be materialized over the whole outbox. An imported event
|
|
450
|
+
// carries no invocation of this scope, so that column reads null.
|
|
430
451
|
const rows = ctx.sql.query(`SELECT d.event_id, d.consumer_module, d.delivered_at, d.error, d.attempts,
|
|
431
452
|
d.invocation_id AS attempt_invocation_id,
|
|
432
|
-
o.type,
|
|
453
|
+
COALESCE(o.type, i.type) AS type,
|
|
454
|
+
COALESCE(o.occurred_at, i.occurred_at) AS occurred_at,
|
|
455
|
+
COALESCE(o.entity_type, i.entity_type) AS entity_type,
|
|
456
|
+
COALESCE(o.entity_id, i.entity_id) AS entity_id,
|
|
457
|
+
o.invocation_id
|
|
433
458
|
FROM _substrat_deliveries d
|
|
434
|
-
JOIN _substrat_outbox o ON o.id = d.event_id
|
|
435
|
-
|
|
459
|
+
LEFT JOIN _substrat_outbox o ON o.id = d.event_id
|
|
460
|
+
LEFT JOIN _substrat_imports i ON i.event_id = d.event_id
|
|
461
|
+
WHERE d.error IS NOT NULL AND d.next_attempt_at IS NULL
|
|
462
|
+
AND (o.id IS NOT NULL OR i.event_id IS NOT NULL)${after}
|
|
436
463
|
ORDER BY d.event_id DESC, d.consumer_module DESC
|
|
437
464
|
LIMIT ?`, params);
|
|
438
465
|
const entries = rows.map(deadLetterOf);
|