@substrat-run/kernel 0.138.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 (164) 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 +21 -2
  6. package/dist/attachment-extractor.d.ts.map +1 -1
  7. package/dist/attachment-extractor.js +31 -6
  8. package/dist/attachment-extractor.js.map +1 -1
  9. package/dist/attachment-text.d.ts +64 -0
  10. package/dist/attachment-text.d.ts.map +1 -1
  11. package/dist/attachment-text.js +87 -16
  12. package/dist/attachment-text.js.map +1 -1
  13. package/dist/attribution.d.ts +36 -3
  14. package/dist/attribution.d.ts.map +1 -1
  15. package/dist/attribution.js +21 -8
  16. package/dist/attribution.js.map +1 -1
  17. package/dist/audit-outcome.d.ts +149 -0
  18. package/dist/audit-outcome.d.ts.map +1 -0
  19. package/dist/audit-outcome.js +233 -0
  20. package/dist/audit-outcome.js.map +1 -0
  21. package/dist/connect-links.d.ts +91 -0
  22. package/dist/connect-links.d.ts.map +1 -0
  23. package/dist/connect-links.js +189 -0
  24. package/dist/connect-links.js.map +1 -0
  25. package/dist/connect-state.d.ts +12 -3
  26. package/dist/connect-state.d.ts.map +1 -1
  27. package/dist/connect-state.js +8 -3
  28. package/dist/connect-state.js.map +1 -1
  29. package/dist/derived-object.d.ts +14 -0
  30. package/dist/derived-object.d.ts.map +1 -0
  31. package/dist/derived-object.js +2 -0
  32. package/dist/derived-object.js.map +1 -0
  33. package/dist/derived-objects.d.ts +108 -0
  34. package/dist/derived-objects.d.ts.map +1 -0
  35. package/dist/derived-objects.js +199 -0
  36. package/dist/derived-objects.js.map +1 -0
  37. package/dist/email-intent.d.ts +56 -0
  38. package/dist/email-intent.d.ts.map +1 -0
  39. package/dist/email-intent.js +71 -0
  40. package/dist/email-intent.js.map +1 -0
  41. package/dist/entity-edges.d.ts +6 -1
  42. package/dist/entity-edges.d.ts.map +1 -1
  43. package/dist/entity-edges.js +13 -0
  44. package/dist/entity-edges.js.map +1 -1
  45. package/dist/entity-grant-shape.d.ts +94 -0
  46. package/dist/entity-grant-shape.d.ts.map +1 -0
  47. package/dist/entity-grant-shape.js +329 -0
  48. package/dist/entity-grant-shape.js.map +1 -0
  49. package/dist/entity-grant.d.ts +45 -0
  50. package/dist/entity-grant.d.ts.map +1 -0
  51. package/dist/entity-grant.js +71 -0
  52. package/dist/entity-grant.js.map +1 -0
  53. package/dist/entity-state-reads.d.ts +40 -0
  54. package/dist/entity-state-reads.d.ts.map +1 -0
  55. package/dist/entity-state-reads.js +91 -0
  56. package/dist/entity-state-reads.js.map +1 -0
  57. package/dist/entity-state.d.ts +204 -0
  58. package/dist/entity-state.d.ts.map +1 -0
  59. package/dist/entity-state.js +356 -0
  60. package/dist/entity-state.js.map +1 -0
  61. package/dist/entity-trash.d.ts +245 -0
  62. package/dist/entity-trash.d.ts.map +1 -0
  63. package/dist/entity-trash.js +380 -0
  64. package/dist/entity-trash.js.map +1 -0
  65. package/dist/findings.d.ts +143 -0
  66. package/dist/findings.d.ts.map +1 -0
  67. package/dist/findings.js +463 -0
  68. package/dist/findings.js.map +1 -0
  69. package/dist/grant-scoped-read.d.ts +41 -0
  70. package/dist/grant-scoped-read.d.ts.map +1 -0
  71. package/dist/grant-scoped-read.js +100 -0
  72. package/dist/grant-scoped-read.js.map +1 -0
  73. package/dist/index.d.ts +40 -8
  74. package/dist/index.d.ts.map +1 -1
  75. package/dist/index.js +26 -6
  76. package/dist/index.js.map +1 -1
  77. package/dist/kernel-outbox.d.ts +14 -0
  78. package/dist/kernel-outbox.d.ts.map +1 -0
  79. package/dist/kernel-outbox.js +37 -0
  80. package/dist/kernel-outbox.js.map +1 -0
  81. package/dist/list-index.d.ts +34 -3
  82. package/dist/list-index.d.ts.map +1 -1
  83. package/dist/list-index.js +83 -31
  84. package/dist/list-index.js.map +1 -1
  85. package/dist/mail-sender.d.ts +78 -0
  86. package/dist/mail-sender.d.ts.map +1 -0
  87. package/dist/mail-sender.js +2 -0
  88. package/dist/mail-sender.js.map +1 -0
  89. package/dist/membership-executor.d.ts +7 -47
  90. package/dist/membership-executor.d.ts.map +1 -1
  91. package/dist/membership-executor.js +28 -53
  92. package/dist/membership-executor.js.map +1 -1
  93. package/dist/migration-digest.d.ts +161 -0
  94. package/dist/migration-digest.d.ts.map +1 -0
  95. package/dist/migration-digest.js +237 -0
  96. package/dist/migration-digest.js.map +1 -0
  97. package/dist/module-erasure.d.ts +156 -0
  98. package/dist/module-erasure.d.ts.map +1 -0
  99. package/dist/module-erasure.js +440 -0
  100. package/dist/module-erasure.js.map +1 -0
  101. package/dist/module-migrations.d.ts +6 -3
  102. package/dist/module-migrations.d.ts.map +1 -1
  103. package/dist/module-migrations.js +12 -5
  104. package/dist/module-migrations.js.map +1 -1
  105. package/dist/peer.d.ts +9 -1
  106. package/dist/peer.d.ts.map +1 -1
  107. package/dist/peer.js +29 -0
  108. package/dist/peer.js.map +1 -1
  109. package/dist/permission-checker.d.ts +6 -0
  110. package/dist/permission-checker.d.ts.map +1 -1
  111. package/dist/permission-checker.js.map +1 -1
  112. package/dist/permission-eval.d.ts +51 -1
  113. package/dist/permission-eval.d.ts.map +1 -1
  114. package/dist/permission-eval.js +104 -2
  115. package/dist/permission-eval.js.map +1 -1
  116. package/dist/platform-sweep.d.ts +8 -2
  117. package/dist/platform-sweep.d.ts.map +1 -1
  118. package/dist/platform-sweep.js +17 -0
  119. package/dist/platform-sweep.js.map +1 -1
  120. package/dist/scope-host.d.ts +479 -24
  121. package/dist/scope-host.d.ts.map +1 -1
  122. package/dist/scope-host.js +82 -7
  123. package/dist/scope-host.js.map +1 -1
  124. package/dist/scope-role-admin.d.ts +68 -0
  125. package/dist/scope-role-admin.d.ts.map +1 -0
  126. package/dist/scope-role-admin.js +80 -0
  127. package/dist/scope-role-admin.js.map +1 -0
  128. package/dist/search-index.d.ts +21 -1
  129. package/dist/search-index.d.ts.map +1 -1
  130. package/dist/search-index.js +60 -37
  131. package/dist/search-index.js.map +1 -1
  132. package/dist/spine-guard.d.ts +71 -2
  133. package/dist/spine-guard.d.ts.map +1 -1
  134. package/dist/spine-guard.js +393 -10
  135. package/dist/spine-guard.js.map +1 -1
  136. package/dist/spine-restore.d.ts.map +1 -1
  137. package/dist/spine-restore.js +5 -0
  138. package/dist/spine-restore.js.map +1 -1
  139. package/dist/sql-identifier.d.ts +12 -0
  140. package/dist/sql-identifier.d.ts.map +1 -0
  141. package/dist/sql-identifier.js +17 -0
  142. package/dist/sql-identifier.js.map +1 -0
  143. package/dist/sql-statements.d.ts +71 -0
  144. package/dist/sql-statements.d.ts.map +1 -0
  145. package/dist/sql-statements.js +191 -0
  146. package/dist/sql-statements.js.map +1 -0
  147. package/dist/subject-redaction.d.ts +9 -1
  148. package/dist/subject-redaction.d.ts.map +1 -1
  149. package/dist/subject-redaction.js.map +1 -1
  150. package/dist/system-switch-record.d.ts +3 -0
  151. package/dist/system-switch-record.d.ts.map +1 -1
  152. package/dist/system-switch-record.js +2 -0
  153. package/dist/system-switch-record.js.map +1 -1
  154. package/dist/table-ownership.d.ts +103 -0
  155. package/dist/table-ownership.d.ts.map +1 -0
  156. package/dist/table-ownership.js +285 -0
  157. package/dist/table-ownership.js.map +1 -0
  158. package/dist/timeline.d.ts +4 -4
  159. package/dist/timeline.js +4 -4
  160. package/dist/visible-continuation.d.ts +38 -0
  161. package/dist/visible-continuation.d.ts.map +1 -0
  162. package/dist/visible-continuation.js +162 -0
  163. package/dist/visible-continuation.js.map +1 -0
  164. package/package.json +2 -2
