archctx-contracts 0.4.4 → 0.4.6

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.
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "schemaVersion": "archcontext.capabilities/v1",
3
- "package": { "name": "archctx", "version": "0.4.4" },
3
+ "package": { "name": "archctx", "version": "0.4.6" },
4
4
  "protocols": {
5
5
  "projectionRequest": "archcontext.projection-request/v1",
6
- "projectionResult": "archcontext.projection-result/v1",
6
+ "projectionResult": "archcontext.projection-result/v2",
7
7
  "architectureRefreshSignal": "archcontext.architecture-refresh-signal/v1"
8
8
  },
9
9
  "renderers": {
10
10
  "architectureDocs": "archcontext.docs-renderer/v4",
11
11
  "agentContext": "archcontext.agent-context-renderer/v1"
12
12
  },
13
- "features": ["architecture-docs-renderer-v2", "architecture-refresh-signal-v1", "projection-protocol-v1"]
13
+ "features": ["architecture-docs-renderer-v2", "architecture-refresh-signal-v1", "projection-apply-receipt-v1", "projection-protocol-v2"]
14
14
  }
@@ -2,7 +2,7 @@
2
2
  "schemaVersion": "archcontext.product-version-manifest/v1",
3
3
  "product": {
4
4
  "name": "archctx",
5
- "version": "0.4.4",
5
+ "version": "0.4.6",
6
6
  "distribution": "one-package"
7
7
  },
8
8
  "packageManager": "bun@1.3.10",
@@ -12,25 +12,25 @@
12
12
  "surfaces": {
13
13
  "cli": {
14
14
  "packageName": "@archcontext/surfaces",
15
- "version": "0.4.4",
15
+ "version": "0.4.6",
16
16
  "entrypoint": "archctx"
17
17
  },
18
18
  "daemon": {
19
19
  "packageName": "@archcontext/local-runtime",
20
- "version": "0.4.4",
20
+ "version": "0.4.6",
21
21
  "entrypoint": "archctx daemon start",
22
22
  "rpcSchemaVersion": "archcontext.runtime-rpc/v1"
23
23
  },
24
24
  "mcp": {
25
25
  "packageName": "@archcontext/surfaces",
26
- "version": "0.4.4",
26
+ "version": "0.4.6",
27
27
  "entrypoint": "archctx mcp",
28
28
  "transport": "stdio"
29
29
  }
30
30
  },
31
31
  "schemas": {
32
32
  "schemaSetVersion": "2026-06-25.al0-ledger",
33
- "contractsPackageVersion": "0.4.4",
33
+ "contractsPackageVersion": "0.4.6",
34
34
  "reviewChallenge": "archcontext.review-challenge/v2",
35
35
  "attestation": "archcontext.attestation/v2",
36
36
  "deviceIdentity": "archcontext.device-identity/v1",
@@ -1,5 +1,5 @@
1
1
  {
2
- "schemaVersion": "archcontext.projection-result/v1",
2
+ "schemaVersion": "archcontext.projection-result/v2",
3
3
  "requestId": "projection_request.repo_main",
4
4
  "status": "applied",
5
5
  "inputSnapshot": {
@@ -43,5 +43,5 @@
43
43
  ],
44
44
  "humanActions": [],
45
45
  "refreshSignals": [],
46
- "receiptDigest": "sha256:ba5f380502a07529a40febb353a6790fbeb950cad06bfe6c0331e3f9d35907bf"
46
+ "receiptDigest": "sha256:6adef4b3486194b61e1851fdd5c1c352de374e10011beb9a38ec845ee91ff702"
47
47
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "archctx-contracts",
3
- "version": "0.4.4",
3
+ "version": "0.4.6",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -22,7 +22,7 @@
22
22
  "required": ["projectionRequest", "projectionResult", "architectureRefreshSignal"],
23
23
  "properties": {
24
24
  "projectionRequest": { "const": "archcontext.projection-request/v1" },
25
- "projectionResult": { "const": "archcontext.projection-result/v1" },
25
+ "projectionResult": { "const": "archcontext.projection-result/v2" },
26
26
  "architectureRefreshSignal": { "const": "archcontext.architecture-refresh-signal/v1" }
27
27
  }
28
28
  },
@@ -39,7 +39,7 @@
39
39
  "type": "array",
40
40
  "minItems": 3,
41
41
  "uniqueItems": true,
42
- "items": { "enum": ["architecture-docs-renderer-v2", "architecture-refresh-signal-v1", "projection-protocol-v1"] }
42
+ "items": { "enum": ["architecture-docs-renderer-v2", "architecture-refresh-signal-v1", "projection-apply-receipt-v1", "projection-protocol-v2"] }
43
43
  }
