@substrat-run/kernel 0.116.0 → 0.118.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 (64) hide show
  1. package/dist/capability.d.ts +220 -0
  2. package/dist/capability.d.ts.map +1 -0
  3. package/dist/capability.js +537 -0
  4. package/dist/capability.js.map +1 -0
  5. package/dist/check-key.d.ts +30 -0
  6. package/dist/check-key.d.ts.map +1 -0
  7. package/dist/check-key.js +37 -0
  8. package/dist/check-key.js.map +1 -0
  9. package/dist/denial-query.d.ts +32 -1
  10. package/dist/denial-query.d.ts.map +1 -1
  11. package/dist/denial-query.js +66 -28
  12. package/dist/denial-query.js.map +1 -1
  13. package/dist/index.d.ts +20 -5
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +15 -4
  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/outbox-event.d.ts +131 -0
  22. package/dist/outbox-event.d.ts.map +1 -0
  23. package/dist/outbox-event.js +185 -0
  24. package/dist/outbox-event.js.map +1 -0
  25. package/dist/permission-checker.d.ts +8 -1
  26. package/dist/permission-checker.d.ts.map +1 -1
  27. package/dist/permission-checker.js +18 -0
  28. package/dist/permission-checker.js.map +1 -1
  29. package/dist/permission-eval.d.ts +8 -0
  30. package/dist/permission-eval.d.ts.map +1 -1
  31. package/dist/permission-eval.js +164 -91
  32. package/dist/permission-eval.js.map +1 -1
  33. package/dist/platform-request-query.d.ts +58 -1
  34. package/dist/platform-request-query.d.ts.map +1 -1
  35. package/dist/platform-request-query.js +61 -1
  36. package/dist/platform-request-query.js.map +1 -1
  37. package/dist/platform-sweep.d.ts +123 -8
  38. package/dist/platform-sweep.d.ts.map +1 -1
  39. package/dist/platform-sweep.js +158 -34
  40. package/dist/platform-sweep.js.map +1 -1
  41. package/dist/row-decode.d.ts +107 -0
  42. package/dist/row-decode.d.ts.map +1 -0
  43. package/dist/row-decode.js +93 -0
  44. package/dist/row-decode.js.map +1 -0
  45. package/dist/scope-host.d.ts +211 -9
  46. package/dist/scope-host.d.ts.map +1 -1
  47. package/dist/scope-host.js +33 -0
  48. package/dist/scope-host.js.map +1 -1
  49. package/dist/scope-tuple-seat.d.ts +72 -0
  50. package/dist/scope-tuple-seat.d.ts.map +1 -0
  51. package/dist/scope-tuple-seat.js +93 -0
  52. package/dist/scope-tuple-seat.js.map +1 -0
  53. package/dist/subject-redaction.d.ts +273 -0
  54. package/dist/subject-redaction.d.ts.map +1 -0
  55. package/dist/subject-redaction.js +362 -0
  56. package/dist/subject-redaction.js.map +1 -0
  57. package/dist/system-switch.d.ts +108 -0
  58. package/dist/system-switch.d.ts.map +1 -0
  59. package/dist/system-switch.js +145 -0
  60. package/dist/system-switch.js.map +1 -0
  61. package/dist/timeline.d.ts.map +1 -1
  62. package/dist/timeline.js +109 -57
  63. package/dist/timeline.js.map +1 -1
  64. package/package.json +2 -2
