@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/README.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # @sentropic/artifacts — generic artifact contracts (L-5a)
2
2
 
3
+ Version 0.2.1 fixes host commit authorization being masked by the built-in
4
+ publication guard and wires the optional trusted `mapHostError` dependency.
5
+ Publication retains its lifecycle, ACL and policy revision checks; the host
6
+ `authorizeCommit` binding decides account approval and its sanitized refusal code.
7
+ Custom services using `createGuardedPublicationService` may pass the request-bound
8
+ `authorizeCommit` in `publishFinalized(input)`. It runs after
9
+ lifecycle, ACL and policy revision checks. When omitted, publication keeps the
10
+ default `participant.checkApproval` check and denies unapproved or expired commits.
11
+
12
+ Bind the same mapper on `createHttpArtifactApp`, `createArtifactsToolExecutor`
13
+ (or `createArtifactsAdapter`) and `createArtifactCommitCoordinator` so host wrapper
14
+ errors are classified at their originating layer. For example:
15
+
16
+ ```ts
17
+ import { ArtifactOperationError, type ArtifactHostFailureMapper }
18
+ from '@sentropic/artifacts/server/outcome-contract';
19
+
20
+ const mapHostError: ArtifactHostFailureMapper = cause => {
21
+ const code = (cause as { code?: string } | null)?.code;
22
+ return code === 'artifact_s3_timeout' || code === 'artifact_connection_lost'
23
+ ? new ArtifactOperationError('provider_unavailable', { cause }) : undefined;
24
+ };
25
+ ```
26
+
27
+ Pass `{ ...existingOptions, mapHostError }` to each construction. The packed
28
+ `examples/radar-commit-setup.ts` also accepts this option. Unrecognized failures
29
+ keep their existing classification; private causes never enter HTTP/MCP results.
30
+ Frozen contracts, command identity and selected tools remain unchanged.
31
+
32
+
3
33
  Generic, host-independent artifact domain state for the Sentropic artifacts
4
34
  provider (`sentropic.artifacts`, spec-v8 §9), covering L-5a–5e. Version
5
35
  `0.1.2` adds UTF-8 text publication, exposes storage adapters and ships SQL migrations.
@@ -7,6 +37,23 @@ Version `0.1.1` fixed activation ticket release and channel binding (FL-ACT).
7
37
  Version `0.1.4` hardens host authority, replay isolation, publication and content
8
38
  responses, and adds the platform-free `@sentropic/artifacts/mcp-core` entry.
9
39
 
40
+ ## 0.2.0 contract line (L0 frozen)
41
+
42
+ The `0.2.0` line freezes the immo #861 declaration shapes only:
43
+ explicit capability selection and the effective publication profile
44
+ (`l0-freeze/profile-contract.ts`), the effective command identity
45
+ (`l0-freeze/command-identity-contract.ts`), the commit finalization unit
46
+ (`l0-freeze/commit-contract.ts`) and the typed failure outcome
47
+ (`l0-freeze/failure-contract.ts`). The section 3.2 host API names are frozen
48
+ against spec r2. Trusted scope/subject domain qualification and
49
+ actor-mapping acceptance remain gated on i-cond Q0; the structural freeze
50
+ is not a completed Q0-dependent qualification. L1+ promotes the shapes
51
+ into `src/`; version reservations are pack-only dev versions owned by the
52
+ integration owner, never registry candidates. The common-contract reuse map lives in
53
+ `docs/common-contract-map.md`; the migration inventory lives in
54
+ `docs/migration-inventory-l0.md`. L0 changes no behavior, no `src/` file
55
+ and no package version.
56
+
10
57
  ## Migrating to 0.1.4
11
58
 
12
59
  HTTP hosts must supply a trusted `HttpRequest.storageScopeId` for `/ensure`.
