@substrat-run/control-plane-api 0.53.0 → 0.54.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.
package/dist/index.d.ts CHANGED
@@ -22,7 +22,7 @@ export type { DoNamespaceReader, DoNamespaceRecord, CfDoNamespaceOptions } from
22
22
  export { createCfObservabilityReader } from './cf-observability.js';
23
23
  export type { CfObservabilityOptions } from './cf-observability.js';
24
24
  export type { ScopeBackup, ScopeBackupStore, DirectoryBackup, DirectoryBackupStore, } from './backups.js';
25
- export { createR2AccessLogSink, createR2BackupStore, createR2DirectoryBackupStore, } from './r2-backups.js';
25
+ export { createR2AccessLogSink, createR2BackupStore, createR2DirectoryBackupStore, pruneAccessLogBatches, pruneScopeBackups, } from './r2-backups.js';
26
26
  export { backupDirectoryIfDue } from './directory-backup.js';
27
27
  export type { DirectoryBackupOptions, DirectoryBackupResult } from './directory-backup.js';
28
28
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AACjD,YAAY,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AACvD,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACpB,aAAa,EACb,2BAA2B,EAC3B,mBAAmB,EACnB,cAAc,EACd,gBAAgB,EAChB,sBAAsB,EACtB,gBAAgB,GACjB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACrG,YAAY,EACV,iBAAiB,EACjB,aAAa,EACb,kBAAkB,EAClB,kBAAkB,EAClB,eAAe,EACf,WAAW,EACX,SAAS,GACV,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACpE,YAAY,EAAE,yBAAyB,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AACxF,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EACL,0BAA0B,EAC1B,qBAAqB,EACrB,uBAAuB,EACvB,mBAAmB,EACnB,sBAAsB,EACtB,sBAAsB,EACtB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,GAC5B,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EACV,qBAAqB,EACrB,sBAAsB,EACtB,mBAAmB,EACnB,sBAAsB,GACvB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,qBAAqB,EACrB,cAAc,EACd,gBAAgB,EAChB,sBAAsB,EACtB,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,gBAAgB,EAAE,cAAc,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AACrG,OAAO,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAC;AAChG,YAAY,EACV,kBAAkB,EAClB,qBAAqB,EACrB,iBAAiB,EACjB,aAAa,EACb,aAAa,GACd,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,iBAAiB,EACjB,uBAAuB,EACvB,yBAAyB,EACzB,mBAAmB,GACpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,+BAA+B,EAC/B,WAAW,EACX,cAAc,EACd,yBAAyB,EACzB,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EACV,yBAAyB,EACzB,gCAAgC,EAChC,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACjG,YAAY,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAAE,yBAAyB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACpF,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AACrG,OAAO,EAAE,2BAA2B,EAAE,MAAM,uBAAuB,CAAC;AACpE,YAAY,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AACpE,YAAY,EACV,WAAW,EACX,gBAAgB,EAChB,eAAe,EACf,oBAAoB,GACrB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,4BAA4B,EAC5B,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,iBAAiB,CAAC;AAIzB,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAC7D,YAAY,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC"}
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ export { blobStoreBindings, collectBlobStoreHandles, collectTenantStoreHandles,
10
10
  export { createCustomHostnameProvisioner, mapCfStatus, extractRecords, reconcilePendingHostnames, isCustomHostname, validateBindableHostname, } from './custom-hostnames.js';
11
11
  export { createCfDoNamespaceReader, namespacesForScript } from './do-namespaces.js';
12
12
  export { createCfObservabilityReader } from './cf-observability.js';
13
- export { createR2AccessLogSink, createR2BackupStore, createR2DirectoryBackupStore, } from './r2-backups.js';
13
+ export { createR2AccessLogSink, createR2BackupStore, createR2DirectoryBackupStore, pruneAccessLogBatches, pruneScopeBackups, } from './r2-backups.js';
14
14
  // #40 — the scheduled directory copy. Exported as a function the CP worker's cron calls,
15
15
  // not as a route with a timer behind it: this package stays library-only, exactly as the
16
16
  // platform-request drain does (the recurrence has to come from a deployment, #444).
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAEjD,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACpB,aAAa,EACb,2BAA2B,EAC3B,mBAAmB,EACnB,cAAc,EACd,gBAAgB,EAChB,sBAAsB,EACtB,gBAAgB,GACjB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAUrG,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEpE,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EACL,0BAA0B,EAC1B,qBAAqB,EACrB,uBAAuB,EACvB,mBAAmB,EACnB,sBAAsB,EACtB,sBAAsB,GAUvB,MAAM,qBAAqB,CAAC;AAO7B,OAAO,EACL,qBAAqB,EACrB,cAAc,EACd,gBAAgB,EAChB,sBAAsB,EACtB,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAC;AAQhG,OAAO,EACL,iBAAiB,EACjB,uBAAuB,EACvB,yBAAyB,EACzB,mBAAmB,GACpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,+BAA+B,EAC/B,WAAW,EACX,cAAc,EACd,yBAAyB,EACzB,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,uBAAuB,CAAC;AAS/B,OAAO,EAAE,yBAAyB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAEpF,OAAO,EAAE,2BAA2B,EAAE,MAAM,uBAAuB,CAAC;AAQpE,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,4BAA4B,GAC7B,MAAM,iBAAiB,CAAC;AACzB,yFAAyF;AACzF,yFAAyF;AACzF,oFAAoF;AACpF,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAEjD,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACpB,aAAa,EACb,2BAA2B,EAC3B,mBAAmB,EACnB,cAAc,EACd,gBAAgB,EAChB,sBAAsB,EACtB,gBAAgB,GACjB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAUrG,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEpE,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EACL,0BAA0B,EAC1B,qBAAqB,EACrB,uBAAuB,EACvB,mBAAmB,EACnB,sBAAsB,EACtB,sBAAsB,GAUvB,MAAM,qBAAqB,CAAC;AAO7B,OAAO,EACL,qBAAqB,EACrB,cAAc,EACd,gBAAgB,EAChB,sBAAsB,EACtB,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAC;AAQhG,OAAO,EACL,iBAAiB,EACjB,uBAAuB,EACvB,yBAAyB,EACzB,mBAAmB,GACpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,+BAA+B,EAC/B,WAAW,EACX,cAAc,EACd,yBAAyB,EACzB,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,uBAAuB,CAAC;AAS/B,OAAO,EAAE,yBAAyB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAEpF,OAAO,EAAE,2BAA2B,EAAE,MAAM,uBAAuB,CAAC;AAQpE,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,4BAA4B,EAC5B,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,iBAAiB,CAAC;AACzB,yFAAyF;AACzF,yFAAyF;AACzF,oFAAoF;AACpF,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC"}
@@ -37,4 +37,56 @@ export declare function createR2DirectoryBackupStore(bucket: unknown): Directory
37
37
  * retention policy to one company's roadmap.
38
38
  */
39
39
  export declare function createR2AccessLogSink(bucket: unknown): AccessLogSink;
40
+ /**
41
+ * The stored-copy lifecycle rule (#557) — the retention leg #36 left unmade.
42
+ *
43
+ * Every prune here is **operator-chosen**: these functions run only when a deployment
44
+ * has named a window (the CP worker's `SCOPE_BACKUP_RETENTION_DAYS` /
45
+ * `ACCESS_LOG_RETENTION_DAYS`), never on a default. That is the #553 posture — the
46
+ * platform never deletes evidence on a schedule a human did not choose — applied to the
47
+ * copies themselves. Enforced in code rather than by an R2 bucket lifecycle rule so the
48
+ * policy is visible in the repo, testable against the fake bucket, and identical on any
49
+ * future non-R2 store.
50
+ *
51
+ * Both prunes are age-based and **conservative by construction**: an object whose age
52
+ * cannot be determined is kept, and a batch is deleted only when its NEWEST row is out
53
+ * of the window — never one that still holds in-window rows. The walk collects stale
54
+ * keys first and deletes after, so deletion never races the pagination cursor.
55
+ */
56
+ interface PruneOptions {
57
+ /**
58
+ * Copies strictly older than this many days are dropped. `0` is honored (drop
59
+ * everything on the next pass) — an operator can ask for it, as with the reap windows.
60
+ */
61
+ olderThanDays: number;
62
+ /** Injected clock, for tests. */
63
+ now?: () => Date;
64
+ }
65
+ /**
66
+ * Drop `scopes/…` reap copies (#493) older than the window. A copy's age is its
67
+ * `capturedAt` — the key's last segment, same as the store's own fallback address — so a
68
+ * hand-uploaded object without metadata still ages out rather than living forever by
69
+ * omission. An unparsable timestamp keeps the copy: deleting what cannot be dated is not
70
+ * retention.
71
+ *
72
+ * Note the interaction with #37 (erasure): per-subject payloads inside these dumps are
73
+ * sealed under per-subject DEKs, so an old copy's PII exposure is bounded by key
74
+ * destruction already — this rule is the cost/hygiene bound, not the privacy one.
75
+ */
76
+ export declare function pruneScopeBackups(bucket: unknown, options: PruneOptions): Promise<number>;
77
+ /**
78
+ * Drop `access-log/…` NDJSON batches (#553) whose NEWEST row is older than the window.
79
+ * Age comes from the mirrored `to` metadata, falling back to the key's own `lastId` (a
80
+ * ULID carries its timestamp), so a batch is datable from either half of what `ship`
81
+ * wrote. Undatable ⇒ kept, as above.
82
+ *
83
+ * The rows themselves already left the directory DO — `pruneAccessLog` deleted them only
84
+ * after this object durably existed — so this window is where the record's Tier 2
85
+ * lifetime finally ends, and choosing it is choosing how far back "which rows left, and
86
+ * when" can be answered from the object store. The admin log's own `drainAccessLog` /
87
+ * `pruneAccessLog` rows are never touched: the egress stays witnessed after its payload
88
+ * expires.
89
+ */
90
+ export declare function pruneAccessLogBatches(bucket: unknown, options: PruneOptions): Promise<number>;
91
+ export {};
40
92
  //# sourceMappingURL=r2-backups.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"r2-backups.d.ts","sourceRoot":"","sources":["../src/r2-backups.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAUH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,KAAK,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAsC3E,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,OAAO,GAAG,gBAAgB,CAsErE;AAcD;;;;;;;;GAQG;AACH,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,OAAO,GAAG,oBAAoB,CAgElF;AAcD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,OAAO,GAAG,aAAa,CAgCpE"}
1
+ {"version":3,"file":"r2-backups.d.ts","sourceRoot":"","sources":["../src/r2-backups.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAUH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,KAAK,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAsC3E,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,OAAO,GAAG,gBAAgB,CAsErE;AAcD;;;;;;;;GAQG;AACH,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,OAAO,GAAG,oBAAoB,CAgElF;AAcD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,OAAO,GAAG,aAAa,CAgCpE;AAED;;;;;;;;;;;;;;;GAeG;AACH,UAAU,YAAY;IACpB;;;OAGG;IACH,aAAa,EAAE,MAAM,CAAC;IACtB,iCAAiC;IACjC,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAClB;AAuBD;;;;;;;;;;GAUG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAa/F;AAiBD;;;;;;;;;;;;GAYG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,OAAO,EACf,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,MAAM,CAAC,CAqBjB"}
@@ -230,4 +230,96 @@ export function createR2AccessLogSink(bucket) {
230
230
  },
231
231
  };
232
232
  }
