@sentropic/artifacts 0.1.4 → 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.
Files changed (69) hide show
  1. package/README.md +47 -0
  2. package/dist/adapters/postgres-commit.d.ts +15 -0
  3. package/dist/adapters/postgres-commit.js +80 -0
  4. package/dist/adapters/postgres.d.ts +5 -2
  5. package/dist/adapters/postgres.js +39 -0
  6. package/dist/http.d.ts +43 -30
  7. package/dist/http.js +158 -507
  8. package/dist/index.d.ts +2 -0
  9. package/dist/index.js +2 -0
  10. package/dist/mcp-core.d.ts +32 -39
  11. package/dist/mcp-core.js +83 -244
  12. package/dist/mcp.js +5 -5
  13. package/dist/profile/contracts.d.ts +67 -0
  14. package/dist/profile/contracts.js +31 -0
  15. package/dist/profile/index.d.ts +6 -0
  16. package/dist/profile/index.js +117 -0
  17. package/dist/profile/json.d.ts +10 -0
  18. package/dist/profile/json.js +76 -0
  19. package/dist/profile/radar.d.ts +3 -0
  20. package/dist/profile/radar.js +21 -0
  21. package/dist/profile/schemas.d.ts +6 -0
  22. package/dist/profile/schemas.js +76 -0
  23. package/dist/profile/validation.d.ts +5 -0
  24. package/dist/profile/validation.js +104 -0
  25. package/dist/profile/validators.d.ts +3 -0
  26. package/dist/profile/validators.js +51 -0
  27. package/dist/server/authorized-replay.d.ts +33 -0
  28. package/dist/server/authorized-replay.js +31 -0
  29. package/dist/server/command-identity.d.ts +52 -0
  30. package/dist/server/command-identity.js +71 -0
  31. package/dist/server/commit/brands.d.ts +7 -0
  32. package/dist/server/commit/brands.js +3 -0
  33. package/dist/server/commit/coordinator.d.ts +2 -0
  34. package/dist/server/commit/coordinator.js +729 -0
  35. package/dist/server/commit/types.d.ts +382 -0
  36. package/dist/server/commit/types.js +94 -0
  37. package/dist/server/commit-contract.d.ts +4 -0
  38. package/dist/server/commit-contract.js +2 -0
  39. package/dist/server/failures.d.ts +84 -0
  40. package/dist/server/failures.js +133 -0
  41. package/dist/server/gc.d.ts +42 -1
  42. package/dist/server/gc.js +168 -5
  43. package/dist/server/idempotency.d.ts +4 -0
  44. package/dist/server/idempotency.js +14 -0
  45. package/dist/server/lifecycle.d.ts +2 -1
  46. package/dist/server/lifecycle.js +15 -1
  47. package/dist/server/object-intents.d.ts +14 -1
  48. package/dist/server/object-intents.js +31 -0
  49. package/dist/server/outcome-contract.d.ts +3 -0
  50. package/dist/server/outcome-contract.js +3 -0
  51. package/dist/server/outcome-projection.d.ts +21 -0
  52. package/dist/server/outcome-projection.js +29 -0
  53. package/dist/server/preparation-cleanup.d.ts +31 -0
  54. package/dist/server/preparation-cleanup.js +106 -0
  55. package/dist/server/publication.d.ts +78 -0
  56. package/dist/server/publication.js +263 -31
  57. package/dist/server/service.d.ts +60 -0
  58. package/dist/server/service.js +181 -0
  59. package/dist/testing.d.ts +50 -0
  60. package/dist/testing.js +86 -0
  61. package/docs/migration-l6.md +134 -0
  62. package/examples/radar-commit-setup.ts +302 -0
  63. package/l0-freeze/command-identity-contract.ts +303 -0
  64. package/l0-freeze/commit-contract.ts +421 -0
  65. package/l0-freeze/failure-contract.ts +174 -0
  66. package/l0-freeze/profile-contract.ts +219 -0
  67. package/migrations/0003_coordinator.sql +28 -0
  68. package/package.json +27 -1
  69. package/src/contracts.ts +125 -0
package/dist/index.d.ts CHANGED
@@ -23,3 +23,5 @@ export * from './content/api-profile.js';
23
23
  export * from './server/runtime.js';
24
24
  export * from './server/reader-profile.js';
25
25
  export * from './http.js';
26
+ export * from './server/commit-contract.js';
27
+ export * from './server/outcome-contract.js';
package/dist/index.js CHANGED
@@ -27,3 +27,5 @@ export * from './content/api-profile.js';
27
27
  export * from './server/runtime.js';
28
28
  export * from './server/reader-profile.js';
29
29
  export * from './http.js';
