@sentropic/artifacts 0.1.4 → 0.2.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/README.md +17 -0
- package/dist/adapters/postgres-commit.d.ts +15 -0
- package/dist/adapters/postgres-commit.js +80 -0
- package/dist/adapters/postgres.d.ts +5 -2
- package/dist/adapters/postgres.js +39 -0
- package/dist/http.d.ts +41 -30
- package/dist/http.js +168 -507
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/mcp-core.d.ts +29 -39
- package/dist/mcp-core.js +83 -244
- package/dist/mcp.js +5 -5
- package/dist/profile/contracts.d.ts +67 -0
- package/dist/profile/contracts.js +31 -0
- package/dist/profile/index.d.ts +6 -0
- package/dist/profile/index.js +117 -0
- package/dist/profile/json.d.ts +10 -0
- package/dist/profile/json.js +76 -0
- package/dist/profile/radar.d.ts +3 -0
- package/dist/profile/radar.js +21 -0
- package/dist/profile/schemas.d.ts +6 -0
- package/dist/profile/schemas.js +76 -0
- package/dist/profile/validation.d.ts +5 -0
- package/dist/profile/validation.js +104 -0
- package/dist/profile/validators.d.ts +3 -0
- package/dist/profile/validators.js +51 -0
- package/dist/server/authorized-replay.d.ts +33 -0
- package/dist/server/authorized-replay.js +31 -0
- package/dist/server/command-identity.d.ts +52 -0
- package/dist/server/command-identity.js +71 -0
- package/dist/server/commit/brands.d.ts +7 -0
- package/dist/server/commit/brands.js +3 -0
- package/dist/server/commit/coordinator.d.ts +2 -0
- package/dist/server/commit/coordinator.js +729 -0
- package/dist/server/commit/types.d.ts +379 -0
- package/dist/server/commit/types.js +94 -0
- package/dist/server/commit-contract.d.ts +4 -0
- package/dist/server/commit-contract.js +2 -0
- package/dist/server/failures.d.ts +84 -0
- package/dist/server/failures.js +133 -0
- package/dist/server/gc.d.ts +42 -1
- package/dist/server/gc.js +168 -5
- package/dist/server/idempotency.d.ts +4 -0
- package/dist/server/idempotency.js +14 -0
- package/dist/server/lifecycle.d.ts +2 -1
- package/dist/server/lifecycle.js +15 -1
- package/dist/server/object-intents.d.ts +14 -1
- package/dist/server/object-intents.js +31 -0
- package/dist/server/outcome-contract.d.ts +3 -0
- package/dist/server/outcome-contract.js +3 -0
- package/dist/server/outcome-projection.d.ts +21 -0
- package/dist/server/outcome-projection.js +29 -0
- package/dist/server/preparation-cleanup.d.ts +31 -0
- package/dist/server/preparation-cleanup.js +106 -0
- package/dist/server/publication.d.ts +76 -0
- package/dist/server/publication.js +270 -31
- package/dist/server/service.d.ts +60 -0
- package/dist/server/service.js +181 -0
- package/dist/testing.d.ts +50 -0
- package/dist/testing.js +86 -0
- package/docs/migration-l6.md +129 -0
- package/examples/radar-commit-setup.ts +299 -0
- package/l0-freeze/command-identity-contract.ts +303 -0
- package/l0-freeze/commit-contract.ts +421 -0
- package/l0-freeze/failure-contract.ts +174 -0
- package/l0-freeze/profile-contract.ts +219 -0
- package/migrations/0003_coordinator.sql +28 -0
- package/package.json +27 -1
- package/src/contracts.ts +125 -0
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* L3 (immo #861) — finalization protocol types (spec r2 §§3.1–3.2).
|
|
3
|
+
*
|
|
4
|
+
* r2-exact server-side shapes, reconciled with the fixed L0 declarations:
|
|
5
|
+
* invocation context, commit context with nested command identity, opaque
|
|
6
|
+
* fence, typed authorization decision, opaque transaction plus the
|
|
7
|
+
* coordinator-bound policy participant, lock order and coordinator
|
|
8
|
+
* request/receipt types. Outcome shapes mirror the fixed success
|
|
9
|
+
* correlation fields (invocationId, commitState, replayed travel on every
|
|
10
|
+
* success). The export-map promotion of these modules is integration-owner
|
|
11
|
+
* work qualified in L6/L8. The public coordinator and participant satisfy
|
|
12
|
+
* the frozen L0 ABI; prepared-unit methods bind the L4/L5 domain adapters.
|
|
13
|
+
*/
|
|
14
|
+
import type { CommitArrivalObserver, CommitBarrierObserver, CommitBarrierPoint } from '../../testing.js';
|
|
15
|
+
import type { commitFenceBrand, commitTransactionBrand } from './brands.js';
|
|
16
|
+
/** Version of the frozen command-codec shape (r2 C6). */
|
|
17
|
+
export declare const COMMAND_CODEC_VERSION: "artifacts-command/v1";
|
|
18
|
+
/** Server-derived internal key: distinct from any caller key. */
|
|
19
|
+
export type InternalIdempotencyKey = string & {
|
|
20
|
+
readonly __brand: 'internal-key';
|
|
21
|
+
};
|
|
22
|
+
/** Server-created identity for one durable command. */
|
|
23
|
+
export interface CommandIdentity {
|
|
24
|
+
readonly internalKey: InternalIdempotencyKey;
|
|
25
|
+
readonly commandHash: string;
|
|
26
|
+
readonly codecVersion: typeof COMMAND_CODEC_VERSION;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* A4 participant lock order inside the finalization transaction. The
|
|
30
|
+
* coordinator acquires it in three groups: object locks up to and including
|
|
31
|
+
* `authority-revision`, then an explicit authority re-read followed by the
|
|
32
|
+
* `quota` callbacks and the provisional quota reservation (so the guard
|
|
33
|
+
* decision never precedes the quota wait), then `blob-reference-deletion-claim`
|
|
34
|
+
* callbacks and provisional writes. Guarded replay runs only the pre-quota
|
|
35
|
+
* groups: it serializes with object/ACL/policy writers but neither charges
|
|
36
|
+
* quota nor claims blob references.
|
|
37
|
+
*/
|
|
38
|
+
export declare const PARTICIPANT_LOCK_ORDER: readonly ["binding-identity", "artifact-lifecycle", "acl", "current-pointer", "authority-revision", "quota", "blob-reference-deletion-claim"];
|
|
39
|
+
export type ParticipantLockClass = (typeof PARTICIPANT_LOCK_ORDER)[number];
|
|
40
|
+
/** Finalize or guarded replay. */
|
|
41
|
+
export type CommitPurpose = 'finalize' | 'replay';
|
|
42
|
+
/** Request-bound immutable invocation context (r2 §3.2). */
|
|
43
|
+
export interface ArtifactInvocationContext {
|
|
44
|
+
readonly invocationId: string;
|
|
45
|
+
readonly actorId: string;
|
|
46
|
+
readonly storageScopeId: string;
|
|
47
|
+
readonly authorizeCommit: AuthorizeCommit;
|
|
48
|
+
}
|
|
49
|
+
/** Commit context: invocation plus operation, identities, revision, purpose. */
|
|
50
|
+
export interface ArtifactCommitContext {
|
|
51
|
+
readonly invocationId: string;
|
|
52
|
+
readonly actorId: string;
|
|
53
|
+
readonly storageScopeId: string;
|
|
54
|
+
readonly operation: string;
|
|
55
|
+
readonly artifactId: string;
|
|
56
|
+
readonly bindingId: string;
|
|
57
|
+
readonly commandIdentity: CommandIdentity;
|
|
58
|
+
readonly policyRevision: string;
|
|
59
|
+
readonly purpose: CommitPurpose;
|
|
60
|
+
/** Coordinator-supplied decoded bytes; optional to preserve frozen L0 guard compatibility. */
|
|
61
|
+
readonly quotaBytes?: number;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Opaque fence minted by the transaction-bound policy participant. It is
|
|
65
|
+
* bound to one transaction, one invocation and one revision, and is
|
|
66
|
+
* verified by the coordinator; it cannot be synthesized from the revision
|
|
67
|
+
* string. This mirrors the frozen `ArtifactAuthorizationFence` role
|
|
68
|
+
* (`l0-freeze/commit-contract.ts`): the `transactionId` carries the binding
|
|
69
|
+
* to the coordinator-issued transaction, `fenceId` stays an opaque fence
|
|
70
|
+
* identity. `mintFence` MUST return `transactionId` equal to the transaction
|
|
71
|
+
* token the coordinator supplied to `createParticipant` for this
|
|
72
|
+
* transaction. The coordinator rejects any fence whose `transactionId`
|
|
73
|
+
* differs from the current transaction, whose `fenceId` is empty, whose
|
|
74
|
+
* invocation differs from the request, or whose revision differs from the
|
|
75
|
+
* freshly re-read policy revision (`invalid_object`).
|
|
76
|
+
*/
|
|
77
|
+
export interface ArtifactAuthorizationFence {
|
|
78
|
+
readonly [commitFenceBrand]: true;
|
|
79
|
+
readonly fenceId: string;
|
|
80
|
+
readonly transactionId: string;
|
|
81
|
+
readonly invocationId: string;
|
|
82
|
+
readonly policyRevision: string;
|
|
83
|
+
}
|
|
84
|
+
export type AuthorizationDenialReason = 'denied' | 'revoked' | 'expired' | 'invalid_object' | 'unavailable';
|
|
85
|
+
/** Typed authorization decision: never an unqualified boolean. */
|
|
86
|
+
export type ArtifactAuthorizationDecision = {
|
|
87
|
+
readonly allowed: true;
|
|
88
|
+
readonly fence: ArtifactAuthorizationFence;
|
|
89
|
+
} | {
|
|
90
|
+
readonly allowed: false;
|
|
91
|
+
readonly reason: AuthorizationDenialReason;
|
|
92
|
+
readonly publicCode: string;
|
|
93
|
+
readonly status: number;
|
|
94
|
+
readonly retriable: boolean;
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* Host guard: current account/cohort/activation/object/quota policy. The
|
|
98
|
+
* coordinator may invoke the guard twice per finalization: once for the
|
|
99
|
+
* guarded decision after all lock/quota waits, and once to revalidate after
|
|
100
|
+
* the post-guard receipt/intent/barrier waits before COMMIT. The guard MUST
|
|
101
|
+
* therefore be side-effect-free (policy reads plus fence minting); only the
|
|
102
|
+
* second allowed decision gates the commit.
|
|
103
|
+
*/
|
|
104
|
+
export type AuthorizeCommit = (context: ArtifactCommitContext, transaction: ArtifactCommitTransaction) => Promise<ArtifactAuthorizationDecision>;
|
|
105
|
+
/**
|
|
106
|
+
* Opaque transaction identity plus the host's registered
|
|
107
|
+
* transaction-bound policy/quota participant. No commit, rollback,
|
|
108
|
+
* release, SQL or connection capability for the guard. The participant
|
|
109
|
+
* handle is a coordinator-internal extension of the fixed opaque shape;
|
|
110
|
+
* fixed-typed guards still accept this object structurally.
|
|
111
|
+
*/
|
|
112
|
+
export interface ArtifactCommitTransaction {
|
|
113
|
+
readonly [commitTransactionBrand]: true;
|
|
114
|
+
readonly transactionId: string;
|
|
115
|
+
readonly invocationId: string;
|
|
116
|
+
readonly policyRevision: string;
|
|
117
|
+
readonly participant: ArtifactCommitParticipant;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Current policy reads and provisional quota reservation on this
|
|
121
|
+
* transaction. This is the mechanism-level seam behind the frozen
|
|
122
|
+
* `ArtifactCommitParticipant` role (`l0-freeze/commit-contract.ts`): the
|
|
123
|
+
* separate revision/approval reads (instead of one bundled admission read)
|
|
124
|
+
* exist so the coordinator can order the authority lock before the quota
|
|
125
|
+
* wait (A4.4) and re-read both after post-guard waits (A4.6).
|
|
126
|
+
*/
|
|
127
|
+
export interface ArtifactPolicyDriver {
|
|
128
|
+
/** Read the current policy revision row (shared lock) on this transaction. */
|
|
129
|
+
readRevision(): Promise<string>;
|
|
130
|
+
/** Check account/cohort/activation approval under the current revision. */
|
|
131
|
+
checkApproval(input: {
|
|
132
|
+
actorId: string;
|
|
133
|
+
operation: string;
|
|
134
|
+
}): Promise<{
|
|
135
|
+
approved: boolean;
|
|
136
|
+
reason?: AuthorizationDenialReason;
|
|
137
|
+
}>;
|
|
138
|
+
/**
|
|
139
|
+
* Provisionally reserve quota on this transaction. Rolls back with the
|
|
140
|
+
* unit when the guard denies or the unit conflicts.
|
|
141
|
+
*/
|
|
142
|
+
reserveQuota(input: {
|
|
143
|
+
actorId: string;
|
|
144
|
+
operation: string;
|
|
145
|
+
storageScopeId: string;
|
|
146
|
+
bytes: number;
|
|
147
|
+
}): Promise<{
|
|
148
|
+
reserved: boolean;
|
|
149
|
+
}>;
|
|
150
|
+
/** Mint the opaque fence for an allowed decision. */
|
|
151
|
+
mintFence(revision: string, invocationId: string): Omit<ArtifactAuthorizationFence, typeof commitFenceBrand>;
|
|
152
|
+
}
|
|
153
|
+
/** Frozen participant ABI plus coordinator-internal policy reads and fence minting. */
|
|
154
|
+
export interface ArtifactCommitParticipant extends Omit<ArtifactPolicyDriver, 'reserveQuota' | 'mintFence'> {
|
|
155
|
+
readonly policyRevision: string;
|
|
156
|
+
readAdmission(input: AdmissionReadInput): Promise<AdmissionReadResult>;
|
|
157
|
+
reserveQuota(input: QuotaReserveInput | {
|
|
158
|
+
actorId: string;
|
|
159
|
+
operation: string;
|
|
160
|
+
}): Promise<{
|
|
161
|
+
reserved: boolean;
|
|
162
|
+
}>;
|
|
163
|
+
mintFence(revision: string, invocationId: string): ArtifactAuthorizationFence;
|
|
164
|
+
}
|
|
165
|
+
export interface AdmissionReadInput {
|
|
166
|
+
readonly operation: string;
|
|
167
|
+
readonly artifactId: string;
|
|
168
|
+
readonly bindingId: string;
|
|
169
|
+
readonly actorId: string;
|
|
170
|
+
readonly policyRevision: string;
|
|
171
|
+
}
|
|
172
|
+
export interface AdmissionReadResult {
|
|
173
|
+
readonly allowed: boolean;
|
|
174
|
+
readonly reason: AuthorizationDenialReason;
|
|
175
|
+
}
|
|
176
|
+
export interface QuotaReserveInput {
|
|
177
|
+
readonly storageScopeId: string;
|
|
178
|
+
readonly operation: string;
|
|
179
|
+
readonly bytes: number;
|
|
180
|
+
}
|
|
181
|
+
/** Factory binding a participant to one finalization connection. */
|
|
182
|
+
export interface CommitPolicyParticipantFactory {
|
|
183
|
+
createParticipant(client: FinalizeTxClient, transactionToken: string): ArtifactPolicyDriver;
|
|
184
|
+
}
|
|
185
|
+
/** Constrained transaction handle for provider-internal provisional work. */
|
|
186
|
+
export interface FinalizeTxClient {
|
|
187
|
+
query<T>(text: string, params: readonly unknown[]): Promise<{
|
|
188
|
+
rows: T[];
|
|
189
|
+
rowCount: number;
|
|
190
|
+
}>;
|
|
191
|
+
}
|
|
192
|
+
/** Finalization connection: coordinator-owned lifecycle plus receipt SQL. */
|
|
193
|
+
export interface FinalizeConnection extends FinalizeTxClient {
|
|
194
|
+
beginReadCommitted(): Promise<void>;
|
|
195
|
+
lockOperationKey(scopeId: string, internalKey: string): Promise<void>;
|
|
196
|
+
readReceipt(scopeId: string, internalKey: string): Promise<StoredCommitReceipt | undefined>;
|
|
197
|
+
insertReceipt(receipt: StoredCommitReceipt): Promise<void>;
|
|
198
|
+
commit(): Promise<void>;
|
|
199
|
+
rollback(): Promise<void>;
|
|
200
|
+
/**
|
|
201
|
+
* Return the client. Pass the failure when the session state is
|
|
202
|
+
* uncertain (failed COMMIT/rollback): the client is destroyed instead
|
|
203
|
+
* of rejoining the pool with an open transaction and held locks.
|
|
204
|
+
*/
|
|
205
|
+
release(error?: unknown): void;
|
|
206
|
+
}
|
|
207
|
+
/** Executor seam: coordination row and raw finalization connections. */
|
|
208
|
+
export interface CommitExecutor {
|
|
209
|
+
ensureCoordinationRow(scopeId: string, internalKey: string): Promise<void>;
|
|
210
|
+
beginFinalization(): Promise<FinalizeConnection>;
|
|
211
|
+
}
|
|
212
|
+
/** One caller lock callback, run inside the coordinator transaction. */
|
|
213
|
+
export type CommitLockFn = (client: FinalizeTxClient) => Promise<void>;
|
|
214
|
+
/** Durable success receipt stored once per (scope, internal key). */
|
|
215
|
+
export interface StoredCommitReceipt {
|
|
216
|
+
readonly scopeId: string;
|
|
217
|
+
readonly internalKey: string;
|
|
218
|
+
readonly commandHash: string;
|
|
219
|
+
readonly codecVersion: string;
|
|
220
|
+
readonly receipt: unknown;
|
|
221
|
+
readonly createdAt: string;
|
|
222
|
+
}
|
|
223
|
+
/** Coordinator-owned test observation points. */
|
|
224
|
+
export type CoordinatorTestPoint = 'before_connection_acquire' | 'before_key_lock' | 'before_finalization_row_lock' | 'after_finalization_locks_before_guard' | 'after_replay_lookup_before_guard' | 'before_commit';
|
|
225
|
+
export declare const COORDINATOR_TEST_POINTS: readonly CommitBarrierPoint[];
|
|
226
|
+
/** One atomic finalization request. */
|
|
227
|
+
export interface FinalizeRequest {
|
|
228
|
+
readonly scopeId: string;
|
|
229
|
+
readonly internalKey: InternalIdempotencyKey;
|
|
230
|
+
readonly commandHash: string;
|
|
231
|
+
readonly codecVersion: string;
|
|
232
|
+
readonly invocationId: string;
|
|
233
|
+
readonly actorId: string;
|
|
234
|
+
readonly operation: string;
|
|
235
|
+
readonly artifactId?: string | null;
|
|
236
|
+
readonly bindingName?: string | null;
|
|
237
|
+
readonly policyRevision: string;
|
|
238
|
+
/** Decoded command bytes covered by the provisional quota reservation. */
|
|
239
|
+
readonly quotaBytes?: number;
|
|
240
|
+
/** Fixed class order; stable bytewise order within each class is the caller's obligation. */
|
|
241
|
+
readonly locks?: Partial<Record<ParticipantLockClass, CommitLockFn[]>>;
|
|
242
|
+
/** Provisional writes before the guard decision. */
|
|
243
|
+
readonly provisional?: (client: FinalizeTxClient) => Promise<void>;
|
|
244
|
+
/** Intent completion inside the unit, after the success insert. */
|
|
245
|
+
readonly finishIntents?: (client: FinalizeTxClient) => Promise<void>;
|
|
246
|
+
/** Preparation-abandonment hook, invoked on loser/denial paths. */
|
|
247
|
+
readonly noteAbandonedAttempt?: (attemptId: string) => Promise<void>;
|
|
248
|
+
readonly attemptId?: string;
|
|
249
|
+
readonly guard: AuthorizeCommit;
|
|
250
|
+
readonly receiptData: unknown;
|
|
251
|
+
/** Domain identity must still match the winner under the replay locks. */
|
|
252
|
+
readonly validateReplayReceipt?: (receipt: StoredCommitReceipt) => boolean;
|
|
253
|
+
}
|
|
254
|
+
/** Internal failure categories (r2 E2). */
|
|
255
|
+
export declare const FAILURE_CATEGORIES: readonly ["capability_unselected", "provider_unavailable", "conflict", "unauthorized_at_commit", "provider_error"];
|
|
256
|
+
export type FailureCategory = (typeof FAILURE_CATEGORIES)[number];
|
|
257
|
+
/** Frozen category-to-public projection (r2 E2). */
|
|
258
|
+
export interface FailureProjection {
|
|
259
|
+
readonly category: FailureCategory;
|
|
260
|
+
readonly variant?: string;
|
|
261
|
+
readonly httpStatus: number;
|
|
262
|
+
readonly publicCode: string;
|
|
263
|
+
/** Null when the failure happens before dispatch and creates no ledger entry. */
|
|
264
|
+
readonly ledgerReason: string | null;
|
|
265
|
+
readonly retriable: boolean;
|
|
266
|
+
}
|
|
267
|
+
export declare const FAILURE_PROJECTIONS: readonly FailureProjection[];
|
|
268
|
+
/**
|
|
269
|
+
* Classified internal failure with private cause and commit state. Carries
|
|
270
|
+
* the frozen `operational` kind so L3 outcomes are assignable to the frozen
|
|
271
|
+
* failure contract (`l0-freeze/failure-contract.ts`).
|
|
272
|
+
*/
|
|
273
|
+
export interface ArtifactFailure {
|
|
274
|
+
readonly kind: 'operational';
|
|
275
|
+
readonly category: FailureCategory;
|
|
276
|
+
readonly variant?: string;
|
|
277
|
+
readonly httpStatus: number;
|
|
278
|
+
readonly publicCode: string;
|
|
279
|
+
readonly retriable: boolean;
|
|
280
|
+
readonly invocationId: string;
|
|
281
|
+
readonly operation: string;
|
|
282
|
+
readonly phase: string;
|
|
283
|
+
readonly commitState: 'not_committed' | 'committed' | 'unknown';
|
|
284
|
+
readonly cause: unknown;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Internal discriminated outcome. Success carries invocation identity,
|
|
288
|
+
* commit state and the replay flag alongside the result, mirroring the
|
|
289
|
+
* fixed success correlation fields.
|
|
290
|
+
*/
|
|
291
|
+
export type ArtifactOutcome<T> = {
|
|
292
|
+
readonly ok: true;
|
|
293
|
+
readonly result: T;
|
|
294
|
+
readonly invocationId: string;
|
|
295
|
+
readonly commitState: 'committed';
|
|
296
|
+
readonly replayed: boolean;
|
|
297
|
+
} | {
|
|
298
|
+
readonly ok: false;
|
|
299
|
+
readonly failure: ArtifactFailure;
|
|
300
|
+
};
|
|
301
|
+
/** Coordinator result: the stored receipt plus fixed success correlation. */
|
|
302
|
+
export type FinalizeResult = ArtifactOutcome<StoredCommitReceipt>;
|
|
303
|
+
/** Coordinator construction options. */
|
|
304
|
+
export interface CommitCoordinatorOptions {
|
|
305
|
+
readonly executor: CommitExecutor;
|
|
306
|
+
readonly policy: CommitPolicyParticipantFactory;
|
|
307
|
+
/** Trusted domain adapters; every call resolves fresh state, including replay. */
|
|
308
|
+
readonly operations?: {
|
|
309
|
+
readonly guardedOperations: readonly string[];
|
|
310
|
+
finalize(input: FinalizationInput): Promise<FinalizeRequest>;
|
|
311
|
+
replay(input: ReplayInput): Promise<FinalizeRequest>;
|
|
312
|
+
};
|
|
313
|
+
/** Defined clock for activation-expiry evaluation; defaults to Date.now. */
|
|
314
|
+
readonly nowMs?: () => number;
|
|
315
|
+
readonly testObserver?: CommitBarrierObserver | CommitArrivalObserver;
|
|
316
|
+
/**
|
|
317
|
+
* Optional filter for observer arrivals; all coordinator points by default.
|
|
318
|
+
* Each armed pair pauses at most once. Only the six coordinator
|
|
319
|
+
* transaction points are accepted; the three preparation points
|
|
320
|
+
* (`before_blob_put`, `after_blob_put_before_verify`,
|
|
321
|
+
* `before_blob_readback`) are fired by L4 preparation adapters holding the
|
|
322
|
+
* same observer, never by this coordinator (preparation precedes the key
|
|
323
|
+
* lock per A4.2). Arming a point the coordinator never fires throws at
|
|
324
|
+
* construction instead of hanging a test.
|
|
325
|
+
*/
|
|
326
|
+
readonly testPoints?: readonly CoordinatorTestPoint[];
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* The atomic finalization coordinator mechanism. It implements the unit
|
|
330
|
+
* owned by the frozen `ArtifactCommitCoordinator` role
|
|
331
|
+
* (`l0-freeze/commit-contract.ts`). Trusted operation bindings translate
|
|
332
|
+
* public frozen inputs to prepared domain units on this same coordinator.
|
|
333
|
+
*/
|
|
334
|
+
export declare const COMMIT_PROTOCOL_VERSION: "artifacts-commit/v1";
|
|
335
|
+
export interface FinalizationRecord {
|
|
336
|
+
readonly internalKey: InternalIdempotencyKey;
|
|
337
|
+
readonly commandHash: string;
|
|
338
|
+
readonly codecVersion: typeof COMMAND_CODEC_VERSION;
|
|
339
|
+
readonly replayed: boolean;
|
|
340
|
+
}
|
|
341
|
+
export interface FinalizationInput {
|
|
342
|
+
readonly request: {
|
|
343
|
+
readonly method: string;
|
|
344
|
+
readonly path: string;
|
|
345
|
+
readonly headers: Record<string, string>;
|
|
346
|
+
readonly body: unknown;
|
|
347
|
+
};
|
|
348
|
+
readonly invocation: ArtifactInvocationContext;
|
|
349
|
+
readonly command: {
|
|
350
|
+
readonly operation: string;
|
|
351
|
+
readonly scopeBinding: string;
|
|
352
|
+
readonly payload: Record<string, unknown>;
|
|
353
|
+
readonly provenance: {
|
|
354
|
+
readonly kind: 'inline-original';
|
|
355
|
+
};
|
|
356
|
+
readonly identity: CommandIdentity;
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
export interface ReplayInput {
|
|
360
|
+
readonly invocation: ArtifactInvocationContext;
|
|
361
|
+
readonly internalKey: InternalIdempotencyKey;
|
|
362
|
+
}
|
|
363
|
+
export interface ArtifactCommitCoordinator {
|
|
364
|
+
readonly protocolVersion: typeof COMMIT_PROTOCOL_VERSION;
|
|
365
|
+
readonly guardedOperations: readonly string[];
|
|
366
|
+
readonly testObserver?: CommitArrivalObserver;
|
|
367
|
+
finalizeOperation(input: FinalizationInput): Promise<ArtifactOutcome<FinalizationRecord>>;
|
|
368
|
+
replayOperation(input: ReplayInput): Promise<ArtifactOutcome<FinalizationRecord>>;
|
|
369
|
+
/** Provider-internal prepared units; domain effects remain inside the same transaction. */
|
|
370
|
+
finalizePrepared(request: FinalizeRequest): Promise<FinalizeResult>;
|
|
371
|
+
replayPrepared(request: FinalizeRequest): Promise<FinalizeResult>;
|
|
372
|
+
}
|
|
373
|
+
/** Unknown legacy codec on a found success row: L6 projects the migration condition. */
|
|
374
|
+
export declare class CommitQuarantineError extends Error {
|
|
375
|
+
readonly scopeId: string;
|
|
376
|
+
readonly internalKey: string;
|
|
377
|
+
readonly codecVersion: string;
|
|
378
|
+
constructor(scopeId: string, internalKey: string, codecVersion: string);
|
|
379
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/** Version of the frozen command-codec shape (r2 C6). */
|
|
2
|
+
export const COMMAND_CODEC_VERSION = 'artifacts-command/v1';
|
|
3
|
+
/**
|
|
4
|
+
* A4 participant lock order inside the finalization transaction. The
|
|
5
|
+
* coordinator acquires it in three groups: object locks up to and including
|
|
6
|
+
* `authority-revision`, then an explicit authority re-read followed by the
|
|
7
|
+
* `quota` callbacks and the provisional quota reservation (so the guard
|
|
8
|
+
* decision never precedes the quota wait), then `blob-reference-deletion-claim`
|
|
9
|
+
* callbacks and provisional writes. Guarded replay runs only the pre-quota
|
|
10
|
+
* groups: it serializes with object/ACL/policy writers but neither charges
|
|
11
|
+
* quota nor claims blob references.
|
|
12
|
+
*/
|
|
13
|
+
export const PARTICIPANT_LOCK_ORDER = [
|
|
14
|
+
'binding-identity',
|
|
15
|
+
'artifact-lifecycle',
|
|
16
|
+
'acl',
|
|
17
|
+
'current-pointer',
|
|
18
|
+
'authority-revision',
|
|
19
|
+
'quota',
|
|
20
|
+
'blob-reference-deletion-claim',
|
|
21
|
+
];
|
|
22
|
+
export const COORDINATOR_TEST_POINTS = [
|
|
23
|
+
'before_connection_acquire',
|
|
24
|
+
'before_key_lock',
|
|
25
|
+
'before_finalization_row_lock',
|
|
26
|
+
'after_finalization_locks_before_guard',
|
|
27
|
+
'after_replay_lookup_before_guard',
|
|
28
|
+
'before_commit',
|
|
29
|
+
];
|
|
30
|
+
/** Internal failure categories (r2 E2). */
|
|
31
|
+
export const FAILURE_CATEGORIES = [
|
|
32
|
+
'capability_unselected',
|
|
33
|
+
'provider_unavailable',
|
|
34
|
+
'conflict',
|
|
35
|
+
'unauthorized_at_commit',
|
|
36
|
+
'provider_error',
|
|
37
|
+
];
|
|
38
|
+
export const FAILURE_PROJECTIONS = [
|
|
39
|
+
{
|
|
40
|
+
category: 'capability_unselected', variant: 'unselected_operation',
|
|
41
|
+
httpStatus: 503, publicCode: 'capability_unavailable', ledgerReason: null,
|
|
42
|
+
retriable: false,
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
category: 'provider_unavailable', variant: 'dispatch_failure',
|
|
46
|
+
httpStatus: 503, publicCode: 'capability_unavailable', ledgerReason: 'provider_unavailable',
|
|
47
|
+
retriable: true,
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
category: 'conflict', variant: 'key_contention',
|
|
51
|
+
httpStatus: 409, publicCode: 'conflict', ledgerReason: 'provider_conflict',
|
|
52
|
+
retriable: false,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
category: 'conflict', variant: 'quota_exhausted',
|
|
56
|
+
httpStatus: 409, publicCode: 'conflict', ledgerReason: 'provider_conflict',
|
|
57
|
+
retriable: false,
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
category: 'unauthorized_at_commit', variant: 'policy_denial',
|
|
61
|
+
httpStatus: 403, publicCode: 'access_denied', ledgerReason: 'unauthorized_at_commit',
|
|
62
|
+
retriable: false,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
category: 'unauthorized_at_commit', variant: 'authority_unavailable',
|
|
66
|
+
httpStatus: 503, publicCode: 'capability_unavailable', ledgerReason: 'unauthorized_at_commit',
|
|
67
|
+
retriable: true,
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
category: 'provider_error', variant: 'unclassified',
|
|
71
|
+
httpStatus: 500, publicCode: 'internal_error', ledgerReason: 'provider_error',
|
|
72
|
+
retriable: false,
|
|
73
|
+
},
|
|
74
|
+
];
|
|
75
|
+
/**
|
|
76
|
+
* The atomic finalization coordinator mechanism. It implements the unit
|
|
77
|
+
* owned by the frozen `ArtifactCommitCoordinator` role
|
|
78
|
+
* (`l0-freeze/commit-contract.ts`). Trusted operation bindings translate
|
|
79
|
+
* public frozen inputs to prepared domain units on this same coordinator.
|
|
80
|
+
*/
|
|
81
|
+
export const COMMIT_PROTOCOL_VERSION = 'artifacts-commit/v1';
|
|
82
|
+
/** Unknown legacy codec on a found success row: L6 projects the migration condition. */
|
|
83
|
+
export class CommitQuarantineError extends Error {
|
|
84
|
+
scopeId;
|
|
85
|
+
internalKey;
|
|
86
|
+
codecVersion;
|
|
87
|
+
constructor(scopeId, internalKey, codecVersion) {
|
|
88
|
+
super(`commit quarantine: codec ${codecVersion} for ${scopeId}/${internalKey}`);
|
|
89
|
+
this.name = 'CommitQuarantineError';
|
|
90
|
+
this.scopeId = scopeId;
|
|
91
|
+
this.internalKey = internalKey;
|
|
92
|
+
this.codecVersion = codecVersion;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Supported trusted-host contracts; public request arguments never carry these values. */
|
|
2
|
+
export type { ArtifactInvocationContext, ArtifactCommitContext, ArtifactCommitTransaction, ArtifactAuthorizationDecision, ArtifactAuthorizationFence, AuthorizeCommit, ArtifactCommitParticipant, CommitPolicyParticipantFactory, FinalizeTxClient, FinalizeRequest, FinalizeResult, ArtifactCommitCoordinator, FinalizationInput, ReplayInput, FinalizationRecord, AdmissionReadInput, AdmissionReadResult, QuotaReserveInput, CommitExecutor, CommitCoordinatorOptions, ArtifactPolicyDriver, CoordinatorTestPoint, InternalIdempotencyKey, } from './commit/types.js';
|
|
3
|
+
export { CommitQuarantineError, PARTICIPANT_LOCK_ORDER, COMMIT_PROTOCOL_VERSION } from './commit/types.js';
|
|
4
|
+
export { createArtifactCommitCoordinator } from './commit/coordinator.js';
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/** Trusted server failures (r2 E1–E3). No driver text participates in classification. */
|
|
2
|
+
export type ArtifactCommitState = 'not_committed' | 'committed' | 'unknown';
|
|
3
|
+
/** Structurally identical to L0's server-derived SHA-256 internal key, never a caller key. */
|
|
4
|
+
export type ArtifactInternalKey = string & {
|
|
5
|
+
readonly __brand: 'internal-key';
|
|
6
|
+
};
|
|
7
|
+
export type ArtifactErrorKind = 'provider_unavailable' | 'command_conflict' | 'pointer_conflict' | 'unauthorized_at_commit' | 'capability_unselected' | 'admission_unavailable' | 'invalid_request' | 'anonymous' | 'access_denied' | 'not_found' | 'gone' | 'unprocessable' | 'verification_failed';
|
|
8
|
+
export type ArtifactFailureCategory = ArtifactFailure['category'];
|
|
9
|
+
interface ArtifactFailureFields {
|
|
10
|
+
readonly variant?: string;
|
|
11
|
+
readonly httpStatus: number;
|
|
12
|
+
readonly publicCode: string;
|
|
13
|
+
readonly retriable: boolean;
|
|
14
|
+
readonly invocationId: string;
|
|
15
|
+
readonly operation: string;
|
|
16
|
+
readonly phase: string;
|
|
17
|
+
readonly commitState: ArtifactCommitState;
|
|
18
|
+
/** Original thrown value, retained by identity and deliberately non-enumerable. */
|
|
19
|
+
readonly cause: unknown;
|
|
20
|
+
}
|
|
21
|
+
export interface ArtifactOperationalFailure extends ArtifactFailureFields {
|
|
22
|
+
readonly kind: 'operational';
|
|
23
|
+
readonly category: 'capability_unselected' | 'provider_unavailable' | 'conflict' | 'unauthorized_at_commit' | 'provider_error';
|
|
24
|
+
}
|
|
25
|
+
export interface ArtifactOrdinaryFailure extends Omit<ArtifactFailureFields, 'variant' | 'retriable'> {
|
|
26
|
+
readonly kind: 'ordinary';
|
|
27
|
+
readonly category: 'invalid_request' | 'not_found' | 'gone' | 'unprocessable' | 'verification_failed';
|
|
28
|
+
readonly retriable: false;
|
|
29
|
+
}
|
|
30
|
+
/** Entry authority outcomes are separate from ordinary and operational failures. */
|
|
31
|
+
export interface ArtifactAdmissionFailure extends ArtifactFailureFields {
|
|
32
|
+
readonly kind: 'admission';
|
|
33
|
+
readonly category: 'admission_unavailable' | 'anonymous' | 'access_denied';
|
|
34
|
+
}
|
|
35
|
+
export type ArtifactFailure = ArtifactOperationalFailure | ArtifactOrdinaryFailure | ArtifactAdmissionFailure;
|
|
36
|
+
export type ArtifactOutcome<T> = {
|
|
37
|
+
readonly ok: true;
|
|
38
|
+
readonly result: T;
|
|
39
|
+
readonly invocationId: string;
|
|
40
|
+
readonly commitState: ArtifactCommitState;
|
|
41
|
+
readonly replayed: boolean;
|
|
42
|
+
} | {
|
|
43
|
+
readonly ok: false;
|
|
44
|
+
readonly failure: ArtifactFailure;
|
|
45
|
+
};
|
|
46
|
+
/** State is supplied by the coordinator, never guessed from an error message. */
|
|
47
|
+
export interface ArtifactFailureContext {
|
|
48
|
+
readonly invocationId: string;
|
|
49
|
+
readonly operation: string;
|
|
50
|
+
readonly phase: string;
|
|
51
|
+
readonly commitState: ArtifactCommitState;
|
|
52
|
+
readonly storageScopeId?: string;
|
|
53
|
+
readonly operationKey?: ArtifactInternalKey;
|
|
54
|
+
}
|
|
55
|
+
/** Trusted guard's public refusal; private driver data must never populate these fields. */
|
|
56
|
+
export interface ArtifactPublicRefusal {
|
|
57
|
+
readonly allowed: false;
|
|
58
|
+
readonly reason: 'denied' | 'revoked' | 'expired' | 'invalid_object' | 'unavailable';
|
|
59
|
+
readonly status: number;
|
|
60
|
+
readonly publicCode: string;
|
|
61
|
+
readonly retriable: boolean;
|
|
62
|
+
}
|
|
63
|
+
export declare class ArtifactOperationError extends Error {
|
|
64
|
+
readonly kind: ArtifactErrorKind;
|
|
65
|
+
readonly details: {
|
|
66
|
+
readonly cause?: unknown;
|
|
67
|
+
readonly refusal?: ArtifactPublicRefusal;
|
|
68
|
+
};
|
|
69
|
+
constructor(kind: ArtifactErrorKind, details?: {
|
|
70
|
+
readonly cause?: unknown;
|
|
71
|
+
readonly refusal?: ArtifactPublicRefusal;
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/** Mint only at verified per-key row contention, using the full scoped operation key. */
|
|
75
|
+
export declare class ArtifactKeyLockConflict extends Error {
|
|
76
|
+
readonly storageScopeId: string;
|
|
77
|
+
readonly operationKey: ArtifactInternalKey;
|
|
78
|
+
constructor(storageScopeId: string, operationKey: ArtifactInternalKey, cause: unknown);
|
|
79
|
+
}
|
|
80
|
+
/** Host wrappers map their own typed errors here; artifacts imports no host error classes. */
|
|
81
|
+
export type ArtifactHostFailureMapper = (cause: unknown) => ArtifactOperationError | undefined;
|
|
82
|
+
/** Pure per-invocation classification. Cleanup errors must not replace the primary cause. */
|
|
83
|
+
export declare function classifyArtifactFailure(cause: unknown, context: ArtifactFailureContext, mapHostError?: ArtifactHostFailureMapper): ArtifactFailure;
|
|
84
|
+
export {};
|