@substrat-run/kernel 0.146.0 → 0.148.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 (76) hide show
  1. package/dist/audit-outcome.d.ts +1 -1
  2. package/dist/audit-outcome.d.ts.map +1 -1
  3. package/dist/audit-outcome.js +4 -2
  4. package/dist/audit-outcome.js.map +1 -1
  5. package/dist/export-break.d.ts +137 -0
  6. package/dist/export-break.d.ts.map +1 -0
  7. package/dist/export-break.js +219 -0
  8. package/dist/export-break.js.map +1 -0
  9. package/dist/index.d.ts +17 -8
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +11 -5
  12. package/dist/index.js.map +1 -1
  13. package/dist/install-receipts.d.ts +116 -0
  14. package/dist/install-receipts.d.ts.map +1 -0
  15. package/dist/install-receipts.js +162 -0
  16. package/dist/install-receipts.js.map +1 -0
  17. package/dist/invocation-line.d.ts +63 -0
  18. package/dist/invocation-line.d.ts.map +1 -1
  19. package/dist/invocation-line.js +44 -0
  20. package/dist/invocation-line.js.map +1 -1
  21. package/dist/map-bounded.d.ts +8 -0
  22. package/dist/map-bounded.d.ts.map +1 -0
  23. package/dist/map-bounded.js +16 -0
  24. package/dist/map-bounded.js.map +1 -0
  25. package/dist/meters.d.ts +5 -0
  26. package/dist/meters.d.ts.map +1 -1
  27. package/dist/meters.js +0 -0
  28. package/dist/meters.js.map +1 -1
  29. package/dist/peer.js +1 -1
  30. package/dist/platform-sweep.d.ts +52 -363
  31. package/dist/platform-sweep.d.ts.map +1 -1
  32. package/dist/platform-sweep.js +49 -564
  33. package/dist/platform-sweep.js.map +1 -1
  34. package/dist/schedule-state.d.ts +91 -0
  35. package/dist/schedule-state.d.ts.map +1 -0
  36. package/dist/schedule-state.js +118 -0
  37. package/dist/schedule-state.js.map +1 -0
  38. package/dist/scope-copy-ledger.d.ts +29 -0
  39. package/dist/scope-copy-ledger.d.ts.map +1 -1
  40. package/dist/scope-copy-ledger.js +23 -0
  41. package/dist/scope-copy-ledger.js.map +1 -1
  42. package/dist/scope-copy.d.ts.map +1 -1
  43. package/dist/scope-copy.js +3 -2
  44. package/dist/scope-copy.js.map +1 -1
  45. package/dist/scope-host.d.ts +199 -14
  46. package/dist/scope-host.d.ts.map +1 -1
  47. package/dist/scope-host.js.map +1 -1
  48. package/dist/scope-identity.d.ts +68 -0
  49. package/dist/scope-identity.d.ts.map +1 -0
  50. package/dist/scope-identity.js +75 -0
  51. package/dist/scope-identity.js.map +1 -0
  52. package/dist/scope-store.d.ts +139 -0
  53. package/dist/scope-store.d.ts.map +1 -0
  54. package/dist/scope-store.js +237 -0
  55. package/dist/scope-store.js.map +1 -0
  56. package/dist/spine-restore.js +1 -1
  57. package/dist/sql-limits.d.ts.map +1 -1
  58. package/dist/sql-limits.js +10 -1
  59. package/dist/sql-limits.js.map +1 -1
  60. package/dist/storage-gauge.d.ts +21 -2
  61. package/dist/storage-gauge.d.ts.map +1 -1
  62. package/dist/storage-gauge.js +73 -6
  63. package/dist/storage-gauge.js.map +1 -1
  64. package/dist/store-kind-releases.d.ts +137 -0
  65. package/dist/store-kind-releases.d.ts.map +1 -0
  66. package/dist/store-kind-releases.js +252 -0
  67. package/dist/store-kind-releases.js.map +1 -0
  68. package/dist/upload-link.d.ts +89 -0
  69. package/dist/upload-link.d.ts.map +1 -0
  70. package/dist/upload-link.js +277 -0
  71. package/dist/upload-link.js.map +1 -0
  72. package/dist/vertical-scope-stores.d.ts +43 -0
  73. package/dist/vertical-scope-stores.d.ts.map +1 -0
  74. package/dist/vertical-scope-stores.js +195 -0
  75. package/dist/vertical-scope-stores.js.map +1 -0
  76. package/package.json +2 -2