30
+ export * from './server/commit-contract.js';
31
+ export * from './server/outcome-contract.js';
@@ -1,62 +1,55 @@
1
- /**
2
- * L-5e — reusable artifacts MCP provider (spec-v8 §9 5e, review B3).
3
- *
4
- * Part 1: identity, the 15 exact `artifacts_*` tool specs with closed input
5
- * schemas, and the platform manifest. The logical provider ID is
6
- * `sentropic.artifacts`; the generic provider hardcodes no application
7
- * OAuth vocabulary — a host binds its invocation scope through the
8
- * manifest factory before digests freeze (§3.5). Part 2 adds the tool
9
- * executor over the same HTTP app direct calls use; part 3 adds the
10
- * platform adapter and the kernel registry definition (§3).
11
- */
12
- import type { AppConnectorProviderAdapter, AppMcpProviderManifest, CapabilityTool } from '@sentropic/mcp-platform';
1
+ /** Platform-free provider composition over the shared typed HTTP application. */
2
+ import type { AppConnectorProviderAdapter, AppMcpProviderManifest, AppToolInvocation } from '@sentropic/mcp-platform';
13
3
  import type { ProviderParameterSchema } from '@sentropic/mcp-platform/registry';
14
- import type { HttpRequest, HttpResponse } from './http.js';
4
+ import type { ArtifactApp, HttpResponse } from './http.js';
5
+ import { type CompiledArtifactsProfile } from './profile/index.js';
6
+ import type { ArtifactInvocationContext } from './server/commit/types.js';
7
+ import { type ArtifactOutcome, type ArtifactHostFailureMapper } from './server/failures.js';
8
+ export * from './profile/index.js';
9
+ export { createRadarKeyDerivation } from './server/command-identity.js';
10
+ export type { ArtifactKeyDerivationBinding } from './server/command-identity.js';
15
11
  export declare const ARTIFACTS_PROVIDER_ID = "sentropic.artifacts";
16
12
  export declare const ARTIFACTS_APP_ID = "sentropic-artifacts";
17
- export declare const ARTIFACTS_PROVIDER_VERSION = "0.1.4";
13
+ export declare const ARTIFACTS_PROVIDER_VERSION = "0.2.1";
18
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"];
19
15
  export type ArtifactsToolName = (typeof ARTIFACTS_TOOL_NAMES)[number];
20
- type Schema = Record<string, unknown>;
21
- interface ToolSpec {
22
- name: ArtifactsToolName;
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" | "artifacts_set_access" | "artifacts_promote" | "artifacts_renew" | "artifacts_delete" | "artifacts_restore";
23
18
  description: string;
24
- inputSchema: Schema;
25
- mutability: CapabilityTool['mutability'];
26
- idempotent?: boolean;
27
- confirm?: boolean;
28
- }
29
- export declare const ARTIFACTS_TOOL_SPECS: readonly ToolSpec[];
30
- /** Kernel provider parameter schema (typed non-secret host settings). */
19
+ mutability: import("@sentropic/mcp-platform").Mutability;
20
+ }[];
31
21
  export declare const ARTIFACTS_PARAMETER_SCHEMA: ProviderParameterSchema;
32
22
  export interface ArtifactsManifestOptions {
33
- /** Host authorization profile: scope bound to every tool invocation. */
23
+ profile: CompiledArtifactsProfile;
34
24
  invocationScope?: string;
35
25
  displayName?: string;
36
26
  }
37
- /** Build the sentropic.artifacts manifest (host scope bound pre-freeze). */
38
- export declare function createArtifactsManifest(options?: ArtifactsManifestOptions): AppMcpProviderManifest;
39
- export interface ArtifactsApp {
40
- handle(request: HttpRequest): Promise<HttpResponse>;
41
- }
27
+ export type ArtifactsManifest = AppMcpProviderManifest & {
28
+ audit: AppMcpProviderManifest['audit'] & {
29
+ artifactsProfileDigest: string;
30
+ };
31
+ };
32
+ export declare function createArtifactsManifest(options: ArtifactsManifestOptions): ArtifactsManifest;
33
+ export type ArtifactsApp = Pick<ArtifactApp, 'execute' | 'selectedCapabilities' | 'profileDigest'>;
42
34
  export interface ArtifactsInvokeInput {
43
35
  tool: string;
44
- actorId: string;
45
- /** Trusted host scope; tool arguments cannot select storage. */
46
- storageScopeId?: string;
47
- args: Record<string, unknown>;
36
+ invocation: ArtifactInvocationContext;
37
+ args: unknown;
38
+ idempotencyKey?: string;
48
39
  }
49
- /** Create the 15-tool executor over any conforming artifacts HTTP app. */
50
40
  export declare function createArtifactsToolExecutor(deps: {
51
41
  app: ArtifactsApp;
52
- defaultStorageScopeId?: string;
42
+ profile: CompiledArtifactsProfile;
43
+ mapHostError?: ArtifactHostFailureMapper;
53
44
  }): {
45
+ execute: (input: ArtifactsInvokeInput) => Promise<ArtifactOutcome<HttpResponse>>;
54
46
  invoke(input: ArtifactsInvokeInput): Promise<HttpResponse>;
55
47
  };
