@substrat-run/kernel 0.132.1 → 0.134.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 (45) hide show
  1. package/dist/attachment-extractor.d.ts +180 -0
  2. package/dist/attachment-extractor.d.ts.map +1 -0
  3. package/dist/attachment-extractor.js +230 -0
  4. package/dist/attachment-extractor.js.map +1 -0
  5. package/dist/attachment-text.d.ts +283 -0
  6. package/dist/attachment-text.d.ts.map +1 -0
  7. package/dist/attachment-text.js +497 -0
  8. package/dist/attachment-text.js.map +1 -0
  9. package/dist/idempotency.d.ts +4 -3
  10. package/dist/idempotency.d.ts.map +1 -1
  11. package/dist/idempotency.js +15 -7
  12. package/dist/idempotency.js.map +1 -1
  13. package/dist/index.d.ts +8 -4
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +4 -2
  16. package/dist/index.js.map +1 -1
  17. package/dist/job-run.d.ts +8 -2
  18. package/dist/job-run.d.ts.map +1 -1
  19. package/dist/job-run.js +16 -4
  20. package/dist/job-run.js.map +1 -1
  21. package/dist/list-index.d.ts +2 -1
  22. package/dist/list-index.d.ts.map +1 -1
  23. package/dist/list-index.js +9 -0
  24. package/dist/list-index.js.map +1 -1
  25. package/dist/platform-sweep.d.ts +9 -21
  26. package/dist/platform-sweep.d.ts.map +1 -1
  27. package/dist/platform-sweep.js +30 -1
  28. package/dist/platform-sweep.js.map +1 -1
  29. package/dist/scope-host.d.ts +94 -1
  30. package/dist/scope-host.d.ts.map +1 -1
  31. package/dist/scope-host.js +75 -1
  32. package/dist/scope-host.js.map +1 -1
  33. package/dist/scope-tuple-seat.d.ts +7 -2
  34. package/dist/scope-tuple-seat.d.ts.map +1 -1
  35. package/dist/scope-tuple-seat.js +14 -5
  36. package/dist/scope-tuple-seat.js.map +1 -1
  37. package/dist/search-index.d.ts +7 -0
  38. package/dist/search-index.d.ts.map +1 -1
  39. package/dist/search-index.js +7 -0
  40. package/dist/search-index.js.map +1 -1
  41. package/dist/subject-redaction.d.ts +157 -22
  42. package/dist/subject-redaction.d.ts.map +1 -1
  43. package/dist/subject-redaction.js +250 -26
  44. package/dist/subject-redaction.js.map +1 -1
  45. package/package.json +2 -2
@@ -26,8 +26,10 @@
26
26
  *
27
27
  * What it therefore does NOT reach is an intent kind that carries a subject's PII
28
28
  * WITHOUT carrying the classified event — an intent payload has no `piiClass` of its
29
- * own, so the kernel has nothing to read. No kind does that today (kernel-design.md
30
- * §13.1 limit 7 says so and names the walk), and a kind that started to would be
29
+ * own, so the kernel has nothing to read. One shipped kind can: a `sweep-runs` entry's
30
+ * `error` is a scheduled operation's throw, and that text gets its own walk on the subject's
31
+ * id (`redactSubjectSweepRunIntents`, at the end of this file). Any other kind is limit 7 in
32
+ * kernel-design.md §13.1, and a kind that started carrying a person directly would be
31
33
  * inventing an unclassified PII store inside the spine.
32
34
  *
33
35
  * This is a kernel module rather than two copies of the same SQL for
@@ -119,13 +121,29 @@ export interface SubjectRedactionCounts {
119
121
  * a count of rewritten cells would read larger than anything a DSAR answer could say.
120
122
  */
121
123
  jobRuns: number;
124
+ /**
125
+ * Recorded idempotent responses (#1632) that named the subject and now hold the tombstone.
126
+ * Not on the receipt — a cached reply is not another copy of an event — but answered, so a
127
+ * coordinator can tell a host that looked from one that never did.
128
+ */
129
+ idempotencyResults: number;
130
+ /**
131
+ * Every intent in the journal holding this subject's tombstone after the pass, whether this
132
+ * pass or an earlier one wrote it. The directory half needs them (`redactSubjectDirectoryText`):
133
+ * a drain failure about one of these intents quotes its `last_error`. All of them rather than
134
+ * the new ones, so an erasure that crashed between the two halves converges on its re-run.
135
+ */
136
+ intentIds: string[];
122
137
  }
123
138
  /**
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).
139
+ * What an older ScopeDO answers. One from after #1600 and before #1632 has no job-run count,
140
+ * because that host never looked at the job tables; one from before the free-text half has
141
+ * the count but no `idempotencyResults` or `intentIds`. The coordinator refuses both rather
142
+ * than reading an absence as zero (see `redactSubject`'s callers).
127
143
  */
