@substrat-run/kernel 0.139.0 → 0.140.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 (140) hide show
  1. package/dist/admin-log-ddl.d.ts +21 -0
  2. package/dist/admin-log-ddl.d.ts.map +1 -0
  3. package/dist/admin-log-ddl.js +32 -0
  4. package/dist/admin-log-ddl.js.map +1 -0
  5. package/dist/attachment-extractor.d.ts +12 -1
  6. package/dist/attachment-extractor.d.ts.map +1 -1
  7. package/dist/attachment-extractor.js +10 -5
  8. package/dist/attachment-extractor.js.map +1 -1
  9. package/dist/audit-outcome.d.ts +149 -0
  10. package/dist/audit-outcome.d.ts.map +1 -0
  11. package/dist/audit-outcome.js +233 -0
  12. package/dist/audit-outcome.js.map +1 -0
  13. package/dist/connect-links.d.ts +91 -0
  14. package/dist/connect-links.d.ts.map +1 -0
  15. package/dist/connect-links.js +189 -0
  16. package/dist/connect-links.js.map +1 -0
  17. package/dist/connect-state.d.ts +12 -3
  18. package/dist/connect-state.d.ts.map +1 -1
  19. package/dist/connect-state.js +8 -3
  20. package/dist/connect-state.js.map +1 -1
  21. package/dist/derived-object.d.ts +14 -0
  22. package/dist/derived-object.d.ts.map +1 -0
  23. package/dist/derived-object.js +2 -0
  24. package/dist/derived-object.js.map +1 -0
  25. package/dist/derived-objects.d.ts +108 -0
  26. package/dist/derived-objects.d.ts.map +1 -0
  27. package/dist/derived-objects.js +199 -0
  28. package/dist/derived-objects.js.map +1 -0
  29. package/dist/email-intent.d.ts +56 -0
  30. package/dist/email-intent.d.ts.map +1 -0
  31. package/dist/email-intent.js +71 -0
  32. package/dist/email-intent.js.map +1 -0
  33. package/dist/entity-edges.d.ts +6 -1
  34. package/dist/entity-edges.d.ts.map +1 -1
  35. package/dist/entity-edges.js +13 -0
  36. package/dist/entity-edges.js.map +1 -1
  37. package/dist/entity-grant-shape.d.ts +94 -0
  38. package/dist/entity-grant-shape.d.ts.map +1 -0
  39. package/dist/entity-grant-shape.js +329 -0
  40. package/dist/entity-grant-shape.js.map +1 -0
  41. package/dist/entity-grant.d.ts +45 -0
  42. package/dist/entity-grant.d.ts.map +1 -0
  43. package/dist/entity-grant.js +71 -0
  44. package/dist/entity-grant.js.map +1 -0
  45. package/dist/entity-state-reads.d.ts +4 -7
  46. package/dist/entity-state-reads.d.ts.map +1 -1
  47. package/dist/entity-state-reads.js +13 -57
  48. package/dist/entity-state-reads.js.map +1 -1
  49. package/dist/entity-state.d.ts +37 -10
  50. package/dist/entity-state.d.ts.map +1 -1
  51. package/dist/entity-state.js +80 -69
  52. package/dist/entity-state.js.map +1 -1
  53. package/dist/entity-trash.d.ts +245 -0
  54. package/dist/entity-trash.d.ts.map +1 -0
  55. package/dist/entity-trash.js +380 -0
  56. package/dist/entity-trash.js.map +1 -0
  57. package/dist/grant-scoped-read.d.ts +41 -0
  58. package/dist/grant-scoped-read.d.ts.map +1 -0
  59. package/dist/grant-scoped-read.js +100 -0
  60. package/dist/grant-scoped-read.js.map +1 -0
  61. package/dist/index.d.ts +31 -5
  62. package/dist/index.d.ts.map +1 -1
  63. package/dist/index.js +20 -4
  64. package/dist/index.js.map +1 -1
  65. package/dist/kernel-outbox.d.ts +14 -0
  66. package/dist/kernel-outbox.d.ts.map +1 -0
  67. package/dist/kernel-outbox.js +37 -0
  68. package/dist/kernel-outbox.js.map +1 -0
  69. package/dist/list-index.d.ts +11 -2
  70. package/dist/list-index.d.ts.map +1 -1
  71. package/dist/list-index.js +29 -21
  72. package/dist/list-index.js.map +1 -1
  73. package/dist/mail-sender.d.ts +78 -0
  74. package/dist/mail-sender.d.ts.map +1 -0
  75. package/dist/mail-sender.js +2 -0
  76. package/dist/mail-sender.js.map +1 -0
  77. package/dist/membership-executor.d.ts +0 -52
  78. package/dist/membership-executor.d.ts.map +1 -1
  79. package/dist/membership-executor.js +19 -59
  80. package/dist/membership-executor.js.map +1 -1
  81. package/dist/migration-digest.d.ts +161 -0
  82. package/dist/migration-digest.d.ts.map +1 -0
  83. package/dist/migration-digest.js +237 -0
  84. package/dist/migration-digest.js.map +1 -0
  85. package/dist/module-erasure.d.ts +156 -0
  86. package/dist/module-erasure.d.ts.map +1 -0
  87. package/dist/module-erasure.js +440 -0
  88. package/dist/module-erasure.js.map +1 -0
  89. package/dist/peer.d.ts +9 -1
  90. package/dist/peer.d.ts.map +1 -1
  91. package/dist/peer.js +29 -0
  92. package/dist/peer.js.map +1 -1
  93. package/dist/permission-checker.d.ts +6 -0
  94. package/dist/permission-checker.d.ts.map +1 -1
  95. package/dist/permission-checker.js.map +1 -1
  96. package/dist/permission-eval.d.ts +10 -1
  97. package/dist/permission-eval.d.ts.map +1 -1
  98. package/dist/permission-eval.js +51 -2
  99. package/dist/permission-eval.js.map +1 -1
  100. package/dist/platform-sweep.d.ts +1 -1
  101. package/dist/platform-sweep.d.ts.map +1 -1
  102. package/dist/platform-sweep.js +5 -0
  103. package/dist/platform-sweep.js.map +1 -1
  104. package/dist/scope-host.d.ts +301 -22
  105. package/dist/scope-host.d.ts.map +1 -1
  106. package/dist/scope-host.js +72 -5
  107. package/dist/scope-host.js.map +1 -1
  108. package/dist/search-index.d.ts +8 -0
  109. package/dist/search-index.d.ts.map +1 -1
  110. package/dist/search-index.js +51 -21
  111. package/dist/search-index.js.map +1 -1
  112. package/dist/spine-guard.d.ts +7 -0
  113. package/dist/spine-guard.d.ts.map +1 -1
  114. package/dist/spine-guard.js +54 -6
  115. package/dist/spine-guard.js.map +1 -1
  116. package/dist/spine-restore.d.ts.map +1 -1
  117. package/dist/spine-restore.js +5 -0
  118. package/dist/spine-restore.js.map +1 -1
  119. package/dist/sql-statements.d.ts +71 -0
  120. package/dist/sql-statements.d.ts.map +1 -0
  121. package/dist/sql-statements.js +191 -0
  122. package/dist/sql-statements.js.map +1 -0
  123. package/dist/subject-redaction.d.ts +9 -1
  124. package/dist/subject-redaction.d.ts.map +1 -1
  125. package/dist/subject-redaction.js.map +1 -1
  126. package/dist/system-switch-record.d.ts +3 -0
  127. package/dist/system-switch-record.d.ts.map +1 -1
  128. package/dist/system-switch-record.js +2 -0
  129. package/dist/system-switch-record.js.map +1 -1
  130. package/dist/table-ownership.d.ts +103 -0
  131. package/dist/table-ownership.d.ts.map +1 -0
  132. package/dist/table-ownership.js +285 -0
  133. package/dist/table-ownership.js.map +1 -0
  134. package/dist/timeline.d.ts +4 -4
  135. package/dist/timeline.js +4 -4
  136. package/dist/visible-continuation.d.ts +38 -0
  137. package/dist/visible-continuation.d.ts.map +1 -0
  138. package/dist/visible-continuation.js +162 -0
  139. package/dist/visible-continuation.js.map +1 -0
  140. package/package.json +2 -2