56
48
  export interface ArtifactsAdapterOptions extends ArtifactsManifestOptions {
49
+ /** Trusted mapping for failures in the MCP host binding. */
50
+ mapHostError?: ArtifactHostFailureMapper;
57
51
  app: ArtifactsApp;
58
- defaultStorageScopeId?: string;
52
+ /** Bind verified principal/scope and request authorization in trusted host code. */
53
+ createInvocation(request: AppToolInvocation): ArtifactInvocationContext | Promise<ArtifactInvocationContext>;
59
54
  }
60
- /** Create the platform adapter driving the 15 tools over the shared app. */
61
55
  export declare function createArtifactsAdapter(options: ArtifactsAdapterOptions): AppConnectorProviderAdapter;
62
- export {};
package/dist/mcp-core.js CHANGED
@@ -1,272 +1,111 @@
1
- import { TEXT_MIME_PATTERN } from './http.js';
1
+ import { assertArtifactsTransportProfile } from './http.js';
2
+ import { ARTIFACT_CAPABILITY_NAMES, ArtifactProfileInputError } from './profile/index.js';
3
+ import { ArtifactOperationError, classifyArtifactFailure } from './server/failures.js';
4
+ import { projectArtifactHttpOutcome, projectArtifactMcpOutcome } from './server/outcome-projection.js';
5
+ export * from './profile/index.js';
6
+ export { createRadarKeyDerivation } from './server/command-identity.js';
2
7
  export const ARTIFACTS_PROVIDER_ID = 'sentropic.artifacts';
3
8
  export const ARTIFACTS_APP_ID = 'sentropic-artifacts';