128
- export type LegacySubjectRedactionCounts = Omit<SubjectRedactionCounts, 'jobRuns'>;
144
+ export type LegacySubjectRedactionCounts = Omit<SubjectRedactionCounts, 'jobRuns' | 'idempotencyResults' | 'intentIds'> & {
145
+ jobRuns?: number;
146
+ };
129
147
  /**
130
148
  * The candidate read: every intent whose stored payload TEXT contains the subject id.
131
149
  *
@@ -183,6 +201,27 @@ export declare function platformRequestRedactionParams(id: string, subjectId: st
183
201
  * a meaning for.
184
202
  */
185
203
  export declare function intentPayloadCarriesSubject(payloadText: string, subjectId: string): boolean;
204
+ /**
205
+ * Is this payload one we already redacted?
206
+ *
207
+ * **Asked of the WHOLE payload, once, and never inside the walk (#1600 review).** The
208
+ * first cut short-circuited the walk at any object carrying the marker key, which made
209
+ * `{ _substratRedacted: false, event: <a real envelope> }` a payload the erasure stepped
210
+ * straight past, PII and all — and the comment beside it claimed the check was redundant
211
+ * belt-and-braces because a tombstone has no `piiClass`. That was true of the tombstone
212
+ * and false of everything else wearing its key. An intent payload is `unknown` and module
213
+ * code chooses it, so a marker that means "stop looking" must not be something a payload
214
+ * can merely CONTAIN.
215
+ *
216
+ * The invariant asked instead is un-forgeable in the only way that matters: *a payload
217
+ * that is nothing but a redaction tombstone is already redacted*. One top-level key, and
218
+ * that key's value says why. Anything beside the marker is not this, gets walked, and is
219
+ * judged on its own contents — which is exactly right, because something beside the
220
+ * marker is something the erasure might need to reach. Inner fields beyond `reason` are
221
+ * deliberately not pinned: a later tombstone that carries more would still be nothing but
222
+ * a tombstone, and hard-coding its shape here is how idempotency would quietly regress.
223
+ */
224
+ export declare function isRedactedPayload(parsed: unknown): boolean;
186
225
  /**
187
226
  * The note a redaction leaves on a delivery of a redacted event that had failed.
188
227
  *
@@ -218,7 +257,7 @@ export declare const REDACTED_JOB_NOTE = "redacted by subject erasure (#37) \u20
218
257
  * handed the tombstone AS its input — a step's memo is returned to the handler without
219
258
  * running anything — and walk on from nonsense. `failed` is what the run now is.
220
259
  */
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";
260
+ export declare const CANCELLED_JOB_NOTE = "stopped by subject erasure (#37) \u2014 this run held an erased subject's data, so it cannot resume from it";
222
261
  /**
223
262
  * How the job-run half talks to a scope database — one shape both adapters can satisfy
224
263
  * in a line (`db.prepare(sql)` / `this.sql.exec(sql, …)`), so the reads, the predicate
@@ -228,21 +267,11 @@ export type RedactionSql = (sql: string, params: readonly (string | number | nul
228
267
  /**
229
268
  * The job-run half of an erasure (#1632), shared by both adapters.
230
269
  *
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).
270
+ * A declared `subject_id` identifies the whole run: all payload, cursor, step output,
271
+ * and error text is redacted, even when it contains no classified event envelope.
272
+ * Legacy and unclassified runs retain #1600's embedded-envelope predicate. Free text
273
+ * without either link remains unreachable; matching a name or id substring is not a
274
+ * reliable declaration of whose data a run handles.
246
275
  *
247
276
  * Per column, for a matching run: `payload` and `cursor` become the tombstone where THEY
248
277
  * match (the payload is `NOT NULL`, the cursor may be `NULL` and stays so), `last_error`
@@ -270,4 +299,110 @@ export declare const JOB_STEP_REDACTION_SQL = "UPDATE _substrat_job_steps\n
270
299
  * redacted-note, at (ended_at), at (updated_at), id.
271
300
  */
272
301
  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 = ?";
