@sentropic/artifacts 0.2.0 → 0.2.1
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 +30 -0
- package/dist/http.d.ts +3 -1
- package/dist/http.js +3 -13
- package/dist/mcp-core.d.ts +5 -2
- package/dist/mcp-core.js +5 -5
- package/dist/server/commit/coordinator.js +9 -9
- package/dist/server/commit/types.d.ts +3 -0
- package/dist/server/publication.d.ts +3 -1
- package/dist/server/publication.js +8 -15
- package/docs/migration-l6.md +6 -1
- package/examples/radar-commit-setup.ts +3 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# @sentropic/artifacts — generic artifact contracts (L-5a)
|
|
2
2
|
|
|
3
|
+
Version 0.2.1 fixes host commit authorization being masked by the built-in
|
|
4
|
+
publication guard and wires the optional trusted `mapHostError` dependency.
|
|
5
|
+
Publication retains its lifecycle, ACL and policy revision checks; the host
|
|
6
|
+
`authorizeCommit` binding decides account approval and its sanitized refusal code.
|
|
7
|
+
Custom services using `createGuardedPublicationService` may pass the request-bound
|
|
8
|
+
`authorizeCommit` in `publishFinalized(input)`. It runs after
|
|
9
|
+
lifecycle, ACL and policy revision checks. When omitted, publication keeps the
|
|
10
|
+
default `participant.checkApproval` check and denies unapproved or expired commits.
|
|
11
|
+
|
|
12
|
+
Bind the same mapper on `createHttpArtifactApp`, `createArtifactsToolExecutor`
|
|
13
|
+
(or `createArtifactsAdapter`) and `createArtifactCommitCoordinator` so host wrapper
|
|
14
|
+
errors are classified at their originating layer. For example:
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import { ArtifactOperationError, type ArtifactHostFailureMapper }
|
|
18
|
+
from '@sentropic/artifacts/server/outcome-contract';
|
|
19
|
+
|
|
20
|
+
const mapHostError: ArtifactHostFailureMapper = cause => {
|
|
21
|
+
const code = (cause as { code?: string } | null)?.code;
|
|
22
|
+
return code === 'artifact_s3_timeout' || code === 'artifact_connection_lost'
|
|
23
|
+
? new ArtifactOperationError('provider_unavailable', { cause }) : undefined;
|
|
24
|
+
};
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Pass `{ ...existingOptions, mapHostError }` to each construction. The packed
|
|
28
|
+
`examples/radar-commit-setup.ts` also accepts this option. Unrecognized failures
|
|
29
|
+
keep their existing classification; private causes never enter HTTP/MCP results.
|
|
30
|
+
Frozen contracts, command identity and selected tools remain unchanged.
|
|
31
|
+
|
|
32
|
+
|
|
3
33
|
Generic, host-independent artifact domain state for the Sentropic artifacts
|
|
4
34
|
provider (`sentropic.artifacts`, spec-v8 §9), covering L-5a–5e. Version
|
|
5
35
|
`0.1.2` adds UTF-8 text publication, exposes storage adapters and ships SQL migrations.
|
package/dist/http.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { type GuardedPublicationDeps, type PublicationService } from './server/p
|
|
|
6
6
|
import type { AclStore } from './server/access.js';
|
|
7
7
|
import type { LifecycleService } from './server/lifecycle.js';
|
|
8
8
|
import { type ArtifactInvocationContext, type ArtifactCommitCoordinator } from './server/commit/types.js';
|
|
9
|
-
import { type ArtifactOutcome } from './server/failures.js';
|
|
9
|
+
import { type ArtifactOutcome, type ArtifactHostFailureMapper } from './server/failures.js';
|
|
10
10
|
export { projectArtifactHttpOutcome } from './server/outcome-projection.js';
|
|
11
11
|
export interface HttpRequest {
|
|
12
12
|
method: string;
|
|
@@ -27,6 +27,8 @@ export declare function errorBody(status: number): {
|
|
|
27
27
|
error: string;
|
|
28
28
|
};
|
|
29
29
|
export interface HttpAppDeps {
|
|
30
|
+
/** Trusted mapping of host wrapper errors; causes remain private. Also bind on the coordinator. */
|
|
31
|
+
mapHostError?: ArtifactHostFailureMapper;
|
|
30
32
|
profile: CompiledArtifactsProfile;
|
|
31
33
|
coordinator: ArtifactCommitCoordinator;
|
|
32
34
|
derivation: ArtifactKeyDerivationBinding;
|
package/dist/http.js
CHANGED
|
@@ -110,25 +110,15 @@ export function createHttpArtifactApp(deps) {
|
|
|
110
110
|
const acl = await visible(artifactId, invocation);
|
|
111
111
|
if (acl.ownerId !== invocation.actorId)
|
|
112
112
|
throw new ArtifactOperationError('access_denied');
|
|
113
|
-
// Both finalization and fresh replay combine provider checks with this request's host guard.
|
|
114
|
-
const bindGuard = (request) => ({ ...request,
|
|
115
|
-
guard: async (context, transaction) => {
|
|
116
|
-
const decision = await request.guard(context, transaction);
|
|
117
|
-
return decision.allowed ? invocation.authorizeCommit(context, transaction) : decision;
|
|
118
|
-
} });
|
|
119
|
-
const coordinator = {
|
|
120
|
-
...deps.coordinator,
|
|
121
|
-
finalizePrepared: (request) => deps.coordinator.finalizePrepared(bindGuard(request)),
|
|
122
|
-
replayPrepared: (request) => deps.coordinator.replayPrepared(bindGuard(request)),
|
|
123
|
-
};
|
|
124
113
|
const limits = profile.operations.find((entry) => entry.operation === 'publish').limits;
|
|
125
|
-
const publication = createGuardedPublicationService({ ...deps.publication, coordinator,
|
|
114
|
+
const publication = createGuardedPublicationService({ ...deps.publication, coordinator: deps.coordinator,
|
|
126
115
|
limits: { maxFiles: limits.publishFilesMax, maxFileBytes: limits.maxDecodedFileBytes,
|
|
127
116
|
maxTotalBytes: limits.maxDecodedTotalBytes } });
|
|
128
117
|
const outcome = await publication.publishFinalized({
|
|
129
118
|
ownerId: acl.ownerId, storageScopeId: invocation.storageScopeId, artifactId,
|
|
130
119
|
identity: command.identity, actorId: invocation.actorId, invocationId: invocation.invocationId,
|
|
131
120
|
attemptId: invocation.invocationId, policyRevision: await deps.ensureHost.readPolicyRevision(),
|
|
121
|
+
authorizeCommit: invocation.authorizeCommit,
|
|
132
122
|
source: { kind: 'inline-original' }, profile: normalized.profile,
|
|
133
123
|
files: normalized.files.map((file) => ({
|
|
134
124
|
path: file.path, mimeType: file.mimeType, bytes: Uint8Array.from(file.bytes),
|
|
@@ -178,7 +168,7 @@ export function createHttpArtifactApp(deps) {
|
|
|
178
168
|
catch (cause) {
|
|
179
169
|
const failure = classifyArtifactFailure(cause, { invocationId: invocation?.invocationId ?? '',
|
|
180
170
|
operation, phase, commitState }, (error) => error instanceof ArtifactProfileInputError
|
|
181
|
-
? new ArtifactOperationError(error.category) :
|
|
171
|
+
? new ArtifactOperationError(error.category) : deps.mapHostError?.(error));
|
|
182
172
|
if (cause instanceof CommitQuarantineError) {
|
|
183
173
|
const migration = { ...failure, kind: 'operational', category: 'provider_unavailable',
|
|
184
174
|
variant: 'legacy_quarantine', httpStatus: 503, publicCode: 'capability_unavailable', retriable: false };
|
package/dist/mcp-core.d.ts
CHANGED
|
@@ -4,13 +4,13 @@ import type { ProviderParameterSchema } from '@sentropic/mcp-platform/registry';
|
|
|
4
4
|
import type { ArtifactApp, HttpResponse } from './http.js';
|
|
5
5
|
import { type CompiledArtifactsProfile } from './profile/index.js';
|
|
6
6
|
import type { ArtifactInvocationContext } from './server/commit/types.js';
|
|
7
|
-
import { type ArtifactOutcome } from './server/failures.js';
|
|
7
|
+
import { type ArtifactOutcome, type ArtifactHostFailureMapper } from './server/failures.js';
|
|
8
8
|
export * from './profile/index.js';
|
|
9
9
|
export { createRadarKeyDerivation } from './server/command-identity.js';
|
|
10
10
|
export type { ArtifactKeyDerivationBinding } from './server/command-identity.js';
|
|
11
11
|
export declare const ARTIFACTS_PROVIDER_ID = "sentropic.artifacts";
|
|
12
12
|
export declare const ARTIFACTS_APP_ID = "sentropic-artifacts";
|
|
13
|
-
export declare const ARTIFACTS_PROVIDER_VERSION = "0.2.
|
|
13
|
+
export declare const ARTIFACTS_PROVIDER_VERSION = "0.2.1";
|
|
14
14
|
export declare const ARTIFACTS_TOOL_NAMES: readonly ["artifacts_get_template", "artifacts_get_api_guide", "artifacts_ensure", "artifacts_publish", "artifacts_create_upload", "artifacts_commit_upload", "artifacts_list", "artifacts_get", "artifacts_get_source", "artifacts_fork", "artifacts_set_access", "artifacts_promote", "artifacts_renew", "artifacts_delete", "artifacts_restore"];
|
|
15
15
|
export type ArtifactsToolName = (typeof ARTIFACTS_TOOL_NAMES)[number];
|
|
16
16
|
export declare const ARTIFACTS_TOOL_SPECS: {
|
|
@@ -40,11 +40,14 @@ export interface ArtifactsInvokeInput {
|
|
|
40
40
|
export declare function createArtifactsToolExecutor(deps: {
|
|
41
41
|
app: ArtifactsApp;
|
|
42
42
|
profile: CompiledArtifactsProfile;
|
|
43
|
+
mapHostError?: ArtifactHostFailureMapper;
|
|
43
44
|
}): {
|
|
44
45
|
execute: (input: ArtifactsInvokeInput) => Promise<ArtifactOutcome<HttpResponse>>;
|
|
45
46
|
invoke(input: ArtifactsInvokeInput): Promise<HttpResponse>;
|
|
46
47
|
};
|
|
47
48
|
export interface ArtifactsAdapterOptions extends ArtifactsManifestOptions {
|
|
49
|
+
/** Trusted mapping for failures in the MCP host binding. */
|
|
50
|
+
mapHostError?: ArtifactHostFailureMapper;
|
|
48
51
|
app: ArtifactsApp;
|
|
49
52
|
/** Bind verified principal/scope and request authorization in trusted host code. */
|
|
50
53
|
createInvocation(request: AppToolInvocation): ArtifactInvocationContext | Promise<ArtifactInvocationContext>;
|
package/dist/mcp-core.js
CHANGED
|
@@ -6,7 +6,7 @@ export * from './profile/index.js';
|
|
|
6
6
|
export { createRadarKeyDerivation } from './server/command-identity.js';
|
|
7
7
|
export const ARTIFACTS_PROVIDER_ID = 'sentropic.artifacts';
|
|
8
8
|
export const ARTIFACTS_APP_ID = 'sentropic-artifacts';
|
|
9
|
-
export const ARTIFACTS_PROVIDER_VERSION = '0.2.
|
|
9
|
+
export const ARTIFACTS_PROVIDER_VERSION = '0.2.1';
|
|
10
10
|
export const ARTIFACTS_TOOL_NAMES = ARTIFACT_CAPABILITY_NAMES;
|
|
11
11
|
export const ARTIFACTS_TOOL_SPECS = ARTIFACTS_TOOL_NAMES.map((name) => ({ name,
|
|
12
12
|
description: `Artifacts ${name.slice('artifacts_'.length).replaceAll('_', ' ')}.`,
|
|
@@ -36,9 +36,9 @@ export function createArtifactsManifest(options) {
|
|
|
36
36
|
audit: { eventKinds: ['invoke'], artifactsProfileDigest: profile.profileDigest }, durability: {},
|
|
37
37
|
};
|
|
38
38
|
}
|
|
39
|
-
function rejected(cause, invocationId, operation) {
|
|
39
|
+
function rejected(cause, invocationId, operation, mapHostError) {
|
|
40
40
|
return { ok: false, failure: classifyArtifactFailure(cause, { invocationId, operation,
|
|
41
|
-
phase: cause instanceof ArtifactProfileInputError ? cause.phase : 'entry', commitState: 'not_committed' }, (error) => error instanceof ArtifactProfileInputError ? new ArtifactOperationError(error.category) :
|
|
41
|
+
phase: cause instanceof ArtifactProfileInputError ? cause.phase : 'entry', commitState: 'not_committed' }, (error) => error instanceof ArtifactProfileInputError ? new ArtifactOperationError(error.category) : mapHostError?.(error)) };
|
|
42
42
|
}
|
|
43
43
|
export function createArtifactsToolExecutor(deps) {
|
|
44
44
|
assertArtifactsTransportProfile(deps.profile);
|
|
@@ -70,7 +70,7 @@ export function createArtifactsToolExecutor(deps) {
|
|
|
70
70
|
storageScopeId: input.invocation.storageScopeId, idempotencyKey: input.idempotencyKey }, input.invocation);
|
|
71
71
|
}
|
|
72
72
|
catch (cause) {
|
|
73
|
-
return rejected(cause, input.invocation?.invocationId ?? '', input.tool);
|
|
73
|
+
return rejected(cause, input.invocation?.invocationId ?? '', input.tool, deps.mapHostError);
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
return { execute, async invoke(input) {
|
|
@@ -100,7 +100,7 @@ export function createArtifactsAdapter(options) {
|
|
|
100
100
|
invocation, idempotencyKey: request.idempotencyKey });
|
|
101
101
|
}
|
|
102
102
|
catch (cause) {
|
|
103
|
-
outcome = rejected(cause, request.ctx.requestId, request.capabilityRef);
|
|
103
|
+
outcome = rejected(cause, request.ctx.requestId, request.capabilityRef, options.mapHostError);
|
|
104
104
|
}
|
|
105
105
|
return projectArtifactMcpOutcome(outcome, request.ctx.auditId);
|
|
106
106
|
},
|
|
@@ -59,10 +59,10 @@ const isConnectionLoss = (error) => {
|
|
|
59
59
|
const LOCK_TIMEOUT_CODE = '55P03';
|
|
60
60
|
/** Serialization/deadlock/cancel outcomes: retriable unavailability (r2 E3/P2-2). */
|
|
61
61
|
const RETRIABLE_SQLSTATES = new Set(['40001', '40P01', '57014']);
|
|
62
|
-
const classifyOperational = (request, phase, commitState, error) => {
|
|
62
|
+
const classifyOperational = (request, phase, commitState, error, mapHostError) => {
|
|
63
63
|
const classified = classifyArtifactFailure(error, {
|
|
64
64
|
invocationId: request.invocationId, operation: request.operation, phase, commitState,
|
|
65
|
-
});
|
|
65
|
+
}, mapHostError);
|
|
66
66
|
if (classified.kind === 'operational')
|
|
67
67
|
return classified;
|
|
68
68
|
return failure('provider_error', request, phase, commitState, error, { variant: 'unclassified' });
|
|
@@ -227,7 +227,7 @@ export function createArtifactCommitCoordinator(options) {
|
|
|
227
227
|
const code = typeof error === 'object' && error !== null
|
|
228
228
|
? error.code : undefined;
|
|
229
229
|
if (settleError === undefined && typeof code === 'string' && RETRIABLE_SQLSTATES.has(code)) {
|
|
230
|
-
return { ok: false, failure: classifyOperational(request, 'commit', 'not_committed', error) };
|
|
230
|
+
return { ok: false, failure: classifyOperational(request, 'commit', 'not_committed', error, options.mapHostError) };
|
|
231
231
|
}
|
|
232
232
|
return unknownOutcome(request, 'commit', settleError === undefined ? error : { commit: error, settlement: settleError });
|
|
233
233
|
};
|
|
@@ -374,7 +374,7 @@ export function createArtifactCommitCoordinator(options) {
|
|
|
374
374
|
await abandon(request);
|
|
375
375
|
return {
|
|
376
376
|
ok: false,
|
|
377
|
-
failure: classifyOperational(request, phase, 'not_committed', error),
|
|
377
|
+
failure: classifyOperational(request, phase, 'not_committed', error, options.mapHostError),
|
|
378
378
|
};
|
|
379
379
|
}
|
|
380
380
|
const settle = trackSettlement(connection);
|
|
@@ -423,7 +423,7 @@ export function createArtifactCommitCoordinator(options) {
|
|
|
423
423
|
}
|
|
424
424
|
return {
|
|
425
425
|
ok: false,
|
|
426
|
-
failure: classifyOperational(request, phase, 'not_committed', error),
|
|
426
|
+
failure: classifyOperational(request, phase, 'not_committed', error, options.mapHostError),
|
|
427
427
|
};
|
|
428
428
|
}
|
|
429
429
|
};
|
|
@@ -484,7 +484,7 @@ export function createArtifactCommitCoordinator(options) {
|
|
|
484
484
|
ok: false,
|
|
485
485
|
failure: isConnectionLoss(error)
|
|
486
486
|
? failure('provider_unavailable', request, phase, 'not_committed', error)
|
|
487
|
-
: classifyOperational(request, phase, 'not_committed', error),
|
|
487
|
+
: classifyOperational(request, phase, 'not_committed', error, options.mapHostError),
|
|
488
488
|
};
|
|
489
489
|
}
|
|
490
490
|
const active = connection;
|
|
@@ -613,7 +613,7 @@ export function createArtifactCommitCoordinator(options) {
|
|
|
613
613
|
}
|
|
614
614
|
return {
|
|
615
615
|
ok: false,
|
|
616
|
-
failure: classifyOperational(request, phase, 'not_committed', error),
|
|
616
|
+
failure: classifyOperational(request, phase, 'not_committed', error, options.mapHostError),
|
|
617
617
|
};
|
|
618
618
|
}
|
|
619
619
|
},
|
|
@@ -630,7 +630,7 @@ export function createArtifactCommitCoordinator(options) {
|
|
|
630
630
|
await abandon(request);
|
|
631
631
|
return {
|
|
632
632
|
ok: false,
|
|
633
|
-
failure: classifyOperational(request, phase, 'not_committed', error),
|
|
633
|
+
failure: classifyOperational(request, phase, 'not_committed', error, options.mapHostError),
|
|
634
634
|
};
|
|
635
635
|
}
|
|
636
636
|
const active = connection;
|
|
@@ -720,7 +720,7 @@ export function createArtifactCommitCoordinator(options) {
|
|
|
720
720
|
}
|
|
721
721
|
return {
|
|
722
722
|
ok: false,
|
|
723
|
-
failure: classifyOperational(request, phase, 'not_committed', error),
|
|
723
|
+
failure: classifyOperational(request, phase, 'not_committed', error, options.mapHostError),
|
|
724
724
|
};
|
|
725
725
|
}
|
|
726
726
|
},
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import type { CommitArrivalObserver, CommitBarrierObserver, CommitBarrierPoint } from '../../testing.js';
|
|
15
15
|
import type { commitFenceBrand, commitTransactionBrand } from './brands.js';
|
|
16
|
+
import type { ArtifactHostFailureMapper } from '../failures.js';
|
|
16
17
|
/** Version of the frozen command-codec shape (r2 C6). */
|
|
17
18
|
export declare const COMMAND_CODEC_VERSION: "artifacts-command/v1";
|
|
18
19
|
/** Server-derived internal key: distinct from any caller key. */
|
|
@@ -302,6 +303,8 @@ export type ArtifactOutcome<T> = {
|
|
|
302
303
|
export type FinalizeResult = ArtifactOutcome<StoredCommitReceipt>;
|
|
303
304
|
/** Coordinator construction options. */
|
|
304
305
|
export interface CommitCoordinatorOptions {
|
|
306
|
+
/** Trusted host wrapper classification for failures inside the atomic unit. */
|
|
307
|
+
readonly mapHostError?: ArtifactHostFailureMapper;
|
|
305
308
|
readonly executor: CommitExecutor;
|
|
306
309
|
readonly policy: CommitPolicyParticipantFactory;
|
|
307
310
|
/** Trusted domain adapters; every call resolves fresh state, including replay. */
|
|
@@ -7,7 +7,7 @@ import type { ArtifactAcl } from './access.js';
|
|
|
7
7
|
import type { ArtifactState } from './lifecycle.js';
|
|
8
8
|
import { type RefcountStore } from './gc.js';
|
|
9
9
|
import type { CommandIdentity } from './command-identity.js';
|
|
10
|
-
import type { CommitLockFn, ArtifactCommitCoordinator, FinalizeResult, FinalizeTxClient } from './commit/types.js';
|
|
10
|
+
import type { AuthorizeCommit, CommitLockFn, ArtifactCommitCoordinator, FinalizeResult, FinalizeTxClient } from './commit/types.js';
|
|
11
11
|
export { DEFAULT_PUBLICATION_LIMITS, type PublicationLimits } from './publication-limits.js';
|
|
12
12
|
export interface ReleaseRecord {
|
|
13
13
|
releaseId: string;
|
|
@@ -127,6 +127,8 @@ export interface GuardedPublishInput extends PublishInput {
|
|
|
127
127
|
readonly identity: CommandIdentity;
|
|
128
128
|
readonly actorId: string;
|
|
129
129
|
readonly policyRevision: string;
|
|
130
|
+
/** Request-bound host approval guard; defaults to participant.checkApproval. */
|
|
131
|
+
readonly authorizeCommit?: AuthorizeCommit;
|
|
130
132
|
readonly callerKey?: string;
|
|
131
133
|
readonly attemptId?: string;
|
|
132
134
|
readonly invocationId?: string;
|
|
@@ -247,7 +247,7 @@ export function createGuardedPublicationService(deps) {
|
|
|
247
247
|
const cleanupWindowMs = deps.cleanupWindowMs ?? DEFAULT_CLEANUP_WINDOW_MS;
|
|
248
248
|
if (!Number.isSafeInteger(cleanupWindowMs) || cleanupWindowMs < 1)
|
|
249
249
|
throw new Error('invalid preparation lifetime');
|
|
250
|
-
const guard = (client) => async (context, transaction) => {
|
|
250
|
+
const guard = (client, authorizeCommit) => async (context, transaction) => {
|
|
251
251
|
const { state, acl } = await deps.createUnitWriter(client()).readAuthorization(context.artifactId);
|
|
252
252
|
const verdict = decidePublishAuthorization({ lifecycleState: state, acl, actorId: context.actorId });
|
|
253
253
|
if (!verdict.allowed) {
|
|
@@ -259,19 +259,6 @@ export function createGuardedPublicationService(deps) {
|
|
|
259
259
|
retriable: false,
|
|
260
260
|
};
|
|
261
261
|
}
|
|
262
|
-
const approval = await transaction.participant.checkApproval({
|
|
263
|
-
actorId: context.actorId,
|
|
264
|
-
operation: context.operation,
|
|
265
|
-
});
|
|
266
|
-
if (!approval.approved) {
|
|
267
|
-
return {
|
|
268
|
-
allowed: false,
|
|
269
|
-
reason: approval.reason ?? 'denied',
|
|
270
|
-
publicCode: 'access_denied',
|
|
271
|
-
status: 403,
|
|
272
|
-
retriable: false,
|
|
273
|
-
};
|
|
274
|
-
}
|
|
275
262
|
const revision = await transaction.participant.readRevision();
|
|
276
263
|
if (revision !== context.policyRevision) {
|
|
277
264
|
return {
|
|
@@ -282,6 +269,12 @@ export function createGuardedPublicationService(deps) {
|
|
|
282
269
|
retriable: false,
|
|
283
270
|
};
|
|
284
271
|
}
|
|
272
|
+
if (authorizeCommit !== undefined)
|
|
273
|
+
return authorizeCommit(context, transaction);
|
|
274
|
+
const approval = await transaction.participant.checkApproval({ actorId: context.actorId, operation: context.operation });
|
|
275
|
+
if (!approval.approved) {
|
|
276
|
+
return { allowed: false, reason: approval.reason ?? 'denied', publicCode: 'access_denied', status: 403, retriable: false };
|
|
277
|
+
}
|
|
285
278
|
return { allowed: true, fence: transaction.participant.mintFence(revision, context.invocationId) };
|
|
286
279
|
};
|
|
287
280
|
return {
|
|
@@ -365,7 +358,7 @@ export function createGuardedPublicationService(deps) {
|
|
|
365
358
|
if (finalizationClient === undefined)
|
|
366
359
|
throw new Error('publication guard needs the finalization client');
|
|
367
360
|
return finalizationClient;
|
|
368
|
-
}),
|
|
361
|
+
}, input.authorizeCommit),
|
|
369
362
|
finishIntents: async (client) => {
|
|
370
363
|
await deps.createUnitWriter(client).completeIntent(intent.intentId);
|
|
371
364
|
},
|
package/docs/migration-l6.md
CHANGED
|
@@ -83,7 +83,12 @@ under this lock and compares it to the stored receipt. Missing or changed
|
|
|
83
83
|
targets deny before the host guard; explicit replay requires a binding.
|
|
84
84
|
|
|
85
85
|
Use `createGuardedEnsureService` and `createGuardedPublicationService` for
|
|
86
|
-
custom services.
|
|
86
|
+
custom services. Pass the request-bound host guard as `publishFinalized(input)`'s
|
|
87
|
+
optional `authorizeCommit` field to preserve host approval policy and refusal codes;
|
|
88
|
+
it runs after lifecycle, ACL and policy revision checks. When omitted, publication
|
|
89
|
+
uses `participant.checkApproval` and denies unapproved or expired commits.
|
|
90
|
+
HTTP/MCP pass the invocation's host guard automatically.
|
|
91
|
+
The canonical coordinator exposes `finalizeOperation` and
|
|
87
92
|
`replayOperation` with the frozen L0 inputs. Bind fresh domain request plans
|
|
88
93
|
through its `operations` option; no process-local replay cache is used.
|
|
89
94
|
Provider adapters use `finalizePrepared` and `replayPrepared` on that same
|
|
@@ -206,12 +206,15 @@ export function createRadarCommitSetup(options: {
|
|
|
206
206
|
pool: Pool;
|
|
207
207
|
clock: { nowMs: number };
|
|
208
208
|
quotaKey?: string;
|
|
209
|
+
/** Bind this same mapper on createHttpArtifactApp and createArtifactsAdapter. */
|
|
210
|
+
mapHostError?: CommitCoordinatorOptions['mapHostError'];
|
|
209
211
|
testObserver?: CommitCoordinatorOptions['testObserver'];
|
|
210
212
|
testPoints?: readonly CoordinatorTestPoint[];
|
|
211
213
|
}): RadarCommitSetup {
|
|
212
214
|
const quotaKey = options.quotaKey ?? 'radar-quota';
|
|
213
215
|
const abandonedAttempts: AbandonedAttempt[] = [];
|
|
214
216
|
const coordinator = createArtifactCommitCoordinator({
|
|
217
|
+
mapHostError: options.mapHostError,
|
|
215
218
|
executor: createPostgresCommitExecutor(options.pool),
|
|
216
219
|
policy: createParticipantFactory(quotaKey, options.clock),
|
|
217
220
|
nowMs: () => options.clock.nowMs,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sentropic/artifacts",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Generic configurable artifacts MCP and artifact service: StorageScope/Artifact/Binding/Release model, canonical manifests, object authorization, idempotent intents (spec-v8 §9).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|