@@ -0,0 +1,15 @@
1
+ /**
2
+ * L3 (immo #861) — PostgreSQL finalization executor (spec r2 §3.1 A4).
3
+ *
4
+ * Owns every coordinator SQL statement against coordinator tables only
5
+ * (`artifacts.operation_keys`, `artifacts.operation_receipts`). No domain
6
+ * table, no policy table and no caller SQL here: host domain reads and
7
+ * writes arrive through the participant and the provider-internal
8
+ * transaction handle. Connections are raw pooled clients; the coordinator
9
+ * owns BEGIN/COMMIT/rollback/release. No nested pool lease spans S3 I/O:
10
+ * preparation happens before the coordinator acquires its client.
11
+ */
12
+ import type { Pool } from 'pg';
13
+ import type { CommitExecutor } from '../server/commit/types.js';
14
+ /** Create the PostgreSQL finalization executor over an injected pool (no DDL). */
15
+ export declare function createPostgresCommitExecutor(pool: Pool): CommitExecutor;
@@ -0,0 +1,80 @@
1
+ function toReceipt(row) {
2
+ return {
3
+ scopeId: row.storage_scope_id,
4
+ internalKey: row.internal_key,
5
+ commandHash: row.command_hash,
6
+ codecVersion: row.command_codec,
7
+ receipt: row.receipt,
8
+ createdAt: row.created_at.toISOString(),
9
+ };
10
+ }
11
+ class PgFinalizeConnection {
12
+ client;
13
+ released = false;
14
+ constructor(client) {
15
+ this.client = client;
16
+ }
17
+ async query(text, params) {
18
+ const result = await this.client.query(text, [...params]);
19
+ return { rows: result.rows, rowCount: result.rowCount ?? 0 };
20
+ }
21
+ async beginReadCommitted() {
22
+ await this.client.query('BEGIN ISOLATION LEVEL READ COMMITTED');
23
+ }
24
+ async lockOperationKey(scopeId, internalKey) {
25
+ await this.client.query(`SELECT 1 FROM artifacts.operation_keys
26
+ WHERE storage_scope_id = $1 AND internal_key = $2 FOR UPDATE`, [scopeId, internalKey]);
27
+ }
28
+ async readReceipt(scopeId, internalKey) {
29
+ const result = await this.client.query(`SELECT storage_scope_id, internal_key, command_codec, command_hash, receipt, created_at
30
+ FROM artifacts.operation_receipts
31
+ WHERE storage_scope_id = $1 AND internal_key = $2`, [scopeId, internalKey]);
32
+ const row = result.rows[0];
33
+ return row === undefined ? undefined : toReceipt(row);
34
+ }
35
+ async insertReceipt(receipt) {
36
+ await this.client.query(`INSERT INTO artifacts.operation_receipts
37
+ (storage_scope_id, internal_key, command_codec, command_hash, receipt)
38
+ VALUES ($1, $2, $3, $4, $5)`, [
39
+ receipt.scopeId,
40
+ receipt.internalKey,
41
+ receipt.codecVersion,
42
+ receipt.commandHash,
43
+ JSON.stringify(receipt.receipt),
44
+ ]);
45
+ }
46
+ async commit() {
47
+ await this.client.query('COMMIT');
48
+ }
49
+ async rollback() {
50
+ await this.client.query('ROLLBACK');
51
+ }
52
+ release(error) {
53
+ if (this.released)
54
+ throw new Error('finalization client already released');
55
+ this.released = true;
56
+ this.client.release(error === undefined ? undefined : error);
57
+ }
58
+ }
59
+ /** Create the PostgreSQL finalization executor over an injected pool (no DDL). */
60
+ export function createPostgresCommitExecutor(pool) {
61
+ return {
62
+ async ensureCoordinationRow(scopeId, internalKey) {
63
+ await pool.query(`INSERT INTO artifacts.operation_keys (storage_scope_id, internal_key)
64
+ VALUES ($1, $2) ON CONFLICT DO NOTHING`, [scopeId, internalKey]);
65
+ },
66
+ async beginFinalization() {
67
+ const client = await pool.connect();
68
+ try {
69
+ await client.query('BEGIN ISOLATION LEVEL READ COMMITTED');
70
+ }
71
+ catch (error) {
72
+ // Never hand out a client whose transaction failed to open: destroy
73
+ // it instead of leaking a leased connection the coordinator cannot see.
74
+ client.release(error);
75
+ throw error;
76
+ }
77
+ return new PgFinalizeConnection(client);
78
+ },
79
+ };
80
+ }
@@ -12,9 +12,12 @@ import type { AclStore } from '../server/access.js';
12
12
  import type { RefcountStore } from '../server/gc.js';
13
13
  import type { LifecycleStore } from '../server/lifecycle.js';
14
14
  import type { SourceStore } from '../server/sources.js';
15
- import type { IntentStore } from '../server/object-intents.js';
15
+ import type { PreparationIntentStore } from '../server/object-intents.js';
16
16
  import type { IdempotencyStore } from '../server/idempotency.js';
17
17
  import type { PublicationStateStore } from '../server/publication.js';