302
+ /**
303
+ * The note a redaction leaves where a failure's text named the subject: an ops-failure
304
+ * message, an issue's exemplar, a sweep record's error, or that error still queued inside a
305
+ * `sweep-runs` intent. One note for all four, because the drain lands the last into the third
306
+ * and a reader should meet the same sentence wherever the redaction found the text.
307
+ */
308
+ export declare const REDACTED_FAILURE_NOTE = "redacted by subject erasure (#37) \u2014 what this failure said is gone; that it happened, and when, is not";
309
+ /**
310
+ * The idempotency ledger's half (#1632): a recorded response that names the subject.
311
+ *
312
+ * `result` is an operation's return value, kept for a day so a retry is answered without
313
+ * running again — and an operation that returns a person's record returns their id with it.
314
+ * Nothing else in the row says whose response it is, so the link is the direct one: the
315
+ * subject's id appears in the stored JSON. A `DataSubjectId` is a ULID, so the match does not
316
+ * happen by coincidence. A response that names the person but not their id is not reached.
317
+ *
318
+ * The result becomes the intent tombstone; every other column stays, so the key is still
319
+ * recorded and a retry is still not executed twice. `replayFor` refuses a tombstoned result
320
+ * instead of replaying it. Idempotent: a row already holding this subject's tombstone is
321
+ * skipped. Returns how many rows it rewrote.
322
+ */
323
+ export declare function redactSubjectIdempotency(sql: RedactionSql, subjectId: string, at: string): number;
324
+ /**
325
+ * Every intent in the journal that holds this subject's tombstone — the ids the directory
326
+ * half links its drain-failure rows through. Read after the intent redaction, so it covers
327
+ * this pass's tombstones and every earlier pass's.
328
+ */
329
+ export declare function redactedIntentIds(sql: RedactionSql, subjectId: string): string[];
330
+ /**
331
+ * The scope's free-text half (#1632), shared by both adapters so the order is one fact: a
332
+ * recorded idempotent response and a queued `sweep-runs` entry's error that name the subject,
333
+ * and THEN the intents holding its tombstone — read after every other intent write, so the ids
334
+ * cover this pass's tombstones and every earlier one's.
335
+ */
336
+ export declare function redactSubjectScopeText(sql: RedactionSql, subjectId: string, at: string): Pick<SubjectRedactionCounts, 'idempotencyResults' | 'intentIds'>;
337
+ /**
338
+ * A `sweep-runs` intent's queued error text (#1632).
339
+ *
340
+ * A CP-less pass reports its schedule outcomes as one `sweep-runs` intent; the drain lands
341
+ * each entry in the directory's `_substrat_sweep_runs`, and the intent itself is kept like
342
+ * every other. So an entry's `error` — a scheduled operation's throw — is held twice, and the
343
+ * directory half alone would leave this copy. Only `entries[].error` is rewritten, and only
344
+ * where it names the subject's id: the payload keeps its shape and every other field, so a
345
+ * pending intent still drains (the note is a valid `error`) and lands the note instead of the
346
+ * text. Idempotent: the note does not name the subject.
347
+ */
348
+ export declare function redactSubjectSweepRunIntents(sql: RedactionSql, subjectId: string): void;
349
+ /**
350
+ * How the drain words an ops failure about one intent. The reader below parses the same
351
+ * prefix, so the directory walk and the writer cannot drift about the link between them.
352
+ */
353
+ export declare function platformIntentFailureMessage(intentId: string, text: string): string;
354
+ /** The intent an ops-failure message is about, or null — `platformIntentFailureMessage` read back. */
355
+ export declare function intentIdOfFailureMessage(message: string): string | null;
356
+ /** What the directory half is handed: the erased scope's tenant, and the scope half's intent ids. */
357
+ export interface SubjectTextTarget {
358
+ tenantId: string;
359
+ scopeId: string;
360
+ subjectId: string;
361
+ intentIds: string[];
362
+ }
363
+ /**
364
+ * Whose failure an issue exemplar was copied from, as the issue upsert records it beside
365
+ * `last_tenant_id`: a tenant's, or the platform's own (a failure with no tenant). NULL is never
366
+ * written. It is reserved for an issue from before the columns that no retained row attributes,
367
+ * so "the platform's" and "nobody knows" stay different facts.
368
+ *
369
+ * A kind column rather than a sentinel in `last_tenant_id`, so that column stays what its name
370
+ * says — a tenant id or nothing — for any reader or join, and a later owner kind is a new value
371
+ * here instead of a second magic string.
372
+ */
373
+ export type IssueExemplarOwner = 'tenant' | 'platform';
374
+ /** The owner kind both adapters' issue upserts write, from the failure's tenant. */
375
+ export declare function issueExemplarOwner(tenantId: string | null): IssueExemplarOwner;
376
+ /**
377
+ * #1632: the one-time backfill of an issue's exemplar owner, run as the columns are added to
378
+ * a directory that predates them. A row is attributed only when the retained ops-failure rows
379
+ * carrying its exemplar (same fingerprint, same text) all have one origin: a single tenant, or
380
+ * only the platform. When they name none, or more than one origin, the row stays unknown
381
+ * (NULL), and erasure skips it. Rows whose evidence already expired stay unknown too, so the
382
+ * gap fails closed.
383
+ */
384
+ export declare const ISSUE_EXEMPLAR_OWNER_BACKFILL_SQL = "UPDATE _substrat_issues\n SET last_tenant_id = (SELECT MIN(o.tenant_id) FROM _substrat_ops_failures o\n WHERE o.fingerprint = _substrat_issues.fingerprint\n AND o.message = _substrat_issues.last_message),\n last_owner_kind = CASE WHEN (SELECT MIN(o.tenant_id) FROM _substrat_ops_failures o\n WHERE o.fingerprint = _substrat_issues.fingerprint\n AND o.message = _substrat_issues.last_message) IS NULL\n THEN 'platform' ELSE 'tenant' END\n WHERE last_owner_kind IS NULL\n AND (SELECT COUNT(DISTINCT COALESCE(o.tenant_id, '')) FROM _substrat_ops_failures o\n WHERE o.fingerprint = _substrat_issues.fingerprint\n AND o.message = _substrat_issues.last_message) = 1";
385
+ /**
386
+ * The directory's half (#1632): three tables of failure text the control plane keeps.
387
+ *
388
+ * - `_substrat_ops_failures.message` — the drain's record of an intent it gave up on, or
389
+ * settled `failed`, quotes that intent's `last_error`. When the intent is one the erasure
390
+ * tombstoned (`intentIds`, in this tenant and scope), the message is a copy of text the
391
+ * scope half already redacted, and goes with it. A row in this tenant, or the platform's
392
+ * own row with no tenant, that names the subject's id goes too.
393
+ * - `_substrat_issues.last_message` — the newest exemplar of an ops-failure group. Its
394
+ * writer records whose failure it copied (`last_owner_kind`, `last_tenant_id`, in the same
395
+ * statement), and erasure reads that rather than the ops-failure rows, which issues outlive.
396
+ * A tenant's exemplar is rewritten only for that tenant, by the same two links; the
397
+ * platform's own on a direct id match, as its ops-failure row is. An exemplar of unknown
398
+ * owner (a legacy row nothing attributed) is skipped: the matched text can come from
399
+ * anyone's request, so an unattributed row is never read as this tenant's.
400
+ * - `_substrat_sweep_runs.error` — a scheduled operation's throw, matched on the subject's
401
+ * id in this tenant's rows or the platform's own. There is no link to an event here.
402
+ *
403
+ * Rows for another tenant are never touched. Free text that names the person but not their
404
+ * id, on a row with no intent link, is not reached. Idempotent: the note names neither an
405
+ * intent nor the subject.
406
+ */
407
+ export declare function redactSubjectDirectoryText(sql: RedactionSql, target: SubjectTextTarget): void;
273
408
  //# 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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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