44
44
  }
45
45
  }
@@ -1,20 +1,21 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://archcontext.dev/schemas/runtime/projection-result.schema.json",
4
- "title": "ProjectionResultV1",
4
+ "title": "ProjectionResultV2",
5
5
  "type": "object",
6
6
  "additionalProperties": false,
7
7
  "required": ["schemaVersion", "requestId", "status", "inputSnapshot", "outputSnapshot", "affectedNodeIds", "files", "humanActions", "refreshSignals", "receiptDigest"],
8
8
  "properties": {
9
- "schemaVersion": { "const": "archcontext.projection-result/v1" },
9
+ "schemaVersion": { "const": "archcontext.projection-result/v2" },
10
10
  "requestId": { "type": "string", "pattern": "^[a-zA-Z0-9_.:-]+$", "minLength": 1 },
11
- "status": { "enum": ["adoption-required", "applied", "blocked", "human-action-required", "noop", "permanent-failure", "planned", "retryable-failure"] },
11
+ "status": { "enum": ["adoption-required", "applied", "applied-reconcile-required", "blocked", "human-action-required", "noop", "permanent-failure", "planned", "retryable-failure"] },
12
12
  "inputSnapshot": { "$ref": "#/$defs/snapshot" },
13
13
  "outputSnapshot": { "$ref": "#/$defs/snapshot" },
14
14
  "affectedNodeIds": { "type": "array", "uniqueItems": true, "description": "Canonical wire order is lexicographic and is enforced by projectionResultInvariantIssues.", "items": { "type": "string", "minLength": 1 } },
15
15
  "files": { "type": "array", "description": "Canonical wire order is by path and is enforced by projectionResultInvariantIssues.", "items": { "$ref": "#/$defs/file" } },
16
16
  "humanActions": { "type": "array", "items": { "$ref": "#/$defs/humanAction" } },
17
17
  "refreshSignals": { "type": "array", "description": "Canonical wire order is by signalId and cross-object receipt/snapshot bindings are enforced by projectionResultInvariantIssues.", "items": { "$ref": "#/$defs/refreshSignal" } },
18
+ "applyReceipt": { "$ref": "#/$defs/applyIdentity" },
18
19
  "receiptDigest": { "$ref": "#/$defs/digest" }
19
20
  },
20
21
  "allOf": [
@@ -22,6 +23,13 @@
22
23
  "if": { "properties": { "status": { "enum": ["adoption-required", "human-action-required"] } }, "required": ["status"] },
23
24
  "then": { "properties": { "humanActions": { "minItems": 1 } } },
24
25
  "else": { "properties": { "humanActions": { "maxItems": 0 } } }
26
+ },
27
+ {
28
+ "if": { "properties": { "status": { "const": "applied-reconcile-required" } }, "required": ["status"] },
29
+ "then": {
30
+ "required": ["applyReceipt"],
31
+ "properties": { "refreshSignals": { "maxItems": 0 } }
32
+ }
25
33
  }
26
34
  ],
27
35
  "$defs": {
@@ -157,6 +165,30 @@
157
165
  "reasonCodes": { "type": "array", "minItems": 1, "uniqueItems": true, "items": { "$ref": "#/$defs/reasonCode" } },
158
166
  "affectedNodeIds": { "type": "array", "minItems": 1, "uniqueItems": true, "items": { "type": "string", "minLength": 1 } }
159
167
  }