@@ -0,0 +1,273 @@
1
+ /**
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).
4
+ *
5
+ * `shredSubject` holds K-37's Tier-1 line — *the payload goes, the envelope stays* —
6
+ * and for a year that line reached exactly one table, `_substrat_outbox`. It is not the
7
+ * only place the spine keeps an event's payload. A CP-less host cannot run a connector
8
+ * (no directory, no credentials, no sanctioned egress), so each connector delivery
9
+ * becomes a `connector:<provider>` platform intent whose payload is the WHOLE
10
+ * `DomainEvent` — fat by design, because "the platform's handler needs everything the
11
+ * in-process handler would have been handed" (`connectorDispatchPayload`). That copy
12
+ * lands in `_substrat_platform_requests`, which nothing ever deletes: the retention is
13
+ * deliberate, `listPlatformRequestHistory` exists precisely so a settled row stays
14
+ * readable. So a name the redaction did not reach there survived in the live scope
15
+ * database, and in every export, backup and PITR window taken from it afterwards.
16
+ *
17
+ * **The rule this module implements is narrow on purpose:** an intent that carries a
18
+ * COPY OF AN EVENT is redacted exactly when the erasure redacts that event. Not "any
19
+ * payload mentioning the subject" — the outbox spares a `piiClass: 'none'` event even
20
+ * when it names the subject, and a copy judged more harshly than its original is an
21
+ * incoherent rule, not a stricter one. So the predicate below is the outbox's own
22
+ * predicate (`subject_id = ? AND pii_class != 'none'`) applied to whatever spine
23
+ * envelope the payload embeds, at whatever depth. `subjectId` and `piiClass` occur
24
+ * together only on `domainEventShape`, which is what makes the walk kind-agnostic
25
+ * rather than a guess about `connector:*`.
26
+ *
27
+ * What it therefore does NOT reach is an intent kind that carries a subject's PII
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
31
+ * inventing an unclassified PII store inside the spine.
32
+ *
33
+ * This is a kernel module rather than two copies of the same SQL for
34
+ * `platformRequestHistoryQuery`'s reason, sharpened: three surfaces answer one question
35
+ * from this table, and a privacy guarantee that holds on one adapter is not a guarantee.
36
+ */
37
+ /**
38
+ * The one key a redacted intent payload carries, and nothing else in the system does.
39
+ *
40
+ * Underscore-prefixed on the `_substrat_*` habit: the marker is the platform's, not a
41
+ * field some vertical's payload could plausibly own.
42
+ */
43
+ export declare const REDACTED_INTENT_MARKER = "_substratRedacted";
44
+ /**
45
+ * What replaces a redacted intent's payload.
46
+ *
47
+ * The column is `payload TEXT NOT NULL` on both adapters, so the outbox's
48
+ * `SET payload = NULL` cannot transfer and *what a redacted intent looks like* is a
49
+ * shape to choose rather than a null to write. Three things decide this one:
50
+ *
51
+ * - **Obviously redacted, never plausible data.** A reader who has the row in front of
52
+ * them must not have to know the kind's schema to see that the content is gone.
53
+ * - **It fails every handler's parse.** Each drain handler opens with
54
+ * `<kind>Payload.parse(request.payload)`, and no kind's schema admits an object whose
55
+ * only key is this marker. So a drain that reaches the row after the redaction settles
56
+ * loudly instead of executing a tombstone as if it were an instruction. What this does
57
+ * NOT close, and should not be read as closing, is the drain that had already READ the
58
+ * payload when the erasure landed: it DELIVERS what it read. That window is the one the
59
+ * outbox redaction has too — a consumer mid-dispatch holds the payload it was handed —
60
+ * and closing it wants a lock across the drain hop rather than a shape here. Its
61
+ * WRITEBACK is closed separately and is not part of that residue: `settlePlatformRequest`
62
+ * is a compare-and-set on `status = 'pending'` on both adapters, so a stale pass cannot
63
+ * undo the redaction or put a provider's reply — which can quote the person — back into
64
+ * `last_error`. The two halves were one sentence in the first cut of this file, which
65
+ * made an avoidable write look as unavoidable as the delivery.
66
+ * - **It keeps the pseudonymous key, exactly as the outbox row does.** The redacted
67
+ * outbox row still carries `subject_id`; §5.3's "pseudonymous keys and transaction
68
+ * facts remain" is the same sentence here. A row that is blank for no stated reason
69
+ * reads as corruption; this one says what happened to it and when.
70
+ */
71
+ export declare function redactedIntentPayload(subjectId: string, at: string): string;
72
+ /**
73
+ * The note a redaction leaves on an intent that had already settled.
74
+ *
75
+ * `last_error` is overwritten rather than kept, and that is deliberate: it is free text
76
+ * a third party wrote about the delivery of this person's data — the surface #618 built
77
+ * precisely so a provider's own sentence ("…requires valid personal number field") is
78
+ * readable — so it is content about the subject, not envelope. An erasure that emptied
79
+ * `payload` and left a provider quoting the name two columns over would be the same bug
80
+ * this fixes, one column to the right.
81
+ */
82
+ export declare const REDACTED_INTENT_NOTE = "redacted by subject erasure (#37) \u2014 what this intent said is gone; that it happened, and when, is not";
83
+ /**
84
+ * The note a redaction leaves on an intent that was still `pending`.
85
+ *
86
+ * A pending row is redacted like any other AND settled `failed` in the same statement,
87
+ * because the two halves of the alternative are both wrong: leaving it pending-and-intact
88
+ * keeps the name (the defect), and leaving it pending-and-redacted hands the drain a
89
+ * tombstone to execute. `failed` is the truthful terminal state — the delivery did not
90
+ * happen and now cannot — and the executor delivery behind it was already journaled as
91
+ * routed, so nothing re-routes the event to replace this row.
92
+ */
93
+ export declare const CANCELLED_INTENT_NOTE = "cancelled by subject erasure (#37) \u2014 the payload was redacted before the drain reached it, so this intent never ran";
94
+ /** The row shape the redaction reads to decide. */
95
+ export interface PlatformRequestRedactionCandidate {
96
+ id: string;
97
+ payload: string;
98
+ }
99
+ /**
100
+ * What one spine redaction moved, per table.
101
+ *
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.
106
+ *
107
+ * It lives here rather than in an adapter because it is what the Durable-Object adapter
108
+ * hands back across its RPC, and the coordinator that reads it deliberately does not
109
+ * import the DO module (it would pull the whole scope class into a worker that only
110
+ * coordinates).
111
+ */
112
+ export interface SubjectRedactionCounts {
113
+ events: number;
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;
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'>;
129
+ /**
130
+ * The candidate read: every intent whose stored payload TEXT contains the subject id.
131
+ *
132
+ * A prefilter, not the decision — `intentPayloadCarriesSubject` decides. `instr` rather
133
+ * than `LIKE` because it is a literal substring search with no wildcard to escape, and
134
+ * the needle is the subject id as `JSON.stringify` would have written it into the
135
+ * payload (identical to the raw id for a `dataSubjectId`, which is a ULID and so has
136
+ * nothing to escape — spelled out anyway, since the host contract types the parameter
137
+ * `string`).
138
+ *
139
+ * Unindexed, and that is affordable: this runs once per staff-triggered erasure over one
140
+ * scope's journal, where the alternative is parsing every row's JSON in the host.
141
+ */
142
+ export declare function platformRequestRedactionQuery(subjectId: string): {
143
+ sql: string;
144
+ params: string[];
145
+ };
146
+ /**
147
+ * The write. One statement per redacted row, so the payload, the note and the pending
148
+ * row's settlement are one atomic change rather than three that can half-land.
149
+ *
150
+ * Every SET expression is evaluated against the ORIGINAL row in SQLite, so both `CASE
151
+ * WHEN status = 'pending'` arms read the status as it was before this statement touched
152
+ * it — which is what lets one statement both re-word the note and change the status it
153
+ * branched on. `settled_at` is COALESCEd so a row that already settled keeps the instant
154
+ * it settled at; only a cancelled pending row is stamped now.
155
+ *
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.
168
+ */
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 = ?";
170
+ /** The bound parameters for `PLATFORM_REQUEST_REDACTION_SQL`, in its declared order. */
171
+ export declare function platformRequestRedactionParams(id: string, subjectId: string, at: string): [string, string, string, string, string, string];
172
+ /**
173
+ * Does this stored payload carry a copy of an event the erasure redacts?
174
+ *
175
+ * The outbox's predicate, walked over the payload's structure: an object carrying BOTH
176
+ * `subjectId` equal to this subject AND a `piiClass` other than `'none'` IS a spine
177
+ * envelope — those two field names occur together nowhere else — so wherever the spine
178
+ * has copied one into an intent, the copy inherits the original's redaction.
179
+ *
180
+ * Returns false for a payload that IS already a tombstone (so a re-run after a crash
181
+ * converges rather than re-stamping) and false for a payload that is not JSON at all,
182
+ * which nothing in the kernel can write but which a stricter answer would have to invent
183
+ * a meaning for.
184
+ */
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 = ?";
273
+ //# sourceMappingURL=subject-redaction.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,362 @@
1
+ /**
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).
4
+ *
5
+ * `shredSubject` holds K-37's Tier-1 line — *the payload goes, the envelope stays* —
6
+ * and for a year that line reached exactly one table, `_substrat_outbox`. It is not the
7
+ * only place the spine keeps an event's payload. A CP-less host cannot run a connector
8
+ * (no directory, no credentials, no sanctioned egress), so each connector delivery
9
+ * becomes a `connector:<provider>` platform intent whose payload is the WHOLE
10
+ * `DomainEvent` — fat by design, because "the platform's handler needs everything the
11
+ * in-process handler would have been handed" (`connectorDispatchPayload`). That copy
12
+ * lands in `_substrat_platform_requests`, which nothing ever deletes: the retention is
13
+ * deliberate, `listPlatformRequestHistory` exists precisely so a settled row stays
14
+ * readable. So a name the redaction did not reach there survived in the live scope
15
+ * database, and in every export, backup and PITR window taken from it afterwards.
16
+ *
17
+ * **The rule this module implements is narrow on purpose:** an intent that carries a
18
+ * COPY OF AN EVENT is redacted exactly when the erasure redacts that event. Not "any
19
+ * payload mentioning the subject" — the outbox spares a `piiClass: 'none'` event even
20
+ * when it names the subject, and a copy judged more harshly than its original is an
21
+ * incoherent rule, not a stricter one. So the predicate below is the outbox's own
22
+ * predicate (`subject_id = ? AND pii_class != 'none'`) applied to whatever spine
23
+ * envelope the payload embeds, at whatever depth. `subjectId` and `piiClass` occur
24
+ * together only on `domainEventShape`, which is what makes the walk kind-agnostic
25
+ * rather than a guess about `connector:*`.
26
+ *
27
+ * What it therefore does NOT reach is an intent kind that carries a subject's PII
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
31
+ * inventing an unclassified PII store inside the spine.
32
+ *
33
+ * This is a kernel module rather than two copies of the same SQL for
34
+ * `platformRequestHistoryQuery`'s reason, sharpened: three surfaces answer one question
35
+ * from this table, and a privacy guarantee that holds on one adapter is not a guarantee.
36
+ */
37
+ /**
38
+ * The one key a redacted intent payload carries, and nothing else in the system does.
39
+ *
40
+ * Underscore-prefixed on the `_substrat_*` habit: the marker is the platform's, not a
41
+ * field some vertical's payload could plausibly own.
42
+ */
43
+ export const REDACTED_INTENT_MARKER = '_substratRedacted';
44
+ /**
45
+ * What replaces a redacted intent's payload.
46
+ *
47
+ * The column is `payload TEXT NOT NULL` on both adapters, so the outbox's
48
+ * `SET payload = NULL` cannot transfer and *what a redacted intent looks like* is a
49
+ * shape to choose rather than a null to write. Three things decide this one:
50
+ *
51
+ * - **Obviously redacted, never plausible data.** A reader who has the row in front of
52
+ * them must not have to know the kind's schema to see that the content is gone.
53
+ * - **It fails every handler's parse.** Each drain handler opens with
54
+ * `<kind>Payload.parse(request.payload)`, and no kind's schema admits an object whose
55
+ * only key is this marker. So a drain that reaches the row after the redaction settles
56
+ * loudly instead of executing a tombstone as if it were an instruction. What this does
57
+ * NOT close, and should not be read as closing, is the drain that had already READ the
58
+ * payload when the erasure landed: it DELIVERS what it read. That window is the one the
59
+ * outbox redaction has too — a consumer mid-dispatch holds the payload it was handed —
60
+ * and closing it wants a lock across the drain hop rather than a shape here. Its
61
+ * WRITEBACK is closed separately and is not part of that residue: `settlePlatformRequest`
62
+ * is a compare-and-set on `status = 'pending'` on both adapters, so a stale pass cannot
63
+ * undo the redaction or put a provider's reply — which can quote the person — back into
64
+ * `last_error`. The two halves were one sentence in the first cut of this file, which
65
+ * made an avoidable write look as unavoidable as the delivery.
66
+ * - **It keeps the pseudonymous key, exactly as the outbox row does.** The redacted
67
+ * outbox row still carries `subject_id`; §5.3's "pseudonymous keys and transaction
68
+ * facts remain" is the same sentence here. A row that is blank for no stated reason
69
+ * reads as corruption; this one says what happened to it and when.
70
+ */
71
+ export function redactedIntentPayload(subjectId, at) {
72
+ return JSON.stringify({
73
+ [REDACTED_INTENT_MARKER]: { reason: 'subject-erasure', subjectId, at },
74
+ });
75
+ }
76
+ /**
77
+ * The note a redaction leaves on an intent that had already settled.
78
+ *
79
+ * `last_error` is overwritten rather than kept, and that is deliberate: it is free text
80
+ * a third party wrote about the delivery of this person's data — the surface #618 built
81
+ * precisely so a provider's own sentence ("…requires valid personal number field") is
82
+ * readable — so it is content about the subject, not envelope. An erasure that emptied
83
+ * `payload` and left a provider quoting the name two columns over would be the same bug
84
+ * this fixes, one column to the right.
85
+ */
86
+ export const REDACTED_INTENT_NOTE = 'redacted by subject erasure (#37) — what this intent said is gone; that it happened, and when, is not';
87
+ /**
88
+ * The note a redaction leaves on an intent that was still `pending`.
89
+ *
90
+ * A pending row is redacted like any other AND settled `failed` in the same statement,
91
+ * because the two halves of the alternative are both wrong: leaving it pending-and-intact
92
+ * keeps the name (the defect), and leaving it pending-and-redacted hands the drain a
93
+ * tombstone to execute. `failed` is the truthful terminal state — the delivery did not
94
+ * happen and now cannot — and the executor delivery behind it was already journaled as
95
+ * routed, so nothing re-routes the event to replace this row.
96
+ */
97
+ export const CANCELLED_INTENT_NOTE = 'cancelled by subject erasure (#37) — the payload was redacted before the drain reached it, so this intent never ran';
98
+ /**
99
+ * The candidate read: every intent whose stored payload TEXT contains the subject id.
100
+ *
101
+ * A prefilter, not the decision — `intentPayloadCarriesSubject` decides. `instr` rather
102
+ * than `LIKE` because it is a literal substring search with no wildcard to escape, and
103
+ * the needle is the subject id as `JSON.stringify` would have written it into the
104
+ * payload (identical to the raw id for a `dataSubjectId`, which is a ULID and so has
105
+ * nothing to escape — spelled out anyway, since the host contract types the parameter
106
+ * `string`).
107
+ *
108
+ * Unindexed, and that is affordable: this runs once per staff-triggered erasure over one
109
+ * scope's journal, where the alternative is parsing every row's JSON in the host.
110
+ */
111
+ export function platformRequestRedactionQuery(subjectId) {
112
+ return {
113
+ 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)],
117
+ };
118
+ }
119
+ /**
120
+ * The write. One statement per redacted row, so the payload, the note and the pending
121
+ * row's settlement are one atomic change rather than three that can half-land.
122
+ *
123
+ * Every SET expression is evaluated against the ORIGINAL row in SQLite, so both `CASE
124
+ * WHEN status = 'pending'` arms read the status as it was before this statement touched
125
+ * it — which is what lets one statement both re-word the note and change the status it
126
+ * branched on. `settled_at` is COALESCEd so a row that already settled keeps the instant
127
+ * it settled at; only a cancelled pending row is stamped now.
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.
141
+ */
142
+ export const PLATFORM_REQUEST_REDACTION_SQL = `UPDATE _substrat_platform_requests
143
+ SET payload = ?,
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,
147
+ settled_at = COALESCE(settled_at, ?),
148
+ status = CASE WHEN status = 'pending' THEN 'failed' ELSE status END
149
+ WHERE id = ?`;
150
+ /** The bound parameters for `PLATFORM_REQUEST_REDACTION_SQL`, in its declared order. */
151
+ export function platformRequestRedactionParams(id, subjectId, at) {
152
+ const tombstone = redactedIntentPayload(subjectId, at);
153
+ return [tombstone, CANCELLED_INTENT_NOTE, REDACTED_INTENT_NOTE, tombstone, at, id];
154
+ }
155
+ /**
156
+ * Does this stored payload carry a copy of an event the erasure redacts?
157
+ *
158
+ * The outbox's predicate, walked over the payload's structure: an object carrying BOTH
159
+ * `subjectId` equal to this subject AND a `piiClass` other than `'none'` IS a spine
160
+ * envelope — those two field names occur together nowhere else — so wherever the spine
161
+ * has copied one into an intent, the copy inherits the original's redaction.
162
+ *
163
+ * Returns false for a payload that IS already a tombstone (so a re-run after a crash
164
+ * converges rather than re-stamping) and false for a payload that is not JSON at all,
165
+ * which nothing in the kernel can write but which a stricter answer would have to invent
166
+ * a meaning for.
167
+ */
168
+ export function intentPayloadCarriesSubject(payloadText, subjectId) {
169
+ let parsed;
170
+ try {
171
+ parsed = JSON.parse(payloadText);
172
+ }
173
+ catch {
174
+ return false;
175
+ }
176
+ if (isRedactedPayload(parsed))
177
+ return false;
178
+ return carriesSubject(parsed, subjectId);
179
+ }
180
+ /**
181
+ * Is this payload one we already redacted?
182
+ *
183
+ * **Asked of the WHOLE payload, once, and never inside the walk (#1600 review).** The
184
+ * first cut short-circuited the walk at any object carrying the marker key, which made
185
+ * `{ _substratRedacted: false, event: <a real envelope> }` a payload the erasure stepped
186
+ * straight past, PII and all — and the comment beside it claimed the check was redundant
187
+ * belt-and-braces because a tombstone has no `piiClass`. That was true of the tombstone
188
+ * and false of everything else wearing its key. An intent payload is `unknown` and module
189
+ * code chooses it, so a marker that means "stop looking" must not be something a payload
190
+ * can merely CONTAIN.
191
+ *
192
+ * The invariant asked instead is un-forgeable in the only way that matters: *a payload
193
+ * that is nothing but a redaction tombstone is already redacted*. One top-level key, and
194
+ * that key's value says why. Anything beside the marker is not this, gets walked, and is
195
+ * judged on its own contents — which is exactly right, because something beside the
196
+ * marker is something the erasure might need to reach. Inner fields beyond `reason` are
197
+ * deliberately not pinned: a later tombstone that carries more would still be nothing but
198
+ * a tombstone, and hard-coding its shape here is how idempotency would quietly regress.
199
+ */
200
+ function isRedactedPayload(parsed) {
201
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed))
202
+ return false;
203
+ const keys = Object.keys(parsed);
204
+ if (keys.length !== 1 || keys[0] !== REDACTED_INTENT_MARKER)
205
+ return false;
206
+ const marker = parsed[REDACTED_INTENT_MARKER];
207
+ return (typeof marker === 'object' &&
208
+ marker !== null &&
209
+ !Array.isArray(marker) &&
210
+ marker['reason'] === 'subject-erasure');
211
+ }
212
+ function carriesSubject(node, subjectId) {
213
+ if (Array.isArray(node))
214
+ return node.some((child) => carriesSubject(child, subjectId));
215
+ if (node === null || typeof node !== 'object')
216
+ return false;
217
+ const obj = node;
218
+ if (obj['subjectId'] === subjectId &&
219
+ typeof obj['piiClass'] === 'string' &&
220
+ obj['piiClass'] !== 'none') {
221
+ return true;
222
+ }
223
+ return Object.values(obj).some((child) => carriesSubject(child, subjectId));
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 = ?`;
362
+ //# sourceMappingURL=subject-redaction.js.map
@@ -0,0 +1 @@
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"}