233
+ /** Walk one prefix completely, returning every object's key + custom metadata. */
234
+ async function listAll(r2, prefix) {
235
+ const out = [];
236
+ let cursor;
237
+ do {
238
+ const page = await r2.list({
239
+ prefix,
240
+ include: ['customMetadata'],
241
+ ...(cursor ? { cursor } : {}),
242
+ });
243
+ out.push(...page.objects);
244
+ cursor = page.truncated ? page.cursor : undefined;
245
+ } while (cursor);
246
+ return out;
247
+ }
248
+ const DAY_MS = 86_400_000;
249
+ /**
250
+ * Drop `scopes/…` reap copies (#493) older than the window. A copy's age is its
251
+ * `capturedAt` — the key's last segment, same as the store's own fallback address — so a
252
+ * hand-uploaded object without metadata still ages out rather than living forever by
253
+ * omission. An unparsable timestamp keeps the copy: deleting what cannot be dated is not
254
+ * retention.
255
+ *
256
+ * Note the interaction with #37 (erasure): per-subject payloads inside these dumps are
257
+ * sealed under per-subject DEKs, so an old copy's PII exposure is bounded by key
258
+ * destruction already — this rule is the cost/hygiene bound, not the privacy one.
259
+ */
260
+ export async function pruneScopeBackups(bucket, options) {
261
+ const r2 = bucket;
262
+ const cutoff = (options.now?.() ?? new Date()).getTime() - options.olderThanDays * DAY_MS;
263
+ let pruned = 0;
264
+ for (const o of await listAll(r2, 'scopes/')) {
265
+ const capturedAt = o.customMetadata?.capturedAt ?? o.key.slice(o.key.lastIndexOf('/') + 1).replace(/\.json$/, '');
266
+ const at = new Date(capturedAt).getTime();
267
+ if (Number.isNaN(at) || at >= cutoff)
268
+ continue;
269
+ await r2.delete(o.key);
270
+ pruned += 1;
271
+ }
272
+ return pruned;
273
+ }
274
+ /** Crockford base32, the ULID alphabet — no I, L, O, U. */
275
+ const ULID_ALPHABET = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
276
+ /** A ULID's timestamp: its first 10 chars, base32-decoded to epoch ms. Null when malformed. */
277
+ function ulidTimeMs(id) {
278
+ if (id.length !== 26)
279
+ return null;
280
+ let ms = 0;
281
+ for (const ch of id.slice(0, 10)) {
282
+ const v = ULID_ALPHABET.indexOf(ch.toUpperCase());
283
+ if (v < 0)
284
+ return null;
285
+ ms = ms * 32 + v;
286
+ }
287
+ return ms;
288
+ }
289
+ /**
290
+ * Drop `access-log/…` NDJSON batches (#553) whose NEWEST row is older than the window.
291
+ * Age comes from the mirrored `to` metadata, falling back to the key's own `lastId` (a
292
+ * ULID carries its timestamp), so a batch is datable from either half of what `ship`
293
+ * wrote. Undatable ⇒ kept, as above.
294
+ *
295
+ * The rows themselves already left the directory DO — `pruneAccessLog` deleted them only
296
+ * after this object durably existed — so this window is where the record's Tier 2
297
+ * lifetime finally ends, and choosing it is choosing how far back "which rows left, and
298
+ * when" can be answered from the object store. The admin log's own `drainAccessLog` /
299
+ * `pruneAccessLog` rows are never touched: the egress stays witnessed after its payload
300
+ * expires.
301
+ */
302
+ export async function pruneAccessLogBatches(bucket, options) {
303
+ const r2 = bucket;
304
+ const cutoff = (options.now?.() ?? new Date()).getTime() - options.olderThanDays * DAY_MS;
305
+ let pruned = 0;
306
+ for (const o of await listAll(r2, 'access-log/')) {
307
+ let newest = null;
308
+ if (o.customMetadata?.to) {
309
+ const at = new Date(o.customMetadata.to).getTime();
310
+ newest = Number.isNaN(at) ? null : at;
311
+ }
312
+ if (newest === null) {
313
+ // `access-log/<firstId>-<lastId>.ndjson` — the lastId is chars 27…52 of the name.
314
+ const name = o.key.slice('access-log/'.length).replace(/\.ndjson$/, '');
315
+ const lastId = name.length === 53 && name[26] === '-' ? name.slice(27) : null;
316
+ newest = lastId ? ulidTimeMs(lastId) : null;
317
+ }
318
+ if (newest === null || newest >= cutoff)
319
+ continue;
320
+ await r2.delete(o.key);
321
+ pruned += 1;
322
+ }
323
+ return pruned;
324
+ }
233
325
  //# sourceMappingURL=r2-backups.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"r2-backups.js","sourceRoot":"","sources":["../src/r2-backups.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"r2-backups.js","sourceRoot":"","sources":["../src/r2-backups.ts"],"names":[],"mappings":"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"}