@@ -0,0 +1,199 @@
1
+ /**
2
+ * What the kernel derived onto a module's tables, checked and put back (#2090).
3
+ *
4
+ * | Object | Is | When it is missing or not what the kernel emits |
5
+ * |---|---|---|
6
+ * | `_substrat_archived_at` / `_substrat_trashed_at` (#119) | state | fail closed: it cannot be invented again |
7
+ * | the `born` / `moved` archive/trash guard triggers (#119) | derived | put back |
8
+ * | the derived list indexes, partial per view on a stateful table (#811) | derived | put back |
9
+ | the purge sweep's partial index on a trashable table with a horizon (#119) | derived | put back |
10
+ * | the search index and the three triggers keeping it in step (#827) | derived | rebuilt from the rows |
11
+ *
12
+ * The search triggers are a privacy fact, not only a freshness one: subject erasure rewrites a
13
+ * row's text and relies on the update and delete triggers to take the old text out of the index.
14
+ * Without them, erased text stays searchable.
15
+ *
16
+ * **An object is judged by its definition, not its name.** `sqlite_master` holds each one's
17
+ * CREATE statement as it was run, and the kernel knows the statement it emits
18
+ * (`DerivedObject`), so a same-named trigger that does nothing, an index on the wrong columns or
19
+ * with the wrong partial predicate, and an object filed under another table are all "not
20
+ * there". The text is compared exactly: nothing but the kernel writes these objects, and a
21
+ * normalisation that forgave whitespace would forgive it inside a string literal too — a guard
22
+ * keyed on a different entity type. An object an older kernel worded differently is re-created
23
+ * once.
24
+ *
25
+ * Four places ask, and all four come here, so "what a table is owed" is written once:
26
+ *
27
+ * - **runtime DDL** (`ctx.sql`'s after-DDL hook, #1811) repairs inside the operation's
28
+ * transaction, whenever the scope has any derived plan. The text guard already refuses the DDL
29
+ * known to move a stateful table's rows; a search-only or list-only table has no such guard,
30
+ * and a module may rebuild it — this puts its triggers and indexes back before the operation
31
+ * commits. A lost state column fails the operation, and the DDL with it.
32
+ * - **a migration** checks the state columns after its own SQL, inside its own transaction, so a
33
+ * migration that drops one rolls back with nothing applied. The last migration of a pass then
34
+ * repairs the derived objects, inside that same transaction: SQLite's ordinary
35
+ * create-copy-rename rebuild of a table drops its triggers and indexes with it, and the
36
+ * migrations that made them are journaled, so they never run again. Only after the last,
37
+ * because a rebuild split over two migrations copies its rows in the second, and a guard put
38
+ * back in between would refuse the archived ones — which also means such a split must ship
39
+ * in ONE deploy: split over two, the guard is back before the copy runs.
40
+ * - **a wake with nothing pending** (and every `migrateScope`) repairs the same way, once per
41
+ * instance. That is what reaches a scope stripped before this existed, whose migrations are
42
+ * all journaled and will never run again.
43
+ * - **a restore** repairs after the rows, inside the load's transaction, which may legitimately
44
+ * bring them archived or trashed.
45
+ *
46
+ * ## What the journal says a table is owed
47
+ *
48
+ * Every expectation comes from `_substrat_migrations`, not only from the declarations: a scope
49
+ * mid-upgrade has declared a trash its column migration has not added yet, and a restored dump
50
+ * carries the journal of the scope it came from. A column is owed once the migration that added
51
+ * it is journaled; a derived object once the migration that derived it, for the CURRENT
52
+ * declaration, is. An object from a withdrawn declaration is not this module's business.
53
+ */
54
+ import { SubstratError } from '@substrat-run/contracts';
55
+ import { entityStateGuardVersion, entityStateTriggerDdl, entityStateTriggerObjects, purgeIndexObjects, purgeIndexRepairDdl, purgeIndexVersion, stateColumnVersionsOf, } from './entity-state.js';
56
+ import { listIndexDdl, listIndexObjects, listIndexVersion } from './list-index.js';
57
+ import { searchIndexDdl, searchIndexObjects, searchIndexVersion } from './search-index.js';
58
+ /**
59
+ * A table without a state column its journal says it has (`internal`). `migration` is the
60
+ * journaled `<module>@<version>` that added the column — what a scope failing closed on a wake,
61
+ * where no migration ran, records as its failure.
62
+ */
63
+ export class StateColumnLost extends SubstratError {
64
+ migration;
65
+ constructor(migration, message) {
66
+ super('internal', message);
67
+ this.migration = migration;
68
+ }
69
+ }
70
+ /** `main`'s own catalogue by lowercased name — so a same-named temp object cannot stand in. */
71
+ function catalogueOf(sql) {
72
+ return new Map(sql
73
+ .query(`SELECT type, name, tbl_name, sql FROM main.sqlite_master`)
74
+ .map((r) => [r.name.toLowerCase(), { type: r.type, table: r.tbl_name, sql: r.sql }]));
75
+ }
76
+ /** The first object of `d` the catalogue does not hold exactly as the kernel emits it. */
77
+ const wrongIn = (catalogue, d) => d.objects.find((o) => {
78
+ const found = catalogue.get(o.name.toLowerCase());
79
+ return (found?.type !== o.type ||
80
+ found.table.toLowerCase() !== o.table.toLowerCase() ||
81
+ found.sql !== o.sql);
82
+ });
83
+ const columnsOn = (sql, table) => new Set(sql.query(`SELECT name FROM pragma_table_info(?, 'main')`, [table]).map((r) => r.name.toLowerCase()));
84
+ function expectations(sql, plans) {
85
+ const journal = new Set(sql.query(`SELECT module_id, version FROM _substrat_migrations`).map((r) => `${r.module_id}@${r.version}`));
86
+ const applied = (moduleId, version) => journal.has(`${moduleId}@${version}`);
87
+ const byTable = new Map();
88
+ const entry = (table) => {
89
+ const key = table.toLowerCase();
90
+ if (!byTable.has(key))
91
+ byTable.set(key, { table, derived: [] });
92
+ return byTable.get(key);
93
+ };
94
+ for (const plan of plans.state.values()) {
95
+ const e = entry(plan.table);
96
+ const columns = stateColumnVersionsOf(plan)
97
+ .filter((c) => applied(plan.moduleId, c.version))
98
+ .map((c) => ({ column: c.column, migration: `${plan.moduleId}@${c.version}` }));
99
+ if (columns.length)
100
+ e.columns = columns;
101
+ if (applied(plan.moduleId, entityStateGuardVersion(plan))) {
102
+ e.derived.push({ objects: entityStateTriggerObjects(plan), ddl: entityStateTriggerDdl(plan) });
103
+ }
104
+ const purge = purgeIndexObjects(plan);
105
+ if (purge.length && applied(plan.moduleId, purgeIndexVersion(plan))) {
106
+ e.derived.push({ objects: purge, ddl: purgeIndexRepairDdl(plan) });
107
+ }
108
+ }
109
+ for (const plan of plans.lists.values()) {
110
+ const objects = listIndexObjects(plan);
111
+ if (objects.length && applied(plan.moduleId, listIndexVersion(plan))) {
112
+ entry(plan.table).derived.push({ objects, ddl: listIndexDdl(plan) });
113
+ }
114
+ }
115
+ // The DDL ends in a full `rebuild` from the content table, which is also what repairs an index
116
+ // that went stale while its triggers were gone or wrong.
117
+ for (const plan of plans.search.values()) {
118
+ if (applied(plan.moduleId, searchIndexVersion(plan))) {
119
+ entry(plan.table).derived.push({ objects: searchIndexObjects(plan), ddl: searchIndexDdl(plan) });
120
+ }
121
+ }
122
+ return [...byTable.values()];
123
+ }
124
+ const lost = (after, table, what, migration) => {
125
+ throw new StateColumnLost(migration, `${after} left '${table.table}' without ${what} — its archive/trash guarantees depend on it; nothing was changed`);
126
+ };
127
+ /**
128
+ * The state half: the table holds every column its journal added. Throws `StateColumnLost` on
129
+ * the first one gone. An absent table counts as losing them all, unless `absentTable` is
130
+ * `'skip'` — a restore's case, where a dump that did not carry a table is not made to invent one.
131
+ */
132
+ function assertColumns(sql, catalogue, table, after, absentTable = 'fail') {
133
+ if (!table.columns)
134
+ return;
135
+ if (catalogue.get(table.table.toLowerCase())?.type !== 'table') {
136
+ if (absentTable === 'skip')
137
+ return;
138
+ lost(after, table, 'its table', table.columns[0].migration);
139
+ }
140
+ const present = columnsOn(sql, table.table);
141
+ for (const c of table.columns)
142
+ if (!present.has(c.column))
143
+ lost(after, table, c.column, c.migration);
144
+ }
145
+ /**
146
+ * After runtime DDL, inside the operation's transaction: the same repair a migration pass makes.
147
+ * A lost state column throws `internal`, and the operation and its DDL roll back.
148
+ */
149
+ export function afterRuntimeDdl(sql, run, plans) {
150
+ repairDerivedObjects(sql, run, plans, { after: 'runtime DDL' });
151
+ }
152
+ /** Whether a scope has anything derived for `afterRuntimeDdl` to keep — the hook is installed only then. */
153
+ export const derivesAnything = (plans) => plans.state.size > 0 || plans.lists.size > 0 || plans.search.size > 0;
154
+ /**
155
+ * A migration's half, after its own SQL and inside its own transaction: the state columns are
156
+ * still where the journal put them. A migration that dropped one throws here and rolls back —
157
+ * the rows keep their state, and the scope fails closed naming the migration.
158
+ */
159
+ export function assertEntityStateColumns(sql, plans, after) {
160
+ if (plans.state.size === 0)
161
+ return; // most scopes: nothing holds state, so no read at all
162
+ const catalogue = catalogueOf(sql);
163
+ for (const table of expectations(sql, plans))
164
+ assertColumns(sql, catalogue, table, after);
165
+ }
166
+ /**
167
+ * Check the state columns, then re-create every derived set the journal says a table is owed
168
+ * and the catalogue does not hold exactly as emitted. Run inside the caller's transaction, so a
169
+ * failure — a lost column, or DDL that will not apply — rolls the caller back whole.
170
+ *
171
+ * `run` executes a multi-statement script (a Durable Object splits it first). `after` names what
172
+ * left the schema this way, for the message. `absentTable: 'skip'` is a restore's: a table the
173
+ * dump did not carry is left absent, and owed nothing. Everywhere else an owed table that is
174
+ * gone has lost its state, and fails closed.
175
+ */
176
+ export function repairDerivedObjects(sql, run, plans, opts) {
177
+ const catalogue = catalogueOf(sql);
178
+ for (const table of expectations(sql, plans)) {
179
+ assertColumns(sql, catalogue, table, opts.after, opts.absentTable);
180
+ if (catalogue.get(table.table.toLowerCase())?.type !== 'table')
181
+ continue;
182
+ for (const d of table.derived)
183
+ if (wrongIn(catalogue, d))
184
+ run(d.ddl);
185
+ }
186
+ }
187
+ /**
188
+ * Both adapters' migration pass, after one migration's SQL and inside its transaction: the
189
+ * column check, and — after the last migration of the pass — the derived objects repaired.
190
+ * `key` is `<module>@<version>`, which a failure names. A last entry the pass skips (journaled
191
+ * by a pass that ran first) needs nothing here: that pass repaired them after it.
192
+ */
193
+ export function afterMigration(sql, run, plans, key, last) {
194
+ if (last)
195
+ repairDerivedObjects(sql, run, plans, { after: `migration ${key}` });
196
+ else
197
+ assertEntityStateColumns(sql, plans, `migration ${key}`);
198
+ }
199
+ //# sourceMappingURL=derived-objects.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"derived-objects.js","sourceRoot":"","sources":["../src/derived-objects.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ import { type PlatformOutcomeEvent, type PlatformRequestFailure, type PlatformRequestId, type PlatformRequestStatus, type ScopeId, type SendEmailRequest, type TenantId } from '@substrat-run/contracts';
2
+ import type { OperationContext } from './scope-host.js';
3
+ import type { SwitchSql } from './system-switch.js';
4
+ /**
5
+ * Ask the platform to send one email, inside the operation (#2102).
6
+ *
7
+ * The send is a platform intent: a row written in this operation's transaction, so an
8
+ * operation that throws afterwards sends nothing, and one that commits is delivered by the
9
+ * platform's drain — retried while the provider defers it, and reported back as an
10
+ * `email.sent`, `email.refused` or `email.dead-lettered` event on `mail.about` (or on the
11
+ * intent, when the mail names no entity). The returned id is the `request` those events carry.
12
+ *
13
+ * Name the recipient's `subjectId` when they are a person in your data: the row holds their
14
+ * address and the message, and that id is how a subject erasure finds and cancels it.
15
+ *
16
+ * Parsed here rather than at drain time, so a malformed address fails the operation that wrote
17
+ * it, in front of the person who typed it, instead of a refusal event minutes later. Needs the
18
+ * vertical's `emailSender` grant, as the relay always has; without it the send is refused.
19
+ */
20
+ export declare function requestEmail(ctx: Pick<OperationContext, 'requestPlatform'>, mail: Omit<SendEmailRequest, 'piiClass'>): PlatformRequestId;
21
+ /** What the scope stamps on an outcome event it writes: the drain names the rest. */
22
+ export interface OutcomeEventStamp {
23
+ tenantId: TenantId;
24
+ scopeId: ScopeId;
25
+ /** ISO 8601 — the settle's instant. */
26
+ now: string;
27
+ mintEventId(ms: number): string;
28
+ /** The deploy writing it, for the outbox's `version` column. */
29
+ version: string | null;
30
+ }
31
+ export interface PlatformRequestSettle {
32
+ status: PlatformRequestStatus;
33
+ result?: unknown;
34
+ lastError?: string | null;
35
+ failure?: PlatformRequestFailure | null;
36
+ /** #2102: the event this settle writes into the scope, when it moves the row out of `pending`. */
37
+ event?: PlatformOutcomeEvent | null;
38
+ }
39
+ /**
40
+ * Journal a platform-request outcome, and write its outcome event (#2102) — one step, so both
41
+ * adapters say the same thing and the event commits with the settle or not at all. Run it in a
42
+ * transaction.
43
+ *
44
+ * **Compare-and-set on `pending` (#1600 review).** The drain reads pending rows, runs a handler,
45
+ * then settles — and between the read and the settle a subject erasure can redact the row.
46
+ * Settling by `id` alone let that stale pass overwrite the redaction and write a provider's
47
+ * reply, which can quote the person, back into `last_error`. A settle that finds the row
48
+ * already terminal does nothing, deliberately silently — throwing would make the drain's
49
+ * blanket catch retry a row that is correctly over. The event follows the same rule: written
50
+ * only by the settle that actually moved the row, so a repeated settle never writes it twice.
51
+ *
52
+ * The event is refused (before anything is written) on a `pending` settle — an outcome event
53
+ * says the request is over — and when its `request` is not the row being settled.
54
+ */
55
+ export declare function settlePlatformRequestIn(db: SwitchSql, id: PlatformRequestId, outcome: PlatformRequestSettle, stamp: OutcomeEventStamp): void;
56
+ //# sourceMappingURL=email-intent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email-intent.d.ts","sourceRoot":"","sources":["../src/email-intent.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,iBAAiB,EACtB,KAAK,qBAAqB,EAC1B,KAAK,OAAO,EACZ,KAAK,gBAAgB,EACrB,KAAK,QAAQ,EACd,MAAM,yBAAyB,CAAC;AAEjC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAEpD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,YAAY,CAC1B,GAAG,EAAE,IAAI,CAAC,gBAAgB,EAAE,iBAAiB,CAAC,EAC9C,IAAI,EAAE,IAAI,CAAC,gBAAgB,EAAE,UAAU,CAAC,GACvC,iBAAiB,CAKnB;AAED,qFAAqF;AACrF,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,QAAQ,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,uCAAuC;IACvC,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;IAChC,gEAAgE;IAChE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,qBAAqB,CAAC;IAC9B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,OAAO,CAAC,EAAE,sBAAsB,GAAG,IAAI,CAAC;IACxC,kGAAkG;IAClG,KAAK,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAC;CACrC;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,uBAAuB,CACrC,EAAE,EAAE,SAAS,EACb,EAAE,EAAE,iBAAiB,EACrB,OAAO,EAAE,qBAAqB,EAC9B,KAAK,EAAE,iBAAiB,GACvB,IAAI,CAsCN"}
@@ -0,0 +1,71 @@
1
+ import { SEND_EMAIL_KIND, domainEvent, eventId, platformOutcomeEvent, sendEmailRequest, } from '@substrat-run/contracts';
2
+ import { KERNEL_ACTOR, kernelOutboxInsertSql } from './kernel-outbox.js';
3
+ /**
4
+ * Ask the platform to send one email, inside the operation (#2102).
5
+ *
6
+ * The send is a platform intent: a row written in this operation's transaction, so an
7
+ * operation that throws afterwards sends nothing, and one that commits is delivered by the
8
+ * platform's drain — retried while the provider defers it, and reported back as an
9
+ * `email.sent`, `email.refused` or `email.dead-lettered` event on `mail.about` (or on the
10
+ * intent, when the mail names no entity). The returned id is the `request` those events carry.
11
+ *
12
+ * Name the recipient's `subjectId` when they are a person in your data: the row holds their
13
+ * address and the message, and that id is how a subject erasure finds and cancels it.
14
+ *
15
+ * Parsed here rather than at drain time, so a malformed address fails the operation that wrote
16
+ * it, in front of the person who typed it, instead of a refusal event minutes later. Needs the
17
+ * vertical's `emailSender` grant, as the relay always has; without it the send is refused.
18
+ */
19
+ export function requestEmail(ctx, mail) {
20
+ const parsed = sendEmailRequest.parse(mail);
21
+ // Classified like an event naming the same person, so a subject erasure reaches the row.
22
+ const payload = parsed.subjectId ? { ...parsed, piiClass: 'direct' } : parsed;
23
+ return ctx.requestPlatform({ kind: SEND_EMAIL_KIND, payload });
24
+ }
25
+ /**
26
+ * Journal a platform-request outcome, and write its outcome event (#2102) — one step, so both
27
+ * adapters say the same thing and the event commits with the settle or not at all. Run it in a
28
+ * transaction.
29
+ *
30
+ * **Compare-and-set on `pending` (#1600 review).** The drain reads pending rows, runs a handler,
31
+ * then settles — and between the read and the settle a subject erasure can redact the row.
32
+ * Settling by `id` alone let that stale pass overwrite the redaction and write a provider's
33
+ * reply, which can quote the person, back into `last_error`. A settle that finds the row
34
+ * already terminal does nothing, deliberately silently — throwing would make the drain's
35
+ * blanket catch retry a row that is correctly over. The event follows the same rule: written
36
+ * only by the settle that actually moved the row, so a repeated settle never writes it twice.
37
+ *
38
+ * The event is refused (before anything is written) on a `pending` settle — an outcome event
39
+ * says the request is over — and when its `request` is not the row being settled.
40
+ */
41
+ export function settlePlatformRequestIn(db, id, outcome, stamp) {
42
+ const event = outcome.event == null ? null : platformOutcomeEvent.parse(outcome.event);
43
+ if (event && outcome.status === 'pending') {
44
+ throw new Error(`settle of platform request ${id}: an outcome event needs a terminal status, not 'pending'`);
45
+ }
46
+ if (event && event.payload.request !== id) {
47
+ throw new Error(`settle of platform request ${id}: the outcome event names request ${event.payload.request}`);
48
+ }
49
+ const was = db.all('SELECT status FROM _substrat_platform_requests WHERE id = ?', id)[0]?.status;
50
+ db.run(`UPDATE _substrat_platform_requests
51
+ SET status = ?, result = COALESCE(?, result), last_error = ?, last_failure = ?,
52
+ attempts = attempts + 1, settled_at = ?
53
+ WHERE id = ? AND status = 'pending'`, outcome.status, outcome.result === undefined ? null : JSON.stringify(outcome.result), outcome.lastError ?? null, outcome.failure == null ? null : JSON.stringify(outcome.failure), outcome.status === 'pending' ? null : stamp.now, id);
54
+ if (!event || was !== 'pending')
55
+ return;
56
+ const full = domainEvent.parse({
57
+ id: eventId.parse(stamp.mintEventId(Date.parse(stamp.now))),
58
+ type: event.type,
59
+ schemaVersion: 1,
60
+ occurredAt: stamp.now,
61
+ tenantId: stamp.tenantId,
62
+ scopeId: stamp.scopeId,
63
+ actor: KERNEL_ACTOR,
64
+ entity: event.entity,
65
+ piiClass: 'none',
66
+ payload: event.payload,
67
+ });
68
+ const st = kernelOutboxInsertSql(full, stamp.version);
69
+ db.run(st.sql, ...st.params);
70
+ }
71
+ //# sourceMappingURL=email-intent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email-intent.js","sourceRoot":"","sources":["../src/email-intent.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { type DomainEventInput, type Instant } from '@substrat-run/contracts';
1
+ import { type DomainEventInput, type EntityRef, type Instant } from '@substrat-run/contracts';
2
2
  import type { OperationContext, ScopedSql } from './scope-host.js';