18
+ import type { KvPort } from '../server/ports.js';
19
+ /** Read-only migration inventory. Never promotes a legacy success into an authorized receipt. */
20
+ export declare function createPostgresLegacyArtifactRecords(pool: Pool): Pick<KvPort, 'get'>;
18
21
  export interface ArtifactBindingInput {
19
22
  storageScopeId: string;
20
23
  ownerId: string;
@@ -25,7 +28,7 @@ export interface ArtifactBinding {
25
28
  artifactId: string;
26
29
  created: boolean;
27
30
  }
28
- export interface PostgresArtifactBasics extends IntentStore {
31
+ export interface PostgresArtifactBasics extends PreparationIntentStore {
29
32
  ensureArtifact(input: ArtifactBindingInput): Promise<ArtifactBinding>;
30
33
  }
31
34
  export interface PostgresArtifactStore extends PostgresArtifactBasics, PublicationStateStore, IdempotencyStore, AclStore, SourceStore, LifecycleStore, RefcountStore {
@@ -1,3 +1,12 @@
1
+ /** Read-only migration inventory. Never promotes a legacy success into an authorized receipt. */
2
+ export function createPostgresLegacyArtifactRecords(pool) {
3
+ return {
4
+ async get(key) {
5
+ const { rows } = await pool.query('SELECT params_hash, result FROM artifacts.idempotency_keys WHERE key = $1', [key]);
6
+ return rows[0] === undefined ? undefined : JSON.stringify(rows[0]);
7
+ },
8
+ };
9
+ }
1
10
  function toIntent(row) {
2
11
  return {
3
12
  intentId: row.id,
@@ -71,6 +80,36 @@ export function createPostgresArtifactStore(pool) {
71
80
  FROM artifacts.object_intents WHERE state = 'pending' ORDER BY created_at`);
72
81
  return rows.map(toIntent);
73
82
  },
83
+ async listPreparationIntents(query = {}) {
84
+ const { rows } = await pool.query(`SELECT * FROM artifacts.object_intents WHERE kind = 'commit-release' AND state <> 'done'
85
+ AND payload ? 'cleanupDeadlineMs'
86
+ AND ($1::text IS NULL OR payload->>'artifactId' = $1)
87
+ AND ($2::numeric IS NULL OR (COALESCE(error, '') NOT LIKE 'invalid preparation metadata%'
88
+ AND
89
+ CASE WHEN jsonb_typeof(payload->'cleanupDeadlineMs') = 'number'
90
+ THEN (payload->>'cleanupDeadlineMs')::numeric ELSE 0 END <= $2
91
+ AND (payload->>'cleanupState' IS DISTINCT FROM 'failed-cleaned'
92
+ OR (payload->>'cleanupDeadlineMs')::numeric >= $2 - 60000)))
93
+ ORDER BY COALESCE(payload->>'cleanupState' = 'failed-cleaned', false),
94
+ CASE WHEN jsonb_typeof(payload->'cleanupDeadlineMs') = 'number'
95
+ THEN (payload->>'cleanupDeadlineMs')::numeric ELSE 0 END, id
96
+ LIMIT $3`, [query.artifactId ?? null, query.throughMs ?? null, query.limit ?? null]);
97
+ return rows.map(toIntent);
98
+ },
99
+ async claimPreparationCleanup(intentId, throughMs) {
100
+ // This UPDATE serializes with finalization's intent completion on the same row.
101
+ const { rows } = await pool.query(`UPDATE artifacts.object_intents SET state = 'failed',
102
+ error = COALESCE(error, 'preparation expired; explicit retry required'), updated_at = now()
103
+ WHERE id = $1 AND kind = 'commit-release' AND state <> 'done'
104
+ AND (payload->>'cleanupDeadlineMs')::numeric <= $2 RETURNING *`, [intentId, throughMs]);
105
+ return rows[0] === undefined ? undefined : toIntent(rows[0]);
106
+ },
107
+ async settlePreparationCleanup(intentId) {
108
+ const { rowCount } = await pool.query(`UPDATE artifacts.object_intents SET payload = payload || '{"cleanupState":"failed-cleaned"}'::jsonb,
109
+ updated_at = now() WHERE id = $1 AND state = 'failed'`, [intentId]);
110
+ if (rowCount === 0)
111
+ throw new Error('cleanup needs a failed preparation');
112
+ },
74
113
  async putRelease(release) {
75
114
  const parsed = JSON.parse(release.manifest);
76
115
  const client = await pool.connect();
package/dist/http.d.ts CHANGED
@@ -1,47 +1,60 @@
1
- import type { ParameterSchema } from './parameters.js';
2
- import type { ArtifactService } from './server/service.js';
3
- import type { PublicationBlobStore, PublicationService } from './server/publication.js';
1
+ import { type CompiledArtifactsProfile } from './profile/index.js';
2
+ import { type ArtifactKeyDerivationBinding } from './server/command-identity.js';
3
+ import { type GuardedEnsureHost } from './server/service.js';
4
+ import type { KvPort } from './server/ports.js';
5
+ import { type GuardedPublicationDeps, type PublicationService } from './server/publication.js';
6
+ import type { AclStore } from './server/access.js';
4
7
  import type { LifecycleService } from './server/lifecycle.js';
5
- import type { ForkService } from './server/fork.js';
6
- import { type AclStore } from './server/access.js';
7
- import { type SourceStore } from './server/sources.js';
8
- import type { StagingStore, UploadService } from './server/uploads.js';
9
- import type { TemplateCatalogPort } from './server/templates.js';
8
+ import { type ArtifactInvocationContext, type ArtifactCommitCoordinator } from './server/commit/types.js';
9
+ import { type ArtifactOutcome, type ArtifactHostFailureMapper } from './server/failures.js';
10
+ export { projectArtifactHttpOutcome } from './server/outcome-projection.js';
10
11
  export interface HttpRequest {
11
12
  method: string;
12
13
  path: string;
13
14
  body: unknown;
14
15
  actorId: string;
15
- /** Trusted host context, resolved alongside actorId; never copy from the body. */
16
+ /** Trusted host assertion, never caller authority. */
16
17
  storageScopeId?: string;
18
+ /** Optional platform envelope; must equal the tool argument byte for byte. */
19
+ idempotencyKey?: string;
17
20
  }
18
21
  export interface HttpResponse {
19
22
  status: number;
20
23
  body: unknown;
21
24
  }
25
+ /** Stable content-route error codes; private driver messages never leave the host. */
26
+ export declare function errorBody(status: number): {
27
+ error: string;
28
+ };
22
29
  export interface HttpAppDeps {
23
- parameterSchema: ParameterSchema;
24
- artifacts: ArtifactService;
25
- publication: PublicationService;
26
- lifecycle: LifecycleService;
27
- forks: ForkService;
30
+ /** Trusted mapping of host wrapper errors; causes remain private. Also bind on the coordinator. */
31
+ mapHostError?: ArtifactHostFailureMapper;
32
+ profile: CompiledArtifactsProfile;
33
+ coordinator: ArtifactCommitCoordinator;
34
+ derivation: ArtifactKeyDerivationBinding;
35
+ /** Explicit pilot-state inventory reader; supply an empty reader only for a verified fresh store. */
36
+ legacyRecords: Pick<KvPort, 'get'>;
37
+ ensureHost: GuardedEnsureHost;
38
+ publication: Omit<GuardedPublicationDeps, 'coordinator' | 'limits'> & {
39
+ getCurrent: PublicationService['getCurrent'];
40
+ };
28
41
  acls: AclStore;
29
- sources: SourceStore;
30
- blobs: PublicationBlobStore;
42
+ lifecycle: Pick<LifecycleService, 'stateOf' | 'canRead'>;
31
43
  isApprovedAccount?: (principalId: string) => boolean;
32
- /** Bounded upload sessions (L-5e); upload routes refuse when unwired. */
33
- uploads?: UploadService;
34
- staging?: StagingStore;
35
- /** Template kit + API guide catalog (L-5e); refuses when unwired. */
36
- templates?: TemplateCatalogPort;
44
+ /** One trusted URL policy shared by HTTP and MCP receipts. */
45
+ publicationUrls?: (artifactId: string, releaseId: string) => {
46
+ stableUrl: string;
47
+ pinnedUrl: string;
48
+ };
37
49
  }
38
- /** Stable error body for one status; internal messages never leave the app. */
39
- export declare function errorBody(status: number): {
40
- error: string;
41
- };
42
- /** Canonical text MIME types; parameters are omitted (encoding is always UTF-8). */
50
+ export interface ArtifactApp {
51
+ readonly selectedCapabilities: CompiledArtifactsProfile['selectedCapabilities'];
52
+ readonly profileDigest: string;
53
+ execute(request: HttpRequest, invocation: ArtifactInvocationContext): Promise<ArtifactOutcome<HttpResponse>>;
54
+ handle(request: HttpRequest, invocation: ArtifactInvocationContext): Promise<HttpResponse>;
55
+ }
56
+ /** Retained for consumers describing canonical textual publication inputs. */
43
57
  export declare const TEXT_MIME_PATTERN = "^(?:text/[a-z0-9!#$&^_.+-]+|application/(?:javascript|ecmascript|json|xml|[a-z0-9!#$&^_.+-]+\\+(?:json|xml))|image/svg\\+xml)(?![\\s\\S])";
44
- /** Create the HTTP app over injected services (no server, no fetch). */
45
- export declare function createHttpArtifactApp(deps: HttpAppDeps): {
46
- handle(request: HttpRequest): Promise<HttpResponse>;
47
- };
58
+ /** Other mutation surfaces need their own qualification before host activation. */
59
+ export declare function assertArtifactsTransportProfile(value: unknown): asserts value is CompiledArtifactsProfile;
60
+ export declare function createHttpArtifactApp(deps: HttpAppDeps): ArtifactApp;