@substrat-run/kernel 0.147.0 → 0.149.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/attachment-sweep.d.ts +208 -0
- package/dist/attachment-sweep.d.ts.map +1 -0
- package/dist/attachment-sweep.js +318 -0
- package/dist/attachment-sweep.js.map +1 -0
- package/dist/audit-outcome.d.ts +1 -1
- package/dist/audit-outcome.d.ts.map +1 -1
- package/dist/audit-outcome.js +4 -2
- package/dist/audit-outcome.js.map +1 -1
- package/dist/export-break.d.ts +137 -0
- package/dist/export-break.d.ts.map +1 -0
- package/dist/export-break.js +219 -0
- package/dist/export-break.js.map +1 -0
- package/dist/index.d.ts +24 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -7
- package/dist/index.js.map +1 -1
- package/dist/install-receipts.d.ts +116 -0
- package/dist/install-receipts.d.ts.map +1 -0
- package/dist/install-receipts.js +162 -0
- package/dist/install-receipts.js.map +1 -0
- package/dist/invocation-line.d.ts +85 -0
- package/dist/invocation-line.d.ts.map +1 -1
- package/dist/invocation-line.js +43 -0
- package/dist/invocation-line.js.map +1 -1
- package/dist/map-bounded.d.ts +8 -0
- package/dist/map-bounded.d.ts.map +1 -0
- package/dist/map-bounded.js +16 -0
- package/dist/map-bounded.js.map +1 -0
- package/dist/migration-digest.d.ts +56 -0
- package/dist/migration-digest.d.ts.map +1 -1
- package/dist/migration-digest.js +92 -0
- package/dist/migration-digest.js.map +1 -1
- package/dist/migration-fence.d.ts +14 -0
- package/dist/migration-fence.d.ts.map +1 -0
- package/dist/migration-fence.js +515 -0
- package/dist/migration-fence.js.map +1 -0
- package/dist/migration-replay.d.ts +52 -0
- package/dist/migration-replay.d.ts.map +1 -0
- package/dist/migration-replay.js +140 -0
- package/dist/migration-replay.js.map +1 -0
- package/dist/module-migrations.d.ts +3 -3
- package/dist/module-migrations.js +3 -3
- package/dist/peer.d.ts +32 -2
- package/dist/peer.d.ts.map +1 -1
- package/dist/peer.js +100 -4
- package/dist/peer.js.map +1 -1
- package/dist/permission-eval.d.ts +8 -0
- package/dist/permission-eval.d.ts.map +1 -1
- package/dist/permission-eval.js +14 -0
- package/dist/permission-eval.js.map +1 -1
- package/dist/platform-sweep.d.ts +114 -361
- package/dist/platform-sweep.d.ts.map +1 -1
- package/dist/platform-sweep.js +134 -561
- package/dist/platform-sweep.js.map +1 -1
- package/dist/promote-review.d.ts +125 -0
- package/dist/promote-review.d.ts.map +1 -0
- package/dist/promote-review.js +181 -0
- package/dist/promote-review.js.map +1 -0
- package/dist/schedule-state.d.ts +91 -0
- package/dist/schedule-state.d.ts.map +1 -0
- package/dist/schedule-state.js +118 -0
- package/dist/schedule-state.js.map +1 -0
- package/dist/scope-copy-ledger.d.ts +29 -0
- package/dist/scope-copy-ledger.d.ts.map +1 -1
- package/dist/scope-copy-ledger.js +23 -0
- package/dist/scope-copy-ledger.js.map +1 -1
- package/dist/scope-copy.d.ts.map +1 -1
- package/dist/scope-copy.js +3 -2
- package/dist/scope-copy.js.map +1 -1
- package/dist/scope-host.d.ts +303 -11
- package/dist/scope-host.d.ts.map +1 -1
- package/dist/scope-host.js.map +1 -1
- package/dist/scope-identity.d.ts +68 -0
- package/dist/scope-identity.d.ts.map +1 -0
- package/dist/scope-identity.js +75 -0
- package/dist/scope-identity.js.map +1 -0
- package/dist/scope-store.d.ts +9 -6
- package/dist/scope-store.d.ts.map +1 -1
- package/dist/scope-store.js.map +1 -1
- package/dist/spine-restore.d.ts +2 -0
- package/dist/spine-restore.d.ts.map +1 -1
- package/dist/spine-restore.js +6 -1
- package/dist/spine-restore.js.map +1 -1
- package/dist/store-driver-releases.d.ts +306 -0
- package/dist/store-driver-releases.d.ts.map +1 -0
- package/dist/store-driver-releases.js +581 -0
- package/dist/store-driver-releases.js.map +1 -0
- package/dist/table-ownership.d.ts +5 -2
- package/dist/table-ownership.d.ts.map +1 -1
- package/dist/table-ownership.js +19 -10
- package/dist/table-ownership.js.map +1 -1
- package/dist/testing.d.ts +2 -0
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +2 -0
- package/dist/testing.js.map +1 -1
- package/dist/upload-link.d.ts +142 -0
- package/dist/upload-link.d.ts.map +1 -0
- package/dist/upload-link.js +441 -0
- package/dist/upload-link.js.map +1 -0
- package/dist/version-migrations.d.ts +63 -1
- package/dist/version-migrations.d.ts.map +1 -1
- package/dist/version-migrations.js +174 -8
- package/dist/version-migrations.js.map +1 -1
- package/dist/vertical-scope-stores.d.ts +43 -0
- package/dist/vertical-scope-stores.d.ts.map +1 -0
- package/dist/vertical-scope-stores.js +195 -0
- package/dist/vertical-scope-stores.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import type { ScopedSql, TenantBlobStore } from './scope-host.js';
|
|
2
|
+
/**
|
|
3
|
+
* The orphan-byte sweep (#2314): delete attachment bytes that no `_substrat_attachments` row
|
|
4
|
+
* points at, written ONCE for both adapters.
|
|
5
|
+
*
|
|
6
|
+
* An attachment write puts the bytes first and the row second, so a crash between the two, a
|
|
7
|
+
* transport failure whose commit is unknown (#2311 keeps the bytes on doubt, K-54) and a PITR
|
|
8
|
+
* rewind past a row all leave an object nobody reads. The one thing this sweep must never do is
|
|
9
|
+
* delete bytes whose row committed, or whose row a restore can still bring back. Four guards
|
|
10
|
+
* hold that, each on its own:
|
|
11
|
+
*
|
|
12
|
+
* 1. **A tombstone is the linearization point.** The scope does not delete; it CONDEMNS. In one
|
|
13
|
+
* scope transaction it records each candidate that has no row in `_substrat_attachment_condemned`,
|
|
14
|
+
* and the attachment insert refuses a condemned id in ITS transaction
|
|
15
|
+
* (`refuseCondemnedAttachment`). The scope serializes the two, so either the row commits first
|
|
16
|
+
* and the condemnation sees it and skips the id, or the condemnation commits first and the row
|
|
17
|
+
* is refused: the upload fails and nothing commits without its bytes. Only a condemned id's
|
|
18
|
+
* object is ever deleted. No clock and no timing is involved, so a slow upload, a retried call
|
|
19
|
+
* or a skewed clock cannot turn a committed row into a deleted object.
|
|
20
|
+
* 2. **An age floor.** Only an object whose id (a ULID minted just before its `put`) is older than
|
|
21
|
+
* `graceMs` is a candidate, so an upload still in flight is never condemned in the first place
|
|
22
|
+
* and therefore never refused. The floor is courtesy, guard 1 is safety.
|
|
23
|
+
* 3. **The restore fence.** A row a rewind or a dump restore took away can come back: PITR keeps
|
|
24
|
+
* every state of the last 30 days. Every path that replaces a scope's state writes a fence
|
|
25
|
+
* object into the tenant's blob store BEFORE it does (`fenceAttachmentSweep`), and the sweep
|
|
26
|
+
* deletes nothing of that scope until the fence is `fenceMs` old. The fence lives in the blob
|
|
27
|
+
* store, never in the scope, because the rewind it records would erase it there. It is read
|
|
28
|
+
* before the pass and again after the condemnation, immediately before the first delete, so a
|
|
29
|
+
* rewind that lands mid-pass stops the deletes. A fence that cannot be read is a fence.
|
|
30
|
+
* 4. **The serving copy only, and only while it is live.** The judgement is only as good as the
|
|
31
|
+
* copy that makes it. A host refuses a scope that is not active, a store that holds nothing, a
|
|
32
|
+
* copy a carry wiped or kept, a fork, and a scope not provisioned for the tenant. It also
|
|
33
|
+
* refuses while another script's copy of the scope is unsettled in the copy ledger
|
|
34
|
+
* (any state but `SETTLED_COPY_STATES`), because that copy may hold rows the serving one lacks. Which
|
|
35
|
+
* deployment is asked is the driver's business: the platform asks the one serving the scope.
|
|
36
|
+
*
|
|
37
|
+
* The instant a pass judges age and the fence against is the HOST's clock, never a caller's
|
|
38
|
+
* (`OrphanSweepRequest` carries none), so no caller can step past guard 2 or guard 3.
|
|
39
|
+
*
|
|
40
|
+
* A dry run condemns nothing and deletes nothing: it reports what a pass would condemn.
|
|
41
|
+
*/
|
|
42
|
+
/** The tombstone table, shared by both adapters so the two cannot part company. */
|
|
43
|
+
export declare const ATTACHMENT_CONDEMNED_DDL = "\n CREATE TABLE IF NOT EXISTS _substrat_attachment_condemned (\n -- The attachment id whose object the sweep deleted, or is about to. No row may ever be\n -- inserted for it again: the insert path refuses it (#2314).\n id TEXT PRIMARY KEY,\n condemned_at TEXT NOT NULL\n );\n";
|
|
44
|
+
/** The refusal reason an upload meets when its id was condemned before its row could commit. */
|
|
45
|
+
export declare const ATTACHMENT_CONDEMNED = "attachment_condemned";
|
|
46
|
+
/** How old an object must be before a pass looks at it, by default: seven days. */
|
|
47
|
+
export declare const ORPHAN_SWEEP_GRACE_MS: number;
|
|
48
|
+
/**
|
|
49
|
+
* How long a scope's bytes are left alone after its state was replaced, by default: 35 days. A
|
|
50
|
+
* Durable Object keeps 30 days of point-in-time history, so a row a rewind took away can be
|
|
51
|
+
* restored for that long; five more days absorb the clocks and the time a rewind takes to arm.
|
|
52
|
+
*/
|
|
53
|
+
export declare const ORPHAN_SWEEP_FENCE_MS: number;
|
|
54
|
+
/** The most objects one pass deletes, by default. */
|
|
55
|
+
export declare const ORPHAN_SWEEP_LIMIT = 500;
|
|
56
|
+
/** The most ids one condemnation call carries: one JSON parameter, so no SQL limit applies. */
|
|
57
|
+
export declare const ORPHAN_CONDEMN_BATCH = 100;
|
|
58
|
+
/**
|
|
59
|
+
* How long a tombstone is kept. Past this no upload that minted the id can still be in flight
|
|
60
|
+
* (an upload holds its body in memory, and its call to the scope is one request), so the
|
|
61
|
+
* tombstone guards nothing any more.
|
|
62
|
+
*/
|
|
63
|
+
export declare const ORPHAN_TOMBSTONE_RETENTION_MS: number;
|
|
64
|
+
/**
|
|
65
|
+
* Refuse an insert of a condemned attachment id. Called by both adapters inside the
|
|
66
|
+
* attachment write's own transaction, before its `INSERT`, so it and the condemnation are
|
|
67
|
+
* serialized by the scope (guard 1). A `conflict` is a refusal the scope ANSWERS, so the host
|
|
68
|
+
* compensates the object away (it is already gone, or about to be) and the caller is told.
|
|
69
|
+
*/
|
|
70
|
+
export declare function refuseCondemnedAttachment(sql: ScopedSql, attachmentId: string): void;
|
|
71
|
+
/**
|
|
72
|
+
* Refuse a dump load that would put back a row for an id this store has condemned (#2314). A
|
|
73
|
+
* load is the other way an `_substrat_attachments` row is written, so it meets the same
|
|
74
|
+
* tombstone the insert does: a restore that raced a pass must not land a row whose bytes that
|
|
75
|
+
* pass is deleting. Called inside the load's transaction, BEFORE its first drop, against the
|
|
76
|
+
* store as it is. A store with no tombstone table (from before #2314) has condemned nothing.
|
|
77
|
+
*/
|
|
78
|
+
export declare function refuseCondemnedInDump(sql: ScopedSql, tables: readonly {
|
|
79
|
+
name: string;
|
|
80
|
+
columns: readonly string[];
|
|
81
|
+
rows: readonly (readonly unknown[])[];
|
|
82
|
+
}[]): void;
|
|
83
|
+
/** The ids in `ids` that no `_substrat_attachments` row names. Read-only. */
|
|
84
|
+
export declare function rowlessAttachmentIds(sql: ScopedSql, ids: readonly string[]): string[];
|
|
85
|
+
/**
|
|
86
|
+
* Condemn the ids in `ids` that no row names, inside the caller's scope transaction (guard 1),
|
|
87
|
+
* and return every id that is condemned and rowless now: the ones condemned here and the ones an
|
|
88
|
+
* earlier pass condemned but did not get to delete. Only those may be deleted. Idempotent.
|
|
89
|
+
*
|
|
90
|
+
* Each returned id's tombstone is stamped `now`, re-stamped if an earlier pass condemned it, so
|
|
91
|
+
* the prune of tombstones past `ORPHAN_TOMBSTONE_RETENTION_MS` that follows can never take the
|
|
92
|
+
* tombstone of an id this pass is about to delete.
|
|
93
|
+
*/
|
|
94
|
+
export declare function condemnOrphanAttachments(sql: ScopedSql, ids: readonly string[], now: string): string[];
|
|
95
|
+
/**
|
|
96
|
+
* The copy-ledger states (#1722) in which another script's copy of a scope can no longer hold a
|
|
97
|
+
* row the serving copy lacks, nor discover one. An ALLOWLIST: every other state, a future one
|
|
98
|
+
* included, holds the pass. Only `done` qualifies: the copy was wiped (or found already wiped),
|
|
99
|
+
* reaped with its scope, or is the move's destination, the copy that serves. Every other state can
|
|
100
|
+
* still hold, or still turn up, a write the serving copy never saw:
|
|
101
|
+
*
|
|
102
|
+
* - `pending`: a move in flight; its outcome is not known yet.
|
|
103
|
+
* - `eligible`: a carry's source awaiting its fenced wipe. The wipe is what discovers a write that
|
|
104
|
+
* landed after the export and turns the copy `kept`, and it is best effort, so until it has run
|
|
105
|
+
* the copy may hold an upload whose bytes share this scope's prefix.
|
|
106
|
+
* - `kept`: such a write was found; staff resolve it.
|
|
107
|
+
* - `retained`: a copy kept on purpose (a bind that did not land, a copy from before the ledger),
|
|
108
|
+
* whose contents nobody checked against the serving copy.
|
|
109
|
+
*/
|
|
110
|
+
export declare const SETTLED_COPY_STATES: readonly string[];
|
|
111
|
+
/** The refusal of a pass over a scope with a copy not yet settled, or null when there is none. */
|
|
112
|
+
export declare function unsettledCopyRefusal(scopeId: string, copies: readonly {
|
|
113
|
+
state: string;
|
|
114
|
+
scriptRef: string;
|
|
115
|
+
}[]): Error | null;
|
|
116
|
+
/**
|
|
117
|
+
* What a pass throws when the deployment holds no attachment store for the tenant, yet the scope
|
|
118
|
+
* holds attachment rows (#2314): its store binding is missing, which is a failure to fix, never the
|
|
119
|
+
* `bucket: 'none'` of a scope that keeps no files.
|
|
120
|
+
*/
|
|
121
|
+
export declare function missingAttachmentStore(tenantId: string, scopeId: string): Error;
|
|
122
|
+
/** The blob key of a scope's restore fence (guard 3). Outside `att/`, so no pass ever lists it. */
|
|
123
|
+
export declare function attachmentSweepFenceKey(scopeId: string): string;
|
|
124
|
+
/**
|
|
125
|
+
* Record that a scope's state is about to be replaced (a PITR rewind, a dump restore), so no pass
|
|
126
|
+
* deletes its bytes for `ORPHAN_SWEEP_FENCE_MS`. Call it BEFORE the replacement and let a throw
|
|
127
|
+
* refuse the replacement: a rewind whose fence was not written could hand the sweep a state that
|
|
128
|
+
* lacks rows a later restore brings back.
|
|
129
|
+
*/
|
|
130
|
+
export declare function fenceAttachmentSweep(store: TenantBlobStore, scopeId: string, now: string): Promise<void>;
|
|
131
|
+
/**
|
|
132
|
+
* What a caller asks of a host's pass (`ScopeHost.sweepOrphanAttachments`). No instant: the
|
|
133
|
+
* host judges the age floor and the fence against its OWN clock, so no caller can step past
|
|
134
|
+
* either by handing in a later time.
|
|
135
|
+
*/
|
|
136
|
+
export interface OrphanSweepRequest {
|
|
137
|
+
/** Report what a pass would condemn and delete, and change nothing. */
|
|
138
|
+
dryRun?: boolean;
|
|
139
|
+
/** The most objects deleted in one pass. Default `ORPHAN_SWEEP_LIMIT`. */
|
|
140
|
+
limit?: number;
|
|
141
|
+
/**
|
|
142
|
+
* A host with no directory (a CP-less deployment) cannot read the copy ledger, so it cannot see
|
|
143
|
+
* a kept or pending copy of the scope in another version's script, whose rows the serving copy
|
|
144
|
+
* lacks. It refuses unless its caller says it checked the ledger and found no such copy.
|
|
145
|
+
*/
|
|
146
|
+
copiesSettled?: boolean;
|
|
147
|
+
/**
|
|
148
|
+
* The version the platform routed this pass to: the one the scope runs, by the directory. A host
|
|
149
|
+
* that knows the version its own code is deployed as refuses a pass routed to another (a stale
|
|
150
|
+
* script, a dispatch mixup, a promote mid-pass). See {@link orphanSweepVersionRefusal}.
|
|
151
|
+
*/
|
|
152
|
+
expectedVersionId?: string;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Why a pass may not run in a script deployed as `deployed`, when the platform routed it to the
|
|
156
|
+
* script running `expected` (#2314), or null. A script deployed as another version refuses any
|
|
157
|
+
* pass. A deployed script that carries no version identity cannot be told it is the one routed to,
|
|
158
|
+
* so it refuses a destructive pass and lets a dry run proceed, since a dry run deletes nothing.
|
|
159
|
+
* No `expected` (a host the platform does not route to, a self-host) asks nothing.
|
|
160
|
+
*/
|
|
161
|
+
export declare function orphanSweepVersionRefusal(deployed: string | null, expected: string | undefined, destructive: boolean): Error | null;
|
|
162
|
+
/** The options the kernel's pass takes from its host: the request, and the host's clock. */
|
|
163
|
+
export interface OrphanSweepOptions {
|
|
164
|
+
/** The instant the pass judges age and the fence against: the HOST's clock, never a caller's. */
|
|
165
|
+
now: string;
|
|
166
|
+
/** Report what a pass would condemn and delete, and change nothing. */
|
|
167
|
+
dryRun?: boolean;
|
|
168
|
+
/** Candidate age floor in ms. Default `ORPHAN_SWEEP_GRACE_MS`; never below one day. */
|
|
169
|
+
graceMs?: number;
|
|
170
|
+
/** How long a fence holds. Default `ORPHAN_SWEEP_FENCE_MS`; never below 31 days. */
|
|
171
|
+
fenceMs?: number;
|
|
172
|
+
/** The most objects deleted in one pass. Default `ORPHAN_SWEEP_LIMIT`. */
|
|
173
|
+
limit?: number;
|
|
174
|
+
}
|
|
175
|
+
export interface OrphanSweepReport {
|
|
176
|
+
/** Objects under the scope's attachment prefix. */
|
|
177
|
+
listed: number;
|
|
178
|
+
/** Objects old enough to be judged. */
|
|
179
|
+
candidates: number;
|
|
180
|
+
/** Objects whose id no row names: condemned (or, in a dry run, that would be). */
|
|
181
|
+
orphans: number;
|
|
182
|
+
/** Objects deleted. Always 0 in a dry run, and while fenced. */
|
|
183
|
+
deleted: number;
|
|
184
|
+
/** The keys deleted, or in a dry run the keys that would be. At most `limit`. */
|
|
185
|
+
keys: string[];
|
|
186
|
+
/** Set when the restore fence held the pass: the instant until which it holds. */
|
|
187
|
+
fencedUntil?: string;
|
|
188
|
+
dryRun: boolean;
|
|
189
|
+
/**
|
|
190
|
+
* `'none'` when the deployment holds no attachment store for the tenant at all, so nothing was
|
|
191
|
+
* listed (#2314). Set apart from a pass that listed an empty store, so a store binding missing
|
|
192
|
+
* from a vertical that does keep files cannot pass for a clean sweep.
|
|
193
|
+
*/
|
|
194
|
+
bucket?: 'none';
|
|
195
|
+
}
|
|
196
|
+
/** What the scope side of a pass is: the read for a dry run, the condemnation otherwise. */
|
|
197
|
+
export interface OrphanSweepScope {
|
|
198
|
+
rowless(ids: readonly string[]): Promise<string[]>;
|
|
199
|
+
condemn(ids: readonly string[], now: string): Promise<string[]>;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* One pass over one scope's attachment bytes. The host supplies the store and the scope side;
|
|
203
|
+
* this decides, in this order: the fence, the candidates by age, the condemnation in batches,
|
|
204
|
+
* the fence again, the deletes. Every failure before a delete leaves every object in place, and a
|
|
205
|
+
* delete that fails leaves its tombstone, so the next pass finishes it.
|
|
206
|
+
*/
|
|
207
|
+
export declare function sweepOrphanAttachments(store: TenantBlobStore, scopeId: string, scope: OrphanSweepScope, opts: OrphanSweepOptions): Promise<OrphanSweepReport>;
|
|
208
|
+
//# sourceMappingURL=attachment-sweep.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachment-sweep.d.ts","sourceRoot":"","sources":["../src/attachment-sweep.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { substratError } from '@substrat-run/contracts';
|
|
2
|
+
import { attachmentBlobKey } from './scope-host.js';
|
|
3
|
+
import { ulidTime } from './ulid.js';
|
|
4
|
+
/**
|
|
5
|
+
* The orphan-byte sweep (#2314): delete attachment bytes that no `_substrat_attachments` row
|
|
6
|
+
* points at, written ONCE for both adapters.
|
|
7
|
+
*
|
|
8
|
+
* An attachment write puts the bytes first and the row second, so a crash between the two, a
|
|
9
|
+
* transport failure whose commit is unknown (#2311 keeps the bytes on doubt, K-54) and a PITR
|
|
10
|
+
* rewind past a row all leave an object nobody reads. The one thing this sweep must never do is
|
|
11
|
+
* delete bytes whose row committed, or whose row a restore can still bring back. Four guards
|
|
12
|
+
* hold that, each on its own:
|
|
13
|
+
*
|
|
14
|
+
* 1. **A tombstone is the linearization point.** The scope does not delete; it CONDEMNS. In one
|
|
15
|
+
* scope transaction it records each candidate that has no row in `_substrat_attachment_condemned`,
|
|
16
|
+
* and the attachment insert refuses a condemned id in ITS transaction
|
|
17
|
+
* (`refuseCondemnedAttachment`). The scope serializes the two, so either the row commits first
|
|
18
|
+
* and the condemnation sees it and skips the id, or the condemnation commits first and the row
|
|
19
|
+
* is refused: the upload fails and nothing commits without its bytes. Only a condemned id's
|
|
20
|
+
* object is ever deleted. No clock and no timing is involved, so a slow upload, a retried call
|
|
21
|
+
* or a skewed clock cannot turn a committed row into a deleted object.
|
|
22
|
+
* 2. **An age floor.** Only an object whose id (a ULID minted just before its `put`) is older than
|
|
23
|
+
* `graceMs` is a candidate, so an upload still in flight is never condemned in the first place
|
|
24
|
+
* and therefore never refused. The floor is courtesy, guard 1 is safety.
|
|
25
|
+
* 3. **The restore fence.** A row a rewind or a dump restore took away can come back: PITR keeps
|
|
26
|
+
* every state of the last 30 days. Every path that replaces a scope's state writes a fence
|
|
27
|
+
* object into the tenant's blob store BEFORE it does (`fenceAttachmentSweep`), and the sweep
|
|
28
|
+
* deletes nothing of that scope until the fence is `fenceMs` old. The fence lives in the blob
|
|
29
|
+
* store, never in the scope, because the rewind it records would erase it there. It is read
|
|
30
|
+
* before the pass and again after the condemnation, immediately before the first delete, so a
|
|
31
|
+
* rewind that lands mid-pass stops the deletes. A fence that cannot be read is a fence.
|
|
32
|
+
* 4. **The serving copy only, and only while it is live.** The judgement is only as good as the
|
|
33
|
+
* copy that makes it. A host refuses a scope that is not active, a store that holds nothing, a
|
|
34
|
+
* copy a carry wiped or kept, a fork, and a scope not provisioned for the tenant. It also
|
|
35
|
+
* refuses while another script's copy of the scope is unsettled in the copy ledger
|
|
36
|
+
* (any state but `SETTLED_COPY_STATES`), because that copy may hold rows the serving one lacks. Which
|
|
37
|
+
* deployment is asked is the driver's business: the platform asks the one serving the scope.
|
|
38
|
+
*
|
|
39
|
+
* The instant a pass judges age and the fence against is the HOST's clock, never a caller's
|
|
40
|
+
* (`OrphanSweepRequest` carries none), so no caller can step past guard 2 or guard 3.
|
|
41
|
+
*
|
|
42
|
+
* A dry run condemns nothing and deletes nothing: it reports what a pass would condemn.
|
|
43
|
+
*/
|
|
44
|
+
/** The tombstone table, shared by both adapters so the two cannot part company. */
|
|
45
|
+
export const ATTACHMENT_CONDEMNED_DDL = `
|
|
46
|
+
CREATE TABLE IF NOT EXISTS _substrat_attachment_condemned (
|
|
47
|
+
-- The attachment id whose object the sweep deleted, or is about to. No row may ever be
|
|
48
|
+
-- inserted for it again: the insert path refuses it (#2314).
|
|
49
|
+
id TEXT PRIMARY KEY,
|
|
50
|
+
condemned_at TEXT NOT NULL
|
|
51
|
+
);
|
|
52
|
+
`;
|
|
53
|
+
/** The refusal reason an upload meets when its id was condemned before its row could commit. */
|
|
54
|
+
export const ATTACHMENT_CONDEMNED = 'attachment_condemned';
|
|
55
|
+
/** How old an object must be before a pass looks at it, by default: seven days. */
|
|
56
|
+
export const ORPHAN_SWEEP_GRACE_MS = 7 * 86_400_000;
|
|
57
|
+
/**
|
|
58
|
+
* How long a scope's bytes are left alone after its state was replaced, by default: 35 days. A
|
|
59
|
+
* Durable Object keeps 30 days of point-in-time history, so a row a rewind took away can be
|
|
60
|
+
* restored for that long; five more days absorb the clocks and the time a rewind takes to arm.
|
|
61
|
+
*/
|
|
62
|
+
export const ORPHAN_SWEEP_FENCE_MS = 35 * 86_400_000;
|
|
63
|
+
/** The most objects one pass deletes, by default. */
|
|
64
|
+
export const ORPHAN_SWEEP_LIMIT = 500;
|
|
65
|
+
/** The most ids one condemnation call carries: one JSON parameter, so no SQL limit applies. */
|
|
66
|
+
export const ORPHAN_CONDEMN_BATCH = 100;
|
|
67
|
+
/**
|
|
68
|
+
* How long a tombstone is kept. Past this no upload that minted the id can still be in flight
|
|
69
|
+
* (an upload holds its body in memory, and its call to the scope is one request), so the
|
|
70
|
+
* tombstone guards nothing any more.
|
|
71
|
+
*/
|
|
72
|
+
export const ORPHAN_TOMBSTONE_RETENTION_MS = 30 * 86_400_000;
|
|
73
|
+
/**
|
|
74
|
+
* Refuse an insert of a condemned attachment id. Called by both adapters inside the
|
|
75
|
+
* attachment write's own transaction, before its `INSERT`, so it and the condemnation are
|
|
76
|
+
* serialized by the scope (guard 1). A `conflict` is a refusal the scope ANSWERS, so the host
|
|
77
|
+
* compensates the object away (it is already gone, or about to be) and the caller is told.
|
|
78
|
+
*/
|
|
79
|
+
export function refuseCondemnedAttachment(sql, attachmentId) {
|
|
80
|
+
const condemned = sql.query('SELECT 1 AS hit FROM _substrat_attachment_condemned WHERE id = ?', [attachmentId]);
|
|
81
|
+
if (condemned.length > 0) {
|
|
82
|
+
throw substratError('conflict', `attachment ${attachmentId}: its bytes were swept as an orphan before this row could commit; upload the file again`, { reason: ATTACHMENT_CONDEMNED });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Refuse a dump load that would put back a row for an id this store has condemned (#2314). A
|
|
87
|
+
* load is the other way an `_substrat_attachments` row is written, so it meets the same
|
|
88
|
+
* tombstone the insert does: a restore that raced a pass must not land a row whose bytes that
|
|
89
|
+
* pass is deleting. Called inside the load's transaction, BEFORE its first drop, against the
|
|
90
|
+
* store as it is. A store with no tombstone table (from before #2314) has condemned nothing.
|
|
91
|
+
*/
|
|
92
|
+
export function refuseCondemnedInDump(sql, tables) {
|
|
93
|
+
const has = sql.query(`SELECT 1 AS hit FROM sqlite_master WHERE type = 'table' AND name = '_substrat_attachment_condemned'`);
|
|
94
|
+
if (has.length === 0)
|
|
95
|
+
return;
|
|
96
|
+
const ids = [];
|
|
97
|
+
for (const table of tables) {
|
|
98
|
+
if (table.name.toLowerCase() !== '_substrat_attachments')
|
|
99
|
+
continue;
|
|
100
|
+
const at = table.columns.indexOf('id');
|
|
101
|
+
if (at < 0)
|
|
102
|
+
continue;
|
|
103
|
+
for (const row of table.rows)
|
|
104
|
+
if (typeof row[at] === 'string')
|
|
105
|
+
ids.push(row[at]);
|
|
106
|
+
}
|
|
107
|
+
for (let i = 0; i < ids.length; i += 500) {
|
|
108
|
+
const hit = sql.query(`SELECT c.id AS id FROM _substrat_attachment_condemned c
|
|
109
|
+
WHERE c.id IN (SELECT value FROM json_each(?)) ORDER BY c.id LIMIT 5`, [JSON.stringify(ids.slice(i, i + 500))]);
|
|
110
|
+
if (hit.length > 0) {
|
|
111
|
+
throw substratError('conflict', `restore refused: the dump holds attachment rows whose bytes the orphan sweep condemned in this ` +
|
|
112
|
+
`scope (${hit.map((h) => h.id).join(', ')}). Their bytes are gone or going; restore a dump taken ` +
|
|
113
|
+
`after they were swept, or remove those rows from the dump`, { reason: ATTACHMENT_CONDEMNED });
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/** The ids in `ids` that no `_substrat_attachments` row names. Read-only. */
|
|
118
|
+
export function rowlessAttachmentIds(sql, ids) {
|
|
119
|
+
if (ids.length === 0)
|
|
120
|
+
return [];
|
|
121
|
+
return sql
|
|
122
|
+
.query(`SELECT j.value AS value FROM json_each(?) AS j
|
|
123
|
+
WHERE NOT EXISTS (SELECT 1 FROM _substrat_attachments a WHERE a.id = j.value)
|
|
124
|
+
ORDER BY j.value`, [JSON.stringify(ids)])
|
|
125
|
+
.map((r) => r.value);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Condemn the ids in `ids` that no row names, inside the caller's scope transaction (guard 1),
|
|
129
|
+
* and return every id that is condemned and rowless now: the ones condemned here and the ones an
|
|
130
|
+
* earlier pass condemned but did not get to delete. Only those may be deleted. Idempotent.
|
|
131
|
+
*
|
|
132
|
+
* Each returned id's tombstone is stamped `now`, re-stamped if an earlier pass condemned it, so
|
|
133
|
+
* the prune of tombstones past `ORPHAN_TOMBSTONE_RETENTION_MS` that follows can never take the
|
|
134
|
+
* tombstone of an id this pass is about to delete.
|
|
135
|
+
*/
|
|
136
|
+
export function condemnOrphanAttachments(sql, ids, now) {
|
|
137
|
+
if (ids.length > ORPHAN_CONDEMN_BATCH) {
|
|
138
|
+
throw new RangeError(`at most ${ORPHAN_CONDEMN_BATCH} ids are condemned at once, got ${ids.length}`);
|
|
139
|
+
}
|
|
140
|
+
const rowless = rowlessAttachmentIds(sql, ids);
|
|
141
|
+
for (const id of rowless) {
|
|
142
|
+
sql.exec(`INSERT INTO _substrat_attachment_condemned (id, condemned_at) VALUES (?, ?)
|
|
143
|
+
ON CONFLICT (id) DO UPDATE SET condemned_at = excluded.condemned_at`, [id, now]);
|
|
144
|
+
}
|
|
145
|
+
// Only a call that condemned something writes at all, so a pass that finds nothing leaves the
|
|
146
|
+
// scope's write revision where it was (a carry or a kept-copy release is fenced on it). The prune
|
|
147
|
+
// still never reaches this call's own tombstones, stamped `now`, past a horizon well before it.
|
|
148
|
+
if (rowless.length > 0) {
|
|
149
|
+
const horizon = new Date(Date.parse(now) - ORPHAN_TOMBSTONE_RETENTION_MS).toISOString();
|
|
150
|
+
sql.exec('DELETE FROM _substrat_attachment_condemned WHERE condemned_at < ?', [horizon]);
|
|
151
|
+
}
|
|
152
|
+
return rowless;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* The copy-ledger states (#1722) in which another script's copy of a scope can no longer hold a
|
|
156
|
+
* row the serving copy lacks, nor discover one. An ALLOWLIST: every other state, a future one
|
|
157
|
+
* included, holds the pass. Only `done` qualifies: the copy was wiped (or found already wiped),
|
|
158
|
+
* reaped with its scope, or is the move's destination, the copy that serves. Every other state can
|
|
159
|
+
* still hold, or still turn up, a write the serving copy never saw:
|
|
160
|
+
*
|
|
161
|
+
* - `pending`: a move in flight; its outcome is not known yet.
|
|
162
|
+
* - `eligible`: a carry's source awaiting its fenced wipe. The wipe is what discovers a write that
|
|
163
|
+
* landed after the export and turns the copy `kept`, and it is best effort, so until it has run
|
|
164
|
+
* the copy may hold an upload whose bytes share this scope's prefix.
|
|
165
|
+
* - `kept`: such a write was found; staff resolve it.
|
|
166
|
+
* - `retained`: a copy kept on purpose (a bind that did not land, a copy from before the ledger),
|
|
167
|
+
* whose contents nobody checked against the serving copy.
|
|
168
|
+
*/
|
|
169
|
+
export const SETTLED_COPY_STATES = ['done'];
|
|
170
|
+
/** The refusal of a pass over a scope with a copy not yet settled, or null when there is none. */
|
|
171
|
+
export function unsettledCopyRefusal(scopeId, copies) {
|
|
172
|
+
const unsettled = copies.filter((c) => !SETTLED_COPY_STATES.includes(c.state));
|
|
173
|
+
if (unsettled.length === 0)
|
|
174
|
+
return null;
|
|
175
|
+
return substratError('conflict', `refused: scope ${scopeId} has a copy in another script that may hold rows this copy lacks ` +
|
|
176
|
+
`(${unsettled.map((c) => `${c.scriptRef}: ${c.state}`).join(', ')}). Settle it (#1722) before its bytes are swept`);
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* What a pass throws when the deployment holds no attachment store for the tenant, yet the scope
|
|
180
|
+
* holds attachment rows (#2314): its store binding is missing, which is a failure to fix, never the
|
|
181
|
+
* `bucket: 'none'` of a scope that keeps no files.
|
|
182
|
+
*/
|
|
183
|
+
export function missingAttachmentStore(tenantId, scopeId) {
|
|
184
|
+
return new Error(`scope ${scopeId} holds attachment rows, but this deployment holds no attachment store for tenant ${tenantId}: ` +
|
|
185
|
+
'its store binding is missing. Re-push the vertical so the store is declared, then sweep again');
|
|
186
|
+
}
|
|
187
|
+
/** The blob key of a scope's restore fence (guard 3). Outside `att/`, so no pass ever lists it. */
|
|
188
|
+
export function attachmentSweepFenceKey(scopeId) {
|
|
189
|
+
return `scope/${scopeId}/sweep-fence`;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Record that a scope's state is about to be replaced (a PITR rewind, a dump restore), so no pass
|
|
193
|
+
* deletes its bytes for `ORPHAN_SWEEP_FENCE_MS`. Call it BEFORE the replacement and let a throw
|
|
194
|
+
* refuse the replacement: a rewind whose fence was not written could hand the sweep a state that
|
|
195
|
+
* lacks rows a later restore brings back.
|
|
196
|
+
*/
|
|
197
|
+
export async function fenceAttachmentSweep(store, scopeId, now) {
|
|
198
|
+
await store.put(attachmentSweepFenceKey(scopeId), new TextEncoder().encode(now), { contentType: 'text/plain' });
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Until when the fence holds a scope's bytes, or null when there is no fence. A fence that
|
|
202
|
+
* cannot be read or parsed is held to hold forever: the sweep fails closed.
|
|
203
|
+
*/
|
|
204
|
+
async function fencedUntil(store, scopeId, fenceMs) {
|
|
205
|
+
const fence = await store.get(attachmentSweepFenceKey(scopeId));
|
|
206
|
+
if (!fence)
|
|
207
|
+
return null;
|
|
208
|
+
const at = Date.parse(new TextDecoder().decode(fence.body));
|
|
209
|
+
if (!Number.isFinite(at))
|
|
210
|
+
return Number.POSITIVE_INFINITY;
|
|
211
|
+
return at + fenceMs;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Why a pass may not run in a script deployed as `deployed`, when the platform routed it to the
|
|
215
|
+
* script running `expected` (#2314), or null. A script deployed as another version refuses any
|
|
216
|
+
* pass. A deployed script that carries no version identity cannot be told it is the one routed to,
|
|
217
|
+
* so it refuses a destructive pass and lets a dry run proceed, since a dry run deletes nothing.
|
|
218
|
+
* No `expected` (a host the platform does not route to, a self-host) asks nothing.
|
|
219
|
+
*/
|
|
220
|
+
export function orphanSweepVersionRefusal(deployed, expected, destructive) {
|
|
221
|
+
if (expected === undefined)
|
|
222
|
+
return null;
|
|
223
|
+
if (deployed === null) {
|
|
224
|
+
return destructive
|
|
225
|
+
? substratError('conflict', `refused: this script carries no version identity, so a destructive pass cannot be told it reached ` +
|
|
226
|
+
`the script running version ${expected}, the one the platform routed it to`)
|
|
227
|
+
: null;
|
|
228
|
+
}
|
|
229
|
+
if (deployed !== expected) {
|
|
230
|
+
return substratError('conflict', `refused: this script runs version ${deployed}, not ${expected}, the one the platform routed this pass to`);
|
|
231
|
+
}
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
/** The smallest grace a caller may ask for: an upload is one request, never a day. */
|
|
235
|
+
const MIN_GRACE_MS = 86_400_000;
|
|
236
|
+
/** The smallest fence a caller may ask for: the PITR window plus a day. */
|
|
237
|
+
const MIN_FENCE_MS = 31 * 86_400_000;
|
|
238
|
+
/**
|
|
239
|
+
* One pass over one scope's attachment bytes. The host supplies the store and the scope side;
|
|
240
|
+
* this decides, in this order: the fence, the candidates by age, the condemnation in batches,
|
|
241
|
+
* the fence again, the deletes. Every failure before a delete leaves every object in place, and a
|
|
242
|
+
* delete that fails leaves its tombstone, so the next pass finishes it.
|
|
243
|
+
*/
|
|
244
|
+
export async function sweepOrphanAttachments(store, scopeId, scope, opts) {
|
|
245
|
+
const nowMs = Date.parse(opts.now);
|
|
246
|
+
if (!Number.isFinite(nowMs))
|
|
247
|
+
throw new RangeError(`not an instant: ${opts.now}`);
|
|
248
|
+
const graceMs = opts.graceMs ?? ORPHAN_SWEEP_GRACE_MS;
|
|
249
|
+
const fenceMs = opts.fenceMs ?? ORPHAN_SWEEP_FENCE_MS;
|
|
250
|
+
const limit = opts.limit ?? ORPHAN_SWEEP_LIMIT;
|
|
251
|
+
if (!(graceMs >= MIN_GRACE_MS))
|
|
252
|
+
throw new RangeError(`graceMs must be at least ${MIN_GRACE_MS}, got ${graceMs}`);
|
|
253
|
+
if (!(fenceMs >= MIN_FENCE_MS))
|
|
254
|
+
throw new RangeError(`fenceMs must be at least ${MIN_FENCE_MS}, got ${fenceMs}`);
|
|
255
|
+
if (!Number.isSafeInteger(limit) || limit < 0)
|
|
256
|
+
throw new RangeError(`limit must be a non-negative integer, got ${limit}`);
|
|
257
|
+
const dryRun = opts.dryRun === true;
|
|
258
|
+
const report = { listed: 0, candidates: 0, orphans: 0, deleted: 0, keys: [], dryRun };
|
|
259
|
+
const fenced = async () => {
|
|
260
|
+
const until = await fencedUntil(store, scopeId, fenceMs);
|
|
261
|
+
if (until === null || until <= nowMs)
|
|
262
|
+
return false;
|
|
263
|
+
report.fencedUntil = Number.isFinite(until) ? new Date(until).toISOString() : 'never';
|
|
264
|
+
return true;
|
|
265
|
+
};
|
|
266
|
+
if (await fenced())
|
|
267
|
+
return report;
|
|
268
|
+
const prefix = attachmentBlobKey(scopeId, '');
|
|
269
|
+
const keys = await store.list(prefix);
|
|
270
|
+
report.listed = keys.length;
|
|
271
|
+
const cutoff = nowMs - graceMs;
|
|
272
|
+
const candidates = [];
|
|
273
|
+
for (const key of keys) {
|
|
274
|
+
const id = key.slice(prefix.length);
|
|
275
|
+
// Only a key this platform wrote for an attachment is judged: anything else is left alone.
|
|
276
|
+
if (key !== attachmentBlobKey(scopeId, id))
|
|
277
|
+
continue;
|
|
278
|
+
let minted;
|
|
279
|
+
try {
|
|
280
|
+
minted = ulidTime(id);
|
|
281
|
+
}
|
|
282
|
+
catch {
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
if (minted <= cutoff)
|
|
286
|
+
candidates.push(id);
|
|
287
|
+
}
|
|
288
|
+
report.candidates = candidates.length;
|
|
289
|
+
const orphans = [];
|
|
290
|
+
for (let i = 0; i < candidates.length && orphans.length < limit; i += ORPHAN_CONDEMN_BATCH) {
|
|
291
|
+
const batch = candidates.slice(i, i + ORPHAN_CONDEMN_BATCH);
|
|
292
|
+
const judged = dryRun ? await scope.rowless(batch) : await scope.condemn(batch, opts.now);
|
|
293
|
+
// The scope may only ever answer with ids it was asked about.
|
|
294
|
+
const asked = new Set(batch);
|
|
295
|
+
for (const id of judged)
|
|
296
|
+
if (asked.has(id))
|
|
297
|
+
orphans.push(id);
|
|
298
|
+
}
|
|
299
|
+
report.orphans = orphans.length;
|
|
300
|
+
const doomed = orphans.slice(0, limit);
|
|
301
|
+
if (dryRun) {
|
|
302
|
+
report.keys = doomed.map((id) => attachmentBlobKey(scopeId, id));
|
|
303
|
+
return report;
|
|
304
|
+
}
|
|
305
|
+
if (doomed.length === 0)
|
|
306
|
+
return report;
|
|
307
|
+
// Guard 3 again, after the condemnation: a rewind that armed mid-pass wrote its fence first.
|
|
308
|
+
if (await fenced())
|
|
309
|
+
return report;
|
|
310
|
+
for (const id of doomed) {
|
|
311
|
+
const key = attachmentBlobKey(scopeId, id);
|
|
312
|
+
await store.delete(key);
|
|
313
|
+
report.keys.push(key);
|
|
314
|
+
report.deleted++;
|
|
315
|
+
}
|
|
316
|
+
return report;
|
|
317
|
+
}
|
|
318
|
+
//# sourceMappingURL=attachment-sweep.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachment-sweep.js","sourceRoot":"","sources":["../src/attachment-sweep.ts"],"names":[],"mappings":"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"}
|
package/dist/audit-outcome.d.ts
CHANGED
|
@@ -38,7 +38,7 @@ import type { OpsFailureInput } from './scope-host.js';
|
|
|
38
38
|
* capability revoke is written the same way around the revoke in the scope's store
|
|
39
39
|
* (`auditedCapabilityRevoke`), which on the shared control plane is another deployment's.
|
|
40
40
|
*/
|
|
41
|
-
export declare const AUDITED_CHANGE_ACTIONS: readonly ["transferOwner", "manageScopeMember", "setPeerBinding", "revokeFromSystem", "restoreToSystem", "revokeFromPeer", "restoreToPeer", "moveImportCursor", "revokeCapability"];
|
|
41
|
+
export declare const AUDITED_CHANGE_ACTIONS: readonly ["bindConnectionScope", "transferOwner", "manageScopeMember", "setPeerBinding", "revokeFromSystem", "restoreToSystem", "revokeFromPeer", "restoreToPeer", "moveImportCursor", "revokeCapability"];
|
|
42
42
|
export type AuditedChangeAction = (typeof AUDITED_CHANGE_ACTIONS)[number];
|
|
43
43
|
/** The structured line an outcome row that could not be written leaves, keyed so a log search finds every one. */
|
|
44
44
|
export declare const UNRECORDED_OUTCOME_LOG = "audit-outcome-unrecorded";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit-outcome.d.ts","sourceRoot":"","sources":["../src/audit-outcome.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,OAAO,
|
|
1
|
+
{"version":3,"file":"audit-outcome.d.ts","sourceRoot":"","sources":["../src/audit-outcome.ts"],"names":[],"mappings":"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"}
|
package/dist/audit-outcome.js
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
* settle's grace window, so a live request cannot normally be overtaken this way. The rule is
|
|
28
28
|
* stated for the case where one is.
|
|
29
29
|
*/
|
|
30
|
-
import { AUDIT_ERROR_MAX, auditOperationId, memberChangeAudit, ownerTransferAudit, peerBindingAudit, provesNothingChanged, substratError } from '@substrat-run/contracts';
|
|
30
|
+
import { AUDIT_ERROR_MAX, auditOperationId, connectionBindingAudit, memberChangeAudit, ownerTransferAudit, peerBindingAudit, provesNothingChanged, substratError } from '@substrat-run/contracts';
|
|
31
31
|
import { SWITCH_ACTIONS } from './system-switch-record.js';
|
|
32
32
|
import { ulid } from './ulid.js';
|
|
33
33
|
/**
|
|
@@ -40,6 +40,7 @@ import { ulid } from './ulid.js';
|
|
|
40
40
|
* (`auditedCapabilityRevoke`), which on the shared control plane is another deployment's.
|
|
41
41
|
*/
|
|
42
42
|
export const AUDITED_CHANGE_ACTIONS = [
|
|
43
|
+
'bindConnectionScope',
|
|
43
44
|
'transferOwner',
|
|
44
45
|
'manageScopeMember',
|
|
45
46
|
'setPeerBinding',
|
|
@@ -252,7 +253,8 @@ function auditedUnknownOf(row, after, error) {
|
|
|
252
253
|
const entry = { ...after, tenantId: row.tenant_id, scopeId: row.scope_id, phase: 'unknown', error };
|
|
253
254
|
const { tenantId: _t, scopeId: _s, ...parsed } = row.action === 'transferOwner' ? ownerTransferAudit.parse(entry)
|
|
254
255
|
: row.action === 'manageScopeMember' ? memberChangeAudit.parse(entry)
|
|
255
|
-
:
|
|
256
|
+
: row.action === 'bindConnectionScope' ? connectionBindingAudit.parse(entry)
|
|
257
|
+
: peerBindingAudit.parse(entry);
|
|
256
258
|
return parsed;
|
|
257
259
|
}
|
|
258
260
|
const REAL_OUTCOMES = new Set(['applied', 'refused', 'failed']);
|