3
3
  /**
4
4
  * `ctx.link` and `ctx.relink` (K-16, #1864), written once — both adapters hand the same
@@ -29,6 +29,11 @@ export interface EntityEdgeDeps {
29
29
  emit: (event: DomainEventInput) => void;
30
30
  /** K-42's read-only refusal, for the effecting verbs. */
31
31
  assertWrites: (verb: string) => void;
32
+ /**
33
+ * Is this entity out of reach as a parent (#119) — its type declares trash, and it is binned or
34
+ * missing (`isUnreachableParent`)? Refused by `link` and as `relink`'s `to`, one answer for both.
35
+ */
36
+ isUnreachableParent: (entity: EntityRef) => boolean;
32
37
  }
33
38
  export type EntityEdgeVerbs = Pick<OperationContext, 'link' | 'relink'>;
34
39
  export declare function createEntityEdgeVerbs(deps: EntityEdgeDeps): EntityEdgeVerbs;
@@ -1 +1 @@
1
- {"version":3,"file":"entity-edges.d.ts","sourceRoot":"","sources":["../src/entity-edges.ts"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,gBAAgB,EAErB,KAAK,OAAO,EACb,MAAM,yBAAyB,CAAC;AAEjC,OAAO,KAAK,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEnE;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,cAAc;IAC7B,2FAA2F;IAC3F,GAAG,EAAE,SAAS,CAAC;IACf,mFAAmF;IACnF,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC;IACpD,kEAAkE;IAClE,GAAG,EAAE,OAAO,CAAC;IACb,2EAA2E;IAC3E,IAAI,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;IACxC,yDAAyD;IACzD,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC;AAED,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC,gBAAgB,EAAE,MAAM,GAAG,QAAQ,CAAC,CAAC;AAExE,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,cAAc,GAAG,eAAe,CA8I3E"}
1
+ {"version":3,"file":"entity-edges.d.ts","sourceRoot":"","sources":["../src/entity-edges.ts"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,gBAAgB,EACrB,KAAK,SAAS,EACd,KAAK,OAAO,EACb,MAAM,yBAAyB,CAAC;AAEjC,OAAO,KAAK,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEnE;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,cAAc;IAC7B,2FAA2F;IAC3F,GAAG,EAAE,SAAS,CAAC;IACf,mFAAmF;IACnF,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC;IACpD,kEAAkE;IAClE,GAAG,EAAE,OAAO,CAAC;IACb,2EAA2E;IAC3E,IAAI,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;IACxC,yDAAyD;IACzD,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC;;;OAGG;IACH,mBAAmB,EAAE,CAAC,MAAM,EAAE,SAAS,KAAK,OAAO,CAAC;CACrD;AAED,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC,gBAAgB,EAAE,MAAM,GAAG,QAAQ,CAAC,CAAC;AAExE,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,cAAc,GAAG,eAAe,CA4J3E"}
@@ -7,6 +7,17 @@ export function createEntityEdgeVerbs(deps) {
7
7
  `(declare it in a module manifest's entityRelations)`);
8
8
  }
9
9
  };
10
+ /**
11
+ * A binned entity is gone from everyone's view until it is restored (#119), so nothing new
12
+ * hangs off it. The refusal is the one a MISSING parent of the same type gets, word for word:
13
+ * `link` checks no permission, so a refusal that named the trash would tell a caller who may not
14
+ * see the parent that it exists. An ARCHIVED parent passes: archived is filed away and readable.
15
+ */
16
+ const assertParentReachable = (verb, parent) => {
17
+ if (deps.isUnreachableParent(parent)) {
18
+ throw substratError('not_found', `${verb}: ${parent.entityType}:${parent.entityId} not found`);
19
+ }
20
+ };
10
21
  /**
11
22
  * Refuse when `ancestorCandidate` is `child` itself, or already lies beneath `child` in the
12
23
  * live parent graph — shared by `link` (candidate = the new `parent`) and `relink` (candidate
@@ -54,6 +65,7 @@ export function createEntityEdgeVerbs(deps) {
54
65
  const c = entityObjectRef(child, 'ctx.link'); // #1856: an edge the walk can read back
55
66
  const p = entityObjectRef(parent, 'ctx.link');
56
67
  assertDeclared('ctx.link', child, parent);
68
+ assertParentReachable('ctx.link', parent);
57
69
  // Refuse a parent that is the child itself or already beneath it (#1875) — same question
58
70
  // `relink` asks of `to`, same walk.
59
71
  assertNoCycle('ctx.link', 'link', c, p);
@@ -80,6 +92,7 @@ export function createEntityEdgeVerbs(deps) {
80
92
  // whether or not its relation is still declared, so an edge that grants must stay
81
93
  // movable. What `from` must be is an edge that exists.
82
94
  assertDeclared('ctx.relink', child, to);
95
+ assertParentReachable('ctx.relink', to);
83
96
  // The walk's own `live` predicate, so "is a parent" means what a check means by it.
84
97
  const live = deps.sql.query(`SELECT 1 AS live FROM _substrat_tuples
85
98
  WHERE subject = ? AND relation = 'parent' AND object = ? AND ${liveTupleSql()}`, [c, f, deps.now]);
@@ -1 +1 @@
1
- {"version":3,"file":"entity-edges.js","sourceRoot":"","sources":["../src/entity-edges.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"entity-edges.js","sourceRoot":"","sources":["../src/entity-edges.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,94 @@
1
+ import { type EntityGrantShape, type EntityRef, type PrincipalId } from '@substrat-run/contracts';
2
+ import type { SwitchSql } from './system-switch.js';
3
+ /**
4
+ * A declared entity-grant SHAPE, reconciled (#2071).
5
+ *
6
+ * A vertical declares the keys a person is given on their own record (`ENTITY_GRANTS`:
7
+ * meridian's `EMPLOYEE_SELF` on `employee`, todo's owner grant on `owner`). They used to be
8
+ * minted key by key, once, when the person arrived, so a key added to the shape by a later
9
+ * release reached only the people who arrived after it, while `PERMISSIONS.md` said everyone
10
+ * held it.
11
+ *
12
+ * Three pieces, each one statement shared by both adapters:
13
+ *
14
+ * - {@link grantEntityShapeIn}: the grant. The shape's keys plus a MARKER tuple
15
+ * (`ENTITY_SHAPE_MARKER_RELATION`) on the same entity. The marker is what makes a person a
16
+ * holder of the shape, which no set of keys can: someone `ctx.grant`ed one of those keys
17
+ * holds part of the shape and must never be topped up to all of it.
18
+ * - {@link topUpEntityGrantShapes}: one bounded pass of the reconcile, writing each top-up's
19
+ * `entity.grants-topped-up` event beside it. Each live marker whose
20
+ * entity lacks a key of the CURRENT shape gets that key. A key with any row at all is
21
+ * skipped, and that includes a tombstone (K-21): a key someone took back from that person
22
+ * stays taken back. A key merely dropped from the shape is left where it is, because silently
23
+ * revoking authority on a deploy is the riskier mistake.
24
+ * - The retirement (#2082), in the same pass and budget, between the backfill and the top-up: a
25
+ * key the shape declares `retired` is tombstoned for every live marker that still holds it
26
+ * live, with one `entity.grants-retired` event per (person, entity). The tombstones are the
27
+ * progress; a record tuple (`shape:<type>`, `shape-retired:<key>`, `scope:<id>`) ends it once
28
+ * a batch comes back short, so a key granted to a holder again afterwards is left alone. A key
29
+ * back in the shape's `permissions` tombstones that record, so a later release may retire it
30
+ * again. One tuple is one authority: a direct grant of the key on the same entity to the same
31
+ * person is that row, and goes with it.
32
+ * - The backfill, inside the same budget, until it is done once per (scope, entity type): how
33
+ * people granted before markers existed become holders. For own-record holders it reads
34
+ * provenance, never key sets alone.
35
+ * The shape's declared `holder` says whose record each entity is (`'self'`: the entity id is
36
+ * the principal; a table column naming the principal), and a person is marked only on their
37
+ * own record, and only when they hold a row there (live or tombstoned) for some key of the
38
+ * shape — evidence they were given it. Someone `ctx.grant`ed even the whole shape on another
39
+ * person's record is never marked. A shape with no `holder` gets no backfill. Each batch marks
40
+ * at most what the pass's budget allows; the markers are the progress, since a marked pair is
41
+ * no longer a candidate, and a record tuple (`shape:<type>`, `shape-backfilled`, `scope:<id>`)
42
+ * ends it once a batch comes back short.
43
+ * - `holder: 'grantee'` (#2083), for a record that names no principal (a portal customer, a
44
+ * contact): whoever holds a live current or retired key of the shape on such an entity was given it.
45
+ * Each pass records current and retired keys (`shape-grantee-key`), and the explicit tuple
46
+ * writer makes non-shape grants refuse them on that entity type. A tuple from before the
47
+ * first such pass cannot be told apart. A marker already written by a shape grant persists
48
+ * through retirement, so its holder receives the new keys even if their old key was K alone.
49
+ *
50
+ * Only a shape declared `bootstrap: true` is reconciled: one a person is GIVEN on their own
51
+ * record. A sharing shape, which people reach through `ctx.grant` (todo's `list`), is skipped
52
+ * whole, backfill included, so a sharee is never marked a holder.
53
+ */
54
+ /** Holders topped up per pass — one transaction each, so a large scope never holds one long. */
55
+ export declare const SHAPE_TOP_UP_BATCH = 500;
56
+ /** The largest batch a caller may ask for: past this a pass is the long transaction it exists to avoid. */
57
+ export declare const SHAPE_TOP_UP_BATCH_MAX = 5000;
58
+ /** `batch`, or `validation_failed`: a pass size is a positive integer no larger than the max. */
59
+ export declare function shapeTopUpBatch(batch?: number): number;
60
+ /**
61
+ * The shape's grant to one person on one entity: the marker and every key, each an explicit
62
+ * write, so a re-grant brings back what a revoke tombstoned — as `ctx.grant` does.
63
+ * `entityObjectRef` refuses a ref the walk could not read back (#1856). Run it in ONE
64
+ * transaction, so the marker and its keys land together or not at all.
65
+ */
66
+ export declare function grantEntityShapeIn(db: SwitchSql, principal: PrincipalId, entity: EntityRef, permissions: readonly string[]): void;
67
+ /** Where a pass runs and how its events are stamped — the facts only the adapter holds. */
68
+ export interface ShapePass {
69
+ tenantId: string;
70
+ scopeId: string;
71
+ shapes: readonly EntityGrantShape[];
72
+ /** The pass's one instant: marker liveness and every event's `occurredAt`. */
73
+ now: string;
74
+ /** Rows of work at most: a backfill mark or a holder topped up each count one. */
75
+ limit: number;
76
+ /** The adapter's monotonic event-id mint, given the instant in ms. */
77
+ mintEventId: (ms: number) => string;
78
+ /** The deploy writing the events, for the outbox `version` column. */
79
+ version: string | null;
80
+ }
81
+ /**
82
+ * One pass of the reconcile over one scope, at most `limit` rows of work: backfill marks for any
83
+ * shape whose backfill is not done here, then holders whose retired keys are taken back, each
84
+ * with its `entity.grants-retired` event, then holders topped up, each with its
85
+ * `entity.grants-topped-up` event. `done` is false when the budget ran out, and the caller runs
86
+ * another pass. Run it inside ONE transaction, so the keys and their events commit together.
87
+ * Re-running a finished scope writes nothing.
88
+ */
89
+ export declare function topUpEntityGrantShapes(db: SwitchSql, pass: ShapePass): {
90
+ toppedUp: number;
91
+ retired: number;
92
+ done: boolean;
93
+ };
94
+ //# sourceMappingURL=entity-grant-shape.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entity-grant-shape.d.ts","sourceRoot":"","sources":["../src/entity-grant-shape.ts"],"names":[],"mappings":"AAAA,OAAO,EAUL,KAAK,gBAAgB,EACrB,KAAK,SAAS,EACd,KAAK,WAAW,EACjB,MAAM,yBAAyB,CAAC;AAMjC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAEpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AAEH,gGAAgG;AAChG,eAAO,MAAM,kBAAkB,MAAM,CAAC;AACtC,2GAA2G;AAC3G,eAAO,MAAM,sBAAsB,OAAO,CAAC;AAE3C,iGAAiG;AACjG,wBAAgB,eAAe,CAAC,KAAK,GAAE,MAA2B,GAAG,MAAM,CAS1E;AAiBD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CASjI;AAED,2FAA2F;AAC3F,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACpC,8EAA8E;IAC9E,GAAG,EAAE,MAAM,CAAC;IACZ,kFAAkF;IAClF,KAAK,EAAE,MAAM,CAAC;IACd,sEAAsE;IACtE,WAAW,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC,sEAAsE;IACtE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,EAAE,EAAE,SAAS,EACb,IAAI,EAAE,SAAS,GACd;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CA+EtD"}