@sentropic/artifacts 0.2.5 → 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 +19 -0
- package/README.md +13 -3
- package/dist/adapters/postgres.js +11 -4
- package/dist/http.d.ts +3 -2
- package/dist/http.js +221 -36
- package/dist/mcp-core.d.ts +3 -3
- package/dist/mcp-core.js +19 -9
- package/dist/profile/contracts.d.ts +15 -5
- package/dist/profile/contracts.js +9 -6
- package/dist/profile/index.js +33 -4
- package/dist/profile/radar.js +4 -1
- package/dist/profile/schemas.js +31 -5
- package/dist/profile/validation.d.ts +3 -1
- package/dist/profile/validation.js +32 -13
- package/dist/server/access.d.ts +8 -0
- package/dist/server/command-identity.d.ts +4 -2
- package/dist/server/command-identity.js +5 -5
- package/dist/server/commit/coordinator.js +12 -9
- 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 +31 -2
- package/dist/server/failures.js +14 -3
- package/dist/server/lifecycle.d.ts +2 -0
- package/dist/server/lifecycle.js +3 -0
- package/dist/server/management.d.ts +1 -0
- package/dist/server/management.js +9 -2
- package/dist/server/outcome-projection.d.ts +2 -2
- package/dist/server/outcome-projection.js +25 -9
- package/dist/server/publication.d.ts +18 -1
- package/dist/server/publication.js +54 -5
- 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/summary.d.ts +4 -3
- package/dist/server/summary.js +4 -3
- 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/0005_metadata.sql +14 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.6
|
|
4
|
+
|
|
5
|
+
- Integrate part 1 with the source reads, sharing, terminal deletion, masking and
|
|
6
|
+
summaries published in 0.2.5. Apply `0005_metadata.sql` after 0004; it adds
|
|
7
|
+
editorial metadata and nullable release authors/lineage without backfilling history.
|
|
8
|
+
- Add version history and revert as a new release on the same stable URL, preserving
|
|
9
|
+
current metadata and ACLs. The v2 profile uses `targetReleaseId` and paginated history.
|
|
10
|
+
- Add full-bundle or exactly-once text edits, metadata on ensure/publish, and
|
|
11
|
+
transactional release CAS, metadata, references, quota and receipts.
|
|
12
|
+
- Separate trusted `profileRevisionId`, `requireExpectedCurrentReleaseId` and
|
|
13
|
+
`resultEnvelopeVersion`. Compatible bindings keep root results; strict v2 retains
|
|
14
|
+
commit state, replay status and typed failures through mcp-platform 0.4.1.
|
|
15
|
+
- Replay historical receipts before enforcing new preconditions or preparing edits.
|
|
16
|
+
Missing strict preconditions use the R2 `precondition_required` message and details.
|
|
17
|
+
|
|
18
|
+
IMMO activates part 1 only after its qualified input and result switchovers.
|
|
19
|
+
Artifacts 0.2.6 stays inside the existing cluster-mesh `>=0.2.0 <0.3.0` range;
|
|
20
|
+
the API consumes the workspace package and retains its pinned APP-P1 profile.
|
|
21
|
+
|
|
3
22
|
## 0.2.5
|
|
4
23
|
|
|
5
24
|
- D1: Read verified author manifests and file bytes for the current or a selected
|
package/README.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# @sentropic/artifacts — generic artifact contracts (L-5a)
|
|
2
2
|
|
|
3
|
+
Publication profiles can set `requireExpectedCurrentReleaseId: true` to require an
|
|
4
|
+
explicit UUID or `null` on publish/revert. Strict profiles reject writers without
|
|
5
|
+
CAS. Omission remains supported in generic profiles. Replay the same caller key
|
|
6
|
+
after an unknown commit outcome; changing files, edits, metadata or the precondition
|
|
7
|
+
requires a new key. The `artifacts-outcome/v2` MCP envelope requires
|
|
8
|
+
`@sentropic/mcp-platform >=0.4.1` and preserves commit state and typed error details.
|
|
9
|
+
|
|
3
10
|
## Shared PostgreSQL activation (UP-T, 0.2.3)
|
|
4
11
|
|
|
5
12
|
W1.c/W3.a and Radar S1/A6 use the same imports:
|
|
@@ -314,7 +321,8 @@ The installed package includes SQL resources exported under
|
|
|
314
321
|
```js
|
|
315
322
|
import { readFile } from 'node:fs/promises';
|
|
316
323
|
|
|
317
|
-
const migrations = ['0001_artifacts.sql', '0002_lifecycle.sql'
|
|
324
|
+
const migrations = ['0001_artifacts.sql', '0002_lifecycle.sql', '0003_coordinator.sql',
|
|
325
|
+
'0004_terminal_controls.sql', '0005_metadata.sql'];
|
|
318
326
|
for (const name of migrations) {
|
|
319
327
|
const sql = await readFile(new URL(import.meta.resolve(
|
|
320
328
|
`@sentropic/artifacts/migrations/${name}`,
|
|
@@ -324,8 +332,10 @@ for (const name of migrations) {
|
|
|
324
332
|
```
|
|
325
333
|
|
|
326
334
|
Apply `0001_artifacts.sql` first to create the domain schema, then
|
|
327
|
-
`0002_lifecycle.sql` to add lifecycle, sharing, sources and GC support,
|
|
328
|
-
|
|
335
|
+
`0002_lifecycle.sql` to add lifecycle, sharing, sources and GC support, then
|
|
336
|
+
`0003_coordinator.sql`, `0004_terminal_controls.sql`, and `0005_metadata.sql`
|
|
337
|
+
(artifact display metadata and nullable release lineage),
|
|
338
|
+
before using the Postgres store. The host owns migration credentials, serialization,
|
|
329
339
|
transactions and the migration journal; execute each version through that
|
|
330
340
|
runner rather than running this read loop at application startup.
|
|
331
341
|
|
|
@@ -21,6 +21,9 @@ function toIntent(row) {
|
|
|
21
21
|
}
|
|
22
22
|
function toRelease(row) {
|
|
23
23
|
return {
|
|
24
|
+
authorPrincipalId: row.author_principal_id ?? null,
|
|
25
|
+
parentReleaseId: row.parent_release_id ?? null,
|
|
26
|
+
revertedFromReleaseId: row.reverted_from_release_id ?? null,
|
|
24
27
|
releaseId: row.id,
|
|
25
28
|
artifactId: row.artifact_id,
|
|
26
29
|
manifestDigest: row.manifest_digest,
|
|
@@ -143,11 +146,14 @@ export function createPostgresArtifactStore(pool) {
|
|
|
143
146
|
}
|
|
144
147
|
},
|
|
145
148
|
async getRelease(releaseId) {
|
|
146
|
-
const { rows } = await pool.query(`SELECT
|
|
147
|
-
FROM artifacts.releases WHERE id = $1`, [releaseId]);
|
|
149
|
+
const { rows } = await pool.query(`SELECT * FROM artifacts.releases WHERE id = $1`, [releaseId]);
|
|
148
150
|
const row = rows[0];
|
|
149
151
|
return row === undefined ? undefined : toRelease(row);
|
|
150
152
|
},
|
|
153
|
+
async listReleases(artifactId) {
|
|
154
|
+
const { rows } = await pool.query(`SELECT * FROM artifacts.releases WHERE artifact_id = $1 ORDER BY created_at, id`, [artifactId]);
|
|
155
|
+
return rows.map(toRelease);
|
|
156
|
+
},
|
|
151
157
|
async getCurrentReleaseId(artifactId) {
|
|
152
158
|
const { rows } = await pool.query(`SELECT release_id FROM artifacts.artifact_current WHERE artifact_id = $1`, [artifactId]);
|
|
153
159
|
return rows[0]?.release_id;
|
|
@@ -209,14 +215,15 @@ export function createPostgresArtifactStore(pool) {
|
|
|
209
215
|
principalId: grant.principal_id,
|
|
210
216
|
grant: grant.permission,
|
|
211
217
|
})),
|
|
218
|
+
...(row.metadata === null || Object.keys(row.metadata).length === 0 ? {} : { metadata: row.metadata }),
|
|
212
219
|
};
|
|
213
220
|
},
|
|
214
221
|
async putArtifactAcl(artifactId, acl) {
|
|
215
|
-
await pool.query(`UPDATE artifacts.artifacts SET is_public = $2,
|
|
222
|
+
await pool.query(`UPDATE artifacts.artifacts SET is_public = $2, metadata = $4,
|
|
216
223
|
lifecycle_state = CASE WHEN $3 THEN 'withdrawn'
|
|
217
224
|
WHEN lifecycle_state = 'withdrawn' THEN 'active'
|
|
218
225
|
ELSE lifecycle_state END
|
|
219
|
-
WHERE id = $1`, [artifactId, acl.isPublic, acl.withdrawn]);
|
|
226
|
+
WHERE id = $1`, [artifactId, acl.isPublic, acl.withdrawn, JSON.stringify(acl.metadata ?? {})]);
|
|
220
227
|
await pool.query(`DELETE FROM artifacts.artifact_access WHERE artifact_id = $1`, [
|
|
221
228
|
artifactId,
|
|
222
229
|
]);
|
package/dist/http.d.ts
CHANGED
|
@@ -40,7 +40,8 @@ export interface HttpAppDeps {
|
|
|
40
40
|
ensureHost: GuardedEnsureHost;
|
|
41
41
|
publication: Omit<GuardedPublicationDeps, 'coordinator' | 'limits'> & {
|
|
42
42
|
getCurrent: PublicationService['getCurrent'];
|
|
43
|
-
getRelease
|
|
43
|
+
getRelease: PublicationService['getRelease'];
|
|
44
|
+
listReleases: PublicationService['listReleases'];
|
|
44
45
|
};
|
|
45
46
|
sources?: Pick<SourceStore, 'getSource'>;
|
|
46
47
|
management?: {
|
|
@@ -48,7 +49,7 @@ export interface HttpAppDeps {
|
|
|
48
49
|
confirmation?: ArtifactConfirmationPort;
|
|
49
50
|
};
|
|
50
51
|
acls: AclStore;
|
|
51
|
-
lifecycle: Pick<LifecycleService, 'stateOf' | 'canRead'>;
|
|
52
|
+
lifecycle: Pick<LifecycleService, 'stateOf' | 'canRead' | 'getReleaseState'>;
|
|
52
53
|
isApprovedAccount?: (principalId: string) => boolean;
|
|
53
54
|
/** One trusted URL policy shared by HTTP and MCP receipts. */
|
|
54
55
|
publicationUrls?: (artifactId: string, releaseId: string) => {
|
package/dist/http.js
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
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';
|
|
10
13
|
import { readArtifactSource } from './server/source-read.js';
|
|
11
14
|
import { artifactSummary } from './server/summary.js';
|
|
15
|
+
import { artifactVersions } from './server/versions.js';
|
|
12
16
|
import { manageArtifact } from './server/management.js';
|
|
13
17
|
import { createPostgresManagementUnit } from './adapters/postgres-management.js';
|
|
14
18
|
export { projectArtifactHttpOutcome } from './server/outcome-projection.js';
|
|
@@ -20,6 +24,7 @@ export function errorBody(status) {
|
|
|
20
24
|
}
|
|
21
25
|
/** Retained for consumers describing canonical textual publication inputs. */
|
|
22
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);
|
|
23
28
|
function object(value) {
|
|
24
29
|
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
25
30
|
throw new ArtifactProfileInputError();
|
|
@@ -29,7 +34,7 @@ function object(value) {
|
|
|
29
34
|
export function assertArtifactsTransportProfile(value) {
|
|
30
35
|
assertCompiledArtifactsProfile(value);
|
|
31
36
|
const supported = ['artifacts_ensure', 'artifacts_publish', 'artifacts_list', 'artifacts_get',
|
|
32
|
-
'artifacts_get_source', 'artifacts_set_access', 'artifacts_delete'];
|
|
37
|
+
'artifacts_list_versions', 'artifacts_get_source', 'artifacts_revert', 'artifacts_set_access', 'artifacts_delete'];
|
|
33
38
|
if (value.selectedCapabilities.some((name) => !supported.includes(name))) {
|
|
34
39
|
throw new Error('unsupported artifacts transport capability');
|
|
35
40
|
}
|
|
@@ -52,23 +57,138 @@ export function createHttpArtifactApp(deps) {
|
|
|
52
57
|
throw new Error('artifact delete confirmation port required');
|
|
53
58
|
}
|
|
54
59
|
const ensure = createGuardedEnsureService({ profile, coordinator: deps.coordinator,
|
|
55
|
-
derivation: deps.derivation, host:
|
|
56
|
-
|
|
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') {
|
|
57
73
|
const acl = await deps.acls.getArtifactAcl(artifactId);
|
|
58
74
|
if (!acl || acl.storageScopeId !== invocation.storageScopeId)
|
|
59
75
|
throw new ArtifactOperationError('not_found');
|
|
60
76
|
const state = await deps.lifecycle.stateOf(artifactId);
|
|
61
77
|
if (acl.ownerId === invocation.actorId && (state === 'deleted' || state === 'purged')) {
|
|
62
78
|
// Management must still authorize terminal owners before replaying a receipt.
|
|
63
|
-
if (
|
|
79
|
+
if (action === true)
|
|
64
80
|
return acl;
|
|
65
81
|
throw new ArtifactOperationError('gone');
|
|
66
82
|
}
|
|
67
|
-
if (!deps.lifecycle.canRead({ artifactId, state }, acl, invocation.actorId, 'read', deps.isApprovedAccount)) {
|
|
83
|
+
if (!deps.lifecycle.canRead({ artifactId, state }, acl, invocation.actorId, action === true ? 'read' : action, deps.isApprovedAccount)) {
|
|
68
84
|
throw new ArtifactOperationError('not_found');
|
|
69
85
|
}
|
|
70
86
|
return acl;
|
|
71
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
|
+
}
|
|
72
192
|
const app = {
|
|
73
193
|
selectedCapabilities: profile.selectedCapabilities,
|
|
74
194
|
profileDigest: profile.profileDigest,
|
|
@@ -96,10 +216,13 @@ export function createHttpArtifactApp(deps) {
|
|
|
96
216
|
throw new ArtifactProfileInputError();
|
|
97
217
|
}
|
|
98
218
|
let args;
|
|
99
|
-
|
|
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) {
|
|
100
223
|
if (request.body !== undefined && Object.keys(object(request.body)).length)
|
|
101
224
|
throw new ArtifactProfileInputError();
|
|
102
|
-
args = { artifactId: request.path.slice(
|
|
225
|
+
args = { artifactId: request.path.slice(getPrefix.length) };
|
|
103
226
|
}
|
|
104
227
|
else {
|
|
105
228
|
args = { ...object(request.body ?? {}) };
|
|
@@ -109,9 +232,9 @@ export function createHttpArtifactApp(deps) {
|
|
|
109
232
|
delete args.storageScopeId;
|
|
110
233
|
}
|
|
111
234
|
}
|
|
112
|
-
const normalized = profile.validate(capability, args, request.idempotencyKey);
|
|
113
|
-
const command = ['artifacts_ensure', 'artifacts_publish', 'artifacts_set_access', 'artifacts_delete'].includes(capability)
|
|
114
|
-
? createArtifactCommand(profile, capability, args, invocation, deps.derivation, request.idempotencyKey) : undefined;
|
|
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;
|
|
115
238
|
if (command && ['ensure', 'publish'].includes(command.operation)) {
|
|
116
239
|
phase = 'migration';
|
|
117
240
|
await assertArtifactReplayMigration(deps.legacyRecords, command, invocation, normalized.idempotencyKey);
|
|
@@ -121,11 +244,20 @@ export function createHttpArtifactApp(deps) {
|
|
|
121
244
|
const outcome = await ensure.ensure(invocation, { ...normalized,
|
|
122
245
|
envelopeKey: request.idempotencyKey });
|
|
123
246
|
if (outcome.ok) {
|
|
247
|
+
commitState = outcome.commitState;
|
|
124
248
|
const state = await deps.lifecycle.stateOf(outcome.result.artifactId);
|
|
125
249
|
if (state === 'deleted' || state === 'purged')
|
|
126
250
|
throw new ArtifactOperationError('gone');
|
|
127
251
|
}
|
|
128
|
-
|
|
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 } } };
|
|
129
261
|
}
|
|
130
262
|
if (capability === 'artifacts_get_source') {
|
|
131
263
|
const artifactId = normalized.artifactId;
|
|
@@ -145,15 +277,18 @@ export function createHttpArtifactApp(deps) {
|
|
|
145
277
|
commitState: 'not_committed', replayed: false };
|
|
146
278
|
}
|
|
147
279
|
if (capability === 'artifacts_set_access' || capability === 'artifacts_delete') {
|
|
148
|
-
await visible(normalized.artifactId, invocation, true);
|
|
280
|
+
const acl = await visible(normalized.artifactId, invocation, true);
|
|
281
|
+
if (acl.ownerId !== invocation.actorId)
|
|
282
|
+
throw new ArtifactOperationError('access_denied');
|
|
149
283
|
const outcome = await manageArtifact({ coordinator: deps.coordinator,
|
|
150
284
|
createUnit: deps.management?.createUnit ?? createPostgresManagementUnit,
|
|
151
285
|
confirmation: deps.management?.confirmation, readPolicyRevision: () => deps.ensureHost.readPolicyRevision(),
|
|
152
286
|
isApprovedPrincipal: deps.isApprovedAccount ?? (() => false),
|
|
287
|
+
validateNewCommand: () => { profile.validate(capability, args, request.idempotencyKey); },
|
|
153
288
|
}, invocation, command, normalized.confirmationRef);
|
|
154
289
|
return outcome.ok ? { ...outcome, result: { status: 200, body: outcome.result } } : outcome;
|
|
155
290
|
}
|
|
156
|
-
if (capability === 'artifacts_publish') {
|
|
291
|
+
if (capability === 'artifacts_publish' || capability === 'artifacts_revert') {
|
|
157
292
|
const artifactId = normalized.artifactId;
|
|
158
293
|
const acl = await visible(artifactId, invocation);
|
|
159
294
|
if (acl.ownerId !== invocation.actorId)
|
|
@@ -162,31 +297,53 @@ export function createHttpArtifactApp(deps) {
|
|
|
162
297
|
const publication = createGuardedPublicationService({ ...deps.publication, coordinator: deps.coordinator,
|
|
163
298
|
limits: { maxFiles: limits.publishFilesMax, maxFileBytes: limits.maxDecodedFileBytes,
|
|
164
299
|
maxTotalBytes: limits.maxDecodedTotalBytes } });
|
|
165
|
-
const
|
|
166
|
-
|
|
300
|
+
const meta = metadataOf(normalized);
|
|
301
|
+
const input = { ownerId: acl.ownerId, storageScopeId: invocation.storageScopeId, artifactId,
|
|
167
302
|
identity: command.identity, actorId: invocation.actorId, invocationId: invocation.invocationId,
|
|
168
303
|
attemptId: invocation.invocationId, policyRevision: await deps.ensureHost.readPolicyRevision(),
|
|
169
|
-
authorizeCommit: invocation.authorizeCommit,
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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;
|
|
179
326
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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 ?? {});
|
|
184
341
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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);
|
|
190
347
|
}
|
|
191
348
|
if (capability === 'artifacts_get') {
|
|
192
349
|
const artifactId = normalized.artifactId;
|
|
@@ -196,8 +353,34 @@ export function createHttpArtifactApp(deps) {
|
|
|
196
353
|
return { ok: true, invocationId: invocation.invocationId, commitState: 'not_committed', replayed: false,
|
|
197
354
|
result: { status: 200, body: { ...artifactSummary({ artifactId, acl, state, current,
|
|
198
355
|
actorId: invocation.actorId, urls: deps.publicationUrls, approved: deps.isApprovedAccount }),
|
|
356
|
+
...(acl.metadata !== undefined ? { metadata: acl.metadata } : {}),
|
|
199
357
|
...(acl.ownerId === invocation.actorId ? { grants: acl.grants } : {}) } } };
|
|
200
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
|
+
}
|
|
201
384
|
const visibleArtifacts = [];
|
|
202
385
|
for (const artifactId of await deps.acls.listArtifactIds()) {
|
|
203
386
|
const acl = await deps.acls.getArtifactAcl(artifactId);
|
|
@@ -217,7 +400,7 @@ export function createHttpArtifactApp(deps) {
|
|
|
217
400
|
catch (cause) {
|
|
218
401
|
const failure = classifyArtifactFailure(cause, { invocationId: invocation?.invocationId ?? '',
|
|
219
402
|
operation, phase, commitState }, (error) => error instanceof ArtifactProfileInputError
|
|
220
|
-
? new ArtifactOperationError(error.category) : deps.mapHostError?.(error));
|
|
403
|
+
? new ArtifactOperationError(error.category, { publicDetails: error.details }) : deps.mapHostError?.(error));
|
|
221
404
|
if (cause instanceof CommitQuarantineError) {
|
|
222
405
|
const migration = { ...failure, kind: 'operational', category: 'provider_unavailable',
|
|
223
406
|
variant: 'legacy_quarantine', httpStatus: 503, publicCode: 'capability_unavailable', retriable: false };
|
|
@@ -227,7 +410,9 @@ export function createHttpArtifactApp(deps) {
|
|
|
227
410
|
return { ok: false, failure };
|
|
228
411
|
}
|
|
229
412
|
},
|
|
230
|
-
async handle(request, invocation) {
|
|
413
|
+
async handle(request, invocation) {
|
|
414
|
+
return projectArtifactHttpOutcome(await app.execute(request, invocation), profile.resultEnvelopeVersion === 2);
|
|
415
|
+
},
|
|
231
416
|
};
|
|
232
417
|
return Object.freeze(app);
|
|
233
418
|
}
|
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_set_access" | "artifacts_delete" | "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";
|
|
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,8 +25,10 @@ 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 },
|
|
@@ -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':
|
|
@@ -73,6 +77,12 @@ export function createArtifactsToolExecutor(deps) {
|
|
|
73
77
|
case 'artifacts_delete':
|
|
74
78
|
request = { method: 'POST', path: '/delete', body: args };
|
|
75
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;
|
|
76
86
|
default: throw new ArtifactProfileInputError(true);
|
|
77
87
|
}
|
|
78
88
|
return await deps.app.execute({ ...request, actorId: input.invocation.actorId,
|
|
@@ -83,7 +93,7 @@ export function createArtifactsToolExecutor(deps) {
|
|
|
83
93
|
}
|
|
84
94
|
}
|
|
85
95
|
return { execute, async invoke(input) {
|
|
86
|
-
return projectArtifactHttpOutcome(await execute(input));
|
|
96
|
+
return projectArtifactHttpOutcome(await execute(input), profile.resultEnvelopeVersion === 2);
|
|
87
97
|
} };
|
|
88
98
|
}
|
|
89
99
|
export function createArtifactsAdapter(options) {
|
|
@@ -101,7 +111,7 @@ export function createArtifactsAdapter(options) {
|
|
|
101
111
|
let outcome;
|
|
102
112
|
try {
|
|
103
113
|
// Selection and closed input checks precede even the host invocation binding.
|
|
104
|
-
options.profile.validate(request.capabilityRef, request.input, request.idempotencyKey);
|
|
114
|
+
options.profile.validate(request.capabilityRef, request.input, request.idempotencyKey, { deferPrecondition: true });
|
|
105
115
|
const invocation = await options.createInvocation(request);
|
|
106
116
|
if (invocation.actorId !== request.ctx.principal.sub)
|
|
107
117
|
throw new ArtifactProfileInputError();
|
|
@@ -111,7 +121,7 @@ export function createArtifactsAdapter(options) {
|
|
|
111
121
|
catch (cause) {
|
|
112
122
|
outcome = rejected(cause, request.ctx.requestId, request.capabilityRef, options.mapHostError);
|
|
113
123
|
}
|
|
114
|
-
return projectArtifactMcpOutcome(outcome, request.ctx.auditId);
|
|
124
|
+
return projectArtifactMcpOutcome(outcome, request.ctx.auditId, options.profile.resultEnvelopeVersion ?? 1);
|
|
115
125
|
},
|
|
116
126
|
readResource: async (request) => ({ ok: false, auditId: request.ctx.auditId, redactionClass: 'low',
|
|
117
127
|
error: { code: 'not_found', message: 'not_found', retriable: false } }),
|