@sentropic/artifacts 0.1.4 → 0.2.0

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 +17 -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 +41 -30
  7. package/dist/http.js +168 -507
  8. package/dist/index.d.ts +2 -0
  9. package/dist/index.js +2 -0
  10. package/dist/mcp-core.d.ts +29 -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 +379 -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 +76 -0
  56. package/dist/server/publication.js +270 -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 +129 -0
  62. package/examples/radar-commit-setup.ts +299 -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
@@ -7,6 +7,23 @@ Version `0.1.1` fixed activation ticket release and channel binding (FL-ACT).
7
7
  Version `0.1.4` hardens host authority, replay isolation, publication and content
8
8
  responses, and adds the platform-free `@sentropic/artifacts/mcp-core` entry.
9
9
 
10
+ ## 0.2.0 contract line (L0 frozen)
11
+
12
+ The `0.2.0` line freezes the immo #861 declaration shapes only:
13
+ explicit capability selection and the effective publication profile
14
+ (`l0-freeze/profile-contract.ts`), the effective command identity
15
+ (`l0-freeze/command-identity-contract.ts`), the commit finalization unit
16
+ (`l0-freeze/commit-contract.ts`) and the typed failure outcome
17
+ (`l0-freeze/failure-contract.ts`). The section 3.2 host API names are frozen
18
+ against spec r2. Trusted scope/subject domain qualification and
19
+ actor-mapping acceptance remain gated on i-cond Q0; the structural freeze
20
+ is not a completed Q0-dependent qualification. L1+ promotes the shapes
21
+ into `src/`; version reservations are pack-only dev versions owned by the
22
+ integration owner, never registry candidates. The common-contract reuse map lives in
23
+ `docs/common-contract-map.md`; the migration inventory lives in
24
+ `docs/migration-inventory-l0.md`. L0 changes no behavior, no `src/` file
25
+ and no package version.
26
+
10
27
  ## Migrating to 0.1.4
11
28
 
12
29
  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,58 @@
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 } 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
+ profile: CompiledArtifactsProfile;
31
+ coordinator: ArtifactCommitCoordinator;
32
+ derivation: ArtifactKeyDerivationBinding;
33
+ /** Explicit pilot-state inventory reader; supply an empty reader only for a verified fresh store. */
34
+ legacyRecords: Pick<KvPort, 'get'>;
35
+ ensureHost: GuardedEnsureHost;
36
+ publication: Omit<GuardedPublicationDeps, 'coordinator' | 'limits'> & {
37
+ getCurrent: PublicationService['getCurrent'];
38
+ };
28
39
  acls: AclStore;
29
- sources: SourceStore;
30
- blobs: PublicationBlobStore;
40
+ lifecycle: Pick<LifecycleService, 'stateOf' | 'canRead'>;
31
41
  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;
42
+ /** One trusted URL policy shared by HTTP and MCP receipts. */
43
+ publicationUrls?: (artifactId: string, releaseId: string) => {
44
+ stableUrl: string;
45
+ pinnedUrl: string;
46
+ };
37
47
  }
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). */
48
+ export interface ArtifactApp {
49
+ readonly selectedCapabilities: CompiledArtifactsProfile['selectedCapabilities'];
50
+ readonly profileDigest: string;
51
+ execute(request: HttpRequest, invocation: ArtifactInvocationContext): Promise<ArtifactOutcome<HttpResponse>>;
52
+ handle(request: HttpRequest, invocation: ArtifactInvocationContext): Promise<HttpResponse>;
53
+ }
54
+ /** Retained for consumers describing canonical textual publication inputs. */
43
55
  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
- };
56
+ /** Other mutation surfaces need their own qualification before host activation. */
57
+ export declare function assertArtifactsTransportProfile(value: unknown): asserts value is CompiledArtifactsProfile;
58
+ export declare function createHttpArtifactApp(deps: HttpAppDeps): ArtifactApp;