@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.
Files changed (75) hide show
  1. package/README.md +74 -4
  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/content/handler.js +79 -63
  7. package/dist/http.d.ts +42 -29
  8. package/dist/http.js +168 -491
  9. package/dist/index.d.ts +2 -0
  10. package/dist/index.js +2 -0
  11. package/dist/mcp-core.d.ts +52 -0
  12. package/dist/mcp-core.js +111 -0
  13. package/dist/mcp.d.ts +4 -60
  14. package/dist/mcp.js +7 -276
  15. package/dist/profile/contracts.d.ts +67 -0
  16. package/dist/profile/contracts.js +31 -0
  17. package/dist/profile/index.d.ts +6 -0
  18. package/dist/profile/index.js +117 -0
  19. package/dist/profile/json.d.ts +10 -0
  20. package/dist/profile/json.js +76 -0
  21. package/dist/profile/radar.d.ts +3 -0
  22. package/dist/profile/radar.js +21 -0
  23. package/dist/profile/schemas.d.ts +6 -0
  24. package/dist/profile/schemas.js +76 -0
  25. package/dist/profile/validation.d.ts +5 -0
  26. package/dist/profile/validation.js +104 -0
  27. package/dist/profile/validators.d.ts +3 -0
  28. package/dist/profile/validators.js +51 -0
  29. package/dist/server/authorized-replay.d.ts +33 -0
  30. package/dist/server/authorized-replay.js +31 -0
  31. package/dist/server/command-identity.d.ts +52 -0
  32. package/dist/server/command-identity.js +71 -0
  33. package/dist/server/commit/brands.d.ts +7 -0
  34. package/dist/server/commit/brands.js +3 -0
  35. package/dist/server/commit/coordinator.d.ts +2 -0
  36. package/dist/server/commit/coordinator.js +729 -0
  37. package/dist/server/commit/types.d.ts +379 -0
  38. package/dist/server/commit/types.js +94 -0
  39. package/dist/server/commit-contract.d.ts +4 -0
  40. package/dist/server/commit-contract.js +2 -0
  41. package/dist/server/failures.d.ts +84 -0
  42. package/dist/server/failures.js +133 -0
  43. package/dist/server/gc.d.ts +42 -1
  44. package/dist/server/gc.js +168 -5
  45. package/dist/server/idempotency.d.ts +4 -0
  46. package/dist/server/idempotency.js +14 -0
  47. package/dist/server/lifecycle.d.ts +2 -1
  48. package/dist/server/lifecycle.js +15 -1
  49. package/dist/server/object-intents.d.ts +14 -1
  50. package/dist/server/object-intents.js +31 -0
  51. package/dist/server/outcome-contract.d.ts +3 -0
  52. package/dist/server/outcome-contract.js +3 -0
  53. package/dist/server/outcome-projection.d.ts +21 -0
  54. package/dist/server/outcome-projection.js +29 -0
  55. package/dist/server/preparation-cleanup.d.ts +31 -0
  56. package/dist/server/preparation-cleanup.js +106 -0
  57. package/dist/server/publication-limits.d.ts +10 -0
  58. package/dist/server/publication-limits.js +38 -0
  59. package/dist/server/publication.d.ts +85 -1
  60. package/dist/server/publication.js +288 -31
  61. package/dist/server/service.d.ts +64 -1
  62. package/dist/server/service.js +187 -1
  63. package/dist/server/uploads.d.ts +4 -1
  64. package/dist/server/uploads.js +63 -35
  65. package/dist/testing.d.ts +50 -0
  66. package/dist/testing.js +86 -0
  67. package/docs/migration-l6.md +129 -0
  68. package/examples/radar-commit-setup.ts +299 -0
  69. package/l0-freeze/command-identity-contract.ts +303 -0
  70. package/l0-freeze/commit-contract.ts +421 -0
  71. package/l0-freeze/failure-contract.ts +174 -0
  72. package/l0-freeze/profile-contract.ts +219 -0
  73. package/migrations/0003_coordinator.sql +28 -0
  74. package/package.json +31 -1
  75. 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
+ }