@@ -0,0 +1,204 @@
1
+ /**
2
+ * Entity archive and trash (#119): the kernel half, written once — both adapters hand
3
+ * `createEntityStateVerbs` the same things, the way `createEntityEdgeVerbs` is shared.
4
+ *
5
+ * ## What the kernel owns, and what it leaves to the vertical
6
+ *
7
+ * | Kernel | Vertical |
8
+ * |---|---|
9
+ * | The two columns on the entity's table, added by a derived migration | Which entities declare `archive` / `trash`, and the keys |
10
+ * | The transitions, their refusals, and the event each one emits | The operations that call the verbs, and their routes |
11
+ * | The declared key, re-checked on the entity by every verb and every trashed read | What an archived row looks like on a screen |
12
+ * | `ctx.page` / `ctx.search` leaving archived and trashed rows out by default | Its own hand-written `SELECT`s — see the gap below |
13
+ *
14
+ * ## The state is two columns
15
+ *
16
+ * `_substrat_archived_at` and `_substrat_trashed_at`, each an ISO instant or NULL. Apart, not
17
+ * one enum, so a trash never forgets an archive: `restore` clears the trash and leaves the
18
+ * archive, and an archived row that went to the bin comes back archived. The visible state is
19
+ * derived (contracts `entity-state.ts`): trashed, else archived, else active.
20
+ *
21
+ * Module code cannot write either column: `ctx.sql` refuses a write naming a `_substrat_*`
22
+ * column (`spine-guard.ts`), so the only way into or out of the trash is a verb that checks
23
+ * the key and emits the event. It can READ them, which is how a vertical's own `SELECT` keeps
24
+ * archived rows out of a screen `ctx.page` does not compose.
25
+ *
26
+ * ## The gap, stated
27
+ *
28
+ * The kernel filters the reads it composes — `ctx.page` and `ctx.search` — and nothing else.
29
+ * A handler's own `SELECT … FROM todo_lists WHERE id = ?` sees an archived or trashed row
30
+ * exactly as before. That is the honest limit of a kernel that does not parse module SQL:
31
+ * a get-by-id asks `ctx.entityState(ref)` and decides, and a hand-written list adds the
32
+ * predicate itself (`entityStateWhere`).
33
+ *
34
+ * ## Trash is not erasure, and not tenant deletion
35
+ *
36
+ * Trash keeps the row and every event about it. Subject erasure (#37) reaches a trashed row
37
+ * exactly as it reaches any other, because the row never moved tables; a tenant's deletion
38
+ * (#36) removes it with everything else. Nothing here extends a retention or shortens one.
39
+ */
40
+ import { type DomainEventInput, type EntityStateDeclaration, type EntityStateName, type Instant, type PermissionKey } from '@substrat-run/contracts';
41
+ import type { OperationContext, ScopedSql, SqlMigration } from './scope-host.js';
42
+ import type { DerivedObject } from './derived-object.js';
43
+ /** A resolved declaration: everything the DDL, the reads and the verbs need. */
44
+ export interface EntityStatePlan {
45
+ readonly moduleId: string;
46
+ readonly entityType: string;
47
+ readonly table: string;
48
+ readonly idColumn: string;
49
+ /** Present when the entity can be archived. */
50
+ readonly archivePermission?: PermissionKey;
51
+ /** Present when the entity can be trashed. */
52
+ readonly trashPermission?: PermissionKey;
53
+ /** Present when a trashed entity is purged after this many days (`entity-trash.ts`). */
54
+ readonly purgeAfterDays?: number;
55
+ }
56
+ /** The operation's own check — a pass is recorded as one of its authorizations (K-34). */
57
+ export type StateCheck = OperationContext['check'];
58
+ /**
59
+ * Resolve one module's declarations. Refuses rather than skips, for `searchIndexPlans`'s
60
+ * reason: a declaration the author believes is live that silently is not leaves a trashed
61
+ * row in every list with no error anywhere.
62
+ */
63
+ export declare function entityStatePlans(moduleId: string, declarations: readonly EntityStateDeclaration[] | undefined): EntityStatePlan[];
64
+ /**
65
+ * The migrations that add the columns — one per column, so declaring `trash` on an entity
66
+ * that already declared `archive` adds one column and re-runs nothing.
67
+ *
68
+ * **The version is the declaration**, as for search and list indexes. Unlike theirs, this DDL
69
+ * is not drop-then-create: a column holds the state, and dropping it would un-trash every row.
70
+ * So it runs once per column, ever. Withdrawing a declaration leaves the column where it is,
71
+ * holding what it held; re-declaring it finds the migration already applied.
72
+ *
73
+ * Applied after the module's own migrations (the table must exist) and before its list
74
+ * indexes (their partial `WHERE` names these columns) — `moduleMigrations` writes that order.
75
+ */
76
+ export declare function entityStateMigrations(moduleId: string, declarations: readonly EntityStateDeclaration[] | undefined): SqlMigration[];
77
+ /** The column migrations' versions, and the column each adds — what the journal says a table holds. */
78
+ export declare const stateColumnVersionsOf: (plan: EntityStatePlan) => {
79
+ version: string;
80
+ column: string;
81
+ }[];
82
+ /** The purge index's migration version (#119) — present only on a plan with a purge horizon. */
83
+ export declare const purgeIndexVersion: (plan: EntityStatePlan) => string;
84
+ /**
85
+ * The partial index the purge sweep walks (#119): trashed rows only, oldest trash first, so a
86
+ * sweep reads the due rows and nothing else. Its name is kernel-prefixed, so the reserved
87
+ * namespace. Empty for a plan without a purge horizon.
88
+ */
89
+ export declare function purgeIndexObjects(plan: EntityStatePlan): DerivedObject[];
90
+ /**
91
+ * The purge index's DDL. `IF NOT EXISTS` because a dump load runs it again after the rows are
92
+ * in; a repair that finds it wrong drops it first (`purgeIndexRepairDdl`).
93
+ */
94
+ export declare function purgeIndexDdl(plan: EntityStatePlan): string;
95
+ /** Drop-then-create, for a purge index the catalogue holds under its name but not as emitted. */
96
+ export declare function purgeIndexRepairDdl(plan: EntityStatePlan): string;
97
+ /**
98
+ * The guard triggers' version. By which columns they guard, so declaring a trash on an
99
+ * archivable entity rebuilds them to guard both.
100
+ */
101
+ export declare const entityStateGuardVersion: (plan: EntityStatePlan) => string;
102
+ /** The prefix of every trigger this module derives — kernel-owned, so the reserved one. */
103
+ export declare const ENTITY_STATE_TRIGGER_PREFIX = "_substrat_state_";
104
+ /**
105
+ * The kernel's authorization for ONE move (#119, Codex r2): the row `ctx.archive` & co. write
106
+ * just before their `UPDATE` and delete just after it, in the same transaction. The update
107
+ * trigger below refuses any change to the columns that has no such row — so the only writer the
108
+ * columns admit is the one that can write a `_substrat_*` TABLE, which `ctx.sql` refuses by name
109
+ * (a far simpler reading than finding an assignment target in an expression).
110
+ *
111
+ * Never holds a row between operations: written and removed inside one move, and a move that
112
+ * throws rolls back with its operation. Shared by both adapters' `KERNEL_DDL`, like the other
113
+ * kernel-owned spine tables, so the two cannot part company.
114
+ */
115
+ export declare const ENTITY_STATE_MOVES_TABLE = "_substrat_state_moves";
116
+ export declare const ENTITY_STATE_MOVES_DDL = "\n CREATE TABLE IF NOT EXISTS _substrat_state_moves (\n entity_type TEXT NOT NULL,\n entity_id TEXT NOT NULL,\n PRIMARY KEY (entity_type, entity_id)\n );\n";
117
+ /**
118
+ * The invariant below the column guard, as two triggers on the entity's table.
119
+ *
120
+ * `ctx.sql` refuses the writes that would set the columns (`assertNoReservedColumnWrite`), but
121
+ * that is a reading of SQL text, and a reading can miss a form — it did, twice. These hold
122
+ * whatever the text says:
123
+ *
124
+ * - **born** — `BEFORE INSERT`: a row is never inserted archived or trashed. Every row enters
125
+ * active.
126
+ * - **moved** — `BEFORE UPDATE OF` the columns: a change to either aborts unless the kernel's
127
+ * authorization row for this entity exists (`ENTITY_STATE_MOVES_TABLE`), which only a move
128
+ * writes. An `UPDATE` that does not name the columns does not fire it.
129
+ *
130
+ * Drop-then-create, like the search triggers, and re-run after a dump load: a load drops the
131
+ * table and with it the triggers, then inserts the rows first — a restored binned row is
132
+ * legitimately born trashed, so the triggers are put back only after them.
133
+ */
134
+ export declare function entityStateTriggerDdl(plan: EntityStatePlan): string;
135
+ /** The two guard triggers `entityStateTriggerDdl` creates, each with its CREATE statement. */
136
+ export declare function entityStateTriggerObjects(plan: EntityStatePlan): DerivedObject[];
137
+ /**
138
+ * Add one module's plans to a scope's registry, refusing a second declaration of a type — the
139
+ * table is one module's, and two answers to "who may trash it" is no answer — and a key the
140
+ * module does not declare. The entity is the module's, so its keys are too: a key nobody
141
+ * declared reaches no role, no grant shape and no `PERMISSIONS.md`, and would make the verb
142
+ * look gated while nobody could ever pass it.
143
+ */
144
+ export declare function addStatePlans(byType: Map<string, EntityStatePlan>, moduleId: string, declarations: readonly EntityStateDeclaration[] | undefined, declaredKeys: readonly {
145
+ readonly key: string;
146
+ }[]): void;
147
+ /** The tables whose rows carry the columns, lowercased as SQLite resolves a name — `guardSpine`'s input. */
148
+ export declare const statefulTablesOf: (plans: ReadonlyMap<string, EntityStatePlan>) => ReadonlySet<string>;
149
+ /** The columns a plan has, for code that only needs to know which views exist. */
150
+ export interface StateColumns {
151
+ readonly archive: boolean;
152
+ readonly trash: boolean;
153
+ }
154
+ export declare const stateColumnsOf: (plan: EntityStatePlan) => StateColumns;
155
+ /** The views an entity with these columns has — `active` always, then one per column. */
156
+ export declare function viewsOf(columns: StateColumns): EntityStateName[];
157
+ /**
158
+ * The predicate selecting one view's rows, over bare column names or `alias.`-qualified ones.
159
+ *
160
+ * **Spelled one way, everywhere.** A partial index is used only when the query's `WHERE`
161
+ * contains the index's own terms, so the list index DDL and the list query both come from
162
+ * here — a reworded copy would plan a scan in production and pass every test.
163
+ *
164
+ * `columns` absent is an entity that declares no state: every row is active, so there is no
165
+ * predicate. A view the entity does not have is refused rather than answered empty — asking
166
+ * an entity with no trash for its trashed rows is a wiring mistake, and an empty page would
167
+ * read as an empty bin.
168
+ */
169
+ export declare function entityStateWhere(entityType: string, columns: StateColumns, view: EntityStateName, alias?: string): string;
170
+ export declare function entityStateWhere(entityType: string, columns: StateColumns | undefined, view: EntityStateName | undefined, alias?: string): string | undefined;
171
+ /** The two columns as one row holds them. */
172
+ export interface StateRow {
173
+ readonly archived_at: string | null;
174
+ readonly trashed_at: string | null;
175
+ }
176
+ /** One row's two columns, or `undefined` when the row does not exist. */
177
+ export declare function readStateRow(sql: ScopedSql, plan: EntityStatePlan, entityId: string): StateRow | undefined;
178
+ /**
179
+ * The plan for `entityType`, and the key one of its states is gated on — or `validation_failed`
180
+ * naming what the entity does not declare. Shared by the verbs and the trashed readers, so
181
+ * "declares no trash" is one sentence.
182
+ */
183
+ export declare function stateKeyOf(plans: ReadonlyMap<string, EntityStatePlan>, verb: string, entityType: string, which: 'archive' | 'trash'): {
184
+ plan: EntityStatePlan;
185
+ key: PermissionKey;
186
+ };
187
+ /** What the verbs need from the adapter. */
188
+ export interface EntityStateDeps {
189
+ /** RAW access inside the operation's own transaction — the guarded `ctx.sql` refuses these columns. */
190
+ sql: ScopedSql;
191
+ /** entity type → its plan, for every registered module. */
192
+ plans: ReadonlyMap<string, EntityStatePlan>;
193
+ /** The operation's instant — what each column is stamped with. */
194
+ now: Instant;
195
+ check: StateCheck;
196
+ /** `ctx.emit`'s kernel writer — stamps the actor, the authorization chain and the operation. */
197
+ emit: (event: DomainEventInput) => void;
198
+ /** K-42's read-only refusal, for the effecting verbs. */
199
+ assertWrites: (verb: string) => void;
200
+ }
201
+ /** The verbs, as `OperationContext` carries them. */
202
+ export type EntityStateVerbs = Pick<OperationContext, 'archive' | 'unarchive' | 'trash' | 'restore' | 'entityState'>;
203
+ export declare function createEntityStateVerbs(deps: EntityStateDeps): EntityStateVerbs;
204
+ //# sourceMappingURL=entity-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entity-state.d.ts","sourceRoot":"","sources":["../src/entity-state.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,356 @@
1
+ /**
2
+ * Entity archive and trash (#119): the kernel half, written once — both adapters hand
3
+ * `createEntityStateVerbs` the same things, the way `createEntityEdgeVerbs` is shared.
4
+ *
5
+ * ## What the kernel owns, and what it leaves to the vertical
6
+ *
7
+ * | Kernel | Vertical |
8
+ * |---|---|
9
+ * | The two columns on the entity's table, added by a derived migration | Which entities declare `archive` / `trash`, and the keys |
10
+ * | The transitions, their refusals, and the event each one emits | The operations that call the verbs, and their routes |
11
+ * | The declared key, re-checked on the entity by every verb and every trashed read | What an archived row looks like on a screen |
12
+ * | `ctx.page` / `ctx.search` leaving archived and trashed rows out by default | Its own hand-written `SELECT`s — see the gap below |
13
+ *
14
+ * ## The state is two columns
15
+ *
16
+ * `_substrat_archived_at` and `_substrat_trashed_at`, each an ISO instant or NULL. Apart, not
17
+ * one enum, so a trash never forgets an archive: `restore` clears the trash and leaves the
18
+ * archive, and an archived row that went to the bin comes back archived. The visible state is
19
+ * derived (contracts `entity-state.ts`): trashed, else archived, else active.
20
+ *
21
+ * Module code cannot write either column: `ctx.sql` refuses a write naming a `_substrat_*`
22
+ * column (`spine-guard.ts`), so the only way into or out of the trash is a verb that checks
23
+ * the key and emits the event. It can READ them, which is how a vertical's own `SELECT` keeps
24
+ * archived rows out of a screen `ctx.page` does not compose.
25
+ *
26
+ * ## The gap, stated
27
+ *
28
+ * The kernel filters the reads it composes — `ctx.page` and `ctx.search` — and nothing else.
29
+ * A handler's own `SELECT … FROM todo_lists WHERE id = ?` sees an archived or trashed row
30
+ * exactly as before. That is the honest limit of a kernel that does not parse module SQL:
31
+ * a get-by-id asks `ctx.entityState(ref)` and decides, and a hand-written list adds the
32
+ * predicate itself (`entityStateWhere`).
33
+ *
34
+ * ## Trash is not erasure, and not tenant deletion
35
+ *
36
+ * Trash keeps the row and every event about it. Subject erasure (#37) reaches a trashed row
37
+ * exactly as it reaches any other, because the row never moved tables; a tenant's deletion
38
+ * (#36) removes it with everything else. Nothing here extends a retention or shortens one.
39
+ */
40
+ import { ARCHIVED_AT_COLUMN, ENTITY_ARCHIVED, ENTITY_RESTORED, ENTITY_TRASHED, ENTITY_UNARCHIVED, TRASHED_AT_COLUMN, entityStateChangedPayload, substratError, } from '@substrat-run/contracts';
41
+ import { assertAllowed } from './permission-checker.js';
42
+ import { assertSqlIdentifier } from './sql-identifier.js';
43
+ /**
44
+ * Resolve one module's declarations. Refuses rather than skips, for `searchIndexPlans`'s
45
+ * reason: a declaration the author believes is live that silently is not leaves a trashed
46
+ * row in every list with no error anywhere.
47
+ */
48
+ export function entityStatePlans(moduleId, declarations) {
49
+ if (!declarations?.length)
50
+ return [];
51
+ const plans = [];
52
+ const seen = new Set();
53
+ for (const decl of declarations) {
54
+ const where = `${moduleId} entityStates['${decl.entityType}']`;
55
+ if (seen.has(decl.entityType))
56
+ throw new Error(`entity state: ${where} is declared twice`);
57
+ seen.add(decl.entityType);
58
+ if (!decl.table) {
59
+ throw new Error(`entity state: ${where} carries no table — declare \`archive\`/\`trash\` on the entity and ` +
60
+ 'spread `manifestEntities()`, so the entity registry supplies it');
61
+ }
62
+ if (!decl.archivePermission && !decl.trashPermission) {
63
+ throw new Error(`entity state: ${where} declares neither an archive nor a trash permission`);
64
+ }
65
+ if (decl.purgeAfterDays !== undefined && !decl.trashPermission) {
66
+ throw new Error(`entity state: ${where} declares a purge horizon and no trash — only a trashed entity is purged`);
67
+ }
68
+ plans.push({
69
+ moduleId,
70
+ entityType: decl.entityType,
71
+ table: assertSqlIdentifier('entity state', 'a table', decl.table, where),
72
+ idColumn: assertSqlIdentifier('entity state', 'an id column', decl.idColumn ?? 'id', where),
73
+ ...(decl.archivePermission ? { archivePermission: decl.archivePermission } : {}),
74
+ ...(decl.trashPermission ? { trashPermission: decl.trashPermission } : {}),
75
+ ...(decl.purgeAfterDays !== undefined ? { purgeAfterDays: decl.purgeAfterDays } : {}),
76
+ });
77
+ }
78
+ return plans;
79
+ }
80
+ /**
81
+ * The migrations that add the columns — one per column, so declaring `trash` on an entity
82
+ * that already declared `archive` adds one column and re-runs nothing.
83
+ *
84
+ * **The version is the declaration**, as for search and list indexes. Unlike theirs, this DDL
85
+ * is not drop-then-create: a column holds the state, and dropping it would un-trash every row.
86
+ * So it runs once per column, ever. Withdrawing a declaration leaves the column where it is,
87
+ * holding what it held; re-declaring it finds the migration already applied.
88
+ *
89
+ * Applied after the module's own migrations (the table must exist) and before its list
90
+ * indexes (their partial `WHERE` names these columns) — `moduleMigrations` writes that order.
91
+ */
92
+ export function entityStateMigrations(moduleId, declarations) {
93
+ const out = [];
94
+ for (const plan of entityStatePlans(moduleId, declarations)) {
95
+ for (const { version, column } of stateColumnVersionsOf(plan)) {
96
+ out.push({ version, sql: `ALTER TABLE ${plan.table} ADD COLUMN ${column} TEXT;` });
97
+ }
98
+ // After the columns they name.
99
+ out.push({ version: entityStateGuardVersion(plan), sql: entityStateTriggerDdl(plan) });
100
+ // The purge sweep's walk (#119): the oldest trashed rows first, without reading the rest of
101
+ // the bin. Its own version, so declaring a horizon later adds it and re-runs nothing.
102
+ if (plan.purgeAfterDays !== undefined)
103
+ out.push({ version: purgeIndexVersion(plan), sql: purgeIndexDdl(plan) });
104
+ }
105
+ return out;
106
+ }
107
+ /** The column migrations' versions, and the column each adds — what the journal says a table holds. */
108
+ export const stateColumnVersionsOf = (plan) => [
109
+ ...(plan.archivePermission ? [{ version: `state/${plan.entityType}:archive`, column: ARCHIVED_AT_COLUMN }] : []),
110
+ ...(plan.trashPermission ? [{ version: `state/${plan.entityType}:trash`, column: TRASHED_AT_COLUMN }] : []),
111
+ ];
112
+ /** The purge index's migration version (#119) — present only on a plan with a purge horizon. */
113
+ export const purgeIndexVersion = (plan) => `state/${plan.entityType}:purge`;
114
+ /**
115
+ * The partial index the purge sweep walks (#119): trashed rows only, oldest trash first, so a
116
+ * sweep reads the due rows and nothing else. Its name is kernel-prefixed, so the reserved
117
+ * namespace. Empty for a plan without a purge horizon.
118
+ */
119
+ export function purgeIndexObjects(plan) {
120
+ if (plan.purgeAfterDays === undefined)
121
+ return [];
122
+ const name = `_substrat_purge_${plan.table}`;
123
+ return [
124
+ {
125
+ name,
126
+ type: 'index',
127
+ table: plan.table,
128
+ sql: `CREATE INDEX ${name} ON ${plan.table} (${TRASHED_AT_COLUMN}, ${plan.idColumn}) WHERE ${TRASHED_AT_COLUMN} IS NOT NULL`,
129
+ },
130
+ ];
131
+ }
132
+ /**
133
+ * The purge index's DDL. `IF NOT EXISTS` because a dump load runs it again after the rows are
134
+ * in; a repair that finds it wrong drops it first (`purgeIndexRepairDdl`).
135
+ */
136
+ export function purgeIndexDdl(plan) {
137
+ return purgeIndexObjects(plan)
138
+ .map((idx) => `${idx.sql.replace(/^CREATE INDEX /, 'CREATE INDEX IF NOT EXISTS ')};`)
139
+ .join('\n');
140
+ }
141
+ /** Drop-then-create, for a purge index the catalogue holds under its name but not as emitted. */
142
+ export function purgeIndexRepairDdl(plan) {
143
+ return purgeIndexObjects(plan)
144
+ .flatMap((idx) => [`DROP INDEX IF EXISTS ${idx.name};`, `${idx.sql};`])
145
+ .join('\n');
146
+ }
147
+ /**
148
+ * The guard triggers' version. By which columns they guard, so declaring a trash on an
149
+ * archivable entity rebuilds them to guard both.
150
+ */
151
+ export const entityStateGuardVersion = (plan) => {
152
+ const { archive, trash } = stateColumnsOf(plan);
153
+ return `state/${plan.entityType}:guard:${[...(archive ? ['archive'] : []), ...(trash ? ['trash'] : [])].join('+')}`;
154
+ };
155
+ /** The prefix of every trigger this module derives — kernel-owned, so the reserved one. */
156
+ export const ENTITY_STATE_TRIGGER_PREFIX = '_substrat_state_';
157
+ /**
158
+ * The kernel's authorization for ONE move (#119, Codex r2): the row `ctx.archive` & co. write
159
+ * just before their `UPDATE` and delete just after it, in the same transaction. The update
160
+ * trigger below refuses any change to the columns that has no such row — so the only writer the
161
+ * columns admit is the one that can write a `_substrat_*` TABLE, which `ctx.sql` refuses by name
162
+ * (a far simpler reading than finding an assignment target in an expression).
163
+ *
164
+ * Never holds a row between operations: written and removed inside one move, and a move that
165
+ * throws rolls back with its operation. Shared by both adapters' `KERNEL_DDL`, like the other
166
+ * kernel-owned spine tables, so the two cannot part company.
167
+ */
168
+ export const ENTITY_STATE_MOVES_TABLE = '_substrat_state_moves';
169
+ export const ENTITY_STATE_MOVES_DDL = `
170
+ CREATE TABLE IF NOT EXISTS _substrat_state_moves (
171
+ entity_type TEXT NOT NULL,
172
+ entity_id TEXT NOT NULL,
173
+ PRIMARY KEY (entity_type, entity_id)
174
+ );
175
+ `;
176
+ /** A string literal for SQL text: the entity type is a declaration, still quoted rather than trusted. */
177
+ const literal = (value) => `'${value.replace(/'/g, "''")}'`;
178
+ /**
179
+ * The invariant below the column guard, as two triggers on the entity's table.
180
+ *
181
+ * `ctx.sql` refuses the writes that would set the columns (`assertNoReservedColumnWrite`), but
182
+ * that is a reading of SQL text, and a reading can miss a form — it did, twice. These hold
183
+ * whatever the text says:
184
+ *
185
+ * - **born** — `BEFORE INSERT`: a row is never inserted archived or trashed. Every row enters
186
+ * active.
187
+ * - **moved** — `BEFORE UPDATE OF` the columns: a change to either aborts unless the kernel's
188
+ * authorization row for this entity exists (`ENTITY_STATE_MOVES_TABLE`), which only a move
189
+ * writes. An `UPDATE` that does not name the columns does not fire it.
190
+ *
191
+ * Drop-then-create, like the search triggers, and re-run after a dump load: a load drops the
192
+ * table and with it the triggers, then inserts the rows first — a restored binned row is
193
+ * legitimately born trashed, so the triggers are put back only after them.
194
+ */
195
+ export function entityStateTriggerDdl(plan) {
196
+ return entityStateTriggerObjects(plan)
197
+ .flatMap((t) => [`DROP TRIGGER IF EXISTS ${t.name};`, `${t.sql};`])
198
+ .join('\n');
199
+ }
200
+ /** The two guard triggers `entityStateTriggerDdl` creates, each with its CREATE statement. */
201
+ export function entityStateTriggerObjects(plan) {
202
+ const born = `${ENTITY_STATE_TRIGGER_PREFIX}${plan.table}_born`;
203
+ const moved = `${ENTITY_STATE_TRIGGER_PREFIX}${plan.table}_moved`;
204
+ const columns = stateColumnVersionsOf(plan).map((c) => c.column);
205
+ const trigger = (name, lines) => ({ name, type: 'trigger', table: plan.table, sql: lines.join('\n') });
206
+ return [
207
+ trigger(born, [
208
+ `CREATE TRIGGER ${born} BEFORE INSERT ON ${plan.table} WHEN ${columns.map((c) => `NEW.${c} IS NOT NULL`).join(' OR ')} BEGIN`,
209
+ ` SELECT RAISE(ABORT, 'a row is never inserted archived or trashed - ctx.archive and ctx.trash move it (#119)');`,
210
+ `END`,
211
+ ]),
212
+ trigger(moved, [
213
+ `CREATE TRIGGER ${moved} BEFORE UPDATE OF ${columns.join(', ')} ON ${plan.table}`,
214
+ `WHEN NOT EXISTS (SELECT 1 FROM ${ENTITY_STATE_MOVES_TABLE} WHERE entity_type = ${literal(plan.entityType)} AND entity_id = OLD.${plan.idColumn}) BEGIN`,
215
+ ` SELECT RAISE(ABORT, 'archive and trash state moves only through ctx.archive, ctx.trash and ctx.restore (#119)');`,
216
+ `END`,
217
+ ]),
218
+ ];
219
+ }
220
+ /**
221
+ * Add one module's plans to a scope's registry, refusing a second declaration of a type — the
222
+ * table is one module's, and two answers to "who may trash it" is no answer — and a key the
223
+ * module does not declare. The entity is the module's, so its keys are too: a key nobody
224
+ * declared reaches no role, no grant shape and no `PERMISSIONS.md`, and would make the verb
225
+ * look gated while nobody could ever pass it.
226
+ */
227
+ export function addStatePlans(byType, moduleId, declarations, declaredKeys) {
228
+ for (const plan of entityStatePlans(moduleId, declarations)) {
229
+ for (const key of [plan.archivePermission, plan.trashPermission]) {
230
+ if (key && !declaredKeys.some((p) => p.key === key)) {
231
+ throw new Error(`entity state: ${moduleId} gates '${plan.entityType}' on '${key}', which it does not declare in \`permissions\``);
232
+ }
233
+ }
234
+ const existing = byType.get(plan.entityType);
235
+ if (existing) {
236
+ throw new Error(`entity state: '${plan.entityType}' is declared archivable/trashable by both ` +
237
+ `'${existing.moduleId}' and '${plan.moduleId}' — one entity type, one owner`);
238
+ }
239
+ byType.set(plan.entityType, plan);
240
+ }
241
+ }
242
+ /** The tables whose rows carry the columns, lowercased as SQLite resolves a name — `guardSpine`'s input. */
243
+ export const statefulTablesOf = (plans) => new Set([...plans.values()].map((p) => p.table.toLowerCase()));
244
+ export const stateColumnsOf = (plan) => ({
245
+ archive: plan.archivePermission !== undefined,
246
+ trash: plan.trashPermission !== undefined,
247
+ });
248
+ /** The views an entity with these columns has — `active` always, then one per column. */
249
+ export function viewsOf(columns) {
250
+ return ['active', ...(columns.archive ? ['archived'] : []), ...(columns.trash ? ['trashed'] : [])];
251
+ }
252
+ export function entityStateWhere(entityType, columns, view, alias) {
253
+ const asked = view ?? 'active';
254
+ if ((asked === 'archived' && !columns?.archive) || (asked === 'trashed' && !columns?.trash)) {
255
+ throw substratError('validation_failed', `'${entityType}' has no ${asked} view — it declares no ${asked === 'archived' ? 'archive' : 'trash'}`, { errors: [{ path: 'view', message: `not declared for '${entityType}'` }] });
256
+ }
257
+ if (!columns)
258
+ return undefined;
259
+ const col = (name) => (alias ? `${alias}.${name}` : name);
260
+ if (asked === 'trashed')
261
+ return `${col(TRASHED_AT_COLUMN)} IS NOT NULL`;
262
+ const terms = [];
263
+ if (columns.archive)
264
+ terms.push(`${col(ARCHIVED_AT_COLUMN)} IS ${asked === 'archived' ? 'NOT ' : ''}NULL`);
265
+ if (columns.trash)
266
+ terms.push(`${col(TRASHED_AT_COLUMN)} IS NULL`);
267
+ return terms.join(' AND ');
268
+ }
269
+ const stateOf = (row) => row.trashed_at !== null ? 'trashed' : row.archived_at !== null ? 'archived' : 'active';
270
+ /** One row's two columns, or `undefined` when the row does not exist. */
271
+ export function readStateRow(sql, plan, entityId) {
272
+ const columns = stateColumnsOf(plan);
273
+ return sql.query(`SELECT ${columns.archive ? ARCHIVED_AT_COLUMN : 'NULL'} AS archived_at, ` +
274
+ `${columns.trash ? TRASHED_AT_COLUMN : 'NULL'} AS trashed_at ` +
275
+ `FROM ${plan.table} WHERE ${plan.idColumn} = ?`, [entityId])[0];
276
+ }
277
+ /**
278
+ * The plan for `entityType`, and the key one of its states is gated on — or `validation_failed`
279
+ * naming what the entity does not declare. Shared by the verbs and the trashed readers, so
280
+ * "declares no trash" is one sentence.
281
+ */
282
+ export function stateKeyOf(plans, verb, entityType, which) {
283
+ const plan = plans.get(entityType);
284
+ const key = which === 'archive' ? plan?.archivePermission : plan?.trashPermission;
285
+ if (!plan || !key) {
286
+ throw substratError('validation_failed', `${verb}: '${entityType}' declares no ${which}`, {
287
+ errors: [{ path: 'entityType', message: `'${entityType}' declares no ${which}` }],
288
+ });
289
+ }
290
+ return { plan, key };
291
+ }
292
+ const COLUMN = { archived_at: ARCHIVED_AT_COLUMN, trashed_at: TRASHED_AT_COLUMN };
293
+ const MOVES = {
294
+ archive: { verb: 'ctx.archive', which: 'archive', from: ['active'], column: 'archived_at', set: true, type: ENTITY_ARCHIVED },
295
+ unarchive: { verb: 'ctx.unarchive', which: 'archive', from: ['archived'], column: 'archived_at', set: false, type: ENTITY_UNARCHIVED },
296
+ // Archived rows may be trashed: "delete this old thing" is the commonest reason to open an
297
+ // archive at all. The archive survives the trip, which is what `restore` relies on.
298
+ trash: { verb: 'ctx.trash', which: 'trash', from: ['active', 'archived'], column: 'trashed_at', set: true, type: ENTITY_TRASHED },
299
+ restore: { verb: 'ctx.restore', which: 'trash', from: ['trashed'], column: 'trashed_at', set: false, type: ENTITY_RESTORED },
300
+ };
301
+ export function createEntityStateVerbs(deps) {
302
+ const move = async (m, entity) => {
303
+ deps.assertWrites(m.verb);
304
+ const { plan, key } = stateKeyOf(deps.plans, m.verb, entity.entityType, m.which);
305
+ // The DECLARED key, on THIS entity, before anything about the row is read: whether it
306
+ // exists is not something a caller without the key gets to learn.
307
+ assertAllowed(await deps.check(key, entity));
308
+ const row = readStateRow(deps.sql, plan, entity.entityId);
309
+ if (!row) {
310
+ throw substratError('not_found', `${m.verb}: ${entity.entityType}:${entity.entityId} does not exist`);
311
+ }
312
+ const from = stateOf(row);
313
+ if (!m.from.includes(from)) {
314
+ throw substratError('conflict', `${m.verb}: ${entity.entityType}:${entity.entityId} is ${from} — ` +
315
+ `only ${m.from.join(' or ')} can be moved this way`, { reason: 'invalid_transition' });
316
+ }
317
+ const at = m.set ? deps.now : null;
318
+ // The authorization the update trigger asks for, held for exactly this one statement.
319
+ const authorize = [entity.entityType, entity.entityId];
320
+ deps.sql.exec(`INSERT OR IGNORE INTO ${ENTITY_STATE_MOVES_TABLE} (entity_type, entity_id) VALUES (?, ?)`, authorize);
321
+ try {
322
+ const { changes } = deps.sql.exec(`UPDATE ${plan.table} SET ${COLUMN[m.column]} = ? WHERE ${plan.idColumn} = ?`, [
323
+ at,
324
+ entity.entityId,
325
+ ]);
326
+ // The row exists (read above), so an UPDATE that changed nothing was swallowed — a trigger's
327
+ // RAISE(IGNORE), say. Recording a move that did not happen is the one thing this must not do.
328
+ // `0`, not `!== 1`: a Durable Object's count includes the index rows the move rewrote.
329
+ if (changes === 0) {
330
+ throw substratError('internal', `${m.verb}: the update of ${entity.entityType}:${entity.entityId} changed no row — nothing was recorded`);
331
+ }
332
+ }
333
+ finally {
334
+ deps.sql.exec(`DELETE FROM ${ENTITY_STATE_MOVES_TABLE} WHERE entity_type = ? AND entity_id = ?`, authorize);
335
+ }
336
+ const to = stateOf({ ...row, [m.column]: at });
337
+ const payload = entityStateChangedPayload.parse({ entity, from, to }); // strips extra keys
338
+ deps.emit({ type: m.type, schemaVersion: 1, entity: payload.entity, piiClass: 'none', payload });
339
+ return to;
340
+ };
341
+ return {
342
+ archive: (entity) => move(MOVES.archive, entity),
343
+ unarchive: (entity) => move(MOVES.unarchive, entity),
344
+ trash: (entity) => move(MOVES.trash, entity),
345
+ restore: (entity) => move(MOVES.restore, entity),
346
+ entityState: (entity) => {
347
+ const plan = deps.plans.get(entity.entityType);
348
+ if (!plan) {
349
+ throw substratError('validation_failed', `ctx.entityState: '${entity.entityType}' declares no archive or trash — declare one on the entity`);
350
+ }
351
+ const row = readStateRow(deps.sql, plan, entity.entityId);
352
+ return row ? stateOf(row) : null;
353
+ },
354
+ };
355
+ }
356
+ //# sourceMappingURL=entity-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entity-state.js","sourceRoot":"","sources":["../src/entity-state.ts"],"names":[],"mappings":"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"}