@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.
Files changed (69) hide show
  1. package/README.md +17 -0
  2. package/dist/adapters/postgres-commit.d.ts +15 -0
  3. package/dist/adapters/postgres-commit.js +80 -0
  4. package/dist/adapters/postgres.d.ts +5 -2
  5. package/dist/adapters/postgres.js +39 -0
  6. package/dist/http.d.ts +41 -30
  7. package/dist/http.js +168 -507
  8. package/dist/index.d.ts +2 -0
  9. package/dist/index.js +2 -0
  10. package/dist/mcp-core.d.ts +29 -39
  11. package/dist/mcp-core.js +83 -244
  12. package/dist/mcp.js +5 -5
  13. package/dist/profile/contracts.d.ts +67 -0
  14. package/dist/profile/contracts.js +31 -0
  15. package/dist/profile/index.d.ts +6 -0
  16. package/dist/profile/index.js +117 -0
  17. package/dist/profile/json.d.ts +10 -0
  18. package/dist/profile/json.js +76 -0
  19. package/dist/profile/radar.d.ts +3 -0
  20. package/dist/profile/radar.js +21 -0
  21. package/dist/profile/schemas.d.ts +6 -0
  22. package/dist/profile/schemas.js +76 -0
  23. package/dist/profile/validation.d.ts +5 -0
  24. package/dist/profile/validation.js +104 -0
  25. package/dist/profile/validators.d.ts +3 -0
  26. package/dist/profile/validators.js +51 -0
  27. package/dist/server/authorized-replay.d.ts +33 -0
  28. package/dist/server/authorized-replay.js +31 -0
  29. package/dist/server/command-identity.d.ts +52 -0
  30. package/dist/server/command-identity.js +71 -0
  31. package/dist/server/commit/brands.d.ts +7 -0
  32. package/dist/server/commit/brands.js +3 -0
  33. package/dist/server/commit/coordinator.d.ts +2 -0
  34. package/dist/server/commit/coordinator.js +729 -0
  35. package/dist/server/commit/types.d.ts +379 -0
  36. package/dist/server/commit/types.js +94 -0
  37. package/dist/server/commit-contract.d.ts +4 -0
  38. package/dist/server/commit-contract.js +2 -0
  39. package/dist/server/failures.d.ts +84 -0
  40. package/dist/server/failures.js +133 -0
  41. package/dist/server/gc.d.ts +42 -1
  42. package/dist/server/gc.js +168 -5
  43. package/dist/server/idempotency.d.ts +4 -0
  44. package/dist/server/idempotency.js +14 -0
  45. package/dist/server/lifecycle.d.ts +2 -1
  46. package/dist/server/lifecycle.js +15 -1
  47. package/dist/server/object-intents.d.ts +14 -1
  48. package/dist/server/object-intents.js +31 -0
  49. package/dist/server/outcome-contract.d.ts +3 -0
  50. package/dist/server/outcome-contract.js +3 -0
  51. package/dist/server/outcome-projection.d.ts +21 -0
  52. package/dist/server/outcome-projection.js +29 -0
  53. package/dist/server/preparation-cleanup.d.ts +31 -0
  54. package/dist/server/preparation-cleanup.js +106 -0
  55. package/dist/server/publication.d.ts +76 -0
  56. package/dist/server/publication.js +270 -31
  57. package/dist/server/service.d.ts +60 -0
  58. package/dist/server/service.js +181 -0
  59. package/dist/testing.d.ts +50 -0
  60. package/dist/testing.js +86 -0
  61. package/docs/migration-l6.md +129 -0
  62. package/examples/radar-commit-setup.ts +299 -0
  63. package/l0-freeze/command-identity-contract.ts +303 -0
  64. package/l0-freeze/commit-contract.ts +421 -0
  65. package/l0-freeze/failure-contract.ts +174 -0
  66. package/l0-freeze/profile-contract.ts +219 -0
  67. package/migrations/0003_coordinator.sql +28 -0
  68. package/package.json +27 -1
  69. package/src/contracts.ts +125 -0
