archctx 0.5.8 → 0.5.9

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,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": "archcontext.practice-catalog-manifest/v1",
3
3
  "catalogVersion": "2026.06.0",
4
- "productVersion": "0.5.8",
4
+ "productVersion": "0.5.9",
5
5
  "generatedAt": "1970-01-01T00:00:00.000Z",
6
6
  "entries": [
7
7
  {
@@ -389,5 +389,5 @@
389
389
  "structurizr.dsl",
390
390
  "twelve-factor"
391
391
  ],
392
- "catalogDigest": "sha256:17d66193030b2e854293c28001f34624c4ae25073eb00604f54e7099950537c9"
392
+ "catalogDigest": "sha256:7838ef15b3da8f2c4528b7eadcbf9263adeee2e95ca2608c5819fd2b0b3708f9"
393
393
  }
package/bin/archctx.mjs CHANGED
@@ -897,6 +897,9 @@ function projectionResultInvariantIssues(input) {
897
897
  issues.push(`${prefix} unchanged requires equal non-null digests`);
898
898
  }
899
899
  }
900
+ if (input.priorCommittedApplies) {
901
+ issues.push(...projectionPriorCommittedAppliesIssues(input.priorCommittedApplies, input.requestId));
902
+ }
900
903
  const { receiptDigest, ...receiptPayload } = input;
901
904
  if (projectionResultReceiptDigest(receiptPayload) !== receiptDigest)
902
905
  issues.push("receiptDigest must match the canonical projection result payload");
@@ -915,6 +918,45 @@ function projectionResultInvariantIssues(input) {
915
918
  }
916
919
  return issues;
917
920
  }