+ {"version":3,"file":"subject-redaction.d.ts","sourceRoot":"","sources":["../src/subject-redaction.ts"],"names":[],"mappings":"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"}
@@ -26,14 +26,17 @@
26
26
  *
27
27
  * What it therefore does NOT reach is an intent kind that carries a subject's PII
28
28
  * WITHOUT carrying the classified event — an intent payload has no `piiClass` of its
29
- * own, so the kernel has nothing to read. No kind does that today (kernel-design.md
30
- * §13.1 limit 7 says so and names the walk), and a kind that started to would be
29
+ * own, so the kernel has nothing to read. One shipped kind can: a `sweep-runs` entry's
30
+ * `error` is a scheduled operation's throw, and that text gets its own walk on the subject's
31
+ * id (`redactSubjectSweepRunIntents`, at the end of this file). Any other kind is limit 7 in
32
+ * kernel-design.md §13.1, and a kind that started carrying a person directly would be
31
33
  * inventing an unclassified PII store inside the spine.
32
34
  *
33
35
  * This is a kernel module rather than two copies of the same SQL for
34
36
  * `platformRequestHistoryQuery`'s reason, sharpened: three surfaces answer one question
35
37
  * from this table, and a privacy guarantee that holds on one adapter is not a guarantee.
36
38
  */
39
+ import { SWEEP_RUNS_KIND } from '@substrat-run/contracts';
37
40
  /**
38
41
  * The one key a redacted intent payload carries, and nothing else in the system does.
39
42
  *
@@ -95,6 +98,14 @@ export const REDACTED_INTENT_NOTE = 'redacted by subject erasure (#37) — what
95
98
  * routed, so nothing re-routes the event to replace this row.
96
99
  */
97
100
  export const CANCELLED_INTENT_NOTE = 'cancelled by subject erasure (#37) — the payload was redacted before the drain reached it, so this intent never ran';