4
- export const ARTIFACTS_PROVIDER_VERSION = '0.1.4';
5
- export const ARTIFACTS_TOOL_NAMES = [
6
- 'artifacts_get_template', 'artifacts_get_api_guide', 'artifacts_ensure',
7
- 'artifacts_publish', 'artifacts_create_upload', 'artifacts_commit_upload',
8
- 'artifacts_list', 'artifacts_get', 'artifacts_get_source', 'artifacts_fork',
9
- 'artifacts_set_access', 'artifacts_promote', 'artifacts_renew',
10
- 'artifacts_delete', 'artifacts_restore',
11
- ];
12
- const str = { type: 'string', minLength: 1 };
13
- const int = (minimum, maximum) => ({
14
- type: 'integer', minimum, ...(maximum === undefined ? {} : { maximum }),
15
- });
16
- const obj = (properties, required = []) => ({
17
- type: 'object', properties, required, additionalProperties: false,
18
- });
19
- const record = {
20
- type: 'object',
21
- additionalProperties: { type: ['string', 'number', 'boolean', 'null'] },
22
- };
23
- const fileBytes = { type: 'array', items: int(0, 255) };
24
- const fileEntry = {
25
- ...obj({ path: str, mimeType: str, bytes: fileBytes, text: { type: 'string', description: 'UTF-8 text; mutually exclusive with bytes; text MIME types only.' } }, ['path', 'mimeType']),
26
- oneOf: [
27
- { required: ['bytes'], not: { required: ['text'] } },
28
- { required: ['text'], not: { required: ['bytes'] }, properties: { mimeType: { pattern: TEXT_MIME_PATTERN } } },
29
- ],
30
- };
31
- const sourceRef = obj({
32
- kind: { type: 'string', enum: ['inline-original', 'base-commit', 'recipe'] },
33
- commit: { type: 'string' },
34
- steps: { type: 'array', items: { type: 'string' } },
35
- }, ['kind']);
36
- const idOnly = obj({ artifactId: str }, ['artifactId']);
37
- export const ARTIFACTS_TOOL_SPECS = [
38
- { name: 'artifacts_get_template', description: 'Get an approved template kit (sources + build recipe).', inputSchema: obj({ templateId: str }, ['templateId']), mutability: 'read-only' },
39
- { name: 'artifacts_get_api_guide', description: 'Get the versioned artifacts API guide.', inputSchema: obj({ version: str }), mutability: 'read-only' },
40
- { name: 'artifacts_ensure', description: 'Resolve or create the private artifact view for a binding.', inputSchema: obj({ bindingName: str, storageScopeId: str, idempotencyKey: str, paramsHash: str, parameters: record }, ['bindingName', 'idempotencyKey']), mutability: 'state-transition', idempotent: true },
41
- { name: 'artifacts_publish', description: 'Publish inline files as a new release.', inputSchema: obj({ artifactId: str, files: { type: 'array', minItems: 1, items: fileEntry }, profile: record, source: sourceRef, idempotencyKey: str, paramsHash: str }, ['artifactId', 'files']), mutability: 'append', idempotent: true },
42
- { name: 'artifacts_create_upload', description: 'Open a bounded upload session for large bytes.', inputSchema: obj({ artifactId: str, mimeType: str, maxSize: int(1) }, ['artifactId', 'mimeType']), mutability: 'state-transition' },
43
- { name: 'artifacts_commit_upload', description: 'Verify staged bytes and publish them as a release.', inputSchema: obj({ artifactId: str, uploadId: str, path: str, mimeType: str, digest: str, size: int(0), profile: record, idempotencyKey: str, paramsHash: str }, ['artifactId', 'uploadId', 'path', 'mimeType', 'digest', 'size']), mutability: 'append', idempotent: true },
44
- { name: 'artifacts_list', description: 'List artifacts visible to the caller (ACL before pagination).', inputSchema: obj({ limit: int(1, 100), offset: int(0) }), mutability: 'read-only' },
45
- { name: 'artifacts_get', description: 'Get artifact metadata (grants visible to the owner only).', inputSchema: idOnly, mutability: 'read-only' },
46
- { name: 'artifacts_get_source', description: 'Export the attached source reference (separate right).', inputSchema: idOnly, mutability: 'read-only' },
47
- { name: 'artifacts_fork', description: 'Fork privately under a fresh identity (no ACL copied).', inputSchema: idOnly, mutability: 'state-transition' },
48
- { name: 'artifacts_set_access', description: 'Grant or revoke read/source access, or toggle public mode.', inputSchema: obj({ artifactId: str, action: { type: 'string', enum: ['grant', 'revoke', 'set-public'] }, principalId: str, grant: { type: 'string', enum: ['read', 'source'] }, isPublic: { type: 'boolean' } }, ['artifactId', 'action']), mutability: 'patch', confirm: true },
49
- { name: 'artifacts_promote', description: 'Select a release explicitly (expected-current CAS).', inputSchema: obj({ artifactId: str, releaseId: str, expectedCurrent: str }, ['artifactId', 'releaseId', 'expectedCurrent']), mutability: 'patch' },
50
- { name: 'artifacts_renew', description: 'Renew the artifact retention timestamp.', inputSchema: idOnly, mutability: 'patch' },
51
- { name: 'artifacts_delete', description: 'Withdraw into pending deletion (undo via artifacts_restore).', inputSchema: idOnly, mutability: 'delete', confirm: true },
52
- { name: 'artifacts_restore', description: 'Cancel a pending deletion or withdrawal before purge.', inputSchema: idOnly, mutability: 'state-transition' },
53
- ];
54
- /** Kernel provider parameter schema (typed non-secret host settings). */
55
- export const ARTIFACTS_PARAMETER_SCHEMA = {
56
- properties: {
57
- defaultStorageScopeId: { type: 'string', minLength: 1, description: 'Fallback storage scope for artifacts_ensure.' },
58
- uploadBudgetBytes: { type: 'integer', minimum: 1, description: 'Declared bounded-upload budget.' },
59
- },
60
- required: [],
61
- };
62
- /** Build the sentropic.artifacts manifest (host scope bound pre-freeze). */
63
- export function createArtifactsManifest(options = {}) {
9
+ export const ARTIFACTS_PROVIDER_VERSION = '0.2.1';
10
+ export const ARTIFACTS_TOOL_NAMES = ARTIFACT_CAPABILITY_NAMES;
11
+ export const ARTIFACTS_TOOL_SPECS = ARTIFACTS_TOOL_NAMES.map((name) => ({ name,
12
+ description: `Artifacts ${name.slice('artifacts_'.length).replaceAll('_', ' ')}.`,
13
+ 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
+ }));
16
+ export const ARTIFACTS_PARAMETER_SCHEMA = { properties: {}, required: [] };
17
+ export function createArtifactsManifest(options) {
18
+ assertArtifactsTransportProfile(options?.profile);
19
+ const { profile } = options;
64
20
  const scopes = options.invocationScope === undefined ? [] : [options.invocationScope];
65
- const tools = ARTIFACTS_TOOL_SPECS.map((spec) => ({
66
- kind: 'tool',
67
- name: spec.name,
68
- description: spec.description,
69
- requiredScopes: [...scopes],
70
- requiredClaims: [],
71
- inputSchema: spec.inputSchema,
72
- outputSchema: { type: 'object' },
73
- redactionClass: 'low',
74
- mutability: spec.mutability,
75
- mutatesExternalSystem: spec.mutability !== 'read-only',
76
- idempotency: spec.idempotent === true
77
- ? { required: true, scope: 'principal' }
78
- : { required: false },
79
- gates: {
80
- requiresElicitation: false,
81
- requiresHumanConfirmation: spec.confirm === true,
82
- requiresPrincipalGate: false,
83
- },
84
- }));
85
21
  return {
86
- appId: ARTIFACTS_APP_ID,
87
- providerId: ARTIFACTS_PROVIDER_ID,
88
- version: ARTIFACTS_PROVIDER_VERSION,
22
+ appId: ARTIFACTS_APP_ID, providerId: ARTIFACTS_PROVIDER_ID, version: ARTIFACTS_PROVIDER_VERSION,
89
23
  displayName: options.displayName ?? 'Sentropic Artifacts',
90
- resources: [],
91
- tools,
92
- prompts: [],
93
- authz: { requiredClaims: [], scopes: [...scopes], tenantResolution: 'custom-resolver' },
94
- audit: { eventKinds: ['invoke'] },
95
- durability: {},
24
+ resources: [], prompts: [],
25
+ tools: ARTIFACTS_TOOL_SPECS.filter((spec) => profile.selectedCapabilities.includes(spec.name)).map((spec) => ({
26
+ kind: 'tool', name: spec.name,
27
+ description: spec.description,
28
+ inputSchema: profile.schemas[spec.name], outputSchema: { type: 'object' },
29
+ requiredScopes: [...scopes], requiredClaims: [], redactionClass: 'low',
30
+ mutability: spec.mutability, mutatesExternalSystem: spec.mutability !== 'read-only',
31
+ idempotency: profile.idempotency[spec.name] ?? { required: false },
32
+ gates: { requiresElicitation: false, requiresHumanConfirmation: false, requiresPrincipalGate: false },
33
+ })),
34
+ authz: { requiredClaims: [], scopes, tenantResolution: 'custom-resolver' },
35
+ // The existing manifest codec includes audit metadata, including for an empty tool set.
36
+ audit: { eventKinds: ['invoke'], artifactsProfileDigest: profile.profileDigest }, durability: {},
96
37
  };
97
38
  }
98
- function needString(args, name) {
99
- const value = args[name];
100
- if (typeof value !== 'string' || value.length === 0) {
101
- throw new Error(`invalid request: missing ${name}`);
102
- }
103
- return value;
104
- }
105
- function optString(args, name) {
106
- const value = args[name];
107
- return typeof value === 'string' && value.length !== 0 ? value : undefined;
39
+ function rejected(cause, invocationId, operation, mapHostError) {
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) : mapHostError?.(error)) };
108
42
  }
