@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.
Files changed (51) hide show
  1. package/dist/capability.d.ts +22 -1
  2. package/dist/capability.d.ts.map +1 -1
  3. package/dist/capability.js +20 -0
  4. package/dist/capability.js.map +1 -1
  5. package/dist/connector-calls.d.ts +205 -0
  6. package/dist/connector-calls.d.ts.map +1 -0
  7. package/dist/connector-calls.js +192 -0
  8. package/dist/connector-calls.js.map +1 -0
  9. package/dist/idempotency.d.ts +7 -1
  10. package/dist/idempotency.d.ts.map +1 -1
  11. package/dist/idempotency.js +8 -2
  12. package/dist/idempotency.js.map +1 -1
  13. package/dist/index.d.ts +14 -9
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +8 -5
  16. package/dist/index.js.map +1 -1
  17. package/dist/job-run.d.ts +28 -0
  18. package/dist/job-run.d.ts.map +1 -1
  19. package/dist/job-run.js +37 -0
  20. package/dist/job-run.js.map +1 -1
  21. package/dist/peer.d.ts +132 -0
  22. package/dist/peer.d.ts.map +1 -0
  23. package/dist/peer.js +177 -0
  24. package/dist/peer.js.map +1 -0
  25. package/dist/permission-checker.d.ts +1 -1
  26. package/dist/permission-checker.d.ts.map +1 -1
  27. package/dist/permission-checker.js +4 -1
  28. package/dist/permission-checker.js.map +1 -1
  29. package/dist/platform-sweep.d.ts +116 -1
  30. package/dist/platform-sweep.d.ts.map +1 -1
  31. package/dist/platform-sweep.js +209 -0
  32. package/dist/platform-sweep.js.map +1 -1
  33. package/dist/scope-host.d.ts +228 -7
  34. package/dist/scope-host.d.ts.map +1 -1
  35. package/dist/scope-host.js.map +1 -1
  36. package/dist/subject-redaction.d.ts +120 -7
  37. package/dist/subject-redaction.d.ts.map +1 -1
  38. package/dist/subject-redaction.js +155 -3
  39. package/dist/subject-redaction.js.map +1 -1
  40. package/dist/system-switch.d.ts +38 -0
  41. package/dist/system-switch.d.ts.map +1 -1
  42. package/dist/system-switch.js +50 -3
  43. package/dist/system-switch.js.map +1 -1
  44. package/dist/timeline.d.ts.map +1 -1
  45. package/dist/timeline.js +30 -3
  46. package/dist/timeline.js.map +1 -1
  47. package/dist/vertical-events.d.ts +162 -0
  48. package/dist/vertical-events.d.ts.map +1 -0
  49. package/dist/vertical-events.js +348 -0
  50. package/dist/vertical-events.js.map +1 -0
  51. 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