921
+ function projectionPriorCommittedAppliesIssues(input, requestId) {
922
+ const issues = sortedUniqueIssues("priorCommittedApplies.changeSetId", input.map((entry) => entry.changeSetId));
923
+ if (input.length === 0)
924
+ issues.push("priorCommittedApplies must be omitted instead of empty");
925
+ for (const [index, entry] of input.entries()) {
926
+ const prefix = `priorCommittedApplies[${index}]`;
927
+ issues.push(...sortedUniqueIssues(`${prefix}.files.path`, entry.files.map((file) => file.path)));
928
+ if (entry.requestId !== requestId)
929
+ issues.push(`${prefix}.requestId must match the projection result requestId`);
930
+ if (entry.changeSetId.trim() === "")
931
+ issues.push(`${prefix}.changeSetId must not be empty`);
932
+ if (!ISO_INSTANT.test(entry.committedAt))
933
+ issues.push(`${prefix}.committedAt must be an ISO-8601 UTC instant`);
934
+ if (entry.files.length === 0)
935
+ issues.push(`${prefix}.files must name at least one committed file`);
936
+ if (entry.applyId === undefined !== (entry.lookupKey === undefined)) {
937
+ issues.push(`${prefix}.applyId and lookupKey must be present together or both absent`);
938
+ }
939
+ for (const field of ["applyId", "lookupKey"]) {
940
+ const value = entry[field];
941
+ if (value !== undefined && !SHA256_DIGEST.test(value))
942
+ issues.push(`${prefix}.${field} must be a SHA-256 digest`);
943
+ }
944
+ for (const [fileIndex, file] of entry.files.entries()) {
945
+ const filePrefix = `${prefix}.files[${fileIndex}]`;
946
+ if (!isRepoRelativePosixPath(file.path))
947
+ issues.push(`${filePrefix}.path must be a repository-relative POSIX path`);
948
+ if (!PROJECTION_PRIOR_COMMITTED_APPLY_OPERATIONS.includes(file.operation)) {
949
+ issues.push(`${filePrefix}.operation must be a supported committed operation`);
950
+ }
951
+ const expectsMissing = file.operation === "delete";
952
+ if (expectsMissing && file.hash !== "missing")
953
+ issues.push(`${filePrefix}.hash must be "missing" for a delete`);
954
+ if (!expectsMissing && !SHA256_DIGEST.test(file.hash))
955
+ issues.push(`${filePrefix}.hash must be a SHA-256 body digest`);
956
+ }
957
+ }
958
+ return issues;
959
+ }
918
960
  function projectionResultReceiptDigest(input) {
919
961
  const refreshSignals = input.refreshSignals.map(({ projectionReceiptDigest: _projectionReceiptDigest, ...signal }) => signal);
920
962
  return digestJson({ ...input, refreshSignals });
@@ -1126,10 +1168,11 @@ function sortedUniqueIssues(label, values) {
1126
1168
  const expected = [...new Set(values)].sort();
1127
1169
  return expected.length === values.length && expected.every((value, index) => value === values[index]) ? [] : [`${label} must be sorted and unique`];
1128
1170
  }
1129
- var PROJECTION_REQUEST_SCHEMA_VERSION = "archcontext.projection-request/v1", PROJECTION_RESULT_SCHEMA_VERSION = "archcontext.projection-result/v2", PROJECTION_APPLY_IDENTITY_SCHEMA_VERSION = "archcontext.projection-apply-identity/v1", PROJECTION_APPLY_RECOVERY_BINDING_SCHEMA_VERSION = "archcontext.projection-apply-recovery-binding/v1", PROJECTION_APPLY_RECOVERY_INTENT_SCHEMA_VERSION = "archcontext.projection-apply-recovery-intent/v1", PROJECTION_APPLY_RECOVERY_PROOF_SCHEMA_VERSION = "archcontext.projection-apply-recovery-proof/v1", PROJECTION_APPLY_RECOVERY_RESULT_SCHEMA_VERSION = "archcontext.projection-apply-recovery-result/v1", ARCHITECTURE_REFRESH_SIGNAL_SCHEMA_VERSION = "archcontext.architecture-refresh-signal/v1", ARCHCTX_CAPABILITIES_SCHEMA_VERSION = "archcontext.capabilities/v1", ARCHITECTURE_DOCS_RENDERER_VERSION = "archcontext.docs-renderer/v4", AGENT_CONTEXT_RENDERER_VERSION = "archcontext.agent-context-renderer/v1", PROJECTION_MODES, PROJECTION_TARGETS, ARCHITECTURE_MAJOR_CHANGE_REASON_CODES, ARCHCTX_FEATURES;
1171
+ var PROJECTION_REQUEST_SCHEMA_VERSION = "archcontext.projection-request/v1", PROJECTION_RESULT_SCHEMA_VERSION = "archcontext.projection-result/v2", PROJECTION_APPLY_IDENTITY_SCHEMA_VERSION = "archcontext.projection-apply-identity/v1", PROJECTION_APPLY_RECOVERY_BINDING_SCHEMA_VERSION = "archcontext.projection-apply-recovery-binding/v1", PROJECTION_APPLY_RECOVERY_INTENT_SCHEMA_VERSION = "archcontext.projection-apply-recovery-intent/v1", PROJECTION_APPLY_RECOVERY_PROOF_SCHEMA_VERSION = "archcontext.projection-apply-recovery-proof/v1", PROJECTION_APPLY_RECOVERY_RESULT_SCHEMA_VERSION = "archcontext.projection-apply-recovery-result/v1", ARCHITECTURE_REFRESH_SIGNAL_SCHEMA_VERSION = "archcontext.architecture-refresh-signal/v1", ARCHCTX_CAPABILITIES_SCHEMA_VERSION = "archcontext.capabilities/v1", ARCHITECTURE_DOCS_RENDERER_VERSION = "archcontext.docs-renderer/v4", AGENT_CONTEXT_RENDERER_VERSION = "archcontext.agent-context-renderer/v1", PROJECTION_MODES, PROJECTION_PRIOR_COMMITTED_APPLY_OPERATIONS, PROJECTION_TARGETS, ARCHITECTURE_MAJOR_CHANGE_REASON_CODES, ARCHCTX_FEATURES, SHA256_DIGEST, ISO_INSTANT;
1130
1172
  var init_projection = __esm(() => {
1131
1173
  init_schema();
1132
1174
  PROJECTION_MODES = ["check", "plan", "apply", "adopt"];
1175
+ PROJECTION_PRIOR_COMMITTED_APPLY_OPERATIONS = ["delete", "write"];
1133
1176
  PROJECTION_TARGETS = ["agent-context", "architecture-docs"];
1134
1177
  ARCHITECTURE_MAJOR_CHANGE_REASON_CODES = [
1135
1178
  "constraint-changed",
@@ -1152,11 +1195,14 @@ var init_projection = __esm(() => {
1152
1195
  "module-statistics-v1",
1153
1196
  "projection-apply-receipt-v1",
1154
1197
  "projection-apply-recovery-v1",
1198
+ "projection-prior-committed-applies-v1",
1155
1199
  "projection-protocol-v2",
1156
1200
  "recommendation-v3",
1157
1201
  "refactor-assessment-v1",
1158
1202
  "refactor-resolution-v1"
1159
1203
  ];
1204
+ SHA256_DIGEST = /^sha256:[a-f0-9]{64}$/;
1205
+ ISO_INSTANT = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?Z$/;
1160
1206
  });
1161
1207
 
1162
1208
  // packages/contracts/src/product-version.ts
@@ -1213,7 +1259,7 @@ function productVersionManifest() {
1213
1259
  }
1214
1260
  };
1215
1261
  }
1216
- var ARCHCONTEXT_PRODUCT_NAME = "archctx", ARCHCONTEXT_PRODUCT_VERSION = "0.5.8", ARCHCONTEXT_PACKAGE_MANAGER = "bun@1.4.0", ARCHCONTEXT_NODE_RANGE = ">=22.22 <26", LOCAL_RUNTIME_RPC_SCHEMA_VERSION = "archcontext.runtime-rpc/v1", ARCHCONTEXT_SCHEMA_SET_VERSION = "2026-07-11.explorer-view-v2";
1262
+ var ARCHCONTEXT_PRODUCT_NAME = "archctx", ARCHCONTEXT_PRODUCT_VERSION = "0.5.9", ARCHCONTEXT_PACKAGE_MANAGER = "bun@1.4.0", ARCHCONTEXT_NODE_RANGE = ">=22.22 <26", LOCAL_RUNTIME_RPC_SCHEMA_VERSION = "archcontext.runtime-rpc/v1", ARCHCONTEXT_SCHEMA_SET_VERSION = "2026-07-11.explorer-view-v2";
1217
1263
 
1218
1264
  // packages/contracts/src/refactor.ts
1219
1265
  function refactorProposalDigest(proposal) {
@@ -2558,7 +2604,7 @@ var init_src2 = __esm(() => {
2558
2604
 
2559
2605
  // node_modules/.bun/@colbymchenry+codegraph@1.5.0/node_modules/@colbymchenry/codegraph/npm-sdk.js
2560
2606
  var require_npm_sdk = __commonJS(function(exports, module) {
2561
- var __dirname = "/Users/kito/Projects/arch-context-release-0.5.8/node_modules/.bun/@colbymchenry+codegraph@1.5.0/node_modules/@colbymchenry/codegraph";
2607
+ var __dirname = "/Users/ancienttwo/Projects/arch-context-wt-release-0.5.9/node_modules/.bun/@colbymchenry+codegraph@1.5.0/node_modules/@colbymchenry/codegraph";
2562
2608
  var path = __require("path");
2563
2609
  var os = __require("os");
2564
2610
  var fs = __require("fs");
@@ -17517,11 +17563,14 @@ class ChangeSetEngine {
17517
17563
  if (options.faultAfterOperations && applied >= options.faultAfterOperations)
17518
17564
  throw new Error("fault-injection");
17519
17565
  }
17520
- for (const projection3 of deps.projection.planGeneratedProjection(root)) {
17521
- await this.applyFileOperation(root, projection3.path, projection3.expectedHash, projection3.body, projection3.operation, backups, journalId, applied + 1, agentContextPaths);
17522
- applied += 1;
17523
- if (options.faultAfterOperations && applied >= options.faultAfterOperations)
17524
- throw new Error("fault-injection");
17566
+ const explicitProjectionOnly = draft.operations.length > 0 && draft.operations.every((operation) => (operation.op === "render_projection" || operation.op === "render_agent_context") && operation.projectionFiles !== undefined && operation.projectionFiles.length > 0);
17567
+ if (!explicitProjectionOnly) {
17568
+ for (const projection3 of deps.projection.planGeneratedProjection(root)) {
17569
+ await this.applyFileOperation(root, projection3.path, projection3.expectedHash, projection3.body, projection3.operation, backups, journalId, applied + 1, agentContextPaths);
17570
+ applied += 1;
17571
+ if (options.faultAfterOperations && applied >= options.faultAfterOperations)
17572
+ throw new Error("fault-injection");
17573
+ }
17525
17574
  }
17526
17575
  await this.validateModel(root, draft, deps, "after");
17527
17576
  const commit = await options.afterModelValidatedBeforeCommit?.({ root, draft, journalId });
@@ -17581,7 +17630,8 @@ class ChangeSetEngine {
17581
17630
  tempPath,
17582
17631
  backupPath,
17583
17632
  existed,
17584
- operation
17633
+ operation,
17634
+ bodyHash: operation === "delete_entity" ? "missing" : digestJson({ body })
17585
17635
  });
17586
17636
  }
17587
17637
  backups.push(backup);
@@ -27222,6 +27272,32 @@ class SqliteLocalStore {
27222
27272
  ...recoveryProof ? { recoveryProof } : {}
27223
27273
  };
27224
27274
  }
27275
+ async listCommittedChangeSetsForTaskSession(root, taskSessionId) {
27276
+ const db = await this.database();
27277
+ const canonicalRoot = canonicalRepositoryRoot2(root);
27278
+ const rows = db.prepare(`SELECT journal.journal_id, journal.changeset_id, journal.root, journal.metadata_json, journal.files_json,
27279
+ journal.completed_at, journal.updated_at, receipt.apply_id, receipt.lookup_key
27280
+ FROM changeset_journal journal
27281
+ LEFT JOIN projection_apply_receipts receipt ON receipt.journal_id = journal.journal_id
27282
+ WHERE journal.status = 'committed'
27283
+ ORDER BY journal.completed_at ASC, journal.journal_id ASC`).all();
27284
+ const matched = [];
27285
+ for (const row of rows) {
27286
+ if (canonicalRepositoryRoot2(String(row.root)) !== canonicalRoot)
27287
+ continue;
27288
+ const journalId = String(row.journal_id);
27289
+ if (readChangeSetJournalTaskSessionId(String(row.metadata_json), journalId) !== taskSessionId)
27290
+ continue;
27291
+ matched.push({
27292
+ journalId,
27293
+ changeSetId: String(row.changeset_id),
27294
+ committedAt: String(row.completed_at ?? row.updated_at),
27295
+ ...row.apply_id === null || row.apply_id === undefined ? {} : { applyId: String(row.apply_id), lookupKey: String(row.lookup_key) },
27296
+ files: committedChangeSetJournalFiles(String(row.files_json), journalId)
27297
+ });
27298
+ }
27299
+ return matched;
27300
+ }
27225
27301
  async consumeProjectionApplyReceiptRecovery(proof) {
27226
27302
  const db = await this.database();
27227
27303
  db.exec("BEGIN IMMEDIATE");
@@ -30880,6 +30956,59 @@ function changeSetMetadata(draft) {
30880
30956
  idempotencyKey: draft.idempotencyKey
30881
30957
  };
30882
30958
  }
30959
+ function readChangeSetJournalTaskSessionId(metadataJson, journalId) {
30960
+ let metadata;
30961
+ try {
30962
+ metadata = JSON.parse(metadataJson);
30963
+ } catch {
30964
+ throw new Error(`changeset-journal-metadata-malformed: ${journalId}`);
30965
+ }
30966
+ if (!isJsonRecord(metadata))
30967
+ throw new Error(`changeset-journal-metadata-malformed: ${journalId}`);
30968
+ const reason = metadata.reason;
30969
+ if (!isJsonRecord(reason) || typeof reason.taskSessionId !== "string" || reason.taskSessionId === "") {
30970
+ throw new Error(`changeset-journal-reason-malformed: ${journalId}`);
30971
+ }
30972
+ return reason.taskSessionId;
30973
+ }
30974
+ function committedChangeSetFileOperation(operation, journalId, path) {
30975
+ switch (operation) {
30976
+ case "delete_entity":
30977
+ return "delete";
30978
+ case "create_entity":
30979
+ case "update_entity_fields":
30980
+ case "write_policy":
30981
+ case "write_waiver":
30982
+ case "render_projection":
30983
+ case "render_agent_context":
30984
+ return "write";
30985
+ default:
30986
+ throw new Error(`changeset-journal-file-malformed: ${journalId}:${path}:${operation}`);
30987
+ }
30988
+ }
30989
+ function committedChangeSetJournalFiles(filesJson, journalId) {
30990
+ let parsed;
30991
+ try {
30992
+ parsed = JSON.parse(filesJson);
30993
+ } catch {
30994
+ throw new Error(`changeset-journal-files-malformed: ${journalId}`);
30995
+ }
30996
+ if (!Array.isArray(parsed))
30997
+ throw new Error(`changeset-journal-files-malformed: ${journalId}`);
30998
+ return parsed.map((entry) => {
30999
+ if (!isJsonRecord(entry) || typeof entry.path !== "string" || typeof entry.operation !== "string") {
31000
+ throw new Error(`changeset-journal-file-malformed: ${journalId}`);
31001
+ }
31002
+ if (typeof entry.bodyHash !== "string" || entry.bodyHash === "") {
31003
+ throw new Error(`changeset-journal-file-body-hash-missing: ${journalId}:${entry.path}`);
31004
+ }
31005
+ return {
31006
+ path: entry.path,
31007
+ operation: committedChangeSetFileOperation(entry.operation, journalId, entry.path),
31008
+ hash: entry.bodyHash
31009
+ };
31010
+ }).sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0);
31011
+ }
30883
31012
  function readChangeSetJournalMetadata(db, journalId) {
30884
31013
  const row = db.prepare("SELECT metadata_json FROM changeset_journal WHERE journal_id = ?").get(journalId);
30885
31014
  if (!row)
@@ -34081,6 +34210,34 @@ class ArchctxDaemon {
34081
34210
  ...inspection ?? {}
34082
34211
  });
34083
34212
  }
34213
+ async listProjectionPriorCommittedApplies(root, requestId) {
34214
+ this.assertRunning();
34215
+ await this.openSession(root);
34216
+ let committed;
34217
+ try {
34218
+ committed = await this.localStore.listCommittedChangeSetsForTaskSession(root, requestId);
34219
+ } catch (error) {
34220
+ return errorEnvelope("projection.prior-committed-applies", "AC_PRECONDITION_FAILED", error instanceof Error ? error.message : String(error));
34221
+ }
34222
+ const byChangeSetId = new Map;
34223
+ for (const entry of committed) {
34224
+ if (entry.files.length === 0)
34225
+ continue;
34226
+ byChangeSetId.set(entry.changeSetId, {
34227
+ ...entry.applyId === undefined || entry.lookupKey === undefined ? {} : { applyId: entry.applyId, lookupKey: entry.lookupKey },
34228
+ requestId,
34229
+ changeSetId: entry.changeSetId,
34230
+ committedAt: entry.committedAt,
34231
+ files: entry.files
34232
+ });
34233
+ }
34234
+ const applies = [...byChangeSetId.values()].sort((left, right) => left.changeSetId < right.changeSetId ? -1 : left.changeSetId > right.changeSetId ? 1 : 0);
34235
+ const issues = applies.length === 0 ? [] : projectionPriorCommittedAppliesIssues(applies, requestId);
34236
+ if (issues.length > 0) {
34237
+ return errorEnvelope("projection.prior-committed-applies", "AC_SCHEMA_INVALID", `projection prior committed applies invariant failed: ${issues.join("; ")}`);
34238
+ }
34239
+ return okEnvelope("projection.prior-committed-applies", { applies });
34240
+ }
34084
34241
  async recoverProjectionApply(root, intent) {
34085
34242
  this.assertRunning();
34086
34243
  const intentIssues = projectionApplyRecoveryIntentInvariantIssues(intent);
@@ -36900,6 +37057,9 @@ class RuntimeRpcClient {
36900
37057
  inspectProjectionApplyReceipt(root, lookupKey) {
36901
37058
  return this.call("inspectProjectionApplyReceipt", [root, lookupKey]);
36902
37059
  }
37060
+ listProjectionPriorCommittedApplies(root, requestId) {
37061
+ return this.call("listProjectionPriorCommittedApplies", [root, requestId]);
37062
+ }
36903
37063
  recoverProjectionApply(root, intent) {
36904
37064
  return this.call("recoverProjectionApply", [root, intent]);
36905
37065
  }
@@ -37258,6 +37418,8 @@ class ArchctxRuntimeRpcServer {
37258
37418
  return this.daemon.applyUpdate(params[0], params[1]);
37259
37419
  case "inspectProjectionApplyReceipt":
37260
37420
  return this.daemon.inspectProjectionApplyReceipt(params[0], params[1]);
37421
+ case "listProjectionPriorCommittedApplies":
37422
+ return this.daemon.listProjectionPriorCommittedApplies(params[0], params[1]);
37261
37423
  case "recoverProjectionApply":
37262
37424
  return this.daemon.recoverProjectionApply(params[0], params[1]);
37263
37425
  case "ledgerState":
@@ -39509,6 +39671,9 @@ class RuntimeRpcClient2 {
39509
39671
  inspectProjectionApplyReceipt(root, lookupKey) {
39510
39672
  return this.call("inspectProjectionApplyReceipt", [root, lookupKey]);
39511
39673
  }
39674
+ listProjectionPriorCommittedApplies(root, requestId) {
39675
+ return this.call("listProjectionPriorCommittedApplies", [root, requestId]);
39676
+ }
39512
39677
  recoverProjectionApply(root, intent) {
39513
39678
  return this.call("recoverProjectionApply", [root, intent]);
39514
39679
  }
@@ -41120,7 +41285,7 @@ function architectureProjectionProfile(args2) {
41120
41285
  }
41121
41286
  return profile;
41122
41287
  }
41123
- async function runArchitectureDocsAdoptionCommand(args2, root, daemon, projection3, profile, generatedAt, protocolRequest) {
41288
+ async function runArchitectureDocsAdoptionCommand(args2, root, daemon, projection3, profile, generatedAt, protocolRequest, priorCommittedApplies = []) {
41124
41289
  if (profile !== REPO_HARNESS_PROJECTION_PROFILE) {
41125
41290
  return errorEnvelope("docs.adopt", "AC_SCHEMA_INVALID", `docs adopt requires --profile ${REPO_HARNESS_PROJECTION_PROFILE}`);
41126
41291
  }
@@ -41172,7 +41337,7 @@ async function runArchitectureDocsAdoptionCommand(args2, root, daemon, projectio
41172
41337
  return errorEnvelope("docs.adopt", "AC_PRECONDITION_FAILED", `projection-adoption-fixed-point-unproven: drift=${drift}; rejected=${reasons}; digest=${canonical.plan.projectionDigest === canonicalFirst.plan.projectionDigest ? "stable" : "changed"}`);
41173
41338
  }
41174
41339
  if (protocolRequest) {
41175
- return applyProjectionProtocolFixedPoint(protocolRequest, projection3, canonical, adoption2.changeSetId, root, daemon);
41340
+ return applyProjectionProtocolFixedPoint(protocolRequest, projection3, canonical, adoption2.changeSetId, root, daemon, priorCommittedApplies);
41176
41341
  }
41177
41342
  const filesByPath = new Map;
41178
41343
  for (const file of canonical.files)
@@ -41196,7 +41361,7 @@ async function runArchitectureDocsAdoptionCommand(args2, root, daemon, projectio
41196
41361
  apply: applied.data
41197
41362
  });
41198
41363
  }
41199
- async function applyProjectionProtocolFixedPoint(request, input, fixedPoint, changeSetId, root, daemon) {
41364
+ async function applyProjectionProtocolFixedPoint(request, input, fixedPoint, changeSetId, root, daemon, priorCommittedApplies) {
41200
41365
  const committedFiles = projectionProtocolFilesForExpectedOutput(root, fixedPoint);
41201
41366
  const committedSignals = request.acceptedChange ? input.plan.refreshSignals.map((signal) => ({
41202
41367
  ...signal,
@@ -41214,7 +41379,7 @@ async function applyProjectionProtocolFixedPoint(request, input, fixedPoint, cha
41214
41379
  const appliedResult = projectionProtocolResult(request, input, "applied", fixedPoint, applyIdentity, {
41215
41380
  files: committedFiles,
41216
41381
  refreshSignals: committedSignals
41217
- });
41382
+ }, priorCommittedApplies);
41218
41383
  let recoveryBinding;
41219
41384
  if (applyIdentity) {
41220
41385
  try {
@@ -41300,6 +41465,15 @@ async function runProjectionProtocolCommand(args2, cwd, daemon) {
41300
41465
  }
41301
41466
  const root = findRepositoryRoot(cwd);
41302
41467
  const generatedAt = new Date(0).toISOString();
41468
+ let priorCommittedApplies;
41469
+ try {
41470
+ const listed = await daemon.listProjectionPriorCommittedApplies(root, request.requestId);
41471
+ if (!listed.ok)
41472
+ return listed;
41473
+ priorCommittedApplies = parseProjectionPriorCommittedApplies(listed.data, request.requestId);
41474
+ } catch (error) {
41475
+ return errorEnvelope("projection.run", "AC_PRECONDITION_FAILED", `projection prior committed apply lookup failed: ${error instanceof Error ? error.message : String(error)}`);
41476
+ }
41303
41477
  if (request.mode === "apply" && request.acceptedChange) {
41304
41478
  try {
41305
41479
  assertProjectionExpectedSnapshotAgainstModel(request, root, loadNativeModelFromArchContext(root));
@@ -41326,7 +41500,7 @@ async function runProjectionProtocolCommand(args2, cwd, daemon) {
41326
41500
  }
41327
41501
  const blocked = projectionProtocolHumanStatus(request, projection3);
41328
41502
  if (blocked)
41329
- return projectionProtocolEnvelope(request, projection3, blocked);
41503
+ return projectionProtocolEnvelope(request, projection3, blocked, projection3, priorCommittedApplies);
41330
41504
  if (request.mode === "adopt") {
41331
41505
  const expectedWorktreeDigest = computeWorktreeDigest(root);
41332
41506
  const adopted = await runArchitectureDocsAdoptionCommand([
@@ -41340,7 +41514,7 @@ async function runProjectionProtocolCommand(args2, cwd, daemon) {
41340
41514
  expectedWorktreeDigest,
41341
41515
  "--task-session-id",
41342
41516
  request.requestId
41343
- ], root, daemon, projection3, REPO_HARNESS_PROJECTION_PROFILE, generatedAt, request);
41517
+ ], root, daemon, projection3, REPO_HARNESS_PROJECTION_PROFILE, generatedAt, request, priorCommittedApplies);
41344
41518
  if (!adopted.ok)
41345
41519
  return adopted;
41346
41520
  return adopted;
@@ -41356,10 +41530,23 @@ async function runProjectionProtocolCommand(args2, cwd, daemon) {
41356
41530
  } catch (error) {
41357
41531
  return errorEnvelope("projection.run", "AC_PRECONDITION_FAILED", error instanceof Error ? error.message : String(error));
41358
41532
  }
41359
- return applyProjectionProtocolFixedPoint(request, projection3, fixedPointProjection, changeSetId, root, daemon);
41533
+ return applyProjectionProtocolFixedPoint(request, projection3, fixedPointProjection, changeSetId, root, daemon, priorCommittedApplies);
41360
41534
  }
41361
41535
  const status = projection3.plan.drift.ok ? "noop" : "planned";
41362
- return projectionProtocolEnvelope(request, projection3, status);
41536
+ return projectionProtocolEnvelope(request, projection3, status, projection3, priorCommittedApplies);
41537
+ }
41538
+ function parseProjectionPriorCommittedApplies(value, requestId) {
41539
+ if (!value || typeof value !== "object" || Array.isArray(value))
41540
+ throw new Error("prior committed apply reply must be an object");
41541
+ const applies = value.applies;
41542
+ if (!Array.isArray(applies))
41543
+ throw new Error("prior committed apply reply must carry an applies array");
41544
+ if (applies.length === 0)
41545
+ return [];
41546
+ const issues = projectionPriorCommittedAppliesIssues(applies, requestId);
41547
+ if (issues.length > 0)
41548
+ throw new Error(`prior committed apply invariant failed: ${issues.join("; ")}`);
41549
+ return applies;
41363
41550
  }
41364
41551
  async function runProjectionApplyRecoveryCommand(args2, cwd, daemon) {
41365
41552
  const raw = readFlag(args2, "--request-json");
@@ -41547,10 +41734,10 @@ function projectionProtocolHumanStatus(request, projection3) {
41547
41734
  return "adoption-required";
41548
41735
  return otherRejection || humanSignal ? "human-action-required" : null;
41549
41736
  }
41550
- function projectionProtocolEnvelope(request, input, status, output = input) {
41551
- return projectionProtocolResultEnvelope(projectionProtocolResult(request, input, status, output));
41737
+ function projectionProtocolEnvelope(request, input, status, output, priorCommittedApplies) {
41738
+ return projectionProtocolResultEnvelope(projectionProtocolResult(request, input, status, output, undefined, undefined, priorCommittedApplies));
41552
41739
  }
41553
- function projectionProtocolResult(request, input, status, output = input, applyReceipt, overrides) {
41740
+ function projectionProtocolResult(request, input, status, output = input, applyReceipt, overrides, priorCommittedApplies = []) {
41554
41741
  const inputSnapshot = projectionProtocolSnapshot(request, input.plan.provenance);
41555
41742
  const outputSnapshot = projectionProtocolSnapshot(request, output.plan.provenance);
41556
41743
  const files = overrides?.files ?? projectionProtocolFiles(input);
@@ -41577,7 +41764,8 @@ function projectionProtocolResult(request, input, status, output = input, applyR
41577
41764
  files,
41578
41765
  humanActions,
41579
41766
  refreshSignals,
41580
- ...applyReceipt ? { applyReceipt } : {}
41767
+ ...applyReceipt ? { applyReceipt } : {},
41768
+ ...priorCommittedApplies.length > 0 ? { priorCommittedApplies } : {}
41581
41769
  };
41582
41770
  const receiptDigest = projectionResultReceiptDigest(withoutReceipt);
41583
41771
  const result = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "archctx",
3
- "version": "0.5.8",
3
+ "version": "0.5.9",
4
4
  "description": "Local architecture context CLI for agentic coding workflows.",
5
5
  "private": false,
6
6
  "type": "module",
@@ -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", "module-statistics-v1", "projection-apply-receipt-v1", "projection-apply-recovery-v1", "projection-protocol-v2", "recommendation-v3", "refactor-assessment-v1", "refactor-resolution-v1"] }
42
+ "items": { "enum": ["architecture-docs-renderer-v2", "architecture-refresh-signal-v1", "module-statistics-v1", "projection-apply-receipt-v1", "projection-apply-recovery-v1", "projection-prior-committed-applies-v1", "projection-protocol-v2", "recommendation-v3", "refactor-assessment-v1", "refactor-resolution-v1"] }
43
43
  }
44
44
  }
45
45
  }
@@ -16,6 +16,7 @@
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
18
  "applyReceipt": { "$ref": "#/$defs/applyIdentity" },
19
+ "priorCommittedApplies": { "type": "array", "minItems": 1, "description": "Omitted, never empty. Canonical wire order is by changeSetId and is enforced by projectionResultInvariantIssues.", "items": { "$ref": "#/$defs/priorCommittedApply" } },
19
20
  "receiptDigest": { "$ref": "#/$defs/digest" }
20
21
  },
21
22
  "allOf": [
@@ -166,6 +167,45 @@
166
167
  "affectedNodeIds": { "type": "array", "minItems": 1, "uniqueItems": true, "items": { "type": "string", "minLength": 1 } }
167
168
  }
168
169
  },
170
+ "priorCommittedApply": {
171
+ "type": "object",
172
+ "additionalProperties": false,
173
+ "required": ["requestId", "changeSetId", "committedAt", "files"],
174
+ "properties": {
175
+ "applyId": { "$ref": "#/$defs/digest" },
176
+ "lookupKey": { "$ref": "#/$defs/digest" },
177
+ "requestId": { "type": "string", "pattern": "^[a-zA-Z0-9_.:-]+$", "minLength": 1 },
178
+ "changeSetId": { "type": "string", "minLength": 1 },
179
+ "committedAt": { "type": "string", "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d{1,3})?Z$" },
180
+ "files": {
181
+ "type": "array",
182
+ "minItems": 1,
183
+ "description": "Canonical wire order is by path and is enforced by projectionResultInvariantIssues.",
184
+ "items": {
185
+ "type": "object",
186
+ "additionalProperties": false,
187
+ "required": ["path", "operation", "hash"],
188
+ "properties": {
189
+ "path": {
190
+ "type": "string",
191
+ "minLength": 1,
192
+ "not": { "anyOf": [{ "pattern": "^/" }, { "pattern": "\\\\" }, { "pattern": "(^|/)\\.\\.(/|$)" }] }
193
+ },
194
+ "operation": { "enum": ["delete", "write"] },
195
+ "hash": { "type": "string", "pattern": "^(?:sha256:[a-f0-9]{64}|missing)$" }
196
+ },
197
+ "allOf": [
198
+ {
199
+ "if": { "properties": { "operation": { "const": "delete" } }, "required": ["operation"] },
200
+ "then": { "properties": { "hash": { "const": "missing" } } },
201
+ "else": { "properties": { "hash": { "$ref": "#/$defs/digest" } } }
202
+ }
203
+ ]
204
+ }
205
+ }
206
+ },
207
+ "dependentRequired": { "applyId": ["lookupKey"], "lookupKey": ["applyId"] }
208
+ },
169
209
  "applyIdentity": {
170
210
  "type": "object",
171
211
  "additionalProperties": false,