@sentropic/artifacts 0.1.3 → 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 +74 -4
- 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/content/handler.js +79 -63
- package/dist/http.d.ts +42 -29
- package/dist/http.js +168 -491
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/mcp-core.d.ts +52 -0
- package/dist/mcp-core.js +111 -0
- package/dist/mcp.d.ts +4 -60
- package/dist/mcp.js +7 -276
- 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-limits.d.ts +10 -0
- package/dist/server/publication-limits.js +38 -0
- package/dist/server/publication.d.ts +85 -1
- package/dist/server/publication.js +288 -31
- package/dist/server/service.d.ts +64 -1
- package/dist/server/service.js +187 -1
- package/dist/server/uploads.d.ts +4 -1
- package/dist/server/uploads.js +63 -35
- 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 +31 -1
- package/src/contracts.ts +125 -0
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* L0 frozen command-identity declaration (immo #861, ask 1 shape, spec r2 C5-C7).
|
|
3
|
+
*
|
|
4
|
+
* Stable data shapes for the effective command and its server-created
|
|
5
|
+
* identity, plus the frozen derivation fixtures: preimage construction,
|
|
6
|
+
* expected digests and ambiguous-split refusal. The command codec
|
|
7
|
+
* implementation lands in L1/L6 against the declared relations; domain
|
|
8
|
+
* qualification of trusted scope/subject mappings stays gated on Q0.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** Version of the frozen command-codec shape. */
|
|
12
|
+
export const COMMAND_CODEC_VERSION = 'artifacts-command/v1' as const;
|
|
13
|
+
|
|
14
|
+
/** Caller-supplied key: opaque to the server, never trusted as identity. */
|
|
15
|
+
export type CallerIdempotencyKey = string & { readonly __brand: 'caller-key' };
|
|
16
|
+
|
|
17
|
+
/** Server-derived internal key: distinct from any caller key. */
|
|
18
|
+
export type InternalIdempotencyKey = string & { readonly __brand: 'internal-key' };
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Trusted host derivation binding. The algorithm names Radar's existing
|
|
22
|
+
* SHA-256 construction over the UTF-8 encoding of the five components
|
|
23
|
+
* joined with `|`, in scope/actor/operation/objectId/callerKey order.
|
|
24
|
+
* An ambiguous concatenation MUST fail host configuration or use an
|
|
25
|
+
* explicitly versioned migration; existing durable keys are never
|
|
26
|
+
* silently re-derived.
|
|
27
|
+
*/
|
|
28
|
+
export interface IdempotencyDerivationBinding {
|
|
29
|
+
readonly algorithm: 'radar-derive-idempotency-v1';
|
|
30
|
+
readonly scope: string;
|
|
31
|
+
readonly actor: string;
|
|
32
|
+
readonly operation: string;
|
|
33
|
+
readonly objectId: string;
|
|
34
|
+
readonly callerKey: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Join the five binding components with `|`; the hash covers this preimage. */
|
|
38
|
+
export function buildDerivationPreimage(binding: IdempotencyDerivationBinding): string {
|
|
39
|
+
return [
|
|
40
|
+
binding.scope, binding.actor, binding.operation,
|
|
41
|
+
binding.objectId, binding.callerKey,
|
|
42
|
+
].join('|');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** One expected derivation fixture with its precomputed SHA-256 digest. */
|
|
46
|
+
export interface DerivationVector {
|
|
47
|
+
readonly name: string;
|
|
48
|
+
readonly binding: IdempotencyDerivationBinding;
|
|
49
|
+
readonly preimage: string;
|
|
50
|
+
readonly sha256: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const DERIVATION_VECTORS: readonly DerivationVector[] = [
|
|
54
|
+
{
|
|
55
|
+
name: 'publish-basic',
|
|
56
|
+
binding: {
|
|
57
|
+
algorithm: 'radar-derive-idempotency-v1', scope: 's', actor: 'alice',
|
|
58
|
+
operation: 'publish', objectId: 'obj-1', callerKey: 'key-1',
|
|
59
|
+
},
|
|
60
|
+
preimage: 's|alice|publish|obj-1|key-1',
|
|
61
|
+
sha256: '4a7c17265a6ee4720ff3e941bc42f9c3f2f55b64094076b591d5484d698c7d6a',
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: 'ambiguous-split-preimage',
|
|
65
|
+
binding: {
|
|
66
|
+
algorithm: 'radar-derive-idempotency-v1', scope: 's|issuer', actor: 'alice',
|
|
67
|
+
operation: 'publish', objectId: 'obj-1', callerKey: 'key-1',
|
|
68
|
+
},
|
|
69
|
+
preimage: 's|issuer|alice|publish|obj-1|key-1',
|
|
70
|
+
sha256: '535a79665cbecdcd2c20621d05791568f3f57bacc76b5f059b91f6d0ceda4bb2',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: 'ensure-empty-object',
|
|
74
|
+
binding: {
|
|
75
|
+
algorithm: 'radar-derive-idempotency-v1', scope: 'tenant:t1', actor: 'alice',
|
|
76
|
+
operation: 'ensure', objectId: '', callerKey: 'key-2',
|
|
77
|
+
},
|
|
78
|
+
preimage: 'tenant:t1|alice|ensure||key-2',
|
|
79
|
+
sha256: '437be4b21dce49e880ba3cc697fef667bed4ed0c0ec90e9cbdbad43f1a4a5cbb',
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: 'ensure-alice',
|
|
83
|
+
binding: {
|
|
84
|
+
algorithm: 'radar-derive-idempotency-v1', scope: 's', actor: 'alice',
|
|
85
|
+
operation: 'ensure', objectId: '', callerKey: 'key-2',
|
|
86
|
+
},
|
|
87
|
+
preimage: 's|alice|ensure||key-2',
|
|
88
|
+
sha256: '68934203b875d8ea78a6533b2a971f86b49c1c32416da4af8bd786058a49e1fa',
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: 'ensure-issuer-alice',
|
|
92
|
+
binding: {
|
|
93
|
+
algorithm: 'radar-derive-idempotency-v1', scope: 's', actor: 'issuer|alice',
|
|
94
|
+
operation: 'ensure', objectId: '', callerKey: 'key-2',
|
|
95
|
+
},
|
|
96
|
+
preimage: 's|issuer|alice|ensure||key-2',
|
|
97
|
+
sha256: '1d9b9a3f67238ad0cc6a7ac257232c2d0a1e5dc709d58d09901a2ca68fb98b28',
|
|
98
|
+
},
|
|
99
|
+
];
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* True when two bindings share one preimage through different component
|
|
103
|
+
* splits. Such an ambiguous concatenation is refused: the distinct
|
|
104
|
+
* scope/actor pairs never share a durable identity without an explicitly
|
|
105
|
+
* qualified canonical mapping or a versioned migration.
|
|
106
|
+
*/
|
|
107
|
+
export function detectAmbiguousSplit(
|
|
108
|
+
first: IdempotencyDerivationBinding,
|
|
109
|
+
second: IdempotencyDerivationBinding,
|
|
110
|
+
): boolean {
|
|
111
|
+
if (buildDerivationPreimage(first) !== buildDerivationPreimage(second)) return false;
|
|
112
|
+
return first.scope !== second.scope
|
|
113
|
+
|| first.actor !== second.actor
|
|
114
|
+
|| first.operation !== second.operation
|
|
115
|
+
|| first.objectId !== second.objectId
|
|
116
|
+
|| first.callerKey !== second.callerKey;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* C7 negative fixture: `('s|issuer', 'alice')` and `('s', 'issuer|alice')`
|
|
121
|
+
* produce the same legacy preimage with identical remaining components,
|
|
122
|
+
* so the host MUST refuse (or versioned-migrate) before acceptance. HTTP
|
|
123
|
+
* `alice` versus MCP `issuer|alice` without a qualified canonical mapping
|
|
124
|
+
* are likewise distinct principals and MUST NOT replay each other's receipt.
|
|
125
|
+
*/
|
|
126
|
+
export const AMBIGUOUS_DERIVATION_CASE = {
|
|
127
|
+
first: {
|
|
128
|
+
algorithm: 'radar-derive-idempotency-v1', scope: 's|issuer', actor: 'alice',
|
|
129
|
+
operation: 'publish', objectId: 'obj-1', callerKey: 'key-1',
|
|
130
|
+
},
|
|
131
|
+
second: {
|
|
132
|
+
algorithm: 'radar-derive-idempotency-v1', scope: 's', actor: 'issuer|alice',
|
|
133
|
+
operation: 'publish', objectId: 'obj-1', callerKey: 'key-1',
|
|
134
|
+
},
|
|
135
|
+
expected: 'refuse',
|
|
136
|
+
} as const;
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Platform envelope rule (C7): the optional envelope key is never an
|
|
140
|
+
* alternative source. `accept` proceeds, `conflict` is a 400 on a
|
|
141
|
+
* mismatching envelope, `envelope-only` is a 400 on an envelope without
|
|
142
|
+
* a tool argument. It adds no second platform idempotency store.
|
|
143
|
+
*/
|
|
144
|
+
export function resolvePlatformEnvelope(
|
|
145
|
+
argumentKey: string | undefined,
|
|
146
|
+
envelopeKey: string | undefined,
|
|
147
|
+
): 'accept' | 'conflict' | 'envelope-only' {
|
|
148
|
+
if (argumentKey === undefined) return 'envelope-only';
|
|
149
|
+
if (envelopeKey === undefined || envelopeKey === argumentKey) return 'accept';
|
|
150
|
+
return 'conflict';
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Command-identity relations the L1/L6 codec MUST enforce: equivalent
|
|
155
|
+
* text/byte inputs and object-property order converge, while changed
|
|
156
|
+
* bytes, path, MIME, profile, provenance or file-array order conflict
|
|
157
|
+
* under the same internal key. A hash supplied in a public request never
|
|
158
|
+
* overrides the computed hash.
|
|
159
|
+
*/
|
|
160
|
+
export const COMMAND_IDENTITY_RELATIONS = [
|
|
161
|
+
{
|
|
162
|
+
name: 'text-bytes-equivalence',
|
|
163
|
+
description: 'Equivalent text and byte file inputs converge to one command hash.',
|
|
164
|
+
expectation: 'converge',
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
name: 'file-array-order',
|
|
168
|
+
description: 'Changed file-array order conflicts under the same internal key.',
|
|
169
|
+
expectation: 'conflict',
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
name: 'request-hash-ignored',
|
|
173
|
+
description: 'A hash supplied in a public request never overrides the computed hash.',
|
|
174
|
+
expectation: 'computed-only',
|
|
175
|
+
},
|
|
176
|
+
] as const;
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Deterministic JSON canonicalization for effective command bodies:
|
|
180
|
+
* object keys sort recursively in UTF-16 code-unit order, array order
|
|
181
|
+
* stays significant, and the encoder never mutates its input. L1/L6 hash
|
|
182
|
+
* the canonical bytes (SHA-256) after trusted scope binding, defaults,
|
|
183
|
+
* UTF-8/byte conversion and forced provenance; text/byte equivalence is
|
|
184
|
+
* established on the decoded form before this encoding, and the codec
|
|
185
|
+
* deep-copies before normalization.
|
|
186
|
+
*/
|
|
187
|
+
export function canonicalizeJson(value: unknown): string {
|
|
188
|
+
if (value === null || typeof value !== 'object') return JSON.stringify(value) ?? 'null';
|
|
189
|
+
if (Array.isArray(value)) {
|
|
190
|
+
return `[${value.map((entry) => canonicalizeJson(entry)).join(',')}]`;
|
|
191
|
+
}
|
|
192
|
+
const record = value as Record<string, unknown>;
|
|
193
|
+
const keys = Object.keys(record).sort();
|
|
194
|
+
return `{${keys.map((key) => `${JSON.stringify(key)}:${canonicalizeJson(record[key])}`).join(',')}}`;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** One golden effective-command fixture with its canonical bytes and digest. */
|
|
198
|
+
export interface CanonicalCommandFixture {
|
|
199
|
+
readonly name: string;
|
|
200
|
+
readonly body: {
|
|
201
|
+
readonly operation: string;
|
|
202
|
+
readonly scopeBinding: string;
|
|
203
|
+
readonly payload: Record<string, unknown>;
|
|
204
|
+
};
|
|
205
|
+
readonly canonical: string;
|
|
206
|
+
readonly sha256: string;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export const CANONICAL_COMMAND_FIXTURES: readonly CanonicalCommandFixture[] = [
|
|
210
|
+
{
|
|
211
|
+
name: 'publish-baseline',
|
|
212
|
+
body: {
|
|
213
|
+
operation: 'publish',
|
|
214
|
+
scopeBinding: 's',
|
|
215
|
+
payload: {
|
|
216
|
+
artifactId: 'obj-1',
|
|
217
|
+
files: [
|
|
218
|
+
{ bytes: [60, 104, 49, 62, 72, 105, 60, 47, 104, 49, 62], mimeType: 'text/html', path: 'index.html' },
|
|
219
|
+
{ bytes: [98, 111, 100, 121], mimeType: 'text/css', path: 'styles.css' },
|
|
220
|
+
],
|
|
221
|
+
profile: {},
|
|
222
|
+
provenance: { kind: 'inline-original' },
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
canonical: '{"operation":"publish","payload":{"artifactId":"obj-1","files":[{"bytes":[60,104,49,62,72,105,60,47,104,49,62],"mimeType":"text/html","path":"index.html"},{"bytes":[98,111,100,121],"mimeType":"text/css","path":"styles.css"}],"profile":{},"provenance":{"kind":"inline-original"}},"scopeBinding":"s"}',
|
|
226
|
+
sha256: 'c1c8a186b847635ae4b1d1c27bf1079ae1cf26c1ef4b47ed622cafaa6a273d57',
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
name: 'publish-shuffled-keys',
|
|
230
|
+
body: {
|
|
231
|
+
payload: {
|
|
232
|
+
provenance: { kind: 'inline-original' },
|
|
233
|
+
profile: {},
|
|
234
|
+
files: [
|
|
235
|
+
{ path: 'index.html', mimeType: 'text/html', bytes: [60, 104, 49, 62, 72, 105, 60, 47, 104, 49, 62] },
|
|
236
|
+
{ path: 'styles.css', mimeType: 'text/css', bytes: [98, 111, 100, 121] },
|
|
237
|
+
],
|
|
238
|
+
artifactId: 'obj-1',
|
|
239
|
+
},
|
|
240
|
+
scopeBinding: 's',
|
|
241
|
+
operation: 'publish',
|
|
242
|
+
},
|
|
243
|
+
canonical: '{"operation":"publish","payload":{"artifactId":"obj-1","files":[{"bytes":[60,104,49,62,72,105,60,47,104,49,62],"mimeType":"text/html","path":"index.html"},{"bytes":[98,111,100,121],"mimeType":"text/css","path":"styles.css"}],"profile":{},"provenance":{"kind":"inline-original"}},"scopeBinding":"s"}',
|
|
244
|
+
sha256: 'c1c8a186b847635ae4b1d1c27bf1079ae1cf26c1ef4b47ed622cafaa6a273d57',
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
name: 'publish-swapped-file-order',
|
|
248
|
+
body: {
|
|
249
|
+
operation: 'publish',
|
|
250
|
+
scopeBinding: 's',
|
|
251
|
+
payload: {
|
|
252
|
+
artifactId: 'obj-1',
|
|
253
|
+
files: [
|
|
254
|
+
{ bytes: [98, 111, 100, 121], mimeType: 'text/css', path: 'styles.css' },
|
|
255
|
+
{ bytes: [60, 104, 49, 62, 72, 105, 60, 47, 104, 49, 62], mimeType: 'text/html', path: 'index.html' },
|
|
256
|
+
],
|
|
257
|
+
profile: {},
|
|
258
|
+
provenance: { kind: 'inline-original' },
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
canonical: '{"operation":"publish","payload":{"artifactId":"obj-1","files":[{"bytes":[98,111,100,121],"mimeType":"text/css","path":"styles.css"},{"bytes":[60,104,49,62,72,105,60,47,104,49,62],"mimeType":"text/html","path":"index.html"}],"profile":{},"provenance":{"kind":"inline-original"}},"scopeBinding":"s"}',
|
|
262
|
+
sha256: 'ebf2c9342f120f4aeea857656cce1ff91a359d16eb493af97878350bc7b0f990',
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
name: 'publish-changed-mime',
|
|
266
|
+
body: {
|
|
267
|
+
operation: 'publish',
|
|
268
|
+
scopeBinding: 's',
|
|
269
|
+
payload: {
|
|
270
|
+
artifactId: 'obj-1',
|
|
271
|
+
files: [
|
|
272
|
+
{ bytes: [60, 104, 49, 62, 72, 105, 60, 47, 104, 49, 62], mimeType: 'text/html', path: 'index.html' },
|
|
273
|
+
{ bytes: [98, 111, 100, 121], mimeType: 'text/javascript', path: 'styles.css' },
|
|
274
|
+
],
|
|
275
|
+
profile: {},
|
|
276
|
+
provenance: { kind: 'inline-original' },
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
canonical: '{"operation":"publish","payload":{"artifactId":"obj-1","files":[{"bytes":[60,104,49,62,72,105,60,47,104,49,62],"mimeType":"text/html","path":"index.html"},{"bytes":[98,111,100,121],"mimeType":"text/javascript","path":"styles.css"}],"profile":{},"provenance":{"kind":"inline-original"}},"scopeBinding":"s"}',
|
|
280
|
+
sha256: '90d3680386f461353f16f2c004271b1298a67895b3249eec1566320a52990528',
|
|
281
|
+
},
|
|
282
|
+
];
|
|
283
|
+
|
|
284
|
+
/** Server-created identity for one durable command. */
|
|
285
|
+
export interface CommandIdentity {
|
|
286
|
+
readonly internalKey: InternalIdempotencyKey;
|
|
287
|
+
readonly commandHash: string;
|
|
288
|
+
readonly codecVersion: typeof COMMAND_CODEC_VERSION;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Effective command after trusted scope binding, defaults, UTF-8/byte
|
|
293
|
+
* conversion and forced provenance. Object keys sort recursively; array
|
|
294
|
+
* order remains significant. A hash supplied in a public request never
|
|
295
|
+
* overrides the computed hash.
|
|
296
|
+
*/
|
|
297
|
+
export interface EffectiveArtifactCommand {
|
|
298
|
+
readonly operation: string;
|
|
299
|
+
readonly scopeBinding: string;
|
|
300
|
+
readonly payload: Record<string, unknown>;
|
|
301
|
+
readonly provenance: { readonly kind: 'inline-original' };
|
|
302
|
+
readonly identity: CommandIdentity;
|
|
303
|
+
}
|