168
+ },
169
+ "applyIdentity": {
170
+ "type": "object",
171
+ "additionalProperties": false,
172
+ "required": ["schemaVersion", "applyId", "lookupKey", "repositoryId", "workspaceId", "acceptedChange", "semanticCommit", "ownedFilesDigest", "refreshSignalsDigest"],
173
+ "properties": {
174
+ "schemaVersion": { "const": "archcontext.projection-apply-identity/v1" },
175
+ "applyId": { "$ref": "#/$defs/digest" },
176
+ "lookupKey": { "$ref": "#/$defs/digest" },
177
+ "repositoryId": { "type": "string", "minLength": 1 },
178
+ "workspaceId": { "type": "string", "minLength": 1 },
179
+ "acceptedChange": { "$ref": "#/$defs/acceptedChange" },
180
+ "semanticCommit": {
181
+ "type": "object",
182
+ "additionalProperties": false,
183
+ "required": ["changeSetId", "idempotencyKey"],
184
+ "properties": {
185
+ "changeSetId": { "type": "string", "minLength": 1 },
186
+ "idempotencyKey": { "type": "string", "minLength": 1 }
187
+ }
188
+ },
189
+ "ownedFilesDigest": { "$ref": "#/$defs/digest" },
190
+ "refreshSignalsDigest": { "$ref": "#/$defs/digest" }
191
+ }
160
192
  }
161
193
  }
162
194
  }
@@ -1,5 +1,5 @@
1
1
  export const ARCHCONTEXT_PRODUCT_NAME = "archctx";
2
- export const ARCHCONTEXT_PRODUCT_VERSION = "0.4.4";
2
+ export const ARCHCONTEXT_PRODUCT_VERSION = "0.4.6";
3
3
  export const ARCHCONTEXT_PACKAGE_MANAGER = "bun@1.3.10";
4
4
  export const ARCHCONTEXT_NODE_RANGE = ">=24 <26";
5
5
  export const LOCAL_RUNTIME_RPC_SCHEMA_VERSION = "archcontext.runtime-rpc/v1";
package/src/projection.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  import { digestJson, type Json } from "./schema";
2
2
 
3
3
  export const PROJECTION_REQUEST_SCHEMA_VERSION = "archcontext.projection-request/v1" as const;
4
- export const PROJECTION_RESULT_SCHEMA_VERSION = "archcontext.projection-result/v1" as const;
4
+ export const PROJECTION_RESULT_SCHEMA_VERSION = "archcontext.projection-result/v2" as const;
5
+ export const PROJECTION_APPLY_IDENTITY_SCHEMA_VERSION = "archcontext.projection-apply-identity/v1" as const;
6
+ export const PROJECTION_APPLY_RECEIPT_SCHEMA_VERSION = "archcontext.projection-apply-receipt/v1" as const;
5
7
  export const ARCHITECTURE_REFRESH_SIGNAL_SCHEMA_VERSION = "archcontext.architecture-refresh-signal/v1" as const;
6
8
  export const ARCHCTX_CAPABILITIES_SCHEMA_VERSION = "archcontext.capabilities/v1" as const;
7
9
  export const ARCHITECTURE_DOCS_RENDERER_VERSION = "archcontext.docs-renderer/v4" as const;