@@ -0,0 +1,421 @@
1
+ /**
2
+ * L0 frozen commit declaration (immo #861, ask 2 shape, spec r2 section 3.2).
3
+ *
4
+ * Frozen server-side public exports for the Radar acceptance target:
5
+ * request-bound invocation context, commit context, typed authorization
6
+ * decision with an opaque fence, opaque commit transaction and the
7
+ * coordinator role. Barrier points, lane lock order and app signatures
8
+ * follow in later slices. Runtime implementation lands in L3.
9
+ */
10
+
11
+ import type {
12
+ CommandIdentity,
13
+ COMMAND_CODEC_VERSION,
14
+ EffectiveArtifactCommand,
15
+ InternalIdempotencyKey,
16
+ } from './command-identity-contract.js';
17
+ import type { ArtifactFailure, ArtifactOutcome, OrdinaryFailure } from './failure-contract.js';
18
+ import type { CompiledArtifactsProfile, CompiledValidators } from './profile-contract.js';
19
+
20
+ /** Version of the frozen commit-coordinator protocol. */
21
+ export const COMMIT_PROTOCOL_VERSION = 'artifacts-commit/v1' as const;
22
+
23
+ /** Outcome of a COMMIT acknowledgment wait. */
24
+ export const COMMIT_STATES = ['not_committed', 'committed', 'unknown'] as const;
25
+
26
+ export type CommitState = (typeof COMMIT_STATES)[number];
27
+
28
+ /** Purpose of one guard invocation: final decision or replayed receipt. */
29
+ export const COMMIT_PURPOSES = ['finalize', 'replay'] as const;
30
+
31
+ export type CommitPurpose = (typeof COMMIT_PURPOSES)[number];
32
+
33
+ /**
34
+ * Guard callback supplied by the trusted host through server context.
35
+ * The provider invokes it after lock, pool-connection and object waits,
36
+ * inside the finalization boundary, before committing success. No public
37
+ * caller supplies SQL or a connection through this seam.
38
+ */
39
+ export type AuthorizeCommit = (
40
+ context: ArtifactCommitContext,
41
+ transaction: ArtifactCommitTransaction,
42
+ ) => Promise<ArtifactAuthorizationDecision>;
43
+
44
+ /**
45
+ * Frozen server-created invocation context. Neither body fields nor
46
+ * mutable app-global state may supply it; every publish/ensure invocation
47
+ * carries a fresh context binding the verified actor, the trusted storage
48
+ * scope and the request-bound guard.
49
+ */
50
+ export interface ArtifactInvocationContext {
51
+ readonly invocationId: string;
52
+ readonly actorId: string;
53
+ readonly storageScopeId: string;
54
+ readonly authorizeCommit: AuthorizeCommit;
55
+ }
56
+
57
+ /**
58
+ * Commit-time context passed to the guard after all waits. It adds the
59
+ * operation, artifact/binding identity, computed command identity, policy
60
+ * revision and purpose to the invocation. The fence below, minted by the
61
+ * transaction-bound policy participant, is the only usable allowed
62
+ * credential; it cannot be synthesized from the revision string.
63
+ */
64
+ export interface ArtifactCommitContext {
65
+ readonly invocationId: string;
66
+ readonly actorId: string;
67
+ readonly storageScopeId: string;
68
+ readonly operation: string;
69
+ readonly artifactId: string;
70
+ readonly bindingId: string;
71
+ readonly commandIdentity: CommandIdentity;
72
+ readonly policyRevision: string;
73
+ readonly purpose: CommitPurpose;
74
+ }
75
+
76
+ declare const commitFenceBrand: unique symbol;
77
+
78
+ /** Opaque fence minted by the transaction-bound policy participant. */
79
+ export interface ArtifactAuthorizationFence {
80
+ readonly [commitFenceBrand]: true;
81
+ readonly fenceId: string;
82
+ readonly invocationId: string;
83
+ readonly policyRevision: string;
84
+ readonly transactionId: string;
85
+ }
86
+
87
+ /** Denial reasons for a refused finalization. */
88
+ export type AuthorizationDenialReason =
89
+ | 'denied' | 'revoked' | 'expired' | 'invalid_object' | 'unavailable';
90
+
91
+ /**
92
+ * Typed authorization decision. Never an unqualified boolean: an allowed
93
+ * decision carries a usable guarded fence, a denial carries a typed
94
+ * reason plus the public projection (code, status, retry). Entry rejection
95
+ * stays distinguishable from a refusal raised inside finalization.
96
+ */
97
+ export type ArtifactAuthorizationDecision =
98
+ | { readonly allowed: true; readonly fence: ArtifactAuthorizationFence }
99
+ | {
100
+ readonly allowed: false;
101
+ readonly reason: AuthorizationDenialReason;
102
+ readonly publicCode: string;
103
+ readonly status: number;
104
+ readonly retriable: boolean;
105
+ };
106
+
107
+ declare const commitTransactionBrand: unique symbol;
108
+
109
+ /**
110
+ * Opaque transaction identity plus the host's registered
111
+ * transaction-bound policy/quota participant. It exposes no
112
+ * commit/rollback/release method to the guard. Policy registration and
113
+ * SQL table mapping are fixed at coordinator construction.
114
+ */
115
+ export interface ArtifactCommitTransaction {
116
+ readonly [commitTransactionBrand]: true;
117
+ readonly transactionId: string;
118
+ readonly invocationId: string;
119
+ readonly policyRevision: string;
120
+ readonly participant: ArtifactCommitParticipant;
121
+ }
122
+
123
+ /**
124
+ * Transaction-bound policy/quota participant registered at coordinator
125
+ * construction. The guard reaches policy and quota reads only through
126
+ * this participant on its leased transaction; public tool callers never
127
+ * receive a connection or SQL capability.
128
+ */
129
+ export interface ArtifactCommitParticipant {
130
+ readonly policyRevision: string;
131
+ readAdmission(input: AdmissionReadInput): Promise<AdmissionReadResult>;
132
+ reserveQuota(input: QuotaReserveInput): Promise<QuotaReserveResult>;
133
+ }
134
+
135
+ /** Policy read scoped to one guarded finalization attempt. */
136
+ export interface AdmissionReadInput {
137
+ readonly operation: string;
138
+ readonly artifactId: string;
139
+ readonly bindingId: string;
140
+ readonly actorId: string;
141
+ readonly policyRevision: string;
142
+ }
143
+
144
+ /** Policy read result; denial carries the typed refusal reason. */
145
+ export interface AdmissionReadResult {
146
+ readonly allowed: boolean;
147
+ readonly reason: AuthorizationDenialReason;
148
+ }
149
+
150
+ /** Quota reservation scoped to one guarded finalization attempt. */
151
+ export interface QuotaReserveInput {
152
+ readonly storageScopeId: string;
153
+ readonly operation: string;
154
+ readonly bytes: number;
155
+ }
156
+
157
+ /** Quota reservation result for one guarded finalization attempt. */
158
+ export interface QuotaReserveResult {
159
+ readonly reserved: boolean;
160
+ }
161
+
162
+ /**
163
+ * Coordinator role owning the atomic finalization unit. The optional
164
+ * test observer receives barrier arrivals below the app with invocation
165
+ * correlation; it can never mark an operation authorized or successful.
166
+ * `finalizeOperation` owns the complete unit (key lock, participant
167
+ * locks, guard, commit/rollback); `replayOperation` re-reads the winner
168
+ * under the key lock and returns it through a fresh guarded replay.
169
+ */
170
+ export interface ArtifactCommitCoordinator {
171
+ readonly protocolVersion: typeof COMMIT_PROTOCOL_VERSION;
172
+ readonly guardedOperations: readonly string[];
173
+ readonly testObserver?: CommitBarrierObserver;
174
+ finalizeOperation(
175
+ input: FinalizationInput,
176
+ ): Promise<ArtifactOutcome<FinalizationRecord>>;
177
+ replayOperation(
178
+ input: ReplayInput,
179
+ ): Promise<ArtifactOutcome<FinalizationRecord>>;
180
+ }
181
+
182
+ /** Coordinator completion record for one finalized or replayed operation. */
183
+ export interface FinalizationRecord {
184
+ readonly internalKey: InternalIdempotencyKey;
185
+ readonly commandHash: string;
186
+ readonly codecVersion: typeof COMMAND_CODEC_VERSION;
187
+ readonly replayed: boolean;
188
+ }
189
+
190
+ /** Finalization input: request, invocation context and effective command. */
191
+ export interface FinalizationInput {
192
+ readonly request: HttpRequest;
193
+ readonly invocation: ArtifactInvocationContext;
194
+ readonly command: EffectiveArtifactCommand;
195
+ }
196
+
197
+ /** Guarded replay input: invocation context and the durable internal key. */
198
+ export interface ReplayInput {
199
+ readonly invocation: ArtifactInvocationContext;
200
+ readonly internalKey: InternalIdempotencyKey;
201
+ }
202
+
203
+ /**
204
+ * Nine section 3.2 barrier points. Tests of actual contention hold the
205
+ * exact coordination/policy/quota/pointer row from a second raw client at
206
+ * these points; the coordinator rechecks authority after a resumed
207
+ * barrier. S3 preparation precedes the key-lock barrier.
208
+ */
209
+ export const COMMIT_BARRIER_POINTS = [
210
+ 'before_connection_acquire',
211
+ 'before_key_lock',
212
+ 'before_blob_put',
213
+ 'after_blob_put_before_verify',
214
+ 'before_blob_readback',
215
+ 'before_finalization_row_lock',
216
+ 'after_finalization_locks_before_guard',
217
+ 'after_replay_lookup_before_guard',
218
+ 'before_commit',
219
+ ] as const;
220
+
221
+ export type CommitBarrierPoint = (typeof COMMIT_BARRIER_POINTS)[number];
222
+
223
+ /**
224
+ * Test-only barrier seam. Bound once as the coordinator `testObserver`
225
+ * option; `waitUntilReached` resolves after arrival and `release`
226
+ * resumes only that invocation/point.
227
+ */
228
+ export interface CommitBarrierObserver {
229
+ arrive(point: CommitBarrierPoint, invocationId: string): void;
230
+ }
231
+
232
+ export interface ArtifactCommitBarrier {
233
+ readonly observer: CommitBarrierObserver;
234
+ waitUntilReached(point: CommitBarrierPoint, invocationId: string): Promise<void>;
235
+ release(point: CommitBarrierPoint, invocationId: string): void;
236
+ }
237
+
238
+ export type CreateArtifactCommitBarrier = () => ArtifactCommitBarrier;
239
+
240
+ /**
241
+ * A4 participant lock order inside the finalization transaction, after
242
+ * the key lock: binding identity, artifact/lifecycle row, ACL row,
243
+ * current-pointer row, host authority/revision rows, quota rows, then
244
+ * blob-reference/deletion-claim rows. Multiple identities within one
245
+ * class lock in stable bytewise order. Artifact/lifecycle, ACL, pointer
246
+ * and quota rows use FOR UPDATE; authority rows use FOR SHARE. Binding
247
+ * uniqueness uses its own coordination row even when caller keys differ.
248
+ */
249
+ export const PARTICIPANT_LOCK_ORDER = [
250
+ 'binding-identity',
251
+ 'artifact-lifecycle',
252
+ 'acl',
253
+ 'current-pointer',
254
+ 'authority-revision',
255
+ 'quota',
256
+ 'blob-reference-deletion-claim',
257
+ ] as const;
258
+
259
+ export type ParticipantLockClass = (typeof PARTICIPANT_LOCK_ORDER)[number];
260
+
261
+ /**
262
+ * Frozen finalization order (ask 2, protocol A4). The host guard runs
263
+ * after lock, connection and object waits, inside the finalization
264
+ * boundary, before any committed success.
265
+ */
266
+ export const FINALIZATION_PHASES = [
267
+ 'admit', 'prepare', 'lock', 'provision', 'guard', 'commit',
268
+ ] as const;
269
+
270
+ export type FinalizationPhase = (typeof FINALIZATION_PHASES)[number];
271
+
272
+ /**
273
+ * Residue a refused finalization must never leave committed: no new
274
+ * ready release, pointer advance, attached provenance, ACL/binding or
275
+ * lifecycle change, consumed quota or replayable success.
276
+ */
277
+ export const REFUSAL_RESIDUE_FORBIDDEN = [
278
+ 'ready-release', 'pointer-advance', 'attached-provenance',
279
+ 'acl-binding-change', 'lifecycle-change', 'consumed-quota', 'replayable-success',
280
+ ] as const;
281
+
282
+ export type RefusalResidue = (typeof REFUSAL_RESIDUE_FORBIDDEN)[number];
283
+
284
+ /** Profiles requiring guarded writes must declare a coordinator at startup. */
285
+ export interface CoordinatorRequirement {
286
+ readonly requiresCoordinator: true;
287
+ readonly guardedOperations: readonly string[];
288
+ }
289
+
290
+ /** Transport-free HTTP request shape consumed by the frozen app. */
291
+ export interface HttpRequest {
292
+ readonly method: string;
293
+ readonly path: string;
294
+ readonly headers: Record<string, string>;
295
+ readonly body: unknown;
296
+ }
297
+
298
+ /** Transport-free HTTP response shape produced by the frozen app. */
299
+ export interface HttpResponse {
300
+ readonly status: number;
301
+ readonly headers: Record<string, string>;
302
+ readonly body: unknown;
303
+ }
304
+
305
+ /** Required app dependencies: no automatic legacy fallback exists. */
306
+ export const HTTP_APP_REQUIRED_DEPS = ['profile', 'coordinator'] as const;
307
+
308
+ /**
309
+ * App dependencies. Omitted profile, coordinator or write guard fails
310
+ * closed; the old split finalizer is removed, never selected implicitly.
311
+ */
312
+ export interface HttpAppDeps {
313
+ readonly profile: CompiledArtifactsProfile;
314
+ readonly coordinator: ArtifactCommitCoordinator;
315
+ }
316
+
317
+ /**
318
+ * Frozen app surface. `execute` is the sole execution path returning the
319
+ * shared outcome with invocation, commit-state and replay correlation;
320
+ * `handle` calls it once, then sanitizes with the outcome projector.
321
+ * Hosts needing accounting call `execute` and the projector themselves,
322
+ * never both methods. Reads/entry rejection use `not_committed`; write
323
+ * success uses `committed`.
324
+ */
325
+ export interface ArtifactApp {
326
+ readonly selectedCapabilities: readonly string[];
327
+ execute(
328
+ request: HttpRequest,
329
+ invocation: ArtifactInvocationContext,
330
+ ): Promise<ArtifactOutcome<HttpResponse>>;
331
+ handle(
332
+ request: HttpRequest,
333
+ invocation: ArtifactInvocationContext,
334
+ ): Promise<HttpResponse>;
335
+ }
336
+
337
+ /** Sanitized projection from outcome to HTTP response. */
338
+ export type ProjectArtifactHttpOutcome = (
339
+ outcome: ArtifactOutcome<HttpResponse>,
340
+ ) => HttpResponse;
341
+
342
+ /**
343
+ * Compile-time freeze assertions (P1-1). Checked by `npm run freeze-types`;
344
+ * type-only and erased at runtime. Each `@ts-expect-error` below must stay
345
+ * consumed: if a forged literal ever compiles, the gate fails.
346
+ */
347
+
348
+ // Forged fences/transactions never satisfy the opaque contracts.
349
+ const _forgedFence = {
350
+ fenceId: 'fence-1',
351
+ invocationId: 'inv-1',
352
+ policyRevision: 'rev-1',
353
+ transactionId: 'tx-1',
354
+ };
355
+ // @ts-expect-error — fences are minted by the transaction-bound participant
356
+ const _fenceForgery: ArtifactAuthorizationFence = _forgedFence;
357
+
358
+ const _forgedTransaction = {
359
+ transactionId: 'tx-1',
360
+ invocationId: 'inv-1',
361
+ policyRevision: 'rev-1',
362
+ };
363
+ // @ts-expect-error — transactions are opened by the coordinator
364
+ const _transactionForgery: ArtifactCommitTransaction = _forgedTransaction;
365
+
366
+ // The guard reaches policy/quota only through the registered participant.
367
+ type _ParticipantAccess = ArtifactCommitTransaction['participant'] extends ArtifactCommitParticipant
368
+ ? true
369
+ : never;
370
+ const _participantAccess: _ParticipantAccess = true;
371
+
372
+ // The coordinator exposes callable finalization and replay operations.
373
+ type _FinalizeCallable = ArtifactCommitCoordinator['finalizeOperation'] extends (
374
+ input: FinalizationInput,
375
+ ) => Promise<ArtifactOutcome<FinalizationRecord>>
376
+ ? true
377
+ : never;
378
+ const _finalizeCallable: _FinalizeCallable = true;
379
+
380
+ type _ReplayCallable = ArtifactCommitCoordinator['replayOperation'] extends (
381
+ input: ReplayInput,
382
+ ) => Promise<ArtifactOutcome<FinalizationRecord>>
383
+ ? true
384
+ : never;
385
+ const _replayCallable: _ReplayCallable = true;
386
+
387
+ // Compiled profiles carry semantic validators for the lanes to consume.
388
+ type _ValidatorsWired = CompiledArtifactsProfile['validators'] extends CompiledValidators
389
+ ? true
390
+ : never;
391
+ const _validatorsWired: _ValidatorsWired = true;
392
+
393
+ // Ordinary categories do not belong to the operational failure branch.
394
+ const _ordinaryFields = {
395
+ kind: 'operational',
396
+ category: 'invalid_request',
397
+ httpStatus: 400,
398
+ publicCode: 'invalid_request',
399
+ retriable: false,
400
+ invocationId: 'inv-1',
401
+ operation: 'publish',
402
+ phase: 'entry',
403
+ commitState: 'not_committed',
404
+ cause: undefined,
405
+ } as const;
406
+ // @ts-expect-error — 'invalid_request' is an ordinary category, not operational
407
+ const _ordinaryAsOperational: ArtifactFailure = _ordinaryFields;
408
+
409
+ const _ordinaryPositive: OrdinaryFailure = {
410
+ kind: 'ordinary',
411
+ category: 'invalid_request',
412
+ httpStatus: 400,
413
+ publicCode: 'invalid_request',
414
+ retriable: false,
415
+ invocationId: 'inv-1',
416
+ operation: 'publish',
417
+ phase: 'entry',
418
+ commitState: 'not_committed',
419
+ cause: undefined,
420
+ };
421
+ const _ordinaryInOutcome: ArtifactOutcome<string> = { ok: false, failure: _ordinaryPositive };
@@ -0,0 +1,174 @@
1
+ /**
2
+ * L0 frozen failure declaration (immo #861, ask 3 shape, spec r2 E2/E3).
3
+ *
4
+ * Stable internal outcome shape and category-to-public projection table.
5
+ * `capability_unselected` is a distinct pre-dispatch non-retriable 503
6
+ * with no write-ledger entry; the guard authority outage shares the
7
+ * commit category and ledger reason with policy denial but projects a
8
+ * retriable same-key 503. Driver detail stays in the private cause and
9
+ * never enters HTTP bodies, MCP results or metadata. The classifier and
10
+ * success replay flag land in L2.
11
+ */
12
+
13
+ import type { CommitState } from './commit-contract.js';
14
+
15
+ /**
16
+ * Internal failure categories, distinct from ordinary typed
17
+ * validation/not-found/gone outcomes whose existing public mappings
18
+ * remain supported.
19
+ */
20
+ export const FAILURE_CATEGORIES = [
21
+ 'capability_unselected', 'provider_unavailable', 'conflict',
22
+ 'unauthorized_at_commit', 'provider_error',
23
+ ] as const;
24
+
25
+ export type FailureCategory = (typeof FAILURE_CATEGORIES)[number];
26
+
27
+ /**
28
+ * Frozen category-to-public projection (ask 3, table E2). `variant`
29
+ * selects the projection row inside one category. An absent
30
+ * `radarLedgerReason` means the rejection happens before dispatch and
31
+ * creates no write-commit ledger entry. `phase`/`commitState` are the
32
+ * projection defaults; the per-incident phase travels on ArtifactFailure.
33
+ */
34
+ export interface FailureProjection {
35
+ readonly category: FailureCategory;
36
+ readonly variant?: string;
37
+ readonly httpStatus: number;
38
+ readonly publicCode: string;
39
+ readonly radarLedgerReason?: string;
40
+ readonly retriable: boolean;
41
+ readonly phase: string;
42
+ readonly commitState: CommitState;
43
+ }
44
+
45
+ export const ORDINARY_FAILURE_CATEGORIES = [
46
+ 'invalid_request', 'not_found', 'gone', 'unprocessable', 'verification_failed',
47
+ ] as const;
48
+
49
+ export type OrdinaryFailureCategory = (typeof ORDINARY_FAILURE_CATEGORIES)[number];
50
+
51
+ /**
52
+ * Frozen ordinary domain-failure projection. Ordinary validation,
53
+ * not-found, gone and verification outcomes are entry rejections: they
54
+ * never enter the write-commit ledger and keep the existing public
55
+ * mappings.
56
+ */
57
+ export interface OrdinaryFailureProjection {
58
+ readonly category: OrdinaryFailureCategory;
59
+ readonly httpStatus: number;
60
+ readonly publicCode: string;
61
+ readonly retriable: false;
62
+ readonly phase: string;
63
+ readonly commitState: CommitState;
64
+ }
65
+
66
+ export const ORDINARY_FAILURE_PROJECTIONS: readonly OrdinaryFailureProjection[] = [
67
+ {
68
+ category: 'invalid_request', httpStatus: 400, publicCode: 'invalid_request',
69
+ retriable: false, phase: 'entry', commitState: 'not_committed',
70
+ },
71
+ {
72
+ category: 'not_found', httpStatus: 404, publicCode: 'not_found',
73
+ retriable: false, phase: 'entry', commitState: 'not_committed',
74
+ },
75
+ {
76
+ category: 'gone', httpStatus: 410, publicCode: 'gone',
77
+ retriable: false, phase: 'entry', commitState: 'not_committed',
78
+ },
79
+ {
80
+ category: 'unprocessable', httpStatus: 422, publicCode: 'unprocessable',
81
+ retriable: false, phase: 'entry', commitState: 'not_committed',
82
+ },
83
+ {
84
+ category: 'verification_failed', httpStatus: 422, publicCode: 'verification_failed',
85
+ retriable: false, phase: 'entry', commitState: 'not_committed',
86
+ },
87
+ ] as const;
88
+
89
+ export const FAILURE_PROJECTIONS: readonly FailureProjection[] = [
90
+ {
91
+ category: 'capability_unselected', variant: 'unselected_operation',
92
+ httpStatus: 503, publicCode: 'capability_unavailable',
93
+ retriable: false, phase: 'selection', commitState: 'not_committed',
94
+ },
95
+ {
96
+ category: 'provider_unavailable', variant: 'dispatch_failure',
97
+ httpStatus: 503, publicCode: 'capability_unavailable',
98
+ radarLedgerReason: 'provider_unavailable',
99
+ retriable: true, phase: 'dispatch', commitState: 'not_committed',
100
+ },
101
+ {
102
+ category: 'conflict', variant: 'key_contention',
103
+ httpStatus: 409, publicCode: 'conflict',
104
+ radarLedgerReason: 'provider_conflict',
105
+ retriable: false, phase: 'lock', commitState: 'not_committed',
106
+ },
107
+ {
108
+ category: 'unauthorized_at_commit', variant: 'policy_denial',
109
+ httpStatus: 403, publicCode: 'access_denied',
110
+ radarLedgerReason: 'unauthorized_at_commit',
111
+ retriable: false, phase: 'guard', commitState: 'not_committed',
112
+ },
113
+ {
114
+ category: 'unauthorized_at_commit', variant: 'authority_unavailable',
115
+ httpStatus: 503, publicCode: 'capability_unavailable',
116
+ radarLedgerReason: 'unauthorized_at_commit',
117
+ retriable: true, phase: 'guard', commitState: 'not_committed',
118
+ },
119
+ {
120
+ category: 'provider_error', variant: 'unclassified',
121
+ httpStatus: 500, publicCode: 'internal_error',
122
+ radarLedgerReason: 'provider_error',
123
+ retriable: false, phase: 'operation', commitState: 'not_committed',
124
+ },
125
+ ] as const;
126
+
127
+ /** Classified internal failure with private cause and commit state. */
128
+ export interface ArtifactFailure {
129
+ readonly kind: 'operational';
130
+ readonly category: FailureCategory;
131
+ readonly variant?: string;
132
+ readonly httpStatus: number;
133
+ readonly publicCode: string;
134
+ readonly retriable: boolean;
135
+ readonly invocationId: string;
136
+ readonly operation: string;
137
+ readonly phase: string;
138
+ readonly commitState: CommitState;
139
+ readonly cause: unknown;
140
+ }
141
+
142
+ /** Ordinary typed domain failure with retained private cause. */
143
+ export interface OrdinaryFailure {
144
+ readonly kind: 'ordinary';
145
+ readonly category: OrdinaryFailureCategory;
146
+ readonly httpStatus: number;
147
+ readonly publicCode: string;
148
+ readonly retriable: false;
149
+ readonly invocationId: string;
150
+ readonly operation: string;
151
+ readonly phase: string;
152
+ readonly commitState: CommitState;
153
+ readonly cause: unknown;
154
+ }
155
+
156
+ /**
157
+ * Required success correlation fields: invocation identity, commit state
158
+ * and replay flag travel on every success alongside the result. Both HTTP
159
+ * and MCP consume this same result type.
160
+ */
161
+ export const OUTCOME_SUCCESS_FIELDS = [
162
+ 'ok', 'result', 'invocationId', 'commitState', 'replayed',
163
+ ] as const;
164
+
165
+ /** Internal discriminated outcome shared by HTTP and MCP projections. */
166
+ export type ArtifactOutcome<T> =
167
+ | {
168
+ readonly ok: true;
169
+ readonly result: T;
170
+ readonly invocationId: string;
171
+ readonly commitState: CommitState;
172
+ readonly replayed: boolean;
173
+ }
174
+ | { readonly ok: false; readonly failure: ArtifactFailure | OrdinaryFailure };