@@ -0,0 +1,89 @@
1
+ import { type AttachmentRecord, type CheckSubject, type Decision, type DomainEventInput, type EntityRef, type Instant, type MintedUploadLink, type PermissionKey, type PrincipalId, type UploadLinkMintInput, type UploadLinkPolicy, type UploadLinkRecord, type UploadLinkView } from '@substrat-run/contracts';
2
+ import type { ScopedSql } from './scope-host.js';
3
+ export declare const UPLOAD_LINK_DDL = "\n CREATE TABLE IF NOT EXISTS _substrat_upload_links (\n id TEXT PRIMARY KEY,\n -- SHA-256 hex of the secret. The secret itself is stored NOWHERE.\n token_hash TEXT NOT NULL UNIQUE,\n entity_type TEXT NOT NULL,\n entity_id TEXT NOT NULL,\n -- NULL = the uploader names the file.\n filename TEXT,\n max_bytes INTEGER NOT NULL,\n -- JSON array of media types / families, or NULL = any type.\n content_types TEXT,\n visibility TEXT NOT NULL,\n -- The principal the attachment is written AS: its author, and whose `writePermission`\n -- is checked again at redemption.\n minted_by TEXT NOT NULL,\n minted_at TEXT NOT NULL,\n expires_at TEXT NOT NULL,\n redeemed_at TEXT,\n -- The attachment this link produced.\n attachment_id TEXT,\n -- Tombstone, never a delete: a link that was minted is evidence of why a file arrived.\n revoked_at TEXT,\n revoked_by TEXT\n );\n CREATE INDEX IF NOT EXISTS _substrat_upload_links_entity\n ON _substrat_upload_links (entity_type, entity_id);\n";
4
+ /** The tables `UPLOAD_LINK_DDL` builds — what a copy of a scope leaves behind. */
5
+ export declare const UPLOAD_LINK_TABLE_NAMES: readonly ['_substrat_upload_links'];
6
+ /** `reason` slugs a redemption refuses with, so the HTTP route can answer 410/413/415 and a client can act. */
7
+ export declare const UPLOAD_LINK_GONE = "upload_link_gone";
8
+ export declare const UPLOAD_LINK_TOO_LARGE = "upload_link_too_large";
9
+ export declare const UPLOAD_LINK_TYPE_REFUSED = "upload_link_type_refused";
10
+ export declare const UPLOAD_LINK_WRONG_TARGET = "upload_link_wrong_target";
11
+ /** A fresh link secret: 256 bits, base64url, prefixed. Only its hash is ever stored. */
12
+ export declare function mintUploadLinkSecret(): string;
13
+ /** Is `type` (already stripped of parameters, lowercase) admitted by `allowed`? `null` admits anything. */
14
+ export declare function uploadContentTypeAllowed(allowed: readonly string[] | null, type: string): boolean;
15
+ /** What an operation handler sees as `ctx.attachments`. */
16
+ export interface AttachmentVerbs {
17
+ /**
18
+ * Mint a single-use URL that lets its holder upload ONE file to `entity` — and return the
19
+ * secret and the path that carries it, once.
20
+ *
21
+ * The target must declare `uploadLinks` in its `attachmentTargets` entry, and the caller
22
+ * must hold the target's `writePermission` on the entity (the check an upload makes);
23
+ * it is made again, as the same principal, when the link is redeemed. Only a principal may
24
+ * mint. The operation's own guards — an entity lock, a state check — run in the operation,
25
+ * before this; they are not re-run at redemption, so keep `ttlSeconds` short.
26
+ *
27
+ * ```ts
28
+ * assertAllowed(await ctx.check('contract:write', ref));
29
+ * const link = await ctx.attachments.mintUploadUrl({ entity: ref, filename: 'signed.pdf' });
30
+ * return { url: `${origin}${link.path}`, expiresAt: link.expiresAt };
31
+ * ```
32
+ *
33
+ * The same tripwire `ctx.capabilities.mint` has applies: while this invocation runs,
34
+ * `ctx.emit` and `ctx.sql` refuse a record carrying the secret verbatim. Transactional with
35
+ * the operation: a mint whose operation throws never happened.
36
+ */
37
+ mintUploadUrl(input: UploadLinkMintInput): Promise<MintedUploadLink>;
38
+ /** Revoke a link that has not been redeemed. Allowed to anyone who could have minted it. Idempotent. */
39
+ revokeUploadLink(id: string): Promise<void>;
40
+ /** This scope's upload links, newest first — never the secret. What a follow-up operation reads to find the attachment a link produced. */
41
+ uploadLinks(filter?: {
42
+ entity?: EntityRef;
43
+ limit?: number;
44
+ }): UploadLinkRecord[];
45
+ }
46
+ /** What the verbs need of the target's declaration: its write key and its link policy. */
47
+ export interface UploadTarget {
48
+ write: PermissionKey;
49
+ uploadLinks?: UploadLinkPolicy;
50
+ }
51
+ export interface AttachmentVerbDeps {
52
+ /** RAW spine access inside the operation's own transaction. */
53
+ sql: ScopedSql;
54
+ subject: CheckSubject;
55
+ now: Instant;
56
+ /** The operation's OWN check. */
57
+ check: (permission: PermissionKey, entity?: EntityRef) => Promise<Decision>;
58
+ emit: (event: DomainEventInput) => void;
59
+ assertWrites: (verb: string) => void;
60
+ /** This invocation's minted secrets — appended to by `mint`, read by the secret guards. */
61
+ minted: string[];
62
+ /** The declared attachment target for an entity type, if any module declares one. */
63
+ targetOf: (entityType: string) => UploadTarget | undefined;
64
+ }
65
+ export declare function createAttachmentVerbs(deps: AttachmentVerbDeps): AttachmentVerbs;
66
+ /** What the route needs before it reads a byte, and who the write is made as. */
67
+ export interface UploadLinkInspection {
68
+ view: UploadLinkView;
69
+ mintedBy: PrincipalId;
70
+ }
71
+ /**
72
+ * Look a secret up without spending it: `not_found` for one that names nothing, `conflict`
73
+ * with reason `upload_link_gone` for one that is used, expired or revoked.
74
+ */
75
+ export declare function inspectUploadLink(deps: {
76
+ sql: ScopedSql;
77
+ now: Instant;
78
+ }, secret: unknown): Promise<UploadLinkInspection>;
79
+ /**
80
+ * Spend the link for `record`, INSIDE the transaction that inserts the attachment row — so a
81
+ * refusal anywhere rolls the spend back. Judges the record against the link, never the other
82
+ * way round: the target, the author, the size and the type are all the link's to refuse.
83
+ */
84
+ export declare function redeemUploadLinkInTransaction(deps: {
85
+ sql: ScopedSql;
86
+ now: Instant;
87
+ emit: (event: DomainEventInput) => void;
88
+ }, secret: unknown, record: AttachmentRecord): Promise<void>;
89
+ //# sourceMappingURL=upload-link.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upload-link.d.ts","sourceRoot":"","sources":["../src/upload-link.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,277 @@
1
+ import { ATTACHMENT_UPLOAD_LINK_MINTED, ATTACHMENT_UPLOAD_LINK_REDEEMED, ATTACHMENT_UPLOAD_LINK_REVOKED, UPLOAD_LINK_DEFAULT_TTL_SECONDS, UPLOAD_LINK_MAX_BYTES_CEILING, UPLOAD_LINK_SECRET_PREFIX, principalId as principalIdSchema, substratError, uploadLinkMintInput, uploadLinkPath, } from '@substrat-run/contracts';
2
+ import { toBase64url } from './base64url.js';
3
+ import { capabilityTokenHash } from './capability.js';
4
+ import { assertAllowed } from './permission-checker.js';
5
+ import { ulid } from './ulid.js';
6
+ export const UPLOAD_LINK_DDL = `
7
+ CREATE TABLE IF NOT EXISTS _substrat_upload_links (
8
+ id TEXT PRIMARY KEY,
9
+ -- SHA-256 hex of the secret. The secret itself is stored NOWHERE.
10
+ token_hash TEXT NOT NULL UNIQUE,
11
+ entity_type TEXT NOT NULL,
12
+ entity_id TEXT NOT NULL,
13
+ -- NULL = the uploader names the file.
14
+ filename TEXT,
15
+ max_bytes INTEGER NOT NULL,
16
+ -- JSON array of media types / families, or NULL = any type.
17
+ content_types TEXT,
18
+ visibility TEXT NOT NULL,
19
+ -- The principal the attachment is written AS: its author, and whose \`writePermission\`
20
+ -- is checked again at redemption.
21
+ minted_by TEXT NOT NULL,
22
+ minted_at TEXT NOT NULL,
23
+ expires_at TEXT NOT NULL,
24
+ redeemed_at TEXT,
25
+ -- The attachment this link produced.
26
+ attachment_id TEXT,
27
+ -- Tombstone, never a delete: a link that was minted is evidence of why a file arrived.
28
+ revoked_at TEXT,
29
+ revoked_by TEXT
30
+ );
31
+ CREATE INDEX IF NOT EXISTS _substrat_upload_links_entity
32
+ ON _substrat_upload_links (entity_type, entity_id);
33
+ `;
34
+ /** The tables `UPLOAD_LINK_DDL` builds — what a copy of a scope leaves behind. */
35
+ export const UPLOAD_LINK_TABLE_NAMES = ['_substrat_upload_links'];
36
+ /** `reason` slugs a redemption refuses with, so the HTTP route can answer 410/413/415 and a client can act. */
37
+ export const UPLOAD_LINK_GONE = 'upload_link_gone';
38
+ export const UPLOAD_LINK_TOO_LARGE = 'upload_link_too_large';
39
+ export const UPLOAD_LINK_TYPE_REFUSED = 'upload_link_type_refused';
40
+ export const UPLOAD_LINK_WRONG_TARGET = 'upload_link_wrong_target';
41
+ const COLUMNS = 'id, entity_type, entity_id, filename, max_bytes, content_types, visibility, minted_by, minted_at, ' +
42
+ 'expires_at, redeemed_at, attachment_id, revoked_at, revoked_by';
43
+ /** A fresh link secret: 256 bits, base64url, prefixed. Only its hash is ever stored. */
44
+ export function mintUploadLinkSecret() {
45
+ return UPLOAD_LINK_SECRET_PREFIX + toBase64url(crypto.getRandomValues(new Uint8Array(32)));
46
+ }
47
+ /** A secret that cannot be one of ours is refused before it is hashed. */
48
+ const plausibleSecret = (secret) => typeof secret === 'string' && secret.startsWith(UPLOAD_LINK_SECRET_PREFIX) && secret.length <= 128;
49
+ /** Is `type` (already stripped of parameters, lowercase) admitted by `allowed`? `null` admits anything. */
50
+ export function uploadContentTypeAllowed(allowed, type) {
51
+ if (allowed === null)
52
+ return true;
53
+ const t = type.toLowerCase();
54
+ return allowed.some((a) => (a.endsWith('/*') ? t.startsWith(a.slice(0, -1)) : a === t));
55
+ }
56
+ const stateOf = (row, now) => row.revoked_at !== null ? 'revoked' : row.redeemed_at !== null ? 'redeemed' : row.expires_at <= now ? 'expired' : 'pending';
57
+ function recordOf(row, now) {
58
+ return {
59
+ id: row.id,
60
+ entity: { entityType: row.entity_type, entityId: row.entity_id },
61
+ filename: row.filename,
62
+ maxBytes: row.max_bytes,
63
+ contentTypes: row.content_types === null ? null : JSON.parse(row.content_types),
64
+ visibility: row.visibility,
65
+ mintedBy: principalIdSchema.parse(row.minted_by),
66
+ mintedAt: row.minted_at,
67
+ expiresAt: row.expires_at,
68
+ state: stateOf(row, now),
69
+ redeemedAt: row.redeemed_at,
70
+ attachmentId: row.attachment_id,
71
+ };
72
+ }
73
+ const MS_PER_SECOND = 1000;
74
+ function principalOf(subject, verb) {
75
+ if (subject.kind !== 'principal') {
76
+ throw substratError('forbidden', `${verb}: only a principal may mint or revoke an upload link — a ${subject.kind} actor has no ` +
77
+ 'authorship to lend an attachment', { reason: 'upload_link_delegation' });
78
+ }
79
+ return subject.id;
80
+ }
81
+ export function createAttachmentVerbs(deps) {
82
+ const readRow = (id) => deps.sql.query(`SELECT ${COLUMNS} FROM _substrat_upload_links WHERE id = ?`, [id])[0];
83
+ return {
84
+ async mintUploadUrl(raw) {
85
+ deps.assertWrites('ctx.attachments.mintUploadUrl');
86
+ const minter = principalOf(deps.subject, 'ctx.attachments.mintUploadUrl');
87
+ const input = uploadLinkMintInput.parse(raw);
88
+ const target = deps.targetOf(input.entity.entityType);
89
+ if (!target) {
90
+ throw substratError('validation_failed', `ctx.attachments.mintUploadUrl: '${input.entity.entityType}' is not a declared attachment target`);
91
+ }
92
+ const policy = target.uploadLinks;
93
+ if (!policy) {
94
+ throw substratError('forbidden', `ctx.attachments.mintUploadUrl: '${input.entity.entityType}' does not declare uploadLinks in its attachmentTargets entry`, { reason: 'upload_links_not_declared' });
95
+ }
96
+ const maxBytes = input.maxBytes ?? policy.maxBytes;
97
+ if (maxBytes > policy.maxBytes || maxBytes > UPLOAD_LINK_MAX_BYTES_CEILING) {
98
+ throw substratError('validation_failed', `ctx.attachments.mintUploadUrl: maxBytes ${maxBytes} is above the target's ${policy.maxBytes} — a link narrows its target's policy, never widens it`);
99
+ }
100
+ // Delegation, never elevation: the key an upload checks, on the entity, as the caller.
101
+ assertAllowed(await deps.check(target.write, input.entity));
102
+ const id = ulid();
103
+ const secret = mintUploadLinkSecret();
104
+ const expiresAt = new Date(Date.parse(deps.now) + (input.ttlSeconds ?? UPLOAD_LINK_DEFAULT_TTL_SECONDS) * MS_PER_SECOND).toISOString();
105
+ const visibility = input.visibility ?? 'internal';
106
+ const contentTypes = policy.contentTypes ?? null;
107
+ deps.sql.exec(`INSERT INTO _substrat_upload_links
108
+ (id, token_hash, entity_type, entity_id, filename, max_bytes, content_types, visibility,
109
+ minted_by, minted_at, expires_at)
110
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
111
+ id,
112
+ await capabilityTokenHash(secret),
113
+ input.entity.entityType,
114
+ input.entity.entityId,
115
+ input.filename ?? null,
116
+ maxBytes,
117
+ contentTypes === null ? null : JSON.stringify(contentTypes),
118
+ visibility,
119
+ minter,
120
+ deps.now,
121
+ expiresAt,
122
+ ]);
123
+ deps.minted.push(secret);
124
+ const payload = {
125
+ linkId: id,
126
+ entity: input.entity,
127
+ filename: input.filename ?? null,
128
+ maxBytes,
129
+ contentTypes,
130
+ visibility,
131
+ expiresAt,
132
+ mintedBy: minter,
133
+ };
134
+ deps.emit({
135
+ type: ATTACHMENT_UPLOAD_LINK_MINTED,
136
+ schemaVersion: 1,
137
+ entity: input.entity,
138
+ piiClass: 'none',
139
+ payload,
140
+ });
141
+ return {
142
+ id,
143
+ secret,
144
+ path: uploadLinkPath(input.entity, secret),
145
+ expiresAt,
146
+ maxBytes,
147
+ filename: input.filename ?? null,
148
+ };
149
+ },
150
+ async revokeUploadLink(id) {
151
+ deps.assertWrites('ctx.attachments.revokeUploadLink');
152
+ const revoker = principalOf(deps.subject, 'ctx.attachments.revokeUploadLink');
153
+ const row = readRow(id);
154
+ if (!row)
155
+ throw substratError('not_found', `no upload link ${id} in this scope`);
156
+ const entity = { entityType: row.entity_type, entityId: row.entity_id };
157
+ const target = deps.targetOf(row.entity_type);
158
+ if (target)
159
+ assertAllowed(await deps.check(target.write, entity));
160
+ // A spent or already-revoked link has nothing left to take back.
161
+ if (row.revoked_at !== null || row.redeemed_at !== null)
162
+ return;
163
+ deps.sql.exec(`UPDATE _substrat_upload_links SET revoked_at = ?, revoked_by = ? WHERE id = ?`, [
164
+ deps.now,
165
+ revoker,
166
+ id,
167
+ ]);
168
+ const payload = { linkId: id, entity, revokedBy: revoker };
169
+ deps.emit({
170
+ type: ATTACHMENT_UPLOAD_LINK_REVOKED,
171
+ schemaVersion: 1,
172
+ entity,
173
+ piiClass: 'none',
174
+ payload,
175
+ });
176
+ },
177
+ uploadLinks(filter) {
178
+ const params = [];
179
+ let where = '';
180
+ if (filter?.entity) {
181
+ where = 'WHERE entity_type = ? AND entity_id = ?';
182
+ params.push(filter.entity.entityType, filter.entity.entityId);
183
+ }
184
+ const limit = Math.min(Math.max(Math.trunc(filter?.limit ?? 50), 1), 200);
185
+ params.push(limit);
186
+ return deps.sql
187
+ .query(`SELECT ${COLUMNS} FROM _substrat_upload_links ${where} ORDER BY id DESC LIMIT ?`, params)
188
+ .map((r) => recordOf(r, deps.now));
189
+ },
190
+ };
191
+ }
192
+ // ---------------------------------------------------------------------------
193
+ // Redemption — the host's half. A secret in, an attachment out.
194
+ // ---------------------------------------------------------------------------
195
+ /** One answer for every secret that is not ours or names nothing, so a probe learns nothing it can act on. */
196
+ const unknownLink = () => substratError('not_found', 'This upload link is not valid.');
197
+ /** The 410: it was ours, and it is spent, expired or revoked. Said only to a holder of the secret. */
198
+ const goneLink = () => substratError('conflict', 'This upload link has been used, has expired or was revoked.', { reason: UPLOAD_LINK_GONE });
199
+ async function liveRow(sql, now, secret) {
200
+ if (!plausibleSecret(secret))
201
+ throw unknownLink();
202
+ const row = sql.query(`SELECT ${COLUMNS} FROM _substrat_upload_links WHERE token_hash = ?`, [await capabilityTokenHash(secret)])[0];
203
+ if (!row)
204
+ throw unknownLink();
205
+ if (stateOf(row, now) !== 'pending')
206
+ throw goneLink();
207
+ return row;
208
+ }
209
+ /**
210
+ * Look a secret up without spending it: `not_found` for one that names nothing, `conflict`
211
+ * with reason `upload_link_gone` for one that is used, expired or revoked.
212
+ */
213
+ export async function inspectUploadLink(deps, secret) {
214
+ const row = await liveRow(deps.sql, deps.now, secret);
215
+ const r = recordOf(row, deps.now);
216
+ return {
217
+ view: {
218
+ entity: r.entity,
219
+ filename: r.filename,
220
+ maxBytes: r.maxBytes,
221
+ contentTypes: r.contentTypes,
222
+ visibility: r.visibility,
223
+ },
224
+ mintedBy: r.mintedBy,
225
+ };
226
+ }
227
+ /**
228
+ * Spend the link for `record`, INSIDE the transaction that inserts the attachment row — so a
229
+ * refusal anywhere rolls the spend back. Judges the record against the link, never the other
230
+ * way round: the target, the author, the size and the type are all the link's to refuse.
231
+ */
232
+ export async function redeemUploadLinkInTransaction(deps, secret, record) {
233
+ const row = await liveRow(deps.sql, deps.now, secret);
234
+ if (row.entity_type !== record.entity.entityType || row.entity_id !== record.entity.entityId) {
235
+ // The path named another entity than the link was minted for: nothing is spent.
236
+ throw substratError('not_found', 'This upload link is not valid for that target.', {
237
+ reason: UPLOAD_LINK_WRONG_TARGET,
238
+ });
239
+ }
240
+ if (record.createdBy !== row.minted_by) {
241
+ throw substratError('internal', 'an upload link was redeemed as someone other than its minter');
242
+ }
243
+ if (record.size > row.max_bytes) {
244
+ throw substratError('validation_failed', `The file is ${record.size} bytes; this link admits at most ${row.max_bytes}.`, {
245
+ reason: UPLOAD_LINK_TOO_LARGE,
246
+ });
247
+ }
248
+ const allowed = row.content_types === null ? null : JSON.parse(row.content_types);
249
+ if (!uploadContentTypeAllowed(allowed, record.contentType)) {
250
+ throw substratError('validation_failed', `A file of type ${record.contentType} is not admitted by this link.`, {
251
+ reason: UPLOAD_LINK_TYPE_REFUSED,
252
+ });
253
+ }
254
+ if (row.filename !== null && record.filename !== row.filename) {
255
+ throw substratError('internal', 'an upload link with a fixed file name was redeemed under another');
256
+ }
257
+ const spent = deps.sql.exec(`UPDATE _substrat_upload_links SET redeemed_at = ?, attachment_id = ?
258
+ WHERE id = ? AND redeemed_at IS NULL AND revoked_at IS NULL`, [deps.now, record.id, row.id]);
259
+ if (spent.changes !== 1)
260
+ throw goneLink();
261
+ const payload = {
262
+ linkId: row.id,
263
+ entity: record.entity,
264
+ attachmentId: record.id,
265
+ size: record.size,
266
+ sha256: record.sha256,
267
+ mintedBy: principalIdSchema.parse(row.minted_by),
268
+ };
269
+ deps.emit({
270
+ type: ATTACHMENT_UPLOAD_LINK_REDEEMED,
271
+ schemaVersion: 1,
272
+ entity: record.entity,
273
+ piiClass: 'none',
274
+ payload,
275
+ });
276
+ }
277
+ //# sourceMappingURL=upload-link.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upload-link.js","sourceRoot":"","sources":["../src/upload-link.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ import type { SwitchSql } from './system-switch.js';
2
+ /** Interpolated into both adapters' directory DDL, so `lint:spine-ddl` sees one spelling on each side. */
3
+ export declare const VERTICAL_SCOPE_STORES_DDL = "\n CREATE TABLE IF NOT EXISTS vertical_scope_stores (\n vertical_slug TEXT NOT NULL,\n binding TEXT NOT NULL,\n kind TEXT NOT NULL,\n -- The version that declared the binding first.\n first_version_id TEXT NOT NULL,\n PRIMARY KEY (vertical_slug, binding)\n );\n CREATE TABLE IF NOT EXISTS vertical_scope_store_versions (\n version_id TEXT PRIMARY KEY,\n vertical_slug TEXT NOT NULL,\n -- 0: the manifest could not be read, so the version may declare a store nobody can name.\n readable INTEGER NOT NULL\n );\n CREATE INDEX IF NOT EXISTS vertical_scope_store_versions_slug\n ON vertical_scope_store_versions (vertical_slug, readable);\n -- A vertical every version of which is indexed above.\n CREATE TABLE IF NOT EXISTS vertical_scope_store_indexed (\n vertical_slug TEXT PRIMARY KEY\n );\n";
4
+ /** One declared binding. */
5
+ export interface VerticalScopeStore {
6
+ readonly binding: string;
7
+ readonly kind: string;
8
+ }
9
+ /**
10
+ * Index a version `publishVersion` is inserting, in the insert's transaction, after its row. The
11
+ * vertical's older versions are indexed first when it is not marked yet, so a re-kind is judged
12
+ * against every version, not only those a read reached. Refuses (`conflict`) a binding the vertical
13
+ * already declared as another kind, or one declared twice, and then nothing is written.
14
+ */
15
+ export declare function indexVersionScopeStores(db: SwitchSql, verticalSlug: string, versionId: string, manifestJson: string | null): void;
16
+ /**
17
+ * Every binding any version of the vertical declared, in binding order. An unmarked vertical is
18
+ * indexed first (the caller runs this in one transaction). Refuses (`unavailable`) when a version's
19
+ * manifest cannot be read, or when versions stored before the record re-kinded a binding: the
20
+ * lifecycle cannot name every store the vertical may hold.
21
+ */
22
+ export declare function verticalScopeStoresOf(db: SwitchSql, verticalSlug: string): VerticalScopeStore[];
23
+ /**
24
+ * Every vertical whose any version declared a binding of `kind`, with those bindings (#2300): the
25
+ * store scripts a release rollout of the kind reaches, including a vertical whose serving version
26
+ * dropped the store while a pinned or preview scope still runs one that declares it. Every vertical
27
+ * not indexed yet is indexed first (the caller runs this in one transaction).
28
+ *
29
+ * `unreadable` names the verticals that cannot say: a version whose manifest cannot be read, or
30
+ * versions stored before the record that re-kinded a binding. Any of them may declare the kind, so
31
+ * a caller that must reach every script (a rollout) refuses while the list is not empty, rather
32
+ * than leave a script on another release without saying so.
33
+ */
34
+ export declare function kindDeclarationsOf(db: SwitchSql, kind: string): {
35
+ verticals: {
36
+ vertical: string;
37
+ stores: VerticalScopeStore[];
38
+ }[];
39
+ unreadable: string[];
40
+ };
41
+ /** A deleted vertical's declarations go with its versions, so a slug registered again starts clean. */
42
+ export declare function forgetVerticalScopeStores(db: SwitchSql, verticalSlug: string): void;
43
+ //# sourceMappingURL=vertical-scope-stores.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vertical-scope-stores.d.ts","sourceRoot":"","sources":["../src/vertical-scope-stores.ts"],"names":[],"mappings":"AA6BA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAEpD,0GAA0G;AAC1G,eAAO,MAAM,yBAAyB,u2BAqBrC,CAAC;AAEF,4BAA4B;AAC5B,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAiFD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,EAAE,EAAE,SAAS,EACb,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,GAAG,IAAI,GAC1B,IAAI,CAGN;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,GAAG,kBAAkB,EAAE,CAa/F;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAChC,EAAE,EAAE,SAAS,EACb,IAAI,EAAE,MAAM,GACX;IAAE,SAAS,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,kBAAkB,EAAE,CAAA;KAAE,EAAE,CAAC;IAAC,UAAU,EAAE,MAAM,EAAE,CAAA;CAAE,CA6B3F;AAED,uGAAuG;AACvG,wBAAgB,yBAAyB,CAAC,EAAE,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAInF"}
@@ -0,0 +1,195 @@
1
+ /**
2
+ * Every scope-store binding a vertical has declared, by any version (#2286, RFC scope-stores L17),
3
+ * shared by both adapters.
4
+ *
5
+ * A store object is named by (tenant, vertical, scope, binding) and carries no version, so a later
6
+ * version that drops a binding leaves the old object holding data. The lifecycle (export, wipe,
7
+ * erasure, the meter) reaches the union of every binding any version declared, which this keeps as
8
+ * a directory table, read by its keys, instead of reading every version's manifest per call.
9
+ *
10
+ * A binding keeps its kind for the life of its vertical. The kind names the store script, so a
11
+ * re-kinded binding would point the same name at another script's object, and the union would have
12
+ * to hold both. `publishVersion` refuses it instead (`conflict`), in the transaction that inserts
13
+ * the version: one door, where the push can still be told to pick a new binding name. A manifest
14
+ * that declares one binding twice is refused there too.
15
+ *
16
+ * Each version is indexed once, and marked in `vertical_scope_store_versions`; a vertical whose
17
+ * versions are all indexed is marked in `vertical_scope_store_indexed`, so a read of it is three
18
+ * keyed lookups and no per-version work. A version stored before these tables, or loaded by a
19
+ * directory restore from an older dump, leaves its vertical unmarked. Whichever comes first, a read
20
+ * or a publish, then indexes all of the vertical's versions, oldest first, in its own transaction:
21
+ * nothing runs on a constructor's path, a restore needs no backfill of its own, and a publish judges
22
+ * a re-kind against every older version, not only those indexed so far. A version whose manifest
23
+ * cannot be read is marked unreadable, and a read of its vertical refuses: it may declare a store
24
+ * nobody can name.
25
+ *
26
+ * Nothing here retires a binding: the record never shrinks (K-50 forbids it as a side effect of a
27
+ * push). The explicit retire step is an open item of the RFC (§10.8).
28
+ */
29
+ import { substratError } from '@substrat-run/contracts';
30
+ /** Interpolated into both adapters' directory DDL, so `lint:spine-ddl` sees one spelling on each side. */
31
+ export const VERTICAL_SCOPE_STORES_DDL = `
32
+ CREATE TABLE IF NOT EXISTS vertical_scope_stores (
33
+ vertical_slug TEXT NOT NULL,
34
+ binding TEXT NOT NULL,
35
+ kind TEXT NOT NULL,
36
+ -- The version that declared the binding first.
37
+ first_version_id TEXT NOT NULL,
38
+ PRIMARY KEY (vertical_slug, binding)
39
+ );
40
+ CREATE TABLE IF NOT EXISTS vertical_scope_store_versions (
41
+ version_id TEXT PRIMARY KEY,
42
+ vertical_slug TEXT NOT NULL,
43
+ -- 0: the manifest could not be read, so the version may declare a store nobody can name.
44
+ readable INTEGER NOT NULL
45
+ );
46
+ CREATE INDEX IF NOT EXISTS vertical_scope_store_versions_slug
47
+ ON vertical_scope_store_versions (vertical_slug, readable);
48
+ -- A vertical every version of which is indexed above.
49
+ CREATE TABLE IF NOT EXISTS vertical_scope_store_indexed (
50
+ vertical_slug TEXT PRIMARY KEY
51
+ );
52
+ `;
53
+ /**
54
+ * The bindings a stored manifest declares, or null when the manifest cannot be read. No manifest
55
+ * declares none. Read for its shape only, `{ binding, kind }` strings as the router reads them:
56
+ * whether a kind is one the platform runs is the push's question, and a record that refused to
57
+ * hold a kind it did not know could not refuse that kind's re-kind either.
58
+ */
59
+ function declaredIn(manifestJson) {
60
+ if (manifestJson === null)
61
+ return [];
62
+ let raw;
63
+ try {
64
+ raw = JSON.parse(manifestJson);
65
+ }
66
+ catch {
67
+ return null;
68
+ }
69
+ if (typeof raw !== 'object' || raw === null || Array.isArray(raw))
70
+ return null;
71
+ const stores = raw.scopeStores;
72
+ if (stores === undefined)
73
+ return [];
74
+ if (!Array.isArray(stores))
75
+ return null;
76
+ const declared = [];
77
+ for (const store of stores) {
78
+ const { binding, kind } = (store ?? {});
79
+ if (typeof binding !== 'string' || typeof kind !== 'string')
80
+ return null;
81
+ declared.push({ binding, kind });
82
+ }
83
+ return declared;
84
+ }
85
+ /** One version, judged and written; a version already indexed is left as it is. */
86
+ function indexOne(db, verticalSlug, versionId, manifestJson) {
87
+ if (db.all('SELECT 1 AS present FROM vertical_scope_store_versions WHERE version_id = ?', versionId).length > 0)
88
+ return;
89
+ const declared = declaredIn(manifestJson);
90
+ // Every binding is judged before any is written, so a refusal writes nothing.
91
+ const fresh = [];
92
+ const seen = new Set();
93
+ for (const { binding, kind } of declared ?? []) {
94
+ if (seen.has(binding)) {
95
+ throw substratError('conflict', `a version of '${verticalSlug}' declares scope store '${binding}' twice`);
96
+ }
97
+ seen.add(binding);
98
+ const [held] = db.all('SELECT kind FROM vertical_scope_stores WHERE vertical_slug = ? AND binding = ?', verticalSlug, binding);
99
+ if (held && held.kind !== kind) {
100
+ throw substratError('conflict', `scope store '${binding}' of '${verticalSlug}' is a '${held.kind}', and a binding keeps its kind for ` +
101
+ `the life of its vertical: objects of the old kind may still hold data under that name. ` +
102
+ `Declare the '${kind}' store under a new binding name`);
103
+ }
104
+ if (!held)
105
+ fresh.push({ binding, kind });
106
+ }
107
+ for (const { binding, kind } of fresh) {
108
+ db.run('INSERT INTO vertical_scope_stores (vertical_slug, binding, kind, first_version_id) VALUES (?, ?, ?, ?)', verticalSlug, binding, kind, versionId);
109
+ }
110
+ db.run('INSERT INTO vertical_scope_store_versions (version_id, vertical_slug, readable) VALUES (?, ?, ?)', versionId, verticalSlug, declared === null ? 0 : 1);
111
+ }
112
+ /** Index every version of the vertical not indexed yet, oldest first, then mark the vertical. */
113
+ function indexVertical(db, verticalSlug) {
114
+ if (db.all('SELECT 1 AS present FROM vertical_scope_store_indexed WHERE vertical_slug = ?', verticalSlug).length > 0)
115
+ return;
116
+ const unindexed = db.all(`SELECT v.id, v.manifest_json FROM vertical_versions AS v
117
+ WHERE v.vertical_slug = ?
118
+ AND NOT EXISTS (SELECT 1 FROM vertical_scope_store_versions AS s WHERE s.version_id = v.id)
119
+ ORDER BY v.created_at, v.id`, verticalSlug);
120
+ for (const { id, manifest_json } of unindexed)
121
+ indexOne(db, verticalSlug, id, manifest_json);
122
+ db.run('INSERT OR IGNORE INTO vertical_scope_store_indexed (vertical_slug) VALUES (?)', verticalSlug);
123
+ }
124
+ /**
125
+ * Index a version `publishVersion` is inserting, in the insert's transaction, after its row. The
126
+ * vertical's older versions are indexed first when it is not marked yet, so a re-kind is judged
127
+ * against every version, not only those a read reached. Refuses (`conflict`) a binding the vertical
128
+ * already declared as another kind, or one declared twice, and then nothing is written.
129
+ */
130
+ export function indexVersionScopeStores(db, verticalSlug, versionId, manifestJson) {
131
+ indexVertical(db, verticalSlug);
132
+ indexOne(db, verticalSlug, versionId, manifestJson);
133
+ }
134
+ /**
135
+ * Every binding any version of the vertical declared, in binding order. An unmarked vertical is
136
+ * indexed first (the caller runs this in one transaction). Refuses (`unavailable`) when a version's
137
+ * manifest cannot be read, or when versions stored before the record re-kinded a binding: the
138
+ * lifecycle cannot name every store the vertical may hold.
139
+ */
140
+ export function verticalScopeStoresOf(db, verticalSlug) {
141
+ try {
142
+ indexVertical(db, verticalSlug);
143
+ }
144
+ catch (error) {
145
+ throw substratError('unavailable', `the scope stores of '${verticalSlug}' cannot be named: ${String(error.message ?? error)}`);
146
+ }
147
+ if (db.all('SELECT 1 AS present FROM vertical_scope_store_versions WHERE vertical_slug = ? AND readable = 0 LIMIT 1', verticalSlug).length > 0) {
148
+ throw substratError('unavailable', `a version of '${verticalSlug}' has a manifest that cannot be read, so its scope stores cannot be named`);
149
+ }
150
+ return db.all('SELECT binding, kind FROM vertical_scope_stores WHERE vertical_slug = ? ORDER BY binding', verticalSlug).map(({ binding, kind }) => ({ binding, kind }));
151
+ }
152
+ /**
153
+ * Every vertical whose any version declared a binding of `kind`, with those bindings (#2300): the
154
+ * store scripts a release rollout of the kind reaches, including a vertical whose serving version
155
+ * dropped the store while a pinned or preview scope still runs one that declares it. Every vertical
156
+ * not indexed yet is indexed first (the caller runs this in one transaction).
157
+ *
158
+ * `unreadable` names the verticals that cannot say: a version whose manifest cannot be read, or
159
+ * versions stored before the record that re-kinded a binding. Any of them may declare the kind, so
160
+ * a caller that must reach every script (a rollout) refuses while the list is not empty, rather
161
+ * than leave a script on another release without saying so.
162
+ */
163
+ export function kindDeclarationsOf(db, kind) {
164
+ const slugs = db.all('SELECT DISTINCT vertical_slug FROM vertical_versions ORDER BY vertical_slug');
165
+ const unreadable = new Set();
166
+ for (const { vertical_slug } of slugs) {
167
+ try {
168
+ indexVertical(db, vertical_slug);
169
+ }
170
+ catch {
171
+ unreadable.add(vertical_slug);
172
+ }
173
+ }
174
+ for (const { vertical_slug } of db.all('SELECT DISTINCT vertical_slug FROM vertical_scope_store_versions WHERE readable = 0')) {
175
+ unreadable.add(vertical_slug);
176
+ }
177
+ const rows = db.all('SELECT vertical_slug, binding FROM vertical_scope_stores WHERE kind = ? ORDER BY vertical_slug, binding', kind);
178
+ const byVertical = new Map();
179
+ for (const { vertical_slug, binding } of rows) {
180
+ const stores = byVertical.get(vertical_slug) ?? [];
181
+ stores.push({ binding, kind });
182
+ byVertical.set(vertical_slug, stores);
183
+ }
184
+ return {
185
+ verticals: [...byVertical].map(([vertical, stores]) => ({ vertical, stores })),
186
+ unreadable: [...unreadable].sort(),
187
+ };
188
+ }
189
+ /** A deleted vertical's declarations go with its versions, so a slug registered again starts clean. */
190
+ export function forgetVerticalScopeStores(db, verticalSlug) {
191
+ db.run('DELETE FROM vertical_scope_stores WHERE vertical_slug = ?', verticalSlug);
192
+ db.run('DELETE FROM vertical_scope_store_versions WHERE vertical_slug = ?', verticalSlug);
193
+ db.run('DELETE FROM vertical_scope_store_indexed WHERE vertical_slug = ?', verticalSlug);
194
+ }
195
+ //# sourceMappingURL=vertical-scope-stores.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vertical-scope-stores.js","sourceRoot":"","sources":["../src/vertical-scope-stores.ts"],"names":[],"mappings":"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"}