archctx 0.4.7 → 0.4.8

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/bin/archctx.mjs CHANGED
@@ -961,8 +961,123 @@ function projectionApplyReceiptInvariantIssues(input) {
961
961
  if (digestJson(input.result.applyReceipt) !== digestJson(input.identity)) {
962
962
  issues.push("projection apply receipt result must carry the same apply identity");
963
963
  }
964
+ if (input.recovery)
965
+ issues.push(...projectionApplyRecoveryBindingInvariantIssues(input.recovery, input));
964
966
  return issues;
965
967
  }
968
+ function projectionApplyRecoveryBindingInvariantIssues(binding, receipt) {
969
+ const issues = [
970
+ ...sortedUniqueIssues("recovery.targets", binding.targets),
971
+ ...sortedUniqueIssues("recovery.changedPaths", binding.changedPaths)
972
+ ];
973
+ if (binding.schemaVersion !== PROJECTION_APPLY_RECOVERY_BINDING_SCHEMA_VERSION) {
974
+ issues.push("recovery schemaVersion is invalid");
975
+ }
976
+ if (binding.targets.length === 0)
977
+ issues.push("recovery targets must contain at least one projection target");
978
+ if (receipt) {
979
+ if (binding.receiptDigest !== receipt.result.receiptDigest) {
980
+ issues.push("recovery receiptDigest must match the committed result receiptDigest");
981
+ }
982
+ if (binding.originalExpectedSnapshot.repositoryId !== receipt.result.inputSnapshot.repositoryId || binding.originalExpectedSnapshot.workspaceId !== receipt.result.inputSnapshot.workspaceId || binding.originalExpectedSnapshot.headSha !== receipt.result.inputSnapshot.headSha || binding.originalExpectedSnapshot.worktreeDigest !== receipt.result.inputSnapshot.worktreeDigest) {
983
+ issues.push("recovery originalExpectedSnapshot must match the committed input snapshot");
984
+ }
985
+ if (binding.rendererVersion !== receipt.result.outputSnapshot.rendererVersion || binding.layoutVersion !== receipt.result.outputSnapshot.layoutVersion || digestJson(binding.generatedFrom) !== digestJson(receipt.result.outputSnapshot.generatedFrom)) {
986
+ issues.push("recovery renderer, layout, and CodeGraph provenance must match the committed output snapshot");
987
+ }
988
+ const expectedDigests = projectionApplyReceiptResultingDigests(receipt);
989
+ if (!expectedDigests || digestJson(binding.expectedResultingDigests) !== digestJson(expectedDigests)) {
990
+ issues.push("recovery expectedResultingDigests must match committed refresh signals");
991
+ }
992
+ }
993
+ return issues;
994
+ }
995
+ function projectionApplyRecoveryIntentInvariantIssues(input) {
996
+ const issues = [];
997
+ if (input.schemaVersion !== PROJECTION_APPLY_RECOVERY_INTENT_SCHEMA_VERSION)
998
+ issues.push("recovery intent schemaVersion is invalid");
999
+ if (!/^[a-zA-Z0-9_.:-]+$/.test(input.requestId))
1000
+ issues.push("recovery intent requestId must use the stable identifier character set");
1001
+ if (input.profile !== "repo-harness/v1")
1002
+ issues.push("recovery intent profile is invalid");
1003
+ for (const field of ["lookupKey", "applyId"]) {
1004
+ if (!/^sha256:[a-f0-9]{64}$/.test(input.receipt[field]))
1005
+ issues.push(`recovery intent receipt.${field} must be a SHA-256 digest`);
1006
+ }
1007
+ return issues;
1008
+ }
1009
+ function projectionApplyRecoveryProofDigest(input) {
1010
+ return digestJson(input);
1011
+ }
1012
+ function projectionApplyRecoveryProofInvariantIssues(input) {
1013
+ const issues = [
1014
+ ...sortedUniqueIssues("acceptedChange.reasonCodes", input.acceptedChange.reasonCodes),
1015
+ ...sortedUniqueIssues("acceptedChange.affectedNodeIds", input.acceptedChange.affectedNodeIds)
1016
+ ];
1017
+ if (input.schemaVersion !== PROJECTION_APPLY_RECOVERY_PROOF_SCHEMA_VERSION)
1018
+ issues.push("recovery proof schemaVersion is invalid");
1019
+ if (!/^[a-zA-Z0-9_.:-]+$/.test(input.requestId))
1020
+ issues.push("recovery proof requestId must use the stable identifier character set");
1021
+ if (input.current.snapshot.generatedFrom.codeGraphStatus !== "ready")
1022
+ issues.push("recovery proof requires a ready current CodeGraph snapshot");
1023
+ const { proofDigest: _proofDigest, deliveryStatus: _deliveryStatus, ...payload } = input;
1024
+ if (projectionApplyRecoveryProofDigest(payload) !== input.proofDigest) {
1025
+ issues.push("recovery proofDigest must bind the semantic proof payload");
1026
+ }
1027
+ return issues;
1028
+ }
1029
+ function projectionApplyRecoveryResultInvariantIssues(input) {
1030
+ const issues = [
1031
+ ...projectionApplyRecoveryProofInvariantIssues(input.proof),
1032
+ ...input.refreshSignals.flatMap((signal, index) => architectureRefreshSignalInvariantIssues(signal, `refreshSignals[${index}]`)),
1033
+ ...sortedUniqueIssues("refreshSignals.signalId", input.refreshSignals.map((signal) => signal.signalId))
1034
+ ];
1035
+ if (input.schemaVersion !== PROJECTION_APPLY_RECOVERY_RESULT_SCHEMA_VERSION) {
1036
+ issues.push("recovery result schemaVersion is invalid");
1037
+ }
1038
+ if (input.proof.deliveryStatus === "already-delivered" && input.refreshSignals.length > 0) {
1039
+ issues.push("already-delivered recovery result cannot repeat refreshSignals");
1040
+ }
1041
+ for (const [index, signal] of input.refreshSignals.entries()) {
1042
+ const prefix = `refreshSignals[${index}]`;
1043
+ if (signal.projectionReceiptDigest !== input.proof.receipt.receiptDigest) {
1044
+ issues.push(`${prefix}.projectionReceiptDigest must match recovery proof receiptDigest`);
1045
+ }
1046
+ if (digestJson(signal.acceptedChange) !== digestJson(input.proof.acceptedChange)) {
1047
+ issues.push(`${prefix}.acceptedChange must match recovery proof approval`);
1048
+ }
1049
+ if (digestJson(signal.resultingDigests) !== digestJson(input.proof.expectedResultingDigests)) {
1050
+ issues.push(`${prefix}.resultingDigests must match recovery proof`);
1051
+ }
1052
+ }
1053
+ return issues;
1054
+ }
1055
+ function projectionApplyRecoveryProofReceiptInvariantIssues(proof, receipt) {
1056
+ const issues = [
1057
+ ...projectionApplyRecoveryProofInvariantIssues(proof),
1058
+ ...projectionApplyReceiptInvariantIssues(receipt)
1059
+ ];
1060
+ const binding = receipt.recovery;
1061
+ if (!binding)
1062
+ return [...issues, "committed projection receipt does not support semantic recovery"];
1063
+ if (proof.receipt.lookupKey !== receipt.identity.lookupKey || proof.receipt.applyId !== receipt.identity.applyId || proof.receipt.receiptDigest !== receipt.result.receiptDigest) {
1064
+ issues.push("recovery proof receipt identity must match the committed receipt");
1065
+ }
1066
+ if (digestJson(proof.acceptedChange) !== digestJson(receipt.identity.acceptedChange) || digestJson(proof.expectedResultingDigests) !== digestJson(binding.expectedResultingDigests)) {
1067
+ issues.push("recovery proof approval binding must match the committed receipt");
1068
+ }
1069
+ if (digestJson(proof.current.resultingDigests) !== digestJson(binding.expectedResultingDigests) || proof.current.ownedOutputDigest !== binding.ownedOutputDigest || proof.current.snapshot.rendererVersion !== binding.rendererVersion || proof.current.snapshot.layoutVersion !== binding.layoutVersion || digestJson(proof.current.snapshot.generatedFrom) !== digestJson(binding.generatedFrom)) {
1070
+ issues.push("recovery proof current state must match the committed recovery binding");
1071
+ }
1072
+ return issues;
1073
+ }
1074
+ function projectionApplyReceiptResultingDigests(receipt) {
1075
+ const signals = receipt.result.refreshSignals;
1076
+ if (signals.length === 0)
1077
+ return;
1078
+ const [first] = signals;
1079
+ return signals.every((signal) => digestJson(signal.resultingDigests) === digestJson(first.resultingDigests)) ? first.resultingDigests : undefined;
1080
+ }
966
1081
  function architectureRefreshSignalInvariantIssues(input, prefix = "signal") {
967
1082
  const issues = [
968
1083
  ...sortedUniqueIssues(`${prefix}.reasonCodes`, input.reasonCodes),
@@ -1006,7 +1121,7 @@ function sortedUniqueIssues(label, values) {
1006
1121
  const expected = [...new Set(values)].sort();
1007
1122
  return expected.length === values.length && expected.every((value, index) => value === values[index]) ? [] : [`${label} must be sorted and unique`];
1008
1123
  }
1009
- 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", 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;
1124
+ 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;
1010
1125
  var init_projection = __esm(() => {
1011
1126
  init_schema();
1012
1127
  PROJECTION_MODES = ["check", "plan", "apply", "adopt"];
@@ -1030,6 +1145,7 @@ var init_projection = __esm(() => {
1030
1145
  "architecture-docs-renderer-v2",
1031
1146
  "architecture-refresh-signal-v1",
1032
1147
  "projection-apply-receipt-v1",
1148
+ "projection-apply-recovery-v1",
1033
1149
  "projection-protocol-v2"
1034
1150
  ];
1035
1151
  });
@@ -1088,7 +1204,7 @@ function productVersionManifest() {
1088
1204
  }
1089
1205
  };
1090
1206
  }
1091
- var ARCHCONTEXT_PRODUCT_NAME = "archctx", ARCHCONTEXT_PRODUCT_VERSION = "0.4.7", ARCHCONTEXT_PACKAGE_MANAGER = "bun@1.3.10", 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";
1207
+ var ARCHCONTEXT_PRODUCT_NAME = "archctx", ARCHCONTEXT_PRODUCT_VERSION = "0.4.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";
1092
1208
 
1093
1209
  // packages/contracts/src/validator.ts
1094
1210
  function validateJsonSchema(schema, value) {
@@ -1837,7 +1953,7 @@ var init_src2 = __esm(() => {
1837
1953
 
1838
1954
  // node_modules/.bun/@colbymchenry+codegraph@1.5.0/node_modules/@colbymchenry/codegraph/npm-sdk.js
1839
1955
  var require_npm_sdk = __commonJS(function(exports, module) {
1840
- var __dirname = "/Users/ancienttwo/Projects/arch-context-wt-projection-proof-apply-reconcile/node_modules/.bun/@colbymchenry+codegraph@1.5.0/node_modules/@colbymchenry/codegraph";
1956
+ var __dirname = "/Users/ancienttwo/Projects/arch-context-wt-release-048/node_modules/.bun/@colbymchenry+codegraph@1.5.0/node_modules/@colbymchenry/codegraph";
1841
1957
  var path = __require("path");
1842
1958
  var os = __require("os");
1843
1959
  var fs = __require("fs");
@@ -5623,7 +5739,7 @@ var init_src7 = __esm(() => {
5623
5739
 
5624
5740
  // packages/surfaces/cli/src/main.ts
5625
5741
  import { execFileSync as execFileSync8, spawn as spawn2, spawnSync as spawnSync3 } from "child_process";
5626
- import { accessSync as accessSync3, chmodSync as chmodSync5, closeSync as closeSync8, constants, existsSync as existsSync17, mkdirSync as mkdirSync9, openSync as openSync8, readFileSync as readFileSync16, rmSync as rmSync10, statSync as statSync11, writeFileSync as writeFileSync8 } from "fs";
5742
+ import { accessSync as accessSync3, chmodSync as chmodSync6, closeSync as closeSync8, constants, existsSync as existsSync17, mkdirSync as mkdirSync9, openSync as openSync8, readFileSync as readFileSync16, rmSync as rmSync10, statSync as statSync11, writeFileSync as writeFileSync8 } from "fs";
5627
5743
  import { dirname as dirname11, join as join10, resolve as resolve19 } from "path";
5628
5744
  import { fileURLToPath as fileURLToPath2 } from "url";
5629
5745
 
@@ -8339,17 +8455,15 @@ function stickyArchitectureDocumentationProjectionProvenance(current, existingMa
8339
8455
  if (!prior)
8340
8456
  return current;
8341
8457
  assertArchitectureDocumentationProjectionProvenance(prior, current.rendererVersion);
8342
- return architectureDocumentationSemanticProvenanceDigest(prior) === architectureDocumentationSemanticProvenanceDigest(current) ? prior : current;
8458
+ return architectureDocumentationStickyProvenanceDigest(prior) === architectureDocumentationStickyProvenanceDigest(current) ? prior : current;
8343
8459
  } catch {
8344
8460
  return current;
8345
8461
  }
8346
8462
  }
8347
- function architectureDocumentationSemanticProvenanceDigest(provenance) {
8463
+ function architectureDocumentationStickyProvenanceDigest(provenance) {
8348
8464
  return digestJson({
8349
8465
  sourceTreeDigest: provenance.sourceTreeDigest,
8350
8466
  modelDigest: provenance.modelDigest,
8351
- codeGraphDigest: provenance.codeGraphDigest,
8352
- indexedWorktreeDigest: provenance.indexedWorktreeDigest,
8353
8467
  rendererVersion: provenance.rendererVersion,
8354
8468
  layoutVersion: provenance.layoutVersion,
8355
8469
  generatedFrom: provenance.generatedFrom
@@ -13959,6 +14073,12 @@ var LOCAL_SQLITE_MIGRATIONS = [
13959
14073
  )`,
13960
14074
  "CREATE INDEX IF NOT EXISTS idx_projection_apply_receipts_journal ON projection_apply_receipts(journal_id)"
13961
14075
  ]
14076
+ },
14077
+ {
14078
+ id: "0020_projection_apply_recovery_proof",
14079
+ statements: [
14080
+ "ALTER TABLE projection_apply_receipts ADD COLUMN recovery_proof_json TEXT"
14081
+ ]
13962
14082
  }
13963
14083
  ];
13964
14084
  var ARCHCONTEXT_STATE_DIR_ENV = "ARCHCONTEXT_STATE_DIR";
@@ -14128,7 +14248,6 @@ function recoverRuntimeStateTarget(input) {
14128
14248
  ensurePrivateDir(stagingDir);
14129
14249
  assertRuntimeStateRecoveryPrivatePermissions(stagingDir, 448);
14130
14250
  migrateSqliteDatabaseSync(stagingPath);
14131
- compactSqliteDatabase(stagingPath);
14132
14251
  const stagingIntegrity = assertCurrentLocalStore(stagingPath);
14133
14252
  receiptPath = join2(quarantineDirectory, RUNTIME_STATE_RECOVERY_RECEIPT_FILE);
14134
14253
  writeRuntimeStateRecoveryReceipt(receiptPath, {
@@ -14257,7 +14376,6 @@ function migrateLegacyLocalStoreIfNeeded(root = process.cwd(), env = process.env
14257
14376
  integrityCheck.legacy = vacuumLegacySqliteInto(paths.legacyLocalStorePath, stagingPath);
14258
14377
  makePrivateFile(stagingPath);
14259
14378
  migrateSqliteDatabaseSync(stagingPath);
14260
- compactSqliteDatabase(stagingPath);
14261
14379
  integrityCheck.staging = assertCurrentLocalStore(stagingPath);
14262
14380
  publishStagedLocalStore(stagingPath, paths.localStorePath);
14263
14381
  integrityCheck.target = assertCurrentLocalStore(paths.localStorePath);
@@ -14278,10 +14396,17 @@ function migrateLegacyLocalStoreIfNeeded(root = process.cwd(), env = process.env
14278
14396
  function upgradeExistingLocalStoreTarget(paths, integrityCheck) {
14279
14397
  const lock = acquireLegacyMigrationLock(paths);
14280
14398
  try {
14281
- assertUpgradeableLocalStoreTarget(paths.localStorePath);
14282
- integrityCheck.target = assertSqliteIntegrity(paths.localStorePath);
14283
- migrateSqliteDatabaseSync(paths.localStorePath);
14284
- compactSqliteDatabase(paths.localStorePath);
14399
+ const db = openSqliteDatabaseSync(paths.localStorePath);
14400
+ let compacted = false;
14401
+ try {
14402
+ integrityCheck.target = assertUpgradeableLocalStoreTarget(db, paths.localStorePath);
14403
+ migrateOpenedSqliteDatabase(db);
14404
+ compacted = true;
14405
+ } finally {
14406
+ db.close();
14407
+ }
14408
+ if (compacted)
14409
+ removeSqliteSidecars(paths.localStorePath);
14285
14410
  integrityCheck.target = assertCurrentLocalStore(paths.localStorePath);
14286
14411
  delete integrityCheck.error;
14287
14412
  const markerPath = writeLegacyMigrationMarker(paths, integrityCheck, []);
@@ -14909,30 +15034,41 @@ function readAppliedLocalSqliteMigrations(db) {
14909
15034
  return new Set;
14910
15035
  return new Set(db.prepare("SELECT id FROM schema_migrations").all().map((row) => String(row.id)));
14911
15036
  }
14912
- function openSqliteDatabaseSync(databasePath) {
14913
- if (databasePath !== ":memory:")
14914
- ensurePrivateDir(dirname2(databasePath));
14915
- try {
14916
- const nodeSqlite = runtimeRequire("node:sqlite");
14917
- const db2 = new nodeSqlite.DatabaseSync(databasePath);
14918
- return {
14919
- exec: (sql) => db2.exec(sql),
14920
- prepare: (sql) => db2.prepare(sql),
14921
- close: () => db2.close()
14922
- };
14923
- } catch (error) {
14924
- if (error.code !== "ERR_UNKNOWN_BUILTIN_MODULE" && error.code !== "MODULE_NOT_FOUND") {
14925
- throw error;
14926
- }
14927
- }
14928
- const bunSqlite = runtimeRequire("bun:sqlite");
15037
+ function selectSqliteRuntime() {
15038
+ if (process.versions.bun)
15039
+ return "bun";
15040
+ if (process.versions.node)
15041
+ return "node";
15042
+ throw new Error("Unsupported SQLite runtime: archctx requires the Bun or Node runtime, but neither process.versions.bun nor process.versions.node is set.");
15043
+ }
15044
+ function adaptBunSqliteDatabase(bunSqlite, databasePath) {
14929
15045
  const db = new bunSqlite.Database(databasePath);
15046
+ const releaseStatements = () => db.clearQueryCache();
14930
15047
  return {
14931
15048
  exec: (sql) => db.exec(sql),
14932
15049
  prepare: (sql) => db.query(sql),
15050
+ releaseStatements,
15051
+ close: () => {
15052
+ releaseStatements();
15053
+ db.close();
15054
+ }
15055
+ };
15056
+ }
15057
+ function adaptNodeSqliteDatabase(nodeSqlite, databasePath) {
15058
+ const db = new nodeSqlite.DatabaseSync(databasePath);
15059
+ return {
15060
+ exec: (sql) => db.exec(sql),
15061
+ prepare: (sql) => db.prepare(sql),
14933
15062
  close: () => db.close()
14934
15063
  };
14935
15064
  }
15065
+ function openSqliteDatabaseSync(databasePath) {
15066
+ if (databasePath !== ":memory:")
15067
+ ensurePrivateDir(dirname2(databasePath));
15068
+ if (selectSqliteRuntime() === "bun")
15069
+ return adaptBunSqliteDatabase(runtimeRequire("bun:sqlite"), databasePath);
15070
+ return adaptNodeSqliteDatabase(runtimeRequire("node:sqlite"), databasePath);
15071
+ }
14936
15072
  function legacyMigrationResult(migrated, skippedReason, paths, copiedFiles, details) {
14937
15073
  return {
14938
15074
  schemaVersion: "archcontext.legacy-local-store-migration/v1",
@@ -14979,7 +15115,6 @@ function runtimeStateRecoveryStartupProbe(paths, sourceFiles) {
14979
15115
  makePrivateFile(target);
14980
15116
  }
14981
15117
  migrateSqliteDatabaseSync(probePath);
14982
- compactSqliteDatabase(probePath);
14983
15118
  assertCurrentLocalStore(probePath);
14984
15119
  return { ok: true };
14985
15120
  } catch (error) {
@@ -15194,20 +15329,16 @@ function assertCurrentLocalStoreSchema(db, path) {
15194
15329
  throw new Error(`SQLite local store schema incomplete for ${path}: missing migrations ${missingMigrations.join(", ")}`);
15195
15330
  }
15196
15331
  }
15197
- function assertUpgradeableLocalStoreTarget(path) {
15198
- const db = openSqliteDatabaseSync(path);
15199
- try {
15200
- const integrity = sqliteIntegrityCheckOpenDatabase(db, path);
15201
- const tables = new Set(db.prepare("SELECT name FROM sqlite_master WHERE type = 'table'").all().map((row) => String(row.name)));
15202
- const hasArchContextMarker = ["schema_migrations", "task_states", "repository_sessions", "snapshots"].some((table) => tables.has(table));
15203
- if (!hasArchContextMarker) {
15204
- throw new Error(`SQLite target is not an ArchContext local store candidate: ${path}`);
15205
- }
15206
- if (integrity !== "ok")
15207
- throw new Error(`SQLite integrity_check failed for ${path}: ${integrity}`);
15208
- } finally {
15209
- db.close();
15332
+ function assertUpgradeableLocalStoreTarget(db, path) {
15333
+ const integrity = sqliteIntegrityCheckOpenDatabase(db, path);
15334
+ const tables = new Set(db.prepare("SELECT name FROM sqlite_master WHERE type = 'table'").all().map((row) => String(row.name)));
15335
+ const hasArchContextMarker = ["schema_migrations", "task_states", "repository_sessions", "snapshots"].some((table) => tables.has(table));
15336
+ if (!hasArchContextMarker) {
15337
+ throw new Error(`SQLite target is not an ArchContext local store candidate: ${path}`);
15210
15338
  }
15339
+ if (integrity !== "ok")
15340
+ throw new Error(`SQLite integrity_check failed for ${path}: ${integrity}`);
15341
+ return integrity;
15211
15342
  }
15212
15343
  function assertTrustedLegacyLocalStoreSource(paths) {
15213
15344
  const stat = lstatSync2(paths.legacyLocalStorePath);
@@ -15236,23 +15367,29 @@ function vacuumLegacySqliteInto(sourcePath, targetPath) {
15236
15367
  }
15237
15368
  function migrateSqliteDatabaseSync(databasePath) {
15238
15369
  const db = openSqliteDatabaseSync(databasePath);
15370
+ let compacted = false;
15239
15371
  try {
15240
- applyLocalSqliteMigrations(db);
15241
- backfillArchitectureEventDirectScope(db);
15242
- backfillArchitectureChangeFeed(db);
15372
+ migrateOpenedSqliteDatabase(db);
15373
+ compacted = true;
15243
15374
  } finally {
15244
15375
  db.close();
15245
15376
  }
15377
+ if (compacted)
15378
+ removeSqliteSidecars(databasePath);
15246
15379
  }
15247
- function compactSqliteDatabase(databasePath) {
15248
- const db = openSqliteDatabaseSync(databasePath);
15249
- try {
15250
- db.exec("PRAGMA busy_timeout = 5000");
15251
- db.exec("PRAGMA wal_checkpoint(TRUNCATE)");
15252
- db.exec("PRAGMA journal_mode = DELETE");
15253
- } finally {
15254
- db.close();
15255
- }
15380
+ function migrateOpenedSqliteDatabase(db) {
15381
+ applyLocalSqliteMigrations(db);
15382
+ backfillArchitectureEventDirectScope(db);
15383
+ backfillArchitectureChangeFeed(db);
15384
+ compactMigratedSqliteDatabase(db);
15385
+ }
15386
+ function compactMigratedSqliteDatabase(db) {
15387
+ db.exec("PRAGMA busy_timeout = 5000");
15388
+ db.releaseStatements?.();
15389
+ db.exec("PRAGMA wal_checkpoint(TRUNCATE)");
15390
+ db.exec("PRAGMA journal_mode = DELETE");
15391
+ }
15392
+ function removeSqliteSidecars(databasePath) {
15256
15393
  for (const suffix of ["-wal", "-shm"])
15257
15394
  rmSync(`${databasePath}${suffix}`, { force: true });
15258
15395
  }
@@ -15939,15 +16076,15 @@ function runCodeGraphCli2(binary, workspaceRoot, args) {
15939
16076
  // packages/local-runtime/runtime-daemon/src/index.ts
15940
16077
  import { randomBytes } from "node:crypto";
15941
16078
  import { execFileSync as execFileSync7 } from "node:child_process";
15942
- import { chmodSync as chmodSync3, closeSync as closeSync6, existsSync as existsSync15, mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync4, openSync as openSync6, readdirSync as readdirSync9, readFileSync as readFileSync14, rmSync as rmSync8, statSync as statSync9, writeFileSync as writeFileSync6 } from "node:fs";
16079
+ import { chmodSync as chmodSync4, closeSync as closeSync6, existsSync as existsSync15, lstatSync as lstatSync7, mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync4, openSync as openSync6, readdirSync as readdirSync9, readFileSync as readFileSync14, rmSync as rmSync8, statSync as statSync9, writeFileSync as writeFileSync6 } from "node:fs";
15943
16080
  import { createServer } from "node:http";
15944
16081
  import { tmpdir as tmpdir3 } from "node:os";
15945
- import { dirname as dirname10, join as join9, resolve as resolve18 } from "node:path";
16082
+ import { basename as basename6, dirname as dirname10, join as join9, resolve as resolve18, sep as sep7 } from "node:path";
15946
16083
 
15947
16084
  // packages/core/changeset-engine/src/index.ts
15948
16085
  init_src();
15949
- import { closeSync as closeSync4, existsSync as existsSync8, fsyncSync as fsyncSync2, lstatSync as lstatSync3, mkdirSync as mkdirSync3, openSync as openSync4, readFileSync as readFileSync9, renameSync as renameSync2, rmSync as rmSync3, writeFileSync as writeFileSync2 } from "node:fs";
15950
- import { dirname as dirname5, resolve as resolve11 } from "node:path";
16086
+ import { chmodSync as chmodSync2, closeSync as closeSync4, existsSync as existsSync8, fsyncSync as fsyncSync2, lstatSync as lstatSync3, mkdirSync as mkdirSync3, openSync as openSync4, readFileSync as readFileSync9, renameSync as renameSync2, rmSync as rmSync3, writeFileSync as writeFileSync2 } from "node:fs";
16087
+ import { dirname as dirname5, isAbsolute as isAbsolute5, relative as relative6, resolve as resolve11, sep as sep4 } from "node:path";
15951
16088
 
15952
16089
  // packages/core/policy-engine/src/index.ts
15953
16090
  import { existsSync as existsSync7, realpathSync as realpathSync6 } from "node:fs";
@@ -16218,14 +16355,57 @@ function assertSafeTarget(root, path, scope) {
16218
16355
  throw new Error(`Refusing to write symlink target: ${path}`);
16219
16356
  }
16220
16357
  }
16358
+ function writeFileWithoutFollowingSymlinks(request) {
16359
+ const absoluteRoot = resolve11(request.root);
16360
+ const absolute = assertPathHasNoSymlinkSegments(request.root, request.path);
16361
+ const segments = relative6(absoluteRoot, absolute).split(sep4);
16362
+ if (existsSync8(absolute))
16363
+ assertExpectedHash(absolute, request.expectedHash);
16364
+ else if (request.expectedHash !== "missing")
16365
+ throw new Error(`Expected missing file hash for new path: ${request.path}`);
16366
+ mkdirSync3(dirname5(absolute), { recursive: true });
16367
+ assertNoSymlinkSegments2(absoluteRoot, segments, request.path);
16368
+ atomicWriteFile(absolute, `${absolute}.archctx-tmp-${process.pid}-${nextNoFollowWriteSequence()}`, request.body, request.mode);
16369
+ }
16370
+ function assertPathHasNoSymlinkSegments(root, path) {
16371
+ const absoluteRoot = resolve11(root);
16372
+ const absolute = resolve11(absoluteRoot, path);
16373
+ const contained = relative6(absoluteRoot, absolute);
16374
+ if (contained === "" || contained === ".." || contained.startsWith(`..${sep4}`) || isAbsolute5(contained)) {
16375
+ throw new Error(`Path escapes repository: ${path}`);
16376
+ }
16377
+ assertNoSymlinkSegments2(absoluteRoot, contained.split(sep4), path);
16378
+ return absolute;
16379
+ }
16380
+ var noFollowWriteSequence = 0;
16381
+ function nextNoFollowWriteSequence() {
16382
+ noFollowWriteSequence += 1;
16383
+ return noFollowWriteSequence;
16384
+ }
16385
+ function assertNoSymlinkSegments2(absoluteRoot, segments, displayPath) {
16386
+ let current = absoluteRoot;
16387
+ for (const segment of segments) {
16388
+ current = resolve11(current, segment);
16389
+ let stat;
16390
+ try {
16391
+ stat = lstatSync3(current);
16392
+ } catch {
16393
+ return;
16394
+ }
16395
+ if (stat.isSymbolicLink())
16396
+ throw new Error(`Refusing to write through symlink: ${displayPath}`);
16397
+ }
16398
+ }
16221
16399
  function assertExpectedHash(path, expectedHash) {
16222
16400
  const actual = digestJson({ body: readFileSync9(path, "utf8") });
16223
16401
  if (expectedHash !== actual)
16224
16402
  throw new Error(`Expected hash mismatch: ${path}`);
16225
16403
  }
16226
- function atomicWriteFile(path, tempPath, body) {
16404
+ function atomicWriteFile(path, tempPath, body, mode) {
16227
16405
  mkdirSync3(dirname5(path), { recursive: true });
16228
- writeFileSync2(tempPath, body, "utf8");
16406
+ writeFileSync2(tempPath, body, mode === undefined ? "utf8" : { encoding: "utf8", mode, flag: "wx" });
16407
+ if (mode !== undefined)
16408
+ chmodSync2(tempPath, mode);
16229
16409
  fsyncFile2(tempPath);
16230
16410
  renameSync2(tempPath, path);
16231
16411
  fsyncDirectory2(dirname5(path));
@@ -16511,7 +16691,7 @@ init_src();
16511
16691
  // packages/core/practice-catalog/src/index.ts
16512
16692
  init_src();
16513
16693
  import { existsSync as existsSync9, lstatSync as lstatSync4, readdirSync as readdirSync6, readFileSync as readFileSync10, realpathSync as realpathSync7 } from "node:fs";
16514
- import { dirname as dirname6, relative as relative6, resolve as resolve12, sep as sep4 } from "node:path";
16694
+ import { dirname as dirname6, relative as relative7, resolve as resolve12, sep as sep5 } from "node:path";
16515
16695
  import { fileURLToPath } from "node:url";
16516
16696
  var PRACTICE_CATALOG_VERSION = "2026.06.0";
16517
16697
  var BUILTIN_PRACTICE_ASSETS_DIR = resolve12(dirname6(fileURLToPath(import.meta.url)), "../assets");
@@ -16763,7 +16943,7 @@ function loadRepoOverlayAssets(root, errors) {
16763
16943
  return [];
16764
16944
  const out = [];
16765
16945
  for (const path of listDataFiles(overlayRoot, overlayRoot, errors)) {
16766
- const relativePath = `.archcontext/practices/${relative6(overlayRoot, path).split(sep4).join("/")}`;
16946
+ const relativePath = `.archcontext/practices/${relative7(overlayRoot, path).split(sep5).join("/")}`;
16767
16947
  try {
16768
16948
  assertRepoRelativePath(relativePath);
16769
16949
  assertRealChild(root, path);
@@ -17170,11 +17350,11 @@ function safeRealpath(path) {
17170
17350
  }
17171
17351
  }
17172
17352
  function isRealChild(rootReal, pathReal) {
17173
- const rel = relative6(rootReal, pathReal);
17174
- return rel === "" || !rel.startsWith("..") && !rel.includes(`..${sep4}`) && !resolve12(rel).startsWith("..");
17353
+ const rel = relative7(rootReal, pathReal);
17354
+ return rel === "" || !rel.startsWith("..") && !rel.includes(`..${sep5}`) && !resolve12(rel).startsWith("..");
17175
17355
  }
17176
17356
  function displayPath(path) {
17177
- return path.split(sep4).join("/");
17357
+ return path.split(sep5).join("/");
17178
17358
  }
17179
17359
  function isRecord2(value) {
17180
17360
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
@@ -17736,7 +17916,7 @@ function checkResult(input, result) {
17736
17916
  // packages/core/practice-engine/src/enforcement.ts
17737
17917
  init_src();
17738
17918
  import { existsSync as existsSync10, lstatSync as lstatSync5, readdirSync as readdirSync7, readFileSync as readFileSync11 } from "node:fs";
17739
- import { basename as basename5, join as join5, relative as relative7, resolve as resolve13, sep as sep5 } from "node:path";
17919
+ import { basename as basename5, join as join5, relative as relative8, resolve as resolve13, sep as sep6 } from "node:path";
17740
17920
  var ENFORCEMENT_RANK = { advisory: 0, checkpoint: 1, complete: 2 };
17741
17921
  var POLICY_MODES = new Set(["advisory", "active", "fail-open", "fail-closed"]);
17742
17922
  var DEFAULT_POLICY = {
@@ -18168,8 +18348,8 @@ function assertRepoPolicyFile(root, path, relativePath) {
18168
18348
  throw new Error(`practice-policy-symlink-denied: ${relativePath}`);
18169
18349
  const rootResolved = resolve13(root);
18170
18350
  const pathResolved = resolve13(path);
18171
- const rel = relative7(rootResolved, pathResolved);
18172
- if (rel === "" || rel.startsWith("..") || rel.includes(`..${sep5}`))
18351
+ const rel = relative8(rootResolved, pathResolved);
18352
+ if (rel === "" || rel.startsWith("..") || rel.includes(`..${sep6}`))
18173
18353
  throw new Error(`practice-policy-path-escape: ${relativePath}`);
18174
18354
  if (basename5(pathResolved).startsWith("."))
18175
18355
  throw new Error(`practice-policy-hidden-file-denied: ${relativePath}`);
@@ -18900,6 +19080,32 @@ function createInterventionProposal(input) {
18900
19080
  }
18901
19081
 
18902
19082
  // packages/core/context-compiler/src/index.ts
19083
+ var SELF_REFERENTIAL_CONTEXT_EXTENSION_KEYS = ["byteLength", "budgetExceeded", "digest"];
19084
+ function canonicalContextMeasurementForm(context) {
19085
+ const extensions = { ...context.extensions };
19086
+ for (const key of SELF_REFERENTIAL_CONTEXT_EXTENSION_KEYS)
19087
+ delete extensions[key];
19088
+ return { ...context, extensions };
19089
+ }
19090
+ function finalizeContextBudgetMetadata(context, maxBytes) {
19091
+ const canonical = canonicalContextMeasurementForm(context);
19092
+ const byteLength = Buffer.byteLength(JSON.stringify(canonical), "utf8");
19093
+ const withMetadata = {
19094
+ ...canonical,
19095
+ extensions: {
19096
+ ...canonical.extensions,
19097
+ byteLength,
19098
+ budgetExceeded: byteLength > maxBytes
19099
+ }
19100
+ };
19101
+ return {
19102
+ ...withMetadata,
19103
+ extensions: {
19104
+ ...withMetadata.extensions,
19105
+ digest: digestJson(withMetadata)
19106
+ }
19107
+ };
19108
+ }
18903
19109
  async function compileTaskContext(input) {
18904
19110
  const model = await input.modelStore.validateModel(input.workspace);
18905
19111
  const ledgerReadback = await input.architectureLedger?.queryForTask({
@@ -19110,7 +19316,6 @@ async function compileLandscapeTaskContext(input) {
19110
19316
  });
19111
19317
  }
19112
19318
  function finalizeContext(context, digests) {
19113
- const byteLength = Buffer.byteLength(JSON.stringify(context), "utf8");
19114
19319
  const withMetadata = {
19115
19320
  ...context,
19116
19321
  extensions: {
@@ -19126,18 +19331,10 @@ function finalizeContext(context, digests) {
19126
19331
  codeFactsMode: digests.codeFactsMode,
19127
19332
  landscapeDigest: digests.landscapeDigest,
19128
19333
  activeRepositories: digests.activeRepositories,
19129
- crossRepoRelations: digests.crossRepoRelations,
19130
- byteLength,
19131
- budgetExceeded: byteLength > digests.maxBytes
19132
- }
19133
- };
19134
- return {
19135
- ...withMetadata,
19136
- extensions: {
19137
- ...withMetadata.extensions,
19138
- digest: digestJson(withMetadata)
19334
+ crossRepoRelations: digests.crossRepoRelations
19139
19335
  }
19140
19336
  };
19337
+ return finalizeContextBudgetMetadata(withMetadata, digests.maxBytes);
19141
19338
  }
19142
19339
  function trimPracticeGuidance(guidance, maxMatches) {
19143
19340
  const matches2 = guidance.matches.slice(0, maxMatches);
@@ -21779,6 +21976,7 @@ function clampInteger(value, min, max) {
21779
21976
 
21780
21977
  // packages/local-runtime/runtime-daemon/src/index.ts
21781
21978
  init_src();
21979
+ init_src();
21782
21980
 
21783
21981
  // packages/local-runtime/git-adapter/src/index.ts
21784
21982
  import { execFileSync as execFileSync5, spawnSync as spawnSync2 } from "node:child_process";
@@ -22067,11 +22265,11 @@ function isGitWorktreeError(error) {
22067
22265
  // packages/local-runtime/local-store-sqlite/src/index.ts
22068
22266
  import { execFileSync as execFileSync6 } from "node:child_process";
22069
22267
  import { createHash as createHash9, randomUUID as randomUUID2 } from "node:crypto";
22070
- import { chmodSync as chmodSync2, closeSync as closeSync5, copyFileSync as copyFileSync2, existsSync as existsSync13, fsyncSync as fsyncSync3, lstatSync as lstatSync6, mkdirSync as mkdirSync5, openSync as openSync5, readFileSync as readFileSync12, readSync as readSync4, realpathSync as realpathSync8, renameSync as renameSync3, rmSync as rmSync5, statfsSync as statfsSync2, statSync as statSync8, writeFileSync as writeFileSync3 } from "node:fs";
22268
+ import { chmodSync as chmodSync3, closeSync as closeSync5, copyFileSync as copyFileSync2, existsSync as existsSync13, fsyncSync as fsyncSync3, lstatSync as lstatSync6, mkdirSync as mkdirSync5, openSync as openSync5, readFileSync as readFileSync12, readSync as readSync4, realpathSync as realpathSync8, renameSync as renameSync3, rmSync as rmSync5, statfsSync as statfsSync2, statSync as statSync8, writeFileSync as writeFileSync3 } from "node:fs";
22071
22269
  import { readdir, readFile } from "node:fs/promises";
22072
22270
  import { createRequire as createRequire5 } from "node:module";
22073
22271
  import { homedir as homedir2 } from "node:os";
22074
- import { dirname as dirname8, isAbsolute as isAbsolute5, join as join7, relative as relative8, resolve as resolve16 } from "node:path";
22272
+ import { dirname as dirname8, isAbsolute as isAbsolute6, join as join7, relative as relative9, resolve as resolve16 } from "node:path";
22075
22273
  init_src();
22076
22274
  var runtimeRequire2 = createRequire5(import.meta.url);
22077
22275
  var SQLITE_SIDECAR_SUFFIXES2 = ["", "-wal", "-shm"];
@@ -22887,6 +23085,12 @@ var LOCAL_SQLITE_MIGRATIONS2 = [
22887
23085
  )`,
22888
23086
  "CREATE INDEX IF NOT EXISTS idx_projection_apply_receipts_journal ON projection_apply_receipts(journal_id)"
22889
23087
  ]
23088
+ },
23089
+ {
23090
+ id: "0020_projection_apply_recovery_proof",
23091
+ statements: [
23092
+ "ALTER TABLE projection_apply_receipts ADD COLUMN recovery_proof_json TEXT"
23093
+ ]
22890
23094
  }
22891
23095
  ];
22892
23096
  var CHANGESET_STARTUP_CLEANUP_LIMIT = 100;
@@ -22984,7 +23188,6 @@ function migrateLegacyLocalStoreIfNeeded2(root = process.cwd(), env = process.en
22984
23188
  integrityCheck.legacy = vacuumLegacySqliteInto2(paths.legacyLocalStorePath, stagingPath);
22985
23189
  makePrivateFile2(stagingPath);
22986
23190
  migrateSqliteDatabaseSync2(stagingPath);
22987
- compactSqliteDatabase2(stagingPath);
22988
23191
  integrityCheck.staging = assertCurrentLocalStore2(stagingPath);
22989
23192
  publishStagedLocalStore2(stagingPath, paths.localStorePath);
22990
23193
  integrityCheck.target = assertCurrentLocalStore2(paths.localStorePath);
@@ -23005,10 +23208,17 @@ function migrateLegacyLocalStoreIfNeeded2(root = process.cwd(), env = process.en
23005
23208
  function upgradeExistingLocalStoreTarget2(paths, integrityCheck) {
23006
23209
  const lock = acquireLegacyMigrationLock2(paths);
23007
23210
  try {
23008
- assertUpgradeableLocalStoreTarget2(paths.localStorePath);
23009
- integrityCheck.target = assertSqliteIntegrity2(paths.localStorePath);
23010
- migrateSqliteDatabaseSync2(paths.localStorePath);
23011
- compactSqliteDatabase2(paths.localStorePath);
23211
+ const db = openSqliteDatabaseSync2(paths.localStorePath);
23212
+ let compacted = false;
23213
+ try {
23214
+ integrityCheck.target = assertUpgradeableLocalStoreTarget2(db, paths.localStorePath);
23215
+ migrateOpenedSqliteDatabase2(db);
23216
+ compacted = true;
23217
+ } finally {
23218
+ db.close();
23219
+ }
23220
+ if (compacted)
23221
+ removeSqliteSidecars2(paths.localStorePath);
23012
23222
  integrityCheck.target = assertCurrentLocalStore2(paths.localStorePath);
23013
23223
  delete integrityCheck.error;
23014
23224
  const markerPath = writeLegacyMigrationMarker2(paths, integrityCheck, []);
@@ -23091,6 +23301,14 @@ class SqliteLocalStore {
23091
23301
  updatedAt: String(row.updated_at)
23092
23302
  }));
23093
23303
  }
23304
+ async deleteRepositorySession(repositoryId) {
23305
+ const db = await this.database();
23306
+ const existing = db.prepare("SELECT repository_id FROM repository_sessions WHERE repository_id = ?").get(repositoryId);
23307
+ if (!existing)
23308
+ return false;
23309
+ db.prepare("DELETE FROM repository_sessions WHERE repository_id = ?").run(repositoryId);
23310
+ return true;
23311
+ }
23094
23312
  async enqueueRuntimeAgentJob(input) {
23095
23313
  if (input.job.status !== "queued")
23096
23314
  throw new Error("runtime-agent-job-enqueue-requires-queued-status");
@@ -23201,7 +23419,7 @@ class SqliteLocalStore {
23201
23419
  maxAttempts,
23202
23420
  debounceUntil: input.debounceUntil
23203
23421
  });
23204
- const inserted = runtimeAgentJobById(db, input.job.jobId);
23422
+ const inserted = runtimeAgentJobInScope(db, runtimeAgentJobScope(input.job), input.job.jobId);
23205
23423
  if (!inserted)
23206
23424
  throw new Error(`runtime-agent-job-insert-failed: ${input.job.jobId}`);
23207
23425
  const backpressure = maxQueuedJobs === undefined ? undefined : {
@@ -23285,21 +23503,15 @@ class SqliteLocalStore {
23285
23503
  const nextAttempt = record2.attemptCount + 1;
23286
23504
  if (nextAttempt > record2.maxAttempts) {
23287
23505
  const failed = runtimeAgentJobWithPatch(record2.job, { status: "failed", updatedAt: input.now });
23288
- db.prepare(`UPDATE runtime_job_queue
23289
- SET status = ?, job_json = ?, updated_at = ?, attempt_count = ?, lease_owner = NULL,
23290
- leased_at = NULL, lease_expires_at = NULL, last_error = ?, dead_lettered_at = ?
23291
- WHERE job_id = ?`).run("failed", stableJson2(failed), input.now, nextAttempt, "max-attempts-exhausted", input.now, record2.job.jobId);
23506
+ updateRuntimeAgentJobInScope(db, input, record2.job.jobId, `status = ?, job_json = ?, updated_at = ?, attempt_count = ?, lease_owner = NULL,
23507
+ leased_at = NULL, lease_expires_at = NULL, last_error = ?, dead_lettered_at = ?`, ["failed", stableJson2(failed), input.now, nextAttempt, "max-attempts-exhausted", input.now]);
23292
23508
  db.exec("COMMIT");
23293
23509
  return;
23294
23510
  }
23295
23511
  const running = runtimeAgentJobWithPatch(record2.job, { status: "running", updatedAt: input.now });
23296
- db.prepare(`UPDATE runtime_job_queue
23297
- SET status = ?, job_json = ?, updated_at = ?, attempt_count = ?, lease_owner = ?,
23298
- leased_at = ?, lease_expires_at = ?, last_error = NULL
23299
- WHERE job_id = ?`).run("running", stableJson2(running), input.now, nextAttempt, input.workerId, input.now, leaseExpiresAt, record2.job.jobId);
23300
- const claimed = runtimeAgentJobById(db, record2.job.jobId);
23301
- if (!claimed)
23302
- throw new Error(`runtime-agent-job-not-found: ${record2.job.jobId}`);
23512
+ updateRuntimeAgentJobInScope(db, input, record2.job.jobId, `status = ?, job_json = ?, updated_at = ?, attempt_count = ?, lease_owner = ?,
23513
+ leased_at = ?, lease_expires_at = ?, last_error = NULL`, ["running", stableJson2(running), input.now, nextAttempt, input.workerId, input.now, leaseExpiresAt]);
23514
+ const claimed = requireRuntimeAgentJobInScope(db, input, record2.job.jobId);
23303
23515
  db.exec("COMMIT");
23304
23516
  return claimed;
23305
23517
  } catch (error) {
@@ -23309,9 +23521,7 @@ class SqliteLocalStore {
23309
23521
  }
23310
23522
  async completeRuntimeAgentJob(input) {
23311
23523
  const db = await this.database();
23312
- const record2 = runtimeAgentJobById(db, input.jobId);
23313
- if (!record2)
23314
- throw new Error(`runtime-agent-job-not-found: ${input.jobId}`);
23524
+ const record2 = requireRuntimeAgentJobInScope(db, input, input.jobId);
23315
23525
  if (record2.job.status !== "running")
23316
23526
  throw new Error(`runtime-agent-job-complete-requires-running: ${input.jobId}`);
23317
23527
  if (input.workerId && record2.leaseOwner && record2.leaseOwner !== input.workerId) {
@@ -23324,52 +23534,31 @@ class SqliteLocalStore {
23324
23534
  outputDigest: input.outputDigest,
23325
23535
  runMetadata: input.runMetadata
23326
23536
  });
23327
- db.prepare(`UPDATE runtime_job_queue
23328
- SET status = ?, job_json = ?, updated_at = ?, output_digest = ?, lease_owner = NULL,
23329
- leased_at = NULL, lease_expires_at = NULL, last_error = ?, dead_lettered_at = COALESCE(?, dead_lettered_at)
23330
- WHERE job_id = ?`).run(input.status, stableJson2(job), input.now, input.outputDigest ?? record2.job.outputDigest ?? null, input.error ?? null, deadLetteredAt ?? null, input.jobId);
23331
- const updated = runtimeAgentJobById(db, input.jobId);
23332
- if (!updated)
23333
- throw new Error(`runtime-agent-job-not-found: ${input.jobId}`);
23334
- return updated;
23537
+ updateRuntimeAgentJobInScope(db, input, input.jobId, `status = ?, job_json = ?, updated_at = ?, output_digest = ?, lease_owner = NULL,
23538
+ leased_at = NULL, lease_expires_at = NULL, last_error = ?, dead_lettered_at = COALESCE(?, dead_lettered_at)`, [input.status, stableJson2(job), input.now, input.outputDigest ?? record2.job.outputDigest ?? null, input.error ?? null, deadLetteredAt ?? null]);
23539
+ return requireRuntimeAgentJobInScope(db, input, input.jobId);
23335
23540
  }
23336
23541
  async retryRuntimeAgentJob(input) {
23337
23542
  const db = await this.database();
23338
- const record2 = runtimeAgentJobById(db, input.jobId);
23339
- if (!record2)
23340
- throw new Error(`runtime-agent-job-not-found: ${input.jobId}`);
23543
+ const record2 = requireRuntimeAgentJobInScope(db, input, input.jobId);
23341
23544
  if (record2.attemptCount >= record2.maxAttempts) {
23342
23545
  const failed = runtimeAgentJobWithPatch(record2.job, { status: "failed", updatedAt: input.now });
23343
- db.prepare(`UPDATE runtime_job_queue
23344
- SET status = ?, job_json = ?, updated_at = ?, lease_owner = NULL,
23345
- leased_at = NULL, lease_expires_at = NULL, last_error = ?, dead_lettered_at = ?
23346
- WHERE job_id = ?`).run("failed", stableJson2(failed), input.now, input.reason ?? "max-attempts-exhausted", input.now, input.jobId);
23546
+ updateRuntimeAgentJobInScope(db, input, input.jobId, `status = ?, job_json = ?, updated_at = ?, lease_owner = NULL,
23547
+ leased_at = NULL, lease_expires_at = NULL, last_error = ?, dead_lettered_at = ?`, ["failed", stableJson2(failed), input.now, input.reason ?? "max-attempts-exhausted", input.now]);
23347
23548
  } else {
23348
23549
  const queued = runtimeAgentJobWithPatch(record2.job, { status: "queued", updatedAt: input.now });
23349
- db.prepare(`UPDATE runtime_job_queue
23350
- SET status = ?, job_json = ?, updated_at = ?, lease_owner = NULL,
23351
- leased_at = NULL, lease_expires_at = NULL, last_error = ?, dead_lettered_at = NULL
23352
- WHERE job_id = ?`).run("queued", stableJson2(queued), input.now, input.reason ?? null, input.jobId);
23353
- }
23354
- const updated = runtimeAgentJobById(db, input.jobId);
23355
- if (!updated)
23356
- throw new Error(`runtime-agent-job-not-found: ${input.jobId}`);
23357
- return updated;
23550
+ updateRuntimeAgentJobInScope(db, input, input.jobId, `status = ?, job_json = ?, updated_at = ?, lease_owner = NULL,
23551
+ leased_at = NULL, lease_expires_at = NULL, last_error = ?, dead_lettered_at = NULL`, ["queued", stableJson2(queued), input.now, input.reason ?? null]);
23552
+ }
23553
+ return requireRuntimeAgentJobInScope(db, input, input.jobId);
23358
23554
  }
23359
23555
  async cancelRuntimeAgentJob(input) {
23360
23556
  const db = await this.database();
23361
- const record2 = runtimeAgentJobById(db, input.jobId);
23362
- if (!record2)
23363
- throw new Error(`runtime-agent-job-not-found: ${input.jobId}`);
23557
+ const record2 = requireRuntimeAgentJobInScope(db, input, input.jobId);
23364
23558
  const job = runtimeAgentJobWithPatch(record2.job, { status: input.status, updatedAt: input.now });
23365
- db.prepare(`UPDATE runtime_job_queue
23366
- SET status = ?, job_json = ?, updated_at = ?, lease_owner = NULL,
23367
- leased_at = NULL, lease_expires_at = NULL, last_error = ?, superseded_by_job_id = COALESCE(?, superseded_by_job_id)
23368
- WHERE job_id = ?`).run(input.status, stableJson2(job), input.now, input.reason ?? null, input.supersededByJobId ?? null, input.jobId);
23369
- const updated = runtimeAgentJobById(db, input.jobId);
23370
- if (!updated)
23371
- throw new Error(`runtime-agent-job-not-found: ${input.jobId}`);
23372
- return updated;
23559
+ updateRuntimeAgentJobInScope(db, input, input.jobId, `status = ?, job_json = ?, updated_at = ?, lease_owner = NULL,
23560
+ leased_at = NULL, lease_expires_at = NULL, last_error = ?, superseded_by_job_id = COALESCE(?, superseded_by_job_id)`, [input.status, stableJson2(job), input.now, input.reason ?? null, input.supersededByJobId ?? null]);
23561
+ return requireRuntimeAgentJobInScope(db, input, input.jobId);
23373
23562
  }
23374
23563
  async cancelStaleRuntimeAgentJobs(input) {
23375
23564
  const db = await this.database();
@@ -23382,6 +23571,8 @@ class SqliteLocalStore {
23382
23571
  const cancelled = [];
23383
23572
  for (const record2 of staleRows) {
23384
23573
  cancelled.push(await this.cancelRuntimeAgentJob({
23574
+ repository: input.repository,
23575
+ worktree: input.worktree,
23385
23576
  jobId: record2.job.jobId,
23386
23577
  status: "expired",
23387
23578
  now: input.now,
@@ -23443,29 +23634,58 @@ class SqliteLocalStore {
23443
23634
  (lookup_key, apply_id, journal_id, receipt_json, created_at, updated_at)
23444
23635
  VALUES (?, ?, ?, ?, ?, ?)`).run(receipt.identity.lookupKey, receipt.identity.applyId, journalId, stableJson2(receipt), createdAt, createdAt);
23445
23636
  }
23446
- async consumeProjectionApplyReceipt(lookupKey) {
23637
+ async inspectProjectionApplyReceipt(lookupKey) {
23638
+ const db = await this.database();
23639
+ const row = db.prepare(`SELECT receipt.receipt_json, receipt.refresh_consumed_at, receipt.recovery_proof_json
23640
+ FROM projection_apply_receipts receipt
23641
+ JOIN changeset_journal journal ON journal.journal_id = receipt.journal_id
23642
+ WHERE receipt.lookup_key = ? AND journal.status = 'committed'`).get(lookupKey);
23643
+ if (!row?.receipt_json)
23644
+ return;
23645
+ const receipt = parseProjectionApplyReceipt(String(row.receipt_json));
23646
+ const recoveryProof = row.recovery_proof_json === null ? undefined : parseProjectionApplyRecoveryProof(String(row.recovery_proof_json), receipt);
23647
+ return {
23648
+ receipt,
23649
+ deliveryStatus: row.refresh_consumed_at === null ? "pending" : "delivered",
23650
+ ...recoveryProof ? { recoveryProof } : {}
23651
+ };
23652
+ }
23653
+ async consumeProjectionApplyReceiptRecovery(proof) {
23447
23654
  const db = await this.database();
23448
23655
  db.exec("BEGIN IMMEDIATE");
23449
23656
  try {
23450
- const row = db.prepare(`SELECT receipt.receipt_json, receipt.refresh_consumed_at
23657
+ const row = db.prepare(`SELECT receipt.receipt_json, receipt.refresh_consumed_at, receipt.recovery_proof_json
23451
23658
  FROM projection_apply_receipts receipt
23452
23659
  JOIN changeset_journal journal ON journal.journal_id = receipt.journal_id
23453
- WHERE receipt.lookup_key = ? AND journal.status = 'committed'`).get(lookupKey);
23660
+ WHERE receipt.lookup_key = ? AND journal.status = 'committed'`).get(proof.receipt.lookupKey);
23454
23661
  if (!row?.receipt_json) {
23455
23662
  db.exec("COMMIT");
23456
23663
  return;
23457
23664
  }
23458
- const receipt = JSON.parse(String(row.receipt_json));
23459
- const issues = projectionApplyReceiptInvariantIssues(receipt);
23460
- if (issues.length > 0)
23461
- throw new Error(`projection-apply-receipt-invalid: ${issues.join("; ")}`);
23462
- const refreshSignalsDelivered = row.refresh_consumed_at === null;
23463
- if (refreshSignalsDelivered) {
23464
- const consumedAt = nowIso2();
23465
- db.prepare("UPDATE projection_apply_receipts SET refresh_consumed_at = ?, updated_at = ? WHERE lookup_key = ? AND refresh_consumed_at IS NULL").run(consumedAt, consumedAt, lookupKey);
23665
+ const receipt = parseProjectionApplyReceipt(String(row.receipt_json));
23666
+ const proofIssues = projectionApplyRecoveryProofReceiptInvariantIssues(proof, receipt);
23667
+ if (proofIssues.length > 0)
23668
+ throw new Error(`projection-apply-recovery-proof-invalid: ${proofIssues.join("; ")}`);
23669
+ if (row.refresh_consumed_at !== null) {
23670
+ const stored = row.recovery_proof_json === null ? undefined : parseProjectionApplyRecoveryProof(String(row.recovery_proof_json), receipt);
23671
+ if (!stored)
23672
+ throw new Error("projection-apply-receipt-delivered-without-recovery-proof");
23673
+ db.exec("COMMIT");
23674
+ return {
23675
+ receipt,
23676
+ proof: { ...stored, deliveryStatus: "already-delivered" },
23677
+ refreshSignalsDelivered: false
23678
+ };
23466
23679
  }
23680
+ const deliveredProof = { ...proof, deliveryStatus: "delivered" };
23681
+ const updatedAt = nowIso2();
23682
+ const update = db.prepare(`UPDATE projection_apply_receipts
23683
+ SET refresh_consumed_at = ?, recovery_proof_json = ?, updated_at = ?
23684
+ WHERE lookup_key = ? AND apply_id = ? AND refresh_consumed_at IS NULL AND recovery_proof_json IS NULL`).run(updatedAt, stableJson2(deliveredProof), updatedAt, proof.receipt.lookupKey, proof.receipt.applyId);
23685
+ if (update.changes !== 1)
23686
+ throw new Error("projection-apply-recovery-consume-race");
23467
23687
  db.exec("COMMIT");
23468
- return { receipt, refreshSignalsDelivered };
23688
+ return { receipt, proof: deliveredProof, refreshSignalsDelivered: true };
23469
23689
  } catch (error) {
23470
23690
  db.exec("ROLLBACK");
23471
23691
  throw error;
@@ -26536,51 +26756,48 @@ async function rebuildDerivedLandscapeState(store, input) {
26536
26756
  digest: landscapeDigest(landscape, scopedRelations)
26537
26757
  };
26538
26758
  }
26539
- async function openSqliteDatabase(databasePath) {
26540
- if (databasePath !== ":memory:")
26541
- ensurePrivateDir2(dirname8(databasePath));
26542
- try {
26543
- const nodeSqlite = await import("node:sqlite");
26544
- const db = new nodeSqlite.DatabaseSync(databasePath);
26545
- return {
26546
- exec: (sql) => db.exec(sql),
26547
- prepare: (sql) => db.prepare(sql),
26548
- close: () => db.close()
26549
- };
26550
- } catch {
26551
- const bunSqlite = await import("bun:sqlite");
26552
- const db = new bunSqlite.Database(databasePath);
26553
- return {
26554
- exec: (sql) => db.exec(sql),
26555
- prepare: (sql) => db.query(sql),
26556
- close: () => db.close()
26557
- };
26558
- }
26759
+ function selectSqliteRuntime2() {
26760
+ if (process.versions.bun)
26761
+ return "bun";
26762
+ if (process.versions.node)
26763
+ return "node";
26764
+ throw new Error("Unsupported SQLite runtime: archctx requires the Bun or Node runtime, but neither process.versions.bun nor process.versions.node is set.");
26559
26765
  }
26560
- function openSqliteDatabaseSync2(databasePath) {
26561
- if (databasePath !== ":memory:")
26562
- ensurePrivateDir2(dirname8(databasePath));
26563
- try {
26564
- const nodeSqlite = runtimeRequire2("node:sqlite");
26565
- const db2 = new nodeSqlite.DatabaseSync(databasePath);
26566
- return {
26567
- exec: (sql) => db2.exec(sql),
26568
- prepare: (sql) => db2.prepare(sql),
26569
- close: () => db2.close()
26570
- };
26571
- } catch (error) {
26572
- if (error.code !== "ERR_UNKNOWN_BUILTIN_MODULE" && error.code !== "MODULE_NOT_FOUND") {
26573
- throw error;
26574
- }
26575
- }
26576
- const bunSqlite = runtimeRequire2("bun:sqlite");
26766
+ function adaptBunSqliteDatabase2(bunSqlite, databasePath) {
26577
26767
  const db = new bunSqlite.Database(databasePath);
26768
+ const releaseStatements = () => db.clearQueryCache();
26578
26769
  return {
26579
26770
  exec: (sql) => db.exec(sql),
26580
26771
  prepare: (sql) => db.query(sql),
26772
+ releaseStatements,
26773
+ close: () => {
26774
+ releaseStatements();
26775
+ db.close();
26776
+ }
26777
+ };
26778
+ }
26779
+ function adaptNodeSqliteDatabase2(nodeSqlite, databasePath) {
26780
+ const db = new nodeSqlite.DatabaseSync(databasePath);
26781
+ return {
26782
+ exec: (sql) => db.exec(sql),
26783
+ prepare: (sql) => db.prepare(sql),
26581
26784
  close: () => db.close()
26582
26785
  };
26583
26786
  }
26787
+ async function openSqliteDatabase(databasePath) {
26788
+ if (databasePath !== ":memory:")
26789
+ ensurePrivateDir2(dirname8(databasePath));
26790
+ if (selectSqliteRuntime2() === "bun")
26791
+ return adaptBunSqliteDatabase2(await import("bun:sqlite"), databasePath);
26792
+ return adaptNodeSqliteDatabase2(await import("node:sqlite"), databasePath);
26793
+ }
26794
+ function openSqliteDatabaseSync2(databasePath) {
26795
+ if (databasePath !== ":memory:")
26796
+ ensurePrivateDir2(dirname8(databasePath));
26797
+ if (selectSqliteRuntime2() === "bun")
26798
+ return adaptBunSqliteDatabase2(runtimeRequire2("bun:sqlite"), databasePath);
26799
+ return adaptNodeSqliteDatabase2(runtimeRequire2("node:sqlite"), databasePath);
26800
+ }
26584
26801
  function legacyMigrationResult2(migrated, skippedReason, paths, copiedFiles, details) {
26585
26802
  return {
26586
26803
  schemaVersion: "archcontext.legacy-local-store-migration/v1",
@@ -26632,20 +26849,16 @@ function assertCurrentLocalStoreSchema2(db, path) {
26632
26849
  throw new Error(`SQLite local store schema incomplete for ${path}: missing migrations ${missingMigrations.join(", ")}`);
26633
26850
  }
26634
26851
  }
26635
- function assertUpgradeableLocalStoreTarget2(path) {
26636
- const db = openSqliteDatabaseSync2(path);
26637
- try {
26638
- const integrity = sqliteIntegrityCheckOpenDatabase2(db, path);
26639
- const tables = new Set(db.prepare("SELECT name FROM sqlite_master WHERE type = 'table'").all().map((row) => String(row.name)));
26640
- const hasArchContextMarker = ["schema_migrations", "task_states", "repository_sessions", "snapshots"].some((table) => tables.has(table));
26641
- if (!hasArchContextMarker) {
26642
- throw new Error(`SQLite target is not an ArchContext local store candidate: ${path}`);
26643
- }
26644
- if (integrity !== "ok")
26645
- throw new Error(`SQLite integrity_check failed for ${path}: ${integrity}`);
26646
- } finally {
26647
- db.close();
26852
+ function assertUpgradeableLocalStoreTarget2(db, path) {
26853
+ const integrity = sqliteIntegrityCheckOpenDatabase2(db, path);
26854
+ const tables = new Set(db.prepare("SELECT name FROM sqlite_master WHERE type = 'table'").all().map((row) => String(row.name)));
26855
+ const hasArchContextMarker = ["schema_migrations", "task_states", "repository_sessions", "snapshots"].some((table) => tables.has(table));
26856
+ if (!hasArchContextMarker) {
26857
+ throw new Error(`SQLite target is not an ArchContext local store candidate: ${path}`);
26648
26858
  }
26859
+ if (integrity !== "ok")
26860
+ throw new Error(`SQLite integrity_check failed for ${path}: ${integrity}`);
26861
+ return integrity;
26649
26862
  }
26650
26863
  function assertTrustedLegacyLocalStoreSource2(paths) {
26651
26864
  const stat = lstatSync6(paths.legacyLocalStorePath);
@@ -26674,23 +26887,29 @@ function vacuumLegacySqliteInto2(sourcePath, targetPath) {
26674
26887
  }
26675
26888
  function migrateSqliteDatabaseSync2(databasePath) {
26676
26889
  const db = openSqliteDatabaseSync2(databasePath);
26890
+ let compacted = false;
26677
26891
  try {
26678
- applyLocalSqliteMigrations2(db);
26679
- backfillArchitectureEventDirectScope2(db);
26680
- backfillArchitectureChangeFeed2(db);
26892
+ migrateOpenedSqliteDatabase2(db);
26893
+ compacted = true;
26681
26894
  } finally {
26682
26895
  db.close();
26683
26896
  }
26897
+ if (compacted)
26898
+ removeSqliteSidecars2(databasePath);
26684
26899
  }
26685
- function compactSqliteDatabase2(databasePath) {
26686
- const db = openSqliteDatabaseSync2(databasePath);
26687
- try {
26688
- db.exec("PRAGMA busy_timeout = 5000");
26689
- db.exec("PRAGMA wal_checkpoint(TRUNCATE)");
26690
- db.exec("PRAGMA journal_mode = DELETE");
26691
- } finally {
26692
- db.close();
26693
- }
26900
+ function migrateOpenedSqliteDatabase2(db) {
26901
+ applyLocalSqliteMigrations2(db);
26902
+ backfillArchitectureEventDirectScope2(db);
26903
+ backfillArchitectureChangeFeed2(db);
26904
+ compactMigratedSqliteDatabase2(db);
26905
+ }
26906
+ function compactMigratedSqliteDatabase2(db) {
26907
+ db.exec("PRAGMA busy_timeout = 5000");
26908
+ db.releaseStatements?.();
26909
+ db.exec("PRAGMA wal_checkpoint(TRUNCATE)");
26910
+ db.exec("PRAGMA journal_mode = DELETE");
26911
+ }
26912
+ function removeSqliteSidecars2(databasePath) {
26694
26913
  for (const suffix of ["-wal", "-shm"])
26695
26914
  rmSync5(`${databasePath}${suffix}`, { force: true });
26696
26915
  }
@@ -26821,14 +27040,14 @@ function ensurePrivateDir2(path) {
26821
27040
  mkdirSync5(path, { recursive: true, mode: 448 });
26822
27041
  if (process.platform !== "win32") {
26823
27042
  try {
26824
- chmodSync2(path, 448);
27043
+ chmodSync3(path, 448);
26825
27044
  } catch {}
26826
27045
  }
26827
27046
  }
26828
27047
  function makePrivateFile2(path) {
26829
27048
  if (process.platform !== "win32") {
26830
27049
  try {
26831
- chmodSync2(path, 384);
27050
+ chmodSync3(path, 384);
26832
27051
  } catch {}
26833
27052
  }
26834
27053
  }
@@ -26845,7 +27064,7 @@ function readGitPath2(root, args) {
26845
27064
  }
26846
27065
  }
26847
27066
  function resolveMaybeRelative2(base, path) {
26848
- return isAbsolute5(path) ? resolve16(path) : resolve16(base, path);
27067
+ return isAbsolute6(path) ? resolve16(path) : resolve16(base, path);
26849
27068
  }
26850
27069
  function canonicalPath2(path) {
26851
27070
  const resolved = resolve16(path);
@@ -26858,8 +27077,8 @@ function canonicalPath2(path) {
26858
27077
  function isPathInsideOrSame2(path, parent) {
26859
27078
  const child = resolve16(path);
26860
27079
  const base = resolve16(parent);
26861
- const fromBase = relative8(base, child);
26862
- return fromBase === "" || !!fromBase && !fromBase.startsWith("..") && !isAbsolute5(fromBase);
27080
+ const fromBase = relative9(base, child);
27081
+ return fromBase === "" || !!fromBase && !fromBase.startsWith("..") && !isAbsolute6(fromBase);
26863
27082
  }
26864
27083
  function stableStorageId2(prefix, value) {
26865
27084
  return `${prefix}.${createHash9("sha256").update(value).digest("hex").slice(0, 16)}`;
@@ -26951,10 +27170,29 @@ function insertRuntimeAgentJob(db, input) {
26951
27170
  lease_expires_at, last_error, dead_lettered_at, debounce_until, superseded_by_job_id)
26952
27171
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, NULL, NULL, NULL, NULL, ?, NULL)`).run(input.job.jobId, input.job.repository.repositoryId, input.job.repository.storageRepositoryId, input.job.worktree.workspaceId, input.job.worktree.storageWorkspaceId, input.analysisKind, input.coalesceKey, input.priority, input.job.status, input.job.runnerPort, input.job.fingerprint, input.job.inputDigest, input.job.promptTemplateDigest, input.job.outputDigest ?? null, input.job.stalePolicy, stableJson2(input.job), input.job.queuedAt, input.job.updatedAt, 0, input.maxAttempts, input.debounceUntil ?? null);
26953
27172
  }
26954
- function runtimeAgentJobById(db, jobId) {
26955
- const row = db.prepare("SELECT * FROM runtime_job_queue WHERE job_id = ?").get(jobId);
27173
+ function runtimeAgentJobInScope(db, scope, jobId) {
27174
+ const row = db.prepare(`SELECT * FROM runtime_job_queue
27175
+ WHERE job_id = ?
27176
+ AND storage_repository_id = ?
27177
+ AND storage_workspace_id = ?`).get(jobId, scope.repository.storageRepositoryId, scope.worktree.storageWorkspaceId);
26956
27178
  return row ? runtimeAgentJobRecordFromRow(row) : undefined;
26957
27179
  }
27180
+ function requireRuntimeAgentJobInScope(db, scope, jobId) {
27181
+ const record2 = runtimeAgentJobInScope(db, scope, jobId);
27182
+ if (!record2)
27183
+ throw new Error(`runtime-agent-job-not-found-in-scope: ${jobId}`);
27184
+ return record2;
27185
+ }
27186
+ function updateRuntimeAgentJobInScope(db, scope, jobId, assignments, params) {
27187
+ db.prepare(`UPDATE runtime_job_queue
27188
+ SET ${assignments}
27189
+ WHERE job_id = ?
27190
+ AND storage_repository_id = ?
27191
+ AND storage_workspace_id = ?`).run(...params, jobId, scope.repository.storageRepositoryId, scope.worktree.storageWorkspaceId);
27192
+ }
27193
+ function runtimeAgentJobScope(job) {
27194
+ return { repository: job.repository, worktree: job.worktree };
27195
+ }
26958
27196
  function runtimeAgentJobRecordFromRow(row) {
26959
27197
  return {
26960
27198
  job: JSON.parse(String(row.job_json)),
@@ -26991,6 +27229,30 @@ function runtimeAgentJobWithPatch(job, patch) {
26991
27229
  function nullableString(value) {
26992
27230
  return typeof value === "string" && value.length > 0 ? value : undefined;
26993
27231
  }
27232
+ function parseProjectionApplyReceipt(raw) {
27233
+ let receipt;
27234
+ try {
27235
+ receipt = JSON.parse(raw);
27236
+ } catch (error) {
27237
+ throw new Error(`projection-apply-receipt-invalid-json: ${error instanceof Error ? error.message : String(error)}`);
27238
+ }
27239
+ const issues = projectionApplyReceiptInvariantIssues(receipt);
27240
+ if (issues.length > 0)
27241
+ throw new Error(`projection-apply-receipt-invalid: ${issues.join("; ")}`);
27242
+ return receipt;
27243
+ }
27244
+ function parseProjectionApplyRecoveryProof(raw, receipt) {
27245
+ let proof;
27246
+ try {
27247
+ proof = JSON.parse(raw);
27248
+ } catch (error) {
27249
+ throw new Error(`projection-apply-recovery-proof-invalid-json: ${error instanceof Error ? error.message : String(error)}`);
27250
+ }
27251
+ const issues = projectionApplyRecoveryProofReceiptInvariantIssues(proof, receipt);
27252
+ if (issues.length > 0)
27253
+ throw new Error(`projection-apply-recovery-proof-invalid: ${issues.join("; ")}`);
27254
+ return proof;
27255
+ }
26994
27256
  function nowIso2() {
26995
27257
  return new Date().toISOString();
26996
27258
  }
@@ -27650,13 +27912,13 @@ async function withGithubIssueBodyFile(body, fn, deps = {}) {
27650
27912
  rmSync7(dir, { recursive: true, force: true });
27651
27913
  }
27652
27914
  }
27653
- var SECRET_PATTERNS = [
27654
- /gh[opsu]_[A-Za-z0-9_]+/,
27655
- /Bearer\s+[A-Za-z0-9._-]+/i,
27656
- /-----BEGIN [A-Z ]*PRIVATE KEY-----/,
27657
- /GITHUB_WEBHOOK_SECRET/i,
27658
- /installation[_-]?token/i,
27659
- /jwt/i
27915
+ var SECRET_DETECTORS = [
27916
+ { id: "github-token-prefix", pattern: /gh[opsu]_[A-Za-z0-9_]+/ },
27917
+ { id: "bearer-credential", pattern: /Bearer\s+[A-Za-z0-9._-]+/i },
27918
+ { id: "private-key-header", pattern: /-----BEGIN [A-Z ]*PRIVATE KEY-----/ },
27919
+ { id: "github-webhook-secret", pattern: /GITHUB_WEBHOOK_SECRET/i },
27920
+ { id: "installation-token-value", pattern: /installation[_-]?token["']?\s*[:=]\s*["']?[A-Za-z0-9._-]{16,}/i },
27921
+ { id: "compact-jwt-value", pattern: /\beyJ[A-Za-z0-9_-]{6,}\.[A-Za-z0-9_-]{6,}\.[A-Za-z0-9_-]{6,}/ }
27660
27922
  ];
27661
27923
  var GITHUB_ISSUE_BODY_MAX_LENGTH = 65536;
27662
27924
  function githubIssueFooterMarker(runId, draftDigest) {
@@ -27674,11 +27936,19 @@ function preflightGithubIssueDrafts(runId, drafts) {
27674
27936
 
27675
27937
  ${footer}
27676
27938
  `;
27677
- const payload = [draft.title, body, ...draft.labels].join(`
27678
- `);
27679
- for (const pattern of SECRET_PATTERNS) {
27680
- if (pattern.test(payload)) {
27681
- return { ok: false, reason: `github issue draft ${draft.draftId} matched a secret-shaped pattern; publishing aborted for the entire run` };
27939
+ const scanned = [
27940
+ { field: "title", text: draft.title },
27941
+ { field: "body", text: body },
27942
+ ...draft.labels.map((label) => ({ field: "label", text: label }))
27943
+ ];
27944
+ for (const { field, text } of scanned) {
27945
+ for (const detector of SECRET_DETECTORS) {
27946
+ if (detector.pattern.test(text)) {
27947
+ return {
27948
+ ok: false,
27949
+ reason: `github issue draft ${draft.draftId} matched a secret-shaped pattern (detector ${detector.id}) in its ${field}; publishing aborted for the entire run`
27950
+ };
27951
+ }
27682
27952
  }
27683
27953
  }
27684
27954
  if (body.length > GITHUB_ISSUE_BODY_MAX_LENGTH) {
@@ -28500,6 +28770,8 @@ function auditGithubIssuesEnabledInManifestText(manifestText) {
28500
28770
  return false;
28501
28771
  }
28502
28772
  var RUNTIME_RPC_VERSION = LOCAL_RUNTIME_RPC_SCHEMA_VERSION;
28773
+ var RUNTIME_RPC_MAX_REQUEST_BODY_BYTES = 16 * 1024 * 1024;
28774
+ var RUNTIME_RPC_REQUEST_BODY_TIMEOUT_MS = 30000;
28503
28775
 
28504
28776
  class RuntimeUpdateInputError extends Error {
28505
28777
  }
@@ -29083,12 +29355,15 @@ class ArchctxDaemon {
29083
29355
  const scope = await this.architectureLedgerScope(repositoryRoot);
29084
29356
  const jobs = await this.localStore.listRuntimeAgentJobs(scope);
29085
29357
  const record2 = jobs.find((candidate) => candidate.job.jobId === input.jobId);
29086
- if (record2 && record2.job.status !== "running") {
29358
+ if (!record2)
29359
+ return runtimeAgentJobOutOfScopeEnvelope("jobs.complete", input.jobId);
29360
+ if (record2.job.status !== "running") {
29087
29361
  return errorEnvelope("jobs.complete", "AC_PRECONDITION_FAILED", `runtime agent job completion requires a running job: ${input.jobId}`);
29088
29362
  }
29089
29363
  if (input.status === "succeeded") {
29090
- if (record2 && record2.job.stalePolicy === "cancel-on-head-change" && isRuntimeAgentJobCursorStale(record2.job, scope)) {
29364
+ if (record2.job.stalePolicy === "cancel-on-head-change" && isRuntimeAgentJobCursorStale(record2.job, scope)) {
29091
29365
  await this.localStore.cancelRuntimeAgentJob({
29366
+ ...scope,
29092
29367
  jobId: input.jobId,
29093
29368
  status: "expired",
29094
29369
  now: input.now ?? this.clock(),
@@ -29100,7 +29375,7 @@ class ArchctxDaemon {
29100
29375
  if (input.proposalPlan) {
29101
29376
  const validation = validateRuntimeAgentProposalPlan({
29102
29377
  proposalPlan: input.proposalPlan,
29103
- job: record2?.job,
29378
+ job: record2.job,
29104
29379
  jobId: input.jobId,
29105
29380
  outputDigest: input.outputDigest
29106
29381
  });
@@ -29112,6 +29387,7 @@ class ArchctxDaemon {
29112
29387
  proposalPlan: input.proposalPlan
29113
29388
  } : input.runMetadata;
29114
29389
  const job = await this.localStore.completeRuntimeAgentJob({
29390
+ ...scope,
29115
29391
  jobId: input.jobId,
29116
29392
  status: input.status,
29117
29393
  workerId: input.workerId,
@@ -29124,8 +29400,12 @@ class ArchctxDaemon {
29124
29400
  }
29125
29401
  async jobsRetry(root, input) {
29126
29402
  this.assertRunning();
29127
- await this.architectureLedgerScope(findRepositoryRoot2(root));
29403
+ const scope = await this.architectureLedgerScope(findRepositoryRoot2(root));
29404
+ if (!await this.runtimeAgentJobInScope(scope, input.jobId)) {
29405
+ return runtimeAgentJobOutOfScopeEnvelope("jobs.retry", input.jobId);
29406
+ }
29128
29407
  const job = await this.localStore.retryRuntimeAgentJob({
29408
+ ...scope,
29129
29409
  jobId: input.jobId,
29130
29410
  reason: input.reason,
29131
29411
  now: input.now ?? this.clock()
@@ -29134,8 +29414,12 @@ class ArchctxDaemon {
29134
29414
  }
29135
29415
  async jobsCancel(root, input) {
29136
29416
  this.assertRunning();
29137
- await this.architectureLedgerScope(findRepositoryRoot2(root));
29417
+ const scope = await this.architectureLedgerScope(findRepositoryRoot2(root));
29418
+ if (!await this.runtimeAgentJobInScope(scope, input.jobId)) {
29419
+ return runtimeAgentJobOutOfScopeEnvelope("jobs.cancel", input.jobId);
29420
+ }
29138
29421
  const job = await this.localStore.cancelRuntimeAgentJob({
29422
+ ...scope,
29139
29423
  jobId: input.jobId,
29140
29424
  status: input.status ?? "cancelled",
29141
29425
  reason: input.reason,
@@ -29144,6 +29428,10 @@ class ArchctxDaemon {
29144
29428
  });
29145
29429
  return okEnvelope("jobs.cancel", { job });
29146
29430
  }
29431
+ async runtimeAgentJobInScope(scope, jobId) {
29432
+ const jobs = await this.localStore.listRuntimeAgentJobs(scope);
29433
+ return jobs.find((candidate) => candidate.job.jobId === jobId);
29434
+ }
29147
29435
  async auditRun(root, input = {}) {
29148
29436
  this.assertRunning();
29149
29437
  const repositoryRoot = findRepositoryRoot2(root);
@@ -29367,14 +29655,22 @@ class ArchctxDaemon {
29367
29655
  return typeof manifestRaw === "string" && auditGithubIssuesEnabledInManifestText(manifestRaw);
29368
29656
  }
29369
29657
  async canFileGithubIssues(root) {
29370
- const repoNameWithOwner = repositoryNameWithOwner(root);
29371
- if (repoNameWithOwner === "local/unknown") {
29658
+ const target = readGitRemoteTarget(root);
29659
+ if (!target) {
29372
29660
  return {
29373
29661
  ok: false,
29374
29662
  code: "AC_PRECONDITION_FAILED",
29375
29663
  message: "audit approve requires a resolvable GitHub owner/repo; git remote 'origin' is missing or is not a parseable GitHub URL"
29376
29664
  };
29377
29665
  }
29666
+ if (target.host !== SUPPORTED_GITHUB_REMOTE_HOST) {
29667
+ return {
29668
+ ok: false,
29669
+ code: "AC_PRECONDITION_FAILED",
29670
+ message: `audit approve supports ${SUPPORTED_GITHUB_REMOTE_HOST} remotes only (ADR-0042); git remote 'origin' resolves to host "${target.host}", and ${target.owner}/${target.repo} there is not the same repository as ${target.owner}/${target.repo} on ${SUPPORTED_GITHUB_REMOTE_HOST}`
29671
+ };
29672
+ }
29673
+ const repoNameWithOwner = `${target.owner}/${target.repo}`;
29378
29674
  const token = process.env[AUDIT_APPROVE_GH_TOKEN_ENV];
29379
29675
  if (!token) {
29380
29676
  return {
@@ -29402,7 +29698,7 @@ class ArchctxDaemon {
29402
29698
  message: `audit approve received an unrecognized visibility "${probedVisibility}" for ${repoNameWithOwner}; refusing to guess whether it is safe to publish`
29403
29699
  };
29404
29700
  }
29405
- return { ok: true, repoNameWithOwner, visibility, token };
29701
+ return { ok: true, host: target.host, repoNameWithOwner, visibility, token };
29406
29702
  }
29407
29703
  async auditList(root, input = {}) {
29408
29704
  this.assertRunning();
@@ -29477,9 +29773,9 @@ class ArchctxDaemon {
29477
29773
  const capability = await this.canFileGithubIssues(repositoryRoot);
29478
29774
  if (!capability.ok)
29479
29775
  return errorEnvelope("audit.approve", capability.code, capability.message);
29480
- const expectedConfirmToken = `public:${capability.repoNameWithOwner}:${run.baseSha}:${run.runId}`;
29776
+ const expectedConfirmToken = `public:${capability.host}/${capability.repoNameWithOwner}:${run.baseSha}:${run.runId}`;
29481
29777
  if (capability.visibility !== "private" && input.confirmPublicToken !== expectedConfirmToken) {
29482
- return errorEnvelope("audit.approve", "AC_USER_CONFIRMATION_REQUIRED", `audit run ${run.runId} targets a ${capability.visibility} repository (${capability.repoNameWithOwner}); rerun with explicit confirmation: archctx audit approve ${run.runId} --confirm-public-repo ${expectedConfirmToken}`);
29778
+ return errorEnvelope("audit.approve", "AC_USER_CONFIRMATION_REQUIRED", `audit run ${run.runId} targets a ${capability.visibility} repository (${capability.host}/${capability.repoNameWithOwner}); rerun with explicit confirmation: archctx audit approve ${run.runId} --confirm-public-repo ${expectedConfirmToken}`);
29483
29779
  }
29484
29780
  const preflight = preflightGithubIssueDrafts(run.runId, drafts);
29485
29781
  if (!preflight.ok)
@@ -29710,7 +30006,8 @@ class ArchctxDaemon {
29710
30006
  return errorEnvelope("docs.pin", "AC_SCHEMA_INVALID", "docs pin requires --library-id and --version");
29711
30007
  assertContext7LibraryId(input.libraryId);
29712
30008
  assertContext7Version(input.version);
29713
- const lock = upsertContext7Pin(readContext7Lockfile(session.workspace.root), {
30009
+ const current = readContext7LockfileState(session.workspace.root);
30010
+ const lock = upsertContext7Pin(current.lock, {
29714
30011
  libraryId: input.libraryId,
29715
30012
  version: input.version,
29716
30013
  pinnedAt: this.clock(),
@@ -29724,7 +30021,7 @@ class ArchctxDaemon {
29724
30021
  lock
29725
30022
  });
29726
30023
  }
29727
- writeContext7Lockfile(session.workspace.root, lock);
30024
+ writeContext7Lockfile(session.workspace.root, lock, current.expectedHash);
29728
30025
  return okEnvelope("docs.pin", {
29729
30026
  schemaVersion: "archcontext.context7-pin/v1",
29730
30027
  approved: true,
@@ -30066,12 +30363,55 @@ class ArchctxDaemon {
30066
30363
  });
30067
30364
  });
30068
30365
  }
30069
- async reconcileProjectionApply(root, lookupKey) {
30366
+ async inspectProjectionApplyReceipt(root, lookupKey) {
30367
+ this.assertRunning();
30368
+ await this.openSession(root);
30369
+ const inspection = await this.localStore.inspectProjectionApplyReceipt(lookupKey);
30370
+ return okEnvelope("projection.inspect-receipt", {
30371
+ found: inspection !== undefined,
30372
+ ...inspection ?? {}
30373
+ });
30374
+ }
30375
+ async recoverProjectionApply(root, intent) {
30070
30376
  this.assertRunning();
30377
+ const intentIssues = projectionApplyRecoveryIntentInvariantIssues(intent);
30378
+ if (intentIssues.length > 0) {
30379
+ return errorEnvelope("projection.recover", "AC_SCHEMA_INVALID", `projection recovery intent invariant failed: ${intentIssues.join("; ")}`);
30380
+ }
30071
30381
  return this.withWriter(async () => {
30072
- await this.openSession(root);
30073
- const consumption = await this.localStore.consumeProjectionApplyReceipt(lookupKey);
30074
- return okEnvelope("projection.reconcile", {
30382
+ const session = await this.openSession(root);
30383
+ const inspection = await this.localStore.inspectProjectionApplyReceipt(intent.receipt.lookupKey);
30384
+ if (!inspection || inspection.receipt.identity.applyId !== intent.receipt.applyId) {
30385
+ return errorEnvelope("projection.recover", "AC_PRECONDITION_FAILED", "committed projection apply receipt was not found");
30386
+ }
30387
+ if (inspection.deliveryStatus === "delivered") {
30388
+ if (!inspection.recoveryProof) {
30389
+ return errorEnvelope("projection.recover", "AC_PRECONDITION_FAILED", "committed projection receipt was delivered without a recovery proof");
30390
+ }
30391
+ return okEnvelope("projection.recover", {
30392
+ found: true,
30393
+ receipt: inspection.receipt,
30394
+ proof: { ...inspection.recoveryProof, deliveryStatus: "already-delivered" },
30395
+ refreshSignalsDelivered: false
30396
+ });
30397
+ }
30398
+ let fixedPoint;
30399
+ try {
30400
+ fixedPoint = buildRuntimeProjectionRecoveryFixedPoint(root);
30401
+ } catch (error) {
30402
+ return errorEnvelope("projection.recover", "AC_PRECONDITION_FAILED", error instanceof Error ? error.message : String(error));
30403
+ }
30404
+ const issues = runtimeProjectionRecoveryFixedPointIssues(inspection.receipt, fixedPoint);
30405
+ if (issues.length > 0) {
30406
+ return errorEnvelope("projection.recover", "AC_PRECONDITION_FAILED", `projection recovery proof failed: ${issues.join("; ")}`);
30407
+ }
30408
+ const proof = createRuntimeProjectionRecoveryProof(intent, inspection.receipt, fixedPoint);
30409
+ const currentWorktreeDigest = runtimeWorktreeDigest(root, "architecture-documentation-projection");
30410
+ if (session.workspace.repositoryId !== proof.current.snapshot.repositoryId || session.workspace.headSha !== proof.current.snapshot.headSha || currentWorktreeDigest !== proof.current.snapshot.worktreeDigest) {
30411
+ return errorEnvelope("projection.recover", "AC_PRECONDITION_FAILED", "projection recovery authority changed before delivery");
30412
+ }
30413
+ const consumption = await this.localStore.consumeProjectionApplyReceiptRecovery(proof);
30414
+ return okEnvelope("projection.recover", {
30075
30415
  found: consumption !== undefined,
30076
30416
  ...consumption ?? {}
30077
30417
  });
@@ -30140,7 +30480,6 @@ class ArchctxDaemon {
30140
30480
  await this.changeSetEngine.apply(root, draft, { approved: true });
30141
30481
  }
30142
30482
  async applyArchitectureProjectionRollbackChangeSet(root, projectedFiles, currentFiles, createdAt) {
30143
- const targetPaths = new Set(projectedFiles.map((file) => file.path));
30144
30483
  const backupBase = `.archcontext/backups/ledger-rollback/${safePathSegment(createdAt)}`;
30145
30484
  const backupRelativePath = uniqueBackupPath(root, backupBase);
30146
30485
  const manifestPath = `${backupRelativePath}/manifest.json`;
@@ -30149,7 +30488,7 @@ class ArchctxDaemon {
30149
30488
  path: backupRelativePath,
30150
30489
  manifestPath
30151
30490
  });
30152
- const removedPaths = currentFiles.filter((file) => !targetPaths.has(file.path)).map((file) => file.path);
30491
+ const removedPaths = obsoleteManagedProjectionPaths(currentFiles, projectedFiles);
30153
30492
  await this.applyArchitectureProjectionChangeSet(root, {
30154
30493
  id: `changeset.ledger-rollback-${shortDigest3(digestJson({ createdAt, projectionDigest: architectureLedgerProjectionDigest(projectedFiles) }))}`,
30155
30494
  files: [
@@ -30523,11 +30862,12 @@ class ArchctxDaemon {
30523
30862
  const scope = await this.architectureLedgerScope(root);
30524
30863
  const state = await this.localStore.readArchitectureLedgerState(scope);
30525
30864
  const projectedFiles = projectArchitectureLedgerStateToYamlFiles(state);
30865
+ const removedPaths = obsoleteManagedProjectionPaths(listModelFiles(root).filter((file) => isArchitectureLedgerManagedModelPath(file.path)), projectedFiles);
30526
30866
  if (writes) {
30527
30867
  await this.applyArchitectureProjectionChangeSet(root, {
30528
30868
  id: `changeset.ledger-project-${shortDigest3(architectureLedgerProjectionDigest(projectedFiles))}`,
30529
30869
  files: projectedFiles.map(({ path, body }) => ({ path, body })),
30530
- removedPaths: []
30870
+ removedPaths
30531
30871
  });
30532
30872
  }
30533
30873
  const drift = compareArchitectureLedgerStateToYaml({
@@ -30548,6 +30888,7 @@ class ArchctxDaemon {
30548
30888
  projectionDigest: architectureLedgerProjectionDigest(projectedFiles),
30549
30889
  graphDigest: architectureLedgerStateDigest(state),
30550
30890
  writtenPaths: writes ? projectedFiles.map((file) => file.path) : [],
30891
+ removedPaths: writes ? removedPaths : [],
30551
30892
  projectedFiles: writes ? undefined : projectedFiles,
30552
30893
  drift,
30553
30894
  reconcile
@@ -31024,12 +31365,21 @@ class ArchctxDaemon {
31024
31365
  const paths = createDeveloperReviewRunPaths({
31025
31366
  sourceRoot,
31026
31367
  challengeId: input.challenge.challengeId,
31027
- tempRoot: input.tempRoot,
31028
- stateDir: input.stateDir
31368
+ tempRoot: input.tempRoot
31029
31369
  });
31030
31370
  mkdirSync7(paths.stateDir, { recursive: true });
31031
31371
  mkdirSync7(paths.runRoot, { recursive: true });
31032
31372
  mkdirSync7(paths.worktreeTempRoot, { recursive: true });
31373
+ const createdAt = this.clock();
31374
+ writeDeveloperReviewRunOwnerMarker({
31375
+ runRoot: paths.runRoot,
31376
+ runId: paths.runId,
31377
+ challengeId: input.challenge.challengeId,
31378
+ stateDir: paths.stateDir,
31379
+ manifestPath: paths.manifestPath,
31380
+ lockPath: paths.lockPath,
31381
+ createdAt
31382
+ });
31033
31383
  const preparing = {
31034
31384
  schemaVersion: "archcontext.developer-review-run/v1",
31035
31385
  runId: paths.runId,
@@ -31041,7 +31391,7 @@ class ArchctxDaemon {
31041
31391
  manifestPath: paths.manifestPath,
31042
31392
  lockPath: paths.lockPath,
31043
31393
  pid: process.pid,
31044
- createdAt: this.clock(),
31394
+ createdAt,
31045
31395
  status: "preparing",
31046
31396
  codeGraphTemporaryState: {
31047
31397
  root: paths.runRoot,
@@ -31101,12 +31451,13 @@ class ArchctxDaemon {
31101
31451
  }
31102
31452
  }
31103
31453
  cleanupDeveloperReviewRun(run) {
31454
+ const targets = resolveOwnedDeveloperReviewRunTargets(run);
31104
31455
  const removed = [];
31105
31456
  const errors = [];
31106
- if (run.worktree) {
31457
+ if (targets.worktree) {
31107
31458
  try {
31108
- const hadWorktree = existsSync15(run.worktree.worktreeRoot);
31109
- removeDetachedReviewWorktree(run.worktree);
31459
+ const hadWorktree = existsSync15(targets.worktree.worktreeRoot);
31460
+ removeDetachedReviewWorktree(targets.worktree);
31110
31461
  if (hadWorktree)
31111
31462
  removed.push("worktree");
31112
31463
  } catch (error) {
@@ -31114,10 +31465,12 @@ class ArchctxDaemon {
31114
31465
  }
31115
31466
  }
31116
31467
  for (const [kind, path] of [
31117
- ["run-root", run.runRoot],
31118
- ["manifest", run.manifestPath],
31119
- ["lock", run.lockPath]
31468
+ ["run-root", targets.runRoot],
31469
+ ["manifest", targets.manifestPath],
31470
+ ["lock", targets.lockPath]
31120
31471
  ]) {
31472
+ if (!path)
31473
+ continue;
31121
31474
  try {
31122
31475
  const existed = existsSync15(path);
31123
31476
  removePathWithRetry(path);
@@ -31139,14 +31492,15 @@ class ArchctxDaemon {
31139
31492
  recoverDeveloperReviewRuns(input) {
31140
31493
  this.assertRunning();
31141
31494
  const sourceRoot = findRepositoryRoot2(input.repositoryRoot);
31142
- const stateDir = input.stateDir ? resolve18(input.stateDir) : defaultDeveloperReviewRunStateDir(sourceRoot);
31495
+ const stateDir = defaultDeveloperReviewRunStateDir(sourceRoot);
31143
31496
  const recovery = {
31144
31497
  schemaVersion: "archcontext.developer-review-run-recovery/v1",
31145
31498
  sourceRoot,
31146
31499
  stateDir,
31147
31500
  recovered: [],
31148
31501
  removedLocks: [],
31149
- skippedActive: []
31502
+ skippedActive: [],
31503
+ rejected: []
31150
31504
  };
31151
31505
  if (!existsSync15(stateDir))
31152
31506
  return recovery;
@@ -31154,21 +31508,39 @@ class ArchctxDaemon {
31154
31508
  if (!entry.endsWith(".json"))
31155
31509
  continue;
31156
31510
  const manifestPath = join9(stateDir, entry);
31511
+ const stats = lstatIfExists(manifestPath);
31512
+ if (!stats || !stats.isFile()) {
31513
+ recovery.rejected.push(`${entry}: not-a-regular-file`);
31514
+ continue;
31515
+ }
31157
31516
  const manifest = readDeveloperReviewRunManifest(manifestPath);
31158
31517
  if (!manifest) {
31159
31518
  rmSync8(manifestPath, { force: true });
31160
31519
  continue;
31161
31520
  }
31521
+ if (resolve18(manifest.manifestPath) !== manifestPath) {
31522
+ recovery.rejected.push(`${entry}: manifest-path-mismatch`);
31523
+ continue;
31524
+ }
31162
31525
  if (!input.force && isDeveloperReviewPidAlive(manifest.pid)) {
31163
31526
  recovery.skippedActive.push(manifest.runId);
31164
31527
  continue;
31165
31528
  }
31166
- recovery.recovered.push(this.cleanupDeveloperReviewRun(manifest));
31529
+ try {
31530
+ recovery.recovered.push(this.cleanupDeveloperReviewRun(manifest));
31531
+ } catch (error) {
31532
+ recovery.rejected.push(`${entry}: ${error instanceof Error ? error.message : String(error)}`);
31533
+ }
31167
31534
  }
31168
31535
  for (const entry of readdirSync9(stateDir).sort()) {
31169
31536
  if (!entry.endsWith(".lock"))
31170
31537
  continue;
31171
31538
  const lockPath = join9(stateDir, entry);
31539
+ const stats = lstatIfExists(lockPath);
31540
+ if (!stats || !stats.isFile()) {
31541
+ recovery.rejected.push(`${entry}: not-a-regular-file`);
31542
+ continue;
31543
+ }
31172
31544
  const lock = readJsonObject(lockPath);
31173
31545
  const pid = typeof lock?.pid === "number" ? lock.pid : undefined;
31174
31546
  const runId = typeof lock?.runId === "string" ? lock.runId : entry;
@@ -31330,16 +31702,46 @@ class ArchctxDaemon {
31330
31702
  }
31331
31703
  async repoRemove(repositoryId) {
31332
31704
  this.assertRunning();
31333
- this.sessions.delete(repositoryId);
31705
+ const hadOpenSession = this.sessions.has(repositoryId);
31706
+ const hadPersistedSession = (await this.localStore.listRepositorySessions()).some((session) => session.repositoryId === repositoryId);
31707
+ const registered = this.landscape?.repositories.some((repo) => repo.repositoryId === repositoryId) ?? false;
31708
+ if (!registered && !hadOpenSession && !hadPersistedSession) {
31709
+ return errorEnvelope("repo.remove", "AC_REPO_NOT_FOUND", `repository is not registered: ${repositoryId}`);
31710
+ }
31711
+ let detachedRelationIds = [];
31712
+ let nextLandscape;
31334
31713
  if (this.landscape) {
31335
- this.landscape = {
31714
+ detachedRelationIds = (await this.localStore.listCrossRepoRelations(this.landscape)).filter((relation) => relation.source.repositoryId === repositoryId || relation.target.repositoryId === repositoryId).map((relation) => relation.id).sort();
31715
+ const detached = new Set(detachedRelationIds);
31716
+ const next = {
31336
31717
  ...this.landscape,
31337
31718
  repositories: this.landscape.repositories.filter((repo) => repo.repositoryId !== repositoryId),
31338
- relations: this.landscape.relations
31719
+ relations: this.landscape.relations.filter((relationId) => !detached.has(relationId)),
31720
+ ...this.landscape.scope === undefined ? {} : {
31721
+ scope: {
31722
+ ...this.landscape.scope,
31723
+ defaultActiveRepositories: (this.landscape.scope.defaultActiveRepositories ?? []).filter((activeId) => activeId !== repositoryId)
31724
+ }
31725
+ }
31339
31726
  };
31340
- await this.localStore.saveLandscape(this.landscape);
31727
+ const validation = validateLandscape(next, await this.localStore.listCrossRepoRelations(next));
31728
+ if (!validation.valid) {
31729
+ return errorEnvelope("repo.remove", "AC_SCHEMA_INVALID", validation.errors.join("; "));
31730
+ }
31731
+ nextLandscape = next;
31732
+ }
31733
+ this.sessions.delete(repositoryId);
31734
+ await this.localStore.deleteRepositorySession(repositoryId);
31735
+ if (nextLandscape) {
31736
+ this.landscape = nextLandscape;
31737
+ await this.localStore.saveLandscape(nextLandscape);
31341
31738
  }
31342
- return okEnvelope("repo.remove", { repositoryId, removed: true });
31739
+ return okEnvelope("repo.remove", {
31740
+ repositoryId,
31741
+ removed: true,
31742
+ sessionRemoved: hadOpenSession || hadPersistedSession,
31743
+ detachedRelationIds
31744
+ });
31343
31745
  }
31344
31746
  async loadLandscape(landscape) {
31345
31747
  this.assertRunning();
@@ -32195,16 +32597,76 @@ data: ${payload}
32195
32597
  }
32196
32598
  }
32197
32599
 
32600
+ class RuntimeRpcTransportError extends Error {
32601
+ code;
32602
+ method;
32603
+ timeoutMs;
32604
+ elapsedMs;
32605
+ constructor(code, method, timeoutMs, elapsedMs) {
32606
+ super(code === "RPC_TIMEOUT" ? `runtime RPC timeout: ${method} exceeded ${timeoutMs}ms` : `runtime RPC cancelled: ${method} aborted after ${elapsedMs}ms`);
32607
+ this.code = code;
32608
+ this.method = method;
32609
+ this.timeoutMs = timeoutMs;
32610
+ this.elapsedMs = elapsedMs;
32611
+ this.name = "RuntimeRpcTransportError";
32612
+ }
32613
+ }
32614
+ var RUNTIME_RPC_CLIENT_TIMEOUT_POLICY = {
32615
+ health: 5000,
32616
+ short: 15000,
32617
+ normal: 120000,
32618
+ long: 900000
32619
+ };
32620
+ var RUNTIME_RPC_SHORT_METHODS = new Set([
32621
+ "shutdown",
32622
+ "runtimeStatus",
32623
+ "landscapeStatus",
32624
+ "repoList",
32625
+ "explorerStatus",
32626
+ "explorerServiceContract",
32627
+ "jobsList",
32628
+ "jobsStats",
32629
+ "ledgerState",
32630
+ "ledgerDrift",
32631
+ "stopExplorer",
32632
+ "revokeExplorerToken"
32633
+ ]);
32634
+ var RUNTIME_RPC_LONG_METHODS = new Set([
32635
+ "init",
32636
+ "sync",
32637
+ "prepare",
32638
+ "context",
32639
+ "checkpoint",
32640
+ "auditRun",
32641
+ "auditApprove",
32642
+ "recommendations",
32643
+ "book",
32644
+ "ledgerRebuild",
32645
+ "ledgerMigrate",
32646
+ "startDeveloperReviewRun",
32647
+ "runSignedDeveloperReviewAttestation"
32648
+ ]);
32649
+ function runtimeRpcMethodTimeout(method, policy) {
32650
+ if (RUNTIME_RPC_SHORT_METHODS.has(method))
32651
+ return policy.short;
32652
+ if (RUNTIME_RPC_LONG_METHODS.has(method))
32653
+ return policy.long;
32654
+ return policy.normal;
32655
+ }
32656
+
32198
32657
  class RuntimeRpcClient {
32199
32658
  connection;
32200
- constructor(connection) {
32659
+ options;
32660
+ timeouts;
32661
+ constructor(connection, options = {}) {
32201
32662
  this.connection = connection;
32663
+ this.options = options;
32664
+ this.timeouts = { ...RUNTIME_RPC_CLIENT_TIMEOUT_POLICY, ...options.timeouts };
32202
32665
  }
32203
32666
  async health() {
32204
- const response = await fetch(`${this.connection.url}health`, {
32667
+ return await this.request("health", this.timeouts.health, `${this.connection.url}health`, {
32205
32668
  headers: { "X-ArchContext-RPC-Version": RUNTIME_RPC_VERSION }
32206
32669
  });
32207
- return await response.json();
32208
32670
  }
32209
32671
  async shutdown() {
32210
32672
  return this.call("shutdown", []);
@@ -32288,8 +32750,11 @@ class RuntimeRpcClient {
32288
32750
  applyUpdate(root, input) {
32289
32751
  return this.call("applyUpdate", [root, input]);
32290
32752
  }
32291
- reconcileProjectionApply(root, lookupKey) {
32292
- return this.call("reconcileProjectionApply", [root, lookupKey]);
32753
+ inspectProjectionApplyReceipt(root, lookupKey) {
32754
+ return this.call("inspectProjectionApplyReceipt", [root, lookupKey]);
32755
+ }
32756
+ recoverProjectionApply(root, intent) {
32757
+ return this.call("recoverProjectionApply", [root, intent]);
32293
32758
  }
32294
32759
  ledgerState(root) {
32295
32760
  return this.call("ledgerState", [root]);
@@ -32367,7 +32832,7 @@ class RuntimeRpcClient {
32367
32832
  return unwrapRpcData(await this.call("recoverDeveloperReviewRuns", [input]));
32368
32833
  }
32369
32834
  async call(method, params) {
32370
- const response = await fetch(`${this.connection.url}rpc`, {
32835
+ return await this.request(method, runtimeRpcMethodTimeout(method, this.timeouts), `${this.connection.url}rpc`, {
32371
32836
  method: "POST",
32372
32837
  headers: {
32373
32838
  Authorization: `Bearer ${this.connection.token}`,
@@ -32376,7 +32841,35 @@ class RuntimeRpcClient {
32376
32841
  },
32377
32842
  body: JSON.stringify({ schemaVersion: RUNTIME_RPC_VERSION, method, params })
32378
32843
  });
32379
- return await response.json();
32844
+ }
32845
+ async request(method, timeoutMs, url, init) {
32846
+ const controller = new AbortController;
32847
+ const startedAt = Date.now();
32848
+ let timedOut = false;
32849
+ const timer = setTimeout(() => {
32850
+ timedOut = true;
32851
+ controller.abort();
32852
+ }, timeoutMs);
32853
+ const callerSignal = this.options.signal;
32854
+ const onCallerAbort = () => controller.abort();
32855
+ if (callerSignal?.aborted)
32856
+ controller.abort();
32857
+ else
32858
+ callerSignal?.addEventListener("abort", onCallerAbort, { once: true });
32859
+ try {
32860
+ const response = await fetch(url, { ...init, signal: controller.signal });
32861
+ return await response.json();
32862
+ } catch (error) {
32863
+ const elapsedMs = Date.now() - startedAt;
32864
+ if (timedOut)
32865
+ throw new RuntimeRpcTransportError("RPC_TIMEOUT", method, timeoutMs, elapsedMs);
32866
+ if (callerSignal?.aborted)
32867
+ throw new RuntimeRpcTransportError("RPC_ABORTED", method, timeoutMs, elapsedMs);
32868
+ throw error;
32869
+ } finally {
32870
+ clearTimeout(timer);
32871
+ callerSignal?.removeEventListener("abort", onCallerAbort);
32872
+ }
32380
32873
  }
32381
32874
  }
32382
32875
 
@@ -32427,7 +32920,7 @@ class ArchctxRuntimeRpcServer {
32427
32920
  startedAt: (this.options.clock ?? (() => new Date().toISOString()))()
32428
32921
  };
32429
32922
  writeFileSync6(connectionPath, JSON.stringify(this.connection, null, 2), { mode: 384 });
32430
- chmodSync3(connectionPath, 384);
32923
+ chmodSync4(connectionPath, 384);
32431
32924
  this.armIdleTimer();
32432
32925
  return this.connection;
32433
32926
  }
@@ -32521,13 +33014,26 @@ class ArchctxRuntimeRpcServer {
32521
33014
  writeJson(response, 401, { schemaVersion: RUNTIME_RPC_VERSION, ok: false, error: "runtime RPC token required" });
32522
33015
  return;
32523
33016
  }
32524
- const body = await readRequestJson(request);
32525
- if (body.schemaVersion !== RUNTIME_RPC_VERSION) {
32526
- writeJson(response, 400, { schemaVersion: RUNTIME_RPC_VERSION, ok: false, error: "runtime RPC version mismatch" });
32527
- return;
32528
- }
32529
33017
  this.inFlightRpcRequests += 1;
32530
33018
  try {
33019
+ const read = await readRpcRequestBody(request, {
33020
+ maxBytes: this.options.maxRequestBodyBytes ?? RUNTIME_RPC_MAX_REQUEST_BODY_BYTES,
33021
+ timeoutMs: this.options.requestBodyTimeoutMs ?? RUNTIME_RPC_REQUEST_BODY_TIMEOUT_MS
33022
+ });
33023
+ if (!read.ok) {
33024
+ if (read.kind === "rejected")
33025
+ writeJson(response, read.status, { schemaVersion: RUNTIME_RPC_VERSION, ok: false, error: read.error });
33026
+ return;
33027
+ }
33028
+ if (!read.value || typeof read.value !== "object" || Array.isArray(read.value)) {
33029
+ writeJson(response, 400, { schemaVersion: RUNTIME_RPC_VERSION, ok: false, error: "runtime RPC request body must be an object" });
33030
+ return;
33031
+ }
33032
+ const body = read.value;
33033
+ if (body.schemaVersion !== RUNTIME_RPC_VERSION) {
33034
+ writeJson(response, 400, { schemaVersion: RUNTIME_RPC_VERSION, ok: false, error: "runtime RPC version mismatch" });
33035
+ return;
33036
+ }
32531
33037
  const result = await this.dispatch(body.method ?? "", body.params ?? []);
32532
33038
  writeJson(response, 200, result);
32533
33039
  if (body.method === "shutdown")
@@ -32594,8 +33100,10 @@ class ArchctxRuntimeRpcServer {
32594
33100
  return this.daemon.completeTask(params[0], params[1]);
32595
33101
  case "applyUpdate":
32596
33102
  return this.daemon.applyUpdate(params[0], params[1]);
32597
- case "reconcileProjectionApply":
32598
- return this.daemon.reconcileProjectionApply(params[0], params[1]);
33103
+ case "inspectProjectionApplyReceipt":
33104
+ return this.daemon.inspectProjectionApplyReceipt(params[0], params[1]);
33105
+ case "recoverProjectionApply":
33106
+ return this.daemon.recoverProjectionApply(params[0], params[1]);
32599
33107
  case "ledgerState":
32600
33108
  return this.daemon.ledgerState(params[0]);
32601
33109
  case "ledgerDrift":
@@ -32639,13 +33147,13 @@ class ArchctxRuntimeRpcServer {
32639
33147
  case "runtimeStatus":
32640
33148
  return this.daemon.runtimeStatus(params[0]);
32641
33149
  case "startDeveloperReviewRun":
32642
- return okEnvelope("developerReview.startRun", this.daemon.startDeveloperReviewRun(params[0]));
33150
+ return okEnvelope("developerReview.startRun", this.daemon.startDeveloperReviewRun(decodeStartDeveloperReviewRunParams(params)));
32643
33151
  case "runSignedDeveloperReviewAttestation":
32644
- return okEnvelope("developerReview.attestation", await this.daemon.runSignedDeveloperReviewAttestation(params[0]));
33152
+ return okEnvelope("developerReview.attestation", await this.daemon.runSignedDeveloperReviewAttestation(decodeSignedDeveloperReviewAttestationParams(params)));
32645
33153
  case "cleanupDeveloperReviewRun":
32646
- return okEnvelope("developerReview.cleanupRun", this.daemon.cleanupDeveloperReviewRun(params[0]));
33154
+ return okEnvelope("developerReview.cleanupRun", this.daemon.cleanupDeveloperReviewRun(decodeDeveloperReviewRunManifest(params[0], "cleanupDeveloperReviewRun")));
32647
33155
  case "recoverDeveloperReviewRuns":
32648
- return okEnvelope("developerReview.recoverRuns", this.daemon.recoverDeveloperReviewRuns(params[0]));
33156
+ return okEnvelope("developerReview.recoverRuns", this.daemon.recoverDeveloperReviewRuns(decodeRecoverDeveloperReviewRunsParams(params)));
32649
33157
  case "shutdown":
32650
33158
  return okEnvelope("daemon.stop", { stopping: true });
32651
33159
  default:
@@ -32823,6 +33331,9 @@ function shouldSkipGeneratedProjectionJob(metadata, input) {
32823
33331
  function isRuntimeAgentJobCursorStale(job, scope) {
32824
33332
  return job.worktree.headSha !== scope.worktree.headSha || job.worktree.worktreeDigest !== scope.worktree.worktreeDigest;
32825
33333
  }
33334
+ function runtimeAgentJobOutOfScopeEnvelope(requestId, jobId) {
33335
+ return errorEnvelope(requestId, "AC_PRECONDITION_FAILED", `runtime agent job does not belong to this repository/worktree: ${jobId}`, "runtime-agent-job-out-of-scope");
33336
+ }
32826
33337
  function runtimeWorktreeDigest(root, profile) {
32827
33338
  switch (profile) {
32828
33339
  case "repository":
@@ -32833,6 +33344,137 @@ function runtimeWorktreeDigest(root, profile) {
32833
33344
  throw new RuntimeUpdateInputError(`unsupported worktree digest profile: ${String(profile)}`);
32834
33345
  }
32835
33346
  }
33347
+ function buildRuntimeProjectionRecoveryFixedPoint(root) {
33348
+ const loaded = loadArchitectureDocumentationInputs(root, REPO_HARNESS_PROJECTION_PROFILE);
33349
+ const sourceDigest = digestJson({
33350
+ model: loaded.model,
33351
+ profile: REPO_HARNESS_PROJECTION_PROFILE,
33352
+ decisions: loaded.decisions.map((decision) => ({ id: decision.id, path: decision.path, title: decision.title, status: decision.status }))
33353
+ });
33354
+ const codeGraphInputs = prepareArchitectureDocumentationProjectionSnapshot(root, loaded.model);
33355
+ const provenance = codeGraphInputs.provenance;
33356
+ const projection3 = renderArchitectureDocumentationProjection({
33357
+ model: loaded.model,
33358
+ profile: REPO_HARNESS_PROJECTION_PROFILE,
33359
+ decisions: loaded.decisions,
33360
+ existingFiles: loaded.existingFiles,
33361
+ verifiedAgainst: assertArchitectureProjectionVerifiedAgainst({
33362
+ branch: readCurrentBranch(root),
33363
+ commit: readHeadSha2(root),
33364
+ committedAt: readHeadCommittedAt(root)
33365
+ }),
33366
+ sourceChangesSinceStamp: loadCapabilitySourceChangesSinceStamps(root, loaded.model),
33367
+ sourceScaleSignals: loadCapabilitySourceScaleSignals(root, loaded.model),
33368
+ importGraphs: codeGraphInputs.importGraphs,
33369
+ selectorEvidence: codeGraphInputs.selectorEvidence,
33370
+ provenance,
33371
+ sourceDigest,
33372
+ generatedAt: new Date(0).toISOString(),
33373
+ refreshContext: {
33374
+ repositoryId: repositoryFingerprint2(root),
33375
+ workspaceId: runtimeProjectionWorkspaceId(root),
33376
+ headSha: provenance.baseHeadSha,
33377
+ worktreeDigest: provenance.worktreeDigest
33378
+ }
33379
+ });
33380
+ const snapshot = {
33381
+ repositoryId: repositoryFingerprint2(root),
33382
+ workspaceId: runtimeProjectionWorkspaceId(root),
33383
+ headSha: readHeadSha2(root),
33384
+ worktreeDigest: architectureDocumentationProjectionWorktreeDigest(root, loaded.model),
33385
+ baseHeadSha: projection3.provenance.baseHeadSha,
33386
+ sourceTreeDigest: projection3.provenance.sourceTreeDigest,
33387
+ modelDigest: projection3.provenance.modelDigest,
33388
+ codeGraphDigest: projection3.provenance.codeGraphDigest,
33389
+ indexedWorktreeDigest: projection3.provenance.indexedWorktreeDigest,
33390
+ projectionInputDigest: projection3.provenance.projectionInputDigest,
33391
+ rendererVersion: projection3.provenance.rendererVersion,
33392
+ layoutVersion: projection3.provenance.layoutVersion,
33393
+ generatedFrom: projection3.provenance.generatedFrom
33394
+ };
33395
+ return {
33396
+ projection: projection3,
33397
+ snapshot,
33398
+ ownedOutputDigest: runtimeProjectionOwnedOutputDigest({ files: [...projection3.files, projection3.manifest] })
33399
+ };
33400
+ }
33401
+ function runtimeProjectionRecoveryFixedPointIssues(receipt, fixedPoint) {
33402
+ const binding = receipt.recovery;
33403
+ if (!binding)
33404
+ return ["committed projection receipt does not support semantic recovery"];
33405
+ const issues = [];
33406
+ const projection3 = fixedPoint.projection;
33407
+ if (!projection3.drift.ok || projection3.rejected.length > 0)
33408
+ issues.push("projection owned outputs are not a clean fixed point");
33409
+ if (projection3.majorChange.mode !== "none" || projection3.majorChange.reasonCodes.length > 0 || projection3.majorChange.affectedNodeIds.length > 0) {
33410
+ issues.push("current architecture state contains an unresolved major change");
33411
+ }
33412
+ if (projection3.refreshSignals.length > 0)
33413
+ issues.push("current architecture state has unresolved refresh signals");
33414
+ for (const field of ["repositoryId", "workspaceId", "headSha", "worktreeDigest"]) {
33415
+ if (fixedPoint.snapshot[field] !== binding.originalExpectedSnapshot[field]) {
33416
+ issues.push(`current projection snapshot differs from the approved snapshot: ${field}`);
33417
+ }
33418
+ }
33419
+ if (binding.generatedFrom.codeGraphStatus !== "ready")
33420
+ issues.push("approved CodeGraph snapshot is unavailable");
33421
+ if (projection3.provenance.generatedFrom.codeGraphStatus !== "ready")
33422
+ issues.push("current CodeGraph snapshot is unavailable");
33423
+ if (digestJson(projection3.architectureDigests) !== digestJson(binding.expectedResultingDigests)) {
33424
+ issues.push("current model, source, flow-proof, or projection digest differs from the approved result");
33425
+ }
33426
+ if (projection3.provenance.projectionInputDigest !== receipt.result.outputSnapshot.projectionInputDigest || projection3.provenance.codeGraphDigest !== receipt.result.outputSnapshot.codeGraphDigest || projection3.provenance.rendererVersion !== binding.rendererVersion || projection3.provenance.layoutVersion !== binding.layoutVersion || digestJson(projection3.provenance.generatedFrom) !== digestJson(binding.generatedFrom)) {
33427
+ issues.push("current renderer, layout, or CodeGraph provenance differs from the approved result");
33428
+ }
33429
+ if (fixedPoint.ownedOutputDigest !== binding.ownedOutputDigest)
33430
+ issues.push("current projection-owned output bytes differ from the approved result");
33431
+ if (fixedPoint.snapshot.generatedFrom.codeGraphStatus !== "ready")
33432
+ issues.push("current proof snapshot requires CodeGraph ready");
33433
+ return issues;
33434
+ }
33435
+ function createRuntimeProjectionRecoveryProof(intent, receipt, fixedPoint) {
33436
+ const current = {
33437
+ snapshot: fixedPoint.snapshot,
33438
+ resultingDigests: fixedPoint.projection.architectureDigests,
33439
+ ownedOutputDigest: fixedPoint.ownedOutputDigest,
33440
+ fixedPointDigest: digestJson({
33441
+ schemaVersion: "archcontext.projection-apply-recovery-fixed-point/v1",
33442
+ snapshot: fixedPoint.snapshot,
33443
+ resultingDigests: fixedPoint.projection.architectureDigests,
33444
+ ownedOutputDigest: fixedPoint.ownedOutputDigest,
33445
+ drift: fixedPoint.projection.drift,
33446
+ majorChange: fixedPoint.projection.majorChange,
33447
+ refreshSignalIds: fixedPoint.projection.refreshSignals.map((signal) => signal.signalId)
33448
+ })
33449
+ };
33450
+ const payload = {
33451
+ schemaVersion: "archcontext.projection-apply-recovery-proof/v1",
33452
+ requestId: intent.requestId,
33453
+ requestDigest: digestJson(intent),
33454
+ receipt: {
33455
+ lookupKey: receipt.identity.lookupKey,
33456
+ applyId: receipt.identity.applyId,
33457
+ receiptDigest: receipt.result.receiptDigest
33458
+ },
33459
+ acceptedChange: receipt.identity.acceptedChange,
33460
+ expectedResultingDigests: receipt.recovery.expectedResultingDigests,
33461
+ current
33462
+ };
33463
+ return {
33464
+ ...payload,
33465
+ proofDigest: projectionApplyRecoveryProofDigest(payload),
33466
+ deliveryStatus: "delivered"
33467
+ };
33468
+ }
33469
+ function runtimeProjectionOwnedOutputDigest(projection3) {
33470
+ return digestJson({
33471
+ schemaVersion: "archcontext.projection-owned-output/v1",
33472
+ files: projection3.files.map((file) => ({ path: file.path, body: file.body })).sort((left, right) => left.path.localeCompare(right.path))
33473
+ });
33474
+ }
33475
+ function runtimeProjectionWorkspaceId(root) {
33476
+ return `workspace.${digestJson({ root: canonicalRepositoryRoot2(root) }).replace(/^sha256:/, "").slice(0, 16)}`;
33477
+ }
32836
33478
  function isArchContextGeneratedProjectionPath(path) {
32837
33479
  return path.replace(/\\/g, "/").startsWith(".archcontext/generated/");
32838
33480
  }
@@ -33184,13 +33826,18 @@ function runtimeAttestationIdentity(snapshot, composition) {
33184
33826
  })
33185
33827
  };
33186
33828
  }
33829
+ var DEVELOPER_REVIEW_RUN_ROOT_PREFIX = "archctx-developer-review-";
33830
+ var DEVELOPER_REVIEW_RUN_OWNER_MARKER_FILE = ".archctx-developer-review-run.json";
33831
+ var DEVELOPER_REVIEW_RUN_OWNER_SCHEMA_VERSION = "archcontext.developer-review-run-owner/v1";
33832
+ var DEVELOPER_REVIEW_RUN_ROOT_SUFFIX = /^[A-Za-z0-9]{6}$/;
33833
+ var DEVELOPER_REVIEW_RUN_ID_SUFFIX = /^[0-9a-f]{12}$/;
33187
33834
  function createDeveloperReviewRunPaths(input) {
33188
33835
  const safeChallengeId = safeControlFileSegment(input.challengeId);
33189
33836
  const runId = `${safeChallengeId}-${randomBytes(6).toString("hex")}`;
33190
- const stateDir = input.stateDir ? resolve18(input.stateDir) : defaultDeveloperReviewRunStateDir(input.sourceRoot);
33837
+ const stateDir = defaultDeveloperReviewRunStateDir(input.sourceRoot);
33191
33838
  const tempParent = input.tempRoot ? resolve18(input.tempRoot) : tmpdir3();
33192
33839
  mkdirSync7(tempParent, { recursive: true });
33193
- const runRoot = mkdtempSync4(join9(tempParent, `archctx-developer-review-${safeChallengeId.slice(0, 32)}-`));
33840
+ const runRoot = mkdtempSync4(join9(tempParent, `${DEVELOPER_REVIEW_RUN_ROOT_PREFIX}${safeChallengeId.slice(0, 32)}-`));
33194
33841
  return {
33195
33842
  runId,
33196
33843
  stateDir,
@@ -33200,6 +33847,92 @@ function createDeveloperReviewRunPaths(input) {
33200
33847
  lockPath: join9(stateDir, `${safeChallengeId}.lock`)
33201
33848
  };
33202
33849
  }
33850
+ function writeDeveloperReviewRunOwnerMarker(input) {
33851
+ const marker = {
33852
+ schemaVersion: DEVELOPER_REVIEW_RUN_OWNER_SCHEMA_VERSION,
33853
+ runId: input.runId,
33854
+ challengeId: input.challengeId,
33855
+ stateDir: input.stateDir,
33856
+ manifestPath: input.manifestPath,
33857
+ lockPath: input.lockPath,
33858
+ pid: process.pid,
33859
+ createdAt: input.createdAt
33860
+ };
33861
+ writePrivateJson3(join9(input.runRoot, DEVELOPER_REVIEW_RUN_OWNER_MARKER_FILE), marker, "wx");
33862
+ }
33863
+ function readDeveloperReviewRunOwnerMarker(path) {
33864
+ const stats = lstatIfExists(path);
33865
+ if (!stats || !stats.isFile())
33866
+ return;
33867
+ const parsed = readJsonObject(path);
33868
+ if (!parsed || parsed.schemaVersion !== DEVELOPER_REVIEW_RUN_OWNER_SCHEMA_VERSION)
33869
+ return;
33870
+ if (typeof parsed.runId !== "string" || typeof parsed.challengeId !== "string")
33871
+ return;
33872
+ if (typeof parsed.manifestPath !== "string" || typeof parsed.lockPath !== "string")
33873
+ return;
33874
+ return parsed;
33875
+ }
33876
+ function developerReviewRunNotOwned(reason) {
33877
+ return new Error(`developer-review-run-not-owned: ${reason}`);
33878
+ }
33879
+ function resolveOwnedDeveloperReviewRunTargets(run) {
33880
+ const safeChallengeId = safeControlFileSegment(run.challengeId);
33881
+ const runIdSuffix = run.runId.startsWith(`${safeChallengeId}-`) ? run.runId.slice(safeChallengeId.length + 1) : undefined;
33882
+ if (!runIdSuffix || !DEVELOPER_REVIEW_RUN_ID_SUFFIX.test(runIdSuffix))
33883
+ throw developerReviewRunNotOwned("run-id");
33884
+ const stateDir = defaultDeveloperReviewRunStateDir(run.sourceRoot);
33885
+ const manifestPath = join9(stateDir, `${safeChallengeId}.json`);
33886
+ const lockPath = join9(stateDir, `${safeChallengeId}.lock`);
33887
+ if (resolve18(run.manifestPath) !== manifestPath)
33888
+ throw developerReviewRunNotOwned("manifest-path");
33889
+ if (resolve18(run.lockPath) !== lockPath)
33890
+ throw developerReviewRunNotOwned("lock-path");
33891
+ const runRoot = resolve18(run.runRoot);
33892
+ const runRootPrefix = `${DEVELOPER_REVIEW_RUN_ROOT_PREFIX}${safeChallengeId.slice(0, 32)}-`;
33893
+ const runRootName = basename6(runRoot);
33894
+ if (!runRootName.startsWith(runRootPrefix) || !DEVELOPER_REVIEW_RUN_ROOT_SUFFIX.test(runRootName.slice(runRootPrefix.length))) {
33895
+ throw developerReviewRunNotOwned("run-root-name");
33896
+ }
33897
+ const worktreeTempRoot = join9(runRoot, "worktrees");
33898
+ if (resolve18(run.worktreeTempRoot) !== worktreeTempRoot)
33899
+ throw developerReviewRunNotOwned("worktree-temp-root");
33900
+ if (resolve18(run.codeGraphTemporaryState.root) !== runRoot)
33901
+ throw developerReviewRunNotOwned("codegraph-temporary-state-root");
33902
+ const runRootStats = lstatIfExists(runRoot);
33903
+ if (!runRootStats)
33904
+ return { stateDir, manifestPath, lockPath };
33905
+ if (!runRootStats.isDirectory())
33906
+ throw developerReviewRunNotOwned("run-root-not-a-directory");
33907
+ const marker = readDeveloperReviewRunOwnerMarker(join9(runRoot, DEVELOPER_REVIEW_RUN_OWNER_MARKER_FILE));
33908
+ if (!marker)
33909
+ throw developerReviewRunNotOwned("run-root-owner-marker-missing");
33910
+ if (marker.runId !== run.runId || marker.challengeId !== run.challengeId)
33911
+ throw developerReviewRunNotOwned("run-root-owner-marker-mismatch");
33912
+ if (marker.manifestPath !== manifestPath || marker.lockPath !== lockPath)
33913
+ throw developerReviewRunNotOwned("run-root-owner-marker-mismatch");
33914
+ if (!run.worktree)
33915
+ return { stateDir, manifestPath, lockPath, runRoot };
33916
+ const temporaryRoot = resolve18(run.worktree.temporaryRoot);
33917
+ const worktreeRoot = resolve18(run.worktree.worktreeRoot);
33918
+ if (!isContainedPath(worktreeTempRoot, temporaryRoot))
33919
+ throw developerReviewRunNotOwned("worktree-temporary-root");
33920
+ if (!isContainedPath(temporaryRoot, worktreeRoot))
33921
+ throw developerReviewRunNotOwned("worktree-root");
33922
+ if (defaultDeveloperReviewRunStateDir(run.worktree.sourceRoot) !== stateDir)
33923
+ throw developerReviewRunNotOwned("worktree-source-root");
33924
+ return { stateDir, manifestPath, lockPath, runRoot, worktree: { ...run.worktree, temporaryRoot, worktreeRoot } };
33925
+ }
33926
+ function isContainedPath(parent, child) {
33927
+ return child === parent || child.startsWith(parent.endsWith(sep7) ? parent : `${parent}${sep7}`);
33928
+ }
33929
+ function lstatIfExists(path) {
33930
+ try {
33931
+ return lstatSync7(path);
33932
+ } catch {
33933
+ return;
33934
+ }
33935
+ }
33203
33936
  function safeControlFileSegment(value) {
33204
33937
  const sanitized = value.replace(/[^A-Za-z0-9_.-]/g, "_").slice(0, 80);
33205
33938
  return sanitized.length > 0 ? sanitized : "developer-review";
@@ -33266,9 +33999,7 @@ function appendExternalDocumentationToContext(context, resource, candidate, maxB
33266
33999
  const resources = context.resources.some((entry) => entry.uri === resource.uri) ? context.resources : [...context.resources, externalResource];
33267
34000
  const unknown = `External documentation is advisory and untrusted for ${candidate.packageName}@${candidate.version}: ${candidate.intent}`;
33268
34001
  const unknowns = context.unknowns.includes(unknown) ? context.unknowns : [...context.unknowns, unknown];
33269
- const extensionWithoutDigest = { ...context.extensions };
33270
- delete extensionWithoutDigest.digest;
33271
- const withoutDigest = {
34002
+ const augmented = {
33272
34003
  ...context,
33273
34004
  unknowns,
33274
34005
  resources,
@@ -33287,7 +34018,7 @@ function appendExternalDocumentationToContext(context, resource, candidate, maxB
33287
34018
  }
33288
34019
  },
33289
34020
  extensions: {
33290
- ...extensionWithoutDigest,
34021
+ ...context.extensions,
33291
34022
  externalDocumentationDigest: digestJson({
33292
34023
  provider: resource.provider,
33293
34024
  libraryId: candidate.libraryId,
@@ -33298,22 +34029,7 @@ function appendExternalDocumentationToContext(context, resource, candidate, maxB
33298
34029
  })
33299
34030
  }
33300
34031
  };
33301
- const byteLength = Buffer.byteLength(JSON.stringify(withoutDigest), "utf8");
33302
- const withMetadata = {
33303
- ...withoutDigest,
33304
- extensions: {
33305
- ...withoutDigest.extensions,
33306
- byteLength,
33307
- budgetExceeded: byteLength > maxBytes
33308
- }
33309
- };
33310
- return {
33311
- ...withMetadata,
33312
- extensions: {
33313
- ...withMetadata.extensions,
33314
- digest: digestJson(withMetadata)
33315
- }
33316
- };
34032
+ return finalizeContextBudgetMetadata(augmented, maxBytes);
33317
34033
  }
33318
34034
  function prepareContextHasVersionRelatedUnknown(context) {
33319
34035
  const unknowns = context.unknowns.join(" ").toLowerCase();
@@ -33411,15 +34127,22 @@ function isExactPackageVersion(value) {
33411
34127
  return /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(value);
33412
34128
  }
33413
34129
  function readContext7Lockfile(root) {
33414
- const path = resolve18(root, CONTEXT7_LOCKFILE);
34130
+ return readContext7LockfileState(root).lock;
34131
+ }
34132
+ function readContext7LockfileState(root) {
34133
+ const path = assertPathHasNoSymlinkSegments(root, CONTEXT7_LOCKFILE);
33415
34134
  if (!existsSync15(path)) {
33416
34135
  return {
33417
- schemaVersion: CONTEXT7_LOCKFILE_SCHEMA_VERSION,
33418
- provider: "context7",
33419
- libraries: []
34136
+ lock: {
34137
+ schemaVersion: CONTEXT7_LOCKFILE_SCHEMA_VERSION,
34138
+ provider: "context7",
34139
+ libraries: []
34140
+ },
34141
+ expectedHash: "missing"
33420
34142
  };
33421
34143
  }
33422
- const parsed = JSON.parse(readFileSync14(path, "utf8"));
34144
+ const body = readFileSync14(path, "utf8");
34145
+ const parsed = JSON.parse(body);
33423
34146
  if (parsed.schemaVersion !== CONTEXT7_LOCKFILE_SCHEMA_VERSION || parsed.provider !== "context7" || !Array.isArray(parsed.libraries)) {
33424
34147
  throw new Error("Invalid Context7 lockfile");
33425
34148
  }
@@ -33428,8 +34151,11 @@ function readContext7Lockfile(root) {
33428
34151
  assertContext7Version(library.version);
33429
34152
  }
33430
34153
  return {
33431
- ...parsed,
33432
- libraries: [...parsed.libraries].sort((a, b) => a.libraryId.localeCompare(b.libraryId))
34154
+ lock: {
34155
+ ...parsed,
34156
+ libraries: [...parsed.libraries].sort((a, b) => a.libraryId.localeCompare(b.libraryId))
34157
+ },
34158
+ expectedHash: digestJson({ body })
33433
34159
  };
33434
34160
  }
33435
34161
  function upsertContext7Pin(lock, pin) {
@@ -33439,8 +34165,14 @@ function upsertContext7Pin(lock, pin) {
33439
34165
  libraries: [...lock.libraries.filter((library) => library.libraryId !== pin.libraryId), pin].sort((a, b) => a.libraryId.localeCompare(b.libraryId))
33440
34166
  };
33441
34167
  }
33442
- function writeContext7Lockfile(root, lock) {
33443
- writePrivateJson3(resolve18(root, CONTEXT7_LOCKFILE), lock);
34168
+ function writeContext7Lockfile(root, lock, expectedHash) {
34169
+ writeFileWithoutFollowingSymlinks({
34170
+ root,
34171
+ path: CONTEXT7_LOCKFILE,
34172
+ body: JSON.stringify(lock, null, 2),
34173
+ mode: 384,
34174
+ expectedHash
34175
+ });
33444
34176
  }
33445
34177
  function writeDeveloperReviewRunManifest(manifest) {
33446
34178
  writePrivateJson3(manifest.manifestPath, manifest);
@@ -33448,25 +34180,192 @@ function writeDeveloperReviewRunManifest(manifest) {
33448
34180
  function writePrivateJson3(path, value, flag = "w") {
33449
34181
  mkdirSync7(dirname10(path), { recursive: true });
33450
34182
  writeFileSync6(path, JSON.stringify(value, null, 2), { mode: 384, flag });
33451
- chmodSync3(path, 384);
34183
+ chmodSync4(path, 384);
33452
34184
  }
33453
34185
  function readDeveloperReviewRunManifest(path) {
33454
- const parsed = readJsonObject(path);
33455
- if (!parsed || parsed.schemaVersion !== "archcontext.developer-review-run/v1")
33456
- return;
33457
- if (typeof parsed.runId !== "string" || typeof parsed.challengeId !== "string")
33458
- return;
33459
- if (typeof parsed.repositoryId !== "number" || typeof parsed.sourceRoot !== "string")
33460
- return;
33461
- if (typeof parsed.runRoot !== "string" || typeof parsed.worktreeTempRoot !== "string")
33462
- return;
33463
- if (typeof parsed.manifestPath !== "string" || typeof parsed.lockPath !== "string")
33464
- return;
33465
- if (typeof parsed.pid !== "number" || typeof parsed.createdAt !== "string")
33466
- return;
33467
- if (parsed.status !== "preparing" && parsed.status !== "running")
34186
+ try {
34187
+ return decodeDeveloperReviewRunManifest(readJsonObject(path), "developer-review-run-manifest");
34188
+ } catch {
33468
34189
  return;
33469
- return parsed;
34190
+ }
34191
+ }
34192
+ function rpcInputInvalid(context, detail) {
34193
+ return new Error(`runtime-rpc-input-invalid: ${context} ${detail}`);
34194
+ }
34195
+ function decodeRpcRecord(value, context, label, allowedKeys) {
34196
+ if (!value || typeof value !== "object" || Array.isArray(value))
34197
+ throw rpcInputInvalid(context, `${label} must be an object`);
34198
+ const record2 = value;
34199
+ for (const key of Object.keys(record2)) {
34200
+ if (!allowedKeys.includes(key))
34201
+ throw rpcInputInvalid(context, `${label} has unknown field ${key}`);
34202
+ }
34203
+ return record2;
34204
+ }
34205
+ function rpcString(record2, context, label) {
34206
+ const value = record2[label];
34207
+ if (typeof value !== "string" || value.length === 0)
34208
+ throw rpcInputInvalid(context, `${label} must be a non-empty string`);
34209
+ return value;
34210
+ }
34211
+ function rpcOptionalString(record2, context, label) {
34212
+ return record2[label] === undefined ? undefined : rpcString(record2, context, label);
34213
+ }
34214
+ function rpcNumber(record2, context, label) {
34215
+ const value = record2[label];
34216
+ if (typeof value !== "number" || !Number.isFinite(value))
34217
+ throw rpcInputInvalid(context, `${label} must be a number`);
34218
+ return value;
34219
+ }
34220
+ function rpcLiteral(record2, context, label, allowed) {
34221
+ const value = record2[label];
34222
+ if (typeof value !== "string" || !allowed.includes(value))
34223
+ throw rpcInputInvalid(context, `${label} is not an accepted value`);
34224
+ return value;
34225
+ }
34226
+ function decodeRpcReviewChallengeV2(value, context) {
34227
+ const record2 = decodeRpcRecord(value, context, "challenge", [
34228
+ "schemaVersion",
34229
+ "challengeId",
34230
+ "installationId",
34231
+ "repositoryId",
34232
+ "pullRequestNumber",
34233
+ "headSha",
34234
+ "baseSha",
34235
+ "nonce",
34236
+ "requiredTrust",
34237
+ "policyProfileId",
34238
+ "createdAt",
34239
+ "expiresAt",
34240
+ "status"
34241
+ ]);
34242
+ return {
34243
+ schemaVersion: rpcLiteral(record2, context, "schemaVersion", ["archcontext.review-challenge/v2"]),
34244
+ challengeId: rpcString(record2, context, "challengeId"),
34245
+ installationId: rpcNumber(record2, context, "installationId"),
34246
+ repositoryId: rpcNumber(record2, context, "repositoryId"),
34247
+ pullRequestNumber: rpcNumber(record2, context, "pullRequestNumber"),
34248
+ headSha: rpcString(record2, context, "headSha"),
34249
+ baseSha: rpcString(record2, context, "baseSha"),
34250
+ nonce: rpcString(record2, context, "nonce"),
34251
+ requiredTrust: rpcLiteral(record2, context, "requiredTrust", ["developer", "organization"]),
34252
+ policyProfileId: rpcString(record2, context, "policyProfileId"),
34253
+ createdAt: rpcString(record2, context, "createdAt"),
34254
+ expiresAt: rpcString(record2, context, "expiresAt"),
34255
+ status: rpcLiteral(record2, context, "status", ["PENDING", "LEASED", "SUBMITTED", "VERIFIED", "REJECTED", "SUPERSEDED", "EXPIRED"])
34256
+ };
34257
+ }
34258
+ function decodeRpcDetachedReviewWorktree(value, context, label = "worktree") {
34259
+ const record2 = decodeRpcRecord(value, context, label, [
34260
+ "schemaVersion",
34261
+ "sourceRoot",
34262
+ "worktreeRoot",
34263
+ "temporaryRoot",
34264
+ "headSha",
34265
+ "headTreeOid",
34266
+ "detached",
34267
+ "clean"
34268
+ ]);
34269
+ if (record2.detached !== true)
34270
+ throw rpcInputInvalid(context, `${label}.detached must be true`);
34271
+ if (record2.clean !== true)
34272
+ throw rpcInputInvalid(context, `${label}.clean must be true`);
34273
+ return {
34274
+ schemaVersion: rpcLiteral(record2, context, "schemaVersion", ["archcontext.detached-review-worktree/v1"]),
34275
+ sourceRoot: rpcString(record2, context, "sourceRoot"),
34276
+ worktreeRoot: rpcString(record2, context, "worktreeRoot"),
34277
+ temporaryRoot: rpcString(record2, context, "temporaryRoot"),
34278
+ headSha: rpcString(record2, context, "headSha"),
34279
+ headTreeOid: rpcString(record2, context, "headTreeOid"),
34280
+ detached: true,
34281
+ clean: true
34282
+ };
34283
+ }
34284
+ function decodeDeveloperReviewRunManifest(value, context) {
34285
+ const record2 = decodeRpcRecord(value, context, "run", [
34286
+ "schemaVersion",
34287
+ "runId",
34288
+ "challengeId",
34289
+ "repositoryId",
34290
+ "sourceRoot",
34291
+ "runRoot",
34292
+ "worktreeTempRoot",
34293
+ "manifestPath",
34294
+ "lockPath",
34295
+ "pid",
34296
+ "createdAt",
34297
+ "status",
34298
+ "codeGraphTemporaryState",
34299
+ "worktree"
34300
+ ]);
34301
+ const codeGraphTemporaryState = decodeRpcRecord(record2.codeGraphTemporaryState, context, "run.codeGraphTemporaryState", ["root", "cleanup"]);
34302
+ return {
34303
+ schemaVersion: rpcLiteral(record2, context, "schemaVersion", ["archcontext.developer-review-run/v1"]),
34304
+ runId: rpcString(record2, context, "runId"),
34305
+ challengeId: rpcString(record2, context, "challengeId"),
34306
+ repositoryId: rpcNumber(record2, context, "repositoryId"),
34307
+ sourceRoot: rpcString(record2, context, "sourceRoot"),
34308
+ runRoot: rpcString(record2, context, "runRoot"),
34309
+ worktreeTempRoot: rpcString(record2, context, "worktreeTempRoot"),
34310
+ manifestPath: rpcString(record2, context, "manifestPath"),
34311
+ lockPath: rpcString(record2, context, "lockPath"),
34312
+ pid: rpcNumber(record2, context, "pid"),
34313
+ createdAt: rpcString(record2, context, "createdAt"),
34314
+ status: rpcLiteral(record2, context, "status", ["preparing", "running"]),
34315
+ codeGraphTemporaryState: {
34316
+ root: rpcString(codeGraphTemporaryState, context, "root"),
34317
+ cleanup: rpcLiteral(codeGraphTemporaryState, context, "cleanup", ["remove-run-root"])
34318
+ },
34319
+ ...record2.worktree === undefined ? {} : { worktree: decodeRpcDetachedReviewWorktree(record2.worktree, context, "run.worktree") }
34320
+ };
34321
+ }
34322
+ function decodeStartDeveloperReviewRunParams(params) {
34323
+ const context = "startDeveloperReviewRun";
34324
+ const record2 = decodeRpcRecord(params[0], context, "params[0]", ["repositoryRoot", "challenge", "expectedHeadTreeOid"]);
34325
+ const expectedHeadTreeOid = rpcOptionalString(record2, context, "expectedHeadTreeOid");
34326
+ return {
34327
+ repositoryRoot: rpcString(record2, context, "repositoryRoot"),
34328
+ challenge: decodeRpcReviewChallengeV2(record2.challenge, context),
34329
+ ...expectedHeadTreeOid === undefined ? {} : { expectedHeadTreeOid }
34330
+ };
34331
+ }
34332
+ function decodeSignedDeveloperReviewAttestationParams(params) {
34333
+ const context = "runSignedDeveloperReviewAttestation";
34334
+ const record2 = decodeRpcRecord(params[0], context, "params[0]", [
34335
+ "challenge",
34336
+ "worktree",
34337
+ "keyRef",
34338
+ "principalId",
34339
+ "publicKeyId",
34340
+ "taskSessionId",
34341
+ "mergeBaseSha",
34342
+ "startedAt",
34343
+ "completedAt"
34344
+ ]);
34345
+ const optional = {
34346
+ taskSessionId: rpcOptionalString(record2, context, "taskSessionId"),
34347
+ mergeBaseSha: rpcOptionalString(record2, context, "mergeBaseSha"),
34348
+ startedAt: rpcOptionalString(record2, context, "startedAt"),
34349
+ completedAt: rpcOptionalString(record2, context, "completedAt")
34350
+ };
34351
+ return {
34352
+ challenge: decodeRpcReviewChallengeV2(record2.challenge, context),
34353
+ worktree: decodeRpcDetachedReviewWorktree(record2.worktree, context),
34354
+ keyRef: rpcString(record2, context, "keyRef"),
34355
+ principalId: rpcString(record2, context, "principalId"),
34356
+ publicKeyId: rpcString(record2, context, "publicKeyId"),
34357
+ ...Object.fromEntries(Object.entries(optional).filter(([, value]) => value !== undefined))
34358
+ };
34359
+ }
34360
+ function decodeRecoverDeveloperReviewRunsParams(params) {
34361
+ const context = "recoverDeveloperReviewRuns";
34362
+ const record2 = decodeRpcRecord(params[0], context, "params[0]", ["repositoryRoot", "force"]);
34363
+ if (record2.force !== undefined && typeof record2.force !== "boolean")
34364
+ throw rpcInputInvalid(context, "force must be a boolean");
34365
+ return {
34366
+ repositoryRoot: rpcString(record2, context, "repositoryRoot"),
34367
+ ...record2.force === undefined ? {} : { force: record2.force }
34368
+ };
33470
34369
  }
33471
34370
  function readJsonObject(path) {
33472
34371
  try {
@@ -33651,33 +34550,42 @@ function readCurrentBranch(root) {
33651
34550
  }
33652
34551
  }
33653
34552
  function repositoryNameWithOwner(root) {
34553
+ const target = readGitRemoteTarget(root);
34554
+ return target ? `${target.owner}/${target.repo}` : "local/unknown";
34555
+ }
34556
+ var SUPPORTED_GITHUB_REMOTE_HOST = "github.com";
34557
+ function readGitRemoteTarget(root) {
33654
34558
  try {
33655
34559
  const url = execFileSync7("git", ["remote", "get-url", "origin"], {
33656
34560
  cwd: root,
33657
34561
  encoding: "utf8",
33658
34562
  stdio: ["ignore", "pipe", "ignore"]
33659
34563
  }).trim();
33660
- return parseGitRemoteOwnerRepo(url) ?? "local/unknown";
34564
+ return parseGitRemoteTarget(url);
33661
34565
  } catch {
33662
- return "local/unknown";
34566
+ return;
33663
34567
  }
33664
34568
  }
33665
- function parseGitRemoteOwnerRepo(url) {
34569
+ function parseGitRemoteTarget(url) {
33666
34570
  const stripped = url.trim().replace(/\.git$/, "");
33667
- const scpMatch = /^[^/@]+@[^:/]+:(.+)$/.exec(stripped);
34571
+ const scpMatch = /^[^/@]+@([^:/]+):(.+)$/.exec(stripped);
33668
34572
  if (scpMatch)
33669
- return normalizeOwnerRepoPath(scpMatch[1]);
34573
+ return gitRemoteTarget(scpMatch[1], scpMatch[2]);
33670
34574
  try {
33671
- return normalizeOwnerRepoPath(new URL(stripped).pathname);
34575
+ const parsed = new URL(stripped);
34576
+ return gitRemoteTarget(parsed.hostname, parsed.pathname);
33672
34577
  } catch {
33673
34578
  return;
33674
34579
  }
33675
34580
  }
33676
- function normalizeOwnerRepoPath(path) {
34581
+ function gitRemoteTarget(host, path) {
34582
+ const canonicalHost = host.trim().toLowerCase();
34583
+ if (!canonicalHost)
34584
+ return;
33677
34585
  const segments = path.split("/").map((segment) => segment.trim()).filter(Boolean);
33678
34586
  if (segments.length < 2)
33679
34587
  return;
33680
- return segments.slice(-2).join("/");
34588
+ return { host: canonicalHost, owner: segments[segments.length - 2], repo: segments[segments.length - 1] };
33681
34589
  }
33682
34590
  function normalizeGithubRepoVisibility(value) {
33683
34591
  const lowered = value.trim().toLowerCase();
@@ -33693,6 +34601,10 @@ function auditApproveResultPayload(runId, status, totalCount, issuedIssues) {
33693
34601
  issuedIssues
33694
34602
  };
33695
34603
  }
34604
+ function obsoleteManagedProjectionPaths(currentFiles, projectedFiles) {
34605
+ const targetPaths = new Set(projectedFiles.map((file) => file.path));
34606
+ return currentFiles.filter((file) => !targetPaths.has(file.path)).map((file) => file.path);
34607
+ }
33696
34608
  function expectedFileHash(root, path) {
33697
34609
  const absolute = resolve18(root, path);
33698
34610
  return existsSync15(absolute) ? digestJson({ body: readFileSync14(absolute, "utf8") }) : "missing";
@@ -33987,13 +34899,63 @@ function isRpcVersionHeaderCompatible(request) {
33987
34899
  const header = requestRpcVersionHeader(request);
33988
34900
  return header === undefined || header === RUNTIME_RPC_VERSION;
33989
34901
  }
33990
- async function readRequestJson(request) {
33991
- const chunks = [];
33992
- for await (const chunk of request)
33993
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
33994
- if (chunks.length === 0)
33995
- return {};
33996
- return JSON.parse(Buffer.concat(chunks).toString("utf8"));
34902
+ async function readRpcRequestBody(request, limits) {
34903
+ const declaredLength = Number(request.headers["content-length"]);
34904
+ if (Number.isFinite(declaredLength) && declaredLength > limits.maxBytes) {
34905
+ request.pause();
34906
+ return { ok: false, kind: "rejected", status: 413, error: "runtime RPC request body exceeds the configured limit" };
34907
+ }
34908
+ return await new Promise((resolveBody) => {
34909
+ let chunks = [];
34910
+ let size = 0;
34911
+ let settled = false;
34912
+ const timer = setTimeout(() => {
34913
+ settle({ ok: false, kind: "rejected", status: 408, error: "runtime RPC request body read timeout" });
34914
+ }, limits.timeoutMs);
34915
+ const settle = (result) => {
34916
+ if (settled)
34917
+ return;
34918
+ settled = true;
34919
+ clearTimeout(timer);
34920
+ chunks = [];
34921
+ request.off("data", onData);
34922
+ request.off("end", onEnd);
34923
+ request.off("aborted", onAborted);
34924
+ request.off("error", onAborted);
34925
+ request.off("close", onClose);
34926
+ if (!result.ok)
34927
+ request.pause();
34928
+ resolveBody(result);
34929
+ };
34930
+ const onData = (chunk) => {
34931
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
34932
+ size += buffer.length;
34933
+ if (size > limits.maxBytes) {
34934
+ settle({ ok: false, kind: "rejected", status: 413, error: "runtime RPC request body exceeds the configured limit" });
34935
+ return;
34936
+ }
34937
+ chunks.push(buffer);
34938
+ };
34939
+ const onEnd = () => {
34940
+ if (size === 0) {
34941
+ settle({ ok: true, value: {} });
34942
+ return;
34943
+ }
34944
+ const body = Buffer.concat(chunks).toString("utf8");
34945
+ try {
34946
+ settle({ ok: true, value: JSON.parse(body) });
34947
+ } catch {
34948
+ settle({ ok: false, kind: "rejected", status: 400, error: "runtime RPC request body is not valid JSON" });
34949
+ }
34950
+ };
34951
+ const onAborted = () => settle({ ok: false, kind: "aborted" });
34952
+ const onClose = () => settle({ ok: false, kind: "aborted" });
34953
+ request.on("data", onData);
34954
+ request.on("end", onEnd);
34955
+ request.on("aborted", onAborted);
34956
+ request.on("error", onAborted);
34957
+ request.on("close", onClose);
34958
+ });
33997
34959
  }
33998
34960
  function writeJson(response, statusCode, body) {
33999
34961
  response.writeHead(statusCode, { "Content-Type": "application/json; charset=utf-8" });
@@ -34149,20 +35111,82 @@ function structurizrElementType(kind) {
34149
35111
  init_src();
34150
35112
 
34151
35113
  // packages/local-runtime/runtime-daemon/src/index.ts
34152
- import { chmodSync as chmodSync4, closeSync as closeSync7, existsSync as existsSync16, mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync5, openSync as openSync7, readdirSync as readdirSync10, readFileSync as readFileSync15, rmSync as rmSync9, statSync as statSync10, writeFileSync as writeFileSync7 } from "node:fs";
35114
+ import { chmodSync as chmodSync5, closeSync as closeSync7, existsSync as existsSync16, lstatSync as lstatSync8, mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync5, openSync as openSync7, readdirSync as readdirSync10, readFileSync as readFileSync15, rmSync as rmSync9, statSync as statSync10, writeFileSync as writeFileSync7 } from "node:fs";
35115
+ init_src();
34153
35116
  init_src();
34154
35117
  var DEFAULT_DAEMON_IDLE_TIMEOUT_MS2 = 30 * 60000;
34155
35118
  var RUNTIME_RPC_VERSION2 = LOCAL_RUNTIME_RPC_SCHEMA_VERSION;
35119
+ var RUNTIME_RPC_MAX_REQUEST_BODY_BYTES2 = 16 * 1024 * 1024;
35120
+ class RuntimeRpcTransportError2 extends Error {
35121
+ code;
35122
+ method;
35123
+ timeoutMs;
35124
+ elapsedMs;
35125
+ constructor(code, method, timeoutMs, elapsedMs) {
35126
+ super(code === "RPC_TIMEOUT" ? `runtime RPC timeout: ${method} exceeded ${timeoutMs}ms` : `runtime RPC cancelled: ${method} aborted after ${elapsedMs}ms`);
35127
+ this.code = code;
35128
+ this.method = method;
35129
+ this.timeoutMs = timeoutMs;
35130
+ this.elapsedMs = elapsedMs;
35131
+ this.name = "RuntimeRpcTransportError";
35132
+ }
35133
+ }
35134
+ var RUNTIME_RPC_CLIENT_TIMEOUT_POLICY2 = {
35135
+ health: 5000,
35136
+ short: 15000,
35137
+ normal: 120000,
35138
+ long: 900000
35139
+ };
35140
+ var RUNTIME_RPC_SHORT_METHODS2 = new Set([
35141
+ "shutdown",
35142
+ "runtimeStatus",
35143
+ "landscapeStatus",
35144
+ "repoList",
35145
+ "explorerStatus",
35146
+ "explorerServiceContract",
35147
+ "jobsList",
35148
+ "jobsStats",
35149
+ "ledgerState",
35150
+ "ledgerDrift",
35151
+ "stopExplorer",
35152
+ "revokeExplorerToken"
35153
+ ]);
35154
+ var RUNTIME_RPC_LONG_METHODS2 = new Set([
35155
+ "init",
35156
+ "sync",
35157
+ "prepare",
35158
+ "context",
35159
+ "checkpoint",
35160
+ "auditRun",
35161
+ "auditApprove",
35162
+ "recommendations",
35163
+ "book",
35164
+ "ledgerRebuild",
35165
+ "ledgerMigrate",
35166
+ "startDeveloperReviewRun",
35167
+ "runSignedDeveloperReviewAttestation"
35168
+ ]);
35169
+ function runtimeRpcMethodTimeout2(method, policy) {
35170
+ if (RUNTIME_RPC_SHORT_METHODS2.has(method))
35171
+ return policy.short;
35172
+ if (RUNTIME_RPC_LONG_METHODS2.has(method))
35173
+ return policy.long;
35174
+ return policy.normal;
35175
+ }
35176
+
34156
35177
  class RuntimeRpcClient2 {
34157
35178
  connection;
34158
- constructor(connection) {
35179
+ options;
35180
+ timeouts;
35181
+ constructor(connection, options = {}) {
34159
35182
  this.connection = connection;
35183
+ this.options = options;
35184
+ this.timeouts = { ...RUNTIME_RPC_CLIENT_TIMEOUT_POLICY2, ...options.timeouts };
34160
35185
  }
34161
35186
  async health() {
34162
- const response = await fetch(`${this.connection.url}health`, {
35187
+ return await this.request("health", this.timeouts.health, `${this.connection.url}health`, {
34163
35188
  headers: { "X-ArchContext-RPC-Version": RUNTIME_RPC_VERSION2 }
34164
35189
  });
34165
- return await response.json();
34166
35190
  }
34167
35191
  async shutdown() {
34168
35192
  return this.call("shutdown", []);
@@ -34246,8 +35270,11 @@ class RuntimeRpcClient2 {
34246
35270
  applyUpdate(root, input) {
34247
35271
  return this.call("applyUpdate", [root, input]);
34248
35272
  }
34249
- reconcileProjectionApply(root, lookupKey) {
34250
- return this.call("reconcileProjectionApply", [root, lookupKey]);
35273
+ inspectProjectionApplyReceipt(root, lookupKey) {
35274
+ return this.call("inspectProjectionApplyReceipt", [root, lookupKey]);
35275
+ }
35276
+ recoverProjectionApply(root, intent) {
35277
+ return this.call("recoverProjectionApply", [root, intent]);
34251
35278
  }
34252
35279
  ledgerState(root) {
34253
35280
  return this.call("ledgerState", [root]);
@@ -34325,7 +35352,7 @@ class RuntimeRpcClient2 {
34325
35352
  return unwrapRpcData2(await this.call("recoverDeveloperReviewRuns", [input]));
34326
35353
  }
34327
35354
  async call(method, params) {
34328
- const response = await fetch(`${this.connection.url}rpc`, {
35355
+ return await this.request(method, runtimeRpcMethodTimeout2(method, this.timeouts), `${this.connection.url}rpc`, {
34329
35356
  method: "POST",
34330
35357
  headers: {
34331
35358
  Authorization: `Bearer ${this.connection.token}`,
@@ -34334,7 +35361,35 @@ class RuntimeRpcClient2 {
34334
35361
  },
34335
35362
  body: JSON.stringify({ schemaVersion: RUNTIME_RPC_VERSION2, method, params })
34336
35363
  });
34337
- return await response.json();
35364
+ }
35365
+ async request(method, timeoutMs, url, init) {
35366
+ const controller = new AbortController;
35367
+ const startedAt = Date.now();
35368
+ let timedOut = false;
35369
+ const timer = setTimeout(() => {
35370
+ timedOut = true;
35371
+ controller.abort();
35372
+ }, timeoutMs);
35373
+ const callerSignal = this.options.signal;
35374
+ const onCallerAbort = () => controller.abort();
35375
+ if (callerSignal?.aborted)
35376
+ controller.abort();
35377
+ else
35378
+ callerSignal?.addEventListener("abort", onCallerAbort, { once: true });
35379
+ try {
35380
+ const response = await fetch(url, { ...init, signal: controller.signal });
35381
+ return await response.json();
35382
+ } catch (error) {
35383
+ const elapsedMs = Date.now() - startedAt;
35384
+ if (timedOut)
35385
+ throw new RuntimeRpcTransportError2("RPC_TIMEOUT", method, timeoutMs, elapsedMs);
35386
+ if (callerSignal?.aborted)
35387
+ throw new RuntimeRpcTransportError2("RPC_ABORTED", method, timeoutMs, elapsedMs);
35388
+ throw error;
35389
+ } finally {
35390
+ clearTimeout(timer);
35391
+ callerSignal?.removeEventListener("abort", onCallerAbort);
35392
+ }
34338
35393
  }
34339
35394
  }
34340
35395
  function defaultDaemonConnectionPath2(root = process.cwd()) {
@@ -35109,7 +36164,7 @@ async function runCliUnchecked(command2 = "help", args2 = [], cwd, deps = {}) {
35109
36164
  requestId: "help",
35110
36165
  data: {
35111
36166
  commands: ["capabilities", "projection", "init", "sync", "validate", "context", "status", "daemon", "state", "repo", "landscape", "ledger", "book", "recommendations", "explore", "prepare", "practices", "checkpoint", "hook", "hooks", "investigate", "agents", "jobs", "audit", "plan", "apply", "review", "complete", "github", "config", "mcp", "install", "uninstall", "doctor", "update", "paths", "privacy-audit", "export", "import", "resolve", "tunnel"],
35112
- examples: ["archctx init --name MyApp", "archctx state recover --from-git", "archctx ledger migrate --from-yaml --dry-run", "archctx ledger promote --mode authoritative --preflight --rollback-plan", "archctx book recommendations --open --explain", "archctx recommendations accept --id recommendation.<id> --reason 'Accepted after local readback.'", "archctx recommendations metrics", "archctx practices validate --strict", "archctx practices list --json", "archctx practices waivers", "archctx practices waive --practice-id modularity.no-new-cycle --owner team-architecture --reason 'External migration window requires this edge until cutover.' --review-at 2026-07-10T00:00:00.000Z --expires-at 2026-07-24T00:00:00.000Z --evidence-digest sha256:<64-hex> --subject module.a->module.b", "archctx checkpoint --task-session-id task_cli", "archctx investigate --runner-port codex", "archctx agents status --status queued,running", "archctx agents budget", "archctx hook enqueue --event post-edit --path src/app.ts", "archctx jobs list --status queued", "archctx audit run --reason 'quarterly architecture audit'", "archctx audit run --no-wait", "archctx audit list --status pending", "archctx audit show audit_run.<id>", "archctx audit approve audit_run.<id>", "archctx audit approve audit_run.<id> --confirm-public-repo public:<owner/repo>:<baseSha>:<runId>", "archctx audit approve audit_run.<id> --resume", "archctx hooks install --host codex", "archctx paths", "archctx update --check", "archctx doctor --check-updates", "archctx github connect", "archctx github status", "archctx daemon start", "archctx explore start --foreground", "archctx export likec4", "archctx import structurizr --content '<json>'", "archctx resolve --path packages/core/projection-engine/src/index.ts", "archctx tunnel"]
36167
+ examples: ["archctx init --name MyApp", "archctx state recover --from-git", "archctx ledger migrate --from-yaml --dry-run", "archctx ledger promote --mode authoritative --preflight --rollback-plan", "archctx book recommendations --open --explain", "archctx recommendations accept --id recommendation.<id> --reason 'Accepted after local readback.'", "archctx recommendations metrics", "archctx practices validate --strict", "archctx practices list --json", "archctx practices waivers", "archctx practices waive --practice-id modularity.no-new-cycle --owner team-architecture --reason 'External migration window requires this edge until cutover.' --review-at 2026-07-10T00:00:00.000Z --expires-at 2026-07-24T00:00:00.000Z --evidence-digest sha256:<64-hex> --subject module.a->module.b", "archctx checkpoint --task-session-id task_cli", "archctx investigate --runner-port codex", "archctx agents status --status queued,running", "archctx agents budget", "archctx hook enqueue --event post-edit --path src/app.ts", "archctx jobs list --status queued", "archctx audit run --reason 'quarterly architecture audit'", "archctx audit run --no-wait", "archctx audit list --status pending", "archctx audit show audit_run.<id>", "archctx audit approve audit_run.<id>", "archctx audit approve audit_run.<id> --confirm-public-repo public:<host>/<owner>/<repo>:<baseSha>:<runId>", "archctx audit approve audit_run.<id> --resume", "archctx hooks install --host codex", "archctx paths", "archctx update --check", "archctx doctor --check-updates", "archctx github connect", "archctx github status", "archctx daemon start", "archctx explore start --foreground", "archctx export likec4", "archctx import structurizr --content '<json>'", "archctx resolve --path packages/core/projection-engine/src/index.ts", "archctx tunnel"]
35113
36168
  }
35114
36169
  };
35115
36170
  }
@@ -35801,9 +36856,11 @@ async function runArchitectureDocsAdoptionCommand(args2, root, daemon, projectio
35801
36856
  });
35802
36857
  }
35803
36858
  async function runProjectionProtocolCommand(args2, cwd, daemon) {
35804
- if ((args2[0] ?? "run") !== "run") {
35805
- return errorEnvelope("projection", "AC_SCHEMA_INVALID", "projection requires run --request-json <ProjectionRequestV1>");
35806
- }
36859
+ const subcommand = args2[0] ?? "run";
36860
+ if (subcommand === "recover")
36861
+ return runProjectionApplyRecoveryCommand(args2.slice(1), cwd, daemon);
36862
+ if (subcommand !== "run")
36863
+ return errorEnvelope("projection", "AC_SCHEMA_INVALID", "projection requires run|recover --request-json <request>");
35807
36864
  const raw = readFlag(args2, "--request-json");
35808
36865
  if (!raw)
35809
36866
  return errorEnvelope("projection.run", "AC_SCHEMA_INVALID", "projection run requires --request-json");
@@ -35818,21 +36875,19 @@ async function runProjectionProtocolCommand(args2, cwd, daemon) {
35818
36875
  if (request.mode === "apply" && request.acceptedChange) {
35819
36876
  try {
35820
36877
  assertProjectionExpectedSnapshotAgainstModel(request, root, loadNativeModelFromArchContext(root));
36878
+ const inspected = await daemon.inspectProjectionApplyReceipt(root, projectionApplyLookupKey({
36879
+ repositoryId: request.expected.repositoryId,
36880
+ workspaceId: request.expected.workspaceId,
36881
+ acceptedChange: request.acceptedChange
36882
+ }));
36883
+ if (!inspected.ok)
36884
+ return inspected;
36885
+ if (inspected.data.found === true) {
36886
+ return errorEnvelope("projection.run", "AC_PRECONDITION_FAILED", "committed projection receipt requires explicit projection recover");
36887
+ }
35821
36888
  } catch (error) {
35822
36889
  return errorEnvelope("projection.run", "AC_PRECONDITION_FAILED", error instanceof Error ? error.message : String(error));
35823
36890
  }
35824
- const reconciled = await daemon.reconcileProjectionApply(root, projectionApplyLookupKey({
35825
- repositoryId: request.expected.repositoryId,
35826
- workspaceId: request.expected.workspaceId,
35827
- acceptedChange: request.acceptedChange
35828
- }));
35829
- if (!reconciled.ok)
35830
- return reconciled;
35831
- const data = reconciled.data;
35832
- if (data.found === true) {
35833
- const receipt = data.receipt;
35834
- return projectionProtocolResultEnvelope(data.refreshSignalsDelivered === true ? projectionResultDelivery(receipt.result, "applied", receipt.result.refreshSignals, request.requestId) : projectionResultDelivery(receipt.result, "noop", [], request.requestId));
35835
- }
35836
36891
  }
35837
36892
  let projection3;
35838
36893
  try {
@@ -35873,21 +36928,51 @@ async function runProjectionProtocolCommand(args2, cwd, daemon) {
35873
36928
  }
35874
36929
  if (request.mode === "apply" && !projection3.plan.drift.ok) {
35875
36930
  const changeSetId = `changeset.docs-projection-${projection3.plan.projectionDigest.replace(/^sha256:/, "").slice(0, 16)}`;
36931
+ let fixedPointProjection;
36932
+ try {
36933
+ fixedPointProjection = request.acceptedChange ? buildArchitectureDocsProjection(root, generatedAt, REPO_HARNESS_PROJECTION_PROFILE, projection3.files) : projection3;
36934
+ if (request.acceptedChange && (fixedPointProjection.plan.rejected.length > 0 || fixedPointProjection.plan.majorChange.mode !== "none" || fixedPointProjection.plan.refreshSignals.length > 0)) {
36935
+ return errorEnvelope("projection.run", "AC_PRECONDITION_FAILED", "accepted projection did not produce a no-accepted-change semantic fixed point");
36936
+ }
36937
+ } catch (error) {
36938
+ return errorEnvelope("projection.run", "AC_PRECONDITION_FAILED", error instanceof Error ? error.message : String(error));
36939
+ }
36940
+ const committedFiles = projectionProtocolFilesForExpectedOutput(root, fixedPointProjection);
36941
+ const committedSignals = request.acceptedChange ? projection3.plan.refreshSignals.map((signal) => ({
36942
+ ...signal,
36943
+ resultingDigests: fixedPointProjection.plan.architectureDigests
36944
+ })) : fixedPointProjection.plan.refreshSignals;
35876
36945
  const applyIdentity = request.acceptedChange ? createProjectionApplyIdentity({
35877
36946
  repositoryId: request.expected.repositoryId,
35878
36947
  workspaceId: request.expected.workspaceId,
35879
36948
  acceptedChange: request.acceptedChange,
35880
36949
  changeSetId,
35881
36950
  idempotencyKey: `idem_${changeSetId}`,
35882
- files: projectionProtocolFiles(projection3),
35883
- refreshSignals: projection3.plan.refreshSignals
36951
+ files: committedFiles,
36952
+ refreshSignals: committedSignals
35884
36953
  }) : undefined;
35885
- const appliedResult = projectionProtocolResult(request, projection3, "applied", projection3, applyIdentity);
35886
- const applyReceipt = applyIdentity ? { schemaVersion: "archcontext.projection-apply-receipt/v1", identity: applyIdentity, result: appliedResult } : undefined;
36954
+ const appliedResult = projectionProtocolResult(request, projection3, "applied", fixedPointProjection, applyIdentity, {
36955
+ files: committedFiles,
36956
+ refreshSignals: committedSignals
36957
+ });
36958
+ let recoveryBinding;
36959
+ if (applyIdentity) {
36960
+ try {
36961
+ recoveryBinding = createProjectionApplyRecoveryBinding(request, fixedPointProjection, appliedResult);
36962
+ } catch (error) {
36963
+ return errorEnvelope("projection.run", "AC_PRECONDITION_FAILED", error instanceof Error ? error.message : String(error));
36964
+ }
36965
+ }
36966
+ const applyReceipt = applyIdentity ? {
36967
+ schemaVersion: "archcontext.projection-apply-receipt/v1",
36968
+ identity: applyIdentity,
36969
+ result: appliedResult,
36970
+ recovery: recoveryBinding
36971
+ } : undefined;
35887
36972
  const planned = await daemon.planUpdate(root, {
35888
36973
  id: changeSetId,
35889
36974
  reason: { taskSessionId: request.requestId },
35890
- operations: [architectureDocsRenderProjectionOperation(root, projection3.files)],
36975
+ operations: [architectureDocsRenderProjectionOperation(root, fixedPointProjection.files)],
35891
36976
  worktreeDigestPrecondition: {
35892
36977
  profile: "architecture-documentation-projection",
35893
36978
  expectedDigest: request.expected.worktreeDigest
@@ -35921,18 +37006,120 @@ async function runProjectionProtocolCommand(args2, cwd, daemon) {
35921
37006
  if (!postApplyMatches) {
35922
37007
  return projectionProtocolResultEnvelope(projectionResultDelivery(appliedResult, "applied-reconcile-required", []));
35923
37008
  }
35924
- const reconciled = await daemon.reconcileProjectionApply(root, applyReceipt.identity.lookupKey);
35925
- if (!reconciled.ok)
35926
- return reconciled;
35927
- const consumption = reconciled.data;
35928
- if (consumption.found !== true || consumption.refreshSignalsDelivered !== true) {
37009
+ const delivery = await daemon.recoverProjectionApply(root, {
37010
+ schemaVersion: PROJECTION_APPLY_RECOVERY_INTENT_SCHEMA_VERSION,
37011
+ requestId: request.requestId,
37012
+ profile: REPO_HARNESS_PROJECTION_PROFILE,
37013
+ receipt: {
37014
+ lookupKey: applyReceipt.identity.lookupKey,
37015
+ applyId: applyReceipt.identity.applyId
37016
+ }
37017
+ });
37018
+ if (!delivery.ok)
37019
+ return delivery;
37020
+ const delivered = delivery.data;
37021
+ if (delivered.found !== true || delivered.refreshSignalsDelivered !== true) {
35929
37022
  return errorEnvelope("projection.run", "AC_PRECONDITION_FAILED", "committed projection apply receipt was not available for first delivery");
35930
37023
  }
35931
- return projectionProtocolResultEnvelope(consumption.receipt.result);
37024
+ return projectionProtocolResultEnvelope(projectionResultDelivery(appliedResult, "applied", delivered.receipt.result.refreshSignals, request.requestId));
35932
37025
  }
35933
37026
  const status = projection3.plan.drift.ok ? "noop" : "planned";
35934
37027
  return projectionProtocolEnvelope(request, projection3, status);
35935
37028
  }
37029
+ async function runProjectionApplyRecoveryCommand(args2, cwd, daemon) {
37030
+ const raw = readFlag(args2, "--request-json");
37031
+ if (!raw)
37032
+ return errorEnvelope("projection.recover", "AC_SCHEMA_INVALID", "projection recover requires --request-json");
37033
+ let intent;
37034
+ try {
37035
+ intent = parseProjectionApplyRecoveryIntent(raw);
37036
+ } catch (error) {
37037
+ return errorEnvelope("projection.recover", "AC_SCHEMA_INVALID", error instanceof Error ? error.message : String(error));
37038
+ }
37039
+ return recoverProjectionApplyIntent(intent, findRepositoryRoot(cwd), daemon);
37040
+ }
37041
+ async function recoverProjectionApplyIntent(intent, root, daemon) {
37042
+ let recovered;
37043
+ try {
37044
+ recovered = await daemon.recoverProjectionApply(root, intent);
37045
+ } catch (error) {
37046
+ return errorEnvelope("projection.recover", "AC_PRECONDITION_FAILED", error instanceof Error ? error.message : String(error));
37047
+ }
37048
+ if (!recovered.ok)
37049
+ return recovered;
37050
+ const consumption = recovered.data;
37051
+ if (consumption.found !== true || !consumption.receipt || !consumption.proof) {
37052
+ return errorEnvelope("projection.recover", "AC_PRECONDITION_FAILED", "committed projection apply receipt was unavailable for recovery delivery");
37053
+ }
37054
+ return projectionApplyRecoveryResultEnvelope({
37055
+ schemaVersion: PROJECTION_APPLY_RECOVERY_RESULT_SCHEMA_VERSION,
37056
+ proof: consumption.proof,
37057
+ refreshSignals: consumption.refreshSignalsDelivered ? consumption.receipt.result.refreshSignals : []
37058
+ });
37059
+ }
37060
+ function createProjectionApplyRecoveryBinding(request, projection3, result) {
37061
+ const provenance = projection3.plan.provenance;
37062
+ return {
37063
+ schemaVersion: "archcontext.projection-apply-recovery-binding/v1",
37064
+ targets: [...request.targets],
37065
+ changedPaths: [...request.changedPaths],
37066
+ originalExpectedSnapshot: { ...request.expected },
37067
+ expectedResultingDigests: projection3.plan.architectureDigests,
37068
+ rendererVersion: provenance.rendererVersion,
37069
+ layoutVersion: provenance.layoutVersion,
37070
+ generatedFrom: provenance.generatedFrom,
37071
+ ownedOutputDigest: projectionOwnedOutputDigest(projection3),
37072
+ receiptDigest: result.receiptDigest
37073
+ };
37074
+ }
37075
+ function projectionOwnedOutputDigest(projection3) {
37076
+ return digestJson({
37077
+ schemaVersion: "archcontext.projection-owned-output/v1",
37078
+ files: projection3.files.map((file) => ({ path: file.path, body: file.body })).sort((left, right) => left.path.localeCompare(right.path))
37079
+ });
37080
+ }
37081
+ function projectionApplyRecoveryResultEnvelope(result) {
37082
+ const issues = projectionApplyRecoveryResultInvariantIssues(result);
37083
+ return issues.length === 0 ? okEnvelope("projection.recover", result) : errorEnvelope("projection.recover", "AC_SCHEMA_INVALID", `projection recovery result invariant failed: ${issues.join("; ")}`);
37084
+ }
37085
+ function parseProjectionApplyRecoveryIntent(raw) {
37086
+ let value;
37087
+ try {
37088
+ value = JSON.parse(raw);
37089
+ } catch {
37090
+ throw new Error("projection recovery intent is not valid JSON");
37091
+ }
37092
+ if (!value || typeof value !== "object" || Array.isArray(value))
37093
+ throw new Error("projection recovery intent must be an object");
37094
+ const input = value;
37095
+ const allowedKeys = new Set(["schemaVersion", "requestId", "profile", "receipt"]);
37096
+ const unsupported = Object.keys(input).find((key) => !allowedKeys.has(key));
37097
+ if (unsupported)
37098
+ throw new Error(`projection recovery intent contains unsupported property: ${unsupported}`);
37099
+ if (input.schemaVersion !== PROJECTION_APPLY_RECOVERY_INTENT_SCHEMA_VERSION)
37100
+ throw new Error("projection recovery intent schemaVersion mismatch");
37101
+ if (input.profile !== REPO_HARNESS_PROJECTION_PROFILE)
37102
+ throw new Error(`projection recovery intent profile must be ${REPO_HARNESS_PROJECTION_PROFILE}`);
37103
+ if (typeof input.requestId !== "string")
37104
+ throw new Error("projection recovery intent requestId is required");
37105
+ const receipt = input.receipt;
37106
+ if (!receipt || typeof receipt !== "object" || Array.isArray(receipt))
37107
+ throw new Error("projection recovery intent receipt must be an object");
37108
+ const receiptInput = receipt;
37109
+ const receiptUnsupported = Object.keys(receiptInput).find((key) => key !== "lookupKey" && key !== "applyId");
37110
+ if (receiptUnsupported)
37111
+ throw new Error(`projection recovery intent receipt contains unsupported property: ${receiptUnsupported}`);
37112
+ for (const field of ["lookupKey", "applyId"]) {
37113
+ if (typeof receiptInput[field] !== "string" || !/^sha256:[a-f0-9]{64}$/.test(receiptInput[field])) {
37114
+ throw new Error(`projection recovery intent receipt.${field} is invalid`);
37115
+ }
37116
+ }
37117
+ const intent = input;
37118
+ const issues = projectionApplyRecoveryIntentInvariantIssues(intent);
37119
+ if (issues.length > 0)
37120
+ throw new Error(`projection recovery intent invariant failed: ${issues.join("; ")}`);
37121
+ return intent;
37122
+ }
35936
37123
  function parseProjectionProtocolRequest(raw) {
35937
37124
  let value;
35938
37125
  try {
@@ -36028,10 +37215,10 @@ function projectionProtocolHumanStatus(request, projection3) {
36028
37215
  function projectionProtocolEnvelope(request, input, status, output = input) {
36029
37216
  return projectionProtocolResultEnvelope(projectionProtocolResult(request, input, status, output));
36030
37217
  }
36031
- function projectionProtocolResult(request, input, status, output = input, applyReceipt) {
37218
+ function projectionProtocolResult(request, input, status, output = input, applyReceipt, overrides) {
36032
37219
  const inputSnapshot = projectionProtocolSnapshot(request, input.plan.provenance);
36033
37220
  const outputSnapshot = projectionProtocolSnapshot(request, output.plan.provenance);
36034
- const files = projectionProtocolFiles(input);
37221
+ const files = overrides?.files ?? projectionProtocolFiles(input);
36035
37222
  const affectedNodeIds = projectionProtocolAffectedNodes(input);
36036
37223
  const requestPayloadDigest = digestJson(request);
36037
37224
  const humanActions = [];
@@ -36044,7 +37231,7 @@ function projectionProtocolResult(request, input, status, output = input, applyR
36044
37231
  requestPayloadDigest
36045
37232
  });
36046
37233
  }
36047
- const refreshSignals = [...output.plan.refreshSignals].sort((left, right) => left.signalId < right.signalId ? -1 : left.signalId > right.signalId ? 1 : 0);
37234
+ const refreshSignals = [...overrides?.refreshSignals ?? output.plan.refreshSignals].sort((left, right) => left.signalId < right.signalId ? -1 : left.signalId > right.signalId ? 1 : 0);
36048
37235
  const withoutReceipt = {
36049
37236
  schemaVersion: "archcontext.projection-result/v2",
36050
37237
  requestId: request.requestId,
@@ -36112,6 +37299,19 @@ function projectionProtocolFiles(projection3) {
36112
37299
  return [];
36113
37300
  }).sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0);
36114
37301
  }
37302
+ function projectionProtocolFilesForExpectedOutput(root, projection3) {
37303
+ return projection3.files.flatMap((file) => {
37304
+ const preimageDigest = currentProjectionFileDigest(root, file.path);
37305
+ const outputDigest = digestJson({ path: file.path, body: file.body });
37306
+ if (preimageDigest === outputDigest)
37307
+ return [];
37308
+ return preimageDigest === "missing" ? [{ path: file.path, action: "create", preimageDigest: null, outputDigest }] : [{ path: file.path, action: "update", preimageDigest, outputDigest }];
37309
+ }).sort((left, right) => left.path.localeCompare(right.path));
37310
+ }
37311
+ function currentProjectionFileDigest(root, path) {
37312
+ const absolute = resolve19(root, path);
37313
+ return existsSync17(absolute) ? digestJson({ path, body: readFileSync16(absolute, "utf8") }) : "missing";
37314
+ }
36115
37315
  function projectionProtocolAffectedNodes(projection3) {
36116
37316
  const targetNodes = new Map(projection3.plan.targets.flatMap((target) => target.scope.kind === "entity" ? [[target.targetId, target.scope.id]] : []));
36117
37317
  return [...new Set([
@@ -37157,7 +38357,7 @@ async function writeGithubDeveloperReviewState(cwd, state) {
37157
38357
  assertNoCliSecretMaterial(serialized);
37158
38358
  writeFileSync8(path, serialized, { mode: 384 });
37159
38359
  if (process.platform !== "win32")
37160
- chmodSync5(path, 384);
38360
+ chmodSync6(path, 384);
37161
38361
  return { state, path };
37162
38362
  }
37163
38363
  function readGithubDeveloperReviewState(path) {
@@ -37231,7 +38431,7 @@ function writeGithubConnection(path, record2) {
37231
38431
  assertNoCliSecretMaterial(serialized);
37232
38432
  writeFileSync8(path, serialized, { mode: 384 });
37233
38433
  if (process.platform !== "win32")
37234
- chmodSync5(path, 384);
38434
+ chmodSync6(path, 384);
37235
38435
  }
37236
38436
  function sanitizeGithubConnection(record2, connectionPath) {
37237
38437
  return {