101
+ /**
102
+ * The needle every walk in this file searches a stored text for: the subject id with the
103
+ * quotes JSON.stringify adds stripped, keeping the escaped body — the exact run of characters
104
+ * a serialized payload holds.
105
+ */
106
+ function subjectNeedle(subjectId) {
107
+ return JSON.stringify(subjectId).slice(1, -1);
108
+ }
98
109
  /**
99
110
  * The candidate read: every intent whose stored payload TEXT contains the subject id.
100
111
  *
@@ -111,9 +122,7 @@ export const CANCELLED_INTENT_NOTE = 'cancelled by subject erasure (#37) — the
111
122
  export function platformRequestRedactionQuery(subjectId) {
112
123
  return {
113
124
  sql: 'SELECT id, payload FROM _substrat_platform_requests WHERE instr(payload, ?) > 0',
114
- // Strip the quotes JSON.stringify adds and keep the escaped body — the exact run of
115
- // characters the serialized payload holds.
116
- params: [JSON.stringify(subjectId).slice(1, -1)],
125
+ params: [subjectNeedle(subjectId)],
117
126
  };
118
127
  }
119
128
  /**
@@ -197,7 +206,7 @@ export function intentPayloadCarriesSubject(payloadText, subjectId) {
197
206
  * deliberately not pinned: a later tombstone that carries more would still be nothing but
198
207
  * a tombstone, and hard-coding its shape here is how idempotency would quietly regress.
199
208
  */
200
- function isRedactedPayload(parsed) {
209
+ export function isRedactedPayload(parsed) {
201
210
  if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed))
202
211
  return false;
203
212
  const keys = Object.keys(parsed);
@@ -262,25 +271,15 @@ export const REDACTED_JOB_NOTE = 'redacted by subject erasure (#37) — what thi
262
271
  * handed the tombstone AS its input — a step's memo is returned to the handler without
263
272
  * running anything — and walk on from nonsense. `failed` is what the run now is.
264
273
  */
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';
274
+ export const CANCELLED_JOB_NOTE = 'stopped by subject erasure (#37) — this run held an erased subject\'s data, so it cannot resume from it';
266
275
  /**
267
276
  * The job-run half of an erasure (#1632), shared by both adapters.
268
277
  *
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).
278
+ * A declared `subject_id` identifies the whole run: all payload, cursor, step output,
279
+ * and error text is redacted, even when it contains no classified event envelope.
280
+ * Legacy and unclassified runs retain #1600's embedded-envelope predicate. Free text
281
+ * without either link remains unreachable; matching a name or id substring is not a
282
+ * reliable declaration of whose data a run handles.
284
283
  *
285
284
  * Per column, for a matching run: `payload` and `cursor` become the tombstone where THEY
286
285
  * match (the payload is `NOT NULL`, the cursor may be `NULL` and stays so), `last_error`
@@ -292,10 +291,29 @@ export const CANCELLED_JOB_NOTE = 'stopped by subject erasure (#37) — this run
292
291
  * Returns the number of distinct runs touched.
293
292
  */
