@sentropic/artifacts 0.2.4 → 0.2.6
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/CHANGELOG.md +34 -0
- package/README.md +51 -3
- package/dist/adapters/postgres-management.d.ts +4 -0
- package/dist/adapters/postgres-management.js +36 -0
- package/dist/adapters/postgres.js +30 -8
- package/dist/http.d.ts +11 -1
- package/dist/http.js +272 -38
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/mcp-core.d.ts +3 -3
- package/dist/mcp-core.js +29 -10
- package/dist/profile/contracts.d.ts +15 -5
- package/dist/profile/contracts.js +9 -6
- package/dist/profile/index.js +39 -4
- package/dist/profile/radar.js +4 -1
- package/dist/profile/schemas.js +41 -8
- package/dist/profile/validation.d.ts +3 -1
- package/dist/profile/validation.js +36 -13
- package/dist/server/access.d.ts +14 -0
- package/dist/server/command-identity.d.ts +6 -3
- package/dist/server/command-identity.js +8 -8
- package/dist/server/commit/coordinator.js +13 -10
- package/dist/server/commit/types.d.ts +4 -0
- package/dist/server/edits.d.ts +17 -0
- package/dist/server/edits.js +43 -0
- package/dist/server/failures.d.ts +34 -2
- package/dist/server/failures.js +17 -3
- package/dist/server/lifecycle.d.ts +4 -0
- package/dist/server/lifecycle.js +15 -0
- package/dist/server/management.d.ts +46 -0
- package/dist/server/management.js +86 -0
- package/dist/server/outcome-projection.d.ts +2 -2
- package/dist/server/outcome-projection.js +25 -7
- package/dist/server/publication.d.ts +22 -11
- package/dist/server/publication.js +56 -7
- package/dist/server/result-schema.d.ts +14 -0
- package/dist/server/result-schema.js +35 -0
- package/dist/server/service.d.ts +2 -0
- package/dist/server/service.js +2 -0
- package/dist/server/source-read.d.ts +38 -0
- package/dist/server/source-read.js +68 -0
- package/dist/server/summary.d.ts +40 -0
- package/dist/server/summary.js +18 -0
- package/dist/server/templates.js +24 -3
- package/dist/server/versions.d.ts +29 -0
- package/dist/server/versions.js +39 -0
- package/docs/migration-l6.md +8 -1
- package/migrations/0004_terminal_controls.sql +47 -0
- package/migrations/0005_metadata.sql +14 -0
- package/package.json +15 -2
package/dist/http.js
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
|
+
/** Selected HTTP control surface over the authorized atomic services. */
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
1
3
|
import { parseManifest } from './codecs.js';
|
|
2
4
|
import { assertCompiledArtifactsProfile, ArtifactProfileInputError } from './profile/index.js';
|
|
3
5
|
import { createArtifactCommand } from './server/command-identity.js';
|
|
4
6
|
import { createGuardedEnsureService } from './server/service.js';
|
|
5
7
|
import { assertArtifactReplayMigration } from './server/idempotency.js';
|
|
6
8
|
import { createGuardedPublicationService } from './server/publication.js';
|
|
9
|
+
import { applyEdits } from './server/edits.js';
|
|
7
10
|
import { CommitQuarantineError } from './server/commit/types.js';
|
|
8
11
|
import { ArtifactOperationError, classifyArtifactFailure } from './server/failures.js';
|
|
9
12
|
import { projectArtifactHttpOutcome } from './server/outcome-projection.js';
|
|
13
|
+
import { readArtifactSource } from './server/source-read.js';
|
|
14
|
+
import { artifactSummary } from './server/summary.js';
|
|
15
|
+
import { artifactVersions } from './server/versions.js';
|
|
16
|
+
import { manageArtifact } from './server/management.js';
|
|
17
|
+
import { createPostgresManagementUnit } from './adapters/postgres-management.js';
|
|
10
18
|
export { projectArtifactHttpOutcome } from './server/outcome-projection.js';
|
|
11
19
|
/** Stable content-route error codes; private driver messages never leave the host. */
|
|
12
20
|
export function errorBody(status) {
|
|
@@ -16,6 +24,7 @@ export function errorBody(status) {
|
|
|
16
24
|
}
|
|
17
25
|
/** Retained for consumers describing canonical textual publication inputs. */
|
|
18
26
|
export const TEXT_MIME_PATTERN = '^(?:text/[a-z0-9!#$&^_.+-]+|application/(?:javascript|ecmascript|json|xml|[a-z0-9!#$&^_.+-]+\\+(?:json|xml))|image/svg\\+xml)(?![\\s\\S])';
|
|
27
|
+
const TEXT_MIME_RE = new RegExp(TEXT_MIME_PATTERN);
|
|
19
28
|
function object(value) {
|
|
20
29
|
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
21
30
|
throw new ArtifactProfileInputError();
|
|
@@ -24,7 +33,8 @@ function object(value) {
|
|
|
24
33
|
/** Other mutation surfaces need their own qualification before host activation. */
|
|
25
34
|
export function assertArtifactsTransportProfile(value) {
|
|
26
35
|
assertCompiledArtifactsProfile(value);
|
|
27
|
-
const supported = ['artifacts_ensure', 'artifacts_publish', 'artifacts_list', 'artifacts_get'
|
|
36
|
+
const supported = ['artifacts_ensure', 'artifacts_publish', 'artifacts_list', 'artifacts_get',
|
|
37
|
+
'artifacts_list_versions', 'artifacts_get_source', 'artifacts_revert', 'artifacts_set_access', 'artifacts_delete'];
|
|
28
38
|
if (value.selectedCapabilities.some((name) => !supported.includes(name))) {
|
|
29
39
|
throw new Error('unsupported artifacts transport capability');
|
|
30
40
|
}
|
|
@@ -40,18 +50,145 @@ export function createHttpArtifactApp(deps) {
|
|
|
40
50
|
if (typeof deps.legacyRecords?.get !== 'function')
|
|
41
51
|
throw new Error('artifacts requires a legacy record inventory');
|
|
42
52
|
const profile = deps.profile;
|
|
53
|
+
if (profile.selectedCapabilities.includes('artifacts_get_source') &&
|
|
54
|
+
(!deps.sources || !deps.publication.getRelease))
|
|
55
|
+
throw new Error('artifact source read ports required');
|
|
56
|
+
if (profile.selectedCapabilities.includes('artifacts_delete') && !deps.management?.confirmation) {
|
|
57
|
+
throw new Error('artifact delete confirmation port required');
|
|
58
|
+
}
|
|
43
59
|
const ensure = createGuardedEnsureService({ profile, coordinator: deps.coordinator,
|
|
44
|
-
derivation: deps.derivation, host:
|
|
45
|
-
|
|
60
|
+
derivation: deps.derivation, host: {
|
|
61
|
+
lockBinding: (client, binding) => deps.ensureHost.lockBinding(client, binding),
|
|
62
|
+
abandonAttempt: id => deps.ensureHost.abandonAttempt(id),
|
|
63
|
+
readPolicyRevision: () => deps.ensureHost.readPolicyRevision(),
|
|
64
|
+
async resolveBinding(client, binding) {
|
|
65
|
+
const receipt = await deps.ensureHost.resolveBinding(client, binding);
|
|
66
|
+
if (receipt.created && binding.metadata) {
|
|
67
|
+
await deps.publication.createUnitWriter(client).updateMetadata(receipt.artifactId, binding.metadata);
|
|
68
|
+
}
|
|
69
|
+
return receipt;
|
|
70
|
+
},
|
|
71
|
+
} });
|
|
72
|
+
async function visible(artifactId, invocation, action = 'read') {
|
|
46
73
|
const acl = await deps.acls.getArtifactAcl(artifactId);
|
|
47
74
|
if (!acl || acl.storageScopeId !== invocation.storageScopeId)
|
|
48
75
|
throw new ArtifactOperationError('not_found');
|
|
49
76
|
const state = await deps.lifecycle.stateOf(artifactId);
|
|
50
|
-
if (
|
|
77
|
+
if (acl.ownerId === invocation.actorId && (state === 'deleted' || state === 'purged')) {
|
|
78
|
+
// Management must still authorize terminal owners before replaying a receipt.
|
|
79
|
+
if (action === true)
|
|
80
|
+
return acl;
|
|
81
|
+
throw new ArtifactOperationError('gone');
|
|
82
|
+
}
|
|
83
|
+
if (!deps.lifecycle.canRead({ artifactId, state }, acl, invocation.actorId, action === true ? 'read' : action, deps.isApprovedAccount)) {
|
|
51
84
|
throw new ArtifactOperationError('not_found');
|
|
52
85
|
}
|
|
53
86
|
return acl;
|
|
54
87
|
}
|
|
88
|
+
/** Display metadata fields present on validated args (ensure/publish). */
|
|
89
|
+
function metadataOf(normalized) {
|
|
90
|
+
const meta = {};
|
|
91
|
+
if (typeof normalized.title === 'string')
|
|
92
|
+
meta.title = normalized.title;
|
|
93
|
+
if (typeof normalized.description === 'string')
|
|
94
|
+
meta.description = normalized.description;
|
|
95
|
+
if (typeof normalized.icon === 'string' || normalized.icon === null)
|
|
96
|
+
meta.icon = normalized.icon;
|
|
97
|
+
return Object.keys(meta).length > 0 ? meta : undefined;
|
|
98
|
+
}
|
|
99
|
+
function blobKey(storageScopeId, digest) {
|
|
100
|
+
// Manifest digests are `sha256:<hex>`; physical keys are `.../blobs/sha256/<hex>`.
|
|
101
|
+
return `artifacts/${storageScopeId}/blobs/${digest.replace(':', '/')}`;
|
|
102
|
+
}
|
|
103
|
+
async function readManifestFiles(manifest, storageScopeId) {
|
|
104
|
+
const parsed = parseManifest(manifest);
|
|
105
|
+
const entries = parsed.files ?? [];
|
|
106
|
+
const files = [];
|
|
107
|
+
for (const entry of entries) {
|
|
108
|
+
const bytes = await deps.publication.blobs.getBlob(blobKey(storageScopeId, entry.digest));
|
|
109
|
+
if (bytes === undefined)
|
|
110
|
+
throw new ArtifactOperationError('release_unavailable', { release: { reason: 'not_retained' } });
|
|
111
|
+
if (bytes.byteLength !== entry.size || `sha256:${createHash('sha256').update(bytes).digest('hex')}` !== entry.digest) {
|
|
112
|
+
throw new ArtifactOperationError('verification_failed');
|
|
113
|
+
}
|
|
114
|
+
let text;
|
|
115
|
+
if (TEXT_MIME_RE.test(entry.mimeType)) {
|
|
116
|
+
try {
|
|
117
|
+
text = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
|
|
118
|
+
}
|
|
119
|
+
catch { /* Non-UTF-8 is not editable. */ }
|
|
120
|
+
}
|
|
121
|
+
files.push({ path: entry.path, mimeType: entry.mimeType, bytes: new Uint8Array(bytes),
|
|
122
|
+
...(text === undefined ? {} : { text }) });
|
|
123
|
+
}
|
|
124
|
+
return files;
|
|
125
|
+
}
|
|
126
|
+
/** Apply publish edits to the current release's text files; typed edit_mismatch changes nothing. */
|
|
127
|
+
async function editedFiles(artifactId, invocation, edits, expected) {
|
|
128
|
+
const current = expected === undefined ? await deps.publication.getCurrent(artifactId)
|
|
129
|
+
: expected === null ? undefined : await deps.publication.getRelease(expected);
|
|
130
|
+
if (current === undefined) {
|
|
131
|
+
if (expected === null || expected === undefined)
|
|
132
|
+
throw new ArtifactOperationError('release_unavailable', { release: { reason: 'unpublished' } });
|
|
133
|
+
throw new ArtifactOperationError('not_found');
|
|
134
|
+
}
|
|
135
|
+
if (parseManifest(current.manifest).artifactId !== artifactId)
|
|
136
|
+
throw new ArtifactOperationError('not_found');
|
|
137
|
+
if (await deps.lifecycle.getReleaseState(current.releaseId) === 'purged') {
|
|
138
|
+
throw new ArtifactOperationError('release_unavailable', { release: { reason: 'purged' } });
|
|
139
|
+
}
|
|
140
|
+
const files = await readManifestFiles(current.manifest, invocation.storageScopeId);
|
|
141
|
+
return applyEdits(files, edits).map((file) => ({
|
|
142
|
+
path: file.path, mimeType: file.mimeType,
|
|
143
|
+
bytes: file.text === undefined ? file.bytes : new Uint8Array(Buffer.from(file.text, 'utf8')),
|
|
144
|
+
}));
|
|
145
|
+
}
|
|
146
|
+
async function conflictSource(outcome, normalized, invocation) {
|
|
147
|
+
if (outcome.ok || outcome.failure.kind !== 'operational' || outcome.failure.variant !== 'pointer_conflict')
|
|
148
|
+
return outcome;
|
|
149
|
+
const artifactId = normalized.artifactId;
|
|
150
|
+
await visible(artifactId, invocation, 'source');
|
|
151
|
+
const conflict = outcome.failure.conflict;
|
|
152
|
+
if (!conflict?.currentReleaseId || normalized.edits === undefined)
|
|
153
|
+
return outcome;
|
|
154
|
+
const release = await deps.publication.getRelease(conflict.currentReleaseId);
|
|
155
|
+
if (!release || await deps.lifecycle.getReleaseState(release.releaseId) === 'purged')
|
|
156
|
+
return outcome;
|
|
157
|
+
const manifest = parseManifest(release.manifest);
|
|
158
|
+
const paths = new Set(normalized.edits.map(edit => edit.path));
|
|
159
|
+
const entries = manifest.files;
|
|
160
|
+
const files = (await readManifestFiles(release.manifest, invocation.storageScopeId)).filter(file => paths.has(file.path))
|
|
161
|
+
.map(file => ({ path: file.path, mimeType: file.mimeType, size: file.bytes.byteLength,
|
|
162
|
+
digest: entries.find(entry => entry.path === file.path).digest,
|
|
163
|
+
...(file.text === undefined ? { bytes: Array.from(file.bytes) } : { text: file.text }) }));
|
|
164
|
+
await visible(artifactId, invocation, 'source');
|
|
165
|
+
const failure = { ...outcome.failure, conflict: { ...conflict, currentSource: {
|
|
166
|
+
artifactId, releaseId: release.releaseId, digest: conflict.currentDigest, manifest: release.manifest,
|
|
167
|
+
files, sourceRef: null, ...deps.publicationUrls?.(artifactId, release.releaseId),
|
|
168
|
+
} } };
|
|
169
|
+
Object.defineProperty(failure, 'cause', { value: outcome.failure.cause, enumerable: false });
|
|
170
|
+
return { ok: false, failure: Object.freeze(failure) };
|
|
171
|
+
}
|
|
172
|
+
/** Shared guarded-commit response for publish and revert (same commit path, same receipt shape). */
|
|
173
|
+
function commitOutcome(outcome, artifactId, meta, setCommitState) {
|
|
174
|
+
if (!outcome.ok) {
|
|
175
|
+
const failure = { ...outcome.failure, kind: 'operational' };
|
|
176
|
+
Object.defineProperty(failure, 'cause', { value: outcome.failure.cause, enumerable: false });
|
|
177
|
+
return { ok: false, failure: Object.freeze(failure) };
|
|
178
|
+
}
|
|
179
|
+
setCommitState(outcome.commitState);
|
|
180
|
+
const receipt = object(outcome.result.receipt);
|
|
181
|
+
if (typeof receipt.releaseId !== 'string' || typeof receipt.manifest !== 'string') {
|
|
182
|
+
throw new Error('invalid publication receipt');
|
|
183
|
+
}
|
|
184
|
+
return { ...outcome, result: { status: 201, body: {
|
|
185
|
+
artifactId, releaseId: receipt.releaseId, manifest: receipt.manifest,
|
|
186
|
+
previousReleaseId: receipt.previousReleaseId ?? null, updatedAt: receipt.updatedAt ?? null,
|
|
187
|
+
...(typeof receipt.revertedFromReleaseId === 'string' ? { revertedFromReleaseId: receipt.revertedFromReleaseId } : {}),
|
|
188
|
+
digest: parseManifest(receipt.manifest).digest,
|
|
189
|
+
...deps.publicationUrls?.(artifactId, receipt.releaseId), ...(receipt.metadata ?? meta ?? {}),
|
|
190
|
+
} } };
|
|
191
|
+
}
|
|
55
192
|
const app = {
|
|
56
193
|
selectedCapabilities: profile.selectedCapabilities,
|
|
57
194
|
profileDigest: profile.profileDigest,
|
|
@@ -79,10 +216,13 @@ export function createHttpArtifactApp(deps) {
|
|
|
79
216
|
throw new ArtifactProfileInputError();
|
|
80
217
|
}
|
|
81
218
|
let args;
|
|
82
|
-
|
|
219
|
+
const getPrefix = request.method !== 'GET' ? undefined : capability === 'artifacts_get' ? '/meta/'
|
|
220
|
+
: capability === 'artifacts_list_versions' ? '/versions/'
|
|
221
|
+
: capability === 'artifacts_get_source' ? '/source/' : undefined;
|
|
222
|
+
if (getPrefix !== undefined) {
|
|
83
223
|
if (request.body !== undefined && Object.keys(object(request.body)).length)
|
|
84
224
|
throw new ArtifactProfileInputError();
|
|
85
|
-
args = { artifactId: request.path.slice(
|
|
225
|
+
args = { artifactId: request.path.slice(getPrefix.length) };
|
|
86
226
|
}
|
|
87
227
|
else {
|
|
88
228
|
args = { ...object(request.body ?? {}) };
|
|
@@ -92,10 +232,10 @@ export function createHttpArtifactApp(deps) {
|
|
|
92
232
|
delete args.storageScopeId;
|
|
93
233
|
}
|
|
94
234
|
}
|
|
95
|
-
const normalized = profile.validate(capability, args, request.idempotencyKey);
|
|
96
|
-
const command =
|
|
97
|
-
? createArtifactCommand(profile, capability, args, invocation, deps.derivation, request.idempotencyKey) : undefined;
|
|
98
|
-
if (command) {
|
|
235
|
+
const normalized = profile.validate(capability, args, request.idempotencyKey, { deferPrecondition: true });
|
|
236
|
+
const command = ['artifacts_ensure', 'artifacts_publish', 'artifacts_set_access', 'artifacts_delete', 'artifacts_revert'].includes(capability)
|
|
237
|
+
? createArtifactCommand(profile, capability, args, invocation, deps.derivation, request.idempotencyKey, { deferPrecondition: true }) : undefined;
|
|
238
|
+
if (command && ['ensure', 'publish'].includes(command.operation)) {
|
|
99
239
|
phase = 'migration';
|
|
100
240
|
await assertArtifactReplayMigration(deps.legacyRecords, command, invocation, normalized.idempotencyKey);
|
|
101
241
|
}
|
|
@@ -103,9 +243,52 @@ export function createHttpArtifactApp(deps) {
|
|
|
103
243
|
if (capability === 'artifacts_ensure') {
|
|
104
244
|
const outcome = await ensure.ensure(invocation, { ...normalized,
|
|
105
245
|
envelopeKey: request.idempotencyKey });
|
|
106
|
-
|
|
246
|
+
if (outcome.ok) {
|
|
247
|
+
commitState = outcome.commitState;
|
|
248
|
+
const state = await deps.lifecycle.stateOf(outcome.result.artifactId);
|
|
249
|
+
if (state === 'deleted' || state === 'purged')
|
|
250
|
+
throw new ArtifactOperationError('gone');
|
|
251
|
+
}
|
|
252
|
+
if (!outcome.ok)
|
|
253
|
+
return outcome;
|
|
254
|
+
const artifactId = outcome.result.artifactId;
|
|
255
|
+
const acl = await visible(artifactId, invocation);
|
|
256
|
+
const summary = artifactSummary({ artifactId, acl, actorId: invocation.actorId,
|
|
257
|
+
state: await deps.lifecycle.stateOf(artifactId), current: await deps.publication.getCurrent(artifactId),
|
|
258
|
+
urls: deps.publicationUrls, approved: deps.isApprovedAccount });
|
|
259
|
+
return { ...outcome, result: { status: 201, body: profile.resultEnvelopeVersion === 2
|
|
260
|
+
? { ...summary, ...outcome.result } : { ...outcome.result, ...acl.metadata } } };
|
|
261
|
+
}
|
|
262
|
+
if (capability === 'artifacts_get_source') {
|
|
263
|
+
const artifactId = normalized.artifactId;
|
|
264
|
+
const result = await readArtifactSource({
|
|
265
|
+
storageScopeId: invocation.storageScopeId,
|
|
266
|
+
authorize: async () => {
|
|
267
|
+
const acl = await visible(artifactId, invocation);
|
|
268
|
+
const state = await deps.lifecycle.stateOf(artifactId);
|
|
269
|
+
if (!deps.lifecycle.canRead({ artifactId, state }, acl, invocation.actorId, 'source', deps.isApprovedAccount)) {
|
|
270
|
+
throw new ArtifactOperationError('access_denied');
|
|
271
|
+
}
|
|
272
|
+
}, getCurrent: deps.publication.getCurrent, getRelease: deps.publication.getRelease,
|
|
273
|
+
getSource: (id) => deps.sources.getSource(id), getBlob: (key) => deps.publication.blobs.getBlob(key),
|
|
274
|
+
urls: deps.publicationUrls, maxFiles: 32, maxTotalBytes: 262144,
|
|
275
|
+
}, normalized);
|
|
276
|
+
return { ok: true, result: { status: 200, body: result }, invocationId: invocation.invocationId,
|
|
277
|
+
commitState: 'not_committed', replayed: false };
|
|
107
278
|
}
|
|
108
|
-
if (capability === '
|
|
279
|
+
if (capability === 'artifacts_set_access' || capability === 'artifacts_delete') {
|
|
280
|
+
const acl = await visible(normalized.artifactId, invocation, true);
|
|
281
|
+
if (acl.ownerId !== invocation.actorId)
|
|
282
|
+
throw new ArtifactOperationError('access_denied');
|
|
283
|
+
const outcome = await manageArtifact({ coordinator: deps.coordinator,
|
|
284
|
+
createUnit: deps.management?.createUnit ?? createPostgresManagementUnit,
|
|
285
|
+
confirmation: deps.management?.confirmation, readPolicyRevision: () => deps.ensureHost.readPolicyRevision(),
|
|
286
|
+
isApprovedPrincipal: deps.isApprovedAccount ?? (() => false),
|
|
287
|
+
validateNewCommand: () => { profile.validate(capability, args, request.idempotencyKey); },
|
|
288
|
+
}, invocation, command, normalized.confirmationRef);
|
|
289
|
+
return outcome.ok ? { ...outcome, result: { status: 200, body: outcome.result } } : outcome;
|
|
290
|
+
}
|
|
291
|
+
if (capability === 'artifacts_publish' || capability === 'artifacts_revert') {
|
|
109
292
|
const artifactId = normalized.artifactId;
|
|
110
293
|
const acl = await visible(artifactId, invocation);
|
|
111
294
|
if (acl.ownerId !== invocation.actorId)
|
|
@@ -114,31 +297,53 @@ export function createHttpArtifactApp(deps) {
|
|
|
114
297
|
const publication = createGuardedPublicationService({ ...deps.publication, coordinator: deps.coordinator,
|
|
115
298
|
limits: { maxFiles: limits.publishFilesMax, maxFileBytes: limits.maxDecodedFileBytes,
|
|
116
299
|
maxTotalBytes: limits.maxDecodedTotalBytes } });
|
|
117
|
-
const
|
|
118
|
-
|
|
300
|
+
const meta = metadataOf(normalized);
|
|
301
|
+
const input = { ownerId: acl.ownerId, storageScopeId: invocation.storageScopeId, artifactId,
|
|
119
302
|
identity: command.identity, actorId: invocation.actorId, invocationId: invocation.invocationId,
|
|
120
303
|
attemptId: invocation.invocationId, policyRevision: await deps.ensureHost.readPolicyRevision(),
|
|
121
|
-
authorizeCommit: invocation.authorizeCommit,
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
304
|
+
authorizeCommit: invocation.authorizeCommit, metadata: meta,
|
|
305
|
+
operation: capability === 'artifacts_revert' ? 'revert' : 'publish',
|
|
306
|
+
expectedCurrentReleaseId: normalized.expectedCurrentReleaseId };
|
|
307
|
+
const replay = await publication.replayFinalized(input);
|
|
308
|
+
if (replay !== undefined)
|
|
309
|
+
return commitOutcome(replay, artifactId, meta, state => { commitState = state; });
|
|
310
|
+
profile.validate(capability, args, request.idempotencyKey);
|
|
311
|
+
let files;
|
|
312
|
+
let source;
|
|
313
|
+
let releaseProfile;
|
|
314
|
+
if (capability === 'artifacts_publish') {
|
|
315
|
+
files = normalized.edits !== undefined
|
|
316
|
+
? await editedFiles(artifactId, invocation, normalized.edits, input.expectedCurrentReleaseId)
|
|
317
|
+
: normalized.files.map((file) => ({
|
|
318
|
+
path: file.path, mimeType: file.mimeType, bytes: Uint8Array.from(file.bytes),
|
|
319
|
+
}));
|
|
320
|
+
if (normalized.edits !== undefined) {
|
|
321
|
+
const { edits: _edits, ...snapshot } = normalized;
|
|
322
|
+
profile.validate(capability, { ...snapshot, files: files.map(file => ({ ...file, bytes: Array.from(file.bytes) })) });
|
|
323
|
+
}
|
|
324
|
+
source = { kind: 'inline-original' };
|
|
325
|
+
releaseProfile = normalized.profile;
|
|
131
326
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
327
|
+
else {
|
|
328
|
+
const releaseId = normalized[profile.resultEnvelopeVersion === 2 ? 'targetReleaseId' : 'releaseId'];
|
|
329
|
+
const target = await deps.publication.getRelease(releaseId);
|
|
330
|
+
const targetManifest = target === undefined ? undefined : parseManifest(target.manifest);
|
|
331
|
+
if (target === undefined || targetManifest?.artifactId !== artifactId) {
|
|
332
|
+
throw new ArtifactOperationError('not_found');
|
|
333
|
+
}
|
|
334
|
+
if (await deps.lifecycle.getReleaseState(releaseId) === 'purged') {
|
|
335
|
+
throw new ArtifactOperationError('release_unavailable', { release: { reason: 'purged' } });
|
|
336
|
+
}
|
|
337
|
+
files = (await readManifestFiles(target.manifest, invocation.storageScopeId))
|
|
338
|
+
.map((file) => ({ path: file.path, mimeType: file.mimeType, bytes: file.bytes }));
|
|
339
|
+
source = { kind: 'base-commit', commit: releaseId };
|
|
340
|
+
releaseProfile = (targetManifest.profile ?? {});
|
|
136
341
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
342
|
+
const outcome = await publication.publishFinalized({
|
|
343
|
+
...input, source, profile: releaseProfile, files,
|
|
344
|
+
});
|
|
345
|
+
const committed = commitOutcome(outcome, artifactId, meta, (state) => { commitState = state; });
|
|
346
|
+
return await conflictSource(committed, normalized, invocation);
|
|
142
347
|
}
|
|
143
348
|
if (capability === 'artifacts_get') {
|
|
144
349
|
const artifactId = normalized.artifactId;
|
|
@@ -146,10 +351,36 @@ export function createHttpArtifactApp(deps) {
|
|
|
146
351
|
const state = await deps.lifecycle.stateOf(artifactId);
|
|
147
352
|
const current = await deps.publication.getCurrent(artifactId);
|
|
148
353
|
return { ok: true, invocationId: invocation.invocationId, commitState: 'not_committed', replayed: false,
|
|
149
|
-
result: { status: 200, body: { artifactId,
|
|
150
|
-
|
|
354
|
+
result: { status: 200, body: { ...artifactSummary({ artifactId, acl, state, current,
|
|
355
|
+
actorId: invocation.actorId, urls: deps.publicationUrls, approved: deps.isApprovedAccount }),
|
|
356
|
+
...(acl.metadata !== undefined ? { metadata: acl.metadata } : {}),
|
|
151
357
|
...(acl.ownerId === invocation.actorId ? { grants: acl.grants } : {}) } } };
|
|
152
358
|
}
|
|
359
|
+
if (capability === 'artifacts_list_versions') {
|
|
360
|
+
const artifactId = normalized.artifactId;
|
|
361
|
+
await visible(artifactId, invocation);
|
|
362
|
+
const current = await deps.publication.getCurrent(artifactId);
|
|
363
|
+
if (profile.resultEnvelopeVersion === 2) {
|
|
364
|
+
const body = await artifactVersions({ artifactId, storageScopeId: invocation.storageScopeId,
|
|
365
|
+
currentReleaseId: current?.releaseId ?? null, releases: await deps.publication.listReleases(artifactId),
|
|
366
|
+
limit: normalized.limit, cursor: normalized.cursor,
|
|
367
|
+
state: id => deps.lifecycle.getReleaseState(id), urls: deps.publicationUrls });
|
|
368
|
+
await visible(artifactId, invocation);
|
|
369
|
+
return { ok: true, invocationId: invocation.invocationId, commitState: 'not_committed', replayed: false,
|
|
370
|
+
result: { status: 200, body } };
|
|
371
|
+
}
|
|
372
|
+
const releases = [];
|
|
373
|
+
for (const release of await deps.publication.listReleases(artifactId)) {
|
|
374
|
+
const manifest = parseManifest(release.manifest);
|
|
375
|
+
releases.push({ releaseId: release.releaseId, createdAt: release.createdAt,
|
|
376
|
+
author: typeof manifest.ownerId === 'string' ? manifest.ownerId : null,
|
|
377
|
+
state: await deps.lifecycle.getReleaseState(release.releaseId),
|
|
378
|
+
isCurrent: current?.releaseId === release.releaseId,
|
|
379
|
+
digest: release.manifestDigest });
|
|
380
|
+
}
|
|
381
|
+
return { ok: true, invocationId: invocation.invocationId, commitState: 'not_committed', replayed: false,
|
|
382
|
+
result: { status: 200, body: { artifactId, total: releases.length, releases } } };
|
|
383
|
+
}
|
|
153
384
|
const visibleArtifacts = [];
|
|
154
385
|
for (const artifactId of await deps.acls.listArtifactIds()) {
|
|
155
386
|
const acl = await deps.acls.getArtifactAcl(artifactId);
|
|
@@ -157,7 +388,8 @@ export function createHttpArtifactApp(deps) {
|
|
|
157
388
|
continue;
|
|
158
389
|
const state = await deps.lifecycle.stateOf(artifactId);
|
|
159
390
|
if (deps.lifecycle.canRead({ artifactId, state }, acl, invocation.actorId, 'read', deps.isApprovedAccount)) {
|
|
160
|
-
visibleArtifacts.push({ artifactId,
|
|
391
|
+
visibleArtifacts.push(artifactSummary({ artifactId, acl, state, actorId: invocation.actorId,
|
|
392
|
+
current: await deps.publication.getCurrent(artifactId), urls: deps.publicationUrls, approved: deps.isApprovedAccount }));
|
|
161
393
|
}
|
|
162
394
|
}
|
|
163
395
|
const offset = normalized.offset;
|
|
@@ -168,7 +400,7 @@ export function createHttpArtifactApp(deps) {
|
|
|
168
400
|
catch (cause) {
|
|
169
401
|
const failure = classifyArtifactFailure(cause, { invocationId: invocation?.invocationId ?? '',
|
|
170
402
|
operation, phase, commitState }, (error) => error instanceof ArtifactProfileInputError
|
|
171
|
-
? new ArtifactOperationError(error.category) : deps.mapHostError?.(error));
|
|
403
|
+
? new ArtifactOperationError(error.category, { publicDetails: error.details }) : deps.mapHostError?.(error));
|
|
172
404
|
if (cause instanceof CommitQuarantineError) {
|
|
173
405
|
const migration = { ...failure, kind: 'operational', category: 'provider_unavailable',
|
|
174
406
|
variant: 'legacy_quarantine', httpStatus: 503, publicCode: 'capability_unavailable', retriable: false };
|
|
@@ -178,7 +410,9 @@ export function createHttpArtifactApp(deps) {
|
|
|
178
410
|
return { ok: false, failure };
|
|
179
411
|
}
|
|
180
412
|
},
|
|
181
|
-
async handle(request, invocation) {
|
|
413
|
+
async handle(request, invocation) {
|
|
414
|
+
return projectArtifactHttpOutcome(await app.execute(request, invocation), profile.resultEnvelopeVersion === 2);
|
|
415
|
+
},
|
|
182
416
|
};
|
|
183
417
|
return Object.freeze(app);
|
|
184
418
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -25,3 +25,6 @@ export * from './server/reader-profile.js';
|
|
|
25
25
|
export * from './http.js';
|
|
26
26
|
export * from './server/commit-contract.js';
|
|
27
27
|
export * from './server/outcome-contract.js';
|
|
28
|
+
export * from './server/source-read.js';
|
|
29
|
+
export * from './server/management.js';
|
|
30
|
+
export * from './adapters/postgres-management.js';
|
package/dist/index.js
CHANGED
|
@@ -29,3 +29,6 @@ export * from './server/reader-profile.js';
|
|
|
29
29
|
export * from './http.js';
|
|
30
30
|
export * from './server/commit-contract.js';
|
|
31
31
|
export * from './server/outcome-contract.js';
|
|
32
|
+
export * from './server/source-read.js';
|
|
33
|
+
export * from './server/management.js';
|
|
34
|
+
export * from './adapters/postgres-management.js';
|
package/dist/mcp-core.d.ts
CHANGED
|
@@ -10,11 +10,11 @@ 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.
|
|
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"];
|
|
13
|
+
export declare const ARTIFACTS_PROVIDER_VERSION = "0.2.6";
|
|
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", "artifacts_list_versions", "artifacts_revert"];
|
|
15
15
|
export type ArtifactsToolName = (typeof ARTIFACTS_TOOL_NAMES)[number];
|
|
16
16
|
export declare const ARTIFACTS_TOOL_SPECS: {
|
|
17
|
-
name: "artifacts_ensure" | "artifacts_publish" | "artifacts_get_template" | "artifacts_get_api_guide" | "artifacts_create_upload" | "artifacts_commit_upload" | "artifacts_list" | "artifacts_get" | "artifacts_get_source" | "artifacts_fork" | "
|
|
17
|
+
name: "artifacts_ensure" | "artifacts_publish" | "artifacts_set_access" | "artifacts_delete" | "artifacts_revert" | "artifacts_get_template" | "artifacts_get_api_guide" | "artifacts_create_upload" | "artifacts_commit_upload" | "artifacts_list" | "artifacts_get" | "artifacts_get_source" | "artifacts_fork" | "artifacts_promote" | "artifacts_renew" | "artifacts_restore" | "artifacts_list_versions";
|
|
18
18
|
description: string;
|
|
19
19
|
mutability: import("@sentropic/mcp-platform").Mutability;
|
|
20
20
|
}[];
|
package/dist/mcp-core.js
CHANGED
|
@@ -2,16 +2,17 @@ import { assertArtifactsTransportProfile } from './http.js';
|
|
|
2
2
|
import { ARTIFACT_CAPABILITY_NAMES, ArtifactProfileInputError } from './profile/index.js';
|
|
3
3
|
import { ArtifactOperationError, classifyArtifactFailure } from './server/failures.js';
|
|
4
4
|
import { projectArtifactHttpOutcome, projectArtifactMcpOutcome } from './server/outcome-projection.js';
|
|
5
|
+
import { ARTIFACT_RESULT_SCHEMA } from './server/result-schema.js';
|
|
5
6
|
export * from './profile/index.js';
|
|
6
7
|
export { createRadarKeyDerivation } from './server/command-identity.js';
|
|
7
8
|
export const ARTIFACTS_PROVIDER_ID = 'sentropic.artifacts';
|
|
8
9
|
export const ARTIFACTS_APP_ID = 'sentropic-artifacts';
|
|
9
|
-
export const ARTIFACTS_PROVIDER_VERSION = '0.2.
|
|
10
|
+
export const ARTIFACTS_PROVIDER_VERSION = '0.2.6';
|
|
10
11
|
export const ARTIFACTS_TOOL_NAMES = ARTIFACT_CAPABILITY_NAMES;
|
|
11
12
|
export const ARTIFACTS_TOOL_SPECS = ARTIFACTS_TOOL_NAMES.map((name) => ({ name,
|
|
12
13
|
description: `Artifacts ${name.slice('artifacts_'.length).replaceAll('_', ' ')}.`,
|
|
13
14
|
mutability: (['artifacts_list', 'artifacts_get', 'artifacts_get_template', 'artifacts_get_api_guide',
|
|
14
|
-
'artifacts_get_source'].includes(name) ? 'read-only' : name === 'artifacts_publish' ? 'append' : 'state-transition'),
|
|
15
|
+
'artifacts_get_source', 'artifacts_list_versions'].includes(name) ? 'read-only' : name === 'artifacts_publish' ? 'append' : 'state-transition'),
|
|
15
16
|
}));
|
|
16
17
|
export const ARTIFACTS_PARAMETER_SCHEMA = { properties: {}, required: [] };
|
|
17
18
|
export function createArtifactsManifest(options) {
|
|
@@ -24,12 +25,14 @@ export function createArtifactsManifest(options) {
|
|
|
24
25
|
resources: [], prompts: [],
|
|
25
26
|
tools: ARTIFACTS_TOOL_SPECS.filter((spec) => profile.selectedCapabilities.includes(spec.name)).map((spec) => ({
|
|
26
27
|
kind: 'tool', name: spec.name,
|
|
27
|
-
description: spec.description
|
|
28
|
-
|
|
28
|
+
description: spec.description + (profile.profileRevisionId
|
|
29
|
+
? ` Active profile: ${profile.profileRevisionId}; resultEnvelopeVersion: ${profile.resultEnvelopeVersion}.` : ''),
|
|
30
|
+
inputSchema: profile.schemas[spec.name], outputSchema: profile.resultEnvelopeVersion === 2
|
|
31
|
+
? ARTIFACT_RESULT_SCHEMA : { type: 'object', additionalProperties: true },
|
|
29
32
|
requiredScopes: [...scopes], requiredClaims: [], redactionClass: 'low',
|
|
30
33
|
mutability: spec.mutability, mutatesExternalSystem: spec.mutability !== 'read-only',
|
|
31
34
|
idempotency: profile.idempotency[spec.name] ?? { required: false },
|
|
32
|
-
gates: { requiresElicitation: false, requiresHumanConfirmation:
|
|
35
|
+
gates: { requiresElicitation: false, requiresHumanConfirmation: spec.name === 'artifacts_delete', requiresPrincipalGate: false },
|
|
33
36
|
})),
|
|
34
37
|
authz: { requiredClaims: [], scopes, tenantResolution: 'custom-resolver' },
|
|
35
38
|
// The existing manifest codec includes audit metadata, including for an empty tool set.
|
|
@@ -38,7 +41,8 @@ export function createArtifactsManifest(options) {
|
|
|
38
41
|
}
|
|
39
42
|
function rejected(cause, invocationId, operation, mapHostError) {
|
|
40
43
|
return { ok: false, failure: classifyArtifactFailure(cause, { invocationId, operation,
|
|
41
|
-
phase: cause instanceof ArtifactProfileInputError ? cause.phase : 'entry', commitState: 'not_committed' }, (error) => error instanceof ArtifactProfileInputError
|
|
44
|
+
phase: cause instanceof ArtifactProfileInputError ? cause.phase : 'entry', commitState: 'not_committed' }, (error) => error instanceof ArtifactProfileInputError
|
|
45
|
+
? new ArtifactOperationError(error.category, { publicDetails: error.details }) : mapHostError?.(error)) };
|
|
42
46
|
}
|
|
43
47
|
export function createArtifactsToolExecutor(deps) {
|
|
44
48
|
assertArtifactsTransportProfile(deps.profile);
|
|
@@ -49,7 +53,7 @@ export function createArtifactsToolExecutor(deps) {
|
|
|
49
53
|
}
|
|
50
54
|
async function execute(input) {
|
|
51
55
|
try {
|
|
52
|
-
const args = profile.validate(input.tool, input.args, input.idempotencyKey);
|
|
56
|
+
const args = profile.validate(input.tool, input.args, input.idempotencyKey, { deferPrecondition: true });
|
|
53
57
|
let request;
|
|
54
58
|
switch (input.tool) {
|
|
55
59
|
case 'artifacts_ensure':
|
|
@@ -64,6 +68,21 @@ export function createArtifactsToolExecutor(deps) {
|
|
|
64
68
|
case 'artifacts_get':
|
|
65
69
|
request = { method: 'GET', path: `/meta/${args.artifactId}`, body: undefined };
|
|
66
70
|
break;
|
|
71
|
+
case 'artifacts_get_source':
|
|
72
|
+
request = { method: 'POST', path: '/get_source', body: args };
|
|
73
|
+
break;
|
|
74
|
+
case 'artifacts_set_access':
|
|
75
|
+
request = { method: 'POST', path: '/set_access', body: args };
|
|
76
|
+
break;
|
|
77
|
+
case 'artifacts_delete':
|
|
78
|
+
request = { method: 'POST', path: '/delete', body: args };
|
|
79
|
+
break;
|
|
80
|
+
case 'artifacts_list_versions':
|
|
81
|
+
request = { method: 'POST', path: '/list_versions', body: args };
|
|
82
|
+
break;
|
|
83
|
+
case 'artifacts_revert':
|
|
84
|
+
request = { method: 'POST', path: '/revert', body: args };
|
|
85
|
+
break;
|
|
67
86
|
default: throw new ArtifactProfileInputError(true);
|
|
68
87
|
}
|
|
69
88
|
return await deps.app.execute({ ...request, actorId: input.invocation.actorId,
|
|
@@ -74,7 +93,7 @@ export function createArtifactsToolExecutor(deps) {
|
|
|
74
93
|
}
|
|
75
94
|
}
|
|
76
95
|
return { execute, async invoke(input) {
|
|
77
|
-
return projectArtifactHttpOutcome(await execute(input));
|
|
96
|
+
return projectArtifactHttpOutcome(await execute(input), profile.resultEnvelopeVersion === 2);
|
|
78
97
|
} };
|
|
79
98
|
}
|
|
80
99
|
export function createArtifactsAdapter(options) {
|
|
@@ -92,7 +111,7 @@ export function createArtifactsAdapter(options) {
|
|
|
92
111
|
let outcome;
|
|
93
112
|
try {
|
|
94
113
|
// Selection and closed input checks precede even the host invocation binding.
|
|
95
|
-
options.profile.validate(request.capabilityRef, request.input, request.idempotencyKey);
|
|
114
|
+
options.profile.validate(request.capabilityRef, request.input, request.idempotencyKey, { deferPrecondition: true });
|
|
96
115
|
const invocation = await options.createInvocation(request);
|
|
97
116
|
if (invocation.actorId !== request.ctx.principal.sub)
|
|
98
117
|
throw new ArtifactProfileInputError();
|
|
@@ -102,7 +121,7 @@ export function createArtifactsAdapter(options) {
|
|
|
102
121
|
catch (cause) {
|
|
103
122
|
outcome = rejected(cause, request.ctx.requestId, request.capabilityRef, options.mapHostError);
|
|
104
123
|
}
|
|
105
|
-
return projectArtifactMcpOutcome(outcome, request.ctx.auditId);
|
|
124
|
+
return projectArtifactMcpOutcome(outcome, request.ctx.auditId, options.profile.resultEnvelopeVersion ?? 1);
|
|
106
125
|
},
|
|
107
126
|
readResource: async (request) => ({ ok: false, auditId: request.ctx.auditId, redactionClass: 'low',
|
|
108
127
|
error: { code: 'not_found', message: 'not_found', retriable: false } }),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { JsonValue } from '../contracts.js';
|
|
2
2
|
import type { EffectiveArtifactCommand } from '../server/command-identity.js';
|
|
3
3
|
export declare const PROFILE_CONTRACT_VERSION: "artifacts-profile/v1";
|
|
4
|
-
export declare const ARTIFACT_CAPABILITY_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"];
|
|
4
|
+
export declare const ARTIFACT_CAPABILITY_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", "artifacts_list_versions", "artifacts_revert"];
|
|
5
5
|
export type ArtifactCapabilityName = (typeof ARTIFACT_CAPABILITY_NAMES)[number];
|
|
6
6
|
export type ArtifactSchema = Readonly<Record<string, unknown>>;
|
|
7
7
|
export interface OperationProfile {
|
|
@@ -22,6 +22,12 @@ export interface ArtifactProfileRules {
|
|
|
22
22
|
readonly mimeTypes: readonly string[];
|
|
23
23
|
}
|
|
24
24
|
export interface ArtifactProviderProfile {
|
|
25
|
+
/** Trusted public contract revision; independent from the host policy revision. */
|
|
26
|
+
readonly profileRevisionId?: string;
|
|
27
|
+
/** Explicit projection switchover; omitted profiles retain the v1 wire shape. */
|
|
28
|
+
readonly resultEnvelopeVersion?: 1 | 2;
|
|
29
|
+
/** Require caller-supplied CAS preconditions on every publication writer. */
|
|
30
|
+
readonly requireExpectedCurrentReleaseId?: boolean;
|
|
25
31
|
readonly version: typeof PROFILE_CONTRACT_VERSION;
|
|
26
32
|
readonly selectedCapabilities: readonly string[];
|
|
27
33
|
readonly operations: readonly OperationProfile[];
|
|
@@ -42,7 +48,9 @@ export interface CompiledArtifactsProfile extends EffectiveArtifactProfile {
|
|
|
42
48
|
readonly required: true;
|
|
43
49
|
readonly scope: 'principal';
|
|
44
50
|
}>>>;
|
|
45
|
-
validate(name: string, args: unknown, envelopeKey?: string
|
|
51
|
+
validate(name: string, args: unknown, envelopeKey?: string, options?: {
|
|
52
|
+
deferPrecondition: true;
|
|
53
|
+
}): Readonly<Record<string, JsonValue>>;
|
|
46
54
|
assertSelected(name: string): asserts name is ArtifactCapabilityName;
|
|
47
55
|
capabilityForRoute(method: string, path: string): ArtifactCapabilityName | undefined;
|
|
48
56
|
}
|
|
@@ -53,15 +61,17 @@ export interface CompiledValidators {
|
|
|
53
61
|
readonly normalizeCommand: (command: unknown) => EffectiveArtifactCommand;
|
|
54
62
|
}
|
|
55
63
|
/** Projection fields are data-only; invocation correlation is added by L6. */
|
|
64
|
+
export declare const PRECONDITION_REQUIRED_MESSAGE = "expectedCurrentReleaseId is required by the active artifact profile. Re-read this artifact with artifacts_get or artifacts_list, then send expectedCurrentReleaseId using the currentReleaseId returned by that read. Send null only if the read confirms that there is no current release. Before editing an existing release, read it with artifacts_get_source and use that releaseId. The server will not infer this value.";
|
|
56
65
|
export declare class ArtifactProfileInputError extends Error {
|
|
66
|
+
readonly details?: Record<string, unknown> | undefined;
|
|
57
67
|
readonly kind: 'operational' | 'ordinary';
|
|
58
68
|
readonly status: number;
|
|
59
69
|
readonly httpStatus: number;
|
|
60
|
-
readonly variant: 'unselected_operation' | 'invalid_request';
|
|
70
|
+
readonly variant: 'unselected_operation' | 'invalid_request' | 'precondition_required';
|
|
61
71
|
readonly publicCode: string;
|
|
62
|
-
readonly category: 'invalid_request' | 'capability_unselected';
|
|
72
|
+
readonly category: 'invalid_request' | 'capability_unselected' | 'precondition_required';
|
|
63
73
|
readonly retriable = false;
|
|
64
74
|
readonly phase: 'selection' | 'entry';
|
|
65
75
|
readonly commitState = "not_committed";
|
|
66
|
-
constructor(unselected?: boolean);
|
|
76
|
+
constructor(unselected?: boolean, preconditionRequired?: boolean, details?: Record<string, unknown> | undefined);
|
|
67
77
|
}
|