- * Two numbers rather than one sum, because the receipt reports them apart: how much was
102
- * said about a person and how many copies of it were queued for a third party are
103
- * different facts, and a DSAR answer that folded them together could not say either.
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
- * Params, in order: payload, cancelled-note, redacted-note, at, id.
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;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;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,6BAA6B,CAAC,SAAS,EAAE,MAAM,GAAG;IAChE,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAOA;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,8BAA8B,oRAK3B,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,CAAC,CAE1C;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,2BAA2B,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAS3F"}
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
- * Params, in order: payload, cancelled-note, redacted-note, at, id.
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
- return [redactedIntentPayload(subjectId, at), CANCELLED_INTENT_NOTE, REDACTED_INTENT_NOTE, at, id];
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":"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"}
1
+ {"version":3,"file":"subject-redaction.js","sourceRoot":"","sources":["../src/subject-redaction.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH;;;;;GAKG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,mBAAmB,CAAC;AAE1D;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,UAAU,qBAAqB,CAAC,SAAiB,EAAE,EAAU;IACjE,OAAO,IAAI,CAAC,SAAS,CAAC;QACpB,CAAC,sBAAsB,CAAC,EAAE,EAAE,MAAM,EAAE,iBAAiB,EAAE,SAAS,EAAE,EAAE,EAAE;KACvE,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAC/B,uGAAuG,CAAC;AAE1G;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAChC,qHAAqH,CAAC;AAwCxH;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,6BAA6B,CAAC,SAAiB;IAI7D,OAAO;QACL,GAAG,EAAE,iFAAiF;QACtF,oFAAoF;QACpF,2CAA2C;QAC3C,MAAM,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;KACjD,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,CAAC,MAAM,8BAA8B,GAAG;;;;;;;gBAO9B,CAAC;AAEjB,wFAAwF;AACxF,MAAM,UAAU,8BAA8B,CAC5C,EAAU,EACV,SAAiB,EACjB,EAAU;IAEV,MAAM,SAAS,GAAG,qBAAqB,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;IACvD,OAAO,CAAC,SAAS,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,SAAS,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;AACrF,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,2BAA2B,CAAC,WAAmB,EAAE,SAAiB;IAChF,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IACnC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,iBAAiB,CAAC,MAAM,CAAC;QAAE,OAAO,KAAK,CAAC;IAC5C,OAAO,cAAc,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;AAC3C,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,SAAS,iBAAiB,CAAC,MAAe;IACxC,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QAAE,OAAO,KAAK,CAAC;IACzF,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACjC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,sBAAsB;QAAE,OAAO,KAAK,CAAC;IAC1E,MAAM,MAAM,GAAI,MAAkC,CAAC,sBAAsB,CAAC,CAAC;IAC3E,OAAO,CACL,OAAO,MAAM,KAAK,QAAQ;QAC1B,MAAM,KAAK,IAAI;QACf,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QACrB,MAAkC,CAAC,QAAQ,CAAC,KAAK,iBAAiB,CACpE,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,IAAa,EAAE,SAAiB;IACtD,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,cAAc,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC;IACvF,IAAI,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5D,MAAM,GAAG,GAAG,IAA+B,CAAC;IAC5C,IACE,GAAG,CAAC,WAAW,CAAC,KAAK,SAAS;QAC9B,OAAO,GAAG,CAAC,UAAU,CAAC,KAAK,QAAQ;QACnC,GAAG,CAAC,UAAU,CAAC,KAAK,MAAM,EAC1B,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,cAAc,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC;AAC9E,CAAC;AAED,uFAAuF;AAEvF;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,sBAAsB,GACjC,2GAA2G,CAAC;AAE9G;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAG;;;oGAGwD,CAAC;AAErG,uFAAuF;AAEvF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAC5B,qHAAqH,CAAC;AAExH;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAC7B,oHAAoH,CAAC;AASvH;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAiB,EAAE,SAAiB,EAAE,EAAU;IACnF,oEAAoE;IACpE,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACtD,MAAM,SAAS,GAAG,qBAAqB,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;IACvD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAE/B,MAAM,KAAK,GAAG,GAAG,CACf,iFAAiF,EACjF,CAAC,MAAM,CAAC,CAC6C,CAAC;IACxD,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,IAAI,CAAC,2BAA2B,CAAC,CAAC,CAAC,MAAM,EAAE,SAAS,CAAC;YAAE,SAAS;QAChE,GAAG,CAAC,sBAAsB,EAAE,CAAC,SAAS,EAAE,iBAAiB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QAC9E,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IACrB,CAAC;IAED,MAAM,UAAU,GAAG,GAAG,CACpB;0DACsD,EACtD,CAAC,MAAM,EAAE,MAAM,CAAC,CAC2C,CAAC;IAC9D,MAAM,IAAI,GAAG,IAAI,GAAG,EAAiD,CAAC;IACtE,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;QAC3B,MAAM,OAAO,GAAG,2BAA2B,CAAC,CAAC,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;QAClE,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,KAAK,IAAI,IAAI,2BAA2B,CAAC,CAAC,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;QACrF,IAAI,OAAO,IAAI,MAAM;YAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;IAC7D,CAAC;IACD,oFAAoF;IACpF,gDAAgD;IAChD,KAAK,MAAM,EAAE,IAAI,IAAI;QAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YAAE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;IAE1F,KAAK,MAAM,CAAC,EAAE,EAAE,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC;QAC7B,GAAG,CAAC,qBAAqB,EAAE;YACzB,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACnB,SAAS;YACT,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAClB,SAAS;YACT,kBAAkB;YAClB,iBAAiB;YACjB,EAAE;YACF,EAAE;YACF,EAAE;SACH,CAAC,CAAC;QACH,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACf,CAAC;IACD,OAAO,IAAI,CAAC,IAAI,CAAC;AACnB,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG;;iCAEL,CAAC;AAElC;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG;;;;;;;;gBAQrB,CAAC"}
@@ -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,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"}
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"}
@@ -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
- const subject = subjectOf(moduleId);
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
- const row = db.all(`SELECT ${SYSTEM_SWITCH_OFF_PREDICATE} AS off`, subjectOf(moduleId))[0];
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
- const subject = subjectOf(input.moduleId);
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":"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"}
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,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"}
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, o.occurred_at, o.entity_type, o.entity_id, o.invocation_id
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
- WHERE d.error IS NOT NULL AND d.next_attempt_at IS NULL${after}
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);