@@ -12,6 +14,7 @@ export const PROJECTION_TARGETS = ["agent-context", "architecture-docs"] as cons
12
14
  export const PROJECTION_RESULT_STATUSES = [
13
15
  "adoption-required",
14
16
  "applied",
17
+ "applied-reconcile-required",
15
18
  "blocked",
16
19
  "human-action-required",
17
20
  "noop",
@@ -51,7 +54,8 @@ export const ARCHITECTURE_REFRESH_TARGETS = [
51
54
  export const ARCHCTX_FEATURES = [
52
55
  "architecture-docs-renderer-v2",
53
56
  "architecture-refresh-signal-v1",
54
- "projection-protocol-v1"
57
+ "projection-apply-receipt-v1",
58
+ "projection-protocol-v2"
55
59
  ] as const;
56
60
 
57
61
  export type ProjectionMode = (typeof PROJECTION_MODES)[number];
@@ -112,6 +116,21 @@ export interface ProjectionHumanActionV1 {
112
116
  requestPayloadDigest: Sha256Digest;
113
117
  }
114
118
 
119
+ export interface ProjectionApplyIdentityV1 {
120
+ schemaVersion: typeof PROJECTION_APPLY_IDENTITY_SCHEMA_VERSION;
121
+ applyId: Sha256Digest;
122
+ lookupKey: Sha256Digest;
123
+ repositoryId: string;
124
+ workspaceId: string;
125
+ acceptedChange: AcceptedArchitectureChangeReferenceV1;
126
+ semanticCommit: {
127
+ changeSetId: string;
128
+ idempotencyKey: string;
129
+ };
130
+ ownedFilesDigest: Sha256Digest;
131
+ refreshSignalsDigest: Sha256Digest;
132
+ }
133
+
115
134
  export interface ArchitectureDigestSetV1 {
116
135
  modelDigest: Sha256Digest;
117
136
  sourceTreeDigest: Sha256Digest;
@@ -143,7 +162,7 @@ export interface ArchitectureRefreshSignalV1 {
143
162
  projectionReceiptDigest: Sha256Digest;
144
163
  }
145
164
 
146
- export interface ProjectionResultV1 {
165
+ export interface ProjectionResultV2 {
147
166
  schemaVersion: typeof PROJECTION_RESULT_SCHEMA_VERSION;
148
167
  requestId: string;
149
168
  status: ProjectionResultStatus;
@@ -153,9 +172,16 @@ export interface ProjectionResultV1 {
153
172
  files: ProjectionFileResultV1[];
154
173
  humanActions: ProjectionHumanActionV1[];
155
174
  refreshSignals: ArchitectureRefreshSignalV1[];
175
+ applyReceipt?: ProjectionApplyIdentityV1;
156
176
  receiptDigest: Sha256Digest;
157
177
  }
158
178
 
179
+ export interface ProjectionApplyReceiptV1 {
180
+ schemaVersion: typeof PROJECTION_APPLY_RECEIPT_SCHEMA_VERSION;
181
+ identity: ProjectionApplyIdentityV1;
182
+ result: ProjectionResultV2;
183
+ }
184
+
159
185
  export interface ArchctxCapabilitiesV1 {
160
186
  schemaVersion: typeof ARCHCTX_CAPABILITIES_SCHEMA_VERSION;
161
187
  package: {
@@ -222,7 +248,7 @@ export function projectionRequestInvariantIssues(input: ProjectionRequestV1): st
222
248
  return issues;
223
249
  }
224
250
 
225
- export function projectionResultInvariantIssues(input: ProjectionResultV1): string[] {
251
+ export function projectionResultInvariantIssues(input: ProjectionResultV2): string[] {
226
252
  const issues = [
227
253
  ...sortedUniqueIssues("affectedNodeIds", input.affectedNodeIds),
228
254
  ...sortedUniqueIssues("files.path", input.files.map((file) => file.path)),
@@ -237,6 +263,10 @@ export function projectionResultInvariantIssues(input: ProjectionResultV1): stri
237
263
  const statusRequiresHumanAction = input.status === "adoption-required" || input.status === "human-action-required";
238
264
  if (statusRequiresHumanAction && input.humanActions.length === 0) issues.push(`${input.status} status requires at least one human action`);
239
265
  if (!statusRequiresHumanAction && input.humanActions.length > 0) issues.push(`human actions are not allowed when status=${input.status}`);
266
+ if (input.status === "applied-reconcile-required") {
267
+ if (!input.applyReceipt) issues.push("applied-reconcile-required status requires applyReceipt");
268
+ if (input.refreshSignals.length > 0) issues.push("applied-reconcile-required status cannot deliver refreshSignals");
269
+ }
240
270
  for (const [index, file] of input.files.entries()) {
241
271
  const prefix = `files[${index}]`;
242
272
  if (file.action === "create" && (file.preimageDigest !== null || file.outputDigest === null)) {
@@ -270,11 +300,74 @@ export function projectionResultInvariantIssues(input: ProjectionResultV1): stri
270
300
  * receipt payload to avoid a circular hash; every signal must then bind that receipt via
271
301
  * projectionReceiptDigest, which projectionResultInvariantIssues verifies.
272
302
  */
273
- export function projectionResultReceiptDigest(input: Omit<ProjectionResultV1, "receiptDigest">): Sha256Digest {
303
+ export function projectionResultReceiptDigest(input: Omit<ProjectionResultV2, "receiptDigest">): Sha256Digest {
274
304
  const refreshSignals = input.refreshSignals.map(({ projectionReceiptDigest: _projectionReceiptDigest, ...signal }) => signal);
275
305
  return digestJson({ ...input, refreshSignals } as unknown as Json) as Sha256Digest;
276
306
  }
277
307
 
308
+ export function projectionApplyLookupKey(input: {
309
+ repositoryId: string;
310
+ workspaceId: string;
311
+ acceptedChange: AcceptedArchitectureChangeReferenceV1;
312
+ }): Sha256Digest {
313
+ return digestJson({
314
+ schemaVersion: "archcontext.projection-apply-lookup/v1",
315
+ repositoryId: input.repositoryId,
316
+ workspaceId: input.workspaceId,
317
+ acceptedChange: input.acceptedChange
318
+ } as unknown as Json) as Sha256Digest;
319
+ }
320
+
321
+ export function createProjectionApplyIdentity(input: {
322
+ repositoryId: string;
323
+ workspaceId: string;
324
+ acceptedChange: AcceptedArchitectureChangeReferenceV1;
325
+ changeSetId: string;
326
+ idempotencyKey: string;
327
+ files: ProjectionFileResultV1[];
328
+ refreshSignals: ArchitectureRefreshSignalV1[];
329
+ }): ProjectionApplyIdentityV1 {
330
+ const lookupKey = projectionApplyLookupKey(input);
331
+ const ownedFilesDigest = digestJson(input.files as unknown as Json) as Sha256Digest;
332
+ const refreshSignals = input.refreshSignals.map(({ projectionReceiptDigest: _receipt, ...signal }) => signal);
333
+ const refreshSignalsDigest = digestJson(refreshSignals as unknown as Json) as Sha256Digest;
334
+ const withoutApplyId = {
335
+ schemaVersion: PROJECTION_APPLY_IDENTITY_SCHEMA_VERSION,
336
+ lookupKey,
337
+ repositoryId: input.repositoryId,
338
+ workspaceId: input.workspaceId,
339
+ acceptedChange: input.acceptedChange,
340
+ semanticCommit: { changeSetId: input.changeSetId, idempotencyKey: input.idempotencyKey },
341
+ ownedFilesDigest,
342
+ refreshSignalsDigest
343
+ };
344
+ return {
345
+ ...withoutApplyId,
346
+ applyId: digestJson(withoutApplyId as unknown as Json) as Sha256Digest
347
+ };
348
+ }
349
+
350
+ export function projectionApplyReceiptInvariantIssues(input: ProjectionApplyReceiptV1): string[] {
351
+ const identity = createProjectionApplyIdentity({
352
+ repositoryId: input.identity.repositoryId,
353
+ workspaceId: input.identity.workspaceId,
354
+ acceptedChange: input.identity.acceptedChange,
355
+ changeSetId: input.identity.semanticCommit.changeSetId,
356
+ idempotencyKey: input.identity.semanticCommit.idempotencyKey,
357
+ files: input.result.files,
358
+ refreshSignals: input.result.refreshSignals
359
+ });
360
+ const issues = projectionResultInvariantIssues(input.result);
361
+ if (digestJson(identity as unknown as Json) !== digestJson(input.identity as unknown as Json)) {
362
+ issues.push("projection apply identity must bind the committed files and original refresh signals");
363
+ }
364
+ if (input.result.status !== "applied") issues.push("projection apply receipt result status must be applied");
365
+ if (digestJson(input.result.applyReceipt as unknown as Json) !== digestJson(input.identity as unknown as Json)) {
366
+ issues.push("projection apply receipt result must carry the same apply identity");
367
+ }
368
+ return issues;
369
+ }
370
+
278
371
  export function architectureRefreshSignalInvariantIssues(input: ArchitectureRefreshSignalV1, prefix = "signal"): string[] {
279
372
  const issues = [
280
373
  ...sortedUniqueIssues(`${prefix}.reasonCodes`, input.reasonCodes),