294
293
  export function redactSubjectJobRuns(sql, subjectId, at) {
295
- // The needle, spelled as `platformRequestRedactionQuery` spells it.
296
- const needle = JSON.stringify(subjectId).slice(1, -1);
294
+ const needle = subjectNeedle(subjectId);
297
295
  const tombstone = redactedIntentPayload(subjectId, at);
298
296
  const runs = new Set();
297
+ const hits = new Map();
298
+ const declared = sql('SELECT id, payload, cursor, status, last_error FROM _substrat_job_runs WHERE subject_id = ?', [subjectId]);
299
+ for (const r of declared) {
300
+ // Older envelope-only erasure may have tombstoned just the payload. Verify every
301
+ // owned field before calling this complete; a terminal note is not a receipt.
302
+ if (r.status !== 'running' && isRedactedPayloadText(r.payload, subjectId) &&
303
+ (r.cursor === null || isRedactedPayloadText(r.cursor, subjectId)) &&
304
+ (r.last_error === CANCELLED_JOB_NOTE || r.last_error === REDACTED_JOB_NOTE)) {
305
+ const memos = sql('SELECT result, last_error FROM _substrat_job_steps WHERE run_id = ?', [r.id]);
306
+ if (memos.every((memo) => (memo.result === null || isRedactedPayloadText(memo.result, subjectId)) &&
307
+ (memo.last_error === null || memo.last_error === REDACTED_JOB_NOTE)))
308
+ continue;
309
+ }
310
+ sql(`UPDATE _substrat_job_steps
311
+ SET result = CASE WHEN result IS NULL THEN NULL ELSE ? END,
312
+ last_error = CASE WHEN last_error IS NULL THEN NULL ELSE ? END
313
+ WHERE run_id = ?`, [tombstone, REDACTED_JOB_NOTE, r.id]);
314
+ hits.set(r.id, { payload: true, cursor: r.cursor !== null });
315
+ runs.add(r.id);
316
+ }
299
317
  const steps = sql('SELECT run_id, step, result FROM _substrat_job_steps WHERE instr(result, ?) > 0', [needle]);
300
318
  for (const s of steps) {
301
319
  if (!intentPayloadCarriesSubject(s.result, subjectId))
@@ -305,11 +323,10 @@ export function redactSubjectJobRuns(sql, subjectId, at) {
305
323
  }
306
324
  const candidates = sql(`SELECT id, payload, cursor FROM _substrat_job_runs
307
325
  WHERE instr(payload, ?) > 0 OR instr(cursor, ?) > 0`, [needle, needle]);
308
- const hits = new Map();
309
326
  for (const r of candidates) {
310
327
  const payload = intentPayloadCarriesSubject(r.payload, subjectId);
311
328
  const cursor = r.cursor !== null && intentPayloadCarriesSubject(r.cursor, subjectId);
312
- if (payload || cursor)
329
+ if ((payload || cursor) && !hits.has(r.id))
313
330
  hits.set(r.id, { payload, cursor });
314
331
  }
315
332
  // A run reached only through a step is still rewritten: its note and its status are
@@ -333,6 +350,17 @@ export function redactSubjectJobRuns(sql, subjectId, at) {
333
350
  }
334
351
  return runs.size;
335
352
  }
353
+ /** Recognise a previous complete payload tombstone without relying on its timestamp. */
354
+ function isRedactedPayloadText(text, subjectId) {
355
+ try {
356
+ const parsed = JSON.parse(text);
357
+ return isRedactedPayload(parsed) &&
358
+ parsed[REDACTED_INTENT_MARKER]['subjectId'] === subjectId;
359
+ }
360
+ catch {
361
+ return false;
362
+ }
363
+ }
336
364
  /**
337
365
  * One matched step: its result becomes the tombstone, its error the note.
338
366
  *
@@ -359,4 +387,200 @@ export const JOB_RUN_REDACTION_SQL = `UPDATE _substrat_job_runs
359
387
  updated_at = ?,
360
388
  status = CASE WHEN status = 'running' THEN 'failed' ELSE status END
361
389
  WHERE id = ?`;
390
+ // -- the spine's free-text copies (#1632) ---------------------------------------------
391
+ /**
392
+ * The note a redaction leaves where a failure's text named the subject: an ops-failure
393
+ * message, an issue's exemplar, a sweep record's error, or that error still queued inside a
394
+ * `sweep-runs` intent. One note for all four, because the drain lands the last into the third
395
+ * and a reader should meet the same sentence wherever the redaction found the text.
396
+ */
397
+ export const REDACTED_FAILURE_NOTE = 'redacted by subject erasure (#37) — what this failure said is gone; that it happened, and when, is not';
398
+ /**
399
+ * The idempotency ledger's half (#1632): a recorded response that names the subject.
400
+ *
401
+ * `result` is an operation's return value, kept for a day so a retry is answered without
402
+ * running again — and an operation that returns a person's record returns their id with it.
403
+ * Nothing else in the row says whose response it is, so the link is the direct one: the
404
+ * subject's id appears in the stored JSON. A `DataSubjectId` is a ULID, so the match does not
405
+ * happen by coincidence. A response that names the person but not their id is not reached.
406
+ *
407
+ * The result becomes the intent tombstone; every other column stays, so the key is still
408
+ * recorded and a retry is still not executed twice. `replayFor` refuses a tombstoned result
409
+ * instead of replaying it. Idempotent: a row already holding this subject's tombstone is
410
+ * skipped. Returns how many rows it rewrote.
411
+ */
412
+ export function redactSubjectIdempotency(sql, subjectId, at) {
413
+ const rows = sql('SELECT subject, key, result FROM _substrat_idempotency WHERE instr(result, ?) > 0', [subjectNeedle(subjectId)]);
414
+ const tombstone = redactedIntentPayload(subjectId, at);
415
+ let redacted = 0;
416
+ for (const row of rows) {
417
+ if (isRedactedPayloadText(row.result, subjectId))
418
+ continue;
419
+ sql('UPDATE _substrat_idempotency SET result = ? WHERE subject = ? AND key = ?', [
420
+ tombstone,
421
+ row.subject,
422
+ row.key,
423
+ ]);
424
+ redacted += 1;
425
+ }
426
+ return redacted;
427
+ }
428
+ /**
429
+ * Every intent in the journal that holds this subject's tombstone — the ids the directory
430
+ * half links its drain-failure rows through. Read after the intent redaction, so it covers
431
+ * this pass's tombstones and every earlier pass's.
432
+ */
433
+ export function redactedIntentIds(sql, subjectId) {
434
+ const q = platformRequestRedactionQuery(subjectId);
435
+ const rows = sql(q.sql, q.params);
436
+ return rows.filter((r) => isRedactedPayloadText(r.payload, subjectId)).map((r) => r.id);
437
+ }
438
+ /**
439
+ * The scope's free-text half (#1632), shared by both adapters so the order is one fact: a
440
+ * recorded idempotent response and a queued `sweep-runs` entry's error that name the subject,
441
+ * and THEN the intents holding its tombstone — read after every other intent write, so the ids
442
+ * cover this pass's tombstones and every earlier one's.
443
+ */
444
+ export function redactSubjectScopeText(sql, subjectId, at) {
445
+ const idempotencyResults = redactSubjectIdempotency(sql, subjectId, at);
446
+ redactSubjectSweepRunIntents(sql, subjectId);
447
+ return { idempotencyResults, intentIds: redactedIntentIds(sql, subjectId) };
448
+ }
449
+ /**
450
+ * A `sweep-runs` intent's queued error text (#1632).
451
+ *
452
+ * A CP-less pass reports its schedule outcomes as one `sweep-runs` intent; the drain lands
453
+ * each entry in the directory's `_substrat_sweep_runs`, and the intent itself is kept like
454
+ * every other. So an entry's `error` — a scheduled operation's throw — is held twice, and the
455
+ * directory half alone would leave this copy. Only `entries[].error` is rewritten, and only
456
+ * where it names the subject's id: the payload keeps its shape and every other field, so a
457
+ * pending intent still drains (the note is a valid `error`) and lands the note instead of the
458
+ * text. Idempotent: the note does not name the subject.
459
+ */
460
+ export function redactSubjectSweepRunIntents(sql, subjectId) {
461
+ const rows = sql('SELECT id, payload FROM _substrat_platform_requests WHERE kind = ? AND instr(payload, ?) > 0', [SWEEP_RUNS_KIND, subjectNeedle(subjectId)]);
462
+ for (const row of rows) {
463
+ let parsed;
464
+ try {
465
+ parsed = JSON.parse(row.payload);
466
+ }
467
+ catch {
468
+ continue;
469
+ }
470
+ if (typeof parsed !== 'object' || parsed === null)
471
+ continue;
472
+ const entries = parsed.entries;
473
+ if (!Array.isArray(entries))
474
+ continue;
475
+ let changed = false;
476
+ const rewritten = entries.map((entry) => {
477
+ if (typeof entry !== 'object' || entry === null)
478
+ return entry;
479
+ const error = entry.error;
480
+ if (typeof error !== 'string' || !error.includes(subjectId))
481
+ return entry;
482
+ changed = true;
483
+ return { ...entry, error: REDACTED_FAILURE_NOTE };
484
+ });
485
+ if (!changed)
486
+ continue;
487
+ sql('UPDATE _substrat_platform_requests SET payload = ? WHERE id = ?', [
488
+ JSON.stringify({ ...parsed, entries: rewritten }),
489
+ row.id,
490
+ ]);
491
+ }
492
+ }
493
+ const PLATFORM_INTENT_FAILURE_PREFIX = 'platform intent ';
494
+ /**
495
+ * How the drain words an ops failure about one intent. The reader below parses the same
496
+ * prefix, so the directory walk and the writer cannot drift about the link between them.
497
+ */
498
+ export function platformIntentFailureMessage(intentId, text) {
499
+ return `${PLATFORM_INTENT_FAILURE_PREFIX}${intentId} ${text}`;
500
+ }
501
+ /** The intent an ops-failure message is about, or null — `platformIntentFailureMessage` read back. */
502
+ export function intentIdOfFailureMessage(message) {
503
+ if (!message.startsWith(PLATFORM_INTENT_FAILURE_PREFIX))
504
+ return null;
505
+ const rest = message.slice(PLATFORM_INTENT_FAILURE_PREFIX.length);
506
+ const end = rest.indexOf(' ');
507
+ return end > 0 ? rest.slice(0, end) : null;
508
+ }
509
+ /** The owner kind both adapters' issue upserts write, from the failure's tenant. */
510
+ export function issueExemplarOwner(tenantId) {
511
+ return tenantId === null ? 'platform' : 'tenant';
512
+ }
513
+ /**
514
+ * #1632: the one-time backfill of an issue's exemplar owner, run as the columns are added to
515
+ * a directory that predates them. A row is attributed only when the retained ops-failure rows
516
+ * carrying its exemplar (same fingerprint, same text) all have one origin: a single tenant, or
517
+ * only the platform. When they name none, or more than one origin, the row stays unknown
518
+ * (NULL), and erasure skips it. Rows whose evidence already expired stay unknown too, so the
519
+ * gap fails closed.
520
+ */
521
+ export const ISSUE_EXEMPLAR_OWNER_BACKFILL_SQL = `UPDATE _substrat_issues
522
+ SET last_tenant_id = (SELECT MIN(o.tenant_id) FROM _substrat_ops_failures o
523
+ WHERE o.fingerprint = _substrat_issues.fingerprint
524
+ AND o.message = _substrat_issues.last_message),
525
+ last_owner_kind = CASE WHEN (SELECT MIN(o.tenant_id) FROM _substrat_ops_failures o
526
+ WHERE o.fingerprint = _substrat_issues.fingerprint
527
+ AND o.message = _substrat_issues.last_message) IS NULL
528
+ THEN 'platform' ELSE 'tenant' END
529
+ WHERE last_owner_kind IS NULL
530
+ AND (SELECT COUNT(DISTINCT COALESCE(o.tenant_id, '')) FROM _substrat_ops_failures o
531
+ WHERE o.fingerprint = _substrat_issues.fingerprint
532
+ AND o.message = _substrat_issues.last_message) = 1`;
533
+ /**
534
+ * The directory's half (#1632): three tables of failure text the control plane keeps.
535
+ *
536
+ * - `_substrat_ops_failures.message` — the drain's record of an intent it gave up on, or
537
+ * settled `failed`, quotes that intent's `last_error`. When the intent is one the erasure
538
+ * tombstoned (`intentIds`, in this tenant and scope), the message is a copy of text the
539
+ * scope half already redacted, and goes with it. A row in this tenant, or the platform's
540
+ * own row with no tenant, that names the subject's id goes too.
541
+ * - `_substrat_issues.last_message` — the newest exemplar of an ops-failure group. Its
542
+ * writer records whose failure it copied (`last_owner_kind`, `last_tenant_id`, in the same
543
+ * statement), and erasure reads that rather than the ops-failure rows, which issues outlive.
544
+ * A tenant's exemplar is rewritten only for that tenant, by the same two links; the
545
+ * platform's own on a direct id match, as its ops-failure row is. An exemplar of unknown
546
+ * owner (a legacy row nothing attributed) is skipped: the matched text can come from
547
+ * anyone's request, so an unattributed row is never read as this tenant's.
548
+ * - `_substrat_sweep_runs.error` — a scheduled operation's throw, matched on the subject's
549
+ * id in this tenant's rows or the platform's own. There is no link to an event here.
550
+ *
551
+ * Rows for another tenant are never touched. Free text that names the person but not their
552
+ * id, on a row with no intent link, is not reached. Idempotent: the note names neither an
553
+ * intent nor the subject.
554
+ */
555
+ export function redactSubjectDirectoryText(sql, target) {
556
+ const needle = subjectNeedle(target.subjectId);
557
+ sql(`UPDATE _substrat_ops_failures SET message = ?
558
+ WHERE instr(message, ?) > 0 AND (tenant_id IS NULL OR tenant_id = ?)`, [REDACTED_FAILURE_NOTE, needle, target.tenantId]);
559
+ sql(`UPDATE _substrat_issues SET last_message = ?
560
+ WHERE instr(last_message, ?) > 0
561
+ AND ((last_owner_kind = 'tenant' AND last_tenant_id = ?) OR last_owner_kind = 'platform')`, [REDACTED_FAILURE_NOTE, needle, target.tenantId]);
562
+ sql(`UPDATE _substrat_sweep_runs SET error = ?
563
+ WHERE instr(error, ?) > 0 AND (tenant_id IS NULL OR tenant_id = ?)`, [REDACTED_FAILURE_NOTE, needle, target.tenantId]);
564
+ if (target.intentIds.length === 0)
565
+ return;
566
+ const intents = new Set(target.intentIds);
567
+ // Rows whose text the drain wrote about one of those intents — read back by the grammar it
568
+ // wrote them in, so the SQL only narrows to the prefix and the id decides.
569
+ const redactLinked = (table, key, text, where, params) => {
570
+ const rows = sql(`SELECT ${key} AS key, ${text} AS text FROM ${table} WHERE ${where}instr(${text}, ?) = 1`, [...params, PLATFORM_INTENT_FAILURE_PREFIX]);
571
+ for (const row of rows) {
572
+ const id = intentIdOfFailureMessage(row.text);
573
+ if (id !== null && intents.has(id)) {
574
+ sql(`UPDATE ${table} SET ${text} = ? WHERE ${key} = ?`, [REDACTED_FAILURE_NOTE, row.key]);
575
+ }
576
+ }
577
+ };
578
+ redactLinked('_substrat_ops_failures', 'id', 'message', 'tenant_id = ? AND scope_id = ? AND ', [
579
+ target.tenantId,
580
+ target.scopeId,
581
+ ]);
582
+ redactLinked('_substrat_issues', 'fingerprint', 'last_message', "last_owner_kind = 'tenant' AND last_tenant_id = ? AND ", [
583
+ target.tenantId,
584
+ ]);
585
+ }
362
586
  //# 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":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@substrat-run/kernel",
3
- "version": "0.132.1",
3
+ "version": "0.134.0",
4
4
  "description": "Substrat kernel contracts and services — pure TypeScript, no platform imports (D-14)",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {
@@ -22,7 +22,7 @@
22
22
  "dist"
23
23
  ],
24
24
  "dependencies": {
25
- "@substrat-run/contracts": "^0.132.1"
25
+ "@substrat-run/contracts": "^0.134.0"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@types/node": "^22.20.4",