109
- /** Create the 15-tool executor over any conforming artifacts HTTP app. */
110
43
  export function createArtifactsToolExecutor(deps) {
111
- const call = (actorId, method, path, body, storageScopeId) => deps.app.handle({
112
- method, path, actorId, body, storageScopeId: storageScopeId ?? deps.defaultStorageScopeId,
113
- });
114
- function setAccess(actorId, args) {
115
- const artifactId = needString(args, 'artifactId');
116
- const action = needString(args, 'action');
117
- if (action === 'grant') {
118
- const grant = needString(args, 'grant');
119
- if (grant !== 'read' && grant !== 'source') {
120
- throw new Error('invalid request: grant must be read or source');
121
- }
122
- return call(actorId, 'POST', '/access/grant', {
123
- artifactId, principalId: needString(args, 'principalId'), grant,
124
- });
125
- }
126
- if (action === 'revoke') {
127
- return call(actorId, 'POST', '/access/revoke', {
128
- artifactId, principalId: needString(args, 'principalId'),
129
- });
130
- }
131
- if (action === 'set-public') {
132
- if (typeof args['isPublic'] !== 'boolean') {
133
- throw new Error('invalid request: isPublic must be a boolean');
134
- }
135
- return call(actorId, 'POST', '/access/public', { artifactId, isPublic: args['isPublic'] });
136
- }
137
- throw new Error(`invalid request: unknown set-access action ${action}`);
44
+ assertArtifactsTransportProfile(deps.profile);
45
+ const profile = deps.profile;
46
+ if (deps.app.profileDigest !== profile.profileDigest ||
47
+ JSON.stringify(deps.app.selectedCapabilities) !== JSON.stringify(profile.selectedCapabilities)) {
48
+ throw new Error('artifacts app/profile mismatch');
138
49
  }
139
- return {
140
- async invoke(input) {
141
- const { actorId, args } = input;
50
+ async function execute(input) {
51
+ try {
52
+ const args = profile.validate(input.tool, input.args, input.idempotencyKey);
53
+ let request;
142
54
  switch (input.tool) {
143
- case 'artifacts_get_template':
144
- return call(actorId, 'GET', `/templates/${needString(args, 'templateId')}`);
145
- case 'artifacts_get_api_guide': {
146
- const version = optString(args, 'version');
147
- return call(actorId, 'GET', version === undefined ? '/api-guide' : `/api-guide/${version}`);
148
- }
149
55
  case 'artifacts_ensure':
150
- return call(actorId, 'POST', '/ensure', {
151
- bindingName: needString(args, 'bindingName'),
152
- idempotencyKey: needString(args, 'idempotencyKey'),
153
- paramsHash: optString(args, 'paramsHash') ?? '',
154
- parameters: args['parameters'] ?? {},
155
- }, input.storageScopeId);
56
+ request = { method: 'POST', path: '/ensure', body: args };
57
+ break;
156
58
  case 'artifacts_publish':
157
- return call(actorId, 'POST', '/publish', {
158
- artifactId: needString(args, 'artifactId'),
159
- files: args['files'],
160
- profile: args['profile'] ?? {},
161
- source: args['source'],
162
- idempotencyKey: optString(args, 'idempotencyKey'),
163
- paramsHash: optString(args, 'paramsHash'),
164
- });
165
- case 'artifacts_create_upload':
166
- return call(actorId, 'POST', '/uploads', {
167
- artifactId: needString(args, 'artifactId'),
168
- mimeType: needString(args, 'mimeType'),
169
- maxSize: args['maxSize'],
170
- });
171
- case 'artifacts_commit_upload':
172
- return call(actorId, 'POST', '/uploads/commit', {
173
- artifactId: needString(args, 'artifactId'),
174
- uploadId: needString(args, 'uploadId'),
175
- path: needString(args, 'path'),
176
- mimeType: needString(args, 'mimeType'),
177
- digest: needString(args, 'digest'),
178
- size: args['size'],
179
- profile: args['profile'] ?? {},
180
- idempotencyKey: optString(args, 'idempotencyKey'),
181
- paramsHash: optString(args, 'paramsHash'),
182
- });
59
+ request = { method: 'POST', path: '/publish', body: args };
60
+ break;
183
61
  case 'artifacts_list':
184
- return call(actorId, 'POST', '/list', { limit: args['limit'], offset: args['offset'] });
62
+ request = { method: 'POST', path: '/list', body: args };
63
+ break;
185
64
  case 'artifacts_get':
186
- return call(actorId, 'GET', `/meta/${needString(args, 'artifactId')}`);
187
- case 'artifacts_get_source':
188
- return call(actorId, 'GET', `/source/${needString(args, 'artifactId')}`);
189
- case 'artifacts_fork':
190
- return call(actorId, 'POST', '/fork', { artifactId: needString(args, 'artifactId') });
191
- case 'artifacts_set_access':
192
- return setAccess(actorId, args);
193
- case 'artifacts_promote':
194
- return call(actorId, 'POST', '/promote', {
195
- artifactId: needString(args, 'artifactId'),
196
- releaseId: needString(args, 'releaseId'),
197
- expectedCurrent: needString(args, 'expectedCurrent'),
198
- });
199
- case 'artifacts_renew':
200
- return call(actorId, 'POST', '/renew', { artifactId: needString(args, 'artifactId') });
201
- case 'artifacts_delete':
202
- return call(actorId, 'POST', '/delete', { artifactId: needString(args, 'artifactId') });
203
- case 'artifacts_restore':
204
- return call(actorId, 'POST', '/restore', { artifactId: needString(args, 'artifactId') });
205
- default:
206
- throw new Error(`unknown artifacts tool: ${input.tool}`);
65
+ request = { method: 'GET', path: `/meta/${args.artifactId}`, body: undefined };
66
+ break;
67
+ default: throw new ArtifactProfileInputError(true);
207
68
  }
208
- },
209
- };
210
- }
211
- function errorCodeOf(body) {
212
- if (typeof body === 'object' && body !== null) {
213
- const code = body['error'];
214
- if (typeof code === 'string' && code.length !== 0)
215
- return code;
69
+ return await deps.app.execute({ ...request, actorId: input.invocation.actorId,
70
+ storageScopeId: input.invocation.storageScopeId, idempotencyKey: input.idempotencyKey }, input.invocation);
71
+ }
72
+ catch (cause) {
73
+ return rejected(cause, input.invocation?.invocationId ?? '', input.tool, deps.mapHostError);
74
+ }
216
75
  }
217
- return 'internal_error';
76
+ return { execute, async invoke(input) {
77
+ return projectArtifactHttpOutcome(await execute(input));
78
+ } };
218
79
  }
219
- /** Create the platform adapter driving the 15 tools over the shared app. */
220
80
  export function createArtifactsAdapter(options) {
221
81
  const manifest = createArtifactsManifest(options);
222
- const executor = createArtifactsToolExecutor({
223
- app: options.app,
224
- defaultStorageScopeId: options.defaultStorageScopeId,
225
- });
82
+ if (typeof options.createInvocation !== 'function')
83
+ throw new Error('artifacts requires a trusted invocation factory');
84
+ const executor = createArtifactsToolExecutor(options);
226
85
  return {
227
- appId: ARTIFACTS_APP_ID,
228
- connectorId: ARTIFACTS_PROVIDER_ID,
229
- manifest,
230
- resolveTenant: (input) => Promise.resolve({
231
- principalRef: input.principalSub,
232
- tenantRef: input.tenantRef,
86
+ appId: ARTIFACTS_APP_ID, connectorId: ARTIFACTS_PROVIDER_ID, manifest,
87
+ resolveTenant: async (input) => ({ principalRef: input.principalSub, tenantRef: input.tenantRef,
233
88
  connectorInstanceId: input.connectorInstanceId,
234
- ...(input.workspaceRef === undefined ? {} : { workspaceRef: input.workspaceRef }),
235
- }),
236
- listCapabilities: () => Promise.resolve([...manifest.tools]),
237
- async invokeTool(req) {
238
- const auditId = req.ctx.auditId;
89
+ ...(input.workspaceRef === undefined ? {} : { workspaceRef: input.workspaceRef }) }),
90
+ listCapabilities: async () => [...manifest.tools],
91
+ async invokeTool(request) {
92
+ let outcome;
239
93
  try {
240
- const args = typeof req.input === 'object' && req.input !== null && !Array.isArray(req.input)
241
- ? req.input
242
- : {};
243
- const res = await executor.invoke({
244
- tool: req.capabilityRef,
245
- actorId: req.ctx.principal.sub,
246
- args,
247
- });
248
- if (res.status >= 200 && res.status < 300) {
249
- return { ok: true, output: res.body, auditId, redactionClass: 'low' };
250
- }
251
- const code = errorCodeOf(res.body);
252
- return {
253
- ok: false, auditId, redactionClass: 'low',
254
- error: { code, message: code, retriable: res.status >= 500 },
255
- };
94
+ // Selection and closed input checks precede even the host invocation binding.
95
+ options.profile.validate(request.capabilityRef, request.input, request.idempotencyKey);
96
+ const invocation = await options.createInvocation(request);
97
+ if (invocation.actorId !== request.ctx.principal.sub)
98
+ throw new ArtifactProfileInputError();
99
+ outcome = await executor.execute({ tool: request.capabilityRef, args: request.input,
100
+ invocation, idempotencyKey: request.idempotencyKey });
256
101
  }
257
- catch {
258
- return {
259
- ok: false, auditId, redactionClass: 'low',
260
- error: { code: 'invalid_request', message: 'invalid_request', retriable: false },
261
- };
102
+ catch (cause) {
103
+ outcome = rejected(cause, request.ctx.requestId, request.capabilityRef, options.mapHostError);
262
104
  }
105
+ return projectArtifactMcpOutcome(outcome, request.ctx.auditId);
263
106
  },
264
- readResource: (req) => Promise.resolve({
265
- ok: false,
266
- auditId: req.ctx.auditId,
267
- redactionClass: 'low',
268
- error: { code: 'not_found', message: 'not_found', retriable: false },
269
- }),
270
- validateSecrets: () => Promise.resolve([]),
107
+ readResource: async (request) => ({ ok: false, auditId: request.ctx.auditId, redactionClass: 'low',
108
+ error: { code: 'not_found', message: 'not_found', retriable: false } }),
109
+ validateSecrets: async () => [],
271
110
  };
272
111
  }
package/dist/mcp.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /** Platform registry wrapper. Use /mcp-core when the platform is absent. */
2
2
  export * from './mcp-core.js';
3
3
  import { computeManifestDigest } from '@sentropic/mcp-platform/tool-configuration';
4
- import { createArtifactsAdapter, ARTIFACTS_TOOL_SPECS, ARTIFACTS_PARAMETER_SCHEMA, } from './mcp-core.js';
4
+ import { createArtifactsAdapter, ARTIFACTS_PARAMETER_SCHEMA, } from './mcp-core.js';
5
5
  const EMPTY_TOOL_CONFIG = { type: 'object', properties: {}, additionalProperties: false };
6
6
  /** Build the kernel registry definition for sentropic.artifacts (§3). */
7
7
  export function createArtifactsRegistryDefinition(options) {
@@ -9,10 +9,10 @@ export function createArtifactsRegistryDefinition(options) {
9
9
  const manifest = adapter.manifest;
10
10
  const handlers = {};
11
11
  const toolValues = {};
12
- for (const spec of ARTIFACTS_TOOL_SPECS) {
13
- const handlerId = `${spec.name}-handler`;
14
- handlers[handlerId] = { handlerId, capabilityRef: spec.name, configSchema: EMPTY_TOOL_CONFIG };
15
- toolValues[spec.name] = { handlerId, values: {} };
12
+ for (const tool of manifest.tools) {
13
+ const handlerId = `${tool.name}-handler`;
14
+ handlers[handlerId] = { handlerId, capabilityRef: tool.name, configSchema: EMPTY_TOOL_CONFIG };
15
+ toolValues[tool.name] = { handlerId, values: {} };
16
16
  }
17
17
  return {
18
18
  manifest,
@@ -0,0 +1,67 @@
1
+ import type { JsonValue } from '../contracts.js';
2
+ import type { EffectiveArtifactCommand } from '../server/command-identity.js';
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"];
5
+ export type ArtifactCapabilityName = (typeof ARTIFACT_CAPABILITY_NAMES)[number];
6
+ export type ArtifactSchema = Readonly<Record<string, unknown>>;
7
+ export interface OperationProfile {
8
+ readonly operation: string;
9
+ readonly limits: Record<string, number>;
10
+ readonly defaults: Record<string, JsonValue | undefined>;
11
+ }
12
+ export interface ArtifactProfileRules {
13
+ readonly lengthUnit: 'utf16';
14
+ readonly textEncoding: 'utf8';
15
+ readonly callerKeyNormalization: 'none';
16
+ readonly rejectLoneSurrogates: true;
17
+ readonly provenance: 'inline-original';
18
+ readonly keyDerivationAlgorithm: string;
19
+ readonly pathMaxBytes: number;
20
+ readonly forbiddenPathPattern: string;
21
+ readonly reservedFirstSegments: readonly string[];
22
+ readonly mimeTypes: readonly string[];
23
+ }
24
+ export interface ArtifactProviderProfile {
25
+ readonly version: typeof PROFILE_CONTRACT_VERSION;
26
+ readonly selectedCapabilities: readonly string[];
27
+ readonly operations: readonly OperationProfile[];
28
+ readonly contractRevisions: Record<string, string>;
29
+ /** Omission selects the fixed v1 semantics, never a capability selection. */
30
+ readonly rules?: ArtifactProfileRules;
31
+ }
32
+ export interface EffectiveArtifactProfile extends ArtifactProviderProfile {
33
+ readonly rules: ArtifactProfileRules;
34
+ }
35
+ export interface CompiledArtifactsProfile extends EffectiveArtifactProfile {
36
+ readonly selectedCapabilities: readonly ArtifactCapabilityName[];
37
+ readonly profile: EffectiveArtifactProfile;
38
+ readonly profileDigest: string;
39
+ readonly validators: CompiledValidators;
40
+ readonly schemas: Readonly<Partial<Record<ArtifactCapabilityName, ArtifactSchema>>>;
41
+ readonly idempotency: Readonly<Partial<Record<ArtifactCapabilityName, {
42
+ readonly required: true;
43
+ readonly scope: 'principal';
44
+ }>>>;
45
+ validate(name: string, args: unknown, envelopeKey?: string): Readonly<Record<string, JsonValue>>;
46
+ assertSelected(name: string): asserts name is ArtifactCapabilityName;
47
+ capabilityForRoute(method: string, path: string): ArtifactCapabilityName | undefined;
48
+ }
49
+ export interface CompiledValidators {
50
+ readonly validateBindingName: (value: unknown) => boolean;
51
+ readonly validateCallerKey: (value: unknown) => boolean;
52
+ readonly validateFileEntry: (entry: unknown) => boolean;
53
+ readonly normalizeCommand: (command: unknown) => EffectiveArtifactCommand;
54
+ }
55
+ /** Projection fields are data-only; invocation correlation is added by L6. */
56
+ export declare class ArtifactProfileInputError extends Error {
57
+ readonly kind: 'operational' | 'ordinary';
58
+ readonly status: number;
59
+ readonly httpStatus: number;
60
+ readonly variant: 'unselected_operation' | 'invalid_request';
61
+ readonly publicCode: string;
62
+ readonly category: 'invalid_request' | 'capability_unselected';
63
+ readonly retriable = false;
64
+ readonly phase: 'selection' | 'entry';
65
+ readonly commitState = "not_committed";
66
+ constructor(unselected?: boolean);
67
+ }
@@ -0,0 +1,31 @@
1
+ export const PROFILE_CONTRACT_VERSION = 'artifacts-profile/v1';
2
+ export const ARTIFACT_CAPABILITY_NAMES = [
3
+ 'artifacts_get_template', 'artifacts_get_api_guide', 'artifacts_ensure',
4
+ 'artifacts_publish', 'artifacts_create_upload', 'artifacts_commit_upload',
5
+ 'artifacts_list', 'artifacts_get', 'artifacts_get_source', 'artifacts_fork',
6
+ 'artifacts_set_access', 'artifacts_promote', 'artifacts_renew',
7
+ 'artifacts_delete', 'artifacts_restore',
8
+ ];
9
+ /** Projection fields are data-only; invocation correlation is added by L6. */
10
+ export class ArtifactProfileInputError extends Error {
11
+ kind;
12
+ status;
13
+ httpStatus;
14
+ variant;
15
+ publicCode;
16
+ category;
17
+ retriable = false;
18
+ phase;
19
+ commitState = 'not_committed';
20
+ constructor(unselected = false) {
21
+ super(unselected ? 'capability_unavailable' : 'invalid_request');
22
+ this.name = 'ArtifactProfileInputError';
23
+ this.kind = unselected ? 'operational' : 'ordinary';
24
+ this.status = unselected ? 503 : 400;
25
+ this.httpStatus = this.status;
26
+ this.variant = unselected ? 'unselected_operation' : 'invalid_request';
27
+ this.publicCode = this.message;
28
+ this.category = unselected ? 'capability_unselected' : 'invalid_request';
29
+ this.phase = unselected ? 'selection' : 'entry';
30
+ }
31
+ }
@@ -0,0 +1,6 @@
1
+ import { type ArtifactProviderProfile, type CompiledArtifactsProfile } from './contracts.js';
2
+ export * from './contracts.js';
3
+ export { RADAR_ARTIFACTS_PROFILE } from './radar.js';
4
+ export declare function assertCompiledArtifactsProfile(value: unknown): asserts value is CompiledArtifactsProfile;
5
+ /** Compile once, before any registry, adapter, schema or integrity evidence. */
6
+ export declare function compileArtifactsProfile(input: ArtifactProviderProfile): CompiledArtifactsProfile;