@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.
- package/README.md +17 -0
- package/dist/adapters/postgres-commit.d.ts +15 -0
- package/dist/adapters/postgres-commit.js +80 -0
- package/dist/adapters/postgres.d.ts +5 -2
- package/dist/adapters/postgres.js +39 -0
- package/dist/http.d.ts +41 -30
- package/dist/http.js +168 -507
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/mcp-core.d.ts +29 -39
- package/dist/mcp-core.js +83 -244
- package/dist/mcp.js +5 -5
- package/dist/profile/contracts.d.ts +67 -0
- package/dist/profile/contracts.js +31 -0
- package/dist/profile/index.d.ts +6 -0
- package/dist/profile/index.js +117 -0
- package/dist/profile/json.d.ts +10 -0
- package/dist/profile/json.js +76 -0
- package/dist/profile/radar.d.ts +3 -0
- package/dist/profile/radar.js +21 -0
- package/dist/profile/schemas.d.ts +6 -0
- package/dist/profile/schemas.js +76 -0
- package/dist/profile/validation.d.ts +5 -0
- package/dist/profile/validation.js +104 -0
- package/dist/profile/validators.d.ts +3 -0
- package/dist/profile/validators.js +51 -0
- package/dist/server/authorized-replay.d.ts +33 -0
- package/dist/server/authorized-replay.js +31 -0
- package/dist/server/command-identity.d.ts +52 -0
- package/dist/server/command-identity.js +71 -0
- package/dist/server/commit/brands.d.ts +7 -0
- package/dist/server/commit/brands.js +3 -0
- package/dist/server/commit/coordinator.d.ts +2 -0
- package/dist/server/commit/coordinator.js +729 -0
- package/dist/server/commit/types.d.ts +379 -0
- package/dist/server/commit/types.js +94 -0
- package/dist/server/commit-contract.d.ts +4 -0
- package/dist/server/commit-contract.js +2 -0
- package/dist/server/failures.d.ts +84 -0
- package/dist/server/failures.js +133 -0
- package/dist/server/gc.d.ts +42 -1
- package/dist/server/gc.js +168 -5
- package/dist/server/idempotency.d.ts +4 -0
- package/dist/server/idempotency.js +14 -0
- package/dist/server/lifecycle.d.ts +2 -1
- package/dist/server/lifecycle.js +15 -1
- package/dist/server/object-intents.d.ts +14 -1
- package/dist/server/object-intents.js +31 -0
- package/dist/server/outcome-contract.d.ts +3 -0
- package/dist/server/outcome-contract.js +3 -0
- package/dist/server/outcome-projection.d.ts +21 -0
- package/dist/server/outcome-projection.js +29 -0
- package/dist/server/preparation-cleanup.d.ts +31 -0
- package/dist/server/preparation-cleanup.js +106 -0
- package/dist/server/publication.d.ts +76 -0
- package/dist/server/publication.js +270 -31
- package/dist/server/service.d.ts +60 -0
- package/dist/server/service.js +181 -0
- package/dist/testing.d.ts +50 -0
- package/dist/testing.js +86 -0
- package/docs/migration-l6.md +129 -0
- package/examples/radar-commit-setup.ts +299 -0
- package/l0-freeze/command-identity-contract.ts +303 -0
- package/l0-freeze/commit-contract.ts +421 -0
- package/l0-freeze/failure-contract.ts +174 -0
- package/l0-freeze/profile-contract.ts +219 -0
- package/migrations/0003_coordinator.sql +28 -0
- package/package.json +27 -1
- 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 {
|
|
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
|
|
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
|
|
2
|
-
import type
|
|
3
|
-
import
|
|
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
|
|
6
|
-
import { type
|
|
7
|
-
|
|
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
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
30
|
-
blobs: PublicationBlobStore;
|
|
40
|
+
lifecycle: Pick<LifecycleService, 'stateOf' | 'canRead'>;
|
|
31
41
|
isApprovedAccount?: (principalId: string) => boolean;
|
|
32
|
-
/**
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
/**
|
|
45
|
-
export declare function
|
|
46
|
-
|
|
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;
|