package/dist/seal.d.ts ADDED
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Per-subject sealing of a scope dump on its way into a platform-retained copy (#37).
3
+ *
4
+ * The sibling of [`mask.ts`](./mask.ts), and the contrast is the point. Masking is for a
5
+ * copy a HUMAN will read: lossy, heuristic, irreversible, and applied when data leaves the
6
+ * governed environment. Sealing is for a copy the PLATFORM keeps: lossless, keyed, and
7
+ * reversible — right up until the subject's key is destroyed, after which it is more
8
+ * irreversible than masking ever was.
9
+ *
10
+ * **Why a backup needs this at all.** Reap backups and stored dumps are full-fidelity on
11
+ * purpose (`backups.ts`: *"a backup that cannot restore is a false promise"*), which is
12
+ * exactly why an erasure cannot reach one. `UPDATE … SET payload = NULL` erases the live
13
+ * scope; it does nothing to the copy sitting in R2 from last Tuesday. Sealing each
14
+ * subject's payloads under their own key on the way out means destroying that one key
15
+ * reaches backwards into every copy already taken — which is the only mechanism that makes
16
+ * "erased" true of an immutable store.
17
+ *
18
+ * **Scope is narrow and deliberate.** Only `_substrat_outbox`, only rows the kernel has
19
+ * already classified (`pii_class != 'none'` with a `subject_id`), only the `payload` cell.
20
+ * The envelope stays readable so a restored copy is still navigable, and vertical-owned
21
+ * tables are untouched — a vertical's own PII is a documented limit of this mechanism, not
22
+ * something quietly half-covered here (see kernel-design.md, open question 17).
23
+ */
24
+ import type { ScopeDumpTable } from '@substrat-run/contracts';
25
+ import type { SealedSecret } from '@substrat-run/kernel';
26
+ /**
27
+ * The key operations both directions need, as `HostAdmin` exposes them. Narrowed to an
28
+ * interface so this module can be tested without a host, and so the seal path cannot reach
29
+ * for any other admin capability.
30
+ */
31
+ export interface SubjectSealer {
32
+ seal(items: readonly {
33
+ subjectId: string;
34
+ plaintext: string;
35
+ }[]): Promise<(SealedSecret | null)[]>;
36
+ open(items: readonly {
37
+ subjectId: string;
38
+ sealed: SealedSecret;
39
+ }[]): Promise<(string | null)[]>;
40
+ }
41
+ /**
42
+ * Seal every classified payload in a dump. Returns new arrays; never mutates the input.
43
+ *
44
+ * A payload whose subject has been shredded seals to `null` — the sealer refuses to mint a
45
+ * key for a tombstoned subject, and the copy records the same absence a live redaction
46
+ * leaves. A cell that is ALREADY sealed passes through untouched, so re-sealing a dump
47
+ * (a backup of a backup, a retried capture) is a no-op rather than double encryption.
48
+ */
49
+ export declare function sealDump(tables: ScopeDumpTable[], sealer: SubjectSealer): Promise<ScopeDumpTable[]>;
50
+ /**
51
+ * Open every sealed payload in a dump — the restore half.
52
+ *
53
+ * A payload whose key is gone opens to `null`. That is where an erasure actually bites: the
54
+ * ciphertext was in the backup all along, and after the shred nothing turns it back into a
55
+ * person. The restored scope then looks exactly like a live scope that was redacted, which
56
+ * is the property worth having — two copies of the same tenant that disagree about who was
57
+ * erased is the failure this shape avoids.
58
+ *
59
+ * Unsealed cells pass through, so a dump taken before sealing existed still restores.
60
+ */
61
+ export declare function openDump(tables: ScopeDumpTable[], sealer: SubjectSealer): Promise<ScopeDumpTable[]>;
62
+ //# sourceMappingURL=seal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seal.d.ts","sourceRoot":"","sources":["../src/seal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzD;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,CACF,KAAK,EAAE,SAAS;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,EAAE,GACzD,OAAO,CAAC,CAAC,YAAY,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC;IACpC,IAAI,CAAC,KAAK,EAAE,SAAS;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,YAAY,CAAA;KAAE,EAAE,GAAG,OAAO,CAAC,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC;CACjG;AAkED;;;;;;;GAOG;AACH,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,cAAc,EAAE,EACxB,MAAM,EAAE,aAAa,GACpB,OAAO,CAAC,cAAc,EAAE,CAAC,CAwB3B;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,cAAc,EAAE,EACxB,MAAM,EAAE,aAAa,GACpB,OAAO,CAAC,cAAc,EAAE,CAAC,CAoB3B"}
package/dist/seal.js ADDED
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Per-subject sealing of a scope dump on its way into a platform-retained copy (#37).
3
+ *
4
+ * The sibling of [`mask.ts`](./mask.ts), and the contrast is the point. Masking is for a
5
+ * copy a HUMAN will read: lossy, heuristic, irreversible, and applied when data leaves the
6
+ * governed environment. Sealing is for a copy the PLATFORM keeps: lossless, keyed, and
7
+ * reversible — right up until the subject's key is destroyed, after which it is more
8
+ * irreversible than masking ever was.
9
+ *
10
+ * **Why a backup needs this at all.** Reap backups and stored dumps are full-fidelity on
11
+ * purpose (`backups.ts`: *"a backup that cannot restore is a false promise"*), which is
12
+ * exactly why an erasure cannot reach one. `UPDATE … SET payload = NULL` erases the live
13
+ * scope; it does nothing to the copy sitting in R2 from last Tuesday. Sealing each
14
+ * subject's payloads under their own key on the way out means destroying that one key
15
+ * reaches backwards into every copy already taken — which is the only mechanism that makes
16
+ * "erased" true of an immutable store.
17
+ *
18
+ * **Scope is narrow and deliberate.** Only `_substrat_outbox`, only rows the kernel has
19
+ * already classified (`pii_class != 'none'` with a `subject_id`), only the `payload` cell.
20
+ * The envelope stays readable so a restored copy is still navigable, and vertical-owned
21
+ * tables are untouched — a vertical's own PII is a documented limit of this mechanism, not
22
+ * something quietly half-covered here (see kernel-design.md, open question 17).
23
+ */
24
+ const SPINE_TABLE = '_substrat_outbox';
25
+ const isSealedCell = (value) => typeof value === 'object' &&
26
+ value !== null &&
27
+ '__sealed' in value &&
28
+ typeof value.__sealed?.ciphertext === 'string';
29
+ /** Parse a cell that may already be a sealed envelope; anything else reads as "not sealed". */
30
+ function readSealed(cell) {
31
+ if (typeof cell !== 'string')
32
+ return undefined;
33
+ try {
34
+ const parsed = JSON.parse(cell);
35
+ return isSealedCell(parsed) ? parsed.__sealed : undefined;
36
+ }
37
+ catch {
38
+ return undefined;
39
+ }
40
+ }
41
+ /** Locate the spine table and the three columns that drive both directions. */
42
+ function spineColumns(tables) {
43
+ const index = tables.findIndex((t) => t.name === SPINE_TABLE);
44
+ if (index < 0)
45
+ return undefined;
46
+ const table = tables[index];
47
+ const subject = table.columns.indexOf('subject_id');
48
+ const pii = table.columns.indexOf('pii_class');
49
+ const payload = table.columns.indexOf('payload');
50
+ // A dump from before any of these columns existed is left entirely alone rather than
51
+ // half-processed — the honest answer for a copy the classification never reached.
52
+ if (subject < 0 || pii < 0 || payload < 0)
53
+ return undefined;
54
+ return { index, subject, pii, payload };
55
+ }
56
+ /** Rows this pass should act on, as (row index, subject id) pairs. */
57
+ function classifiedRows(table, cols) {
58
+ const out = [];
59
+ table.rows.forEach((row, i) => {
60
+ const subjectId = row[cols.subject];
61
+ const piiClass = row[cols.pii];
62
+ if (typeof subjectId !== 'string' || subjectId === '')
63
+ return;
64
+ if (piiClass === 'none' || typeof piiClass !== 'string')
65
+ return;
66
+ if (row[cols.payload] === null || row[cols.payload] === undefined)
67
+ return;
68
+ out.push({ row: i, subjectId });
69
+ });
70
+ return out;
71
+ }
72
+ /**
73
+ * Seal every classified payload in a dump. Returns new arrays; never mutates the input.
74
+ *
75
+ * A payload whose subject has been shredded seals to `null` — the sealer refuses to mint a
76
+ * key for a tombstoned subject, and the copy records the same absence a live redaction
77
+ * leaves. A cell that is ALREADY sealed passes through untouched, so re-sealing a dump
78
+ * (a backup of a backup, a retried capture) is a no-op rather than double encryption.
79
+ */
80
+ export async function sealDump(tables, sealer) {
81
+ const cols = spineColumns(tables);
82
+ if (!cols)
83
+ return tables;
84
+ const table = tables[cols.index];
85
+ const targets = classifiedRows(table, cols).filter((t) => readSealed(table.rows[t.row][cols.payload]) === undefined);
86
+ if (targets.length === 0)
87
+ return tables;
88
+ const sealed = await sealer.seal(targets.map((t) => ({
89
+ subjectId: t.subjectId,
90
+ // Cells are SQLite text; a non-string payload would be a corrupt spine row, and
91
+ // String() keeps that from throwing mid-backup.
92
+ plaintext: String(table.rows[t.row][cols.payload]),
93
+ })));
94
+ const rows = table.rows.map((row) => [...row]);
95
+ targets.forEach((t, i) => {
96
+ const result = sealed[i];
97
+ rows[t.row][cols.payload] = result ? JSON.stringify({ __sealed: result }) : null;
98
+ });
99
+ return tables.map((t, i) => (i === cols.index ? { ...t, rows } : t));
100
+ }
101
+ /**
102
+ * Open every sealed payload in a dump — the restore half.
103
+ *
104
+ * A payload whose key is gone opens to `null`. That is where an erasure actually bites: the
105
+ * ciphertext was in the backup all along, and after the shred nothing turns it back into a
106
+ * person. The restored scope then looks exactly like a live scope that was redacted, which
107
+ * is the property worth having — two copies of the same tenant that disagree about who was
108
+ * erased is the failure this shape avoids.
109
+ *
110
+ * Unsealed cells pass through, so a dump taken before sealing existed still restores.
111
+ */
112
+ export async function openDump(tables, sealer) {
113
+ const cols = spineColumns(tables);
114
+ if (!cols)
115
+ return tables;
116
+ const table = tables[cols.index];
117
+ const targets = [];
118
+ for (const t of classifiedRows(table, cols)) {
119
+ const sealed = readSealed(table.rows[t.row][cols.payload]);
120
+ if (sealed)
121
+ targets.push({ ...t, sealed });
122
+ }
123
+ if (targets.length === 0)
124
+ return tables;
125
+ const opened = await sealer.open(targets.map((t) => ({ subjectId: t.subjectId, sealed: t.sealed })));
126
+ const rows = table.rows.map((row) => [...row]);
127
+ targets.forEach((t, i) => {
128
+ rows[t.row][cols.payload] = opened[i] ?? null;
129
+ });
130
+ return tables.map((t, i) => (i === cols.index ? { ...t, rows } : t));
131
+ }
132
+ //# sourceMappingURL=seal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seal.js","sourceRoot":"","sources":["../src/seal.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@substrat-run/control-plane-api",
3
- "version": "0.53.0",
3
+ "version": "0.54.0",
4
4
  "description": "HTTP surface over HostAdmin — the audited control-plane transport (control-plane.md §4.5)",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {
@@ -23,9 +23,9 @@
23
23
  ],
24
24
  "dependencies": {
25
25
  "hono": "^4.6.0",
26
- "@substrat-run/contracts": "^0.53.0",
27
- "@substrat-run/kernel": "^0.53.0",
28
- "@substrat-run/psl": "^0.2.0"
26
+ "@substrat-run/kernel": "^0.54.0",
27
+ "@substrat-run/psl": "^0.2.0",
28
+ "@substrat-run/contracts": "^0.54.0"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@hono/node-server": "^1.13.0",
@@ -36,7 +36,7 @@
36
36
  "tsx": "^4.19.0",
37
37
  "typescript": "^7.0.0",
38
38
  "vitest": "^3.0.0",
39
- "@substrat-run/adapter-sqlite": "^0.53.0"
39
+ "@substrat-run/adapter-sqlite": "^0.54.0"
40
40
  },
41
41
  "publishConfig": {
42
42
  "access": "public"