archctx 0.2.1 → 0.2.3

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
@@ -696,13 +696,13 @@ function productVersionManifest() {
696
696
  sqliteMigrations: "0001_runtime_state..0004_changeset_journal",
697
697
  codeGraph: {
698
698
  packageName: "@colbymchenry/codegraph",
699
- requiredVersion: "1.0.1",
699
+ requiredVersion: "1.4.0",
700
700
  adapter: "codegraph-cli"
701
701
  }
702
702
  }
703
703
  };
704
704
  }
705
- var ARCHCONTEXT_PRODUCT_NAME = "archctx", ARCHCONTEXT_PRODUCT_VERSION = "0.2.1", ARCHCONTEXT_PACKAGE_MANAGER = "bun@1.3.10", ARCHCONTEXT_NODE_RANGE = ">=24 <26", LOCAL_RUNTIME_RPC_SCHEMA_VERSION = "archcontext.runtime-rpc/v1", ARCHCONTEXT_SCHEMA_SET_VERSION = "2026-06-25.al0-ledger";
705
+ var ARCHCONTEXT_PRODUCT_NAME = "archctx", ARCHCONTEXT_PRODUCT_VERSION = "0.2.3", ARCHCONTEXT_PACKAGE_MANAGER = "bun@1.3.10", ARCHCONTEXT_NODE_RANGE = ">=24 <26", LOCAL_RUNTIME_RPC_SCHEMA_VERSION = "archcontext.runtime-rpc/v1", ARCHCONTEXT_SCHEMA_SET_VERSION = "2026-06-25.al0-ledger";
706
706
  // packages/contracts/src/index.ts
707
707
  var init_src = __esm(() => {
708
708
  init_control_plane_routes();
@@ -5145,8 +5145,8 @@ var init_src7 = __esm(() => {
5145
5145
 
5146
5146
  // packages/surfaces/cli/src/main.ts
5147
5147
  import { execFileSync as execFileSync8, spawn as spawn2, spawnSync as spawnSync3 } from "child_process";
5148
- import { accessSync, chmodSync as chmodSync5, closeSync as closeSync7, constants, existsSync as existsSync14, mkdirSync as mkdirSync9, openSync as openSync7, readFileSync as readFileSync13, rmSync as rmSync10, statSync as statSync8, writeFileSync as writeFileSync8 } from "fs";
5149
- import { dirname as dirname10, join as join10, resolve as resolve17 } from "path";
5148
+ import { accessSync as accessSync2, chmodSync as chmodSync5, closeSync as closeSync7, constants, existsSync as existsSync15, mkdirSync as mkdirSync9, openSync as openSync7, readFileSync as readFileSync14, rmSync as rmSync10, statSync as statSync8, writeFileSync as writeFileSync8 } from "fs";
5149
+ import { dirname as dirname11, join as join10, resolve as resolve18 } from "path";
5150
5150
  import { fileURLToPath as fileURLToPath2 } from "url";
5151
5151
 
5152
5152
  // packages/contracts/src/index.ts
@@ -5292,8 +5292,8 @@ var UNTRUSTED_TOOL_ESCAPE_KEYS = new Set([
5292
5292
  ]);
5293
5293
 
5294
5294
  // packages/cloud/hardening/src/index.ts
5295
- import { readdirSync as readdirSync3, readFileSync as readFileSync3, statSync as statSync4 } from "node:fs";
5296
- import { resolve as resolve3 } from "node:path";
5295
+ import { readdirSync as readdirSync3, readFileSync as readFileSync4, statSync as statSync4 } from "node:fs";
5296
+ import { resolve as resolve4 } from "node:path";
5297
5297
 
5298
5298
  // packages/core/architecture-domain/src/index.ts
5299
5299
  init_src();
@@ -5654,8 +5654,9 @@ init_src2();
5654
5654
 
5655
5655
  // packages/local-runtime/codegraph-adapter/src/index.ts
5656
5656
  import { execFileSync } from "node:child_process";
5657
- import { closeSync, existsSync, openSync, readSync, realpathSync as realpathSync3, statSync as statSync3 } from "node:fs";
5658
- import { basename as basename2, delimiter, isAbsolute, join, posix } from "node:path";
5657
+ import { accessSync, closeSync, constants as fsConstants, existsSync, openSync, readFileSync as readFileSync3, readSync, realpathSync as realpathSync3, statSync as statSync3 } from "node:fs";
5658
+ import { createRequire as createRequire2 } from "node:module";
5659
+ import { basename as basename2, delimiter, dirname, isAbsolute, join, posix, resolve as resolve3 } from "node:path";
5659
5660
 
5660
5661
  // packages/core/architecture-delta/src/index.ts
5661
5662
  init_src();
@@ -6582,9 +6583,11 @@ function numberExtension(value) {
6582
6583
  // packages/local-runtime/codegraph-adapter/src/index.ts
6583
6584
  init_src();
6584
6585
  var REQUIRED_CODEGRAPH_PACKAGE = "@colbymchenry/codegraph";
6585
- var REQUIRED_CODEGRAPH_VERSION = "1.0.1";
6586
+ var REQUIRED_CODEGRAPH_VERSION = "1.4.0";
6586
6587
  var CODEGRAPH_TELEMETRY_ENV = "DO_NOT_TRACK";
6587
6588
  var CODEGRAPH_TELEMETRY_DISABLED_VALUE = "1";
6589
+ var DEFAULT_CODEGRAPH_BINARY = "codegraph";
6590
+ var requireFromAdapter = createRequire2(import.meta.url);
6588
6591
  function disableCodeGraphTelemetryByDefault(env = process.env) {
6589
6592
  env[CODEGRAPH_TELEMETRY_ENV] ??= CODEGRAPH_TELEMETRY_DISABLED_VALUE;
6590
6593
  return env[CODEGRAPH_TELEMETRY_ENV] ?? CODEGRAPH_TELEMETRY_DISABLED_VALUE;
@@ -6595,7 +6598,7 @@ class CodeGraphCliProvider {
6595
6598
  version = REQUIRED_CODEGRAPH_VERSION;
6596
6599
  capabilities = ["index", "context", "impact"];
6597
6600
  workspaceRoot;
6598
- constructor(workspaceRoot = process.cwd(), binary = "codegraph") {
6601
+ constructor(workspaceRoot = process.cwd(), binary = DEFAULT_CODEGRAPH_BINARY) {
6599
6602
  this.binary = binary;
6600
6603
  this.workspaceRoot = workspaceRoot;
6601
6604
  }
@@ -6679,27 +6682,56 @@ class CodeGraphCliProvider {
6679
6682
  }
6680
6683
  }
6681
6684
  }
6682
- function codeGraphCliInvocation(binary, cwd) {
6683
- const resolved = resolveExecutable(binary, cwd);
6685
+ function codeGraphCliInvocation(binary, cwd, pathValue = process.env.PATH ?? "") {
6686
+ const resolved = resolveExecutable(binary, cwd, pathValue);
6684
6687
  if (resolved && isNodeRuntimeScript(resolved)) {
6685
6688
  return { command: process.execPath, argsPrefix: [resolved] };
6686
6689
  }
6690
+ if (!resolved && binary === DEFAULT_CODEGRAPH_BINARY) {
6691
+ const packagedShim = resolvePackagedCodeGraphShim();
6692
+ if (packagedShim)
6693
+ return { command: process.execPath, argsPrefix: [packagedShim] };
6694
+ }
6687
6695
  return { command: binary, argsPrefix: [] };
6688
6696
  }
6689
- function resolveExecutable(binary, cwd) {
6697
+ function resolveExecutable(binary, cwd, pathValue) {
6690
6698
  if (binary.includes("/") || binary.includes("\\")) {
6691
6699
  const path = isAbsolute(binary) ? binary : join(cwd, binary);
6692
6700
  return existsSync(path) ? realpathSync3.native(path) : undefined;
6693
6701
  }
6694
- for (const dir of (process.env.PATH ?? "").split(delimiter)) {
6702
+ for (const dir of pathValue.split(delimiter)) {
6695
6703
  if (!dir)
6696
6704
  continue;
6697
6705
  const path = join(dir, binary);
6698
- if (existsSync(path))
6706
+ if (isExecutablePath(path))
6699
6707
  return realpathSync3.native(path);
6700
6708
  }
6701
6709
  return;
6702
6710
  }
6711
+ function isExecutablePath(path) {
6712
+ try {
6713
+ if (!statSync3(path).isFile())
6714
+ return false;
6715
+ if (process.platform !== "win32")
6716
+ accessSync(path, fsConstants.X_OK);
6717
+ return true;
6718
+ } catch {
6719
+ return false;
6720
+ }
6721
+ }
6722
+ function resolvePackagedCodeGraphShim() {
6723
+ try {
6724
+ const manifestPath = requireFromAdapter.resolve(`${REQUIRED_CODEGRAPH_PACKAGE}/package.json`);
6725
+ const manifest = JSON.parse(readFileSync3(manifestPath, "utf8"));
6726
+ const relativeBin = manifest.bin?.codegraph;
6727
+ if (manifest.version !== REQUIRED_CODEGRAPH_VERSION || !relativeBin)
6728
+ return;
6729
+ const shimPath = resolve3(dirname(manifestPath), relativeBin);
6730
+ return existsSync(shimPath) ? realpathSync3.native(shimPath) : undefined;
6731
+ } catch {
6732
+ return;
6733
+ }
6734
+ }
6703
6735
  function isNodeRuntimeScript(path) {
6704
6736
  if (/\.[cm]?js$/i.test(path))
6705
6737
  return true;
@@ -7094,7 +7126,7 @@ function uninstallMarker(content, host) {
7094
7126
  `).trim();
7095
7127
  }
7096
7128
  function dependencyAudit(root) {
7097
- const packageJson = JSON.parse(readFileSync3(resolve3(root, "package.json"), "utf8"));
7129
+ const packageJson = JSON.parse(readFileSync4(resolve4(root, "package.json"), "utf8"));
7098
7130
  const issues = [];
7099
7131
  if (!packageJson.engines?.node?.includes(">=24"))
7100
7132
  issues.push("node engine must include >=24");
@@ -7107,7 +7139,7 @@ function secretScan(root) {
7107
7139
  continue;
7108
7140
  if (file === "packages/cloud/hardening/src/index.ts" || file.includes("/test/"))
7109
7141
  continue;
7110
- const body = readFileSync3(resolve3(root, file), "utf8");
7142
+ const body = readFileSync4(resolve4(root, file), "utf8");
7111
7143
  if (/(sk-[A-Za-z0-9]{20,}|ghp_[A-Za-z0-9]{20,}|BEGIN PRIVATE KEY)/.test(body))
7112
7144
  findings.push(file);
7113
7145
  }
@@ -7141,11 +7173,11 @@ function listFiles(root) {
7141
7173
  walk(".");
7142
7174
  return out;
7143
7175
  function walk(relativeDir) {
7144
- for (const entry of readdirSync3(resolve3(root, relativeDir), { withFileTypes: true })) {
7176
+ for (const entry of readdirSync3(resolve4(root, relativeDir), { withFileTypes: true })) {
7145
7177
  if ([".git", "node_modules", "coverage"].includes(entry.name))
7146
7178
  continue;
7147
7179
  const child = relativeDir === "." ? entry.name : `${relativeDir}/${entry.name}`;
7148
- const full = resolve3(root, child);
7180
+ const full = resolve4(root, child);
7149
7181
  if (entry.isDirectory())
7150
7182
  walk(child);
7151
7183
  if (entry.isFile() && statSync4(full).size < 1e6)
@@ -7157,10 +7189,10 @@ function listFiles(root) {
7157
7189
  // packages/local-runtime/local-store-sqlite/src/index.ts
7158
7190
  import { execFileSync as execFileSync2 } from "node:child_process";
7159
7191
  import { createHash as createHash5, randomUUID } from "node:crypto";
7160
- import { chmodSync, closeSync as closeSync2, existsSync as existsSync2, fsyncSync, lstatSync, mkdirSync, openSync as openSync2, readFileSync as readFileSync4, realpathSync as realpathSync4, renameSync, rmSync, writeFileSync } from "node:fs";
7161
- import { createRequire as createRequire2 } from "node:module";
7192
+ import { chmodSync, closeSync as closeSync2, existsSync as existsSync2, fsyncSync, lstatSync, mkdirSync, openSync as openSync2, readFileSync as readFileSync5, realpathSync as realpathSync4, renameSync, rmSync, writeFileSync } from "node:fs";
7193
+ import { createRequire as createRequire3 } from "node:module";
7162
7194
  import { homedir } from "node:os";
7163
- import { dirname, isAbsolute as isAbsolute2, join as join2, relative as relative3, resolve as resolve4 } from "node:path";
7195
+ import { dirname as dirname2, isAbsolute as isAbsolute2, join as join2, relative as relative3, resolve as resolve5 } from "node:path";
7164
7196
 
7165
7197
  // packages/core/architecture-ledger/src/index.ts
7166
7198
  init_src();
@@ -7493,7 +7525,7 @@ function planYamlToArchitectureLedgerImport(input) {
7493
7525
  payloadVersion: "archcontext.architecture-ledger-yaml-import/v1",
7494
7526
  repository: input.repository,
7495
7527
  worktree: input.worktree,
7496
- baseDigest: sourceDigest,
7528
+ baseDigest: architectureLedgerStateDigest(emptyArchitectureLedgerState()),
7497
7529
  resultingDigest: graphDigest,
7498
7530
  headSha: input.worktree.headSha,
7499
7531
  actor: { kind: "migration", id: "archctx-ledger-yaml-import" },
@@ -7677,8 +7709,6 @@ function planYamlToArchitectureLedgerRebuild(input) {
7677
7709
  const plan = planYamlToArchitectureLedgerImport(input);
7678
7710
  const previousGraphDigest = architectureLedgerStateDigest(input.previousState);
7679
7711
  const deleteOperations = architectureLedgerDeletionOperations(input.previousState, plan.state);
7680
- if (deleteOperations.length === 0)
7681
- return plan;
7682
7712
  const payload = architectureLedgerPayload(plan.event);
7683
7713
  const event = normalizeArchitectureLedgerEvent({
7684
7714
  ...plan.event,
@@ -8244,41 +8274,41 @@ function projectArchitectureLedgerStateToYamlFiles(state) {
8244
8274
  const canonical = canonicalArchitectureLedgerState(state);
8245
8275
  const files = [
8246
8276
  ...canonical.entities.map((entity) => {
8277
+ const declared = declaredProjectionRecord(entity.metadata, ["archcontext.node/v1"], `entity ${entity.entityId}`);
8278
+ const summary = entity.summary;
8279
+ if (!summary)
8280
+ throw new Error(`architecture-ledger-projection-invalid: entity ${entity.entityId} requires summary`);
8247
8281
  const body = canonicalArchitectureYaml({
8282
+ ...declared,
8248
8283
  schemaVersion: "archcontext.node/v1",
8249
8284
  id: entity.entityId,
8250
8285
  kind: entity.kind,
8251
8286
  name: entity.canonicalName,
8252
8287
  status: entity.status,
8253
- ...entity.path ? { path: entity.path } : {},
8254
- ...entity.summary ? { summary: entity.summary } : {},
8255
- ...entity.metadata ? { metadata: entity.metadata } : {}
8288
+ summary
8256
8289
  });
8257
8290
  return projectionFile(`.archcontext/model/nodes/${pathSegment(entity.entityId)}.yaml`, body, "entity", entity.entityId);
8258
8291
  }),
8259
8292
  ...canonical.relations.map((relation) => {
8260
- const body = canonicalArchitectureYaml({
8293
+ const declared = declaredProjectionRecord(relation.metadata, ["archcontext.relation/v1", "archcontext.cross-repo-relation/v1"], `relation ${relation.relationId}`);
8294
+ const schemaVersion = requireStringField(declared, "schemaVersion", `relation ${relation.relationId}`);
8295
+ const body = schemaVersion === "archcontext.cross-repo-relation/v1" ? canonicalArchitectureYaml({ ...declared, id: relation.relationId, kind: relation.kind }) : canonicalArchitectureYaml({
8296
+ ...declared,
8261
8297
  schemaVersion: "archcontext.relation/v1",
8262
8298
  id: relation.relationId,
8263
8299
  kind: relation.kind,
8264
8300
  source: relation.sourceEntityId,
8265
- target: relation.targetEntityId,
8266
- status: relation.status,
8267
- ...relation.summary ? { summary: relation.summary } : {},
8268
- ...relation.metadata ? { metadata: relation.metadata } : {}
8301
+ target: relation.targetEntityId
8269
8302
  });
8270
8303
  return projectionFile(`.archcontext/model/relations/${pathSegment(relation.relationId)}.yaml`, body, "relation", relation.relationId);
8271
8304
  }),
8272
8305
  ...canonical.constraints.map((constraint) => {
8306
+ const declared = declaredProjectionRecord(constraint.metadata, ["archcontext.constraint/v1"], `constraint ${constraint.constraintId}`);
8273
8307
  const body = canonicalArchitectureYaml({
8308
+ ...declared,
8274
8309
  schemaVersion: "archcontext.constraint/v1",
8275
8310
  id: constraint.constraintId,
8276
- kind: constraint.kind,
8277
- subject: constraint.subjectId,
8278
- status: constraint.status,
8279
- ...constraint.severity ? { severity: constraint.severity } : {},
8280
- ...constraint.summary ? { summary: constraint.summary } : {},
8281
- ...constraint.metadata ? { metadata: constraint.metadata } : {}
8311
+ ...constraint.severity ? { severity: constraint.severity } : {}
8282
8312
  });
8283
8313
  return projectionFile(`.archcontext/model/constraints/${pathSegment(constraint.constraintId)}.yaml`, body, "constraint", constraint.constraintId);
8284
8314
  })
@@ -8453,12 +8483,11 @@ function yamlRecordToLedgerOperation(value, path, schemaVersion) {
8453
8483
  const entityId = requireStringField(value, "id", path);
8454
8484
  const entity = {
8455
8485
  entityId,
8456
- kind: stringField(value, "kind") ?? "component",
8457
- canonicalName: stringField(value, "name") ?? stringField(value, "canonicalName") ?? entityId,
8458
- status: activeStatus(value.status),
8459
- ...stringField(value, "path") ? { path: stringField(value, "path") } : {},
8460
- ...stringField(value, "summary") ? { summary: stringField(value, "summary") } : {},
8461
- ...metadataField(value, ["schemaVersion", "id", "kind", "name", "canonicalName", "status", "path", "summary"])
8486
+ kind: requireStringField(value, "kind", path),
8487
+ canonicalName: requireStringField(value, "name", path),
8488
+ status: declaredStatus(value.status, path),
8489
+ summary: requireStringField(value, "summary", path),
8490
+ metadata: { declared: value }
8462
8491
  };
8463
8492
  return { operation: { op: "upsert_entity", entity }, targetKind: "entity", targetId: entityId };
8464
8493
  }
@@ -8466,12 +8495,12 @@ function yamlRecordToLedgerOperation(value, path, schemaVersion) {
8466
8495
  const relationId = requireStringField(value, "id", path);
8467
8496
  const relation = {
8468
8497
  relationId,
8469
- kind: stringField(value, "kind") ?? "depends_on",
8498
+ kind: requireStringField(value, "kind", path),
8470
8499
  sourceEntityId: requireStringField(value, "source", path),
8471
8500
  targetEntityId: requireStringField(value, "target", path),
8472
- status: activeStatus(value.status),
8473
- ...stringField(value, "summary") ?? stringField(value, "intent") ? { summary: stringField(value, "summary") ?? stringField(value, "intent") } : {},
8474
- ...metadataField(value, ["schemaVersion", "id", "kind", "source", "target", "status", "summary", "intent"])
8501
+ status: "active",
8502
+ summary: requireStringField(value, "intent", path),
8503
+ metadata: { declared: value }
8475
8504
  };
8476
8505
  return { operation: { op: "upsert_relation", relation }, targetKind: "relation", targetId: relationId };
8477
8506
  }
@@ -8481,25 +8510,28 @@ function yamlRecordToLedgerOperation(value, path, schemaVersion) {
8481
8510
  const target = repoScopedTarget(value.target, path, "target");
8482
8511
  const relation = {
8483
8512
  relationId,
8484
- kind: stringField(value, "kind") ?? "depends_on",
8513
+ kind: requireStringField(value, "kind", path),
8485
8514
  sourceEntityId: source,
8486
8515
  targetEntityId: target,
8487
- status: activeStatus(value.status),
8488
- ...stringField(value, "intent") ? { summary: stringField(value, "intent") } : {},
8489
- ...metadataField(value, ["schemaVersion", "id", "kind", "source", "target", "status", "intent"])
8516
+ status: "active",
8517
+ summary: requireStringField(value, "intent", path),
8518
+ metadata: { declared: value }
8490
8519
  };
8491
8520
  return { operation: { op: "upsert_relation", relation }, targetKind: "relation", targetId: relationId };
8492
8521
  }
8493
8522
  if (schemaVersion === "archcontext.constraint/v1") {
8494
8523
  const constraintId = requireStringField(value, "id", path);
8524
+ const scope = requiredRecordField(value, "scope", path);
8525
+ const rule = requiredRecordField(value, "rule", path);
8526
+ const subjectId = firstStringArrayValue(scope.nodes) ?? firstStringArrayValue(scope.relations) ?? "repository";
8495
8527
  const constraint = {
8496
8528
  constraintId,
8497
- kind: stringField(value, "kind") ?? "constraint",
8498
- subjectId: stringField(value, "subject") ?? stringField(value, "subjectId") ?? "repository",
8499
- status: activeStatus(value.status),
8500
- ...severityField(value.severity) ? { severity: severityField(value.severity) } : {},
8501
- ...stringField(value, "summary") ? { summary: stringField(value, "summary") } : {},
8502
- ...metadataField(value, ["schemaVersion", "id", "kind", "subject", "subjectId", "status", "severity", "summary"])
8529
+ kind: requireStringField(rule, "type", path),
8530
+ subjectId,
8531
+ status: "active",
8532
+ severity: requiredSeverity(value.severity, path),
8533
+ summary: requireStringField(value, "rationale", path),
8534
+ metadata: { declared: value }
8503
8535
  };
8504
8536
  return { operation: { op: "upsert_constraint", constraint }, targetKind: "constraint", targetId: constraintId };
8505
8537
  }
@@ -8636,19 +8668,30 @@ function isEvidenceOnlySchema(schemaVersion) {
8636
8668
  function isGeneratedProjectionFile(file) {
8637
8669
  return file.schemaVersion === "archcontext.generated/v1" || file.path === ".archcontext/generated" || file.path.startsWith(".archcontext/generated/") || file.body.includes("Generated by ArchContext");
8638
8670
  }
8639
- function metadataField(value, omitted) {
8640
- const metadata = {};
8641
- const explicit = value.metadata;
8642
- if (isRecord(explicit)) {
8643
- for (const key of Object.keys(explicit).sort())
8644
- metadata[key] = explicit[key];
8645
- }
8646
- for (const key of Object.keys(value).sort()) {
8647
- if (omitted.includes(key) || key === "metadata")
8648
- continue;
8649
- metadata[key] = value[key];
8671
+ function declaredProjectionRecord(metadata, allowedSchemaVersions, label) {
8672
+ const declared = metadata?.declared;
8673
+ if (!isRecord(declared))
8674
+ throw new Error(`architecture-ledger-projection-invalid: ${label} has no declared YAML record`);
8675
+ const schemaVersion = requireStringField(declared, "schemaVersion", label);
8676
+ if (!allowedSchemaVersions.includes(schemaVersion)) {
8677
+ throw new Error(`architecture-ledger-projection-invalid: ${label} has unsupported declared schema ${schemaVersion}`);
8650
8678
  }
8651
- return Object.keys(metadata).length === 0 ? {} : { metadata };
8679
+ return declared;
8680
+ }
8681
+ function requiredRecordField(value, key, path) {
8682
+ const field = value[key];
8683
+ if (!isRecord(field))
8684
+ throw new Error(`${path}: ${key} is required`);
8685
+ return field;
8686
+ }
8687
+ function firstStringArrayValue(value) {
8688
+ return Array.isArray(value) ? value.find((item) => typeof item === "string" && item.length > 0) : undefined;
8689
+ }
8690
+ function requiredSeverity(value, path) {
8691
+ const severity = severityField(value);
8692
+ if (!severity)
8693
+ throw new Error(`${path}: severity is required`);
8694
+ return severity;
8652
8695
  }
8653
8696
  function declaredYamlSubject(value, path) {
8654
8697
  const id = stringField(value, "id");
@@ -8681,8 +8724,10 @@ function requireStringField(value, key, path) {
8681
8724
  function stringField(value, key) {
8682
8725
  return typeof value[key] === "string" ? value[key] : undefined;
8683
8726
  }
8684
- function activeStatus(value) {
8685
- return value === "deprecated" || value === "removed" ? value : "active";
8727
+ function declaredStatus(value, path) {
8728
+ if (value === "active" || value === "planned" || value === "deprecated" || value === "removed")
8729
+ return value;
8730
+ throw new Error(`${path}: status is required`);
8686
8731
  }
8687
8732
  function severityField(value) {
8688
8733
  return value === "notice" || value === "warning" || value === "error" || value === "critical" ? value : undefined;
@@ -8768,10 +8813,85 @@ function validateArchitectureLedgerEvent(event) {
8768
8813
  if (event.worktree.workspaceId.length === 0 || event.worktree.storageWorkspaceId.length === 0 || event.worktree.headSha.length === 0) {
8769
8814
  throw new Error(`architecture-ledger-invalid-event: worktree identity required for ${event.eventId}`);
8770
8815
  }
8816
+ assertArchitectureLedgerPersistenceSafe(event.payload, `event.payload for ${event.eventId}`);
8817
+ if (event.extensions)
8818
+ assertArchitectureLedgerPersistenceSafe(event.extensions, `event.extensions for ${event.eventId}`);
8819
+ assertArchitectureLedgerPersistenceSafe(event.provenance, `event.provenance for ${event.eventId}`);
8771
8820
  const payload = architectureLedgerPayload(event);
8772
8821
  for (const operation of payload.operations ?? [])
8773
8822
  validateArchitectureLedgerOperation(operation, event.eventId);
8774
8823
  }
8824
+ var ARCHITECTURE_LEDGER_MAX_PERSISTED_JSON_BYTES = 262144;
8825
+ var ARCHITECTURE_LEDGER_MAX_PERSISTED_STRING_BYTES = 8192;
8826
+ var ARCHITECTURE_LEDGER_MAX_PERSISTED_DEPTH = 32;
8827
+ var ARCHITECTURE_LEDGER_FORBIDDEN_RAW_KEYS = new Set([
8828
+ "rawsource",
8829
+ "sourcebody",
8830
+ "sourcecode",
8831
+ "rawdiff",
8832
+ "diffbody",
8833
+ "rawpatch",
8834
+ "patchbody",
8835
+ "prompt",
8836
+ "promptbody",
8837
+ "completion",
8838
+ "completionbody",
8839
+ "codegraphoutput",
8840
+ "fullcodegraphoutput",
8841
+ "webhookbody",
8842
+ "rawwebhook",
8843
+ "secret",
8844
+ "secrets",
8845
+ "credential",
8846
+ "credentials",
8847
+ "privatekey",
8848
+ "accesstoken",
8849
+ "refreshtoken"
8850
+ ]);
8851
+ var ARCHITECTURE_LEDGER_SAFE_SENSITIVE_KEY_SUFFIX = /(?:digest|id|ids|count|counts|ref|refs|path|paths|persisted)$/;
8852
+ var ARCHITECTURE_LEDGER_FORBIDDEN_STRING_PATTERNS = [
8853
+ /diff --git /,
8854
+ /-----BEGIN [A-Z ]*PRIVATE KEY-----/,
8855
+ /\bgh[pousr]_[A-Za-z0-9]{20,}\b/,
8856
+ /\bsk-[A-Za-z0-9_-]{20,}\b/,
8857
+ /\bAKIA[A-Z0-9]{16}\b/,
8858
+ /\bBearer\s+[A-Za-z0-9._~+/=-]{12,}\b/i,
8859
+ /\b(?:api[_-]?key|secret|token|password|private[_-]?key)\s*[:=]\s*["']?[^\s"',;]{8,}/i
8860
+ ];
8861
+ function assertArchitectureLedgerPersistenceSafe(value, label = "architecture-ledger-value") {
8862
+ const encoded = JSON.stringify(value);
8863
+ if (Buffer2.byteLength(encoded, "utf8") > ARCHITECTURE_LEDGER_MAX_PERSISTED_JSON_BYTES) {
8864
+ throw new Error(`architecture-ledger-privacy-denied: persisted JSON exceeds size limit at ${label}`);
8865
+ }
8866
+ visit(value, label, 0);
8867
+ function visit(current, path, depth) {
8868
+ if (depth > ARCHITECTURE_LEDGER_MAX_PERSISTED_DEPTH) {
8869
+ throw new Error(`architecture-ledger-privacy-denied: persisted JSON exceeds depth limit at ${path}`);
8870
+ }
8871
+ if (typeof current === "string") {
8872
+ if (Buffer2.byteLength(current, "utf8") > ARCHITECTURE_LEDGER_MAX_PERSISTED_STRING_BYTES) {
8873
+ throw new Error(`architecture-ledger-privacy-denied: persisted string exceeds size limit at ${path}`);
8874
+ }
8875
+ if (ARCHITECTURE_LEDGER_FORBIDDEN_STRING_PATTERNS.some((pattern) => pattern.test(current))) {
8876
+ throw new Error(`architecture-ledger-privacy-denied: forbidden raw or secret-shaped content at ${path}`);
8877
+ }
8878
+ return;
8879
+ }
8880
+ if (current === null || typeof current !== "object")
8881
+ return;
8882
+ if (Array.isArray(current)) {
8883
+ current.forEach((item, index) => visit(item, `${path}[${index}]`, depth + 1));
8884
+ return;
8885
+ }
8886
+ for (const [key, child] of Object.entries(current)) {
8887
+ const normalizedKey = key.replace(/[-_]/g, "").toLowerCase();
8888
+ if (ARCHITECTURE_LEDGER_FORBIDDEN_RAW_KEYS.has(normalizedKey) && !ARCHITECTURE_LEDGER_SAFE_SENSITIVE_KEY_SUFFIX.test(normalizedKey)) {
8889
+ throw new Error(`architecture-ledger-privacy-denied: forbidden persisted field at ${path}.${key}`);
8890
+ }
8891
+ visit(child, `${path}.${key}`, depth + 1);
8892
+ }
8893
+ }
8894
+ }
8775
8895
  function validateArchitectureLedgerOperation(operation, eventId) {
8776
8896
  if (!operation || typeof operation !== "object" || !("op" in operation)) {
8777
8897
  throw new Error(`architecture-ledger-invalid-operation: ${eventId}`);
@@ -8894,7 +9014,7 @@ function requireNonEmpty(value, label, eventId) {
8894
9014
  }
8895
9015
  }
8896
9016
  function requireActiveStatus(value, eventId) {
8897
- if (!["active", "deprecated", "removed"].includes(value)) {
9017
+ if (!["active", "planned", "deprecated", "removed"].includes(value)) {
8898
9018
  throw new Error(`architecture-ledger-invalid-operation: invalid status for ${eventId}`);
8899
9019
  }
8900
9020
  }
@@ -8906,7 +9026,7 @@ function assertRecord(value, label) {
8906
9026
 
8907
9027
  // packages/local-runtime/local-store-sqlite/src/index.ts
8908
9028
  init_src();
8909
- var runtimeRequire = createRequire2(import.meta.url);
9029
+ var runtimeRequire = createRequire3(import.meta.url);
8910
9030
  var SQLITE_SIDECAR_SUFFIXES = ["", "-wal", "-shm"];
8911
9031
  var LEGACY_MIGRATION_MARKER_FILE = "runtime.sqlite.migration.json";
8912
9032
  var LEGACY_MIGRATION_LOCK_FILE = "runtime.sqlite.migration.lock";
@@ -8943,6 +9063,7 @@ var REQUIRED_LOCAL_STORE_TABLES = [
8943
9063
  ];
8944
9064
  var SQLITE_PRAGMAS = [
8945
9065
  "PRAGMA journal_mode = WAL",
9066
+ "PRAGMA synchronous = FULL",
8946
9067
  "PRAGMA foreign_keys = ON",
8947
9068
  "PRAGMA busy_timeout = 5000"
8948
9069
  ];
@@ -9464,6 +9585,13 @@ var LOCAL_SQLITE_MIGRATIONS = [
9464
9585
  )`,
9465
9586
  "CREATE INDEX IF NOT EXISTS idx_audit_runs_status ON audit_runs(storage_repository_id, storage_workspace_id, status)"
9466
9587
  ]
9588
+ },
9589
+ {
9590
+ id: "0011_changeset_cleanup_cursor",
9591
+ statements: [
9592
+ "ALTER TABLE changeset_journal ADD COLUMN cleanup_completed_at TEXT",
9593
+ "CREATE INDEX IF NOT EXISTS idx_changeset_journal_cleanup_pending ON changeset_journal(status, cleanup_completed_at, updated_at)"
9594
+ ]
9467
9595
  }
9468
9596
  ];
9469
9597
  var ARCHCONTEXT_STATE_DIR_ENV = "ARCHCONTEXT_STATE_DIR";
@@ -9471,7 +9599,7 @@ var ARCHCONTEXT_LOCAL_STORE_PATH_ENV = "ARCHCONTEXT_LOCAL_STORE_PATH";
9471
9599
  function defaultArchContextStateRoot(env = process.env, platform = process.platform, home = homedir()) {
9472
9600
  const override = env[ARCHCONTEXT_STATE_DIR_ENV];
9473
9601
  if (override)
9474
- return { path: resolve4(override), source: "environment" };
9602
+ return { path: resolve5(override), source: "environment" };
9475
9603
  if (platform === "darwin")
9476
9604
  return { path: join2(home, "Library", "Application Support", "ArchContext"), source: "os-user-data" };
9477
9605
  if (platform === "win32")
@@ -9489,7 +9617,7 @@ function runtimeStatePaths(root = process.cwd(), env = process.env) {
9489
9617
  const stateRoot = defaultArchContextStateRoot(env);
9490
9618
  const repositoryStateDir = join2(stateRoot.path, "repositories", storageRepositoryId);
9491
9619
  const workspaceStateDir = join2(repositoryStateDir, "worktrees", storageWorkspaceId);
9492
- const legacyControlDir = resolve4(canonicalRepositoryRoot3, ".archcontext", ".local");
9620
+ const legacyControlDir = resolve5(canonicalRepositoryRoot3, ".archcontext", ".local");
9493
9621
  return {
9494
9622
  schemaVersion: "archcontext.runtime-state-paths/v1",
9495
9623
  stateRoot: stateRoot.path,
@@ -9557,7 +9685,7 @@ function migrateLegacyLocalStoreIfNeeded(root = process.cwd(), env = process.env
9557
9685
  status: "explicit-local-store-override"
9558
9686
  });
9559
9687
  }
9560
- ensurePrivateDir(dirname(paths.localStorePath));
9688
+ ensurePrivateDir(dirname2(paths.localStorePath));
9561
9689
  const legacyExists = existsSync2(paths.legacyLocalStorePath);
9562
9690
  const integrityCheck = {};
9563
9691
  const quarantinedFiles = [];
@@ -9655,7 +9783,7 @@ function readAppliedLocalSqliteMigrations(db) {
9655
9783
  }
9656
9784
  function openSqliteDatabaseSync(databasePath) {
9657
9785
  if (databasePath !== ":memory:")
9658
- ensurePrivateDir(dirname(databasePath));
9786
+ ensurePrivateDir(dirname2(databasePath));
9659
9787
  try {
9660
9788
  const nodeSqlite = runtimeRequire("node:sqlite");
9661
9789
  const db2 = new nodeSqlite.DatabaseSync(databasePath);
@@ -9832,7 +9960,7 @@ function publishStagedLocalStore(stagingPath, targetPath) {
9832
9960
  }
9833
9961
  renameSync(stagingPath, targetPath);
9834
9962
  makePrivateFile(targetPath);
9835
- fsyncDirectory(dirname(targetPath));
9963
+ fsyncDirectory(dirname2(targetPath));
9836
9964
  }
9837
9965
  function writeLegacyMigrationMarker(paths, integrityCheck, quarantinedFiles) {
9838
9966
  const markerPath = legacyMigrationMarkerPath(paths);
@@ -9848,7 +9976,7 @@ function writeLegacyMigrationMarker(paths, integrityCheck, quarantinedFiles) {
9848
9976
  }
9849
9977
  function acquireLegacyMigrationLock(paths) {
9850
9978
  const lockPath = legacyMigrationLockPath(paths);
9851
- ensurePrivateDir(dirname(lockPath));
9979
+ ensurePrivateDir(dirname2(lockPath));
9852
9980
  try {
9853
9981
  const fd = openSync2(lockPath, "wx", 384);
9854
9982
  writeFileSync(fd, JSON.stringify({
@@ -9877,7 +10005,7 @@ function releaseLegacyMigrationLock(lock) {
9877
10005
  }
9878
10006
  function isStaleMigrationLock(lockPath) {
9879
10007
  try {
9880
- const parsed = JSON.parse(readFileSync4(lockPath, "utf8"));
10008
+ const parsed = JSON.parse(readFileSync5(lockPath, "utf8"));
9881
10009
  if (typeof parsed.pid !== "number" || parsed.pid <= 0)
9882
10010
  return true;
9883
10011
  return !isProcessAlive(parsed.pid);
@@ -9912,7 +10040,7 @@ function quarantineExistingLocalStore(paths) {
9912
10040
  quarantinedFiles.push(target);
9913
10041
  }
9914
10042
  fsyncDirectory(quarantineDir);
9915
- fsyncDirectory(dirname(paths.localStorePath));
10043
+ fsyncDirectory(dirname2(paths.localStorePath));
9916
10044
  return quarantinedFiles;
9917
10045
  }
9918
10046
  function legacyMigrationMarkerPath(paths) {
@@ -9922,7 +10050,7 @@ function legacyMigrationLockPath(paths) {
9922
10050
  return join2(paths.workspaceStateDir, LEGACY_MIGRATION_LOCK_FILE);
9923
10051
  }
9924
10052
  function writePrivateJson(path, value) {
9925
- ensurePrivateDir(dirname(path));
10053
+ ensurePrivateDir(dirname2(path));
9926
10054
  const fd = openSync2(path, "w", 384);
9927
10055
  try {
9928
10056
  writeFileSync(fd, JSON.stringify(value, null, 2), "utf8");
@@ -9931,7 +10059,7 @@ function writePrivateJson(path, value) {
9931
10059
  closeSync2(fd);
9932
10060
  }
9933
10061
  makePrivateFile(path);
9934
- fsyncDirectory(dirname(path));
10062
+ fsyncDirectory(dirname2(path));
9935
10063
  }
9936
10064
  function ensurePrivateDir(path) {
9937
10065
  mkdirSync(path, { recursive: true, mode: 448 });
@@ -9961,10 +10089,10 @@ function readGitPath(root, args) {
9961
10089
  }
9962
10090
  }
9963
10091
  function resolveMaybeRelative(base, path) {
9964
- return isAbsolute2(path) ? resolve4(path) : resolve4(base, path);
10092
+ return isAbsolute2(path) ? resolve5(path) : resolve5(base, path);
9965
10093
  }
9966
10094
  function canonicalPath(path) {
9967
- const resolved = resolve4(path);
10095
+ const resolved = resolve5(path);
9968
10096
  try {
9969
10097
  return realpathSync4.native(resolved);
9970
10098
  } catch {
@@ -9972,8 +10100,8 @@ function canonicalPath(path) {
9972
10100
  }
9973
10101
  }
9974
10102
  function isPathInsideOrSame(path, parent) {
9975
- const child = resolve4(path);
9976
- const base = resolve4(parent);
10103
+ const child = resolve5(path);
10104
+ const base = resolve5(parent);
9977
10105
  const fromBase = relative3(base, child);
9978
10106
  return fromBase === "" || !!fromBase && !fromBase.startsWith("..") && !isAbsolute2(fromBase);
9979
10107
  }
@@ -10004,21 +10132,21 @@ function isIgnorableDirectoryFsyncError(error) {
10004
10132
  // packages/local-runtime/git-adapter/src/index.ts
10005
10133
  import { execFileSync as execFileSync3, spawnSync } from "node:child_process";
10006
10134
  import { existsSync as existsSync3, mkdirSync as mkdirSync2, mkdtempSync, rmSync as rmSync2 } from "node:fs";
10007
- import { dirname as dirname2, join as join3, resolve as resolve5 } from "node:path";
10135
+ import { dirname as dirname3, join as join3, resolve as resolve6 } from "node:path";
10008
10136
  init_src();
10009
10137
  function findRepositoryRoot(start) {
10010
10138
  try {
10011
- return resolve5(execFileSync3("git", ["rev-parse", "--show-toplevel"], {
10139
+ return resolve6(execFileSync3("git", ["rev-parse", "--show-toplevel"], {
10012
10140
  cwd: start,
10013
10141
  encoding: "utf8",
10014
10142
  stdio: ["ignore", "pipe", "ignore"]
10015
10143
  }).trim());
10016
10144
  } catch {
10017
- let cursor = resolve5(start);
10145
+ let cursor = resolve6(start);
10018
10146
  while (true) {
10019
- if (existsSync3(resolve5(cursor, ".git")))
10147
+ if (existsSync3(resolve6(cursor, ".git")))
10020
10148
  return cursor;
10021
- const parent = dirname2(cursor);
10149
+ const parent = dirname3(cursor);
10022
10150
  if (parent === cursor)
10023
10151
  break;
10024
10152
  cursor = parent;
@@ -10041,18 +10169,19 @@ function readHeadSha(root) {
10041
10169
  // packages/local-runtime/runtime-daemon/src/index.ts
10042
10170
  import { randomBytes } from "node:crypto";
10043
10171
  import { execFileSync as execFileSync6 } from "node:child_process";
10044
- import { chmodSync as chmodSync3, closeSync as closeSync5, existsSync as existsSync12, mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync4, openSync as openSync5, readdirSync as readdirSync8, readFileSync as readFileSync11, rmSync as rmSync8, statSync as statSync6, writeFileSync as writeFileSync6 } from "node:fs";
10172
+ import { chmodSync as chmodSync3, closeSync as closeSync5, existsSync as existsSync13, mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync4, openSync as openSync5, readdirSync as readdirSync8, readFileSync as readFileSync12, rmSync as rmSync8, statSync as statSync6, writeFileSync as writeFileSync6 } from "node:fs";
10045
10173
  import { createServer } from "node:http";
10046
10174
  import { tmpdir as tmpdir3 } from "node:os";
10047
- import { dirname as dirname8, join as join8, resolve as resolve15 } from "node:path";
10175
+ import { dirname as dirname9, join as join8, resolve as resolve16 } from "node:path";
10048
10176
 
10049
10177
  // packages/core/changeset-engine/src/index.ts
10050
10178
  init_src();
10051
- import { closeSync as closeSync3, existsSync as existsSync4, fsyncSync as fsyncSync2, lstatSync as lstatSync2, mkdirSync as mkdirSync3, openSync as openSync3, readFileSync as readFileSync5, renameSync as renameSync2, rmSync as rmSync3, writeFileSync as writeFileSync2 } from "node:fs";
10052
- import { dirname as dirname3, resolve as resolve7 } from "node:path";
10179
+ import { closeSync as closeSync3, existsSync as existsSync5, fsyncSync as fsyncSync2, lstatSync as lstatSync2, mkdirSync as mkdirSync3, openSync as openSync3, readFileSync as readFileSync6, renameSync as renameSync2, rmSync as rmSync3, writeFileSync as writeFileSync2 } from "node:fs";
10180
+ import { dirname as dirname4, resolve as resolve8 } from "node:path";
10053
10181
 
10054
10182
  // packages/core/policy-engine/src/index.ts
10055
- import { isAbsolute as isAbsolute3, relative as relative4, resolve as resolve6, sep as sep3 } from "node:path";
10183
+ import { existsSync as existsSync4, realpathSync as realpathSync5 } from "node:fs";
10184
+ import { isAbsolute as isAbsolute3, relative as relative4, resolve as resolve7, sep as sep3 } from "node:path";
10056
10185
  init_src();
10057
10186
  var INVALID_COMPAT_REASONS = new Set(["just in case", "safer to keep", "many internal callers", "large diff", "old code already exists"]);
10058
10187
  var PLACEHOLDER_DIGEST = `sha256:${"0".repeat(64)}`;
@@ -10062,6 +10191,7 @@ var ALLOWLIST = [
10062
10191
  ".archcontext/practices/",
10063
10192
  ".archcontext/waivers/",
10064
10193
  ".archcontext/decisions/",
10194
+ ".archcontext/backups/",
10065
10195
  ".archcontext/generated/",
10066
10196
  "docs/architecture/"
10067
10197
  ];
@@ -10092,12 +10222,27 @@ function assertAllowedArchContextPath(root, relativePath) {
10092
10222
  if (!ALLOWLIST.some((prefix) => normalized.startsWith(prefix))) {
10093
10223
  throw new Error(`Path is outside ArchContext write allowlist: ${relativePath}`);
10094
10224
  }
10095
- const absoluteRoot = resolve6(root);
10096
- const absoluteTarget = resolve6(root, relativePath);
10225
+ const absoluteRoot = resolve7(root);
10226
+ const absoluteTarget = resolve7(root, relativePath);
10097
10227
  const targetFromRoot = relative4(absoluteRoot, absoluteTarget);
10098
10228
  if (targetFromRoot === "" || targetFromRoot === ".." || targetFromRoot.startsWith(`..${sep3}`) || isAbsolute3(targetFromRoot)) {
10099
10229
  throw new Error(`Path escapes repository: ${relativePath}`);
10100
10230
  }
10231
+ if (!existsSync4(absoluteRoot))
10232
+ return;
10233
+ const canonicalRoot = realpathSync5.native(absoluteRoot);
10234
+ let existingAncestor = absoluteTarget;
10235
+ while (!existsSync4(existingAncestor)) {
10236
+ const parent = resolve7(existingAncestor, "..");
10237
+ if (parent === existingAncestor)
10238
+ throw new Error(`Path has no existing repository ancestor: ${relativePath}`);
10239
+ existingAncestor = parent;
10240
+ }
10241
+ const canonicalAncestor = realpathSync5.native(existingAncestor);
10242
+ const ancestorFromRoot = relative4(canonicalRoot, canonicalAncestor);
10243
+ if (ancestorFromRoot === ".." || ancestorFromRoot.startsWith(`..${sep3}`) || isAbsolute3(ancestorFromRoot)) {
10244
+ throw new Error(`Path escapes repository through symlink: ${relativePath}`);
10245
+ }
10101
10246
  }
10102
10247
  function evaluateChangeSetPaths(root, paths) {
10103
10248
  const findings = [];
@@ -10157,6 +10302,7 @@ class ChangeSetEngine {
10157
10302
  if (!approved)
10158
10303
  throw new Error("ChangeSet must be approved before apply");
10159
10304
  const deps = this.requireDeps();
10305
+ await this.validateModel(root, draft, deps, "before");
10160
10306
  const backups = [];
10161
10307
  const journalId = await deps.journal?.beginChangeSet(root, draft);
10162
10308
  let journalCommitted = false;
@@ -10171,10 +10317,7 @@ class ChangeSetEngine {
10171
10317
  if (options.faultAfterOperations && applied >= options.faultAfterOperations)
10172
10318
  throw new Error("fault-injection");
10173
10319
  }
10174
- } else {
10175
- this.rebuildGeneratedProjection(root, deps);
10176
- applied += 1;
10177
- }
10320
+ } else {}
10178
10321
  if (options.faultAfterOperations && applied >= options.faultAfterOperations)
10179
10322
  throw new Error("fault-injection");
10180
10323
  continue;
@@ -10186,15 +10329,24 @@ class ChangeSetEngine {
10186
10329
  if (options.faultAfterOperations && applied >= options.faultAfterOperations)
10187
10330
  throw new Error("fault-injection");
10188
10331
  }
10189
- this.rebuildGeneratedProjection(root, deps);
10190
- await this.validateModel(root, draft, deps);
10191
- await options.afterModelValidatedBeforeCommit?.({ root, draft, journalId });
10192
- if (journalId) {
10332
+ for (const projection of deps.projection.planGeneratedProjection(root)) {
10333
+ await this.applyFileOperation(root, projection.path, projection.expectedHash, projection.body, projection.operation, backups, journalId, applied + 1);
10334
+ applied += 1;
10335
+ if (options.faultAfterOperations && applied >= options.faultAfterOperations)
10336
+ throw new Error("fault-injection");
10337
+ }
10338
+ await this.validateModel(root, draft, deps, "after");
10339
+ const commit = await options.afterModelValidatedBeforeCommit?.({ root, draft, journalId });
10340
+ if (commit?.journalCommitted)
10341
+ journalCommitted = true;
10342
+ if (journalId && !journalCommitted) {
10193
10343
  await deps.journal?.commitChangeSet(journalId);
10194
10344
  journalCommitted = true;
10195
10345
  }
10196
10346
  try {
10197
10347
  cleanupBackups(backups);
10348
+ if (journalId)
10349
+ await deps.journal?.completeChangeSetCleanup(journalId);
10198
10350
  } catch {}
10199
10351
  const appliedDraft = { ...draft, status: "applied" };
10200
10352
  this.states.set(draft.id, appliedDraft);
@@ -10210,11 +10362,11 @@ class ChangeSetEngine {
10210
10362
  throw error;
10211
10363
  }
10212
10364
  }
10213
- rebuildGeneratedProjection(root, deps) {
10214
- deps.projection.rebuildGeneratedProjection(root);
10215
- }
10216
- async validateModel(root, draft, deps) {
10217
- await deps.modelStore.validateModel({ root, repositoryId: draft.reason.taskSessionId, headSha: draft.base.headSha });
10365
+ async validateModel(root, draft, deps, phase) {
10366
+ const result = await deps.modelStore.validateModel({ root, repositoryId: draft.reason.taskSessionId, headSha: draft.base.headSha });
10367
+ if (!result.valid) {
10368
+ throw new Error(`ChangeSet model validation failed ${phase} apply: ${result.errors.join("; ") || "unknown validation error"}`);
10369
+ }
10218
10370
  }
10219
10371
  requireDeps() {
10220
10372
  if (!this.deps)
@@ -10224,20 +10376,17 @@ class ChangeSetEngine {
10224
10376
  async applyFileOperation(root, path, expectedHash, body, operation, backups, journalId, sequence) {
10225
10377
  assertSafeTarget(root, path);
10226
10378
  const deps = this.requireDeps();
10227
- const absolute = resolve7(root, path);
10228
- const existed = existsSync4(absolute);
10379
+ const absolute = resolve8(root, path);
10380
+ const existed = existsSync5(absolute);
10229
10381
  const backupPath = `${absolute}.archctx-backup`;
10230
10382
  const tempPath = operation === "delete_entity" ? undefined : `${absolute}.archctx-tmp-${process.pid}-${sequence}`;
10231
- if (existsSync4(backupPath))
10383
+ if (existsSync5(backupPath))
10232
10384
  throw new Error(`Backup path already exists: ${path}`);
10233
- if (existed) {
10385
+ if (existed)
10234
10386
  assertExpectedHash(absolute, expectedHash);
10235
- renameSync2(absolute, backupPath);
10236
- fsyncDirectory2(dirname3(absolute));
10237
- } else if (expectedHash !== "missing") {
10387
+ else if (expectedHash !== "missing")
10238
10388
  throw new Error(`Expected missing file hash for new path: ${path}`);
10239
- }
10240
- backups.push({ path: absolute, backupPath, tempPath, existed });
10389
+ const backup = { path: absolute, backupPath, tempPath, existed };
10241
10390
  if (journalId) {
10242
10391
  await deps.journal?.recordChangeSetFile(journalId, {
10243
10392
  path,
@@ -10247,6 +10396,11 @@ class ChangeSetEngine {
10247
10396
  operation
10248
10397
  });
10249
10398
  }
10399
+ backups.push(backup);
10400
+ if (existed) {
10401
+ renameSync2(absolute, backupPath);
10402
+ fsyncDirectory2(dirname4(absolute));
10403
+ }
10250
10404
  if (operation === "delete_entity") {
10251
10405
  rmSync3(absolute, { force: true });
10252
10406
  } else {
@@ -10256,22 +10410,22 @@ class ChangeSetEngine {
10256
10410
  }
10257
10411
  function assertSafeTarget(root, path) {
10258
10412
  assertAllowedArchContextPath(root, path);
10259
- const absolute = resolve7(root, path);
10260
- if (existsSync4(absolute) && lstatSync2(absolute).isSymbolicLink()) {
10413
+ const absolute = resolve8(root, path);
10414
+ if (existsSync5(absolute) && lstatSync2(absolute).isSymbolicLink()) {
10261
10415
  throw new Error(`Refusing to write symlink target: ${path}`);
10262
10416
  }
10263
10417
  }
10264
10418
  function assertExpectedHash(path, expectedHash) {
10265
- const actual = digestJson({ body: readFileSync5(path, "utf8") });
10419
+ const actual = digestJson({ body: readFileSync6(path, "utf8") });
10266
10420
  if (expectedHash !== actual)
10267
10421
  throw new Error(`Expected hash mismatch: ${path}`);
10268
10422
  }
10269
10423
  function atomicWriteFile(path, tempPath, body) {
10270
- mkdirSync3(dirname3(path), { recursive: true });
10424
+ mkdirSync3(dirname4(path), { recursive: true });
10271
10425
  writeFileSync2(tempPath, body, "utf8");
10272
10426
  fsyncFile(tempPath);
10273
10427
  renameSync2(tempPath, path);
10274
- fsyncDirectory2(dirname3(path));
10428
+ fsyncDirectory2(dirname4(path));
10275
10429
  }
10276
10430
  function fsyncFile(path) {
10277
10431
  const fd = openSync3(path, process.platform === "win32" ? "r+" : "r");
@@ -10332,10 +10486,15 @@ function rollback(backups) {
10332
10486
  for (const backup of backups.reverse()) {
10333
10487
  if (backup.tempPath)
10334
10488
  rmSync3(backup.tempPath, { recursive: true, force: true });
10335
- rmSync3(backup.path, { recursive: true, force: true });
10336
- if (backup.existed && existsSync4(backup.backupPath))
10337
- renameSync2(backup.backupPath, backup.path);
10338
- fsyncDirectory2(dirname3(backup.path));
10489
+ if (backup.existed) {
10490
+ if (existsSync5(backup.backupPath)) {
10491
+ rmSync3(backup.path, { recursive: true, force: true });
10492
+ renameSync2(backup.backupPath, backup.path);
10493
+ }
10494
+ } else {
10495
+ rmSync3(backup.path, { recursive: true, force: true });
10496
+ }
10497
+ fsyncDirectory2(dirname4(backup.path));
10339
10498
  }
10340
10499
  }
10341
10500
  function cleanupBackups(backups) {
@@ -10540,19 +10699,19 @@ function digestSuffix2(digest) {
10540
10699
  }
10541
10700
 
10542
10701
  // packages/core/application/src/index.ts
10543
- import { existsSync as existsSync7, statSync as statSync5 } from "node:fs";
10544
- import { extname, resolve as resolve10 } from "node:path";
10702
+ import { existsSync as existsSync8, statSync as statSync5 } from "node:fs";
10703
+ import { extname, resolve as resolve11 } from "node:path";
10545
10704
 
10546
10705
  // packages/core/context-compiler/src/index.ts
10547
10706
  init_src();
10548
10707
 
10549
10708
  // packages/core/practice-catalog/src/index.ts
10550
10709
  init_src();
10551
- import { existsSync as existsSync5, lstatSync as lstatSync3, readdirSync as readdirSync4, readFileSync as readFileSync6, realpathSync as realpathSync5 } from "node:fs";
10552
- import { dirname as dirname4, relative as relative5, resolve as resolve8, sep as sep4 } from "node:path";
10710
+ import { existsSync as existsSync6, lstatSync as lstatSync3, readdirSync as readdirSync4, readFileSync as readFileSync7, realpathSync as realpathSync6 } from "node:fs";
10711
+ import { dirname as dirname5, relative as relative5, resolve as resolve9, sep as sep4 } from "node:path";
10553
10712
  import { fileURLToPath } from "node:url";
10554
10713
  var PRACTICE_CATALOG_VERSION = "2026.06.0";
10555
- var BUILTIN_PRACTICE_ASSETS_DIR = resolve8(dirname4(fileURLToPath(import.meta.url)), "../assets");
10714
+ var BUILTIN_PRACTICE_ASSETS_DIR = resolve9(dirname5(fileURLToPath(import.meta.url)), "../assets");
10556
10715
  var ALLOWED_CHECK_IDS = new Set([
10557
10716
  "compatibility-contract-required",
10558
10717
  "no-new-cycle",
@@ -10604,13 +10763,13 @@ var PRACTICE_ROOT_KEYS = new Set([
10604
10763
  "overlay"
10605
10764
  ]);
10606
10765
  function loadPracticeCatalog(options = {}) {
10607
- const root = options.root ? resolve8(options.root) : process.cwd();
10766
+ const root = options.root ? resolve9(options.root) : process.cwd();
10608
10767
  const builtInAssetsDir = options.builtInAssetsDir ?? BUILTIN_PRACTICE_ASSETS_DIR;
10609
10768
  const errors = [];
10610
10769
  const warnings = [];
10611
- const builtIn = loadAssetFiles(resolve8(builtInAssetsDir, "practices"), "curated-static", errors);
10612
- const profiles = loadProfileFiles(resolve8(builtInAssetsDir, "profiles"), errors);
10613
- const sources = loadSourceFiles(resolve8(builtInAssetsDir, "sources"), errors);
10770
+ const builtIn = loadAssetFiles(resolve9(builtInAssetsDir, "practices"), "curated-static", errors);
10771
+ const profiles = loadProfileFiles(resolve9(builtInAssetsDir, "profiles"), errors);
10772
+ const sources = loadSourceFiles(resolve9(builtInAssetsDir, "sources"), errors);
10614
10773
  const overlays = options.includeRepoOverlay === false ? [] : loadRepoOverlayAssets(root, errors);
10615
10774
  const merged = mergeCatalogAssets(builtIn, overlays, errors, options.now ?? new Date().toISOString());
10616
10775
  const sourceIds = new Set(sources.map((source) => source.id));
@@ -10796,8 +10955,8 @@ function loadProfileFiles(dir, errors) {
10796
10955
  return profiles.sort((a, b) => a.id.localeCompare(b.id));
10797
10956
  }
10798
10957
  function loadRepoOverlayAssets(root, errors) {
10799
- const overlayRoot = resolve8(root, ".archcontext/practices");
10800
- if (!existsSync5(overlayRoot))
10958
+ const overlayRoot = resolve9(root, ".archcontext/practices");
10959
+ if (!existsSync6(overlayRoot))
10801
10960
  return [];
10802
10961
  const out = [];
10803
10962
  for (const path of listDataFiles(overlayRoot, overlayRoot, errors)) {
@@ -11140,7 +11299,7 @@ function validateProfile(value, path, errors) {
11140
11299
  return invalid ? undefined : profile;
11141
11300
  }
11142
11301
  function listDataFiles(dir, allowedRoot, errors) {
11143
- if (!existsSync5(dir))
11302
+ if (!existsSync6(dir))
11144
11303
  return [];
11145
11304
  const out = [];
11146
11305
  const rootReal = safeRealpath(allowedRoot);
@@ -11160,7 +11319,7 @@ function listDataFiles(dir, allowedRoot, errors) {
11160
11319
  }
11161
11320
  if (stat.isDirectory()) {
11162
11321
  for (const entry of readdirSync4(current, { withFileTypes: true }))
11163
- walk(resolve8(current, entry.name));
11322
+ walk(resolve9(current, entry.name));
11164
11323
  return;
11165
11324
  }
11166
11325
  if (!/\.(ya?ml|json)$/.test(current))
@@ -11175,7 +11334,7 @@ function listDataFiles(dir, allowedRoot, errors) {
11175
11334
  }
11176
11335
  function parseDataFile(path, errors) {
11177
11336
  try {
11178
- const body = readFileSync6(path, "utf8").replace(/^\uFEFF/, "").trim();
11337
+ const body = readFileSync7(path, "utf8").replace(/^\uFEFF/, "").trim();
11179
11338
  const withoutDocumentMarker = body.startsWith("---") ? body.replace(/^---\s*/, "").trim() : body;
11180
11339
  return JSON.parse(withoutDocumentMarker);
11181
11340
  } catch (error) {
@@ -11202,14 +11361,14 @@ function assertRealChild(root, path) {
11202
11361
  }
11203
11362
  function safeRealpath(path) {
11204
11363
  try {
11205
- return realpathSync5.native(path);
11364
+ return realpathSync6.native(path);
11206
11365
  } catch {
11207
- return resolve8(path);
11366
+ return resolve9(path);
11208
11367
  }
11209
11368
  }
11210
11369
  function isRealChild(rootReal, pathReal) {
11211
11370
  const rel = relative5(rootReal, pathReal);
11212
- return rel === "" || !rel.startsWith("..") && !rel.includes(`..${sep4}`) && !resolve8(rel).startsWith("..");
11371
+ return rel === "" || !rel.startsWith("..") && !rel.includes(`..${sep4}`) && !resolve9(rel).startsWith("..");
11213
11372
  }
11214
11373
  function displayPath(path) {
11215
11374
  return path.split(sep4).join("/");
@@ -11773,8 +11932,8 @@ function checkResult(input, result) {
11773
11932
  }
11774
11933
  // packages/core/practice-engine/src/enforcement.ts
11775
11934
  init_src();
11776
- import { existsSync as existsSync6, lstatSync as lstatSync4, readdirSync as readdirSync5, readFileSync as readFileSync7 } from "node:fs";
11777
- import { basename as basename3, join as join4, relative as relative6, resolve as resolve9, sep as sep5 } from "node:path";
11935
+ import { existsSync as existsSync7, lstatSync as lstatSync4, readdirSync as readdirSync5, readFileSync as readFileSync8 } from "node:fs";
11936
+ import { basename as basename3, join as join4, relative as relative6, resolve as resolve10, sep as sep5 } from "node:path";
11778
11937
  var ENFORCEMENT_RANK = { advisory: 0, checkpoint: 1, complete: 2 };
11779
11938
  var POLICY_MODES = new Set(["advisory", "active", "fail-open", "fail-closed"]);
11780
11939
  var DEFAULT_POLICY = {
@@ -11797,16 +11956,16 @@ function shouldEvaluatePracticeEnforcement(policy) {
11797
11956
  return practiceEnforcementPolicyMode(policy) !== "advisory";
11798
11957
  }
11799
11958
  function loadPracticeEnforcementPolicy(root) {
11800
- const path = resolve9(root, ".archcontext/policies/practices.yaml");
11801
- if (!existsSync6(path))
11959
+ const path = resolve10(root, ".archcontext/policies/practices.yaml");
11960
+ if (!existsSync7(path))
11802
11961
  return defaultPracticeEnforcementPolicy();
11803
11962
  assertRepoPolicyFile(root, path, ".archcontext/policies/practices.yaml");
11804
11963
  const parsed = parseJsonYamlFile(path);
11805
11964
  return validatePracticeEnforcementPolicy(parsed, ".archcontext/policies/practices.yaml");
11806
11965
  }
11807
11966
  function loadPracticeWaivers(root) {
11808
- const dir = resolve9(root, ".archcontext/waivers");
11809
- if (!existsSync6(dir))
11967
+ const dir = resolve10(root, ".archcontext/waivers");
11968
+ if (!existsSync7(dir))
11810
11969
  return [];
11811
11970
  const stat = lstatSync4(dir);
11812
11971
  if (stat.isSymbolicLink())
@@ -11826,17 +11985,17 @@ function loadPracticeWaivers(root) {
11826
11985
  return waivers.sort((left, right) => waiverDigest(left).localeCompare(waiverDigest(right)));
11827
11986
  }
11828
11987
  function loadPracticeWaiverOwnerRegistry(root) {
11829
- const modelDir = resolve9(root, ".archcontext/model/nodes");
11988
+ const modelDir = resolve10(root, ".archcontext/model/nodes");
11830
11989
  const sources = [];
11831
11990
  const subjects = [];
11832
- if (existsSync6(modelDir)) {
11991
+ if (existsSync7(modelDir)) {
11833
11992
  if (lstatSync4(modelDir).isSymbolicLink())
11834
11993
  throw new Error("practice-owner-registry-symlink-denied");
11835
11994
  for (const path of collectFiles(root, ".archcontext/model/nodes")) {
11836
- const absolute = resolve9(root, path);
11995
+ const absolute = resolve10(root, path);
11837
11996
  if (lstatSync4(absolute).isSymbolicLink())
11838
11997
  throw new Error(`practice-owner-registry-symlink-denied: ${path}`);
11839
- const body = readFileSync7(absolute, "utf8");
11998
+ const body = readFileSync8(absolute, "utf8");
11840
11999
  const lifecycleOwners = extractOwnershipOwners(body, "lifecycle");
11841
12000
  const dataOwners = extractOwnershipOwners(body, "data");
11842
12001
  for (const kind of ["lifecycle", "data"]) {
@@ -12111,12 +12270,12 @@ function isVagueReason(reason) {
12111
12270
  return /^(temporary|cleanup later|todo|tbd|later|just in case)$/i.test(trimmed);
12112
12271
  }
12113
12272
  function parseJsonYamlFile(path) {
12114
- const body = readFileSync7(path, "utf8").replace(/^\uFEFF/, "").trim();
12273
+ const body = readFileSync8(path, "utf8").replace(/^\uFEFF/, "").trim();
12115
12274
  const withoutDocumentMarker = body.startsWith("---") ? body.replace(/^---\s*/, "").trim() : body;
12116
12275
  return JSON.parse(withoutDocumentMarker);
12117
12276
  }
12118
12277
  function collectFiles(root, relativeDir) {
12119
- const dir = resolve9(root, relativeDir);
12278
+ const dir = resolve10(root, relativeDir);
12120
12279
  const out = [];
12121
12280
  for (const entry of readdirSync5(dir, { withFileTypes: true })) {
12122
12281
  const child = `${relativeDir}/${entry.name}`;
@@ -12204,8 +12363,8 @@ function assertRepoPolicyFile(root, path, relativePath) {
12204
12363
  assertRepoRelativePath(relativePath);
12205
12364
  if (lstatSync4(path).isSymbolicLink())
12206
12365
  throw new Error(`practice-policy-symlink-denied: ${relativePath}`);
12207
- const rootResolved = resolve9(root);
12208
- const pathResolved = resolve9(path);
12366
+ const rootResolved = resolve10(root);
12367
+ const pathResolved = resolve10(path);
12209
12368
  const rel = relative6(rootResolved, pathResolved);
12210
12369
  if (rel === "" || rel.startsWith("..") || rel.includes(`..${sep5}`))
12211
12370
  throw new Error(`practice-policy-path-escape: ${relativePath}`);
@@ -13641,8 +13800,8 @@ function classifyCheckpointPath(root, path) {
13641
13800
  return "ignored";
13642
13801
  if (isGeneratedCheckpointPath(path))
13643
13802
  return "generated";
13644
- const absolute = resolve10(root, path);
13645
- if (!existsSync7(absolute))
13803
+ const absolute = resolve11(root, path);
13804
+ if (!existsSync8(absolute))
13646
13805
  return "deleted";
13647
13806
  try {
13648
13807
  if (!statSync5(absolute).isFile())
@@ -14848,8 +15007,8 @@ function uniqueSorted4(values) {
14848
15007
 
14849
15008
  // packages/core/projection-engine/src/index.ts
14850
15009
  init_src();
14851
- import { existsSync as existsSync8, readdirSync as readdirSync6, readFileSync as readFileSync8 } from "node:fs";
14852
- import { basename as basename4, resolve as resolve11 } from "node:path";
15010
+ import { existsSync as existsSync9, readdirSync as readdirSync6, readFileSync as readFileSync9 } from "node:fs";
15011
+ import { basename as basename4, resolve as resolve12 } from "node:path";
14853
15012
  function nativeNodeSource(node) {
14854
15013
  const value = node.source;
14855
15014
  if (!value || typeof value !== "object" || Array.isArray(value))
@@ -14891,12 +15050,6 @@ function renderArchitectureDocumentationProjection(input) {
14891
15050
  });
14892
15051
  const targets = rendered.map((file) => file.target);
14893
15052
  const expectedByPath = new Map(rendered.map((file) => [file.path, file]));
14894
- const drift = architectureDocumentationProjectionDrift({
14895
- targets,
14896
- expectedFiles: rendered,
14897
- existingFiles: input.existingFiles ?? []
14898
- });
14899
- const rejected = drift.diffs.filter((diff) => diff.reasonCode === "projection-ambiguous-ownership");
14900
15053
  const projectionDigest = digestJson({
14901
15054
  rendererVersion,
14902
15055
  sourceDigest: input.sourceDigest,
@@ -14907,6 +15060,38 @@ function renderArchitectureDocumentationProjection(input) {
14907
15060
  generatedBodyDigest: file.generatedBodyDigest
14908
15061
  })).sort((left, right) => left.path.localeCompare(right.path))
14909
15062
  });
15063
+ const manifestValue = {
15064
+ schemaVersion: "archcontext.architecture-docs-projection-manifest/v1",
15065
+ rendererVersion,
15066
+ sourceDigest: input.sourceDigest,
15067
+ projectionDigest,
15068
+ targetCount: targets.length,
15069
+ fileCount: rendered.length,
15070
+ targets: targets.map((target) => ({
15071
+ targetId: target.targetId,
15072
+ type: target.type,
15073
+ scope: target.scope,
15074
+ path: target.path,
15075
+ ownership: target.ownership,
15076
+ rendererVersion: target.rendererVersion,
15077
+ format: target.format,
15078
+ sourceDigest: target.sourceDigest,
15079
+ outputDigest: target.outputDigest
15080
+ }))
15081
+ };
15082
+ const manifest = {
15083
+ path: "docs/architecture/.projection-manifest.json",
15084
+ body: `${JSON.stringify(manifestValue, null, 2)}
15085
+ `,
15086
+ digest: digestJson(manifestValue)
15087
+ };
15088
+ const drift = architectureDocumentationProjectionDrift({
15089
+ targets,
15090
+ expectedFiles: rendered,
15091
+ expectedManifest: manifest,
15092
+ existingFiles: input.existingFiles ?? []
15093
+ });
15094
+ const rejected = drift.diffs.filter((diff) => diff.reasonCode === "projection-ambiguous-ownership");
14910
15095
  return {
14911
15096
  schemaVersion: "archcontext.architecture-docs-projection-plan/v1",
14912
15097
  rendererVersion,
@@ -14914,6 +15099,7 @@ function renderArchitectureDocumentationProjection(input) {
14914
15099
  projectionDigest,
14915
15100
  targets,
14916
15101
  files: rendered.filter((file) => !rejected.some((diff) => diff.path === file.path && diff.targetId === file.target.targetId)),
15102
+ manifest,
14917
15103
  drift: {
14918
15104
  ...drift,
14919
15105
  diffs: drift.diffs.map((diff) => ({
@@ -14943,12 +15129,12 @@ function architectureDocumentationSourceDigest(input) {
14943
15129
  });
14944
15130
  }
14945
15131
  function loadArchitectureDecisionRecords(root) {
14946
- const dir = resolve11(root, "docs/adr");
14947
- if (!existsSync8(dir))
15132
+ const dir = resolve12(root, "docs/adr");
15133
+ if (!existsSync9(dir))
14948
15134
  return [];
14949
15135
  return readdirSync6(dir).filter((file) => /^ADR-\d{4}-.+\.md$/.test(file)).sort().map((file) => {
14950
15136
  const path = `docs/adr/${file}`;
14951
- const body = readFileSync8(resolve11(root, path), "utf8");
15137
+ const body = readFileSync9(resolve12(root, path), "utf8");
14952
15138
  const title = body.match(/^#\s+(.+)$/m)?.[1]?.trim() ?? basename4(file, ".md");
14953
15139
  const status = body.match(/^Status:\s*(.+)$/mi)?.[1]?.trim();
14954
15140
  return {
@@ -14970,17 +15156,17 @@ function loadArchitectureDocumentationFiles(root) {
14970
15156
  "docs/architecture/diagrams/architecture.structurizr.json",
14971
15157
  "docs/architecture/.projection-manifest.json"
14972
15158
  ]) {
14973
- const absolute = resolve11(root, entry);
14974
- if (existsSync8(absolute))
14975
- files.push({ path: entry, body: readFileSync8(absolute, "utf8") });
15159
+ const absolute = resolve12(root, entry);
15160
+ if (existsSync9(absolute))
15161
+ files.push({ path: entry, body: readFileSync9(absolute, "utf8") });
14976
15162
  }
14977
15163
  for (const dir of ["docs/architecture/modules", "docs/architecture/relations"]) {
14978
- const absoluteDir = resolve11(root, dir);
14979
- if (!existsSync8(absoluteDir))
15164
+ const absoluteDir = resolve12(root, dir);
15165
+ if (!existsSync9(absoluteDir))
14980
15166
  continue;
14981
15167
  for (const file of readdirSync6(absoluteDir).filter((name) => name.endsWith(".md")).sort()) {
14982
15168
  const path = `${dir}/${file}`;
14983
- files.push({ path, body: readFileSync8(resolve11(root, path), "utf8") });
15169
+ files.push({ path, body: readFileSync9(resolve12(root, path), "utf8") });
14984
15170
  }
14985
15171
  }
14986
15172
  return files.sort((left, right) => left.path.localeCompare(right.path));
@@ -15074,19 +15260,19 @@ function exportDocumentationStructurizrWorkspace(model) {
15074
15260
  }
15075
15261
  function loadNativeModelFromArchContext(root) {
15076
15262
  return {
15077
- nodes: readYamlObjects(resolve11(root, ".archcontext/model/nodes")),
15078
- relations: readYamlObjects(resolve11(root, ".archcontext/model/relations"))
15263
+ nodes: readYamlObjects(resolve12(root, ".archcontext/model/nodes")),
15264
+ relations: readYamlObjects(resolve12(root, ".archcontext/model/relations"))
15079
15265
  };
15080
15266
  }
15081
15267
  function mermaidId(id) {
15082
15268
  return stableId(id).replace(/-/g, "_").replace(/\./g, "_");
15083
15269
  }
15084
15270
  function readYamlObjects(dir) {
15085
- if (!existsSync8(dir))
15271
+ if (!existsSync9(dir))
15086
15272
  return [];
15087
15273
  return readdirSync6(dir).filter((file) => /\.ya?ml$/.test(file)).sort().map((file) => {
15088
- const path = resolve11(dir, file);
15089
- return parseJsonOrStableYaml(readFileSync8(path, "utf8"), path);
15274
+ const path = resolve12(dir, file);
15275
+ return parseJsonOrStableYaml(readFileSync9(path, "utf8"), path);
15090
15276
  });
15091
15277
  }
15092
15278
  function escapeMermaid(value) {
@@ -15317,6 +15503,34 @@ function architectureDocumentationProjectionDrift(input) {
15317
15503
  const expectedByPath = new Map(input.expectedFiles.map((file) => [file.path, file]));
15318
15504
  const targetIds = new Set(input.targets.map((target) => target.targetId));
15319
15505
  const diffs = [];
15506
+ const existingManifest = existingByPath.get(input.expectedManifest.path);
15507
+ if (!existingManifest) {
15508
+ diffs.push({
15509
+ path: input.expectedManifest.path,
15510
+ reasonCode: "projection-manifest-missing",
15511
+ expectedDigest: input.expectedManifest.digest
15512
+ });
15513
+ } else {
15514
+ try {
15515
+ const parsed = JSON.parse(existingManifest.body);
15516
+ const actualDigest = digestJson(parsed);
15517
+ if (actualDigest !== input.expectedManifest.digest) {
15518
+ diffs.push({
15519
+ path: input.expectedManifest.path,
15520
+ reasonCode: "projection-manifest-stale",
15521
+ expectedDigest: input.expectedManifest.digest,
15522
+ actualDigest
15523
+ });
15524
+ }
15525
+ } catch {
15526
+ diffs.push({
15527
+ path: input.expectedManifest.path,
15528
+ reasonCode: "projection-manifest-invalid",
15529
+ expectedDigest: input.expectedManifest.digest,
15530
+ actualDigest: digestJson({ path: existingManifest.path, body: existingManifest.body })
15531
+ });
15532
+ }
15533
+ }
15320
15534
  for (const expected of input.expectedFiles) {
15321
15535
  const existing = existingByPath.get(expected.path);
15322
15536
  if (!existing) {
@@ -15355,6 +15569,8 @@ function architectureDocumentationProjectionDrift(input) {
15355
15569
  }
15356
15570
  }
15357
15571
  for (const existing of input.existingFiles) {
15572
+ if (existing.path === input.expectedManifest.path)
15573
+ continue;
15358
15574
  if (!isManagedArchitectureDocumentationPath(existing.path) || expectedByPath.has(existing.path))
15359
15575
  continue;
15360
15576
  const region = findAnyGeneratedRegion(existing.body);
@@ -15403,7 +15619,7 @@ function generatedEndMarker(targetId) {
15403
15619
  return `${ARCHITECTURE_DOCS_GENERATED_END_PREFIX} target="${targetId}" -->`;
15404
15620
  }
15405
15621
  function isManagedArchitectureDocumentationPath(path) {
15406
- return /^docs\/architecture\/(modules|relations)\/.+\.md$/.test(path) || /^docs\/architecture\/diagrams\/architecture\.(mmd|likec4|structurizr\.json)$/.test(path) || path === "docs/architecture/index.md" || path === "docs/architecture/changelog.md" || path === "docs/architecture/decisions/index.md";
15622
+ return /^docs\/architecture\/(modules|relations)\/.+\.md$/.test(path) || /^docs\/architecture\/diagrams\/architecture\.(mmd|likec4|structurizr\.json)$/.test(path) || path === "docs/architecture/index.md" || path === "docs/architecture/changelog.md" || path === "docs/architecture/decisions/index.md" || path === "docs/architecture/.projection-manifest.json";
15407
15623
  }
15408
15624
  function pathSegment2(id) {
15409
15625
  return stableId(id).replace(/\./g, "-");
@@ -16613,23 +16829,23 @@ init_src();
16613
16829
 
16614
16830
  // packages/local-runtime/git-adapter/src/index.ts
16615
16831
  import { execFileSync as execFileSync4, spawnSync as spawnSync2 } from "node:child_process";
16616
- import { existsSync as existsSync9, mkdirSync as mkdirSync4, mkdtempSync as mkdtempSync2, rmSync as rmSync4 } from "node:fs";
16617
- import { dirname as dirname5, join as join5, resolve as resolve12 } from "node:path";
16832
+ import { existsSync as existsSync10, mkdirSync as mkdirSync4, mkdtempSync as mkdtempSync2, rmSync as rmSync4 } from "node:fs";
16833
+ import { dirname as dirname6, join as join5, resolve as resolve13 } from "node:path";
16618
16834
  import { tmpdir } from "node:os";
16619
16835
  init_src();
16620
16836
  function findRepositoryRoot2(start) {
16621
16837
  try {
16622
- return resolve12(execFileSync4("git", ["rev-parse", "--show-toplevel"], {
16838
+ return resolve13(execFileSync4("git", ["rev-parse", "--show-toplevel"], {
16623
16839
  cwd: start,
16624
16840
  encoding: "utf8",
16625
16841
  stdio: ["ignore", "pipe", "ignore"]
16626
16842
  }).trim());
16627
16843
  } catch {
16628
- let cursor = resolve12(start);
16844
+ let cursor = resolve13(start);
16629
16845
  while (true) {
16630
- if (existsSync9(resolve12(cursor, ".git")))
16846
+ if (existsSync10(resolve13(cursor, ".git")))
16631
16847
  return cursor;
16632
- const parent = dirname5(cursor);
16848
+ const parent = dirname6(cursor);
16633
16849
  if (parent === cursor)
16634
16850
  break;
16635
16851
  cursor = parent;
@@ -16729,7 +16945,7 @@ function prepareDetachedReviewWorktree(input) {
16729
16945
  observed: {}
16730
16946
  };
16731
16947
  }
16732
- const parent = input.tempRoot ? resolve12(input.tempRoot) : tmpdir();
16948
+ const parent = input.tempRoot ? resolve13(input.tempRoot) : tmpdir();
16733
16949
  mkdirSync4(parent, { recursive: true });
16734
16950
  const temporaryRoot = mkdtempSync2(join5(parent, "archctx-review-worktree-"));
16735
16951
  const worktreeRoot = join5(temporaryRoot, "worktree");
@@ -16781,7 +16997,7 @@ function removeDetachedReviewWorktree(worktree) {
16781
16997
  } catch {
16782
16998
  removePathWithRetry(worktree.worktreeRoot);
16783
16999
  } finally {
16784
- removePathWithRetry(worktree.temporaryRoot || dirname5(worktree.worktreeRoot));
17000
+ removePathWithRetry(worktree.temporaryRoot || dirname6(worktree.worktreeRoot));
16785
17001
  }
16786
17002
  }
16787
17003
  function removePathWithRetry(path) {
@@ -16898,13 +17114,13 @@ function isGitWorktreeError(error) {
16898
17114
  // packages/local-runtime/local-store-sqlite/src/index.ts
16899
17115
  import { execFileSync as execFileSync5 } from "node:child_process";
16900
17116
  import { createHash as createHash6, randomUUID as randomUUID2 } from "node:crypto";
16901
- import { chmodSync as chmodSync2, closeSync as closeSync4, existsSync as existsSync10, fsyncSync as fsyncSync3, lstatSync as lstatSync5, mkdirSync as mkdirSync5, openSync as openSync4, readFileSync as readFileSync9, realpathSync as realpathSync6, renameSync as renameSync3, rmSync as rmSync5, writeFileSync as writeFileSync3 } from "node:fs";
17117
+ import { chmodSync as chmodSync2, closeSync as closeSync4, existsSync as existsSync11, fsyncSync as fsyncSync3, lstatSync as lstatSync5, mkdirSync as mkdirSync5, openSync as openSync4, readFileSync as readFileSync10, realpathSync as realpathSync7, renameSync as renameSync3, rmSync as rmSync5, writeFileSync as writeFileSync3 } from "node:fs";
16902
17118
  import { readdir, readFile } from "node:fs/promises";
16903
- import { createRequire as createRequire3 } from "node:module";
17119
+ import { createRequire as createRequire4 } from "node:module";
16904
17120
  import { homedir as homedir2 } from "node:os";
16905
- import { dirname as dirname6, isAbsolute as isAbsolute4, join as join6, relative as relative7, resolve as resolve13 } from "node:path";
17121
+ import { dirname as dirname7, isAbsolute as isAbsolute4, join as join6, relative as relative7, resolve as resolve14 } from "node:path";
16906
17122
  init_src();
16907
- var runtimeRequire2 = createRequire3(import.meta.url);
17123
+ var runtimeRequire2 = createRequire4(import.meta.url);
16908
17124
  var SQLITE_SIDECAR_SUFFIXES2 = ["", "-wal", "-shm"];
16909
17125
  var LEGACY_MIGRATION_MARKER_FILE2 = "runtime.sqlite.migration.json";
16910
17126
  var LEGACY_MIGRATION_LOCK_FILE2 = "runtime.sqlite.migration.lock";
@@ -16941,6 +17157,7 @@ var REQUIRED_LOCAL_STORE_TABLES2 = [
16941
17157
  ];
16942
17158
  var SQLITE_PRAGMAS2 = [
16943
17159
  "PRAGMA journal_mode = WAL",
17160
+ "PRAGMA synchronous = FULL",
16944
17161
  "PRAGMA foreign_keys = ON",
16945
17162
  "PRAGMA busy_timeout = 5000"
16946
17163
  ];
@@ -17462,14 +17679,22 @@ var LOCAL_SQLITE_MIGRATIONS2 = [
17462
17679
  )`,
17463
17680
  "CREATE INDEX IF NOT EXISTS idx_audit_runs_status ON audit_runs(storage_repository_id, storage_workspace_id, status)"
17464
17681
  ]
17682
+ },
17683
+ {
17684
+ id: "0011_changeset_cleanup_cursor",
17685
+ statements: [
17686
+ "ALTER TABLE changeset_journal ADD COLUMN cleanup_completed_at TEXT",
17687
+ "CREATE INDEX IF NOT EXISTS idx_changeset_journal_cleanup_pending ON changeset_journal(status, cleanup_completed_at, updated_at)"
17688
+ ]
17465
17689
  }
17466
17690
  ];
17691
+ var CHANGESET_STARTUP_CLEANUP_LIMIT = 100;
17467
17692
  var ARCHCONTEXT_STATE_DIR_ENV2 = "ARCHCONTEXT_STATE_DIR";
17468
17693
  var ARCHCONTEXT_LOCAL_STORE_PATH_ENV2 = "ARCHCONTEXT_LOCAL_STORE_PATH";
17469
17694
  function defaultArchContextStateRoot2(env = process.env, platform = process.platform, home = homedir2()) {
17470
17695
  const override = env[ARCHCONTEXT_STATE_DIR_ENV2];
17471
17696
  if (override)
17472
- return { path: resolve13(override), source: "environment" };
17697
+ return { path: resolve14(override), source: "environment" };
17473
17698
  if (platform === "darwin")
17474
17699
  return { path: join6(home, "Library", "Application Support", "ArchContext"), source: "os-user-data" };
17475
17700
  if (platform === "win32")
@@ -17487,7 +17712,7 @@ function runtimeStatePaths2(root = process.cwd(), env = process.env) {
17487
17712
  const stateRoot = defaultArchContextStateRoot2(env);
17488
17713
  const repositoryStateDir = join6(stateRoot.path, "repositories", storageRepositoryId);
17489
17714
  const workspaceStateDir = join6(repositoryStateDir, "worktrees", storageWorkspaceId);
17490
- const legacyControlDir = resolve13(canonicalRepositoryRoot3, ".archcontext", ".local");
17715
+ const legacyControlDir = resolve14(canonicalRepositoryRoot3, ".archcontext", ".local");
17491
17716
  return {
17492
17717
  schemaVersion: "archcontext.runtime-state-paths/v1",
17493
17718
  stateRoot: stateRoot.path,
@@ -17521,11 +17746,11 @@ function migrateLegacyLocalStoreIfNeeded2(root = process.cwd(), env = process.en
17521
17746
  status: "explicit-local-store-override"
17522
17747
  });
17523
17748
  }
17524
- ensurePrivateDir2(dirname6(paths.localStorePath));
17525
- const legacyExists = existsSync10(paths.legacyLocalStorePath);
17749
+ ensurePrivateDir2(dirname7(paths.localStorePath));
17750
+ const legacyExists = existsSync11(paths.legacyLocalStorePath);
17526
17751
  const integrityCheck = {};
17527
17752
  const quarantinedFiles = [];
17528
- const targetExists = existsSync10(paths.localStorePath);
17753
+ const targetExists = existsSync11(paths.localStorePath);
17529
17754
  if (targetExists) {
17530
17755
  try {
17531
17756
  integrityCheck.target = assertCurrentLocalStore2(paths.localStorePath);
@@ -17989,6 +18214,11 @@ class SqliteLocalStore {
17989
18214
  throw new Error(`ChangeSet journal not found: ${journalId}`);
17990
18215
  db.prepare("UPDATE changeset_journal SET status = ?, updated_at = ?, completed_at = ? WHERE journal_id = ?").run("committed", nowIso2(), nowIso2(), journalId);
17991
18216
  }
18217
+ async completeChangeSetCleanup(journalId) {
18218
+ const db = await this.database();
18219
+ const completedAt = nowIso2();
18220
+ db.prepare("UPDATE changeset_journal SET cleanup_completed_at = ?, updated_at = ? WHERE journal_id = ? AND status = ?").run(completedAt, completedAt, journalId, "committed");
18221
+ }
17992
18222
  async abortChangeSet(journalId, reason) {
17993
18223
  const db = await this.database();
17994
18224
  const row = db.prepare("SELECT metadata_json FROM changeset_journal WHERE journal_id = ?").get(journalId);
@@ -17999,37 +18229,61 @@ class SqliteLocalStore {
17999
18229
  }
18000
18230
  recoverPendingChangeSets() {
18001
18231
  const db = this.requireOpenDatabase();
18002
- const committed = db.prepare("SELECT files_json FROM changeset_journal WHERE status = ?").all("committed");
18232
+ const committed = db.prepare(`SELECT journal_id, files_json FROM changeset_journal
18233
+ WHERE status = ? AND cleanup_completed_at IS NULL
18234
+ ORDER BY updated_at, journal_id
18235
+ LIMIT ?`).all("committed", CHANGESET_STARTUP_CLEANUP_LIMIT);
18003
18236
  for (const row of committed) {
18004
- cleanupCommittedJournalFiles(JSON.parse(String(row.files_json)));
18237
+ try {
18238
+ cleanupCommittedJournalFiles(JSON.parse(String(row.files_json)));
18239
+ const completedAt = nowIso2();
18240
+ db.prepare("UPDATE changeset_journal SET cleanup_completed_at = ?, updated_at = ? WHERE journal_id = ?").run(completedAt, completedAt, String(row.journal_id));
18241
+ } catch {}
18005
18242
  }
18006
18243
  const rows = db.prepare("SELECT journal_id, root, files_json, metadata_json FROM changeset_journal WHERE status = ?").all("pending");
18244
+ let recovered = 0;
18007
18245
  for (const row of rows) {
18008
- const files = JSON.parse(String(row.files_json));
18009
- const metadata = JSON.parse(String(row.metadata_json));
18010
- const plannedLedgerEvent = changeSetJournalPlannedLedgerEvent(metadata);
18011
- const existingLedgerEvent = plannedLedgerEvent ? architectureEventByIdempotency(db, plannedLedgerEvent) : undefined;
18012
- if (plannedLedgerEvent && existingLedgerEvent) {
18013
- const expectedEventHash = normalizeArchitectureLedgerEvent(plannedLedgerEvent, existingLedgerEvent.previousEventHash).eventHash;
18014
- if (expectedEventHash !== existingLedgerEvent.event.eventHash) {
18015
- throw new Error(`changeset-ledger-recovery-idempotency-conflict: ${plannedLedgerEvent.idempotencyKey}`);
18246
+ let metadata = {};
18247
+ try {
18248
+ const files = JSON.parse(String(row.files_json));
18249
+ metadata = JSON.parse(String(row.metadata_json));
18250
+ const plannedLedgerEvent = changeSetJournalPlannedLedgerEvent(metadata);
18251
+ const existingLedgerEvent = plannedLedgerEvent ? architectureEventByIdempotency(db, plannedLedgerEvent) : undefined;
18252
+ if (plannedLedgerEvent && existingLedgerEvent) {
18253
+ const expectedEventHash = normalizeArchitectureLedgerEvent(plannedLedgerEvent, existingLedgerEvent.previousEventHash).eventHash;
18254
+ if (expectedEventHash !== existingLedgerEvent.event.eventHash) {
18255
+ throw new Error(`changeset-ledger-recovery-idempotency-conflict: ${plannedLedgerEvent.idempotencyKey}`);
18256
+ }
18257
+ cleanupCommittedJournalFiles(files);
18258
+ const completedAt2 = nowIso2();
18259
+ db.prepare("UPDATE changeset_journal SET status = ?, metadata_json = ?, updated_at = ?, completed_at = ?, cleanup_completed_at = ? WHERE journal_id = ?").run("committed", stableJson(withChangeSetJournalArchitectureLedger(metadata, {
18260
+ recovery: {
18261
+ schemaVersion: "archcontext.changeset-ledger-recovery/v1",
18262
+ status: "ledger-append-detected",
18263
+ eventId: existingLedgerEvent.event.eventId,
18264
+ eventHash: existingLedgerEvent.event.eventHash ?? "",
18265
+ recoveredAt: completedAt2
18266
+ }
18267
+ })), completedAt2, completedAt2, completedAt2, String(row.journal_id));
18268
+ recovered += 1;
18269
+ continue;
18016
18270
  }
18017
- cleanupCommittedJournalFiles(files);
18018
- db.prepare("UPDATE changeset_journal SET status = ?, metadata_json = ?, updated_at = ?, completed_at = ? WHERE journal_id = ?").run("committed", stableJson(withChangeSetJournalArchitectureLedger(metadata, {
18019
- recovery: {
18020
- schemaVersion: "archcontext.changeset-ledger-recovery/v1",
18021
- status: "ledger-append-detected",
18022
- eventId: existingLedgerEvent.event.eventId,
18023
- eventHash: existingLedgerEvent.event.eventHash ?? "",
18024
- recoveredAt: nowIso2()
18271
+ recoverJournalFiles(String(row.root), files);
18272
+ const completedAt = nowIso2();
18273
+ db.prepare("UPDATE changeset_journal SET status = ?, updated_at = ?, completed_at = ?, cleanup_completed_at = ? WHERE journal_id = ?").run("recovered", completedAt, completedAt, completedAt, String(row.journal_id));
18274
+ recovered += 1;
18275
+ } catch (error) {
18276
+ db.prepare("UPDATE changeset_journal SET metadata_json = ?, updated_at = ? WHERE journal_id = ?").run(stableJson({
18277
+ ...metadata,
18278
+ recoveryError: {
18279
+ schemaVersion: "archcontext.changeset-recovery-error/v1",
18280
+ message: error instanceof Error ? error.message : String(error),
18281
+ failedAt: nowIso2()
18025
18282
  }
18026
- })), nowIso2(), nowIso2(), String(row.journal_id));
18027
- continue;
18283
+ }), nowIso2(), String(row.journal_id));
18028
18284
  }
18029
- recoverJournalFiles(String(row.root), files);
18030
- db.prepare("UPDATE changeset_journal SET status = ?, updated_at = ?, completed_at = ? WHERE journal_id = ?").run("recovered", nowIso2(), nowIso2(), String(row.journal_id));
18031
18285
  }
18032
- return rows.length;
18286
+ return recovered;
18033
18287
  }
18034
18288
  async saveTaskState(taskSessionId, state) {
18035
18289
  const db = await this.database();
@@ -18127,52 +18381,38 @@ class SqliteLocalStore {
18127
18381
  for (const event of input.events)
18128
18382
  validateArchitectureLedgerEvent(event);
18129
18383
  const db = await this.database();
18130
- const startedAt = Date.now();
18131
- const appendedEvents = [];
18132
- const duplicateEvents = [];
18133
18384
  db.exec("BEGIN IMMEDIATE");
18134
18385
  try {
18135
- let processed = 0;
18136
- for (const event of input.events) {
18137
- const duplicate = architectureEventByIdempotency(db, event);
18138
- if (duplicate) {
18139
- const expectedDuplicateHash = normalizeArchitectureLedgerEvent(event, duplicate.previousEventHash).eventHash;
18140
- if (expectedDuplicateHash !== duplicate.event.eventHash) {
18141
- throw new Error(`architecture-ledger-idempotency-conflict: ${event.idempotencyKey}`);
18142
- }
18143
- duplicateEvents.push(duplicate.event);
18144
- continue;
18145
- }
18146
- const previousEventHash = latestArchitectureEventHash(db, event.repository.storageRepositoryId, event.worktree.storageWorkspaceId);
18147
- const normalized = normalizeArchitectureLedgerEvent(event, previousEventHash);
18148
- insertArchitectureEvent(db, normalized);
18149
- persistArchitectureLedgerArtifacts(db, normalized);
18150
- materializeArchitectureLedgerEvent(db, normalized);
18151
- appendedEvents.push(normalized);
18152
- processed += 1;
18153
- if (input.faultAfterEvents !== undefined && processed >= input.faultAfterEvents)
18154
- throw new Error("architecture-ledger-fault-injection");
18155
- }
18156
- const scope = architectureScopeFromEvent(input.events[0] ?? duplicateEvents[0]);
18157
- const state = scope ? readArchitectureLedgerStateFromDb(db, scope) : emptyArchitectureLedgerState();
18158
- if (scope) {
18159
- recordArchitectureLedgerOperation(db, {
18160
- scope,
18161
- operationKind: "append_events",
18162
- durationMs: Date.now() - startedAt,
18163
- rowCount: appendedEvents.length,
18164
- rebuildReason: null
18165
- });
18166
- }
18386
+ const result = appendArchitectureEventsInOpenTransaction(db, input);
18167
18387
  db.exec("COMMIT");
18168
- return {
18169
- appendedEvents,
18170
- duplicateEvents,
18171
- graphDigest: architectureLedgerStateDigest(state),
18172
- entityCount: state.entities.length,
18173
- relationCount: state.relations.length,
18174
- constraintCount: state.constraints.length
18175
- };
18388
+ return result;
18389
+ } catch (error) {
18390
+ db.exec("ROLLBACK");
18391
+ throw error;
18392
+ }
18393
+ }
18394
+ async appendArchitectureEventsAndCommitChangeSet(journalId, input) {
18395
+ if (input.writer !== "runtime-daemon")
18396
+ throw new Error("architecture-ledger-writer-must-be-runtime-daemon");
18397
+ for (const event of input.events)
18398
+ validateArchitectureLedgerEvent(event);
18399
+ const db = await this.database();
18400
+ db.exec("BEGIN IMMEDIATE");
18401
+ try {
18402
+ const row = db.prepare("SELECT status, metadata_json FROM changeset_journal WHERE journal_id = ?").get(journalId);
18403
+ if (!row)
18404
+ throw new Error(`ChangeSet journal not found: ${journalId}`);
18405
+ if (String(row.status) !== "pending")
18406
+ throw new Error(`ChangeSet journal is not pending: ${journalId}`);
18407
+ const metadata = JSON.parse(String(row.metadata_json));
18408
+ const result = appendArchitectureEventsInOpenTransaction(db, input);
18409
+ const completedAt = nowIso2();
18410
+ db.prepare("UPDATE changeset_journal SET status = ?, metadata_json = ?, updated_at = ?, completed_at = ? WHERE journal_id = ?").run("committed", stableJson(withChangeSetJournalArchitectureLedger(metadata, {
18411
+ append: changeSetLedgerAppendSummary(result),
18412
+ appendedAt: completedAt
18413
+ })), completedAt, completedAt, journalId);
18414
+ db.exec("COMMIT");
18415
+ return result;
18176
18416
  } catch (error) {
18177
18417
  db.exec("ROLLBACK");
18178
18418
  throw error;
@@ -18186,24 +18426,50 @@ class SqliteLocalStore {
18186
18426
  WHERE storage_repository_id = ?
18187
18427
  AND storage_workspace_id = ?
18188
18428
  ${statusClause}
18189
- ORDER BY created_at DESC, run_id ASC`).all(input.repository.storageRepositoryId, input.worktree.storageWorkspaceId, ...statuses).map((row) => JSON.parse(String(row.run_json)));
18429
+ ORDER BY created_at DESC, run_id ASC`).all(input.repository.storageRepositoryId, architectureLedgerWorkspaceKey(input.worktree), ...statuses).map((row) => JSON.parse(String(row.run_json)));
18430
+ }
18431
+ async resolveArchitectureLedgerScope(input) {
18432
+ const db = await this.database();
18433
+ const rows = db.prepare(`SELECT event_json FROM architecture_events
18434
+ WHERE storage_repository_id = ? AND workspace_id = ? AND branch = ?
18435
+ ORDER BY event_sequence DESC`).all(input.repository.storageRepositoryId, input.worktree.workspaceId, input.worktree.branch);
18436
+ for (const row of rows) {
18437
+ const event = JSON.parse(String(row.event_json));
18438
+ if (event.worktree.storageWorkspaceId === input.worktree.storageWorkspaceId) {
18439
+ return architectureScopeFromEvent(event);
18440
+ }
18441
+ }
18442
+ return input;
18443
+ }
18444
+ async resolveLatestArchitectureLedgerScope(input) {
18445
+ const db = await this.database();
18446
+ const rows = db.prepare(`SELECT event_json FROM architecture_events
18447
+ WHERE storage_repository_id = ? AND workspace_id = ?
18448
+ ORDER BY event_sequence DESC`).all(input.repository.storageRepositoryId, input.worktree.workspaceId);
18449
+ for (const row of rows) {
18450
+ const event = JSON.parse(String(row.event_json));
18451
+ if (event.worktree.storageWorkspaceId === input.worktree.storageWorkspaceId) {
18452
+ return architectureScopeFromEvent(event);
18453
+ }
18454
+ }
18455
+ return input;
18190
18456
  }
18191
18457
  async getAuditRun(input) {
18192
18458
  const db = await this.database();
18193
18459
  const row = db.prepare(`SELECT run_json FROM audit_runs
18194
- WHERE storage_repository_id = ? AND storage_workspace_id = ? AND run_id = ?`).get(input.repository.storageRepositoryId, input.worktree.storageWorkspaceId, input.runId);
18460
+ WHERE storage_repository_id = ? AND storage_workspace_id = ? AND run_id = ?`).get(input.repository.storageRepositoryId, architectureLedgerWorkspaceKey(input.worktree), architectureLedgerStorageId(input.worktree, input.runId));
18195
18461
  return row ? JSON.parse(String(row.run_json)) : undefined;
18196
18462
  }
18197
18463
  async readArchitectureLedgerSourceCursor(input) {
18198
18464
  const db = await this.database();
18199
18465
  const row = db.prepare(`SELECT cursor_json FROM source_cursors
18200
- WHERE storage_repository_id = ? AND storage_workspace_id = ? AND cursor_id = ?`).get(input.repository.storageRepositoryId, input.worktree.storageWorkspaceId, input.cursorId);
18466
+ WHERE storage_repository_id = ? AND storage_workspace_id = ? AND cursor_id = ?`).get(input.repository.storageRepositoryId, architectureLedgerWorkspaceKey(input.worktree), architectureLedgerStorageId(input.worktree, input.cursorId));
18201
18467
  return row ? JSON.parse(String(row.cursor_json)) : undefined;
18202
18468
  }
18203
18469
  async createArchitectureLedgerSnapshot(input) {
18204
18470
  const db = await this.database();
18205
18471
  const startedAt = Date.now();
18206
- const latest = latestArchitectureEvent(db, input.repository.storageRepositoryId, input.worktree.storageWorkspaceId);
18472
+ const latest = latestArchitectureEvent(db, input.repository.storageRepositoryId, architectureLedgerWorkspaceKey(input.worktree));
18207
18473
  if (!latest)
18208
18474
  throw new Error("architecture-ledger-snapshot-requires-event");
18209
18475
  const state = readArchitectureLedgerStateFromDb(db, input);
@@ -18217,7 +18483,7 @@ class SqliteLocalStore {
18217
18483
  (snapshot_id, repository_id, storage_repository_id, workspace_id, storage_workspace_id, branch, head_sha, worktree_digest,
18218
18484
  source_mode, last_event_id, last_event_hash, graph_digest, projection_digest, entity_count, relation_count,
18219
18485
  constraint_count, input_digests_json, snapshot_json, created_at)
18220
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(snapshot.snapshotId, snapshot.repository.repositoryId, snapshot.repository.storageRepositoryId, snapshot.worktree.workspaceId, snapshot.worktree.storageWorkspaceId, snapshot.worktree.branch, snapshot.worktree.headSha, snapshot.worktree.worktreeDigest, snapshot.sourceMode, snapshot.eventCursor.lastEventId, snapshot.eventCursor.lastEventHash, snapshot.graphDigest, snapshot.projectionDigest, snapshot.entityCount, snapshot.relationCount, snapshot.constraintCount, stableJson(snapshot.inputDigests), stableJson(snapshot), snapshot.createdAt);
18486
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(snapshot.snapshotId, snapshot.repository.repositoryId, snapshot.repository.storageRepositoryId, snapshot.worktree.workspaceId, architectureLedgerWorkspaceKey(snapshot.worktree), snapshot.worktree.branch, snapshot.worktree.headSha, snapshot.worktree.worktreeDigest, snapshot.sourceMode, architectureLedgerStorageId(snapshot.worktree, snapshot.eventCursor.lastEventId), snapshot.eventCursor.lastEventHash, snapshot.graphDigest, snapshot.projectionDigest, snapshot.entityCount, snapshot.relationCount, snapshot.constraintCount, stableJson(snapshot.inputDigests), stableJson(snapshot), snapshot.createdAt);
18221
18487
  recordArchitectureLedgerOperation(db, {
18222
18488
  scope: input,
18223
18489
  operationKind: "create_snapshot",
@@ -18286,16 +18552,16 @@ class SqliteLocalStore {
18286
18552
  const db = await this.database();
18287
18553
  const startedAt = Date.now();
18288
18554
  const snapshot = db.prepare(`SELECT last_event_id FROM architecture_snapshots
18289
- WHERE snapshot_id = ? AND storage_repository_id = ? AND storage_workspace_id = ?`).get(input.beforeSnapshotId, input.repository.storageRepositoryId, input.worktree.storageWorkspaceId);
18555
+ WHERE snapshot_id = ? AND storage_repository_id = ? AND storage_workspace_id = ?`).get(input.beforeSnapshotId, input.repository.storageRepositoryId, architectureLedgerWorkspaceKey(input.worktree));
18290
18556
  if (!snapshot)
18291
18557
  throw new Error(`architecture-ledger-snapshot-not-found: ${input.beforeSnapshotId}`);
18292
18558
  const cursor = db.prepare("SELECT event_sequence FROM architecture_events WHERE event_id = ?").get(String(snapshot.last_event_id));
18293
18559
  if (!cursor)
18294
18560
  throw new Error(`architecture-ledger-snapshot-cursor-not-found: ${String(snapshot.last_event_id)}`);
18295
18561
  const before = Number(db.prepare(`SELECT COUNT(*) AS count FROM architecture_events
18296
- WHERE storage_repository_id = ? AND storage_workspace_id = ? AND event_sequence <= ? AND compacted_by_snapshot_id IS NULL`).get(input.repository.storageRepositoryId, input.worktree.storageWorkspaceId, Number(cursor.event_sequence))?.count ?? 0);
18562
+ WHERE storage_repository_id = ? AND storage_workspace_id = ? AND event_sequence <= ? AND compacted_by_snapshot_id IS NULL`).get(input.repository.storageRepositoryId, architectureLedgerWorkspaceKey(input.worktree), Number(cursor.event_sequence))?.count ?? 0);
18297
18563
  db.prepare(`UPDATE architecture_events SET compacted_by_snapshot_id = ?
18298
- WHERE storage_repository_id = ? AND storage_workspace_id = ? AND event_sequence <= ? AND compacted_by_snapshot_id IS NULL`).run(input.beforeSnapshotId, input.repository.storageRepositoryId, input.worktree.storageWorkspaceId, Number(cursor.event_sequence));
18564
+ WHERE storage_repository_id = ? AND storage_workspace_id = ? AND event_sequence <= ? AND compacted_by_snapshot_id IS NULL`).run(input.beforeSnapshotId, input.repository.storageRepositoryId, architectureLedgerWorkspaceKey(input.worktree), Number(cursor.event_sequence));
18299
18565
  recordArchitectureLedgerOperation(db, {
18300
18566
  scope: input,
18301
18567
  operationKind: "compact_events",
@@ -18315,7 +18581,7 @@ class SqliteLocalStore {
18315
18581
  const replay = await this.verifyArchitectureLedgerReplay(input);
18316
18582
  if (!replay.ok)
18317
18583
  failures.push(...replay.mismatches);
18318
- const snapshotCount = Number(db.prepare("SELECT COUNT(*) AS count FROM architecture_snapshots WHERE storage_repository_id = ? AND storage_workspace_id = ?").get(input.repository.storageRepositoryId, input.worktree.storageWorkspaceId)?.count ?? 0);
18584
+ const snapshotCount = Number(db.prepare("SELECT COUNT(*) AS count FROM architecture_snapshots WHERE storage_repository_id = ? AND storage_workspace_id = ?").get(input.repository.storageRepositoryId, architectureLedgerWorkspaceKey(input.worktree))?.count ?? 0);
18319
18585
  const result = {
18320
18586
  ok: failures.length === 0,
18321
18587
  graphDigest: replay.materializedDigest,
@@ -18334,8 +18600,8 @@ class SqliteLocalStore {
18334
18600
  }
18335
18601
  async backupArchitectureLedger(input) {
18336
18602
  const db = await this.database();
18337
- ensurePrivateDir2(dirname6(input.backupPath));
18338
- if (existsSync10(input.backupPath))
18603
+ ensurePrivateDir2(dirname7(input.backupPath));
18604
+ if (existsSync11(input.backupPath))
18339
18605
  rmSync5(input.backupPath, { force: true });
18340
18606
  db.exec(`VACUUM INTO ${sqliteStringLiteral2(input.backupPath)}`);
18341
18607
  return { backupPath: input.backupPath, integrity: assertSqliteIntegrity2(input.backupPath) };
@@ -18366,9 +18632,80 @@ class SqliteLocalStore {
18366
18632
  function architectureScopeFromEvent(event) {
18367
18633
  return event ? { repository: event.repository, worktree: event.worktree } : undefined;
18368
18634
  }
18635
+ function architectureLedgerWorkspaceKey(worktree) {
18636
+ return `ledger-scope:${digestJson({
18637
+ storageWorkspaceId: worktree.storageWorkspaceId,
18638
+ branch: worktree.branch,
18639
+ headSha: worktree.headSha,
18640
+ worktreeDigest: worktree.worktreeDigest
18641
+ }).slice("sha256:".length)}`;
18642
+ }
18643
+ function architectureLedgerStorageId(worktree, logicalId) {
18644
+ return `${architectureLedgerWorkspaceKey(worktree)}:${logicalId}`;
18645
+ }
18646
+ function appendArchitectureEventsInOpenTransaction(db, input) {
18647
+ const startedAt = Date.now();
18648
+ const appendedEvents = [];
18649
+ const duplicateEvents = [];
18650
+ let processed = 0;
18651
+ for (const event of input.events) {
18652
+ const duplicate = architectureEventByIdempotency(db, event);
18653
+ if (duplicate) {
18654
+ const expectedDuplicateHash = normalizeArchitectureLedgerEvent(event, duplicate.previousEventHash).eventHash;
18655
+ if (expectedDuplicateHash !== duplicate.event.eventHash) {
18656
+ throw new Error(`architecture-ledger-idempotency-conflict: ${event.idempotencyKey}`);
18657
+ }
18658
+ duplicateEvents.push(duplicate.event);
18659
+ continue;
18660
+ }
18661
+ const previousEventHash = latestArchitectureEventHash(db, event.repository.storageRepositoryId, architectureLedgerWorkspaceKey(event.worktree));
18662
+ const normalized = normalizeArchitectureLedgerEvent(event, previousEventHash);
18663
+ const operations = architectureLedgerPayload(normalized).operations ?? [];
18664
+ const scope2 = architectureScopeFromEvent(normalized);
18665
+ if (operations.length > 0) {
18666
+ const currentDigest = architectureLedgerStateDigest(readArchitectureLedgerStateFromDb(db, scope2));
18667
+ if (normalized.baseDigest !== currentDigest) {
18668
+ throw new Error(`architecture-ledger-base-digest-conflict: expected ${currentDigest}, received ${normalized.baseDigest}`);
18669
+ }
18670
+ }
18671
+ insertArchitectureEvent(db, normalized);
18672
+ persistArchitectureLedgerArtifacts(db, normalized);
18673
+ materializeArchitectureLedgerEvent(db, normalized);
18674
+ if (operations.length > 0) {
18675
+ const resultingDigest = architectureLedgerStateDigest(readArchitectureLedgerStateFromDb(db, scope2));
18676
+ if (normalized.resultingDigest !== resultingDigest) {
18677
+ throw new Error(`architecture-ledger-resulting-digest-conflict: expected ${resultingDigest}, received ${normalized.resultingDigest}`);
18678
+ }
18679
+ }
18680
+ appendedEvents.push(normalized);
18681
+ processed += 1;
18682
+ if (input.faultAfterEvents !== undefined && processed >= input.faultAfterEvents) {
18683
+ throw new Error("architecture-ledger-fault-injection");
18684
+ }
18685
+ }
18686
+ const scope = architectureScopeFromEvent(input.events[0] ?? duplicateEvents[0]);
18687
+ const state = scope ? readArchitectureLedgerStateFromDb(db, scope) : emptyArchitectureLedgerState();
18688
+ if (scope) {
18689
+ recordArchitectureLedgerOperation(db, {
18690
+ scope,
18691
+ operationKind: "append_events",
18692
+ durationMs: Date.now() - startedAt,
18693
+ rowCount: appendedEvents.length,
18694
+ rebuildReason: null
18695
+ });
18696
+ }
18697
+ return {
18698
+ appendedEvents,
18699
+ duplicateEvents,
18700
+ graphDigest: architectureLedgerStateDigest(state),
18701
+ entityCount: state.entities.length,
18702
+ relationCount: state.relations.length,
18703
+ constraintCount: state.constraints.length
18704
+ };
18705
+ }
18369
18706
  function architectureEventByIdempotency(db, event) {
18370
18707
  const row = db.prepare(`SELECT event_json, previous_event_hash FROM architecture_events
18371
- WHERE storage_repository_id = ? AND storage_workspace_id = ? AND idempotency_key = ?`).get(event.repository.storageRepositoryId, event.worktree.storageWorkspaceId, event.idempotencyKey);
18708
+ WHERE storage_repository_id = ? AND storage_workspace_id = ? AND idempotency_key = ?`).get(event.repository.storageRepositoryId, architectureLedgerWorkspaceKey(event.worktree), event.idempotencyKey);
18372
18709
  return row ? {
18373
18710
  event: JSON.parse(String(row.event_json)),
18374
18711
  previousEventHash: row.previous_event_hash === null || row.previous_event_hash === undefined ? null : String(row.previous_event_hash)
@@ -18384,11 +18721,12 @@ function latestArchitectureEvent(db, storageRepositoryId, storageWorkspaceId) {
18384
18721
  return row ? { event: JSON.parse(String(row.event_json)), eventHash: String(row.event_hash) } : undefined;
18385
18722
  }
18386
18723
  function insertArchitectureEvent(db, event) {
18724
+ const workspaceKey = architectureLedgerWorkspaceKey(event.worktree);
18387
18725
  db.prepare(`INSERT INTO architecture_events
18388
18726
  (event_id, repository_id, storage_repository_id, workspace_id, storage_workspace_id, branch, head_sha, worktree_digest,
18389
18727
  event_type, payload_version, source, actor_kind, actor_id, base_digest, resulting_digest, previous_event_hash,
18390
18728
  event_hash, idempotency_key, payload_json, provenance_json, event_json, created_at)
18391
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(event.eventId, event.repository.repositoryId, event.repository.storageRepositoryId, event.worktree.workspaceId, event.worktree.storageWorkspaceId, event.worktree.branch, event.worktree.headSha, event.worktree.worktreeDigest, event.eventType, event.payloadVersion, event.source, event.actor.kind, event.actor.id, event.baseDigest, event.resultingDigest, event.previousEventHash ?? null, event.eventHash, event.idempotencyKey, stableJson(event.payload), stableJson(event.provenance), stableJson(event), event.timestamp);
18729
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(architectureLedgerStorageId(event.worktree, event.eventId), event.repository.repositoryId, event.repository.storageRepositoryId, event.worktree.workspaceId, workspaceKey, event.worktree.branch, event.worktree.headSha, event.worktree.worktreeDigest, event.eventType, event.payloadVersion, event.source, event.actor.kind, event.actor.id, event.baseDigest, event.resultingDigest, event.previousEventHash ?? null, event.eventHash, event.idempotencyKey, stableJson(event.payload), stableJson(event.provenance), stableJson(event), event.timestamp);
18392
18730
  insertArchitectureLedgerFts(db, "event", architectureLedgerPayload(event).summary ?? "", architectureLedgerPayload(event).rationale ?? "", architectureLedgerPayload(event).title ?? "", "");
18393
18731
  const payload = architectureLedgerPayload(event);
18394
18732
  insertArchitectureLedgerSearchDoc(db, event, {
@@ -18402,11 +18740,13 @@ function insertArchitectureEvent(db, event) {
18402
18740
  }
18403
18741
  function persistArchitectureLedgerArtifacts(db, event) {
18404
18742
  const payload = architectureLedgerPayload(event);
18743
+ const workspaceKey = architectureLedgerWorkspaceKey(event.worktree);
18744
+ const storageEventId = architectureLedgerStorageId(event.worktree, event.eventId);
18405
18745
  for (const evidence of payload.evidenceItems ?? []) {
18406
18746
  db.prepare(`INSERT OR REPLACE INTO evidence_items
18407
18747
  (evidence_id, repository_id, storage_repository_id, workspace_id, storage_workspace_id, event_id, kind, strength,
18408
18748
  polarity, origin, subject, selector_json, summary, coverage_json, supports_json, provenance_json, evidence_json, digest, created_at)
18409
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(evidence.evidenceId, event.repository.repositoryId, event.repository.storageRepositoryId, event.worktree.workspaceId, event.worktree.storageWorkspaceId, event.eventId, evidence.kind, evidence.strength, evidence.polarity, evidence.origin, evidence.subject, stableJson(evidence.selector), evidence.summary, stableJson(evidence.coverage), stableJson(evidence.supports), stableJson(evidence.provenance), stableJson(evidence), evidence.digest, evidence.createdAt);
18749
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(architectureLedgerStorageId(event.worktree, evidence.evidenceId), event.repository.repositoryId, event.repository.storageRepositoryId, event.worktree.workspaceId, workspaceKey, storageEventId, evidence.kind, evidence.strength, evidence.polarity, evidence.origin, evidence.subject, stableJson(evidence.selector), evidence.summary, stableJson(evidence.coverage), stableJson(evidence.supports), stableJson(evidence.provenance), stableJson(evidence), evidence.digest, evidence.createdAt);
18410
18750
  insertArchitectureLedgerFts(db, "evidence", evidence.summary, "", "", evidence.summary);
18411
18751
  insertArchitectureLedgerSearchDoc(db, event, {
18412
18752
  docId: `evidence:${evidence.evidenceId}`,
@@ -18421,7 +18761,7 @@ function persistArchitectureLedgerArtifacts(db, event) {
18421
18761
  db.prepare(`INSERT OR REPLACE INTO evidence_bindings
18422
18762
  (binding_id, evidence_id, repository_id, storage_repository_id, workspace_id, storage_workspace_id, event_id,
18423
18763
  target_kind, target_id, binding_reason, authority_effect, provenance_json, binding_json, created_at)
18424
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(binding.bindingId, binding.evidenceId, event.repository.repositoryId, event.repository.storageRepositoryId, event.worktree.workspaceId, event.worktree.storageWorkspaceId, event.eventId, binding.target.kind, binding.target.id, binding.bindingReason, binding.authorityEffect, stableJson(binding.provenance), stableJson(binding), binding.createdAt);
18764
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(architectureLedgerStorageId(event.worktree, binding.bindingId), architectureLedgerStorageId(event.worktree, binding.evidenceId), event.repository.repositoryId, event.repository.storageRepositoryId, event.worktree.workspaceId, workspaceKey, storageEventId, binding.target.kind, binding.target.id, binding.bindingReason, binding.authorityEffect, stableJson(binding.provenance), stableJson(binding), binding.createdAt);
18425
18765
  }
18426
18766
  for (const run of payload.recommendationRuns ?? [])
18427
18767
  persistRecommendationRun(db, event, run);
@@ -18441,17 +18781,19 @@ function persistArchitectureLedgerArtifacts(db, event) {
18441
18781
  persistProjectionState(db, event, payload.projectionState);
18442
18782
  }
18443
18783
  function persistRecommendationRun(db, event, run) {
18784
+ const workspaceKey = architectureLedgerWorkspaceKey(event.worktree);
18444
18785
  db.prepare(`INSERT OR REPLACE INTO recommendation_runs
18445
18786
  (run_id, repository_id, storage_repository_id, workspace_id, storage_workspace_id, event_id, status, catalog_digest,
18446
18787
  input_digest, output_digest, metrics_json, run_json, started_at, completed_at)
18447
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(run.runId, event.repository.repositoryId, event.repository.storageRepositoryId, event.worktree.workspaceId, event.worktree.storageWorkspaceId, event.eventId, run.status, run.catalogDigest, run.inputDigest, run.outputDigest, stableJson(run.metrics), stableJson(run), run.startedAt, run.completedAt ?? null);
18788
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(architectureLedgerStorageId(event.worktree, run.runId), event.repository.repositoryId, event.repository.storageRepositoryId, event.worktree.workspaceId, workspaceKey, architectureLedgerStorageId(event.worktree, event.eventId), run.status, run.catalogDigest, run.inputDigest, run.outputDigest, stableJson(run.metrics), stableJson(run), run.startedAt, run.completedAt ?? null);
18448
18789
  }
18449
18790
  function persistRecommendation(db, event, recommendation) {
18791
+ const workspaceKey = architectureLedgerWorkspaceKey(event.worktree);
18450
18792
  db.prepare(`INSERT OR REPLACE INTO recommendations
18451
18793
  (recommendation_id, run_id, repository_id, storage_repository_id, workspace_id, storage_workspace_id, event_id, fingerprint,
18452
18794
  subject, practice_id, status, confidence, enforcement, risk, uncertainty, evidence_binding_ids_json, explanation_json,
18453
18795
  recommendation_json, created_at, updated_at)
18454
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(recommendation.recommendationId, recommendation.runId, event.repository.repositoryId, event.repository.storageRepositoryId, event.worktree.workspaceId, event.worktree.storageWorkspaceId, event.eventId, recommendation.fingerprint, recommendation.subject, recommendation.practiceId ?? null, recommendation.status, recommendation.confidence, recommendation.enforcement, recommendation.risk, recommendation.uncertainty, stableJson(recommendation.evidenceBindingIds), stableJson(recommendation.explanation), stableJson(recommendation), recommendation.createdAt, recommendation.updatedAt);
18796
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(architectureLedgerStorageId(event.worktree, recommendation.recommendationId), architectureLedgerStorageId(event.worktree, recommendation.runId), event.repository.repositoryId, event.repository.storageRepositoryId, event.worktree.workspaceId, workspaceKey, architectureLedgerStorageId(event.worktree, event.eventId), recommendation.fingerprint, recommendation.subject, recommendation.practiceId ?? null, recommendation.status, recommendation.confidence, recommendation.enforcement, recommendation.risk, recommendation.uncertainty, stableJson(recommendation.evidenceBindingIds), stableJson(recommendation.explanation), stableJson(recommendation), recommendation.createdAt, recommendation.updatedAt);
18455
18797
  insertArchitectureLedgerFts(db, "recommendation", recommendation.explanation.join(`
18456
18798
  `), "", recommendation.subject, recommendation.explanation.join(`
18457
18799
  `));
@@ -18468,44 +18810,51 @@ function persistRecommendation(db, event, recommendation) {
18468
18810
  });
18469
18811
  }
18470
18812
  function persistAgentJob(db, event, job) {
18813
+ const workspaceKey = architectureLedgerWorkspaceKey(event.worktree);
18471
18814
  db.prepare(`INSERT OR REPLACE INTO agent_jobs
18472
18815
  (job_id, repository_id, storage_repository_id, workspace_id, storage_workspace_id, event_id, status, runner_port,
18473
18816
  fingerprint, input_digest, output_digest, stale_policy, job_json, queued_at, updated_at)
18474
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(job.jobId, event.repository.repositoryId, event.repository.storageRepositoryId, event.worktree.workspaceId, event.worktree.storageWorkspaceId, event.eventId, job.status, job.runnerPort, job.fingerprint, job.inputDigest, job.outputDigest ?? null, job.stalePolicy, stableJson(job), job.queuedAt, job.updatedAt);
18817
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(architectureLedgerStorageId(event.worktree, job.jobId), event.repository.repositoryId, event.repository.storageRepositoryId, event.worktree.workspaceId, workspaceKey, architectureLedgerStorageId(event.worktree, event.eventId), job.status, job.runnerPort, job.fingerprint, job.inputDigest, job.outputDigest ?? null, job.stalePolicy, stableJson(job), job.queuedAt, job.updatedAt);
18475
18818
  }
18476
18819
  function persistAuditRun(db, event, run) {
18820
+ const workspaceKey = architectureLedgerWorkspaceKey(event.worktree);
18477
18821
  db.prepare(`INSERT OR REPLACE INTO audit_runs
18478
18822
  (run_id, repository_id, storage_repository_id, workspace_id, storage_workspace_id, event_id, job_id, report_id, status,
18479
18823
  repo_name_with_owner, repo_visibility, base_sha, input_digest, output_digest, run_json, created_at)
18480
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(run.runId, event.repository.repositoryId, event.repository.storageRepositoryId, event.worktree.workspaceId, event.worktree.storageWorkspaceId, event.eventId, run.jobId, run.reportId, run.status, run.repoNameWithOwner, run.repoVisibility, run.baseSha, run.inputDigest, run.outputDigest, stableJson(run), run.createdAt);
18824
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(architectureLedgerStorageId(event.worktree, run.runId), event.repository.repositoryId, event.repository.storageRepositoryId, event.worktree.workspaceId, workspaceKey, architectureLedgerStorageId(event.worktree, event.eventId), run.jobId, run.reportId, run.status, run.repoNameWithOwner, run.repoVisibility, run.baseSha, run.inputDigest, run.outputDigest, stableJson(run), run.createdAt);
18481
18825
  }
18482
18826
  function persistProjectionState(db, event, state) {
18483
18827
  const path = String(state.path ?? "projection");
18484
18828
  const projectionDigest = typeof state.projectionDigest === "string" ? state.projectionDigest : digestJson(state);
18829
+ const workspaceKey = architectureLedgerWorkspaceKey(event.worktree);
18485
18830
  db.prepare(`INSERT OR REPLACE INTO projection_state
18486
18831
  (projection_id, repository_id, storage_repository_id, workspace_id, storage_workspace_id, event_id, path, projection_digest, state_json, updated_at)
18487
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(String(state.projectionId ?? stableLedgerId("projection", event.eventId, path)), event.repository.repositoryId, event.repository.storageRepositoryId, event.worktree.workspaceId, event.worktree.storageWorkspaceId, event.eventId, path, projectionDigest, stableJson(state), event.timestamp);
18832
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(architectureLedgerStorageId(event.worktree, String(state.projectionId ?? stableLedgerId("projection", event.eventId, path))), event.repository.repositoryId, event.repository.storageRepositoryId, event.worktree.workspaceId, workspaceKey, architectureLedgerStorageId(event.worktree, event.eventId), path, projectionDigest, stableJson(state), event.timestamp);
18488
18833
  }
18489
18834
  function persistSourceCursor(db, event, cursor) {
18490
18835
  const source = String(cursor.source ?? event.source);
18836
+ const workspaceKey = architectureLedgerWorkspaceKey(event.worktree);
18491
18837
  db.prepare(`INSERT OR REPLACE INTO source_cursors
18492
18838
  (cursor_id, repository_id, storage_repository_id, workspace_id, storage_workspace_id, source, cursor_json, updated_at)
18493
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(String(cursor.cursorId ?? stableLedgerId("cursor", event.eventId, source)), event.repository.repositoryId, event.repository.storageRepositoryId, event.worktree.workspaceId, event.worktree.storageWorkspaceId, source, stableJson(cursor), event.timestamp);
18839
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(architectureLedgerStorageId(event.worktree, String(cursor.cursorId ?? stableLedgerId("cursor", event.eventId, source))), event.repository.repositoryId, event.repository.storageRepositoryId, event.worktree.workspaceId, workspaceKey, source, stableJson(cursor), event.timestamp);
18494
18840
  }
18495
18841
  function persistGenericLedgerJson(db, event, table, idColumn, jsonColumn, value, prefix) {
18496
18842
  const id = String(value[idColumn] ?? value.id ?? stableLedgerId(prefix, event.eventId, stableJson(value)));
18843
+ const storageId = architectureLedgerStorageId(event.worktree, id);
18844
+ const workspaceKey = architectureLedgerWorkspaceKey(event.worktree);
18497
18845
  if (table === "recommendation_feedback") {
18498
18846
  db.prepare(`INSERT OR REPLACE INTO recommendation_feedback
18499
18847
  (feedback_id, recommendation_id, repository_id, storage_repository_id, workspace_id, storage_workspace_id, event_id, feedback_json, created_at)
18500
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(id, String(value.recommendationId ?? value.recommendation_id ?? "unknown"), event.repository.repositoryId, event.repository.storageRepositoryId, event.worktree.workspaceId, event.worktree.storageWorkspaceId, event.eventId, stableJson(value), String(value.createdAt ?? event.timestamp));
18848
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(storageId, architectureLedgerStorageId(event.worktree, String(value.recommendationId ?? value.recommendation_id ?? "unknown")), event.repository.repositoryId, event.repository.storageRepositoryId, event.worktree.workspaceId, workspaceKey, architectureLedgerStorageId(event.worktree, event.eventId), stableJson(value), String(value.createdAt ?? event.timestamp));
18501
18849
  return;
18502
18850
  }
18503
18851
  db.prepare(`INSERT OR REPLACE INTO waivers
18504
18852
  (waiver_id, repository_id, storage_repository_id, workspace_id, storage_workspace_id, event_id, target_kind, target_id, waiver_json, created_at, expires_at)
18505
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(id, event.repository.repositoryId, event.repository.storageRepositoryId, event.worktree.workspaceId, event.worktree.storageWorkspaceId, event.eventId, String(value.targetKind ?? value.target_kind ?? "unknown"), String(value.targetId ?? value.target_id ?? "unknown"), stableJson(value), String(value.createdAt ?? event.timestamp), value.expiresAt ? String(value.expiresAt) : null);
18853
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(storageId, event.repository.repositoryId, event.repository.storageRepositoryId, event.worktree.workspaceId, workspaceKey, architectureLedgerStorageId(event.worktree, event.eventId), String(value.targetKind ?? value.target_kind ?? "unknown"), String(value.targetId ?? value.target_id ?? "unknown"), stableJson(value), String(value.createdAt ?? event.timestamp), value.expiresAt ? String(value.expiresAt) : null);
18506
18854
  }
18507
18855
  function materializeArchitectureLedgerEvent(db, event) {
18508
18856
  const payload = architectureLedgerPayload(event);
18857
+ const workspaceKey = architectureLedgerWorkspaceKey(event.worktree);
18509
18858
  for (const operation of payload.operations ?? []) {
18510
18859
  switch (operation.op) {
18511
18860
  case "upsert_entity":
@@ -18513,31 +18862,32 @@ function materializeArchitectureLedgerEvent(db, event) {
18513
18862
  break;
18514
18863
  case "delete_entity":
18515
18864
  deleteArchitectureLedgerSearchDocs(db, { repository: event.repository, worktree: event.worktree }, [operation.entityId]);
18516
- db.prepare("DELETE FROM architecture_entities_current WHERE storage_repository_id = ? AND storage_workspace_id = ? AND entity_id = ?").run(event.repository.storageRepositoryId, event.worktree.storageWorkspaceId, operation.entityId);
18517
- db.prepare("DELETE FROM architecture_relations_current WHERE storage_repository_id = ? AND storage_workspace_id = ? AND (source_entity_id = ? OR target_entity_id = ?)").run(event.repository.storageRepositoryId, event.worktree.storageWorkspaceId, operation.entityId, operation.entityId);
18865
+ db.prepare("DELETE FROM architecture_entities_current WHERE storage_repository_id = ? AND storage_workspace_id = ? AND entity_id = ?").run(event.repository.storageRepositoryId, workspaceKey, operation.entityId);
18866
+ db.prepare("DELETE FROM architecture_relations_current WHERE storage_repository_id = ? AND storage_workspace_id = ? AND (source_entity_id = ? OR target_entity_id = ?)").run(event.repository.storageRepositoryId, workspaceKey, operation.entityId, operation.entityId);
18518
18867
  break;
18519
18868
  case "upsert_relation":
18520
18869
  upsertArchitectureRelation(db, event, operation.relation);
18521
18870
  break;
18522
18871
  case "delete_relation":
18523
18872
  deleteArchitectureLedgerSearchDocs(db, { repository: event.repository, worktree: event.worktree }, [operation.relationId]);
18524
- db.prepare("DELETE FROM architecture_relations_current WHERE storage_repository_id = ? AND storage_workspace_id = ? AND relation_id = ?").run(event.repository.storageRepositoryId, event.worktree.storageWorkspaceId, operation.relationId);
18873
+ db.prepare("DELETE FROM architecture_relations_current WHERE storage_repository_id = ? AND storage_workspace_id = ? AND relation_id = ?").run(event.repository.storageRepositoryId, workspaceKey, operation.relationId);
18525
18874
  break;
18526
18875
  case "upsert_constraint":
18527
18876
  upsertArchitectureConstraint(db, event, operation.constraint);
18528
18877
  break;
18529
18878
  case "delete_constraint":
18530
18879
  deleteArchitectureLedgerSearchDocs(db, { repository: event.repository, worktree: event.worktree }, [operation.constraintId]);
18531
- db.prepare("DELETE FROM architecture_constraints_current WHERE storage_repository_id = ? AND storage_workspace_id = ? AND constraint_id = ?").run(event.repository.storageRepositoryId, event.worktree.storageWorkspaceId, operation.constraintId);
18880
+ db.prepare("DELETE FROM architecture_constraints_current WHERE storage_repository_id = ? AND storage_workspace_id = ? AND constraint_id = ?").run(event.repository.storageRepositoryId, workspaceKey, operation.constraintId);
18532
18881
  break;
18533
18882
  }
18534
18883
  }
18535
18884
  }
18536
18885
  function upsertArchitectureEntity(db, event, entity) {
18886
+ const workspaceKey = architectureLedgerWorkspaceKey(event.worktree);
18537
18887
  db.prepare(`INSERT OR REPLACE INTO architecture_entities_current
18538
18888
  (storage_repository_id, storage_workspace_id, entity_id, repository_id, workspace_id, branch, head_sha, worktree_digest,
18539
18889
  kind, canonical_name, status, path, summary, metadata_json, last_event_id, updated_at)
18540
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(event.repository.storageRepositoryId, event.worktree.storageWorkspaceId, entity.entityId, event.repository.repositoryId, event.worktree.workspaceId, event.worktree.branch, event.worktree.headSha, event.worktree.worktreeDigest, entity.kind, entity.canonicalName, entity.status, entity.path ?? null, entity.summary ?? null, stableJson(entity.metadata ?? {}), event.eventId, event.timestamp);
18890
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(event.repository.storageRepositoryId, workspaceKey, entity.entityId, event.repository.repositoryId, event.worktree.workspaceId, event.worktree.branch, event.worktree.headSha, event.worktree.worktreeDigest, entity.kind, entity.canonicalName, entity.status, entity.path ?? null, entity.summary ?? null, stableJson(entity.metadata ?? {}), architectureLedgerStorageId(event.worktree, event.eventId), event.timestamp);
18541
18891
  insertArchitectureLedgerFts(db, "entity", entity.summary ?? "", "", entity.canonicalName, "");
18542
18892
  insertArchitectureLedgerSearchDoc(db, event, {
18543
18893
  docId: `entity:${entity.entityId}`,
@@ -18550,10 +18900,11 @@ function upsertArchitectureEntity(db, event, entity) {
18550
18900
  });
18551
18901
  }
18552
18902
  function upsertArchitectureRelation(db, event, relation) {
18903
+ const workspaceKey = architectureLedgerWorkspaceKey(event.worktree);
18553
18904
  db.prepare(`INSERT OR REPLACE INTO architecture_relations_current
18554
18905
  (storage_repository_id, storage_workspace_id, relation_id, repository_id, workspace_id, branch, head_sha, worktree_digest,
18555
18906
  kind, source_entity_id, target_entity_id, status, summary, metadata_json, last_event_id, updated_at)
18556
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(event.repository.storageRepositoryId, event.worktree.storageWorkspaceId, relation.relationId, event.repository.repositoryId, event.worktree.workspaceId, event.worktree.branch, event.worktree.headSha, event.worktree.worktreeDigest, relation.kind, relation.sourceEntityId, relation.targetEntityId, relation.status, relation.summary ?? null, stableJson(relation.metadata ?? {}), event.eventId, event.timestamp);
18907
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(event.repository.storageRepositoryId, workspaceKey, relation.relationId, event.repository.repositoryId, event.worktree.workspaceId, event.worktree.branch, event.worktree.headSha, event.worktree.worktreeDigest, relation.kind, relation.sourceEntityId, relation.targetEntityId, relation.status, relation.summary ?? null, stableJson(relation.metadata ?? {}), architectureLedgerStorageId(event.worktree, event.eventId), event.timestamp);
18557
18908
  insertArchitectureLedgerFts(db, "relation", relation.summary ?? "", "", relation.relationId, "");
18558
18909
  insertArchitectureLedgerSearchDoc(db, event, {
18559
18910
  docId: `relation:${relation.relationId}`,
@@ -18566,10 +18917,11 @@ function upsertArchitectureRelation(db, event, relation) {
18566
18917
  });
18567
18918
  }
18568
18919
  function upsertArchitectureConstraint(db, event, constraint) {
18920
+ const workspaceKey = architectureLedgerWorkspaceKey(event.worktree);
18569
18921
  db.prepare(`INSERT OR REPLACE INTO architecture_constraints_current
18570
18922
  (storage_repository_id, storage_workspace_id, constraint_id, repository_id, workspace_id, branch, head_sha, worktree_digest,
18571
18923
  kind, subject_id, status, severity, summary, metadata_json, last_event_id, updated_at)
18572
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(event.repository.storageRepositoryId, event.worktree.storageWorkspaceId, constraint.constraintId, event.repository.repositoryId, event.worktree.workspaceId, event.worktree.branch, event.worktree.headSha, event.worktree.worktreeDigest, constraint.kind, constraint.subjectId, constraint.status, constraint.severity ?? null, constraint.summary ?? null, stableJson(constraint.metadata ?? {}), event.eventId, event.timestamp);
18924
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(event.repository.storageRepositoryId, workspaceKey, constraint.constraintId, event.repository.repositoryId, event.worktree.workspaceId, event.worktree.branch, event.worktree.headSha, event.worktree.worktreeDigest, constraint.kind, constraint.subjectId, constraint.status, constraint.severity ?? null, constraint.summary ?? null, stableJson(constraint.metadata ?? {}), architectureLedgerStorageId(event.worktree, event.eventId), event.timestamp);
18573
18925
  insertArchitectureLedgerFts(db, "constraint", constraint.summary ?? "", "", constraint.constraintId, "");
18574
18926
  insertArchitectureLedgerSearchDoc(db, event, {
18575
18927
  docId: `constraint:${constraint.constraintId}`,
@@ -18582,7 +18934,7 @@ function upsertArchitectureConstraint(db, event, constraint) {
18582
18934
  });
18583
18935
  }
18584
18936
  function readArchitectureLedgerStateFromDb(db, scope) {
18585
- const scopeParams = [scope.repository.storageRepositoryId, scope.worktree.storageWorkspaceId];
18937
+ const scopeParams = [scope.repository.storageRepositoryId, architectureLedgerWorkspaceKey(scope.worktree)];
18586
18938
  const entities = db.prepare(`SELECT entity_id, kind, canonical_name, status, path, summary, metadata_json
18587
18939
  FROM architecture_entities_current
18588
18940
  WHERE storage_repository_id = ? AND storage_workspace_id = ?
@@ -18623,7 +18975,7 @@ function readArchitectureLedgerStateFromDb(db, scope) {
18623
18975
  }
18624
18976
  function readArchitectureLedgerNeighborhoodFromDb(db, input) {
18625
18977
  const depth = Math.max(0, Math.floor(input.depth));
18626
- const scopeParams = [input.repository.storageRepositoryId, input.worktree.storageWorkspaceId];
18978
+ const scopeParams = [input.repository.storageRepositoryId, architectureLedgerWorkspaceKey(input.worktree)];
18627
18979
  const entityIds = [...new Set(db.prepare(`WITH RECURSIVE seed(entity_id) AS (
18628
18980
  SELECT entity_id FROM architecture_entities_current
18629
18981
  WHERE storage_repository_id = ? AND storage_workspace_id = ? AND entity_id = ?
@@ -18704,42 +19056,43 @@ function architectureEventsForReplay(db, input) {
18704
19056
  let untilEventId = input.untilEventId;
18705
19057
  if (input.snapshotId) {
18706
19058
  const snapshot = db.prepare(`SELECT last_event_id FROM architecture_snapshots
18707
- WHERE snapshot_id = ? AND storage_repository_id = ? AND storage_workspace_id = ?`).get(input.snapshotId, input.repository.storageRepositoryId, input.worktree.storageWorkspaceId);
19059
+ WHERE snapshot_id = ? AND storage_repository_id = ? AND storage_workspace_id = ?`).get(input.snapshotId, input.repository.storageRepositoryId, architectureLedgerWorkspaceKey(input.worktree));
18708
19060
  if (!snapshot)
18709
19061
  throw new Error(`architecture-ledger-snapshot-not-found: ${input.snapshotId}`);
18710
19062
  untilEventId = String(snapshot.last_event_id);
18711
19063
  }
18712
19064
  const rows = db.prepare(`SELECT event_id, event_json FROM architecture_events
18713
19065
  WHERE storage_repository_id = ? AND storage_workspace_id = ?
18714
- ORDER BY event_sequence`).all(input.repository.storageRepositoryId, input.worktree.storageWorkspaceId);
19066
+ ORDER BY event_sequence`).all(input.repository.storageRepositoryId, architectureLedgerWorkspaceKey(input.worktree));
18715
19067
  const events = [];
18716
19068
  for (const row of rows) {
18717
19069
  const event = JSON.parse(String(row.event_json));
18718
19070
  events.push(event);
18719
- if (untilEventId && String(row.event_id) === untilEventId)
19071
+ if (untilEventId && (String(row.event_id) === untilEventId || event.eventId === untilEventId))
18720
19072
  break;
18721
19073
  }
18722
19074
  return events;
18723
19075
  }
18724
19076
  function deleteArchitectureCurrentState(db, scope) {
18725
19077
  for (const table of ["architecture_entities_current", "architecture_relations_current", "architecture_constraints_current"]) {
18726
- db.prepare(`DELETE FROM ${table} WHERE storage_repository_id = ? AND storage_workspace_id = ?`).run(scope.repository.storageRepositoryId, scope.worktree.storageWorkspaceId);
19078
+ db.prepare(`DELETE FROM ${table} WHERE storage_repository_id = ? AND storage_workspace_id = ?`).run(scope.repository.storageRepositoryId, architectureLedgerWorkspaceKey(scope.worktree));
18727
19079
  }
18728
19080
  }
18729
19081
  function insertArchitectureLedgerFts(db, kind, summary, rationale, title, evidenceSummary) {
18730
19082
  db.prepare("INSERT INTO architecture_ledger_fts(kind, summary, rationale, title, evidence_summary) VALUES (?, ?, ?, ?, ?)").run(kind, summary, rationale, title, evidenceSummary);
18731
19083
  }
18732
19084
  function insertArchitectureLedgerSearchDoc(db, event, doc) {
19085
+ const workspaceKey = architectureLedgerWorkspaceKey(event.worktree);
18733
19086
  db.prepare(`DELETE FROM architecture_ledger_search_fts
18734
- WHERE storage_repository_id = ? AND storage_workspace_id = ? AND doc_id = ?`).run(event.repository.storageRepositoryId, event.worktree.storageWorkspaceId, doc.docId);
19087
+ WHERE storage_repository_id = ? AND storage_workspace_id = ? AND doc_id = ?`).run(event.repository.storageRepositoryId, workspaceKey, doc.docId);
18735
19088
  db.prepare(`INSERT INTO architecture_ledger_search_fts
18736
19089
  (doc_id, storage_repository_id, storage_workspace_id, target_kind, target_id, subject_id, title, summary, rationale, evidence_summary)
18737
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(doc.docId, event.repository.storageRepositoryId, event.worktree.storageWorkspaceId, doc.targetKind, doc.targetId, doc.subjectId ?? null, doc.title ?? "", doc.summary ?? "", doc.rationale ?? "", doc.evidenceSummary ?? "");
19090
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(doc.docId, event.repository.storageRepositoryId, workspaceKey, doc.targetKind, doc.targetId, doc.subjectId ?? null, doc.title ?? "", doc.summary ?? "", doc.rationale ?? "", doc.evidenceSummary ?? "");
18738
19091
  }
18739
19092
  function deleteArchitectureLedgerSearchDocs(db, scope, ids) {
18740
19093
  for (const id of ids) {
18741
19094
  db.prepare(`DELETE FROM architecture_ledger_search_fts
18742
- WHERE storage_repository_id = ? AND storage_workspace_id = ? AND (target_id = ? OR subject_id = ?)`).run(scope.repository.storageRepositoryId, scope.worktree.storageWorkspaceId, id, id);
19095
+ WHERE storage_repository_id = ? AND storage_workspace_id = ? AND (target_id = ? OR subject_id = ?)`).run(scope.repository.storageRepositoryId, architectureLedgerWorkspaceKey(scope.worktree), id, id);
18743
19096
  }
18744
19097
  }
18745
19098
  function queryArchitectureLedgerSearchFts(db, input) {
@@ -18753,7 +19106,7 @@ function queryArchitectureLedgerSearchFts(db, input) {
18753
19106
  WHERE storage_repository_id = ? AND storage_workspace_id = ?
18754
19107
  AND architecture_ledger_search_fts MATCH ?
18755
19108
  ORDER BY rank, target_kind, target_id
18756
- LIMIT ?`).all(input.repository.storageRepositoryId, input.worktree.storageWorkspaceId, matchQuery, limit);
19109
+ LIMIT ?`).all(input.repository.storageRepositoryId, architectureLedgerWorkspaceKey(input.worktree), matchQuery, limit);
18757
19110
  return rows.map((row) => {
18758
19111
  const title = String(row.title ?? "");
18759
19112
  const summary = String(row.summary ?? "");
@@ -18797,7 +19150,7 @@ function ftsMatchKind(query, doc) {
18797
19150
  function recordArchitectureLedgerOperation(db, input) {
18798
19151
  db.prepare(`INSERT INTO architecture_ledger_operations
18799
19152
  (operation_id, storage_repository_id, storage_workspace_id, operation_kind, duration_ms, row_count, rebuild_reason, created_at)
18800
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(`ledger_operation_${randomUUID2()}`, input.scope.repository.storageRepositoryId, input.scope.worktree.storageWorkspaceId, input.operationKind, Math.max(0, Math.trunc(input.durationMs)), input.rowCount, input.rebuildReason, nowIso2());
19153
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(`ledger_operation_${randomUUID2()}`, input.scope.repository.storageRepositoryId, architectureLedgerWorkspaceKey(input.scope.worktree), input.operationKind, Math.max(0, Math.trunc(input.durationMs)), input.rowCount, input.rebuildReason, nowIso2());
18801
19154
  }
18802
19155
  function stableLedgerId(prefix, ...parts) {
18803
19156
  return `${prefix}.${createHash6("sha256").update(parts.join("\x00")).digest("hex").slice(0, 24)}`;
@@ -18836,7 +19189,7 @@ function readAppliedLocalSqliteMigrations2(db) {
18836
19189
  async function rebuildDerivedLandscapeState(store, input) {
18837
19190
  const landscapePath = input.landscapePath ?? LANDSCAPE_FILE;
18838
19191
  const relationsDir = input.relationsDir ?? ".archcontext/relations";
18839
- const landscape = parseLandscapeFile(await readFile(resolve13(input.root, landscapePath), "utf8"), landscapePath);
19192
+ const landscape = parseLandscapeFile(await readFile(resolve14(input.root, landscapePath), "utf8"), landscapePath);
18840
19193
  const relations = await readRelationFiles(input.root, relationsDir);
18841
19194
  const scopedRelations = relations.filter((relation) => landscape.relations.includes(relation.id));
18842
19195
  const relationIds = new Set(relations.map((relation) => relation.id));
@@ -18863,7 +19216,7 @@ async function rebuildDerivedLandscapeState(store, input) {
18863
19216
  }
18864
19217
  async function openSqliteDatabase(databasePath) {
18865
19218
  if (databasePath !== ":memory:")
18866
- ensurePrivateDir2(dirname6(databasePath));
19219
+ ensurePrivateDir2(dirname7(databasePath));
18867
19220
  try {
18868
19221
  const nodeSqlite = await import("node:sqlite");
18869
19222
  const db = new nodeSqlite.DatabaseSync(databasePath);
@@ -18884,7 +19237,7 @@ async function openSqliteDatabase(databasePath) {
18884
19237
  }
18885
19238
  function openSqliteDatabaseSync2(databasePath) {
18886
19239
  if (databasePath !== ":memory:")
18887
- ensurePrivateDir2(dirname6(databasePath));
19240
+ ensurePrivateDir2(dirname7(databasePath));
18888
19241
  try {
18889
19242
  const nodeSqlite = runtimeRequire2("node:sqlite");
18890
19243
  const db2 = new nodeSqlite.DatabaseSync(databasePath);
@@ -18980,7 +19333,7 @@ function assertTrustedLegacyLocalStoreSource2(paths) {
18980
19333
  if (!stat.isFile()) {
18981
19334
  throw new Error(`Legacy SQLite source must be a regular file: ${paths.legacyLocalStorePath}`);
18982
19335
  }
18983
- const sourceRealPath = realpathSync6.native(paths.legacyLocalStorePath);
19336
+ const sourceRealPath = realpathSync7.native(paths.legacyLocalStorePath);
18984
19337
  if (!isPathInsideOrSame2(sourceRealPath, paths.repositoryRoot)) {
18985
19338
  throw new Error(`Legacy SQLite source must stay inside the repository root: ${paths.legacyLocalStorePath}`);
18986
19339
  }
@@ -19034,12 +19387,12 @@ function sqliteStringLiteral2(value) {
19034
19387
  function publishStagedLocalStore2(stagingPath, targetPath) {
19035
19388
  for (const suffix of SQLITE_SIDECAR_SUFFIXES2) {
19036
19389
  const path = `${targetPath}${suffix}`;
19037
- if (existsSync10(path))
19390
+ if (existsSync11(path))
19038
19391
  throw new Error(`Cannot publish migrated SQLite over existing target file: ${path}`);
19039
19392
  }
19040
19393
  renameSync3(stagingPath, targetPath);
19041
19394
  makePrivateFile2(targetPath);
19042
- fsyncDirectory3(dirname6(targetPath));
19395
+ fsyncDirectory3(dirname7(targetPath));
19043
19396
  }
19044
19397
  function writeLegacyMigrationMarker2(paths, integrityCheck, quarantinedFiles) {
19045
19398
  const markerPath = legacyMigrationMarkerPath2(paths);
@@ -19055,7 +19408,7 @@ function writeLegacyMigrationMarker2(paths, integrityCheck, quarantinedFiles) {
19055
19408
  }
19056
19409
  function acquireLegacyMigrationLock2(paths) {
19057
19410
  const lockPath = legacyMigrationLockPath2(paths);
19058
- ensurePrivateDir2(dirname6(lockPath));
19411
+ ensurePrivateDir2(dirname7(lockPath));
19059
19412
  try {
19060
19413
  const fd = openSync4(lockPath, "wx", 384);
19061
19414
  writeFileSync3(fd, JSON.stringify({
@@ -19084,7 +19437,7 @@ function releaseLegacyMigrationLock2(lock) {
19084
19437
  }
19085
19438
  function isStaleMigrationLock2(lockPath) {
19086
19439
  try {
19087
- const parsed = JSON.parse(readFileSync9(lockPath, "utf8"));
19440
+ const parsed = JSON.parse(readFileSync10(lockPath, "utf8"));
19088
19441
  if (typeof parsed.pid !== "number" || parsed.pid <= 0)
19089
19442
  return true;
19090
19443
  return !isProcessAlive2(parsed.pid);
@@ -19106,20 +19459,20 @@ function quarantineExistingLocalStore2(paths) {
19106
19459
  const quarantinedFiles = [];
19107
19460
  for (const suffix of SQLITE_SIDECAR_SUFFIXES2) {
19108
19461
  const source = `${paths.localStorePath}${suffix}`;
19109
- if (!existsSync10(source))
19462
+ if (!existsSync11(source))
19110
19463
  continue;
19111
19464
  const target = join6(quarantineDir, `runtime.sqlite${suffix}`);
19112
19465
  renameSync3(source, target);
19113
19466
  quarantinedFiles.push(target);
19114
19467
  }
19115
19468
  const markerPath = legacyMigrationMarkerPath2(paths);
19116
- if (existsSync10(markerPath)) {
19469
+ if (existsSync11(markerPath)) {
19117
19470
  const target = join6(quarantineDir, LEGACY_MIGRATION_MARKER_FILE2);
19118
19471
  renameSync3(markerPath, target);
19119
19472
  quarantinedFiles.push(target);
19120
19473
  }
19121
19474
  fsyncDirectory3(quarantineDir);
19122
- fsyncDirectory3(dirname6(paths.localStorePath));
19475
+ fsyncDirectory3(dirname7(paths.localStorePath));
19123
19476
  return quarantinedFiles;
19124
19477
  }
19125
19478
  function legacyMigrationMarkerPath2(paths) {
@@ -19129,7 +19482,7 @@ function legacyMigrationLockPath2(paths) {
19129
19482
  return join6(paths.workspaceStateDir, LEGACY_MIGRATION_LOCK_FILE2);
19130
19483
  }
19131
19484
  function writePrivateJson2(path, value) {
19132
- ensurePrivateDir2(dirname6(path));
19485
+ ensurePrivateDir2(dirname7(path));
19133
19486
  const fd = openSync4(path, "w", 384);
19134
19487
  try {
19135
19488
  writeFileSync3(fd, JSON.stringify(value, null, 2), "utf8");
@@ -19138,7 +19491,7 @@ function writePrivateJson2(path, value) {
19138
19491
  closeSync4(fd);
19139
19492
  }
19140
19493
  makePrivateFile2(path);
19141
- fsyncDirectory3(dirname6(path));
19494
+ fsyncDirectory3(dirname7(path));
19142
19495
  }
19143
19496
  function ensurePrivateDir2(path) {
19144
19497
  mkdirSync5(path, { recursive: true, mode: 448 });
@@ -19168,19 +19521,19 @@ function readGitPath2(root, args) {
19168
19521
  }
19169
19522
  }
19170
19523
  function resolveMaybeRelative2(base, path) {
19171
- return isAbsolute4(path) ? resolve13(path) : resolve13(base, path);
19524
+ return isAbsolute4(path) ? resolve14(path) : resolve14(base, path);
19172
19525
  }
19173
19526
  function canonicalPath2(path) {
19174
- const resolved = resolve13(path);
19527
+ const resolved = resolve14(path);
19175
19528
  try {
19176
- return realpathSync6.native(resolved);
19529
+ return realpathSync7.native(resolved);
19177
19530
  } catch {
19178
19531
  return resolved;
19179
19532
  }
19180
19533
  }
19181
19534
  function isPathInsideOrSame2(path, parent) {
19182
- const child = resolve13(path);
19183
- const base = resolve13(parent);
19535
+ const child = resolve14(path);
19536
+ const base = resolve14(parent);
19184
19537
  const fromBase = relative7(base, child);
19185
19538
  return fromBase === "" || !!fromBase && !fromBase.startsWith("..") && !isAbsolute4(fromBase);
19186
19539
  }
@@ -19363,32 +19716,40 @@ function changeSetLedgerAppendSummary(result) {
19363
19716
  }
19364
19717
  function changeSetJournalPlannedLedgerEvent(metadata) {
19365
19718
  const architectureLedger = metadata.architectureLedger;
19366
- if (!isJsonRecord(architectureLedger))
19719
+ if (architectureLedger === undefined)
19367
19720
  return;
19721
+ if (!isJsonRecord(architectureLedger))
19722
+ throw new Error("changeset-ledger-recovery-metadata-malformed");
19368
19723
  const plannedEvent = architectureLedger.plannedEvent;
19369
- if (!isJsonRecord(plannedEvent))
19724
+ if (plannedEvent === undefined)
19370
19725
  return;
19726
+ if (!isJsonRecord(plannedEvent))
19727
+ throw new Error("changeset-ledger-recovery-planned-event-malformed");
19728
+ const event = plannedEvent;
19371
19729
  try {
19372
- const event = plannedEvent;
19373
19730
  validateArchitectureLedgerEvent(event);
19374
- return event;
19375
- } catch {
19376
- return;
19731
+ } catch (error) {
19732
+ throw new Error(`changeset-ledger-recovery-planned-event-invalid: ${error instanceof Error ? error.message : String(error)}`);
19377
19733
  }
19734
+ return event;
19378
19735
  }
19379
19736
  function isJsonRecord(value) {
19380
19737
  return typeof value === "object" && value !== null && !Array.isArray(value);
19381
19738
  }
19382
19739
  function recoverJournalFiles(root, files) {
19383
19740
  for (const file of [...files].reverse()) {
19384
- const absolute = resolve13(root, file.path);
19741
+ const absolute = resolve14(root, file.path);
19385
19742
  if (file.tempPath)
19386
19743
  rmSync5(file.tempPath, { recursive: true, force: true });
19387
- rmSync5(absolute, { recursive: true, force: true });
19388
- if (file.existed && file.backupPath && existsSync10(file.backupPath)) {
19389
- renameSync3(file.backupPath, absolute);
19744
+ if (file.existed) {
19745
+ if (file.backupPath && existsSync11(file.backupPath)) {
19746
+ rmSync5(absolute, { recursive: true, force: true });
19747
+ renameSync3(file.backupPath, absolute);
19748
+ }
19749
+ } else {
19750
+ rmSync5(absolute, { recursive: true, force: true });
19390
19751
  }
19391
- fsyncDirectory3(dirname6(absolute));
19752
+ fsyncDirectory3(dirname7(absolute));
19392
19753
  }
19393
19754
  }
19394
19755
  function cleanupCommittedJournalFiles(files) {
@@ -19425,7 +19786,7 @@ function reviewTaskSessionId(result) {
19425
19786
  async function readRelationFiles(root, relationsDir) {
19426
19787
  let entries;
19427
19788
  try {
19428
- entries = await readdir(resolve13(root, relationsDir), { withFileTypes: true });
19789
+ entries = await readdir(resolve14(root, relationsDir), { withFileTypes: true });
19429
19790
  } catch (error) {
19430
19791
  if (error.code === "ENOENT")
19431
19792
  return [];
@@ -19436,15 +19797,25 @@ async function readRelationFiles(root, relationsDir) {
19436
19797
  if (!entry.isFile() || !/\.(json|ya?ml)$/.test(entry.name))
19437
19798
  continue;
19438
19799
  const relativePath = join6(relationsDir, entry.name).split("\\").join("/");
19439
- relations.push(parseCrossRepoRelationFile(await readFile(resolve13(root, relativePath), "utf8"), relativePath));
19800
+ relations.push(parseCrossRepoRelationFile(await readFile(resolve14(root, relativePath), "utf8"), relativePath));
19440
19801
  }
19441
19802
  return relations.sort((a, b) => a.id.localeCompare(b.id));
19442
19803
  }
19443
19804
 
19444
19805
  // packages/local-runtime/model-store-yaml/src/index.ts
19445
19806
  init_src();
19446
- import { existsSync as existsSync11, mkdirSync as mkdirSync6, readdirSync as readdirSync7, readFileSync as readFileSync10, rmSync as rmSync6, writeFileSync as writeFileSync4 } from "node:fs";
19447
- import { dirname as dirname7, resolve as resolve14 } from "node:path";
19807
+ import { existsSync as existsSync12, mkdirSync as mkdirSync6, readdirSync as readdirSync7, readFileSync as readFileSync11, rmSync as rmSync6, writeFileSync as writeFileSync4 } from "node:fs";
19808
+ import { dirname as dirname8, resolve as resolve15 } from "node:path";
19809
+ var GENERATED_ARCHITECTURE_PATH = ".archcontext/generated/ARCHITECTURE.md";
19810
+ var GENERATED_ARCHITECTURE_BODY = [
19811
+ "<!-- Generated by ArchContext. Do not edit by hand. -->",
19812
+ "",
19813
+ "# Architecture",
19814
+ "",
19815
+ "Model projection is generated from `.archcontext/model`.",
19816
+ ""
19817
+ ].join(`
19818
+ `);
19448
19819
  function createDefaultManifest(productId, productName) {
19449
19820
  return {
19450
19821
  schemaVersion: "archcontext.manifest/v1",
@@ -19594,21 +19965,55 @@ function initializeArchContextModel(root, productName = "ArchContext Project") {
19594
19965
  rebuildGeneratedProjection(root);
19595
19966
  }
19596
19967
  function rebuildGeneratedProjection(root) {
19597
- rmSync6(resolve14(root, ".archcontext/generated"), { recursive: true, force: true });
19598
- writeFile(root, ".archcontext/generated/ARCHITECTURE.md", [
19599
- "<!-- Generated by ArchContext. Do not edit by hand. -->",
19600
- "",
19601
- "# Architecture",
19602
- "",
19603
- "Model projection is generated from `.archcontext/model`.",
19604
- ""
19605
- ].join(`
19606
- `));
19968
+ for (const operation of planGeneratedProjection(root)) {
19969
+ if (operation.operation === "delete_entity") {
19970
+ rmSync6(resolve15(root, operation.path), { force: true });
19971
+ } else {
19972
+ writeFile(root, operation.path, operation.body);
19973
+ }
19974
+ }
19975
+ }
19976
+ function planGeneratedProjection(root) {
19977
+ const desiredBody = GENERATED_ARCHITECTURE_BODY;
19978
+ const desiredAbsolute = resolve15(root, GENERATED_ARCHITECTURE_PATH);
19979
+ const operations = [];
19980
+ if (!existsSync12(desiredAbsolute) || readFileSync11(desiredAbsolute, "utf8") !== desiredBody) {
19981
+ operations.push({
19982
+ path: GENERATED_ARCHITECTURE_PATH,
19983
+ expectedHash: existsSync12(desiredAbsolute) ? digestJson({ body: readFileSync11(desiredAbsolute, "utf8") }) : "missing",
19984
+ body: desiredBody,
19985
+ operation: "render_projection"
19986
+ });
19987
+ }
19988
+ for (const path of collectGeneratedProjectionFiles(root)) {
19989
+ if (path === GENERATED_ARCHITECTURE_PATH)
19990
+ continue;
19991
+ const body = readFileSync11(resolve15(root, path), "utf8");
19992
+ operations.push({
19993
+ path,
19994
+ expectedHash: digestJson({ body }),
19995
+ body: "",
19996
+ operation: "delete_entity"
19997
+ });
19998
+ }
19999
+ return operations.sort((left, right) => left.path.localeCompare(right.path));
20000
+ }
20001
+ function collectGeneratedProjectionFiles(root) {
20002
+ const generatedRoot = ".archcontext/generated";
20003
+ if (!existsSync12(resolve15(root, generatedRoot)))
20004
+ return [];
20005
+ const walk = (relativeDir) => readdirSync7(resolve15(root, relativeDir), { withFileTypes: true }).flatMap((entry) => {
20006
+ const child = `${relativeDir}/${entry.name}`;
20007
+ if (entry.isDirectory())
20008
+ return walk(child);
20009
+ return entry.isFile() ? [child] : [];
20010
+ });
20011
+ return walk(generatedRoot).sort();
19607
20012
  }
19608
20013
 
19609
20014
  class YamlModelStore {
19610
20015
  async loadManifest(workspace) {
19611
- return readFileSync10(resolve14(workspace.root, ".archcontext/manifest.yaml"), "utf8");
20016
+ return readFileSync11(resolve15(workspace.root, ".archcontext/manifest.yaml"), "utf8");
19612
20017
  }
19613
20018
  async loadModel(workspace) {
19614
20019
  return listModelFiles(workspace.root);
@@ -19617,7 +20022,7 @@ class YamlModelStore {
19617
20022
  const errors = [];
19618
20023
  for (const required of [".archcontext/manifest.yaml", ".archcontext/product.yaml"]) {
19619
20024
  try {
19620
- readFileSync10(resolve14(workspace.root, required), "utf8");
20025
+ readFileSync11(resolve15(workspace.root, required), "utf8");
19621
20026
  } catch {
19622
20027
  errors.push(`missing ${required}`);
19623
20028
  }
@@ -19647,7 +20052,7 @@ function listModelFiles(root) {
19647
20052
  "docs/adr"
19648
20053
  ]);
19649
20054
  return paths.map((path) => {
19650
- const body = readFileSync10(resolve14(root, path), "utf8");
20055
+ const body = readFileSync11(resolve15(root, path), "utf8");
19651
20056
  return {
19652
20057
  path,
19653
20058
  body,
@@ -19659,8 +20064,8 @@ function listModelFiles(root) {
19659
20064
  function collectArchContextFiles(root, entries) {
19660
20065
  const files = [];
19661
20066
  for (const entry of entries) {
19662
- const absolute = resolve14(root, entry);
19663
- if (!existsSync11(absolute))
20067
+ const absolute = resolve15(root, entry);
20068
+ if (!existsSync12(absolute))
19664
20069
  continue;
19665
20070
  const stat = readdirOrFile(absolute);
19666
20071
  if (stat === "file") {
@@ -19673,7 +20078,7 @@ function collectArchContextFiles(root, entries) {
19673
20078
  return [...new Set(files)].sort();
19674
20079
  function walk(relativeDir) {
19675
20080
  const out = [];
19676
- const dir = resolve14(root, relativeDir);
20081
+ const dir = resolve15(root, relativeDir);
19677
20082
  for (const entry of readdirSync7(dir, { withFileTypes: true })) {
19678
20083
  const child = `${relativeDir}/${entry.name}`;
19679
20084
  if (entry.isDirectory())
@@ -19714,8 +20119,8 @@ function writeYaml(root, path, value) {
19714
20119
  writeFile(root, path, stableYaml(value));
19715
20120
  }
19716
20121
  function writeFile(root, path, body) {
19717
- const absolute = resolve14(root, path);
19718
- mkdirSync6(dirname7(absolute), { recursive: true });
20122
+ const absolute = resolve15(root, path);
20123
+ mkdirSync6(dirname8(absolute), { recursive: true });
19719
20124
  writeFileSync4(absolute, body.endsWith(`
19720
20125
  `) ? body : `${body}
19721
20126
  `, "utf8");
@@ -20075,7 +20480,8 @@ class ArchitectureLedgerReadModelStore {
20075
20480
  return this.fallback.writeChangeSetPreview(changeSet);
20076
20481
  }
20077
20482
  async loadLedgerModel(workspace) {
20078
- const state = await this.localStore.readArchitectureLedgerState(architectureLedgerScopeForWorkspace(workspace));
20483
+ const scope = await this.localStore.resolveArchitectureLedgerScope(architectureLedgerScopeForWorkspace(workspace));
20484
+ const state = await this.localStore.readArchitectureLedgerState(scope);
20079
20485
  const projectedFiles = projectArchitectureLedgerStateToYamlFiles(state).map((file) => ({
20080
20486
  path: file.path,
20081
20487
  body: file.body,
@@ -20159,7 +20565,7 @@ class ArchctxDaemon {
20159
20565
  this.readModelStore = new ArchitectureLedgerReadModelStore(this.modelStore, this.localStore, this.architectureLedger);
20160
20566
  this.changeSetEngine = deps.changeSetEngine ?? new ChangeSetEngine({
20161
20567
  modelStore: this.modelStore,
20162
- projection: { rebuildGeneratedProjection },
20568
+ projection: { planGeneratedProjection },
20163
20569
  journal: this.localStore
20164
20570
  });
20165
20571
  this.devicePrivateKeySigner = deps.devicePrivateKeySigner;
@@ -21108,10 +21514,10 @@ class ArchctxDaemon {
21108
21514
  return errorEnvelope("practices.waive", "AC_SCHEMA_INVALID", error instanceof Error ? error.message : String(error));
21109
21515
  }
21110
21516
  const path = `.archcontext/waivers/${waiverId}.json`;
21111
- const absolute = resolve15(session.workspace.root, path);
21517
+ const absolute = resolve16(session.workspace.root, path);
21112
21518
  const body = `${JSON.stringify(waiver, null, 2)}
21113
21519
  `;
21114
- const expectedHash = existsSync12(absolute) ? digestJson({ body: readFileSync11(absolute, "utf8") }) : "missing";
21520
+ const expectedHash = existsSync13(absolute) ? digestJson({ body: readFileSync12(absolute, "utf8") }) : "missing";
21115
21521
  const draft = this.changeSetEngine.plan({
21116
21522
  id: input.id ?? `changeset.practice-waiver-${waiverId.replace(/[^A-Za-z0-9_-]/g, "-")}`,
21117
21523
  base: {
@@ -21450,6 +21856,16 @@ class ArchctxDaemon {
21450
21856
  const draft = this.changesets.get(input.id);
21451
21857
  if (!draft)
21452
21858
  throw new Error(`Unknown ChangeSet: ${input.id}`);
21859
+ if (draft.base.headSha !== session.workspace.headSha)
21860
+ throw new Error("ChangeSet HEAD changed before apply");
21861
+ if (draft.base.worktreeDigest !== current)
21862
+ throw new Error("ChangeSet worktree digest changed before apply");
21863
+ const currentModel = await this.readModelStore.validateModel(session.workspace);
21864
+ if (!currentModel.valid) {
21865
+ throw new Error(`ChangeSet base model is invalid: ${currentModel.errors.join("; ") || "unknown validation error"}`);
21866
+ }
21867
+ if (draft.base.modelDigest !== currentModel.modelDigest)
21868
+ throw new Error("ChangeSet model digest changed before apply");
21453
21869
  const approved = input.approved ? this.changeSetEngine.approve(draft) : draft;
21454
21870
  let ledgerAppend;
21455
21871
  const writesLedger = architectureLedgerWriteAppendsEvents(this.architectureLedger.writeMode);
@@ -21466,6 +21882,7 @@ class ArchctxDaemon {
21466
21882
  relationCount: appended.relationCount,
21467
21883
  constraintCount: appended.constraintCount
21468
21884
  };
21885
+ return { journalCommitted: Boolean(journalId) };
21469
21886
  } : undefined
21470
21887
  });
21471
21888
  return okEnvelope("apply_update", {
@@ -21499,14 +21916,72 @@ class ArchctxDaemon {
21499
21916
  });
21500
21917
  if (journalId)
21501
21918
  await this.localStore.recordChangeSetLedgerPlan(journalId, { event: plan.event });
21502
- const result = await this.localStore.appendArchitectureEvents({
21503
- writer: "runtime-daemon",
21504
- events: [plan.event]
21505
- });
21506
- if (journalId)
21507
- await this.localStore.recordChangeSetLedgerAppend(journalId, { result });
21919
+ const appendInput = { writer: "runtime-daemon", events: [plan.event] };
21920
+ const result = journalId ? await this.localStore.appendArchitectureEventsAndCommitChangeSet(journalId, appendInput) : await this.localStore.appendArchitectureEvents(appendInput);
21508
21921
  return result;
21509
21922
  }
21923
+ async applyArchitectureProjectionChangeSet(root, input) {
21924
+ const session = await this.openSession(root);
21925
+ const model = await this.modelStore.validateModel(session.workspace);
21926
+ if (!model.valid)
21927
+ throw new Error(`Architecture projection ChangeSet base model is invalid: ${model.errors.join("; ")}`);
21928
+ const projectionFiles = input.files.map((file) => ({
21929
+ path: file.path,
21930
+ body: file.body.endsWith(`
21931
+ `) ? file.body : `${file.body}
21932
+ `,
21933
+ expectedHash: expectedFileHash(root, file.path)
21934
+ }));
21935
+ const operations = [
21936
+ ...projectionFiles.length > 0 ? [{ op: "render_projection", expectedHash: "missing", projectionFiles }] : [],
21937
+ ...input.removedPaths.map((path) => ({
21938
+ op: "delete_entity",
21939
+ path,
21940
+ expectedHash: expectedFileHash(root, path)
21941
+ }))
21942
+ ];
21943
+ const draft = this.changeSetEngine.approve(this.changeSetEngine.plan({
21944
+ id: input.id,
21945
+ base: {
21946
+ headSha: session.workspace.headSha,
21947
+ worktreeDigest: session.snapshot.worktreeDigest,
21948
+ modelDigest: model.modelDigest
21949
+ },
21950
+ reason: { taskSessionId: input.id },
21951
+ operations
21952
+ }));
21953
+ await this.changeSetEngine.apply(root, draft, { approved: true });
21954
+ }
21955
+ async applyArchitectureProjectionRollbackChangeSet(root, projectedFiles, currentFiles, createdAt) {
21956
+ const targetPaths = new Set(projectedFiles.map((file) => file.path));
21957
+ const backupBase = `.archcontext/backups/ledger-rollback/${safePathSegment(createdAt)}`;
21958
+ const backupRelativePath = uniqueBackupPath(root, backupBase);
21959
+ const manifestPath = `${backupRelativePath}/manifest.json`;
21960
+ const { backup, manifest } = architectureProjectionRollbackBackup(currentFiles, {
21961
+ createdAt,
21962
+ path: backupRelativePath,
21963
+ manifestPath
21964
+ });
21965
+ const removedPaths = currentFiles.filter((file) => !targetPaths.has(file.path)).map((file) => file.path);
21966
+ await this.applyArchitectureProjectionChangeSet(root, {
21967
+ id: `changeset.ledger-rollback-${shortDigest3(digestJson({ createdAt, projectionDigest: architectureLedgerProjectionDigest(projectedFiles) }))}`,
21968
+ files: [
21969
+ ...currentFiles.map((file) => ({
21970
+ path: join8(backupRelativePath, archContextRelativePath(file.path)),
21971
+ body: file.body
21972
+ })),
21973
+ { path: manifestPath, body: `${JSON.stringify(manifest, null, 2)}
21974
+ ` },
21975
+ ...projectedFiles.map(({ path, body }) => ({ path, body }))
21976
+ ],
21977
+ removedPaths
21978
+ });
21979
+ return {
21980
+ backup,
21981
+ writtenPaths: projectedFiles.map((file) => file.path),
21982
+ removedPaths
21983
+ };
21984
+ }
21510
21985
  async ledgerState(root) {
21511
21986
  this.assertRunning();
21512
21987
  return okEnvelope("ledger.state", await this.architectureLedgerReadback(root));
@@ -21860,8 +22335,13 @@ class ArchctxDaemon {
21860
22335
  const scope = await this.architectureLedgerScope(root);
21861
22336
  const state = await this.localStore.readArchitectureLedgerState(scope);
21862
22337
  const projectedFiles = projectArchitectureLedgerStateToYamlFiles(state);
21863
- if (writes)
21864
- writeArchitectureProjectionFiles(root, projectedFiles);
22338
+ if (writes) {
22339
+ await this.applyArchitectureProjectionChangeSet(root, {
22340
+ id: `changeset.ledger-project-${shortDigest3(architectureLedgerProjectionDigest(projectedFiles))}`,
22341
+ files: projectedFiles.map(({ path, body }) => ({ path, body })),
22342
+ removedPaths: []
22343
+ });
22344
+ }
21865
22345
  const drift = compareArchitectureLedgerStateToYaml({
21866
22346
  state,
21867
22347
  files: listModelFiles(root),
@@ -22011,7 +22491,7 @@ class ArchctxDaemon {
22011
22491
  const projectedFiles = projectArchitectureLedgerStateToYamlFiles(state);
22012
22492
  const currentManagedFiles = listModelFiles(root).filter((file) => isArchitectureLedgerManagedModelPath(file.path));
22013
22493
  const backupPlan = architectureProjectionRollbackBackup(currentManagedFiles);
22014
- const writeResult = writes ? replaceArchitectureProjectionFilesForYamlRollback(root, projectedFiles, currentManagedFiles, this.clock()) : { backup: backupPlan.backup, writtenPaths: [], removedPaths: [] };
22494
+ const writeResult = writes ? await this.applyArchitectureProjectionRollbackChangeSet(root, projectedFiles, currentManagedFiles, this.clock()) : { backup: backupPlan.backup, writtenPaths: [], removedPaths: [] };
22015
22495
  const drift = compareArchitectureLedgerStateToYaml({
22016
22496
  state,
22017
22497
  files: listModelFiles(root),
@@ -22050,9 +22530,12 @@ class ArchctxDaemon {
22050
22530
  return errorEnvelope("ledger.rebuild", "AC_SCHEMA_INVALID", "ledger rebuild currently requires --from-git");
22051
22531
  return this.withWriter(async () => {
22052
22532
  this.assertFreshWorktree(root, input.expectedWorktreeDigest, "ledger rebuild --from-git");
22053
- const scope = await this.architectureLedgerScope(root);
22533
+ const scope = await this.architectureLedgerGitScope(root);
22054
22534
  const files = listModelFiles(root);
22055
- const previousState = await this.localStore.readArchitectureLedgerState(scope);
22535
+ const exactReplay = await this.localStore.replayArchitectureLedger(scope);
22536
+ const exactScopeHasEvents = exactReplay.events.length > 0;
22537
+ const authorityScope = exactScopeHasEvents ? scope : await this.localStore.resolveLatestArchitectureLedgerScope(scope);
22538
+ const previousState = exactScopeHasEvents ? exactReplay.state : await this.localStore.readArchitectureLedgerState(authorityScope);
22056
22539
  const previousGraphDigest = architectureLedgerStateDigest(previousState);
22057
22540
  const rebuildCommand = input.acceptExternalProjection ? "archctx ledger rebuild --from-git --accept-external-projection" : "archctx ledger rebuild --from-git";
22058
22541
  const plan = planYamlToArchitectureLedgerRebuild({
@@ -22062,12 +22545,18 @@ class ArchctxDaemon {
22062
22545
  command: rebuildCommand,
22063
22546
  previousState
22064
22547
  });
22548
+ const importPlan = planYamlToArchitectureLedgerImport({
22549
+ ...scope,
22550
+ files,
22551
+ createdAt: this.clock(),
22552
+ command: rebuildCommand
22553
+ });
22065
22554
  if (plan.unsupportedFiles.length > 0) {
22066
22555
  return errorEnvelope("ledger.rebuild", "AC_SCHEMA_INVALID", "ledger rebuild requires supported YAML model files");
22067
22556
  }
22068
22557
  const cursor = architectureLedgerGitCursorFromPlan({ ...scope, plan });
22069
22558
  const previousCursor = await this.localStore.readArchitectureLedgerSourceCursor({
22070
- ...scope,
22559
+ ...authorityScope,
22071
22560
  cursorId: ARCHITECTURE_LEDGER_GIT_CURSOR_ID
22072
22561
  });
22073
22562
  const cursorChanged = previousCursor?.cursorDigest !== cursor.cursorDigest;
@@ -22083,7 +22572,13 @@ class ArchctxDaemon {
22083
22572
  let rebuildStatus = "unchanged";
22084
22573
  let proposedExternalProjectionChange;
22085
22574
  if (previousGraphDigest === plan.graphDigest) {
22086
- if (cursorChanged) {
22575
+ if (!exactScopeHasEvents) {
22576
+ append = await this.localStore.appendArchitectureEvents({
22577
+ writer: "runtime-daemon",
22578
+ events: [importPlan.event]
22579
+ });
22580
+ rebuildStatus = isEmptyArchitectureLedgerState(previousState) ? "rebuilt" : "cursor-refreshed";
22581
+ } else if (cursorChanged) {
22087
22582
  const cursorPlan = planGitCursorRefreshToArchitectureLedgerEvent({
22088
22583
  ...scope,
22089
22584
  cursor,
@@ -22099,7 +22594,7 @@ class ArchctxDaemon {
22099
22594
  }
22100
22595
  } else if (!previousStateEmpty && !input.acceptExternalProjection) {
22101
22596
  const proposal = planExternalProjectionChangeToArchitectureLedgerEvent({
22102
- ...scope,
22597
+ ...authorityScope,
22103
22598
  files,
22104
22599
  createdAt: this.clock(),
22105
22600
  command: rebuildCommand,
@@ -22122,11 +22617,12 @@ class ArchctxDaemon {
22122
22617
  } else {
22123
22618
  append = await this.localStore.appendArchitectureEvents({
22124
22619
  writer: "runtime-daemon",
22125
- events: [plan.event]
22620
+ events: [exactScopeHasEvents ? plan.event : importPlan.event]
22126
22621
  });
22127
22622
  rebuildStatus = previousStateEmpty ? "rebuilt" : "external-projection-accepted";
22128
22623
  }
22129
- const replay = await this.localStore.rebuildArchitectureLedgerCurrentState(scope);
22624
+ const replayScope = rebuildStatus === "external-projection-proposed" ? authorityScope : scope;
22625
+ const replay = await this.localStore.rebuildArchitectureLedgerCurrentState(replayScope);
22130
22626
  const drift = compareArchitectureLedgerStateToYaml({
22131
22627
  state: replay.state,
22132
22628
  files: listModelFiles(root),
@@ -22272,6 +22768,9 @@ class ArchctxDaemon {
22272
22768
  return this.running ? this.architectureLedgerContextPort(root) : undefined;
22273
22769
  }
22274
22770
  async architectureLedgerScope(root) {
22771
+ return this.localStore.resolveArchitectureLedgerScope(await this.architectureLedgerGitScope(root));
22772
+ }
22773
+ async architectureLedgerGitScope(root) {
22275
22774
  const session = await this.openSession(root);
22276
22775
  const paths = runtimeStatePaths2(root);
22277
22776
  return {
@@ -22334,7 +22833,7 @@ class ArchctxDaemon {
22334
22833
  cleanup: "remove-run-root"
22335
22834
  }
22336
22835
  };
22337
- if (existsSync12(paths.lockPath) || existsSync12(paths.manifestPath)) {
22836
+ if (existsSync13(paths.lockPath) || existsSync13(paths.manifestPath)) {
22338
22837
  removePathWithRetry(paths.runRoot);
22339
22838
  throw new Error(`developer-review-run-already-active: ${input.challenge.challengeId}`);
22340
22839
  }
@@ -22391,7 +22890,7 @@ class ArchctxDaemon {
22391
22890
  const errors = [];
22392
22891
  if (run.worktree) {
22393
22892
  try {
22394
- const hadWorktree = existsSync12(run.worktree.worktreeRoot);
22893
+ const hadWorktree = existsSync13(run.worktree.worktreeRoot);
22395
22894
  removeDetachedReviewWorktree(run.worktree);
22396
22895
  if (hadWorktree)
22397
22896
  removed.push("worktree");
@@ -22405,7 +22904,7 @@ class ArchctxDaemon {
22405
22904
  ["lock", run.lockPath]
22406
22905
  ]) {
22407
22906
  try {
22408
- const existed = existsSync12(path);
22907
+ const existed = existsSync13(path);
22409
22908
  removePathWithRetry(path);
22410
22909
  if (existed)
22411
22910
  removed.push(kind);
@@ -22425,7 +22924,7 @@ class ArchctxDaemon {
22425
22924
  recoverDeveloperReviewRuns(input) {
22426
22925
  this.assertRunning();
22427
22926
  const sourceRoot = findRepositoryRoot2(input.repositoryRoot);
22428
- const stateDir = input.stateDir ? resolve15(input.stateDir) : defaultDeveloperReviewRunStateDir(sourceRoot);
22927
+ const stateDir = input.stateDir ? resolve16(input.stateDir) : defaultDeveloperReviewRunStateDir(sourceRoot);
22429
22928
  const recovery = {
22430
22929
  schemaVersion: "archcontext.developer-review-run-recovery/v1",
22431
22930
  sourceRoot,
@@ -22434,7 +22933,7 @@ class ArchctxDaemon {
22434
22933
  removedLocks: [],
22435
22934
  skippedActive: []
22436
22935
  };
22437
- if (!existsSync12(stateDir))
22936
+ if (!existsSync13(stateDir))
22438
22937
  return recovery;
22439
22938
  for (const entry of readdirSync8(stateDir).sort()) {
22440
22939
  if (!entry.endsWith(".json"))
@@ -22802,7 +23301,7 @@ class ArchctxDaemon {
22802
23301
  }
22803
23302
  async restoreRepositorySessions() {
22804
23303
  for (const record of await this.localStore.listRepositorySessions()) {
22805
- if (!record.root || !existsSync12(record.root))
23304
+ if (!record.root || !existsSync13(record.root))
22806
23305
  continue;
22807
23306
  if (repositoryFingerprint2(record.root) !== record.repositoryId)
22808
23307
  continue;
@@ -23229,8 +23728,8 @@ class ArchctxRuntimeRpcServer {
23229
23728
  const root = this.options.root ?? process.cwd();
23230
23729
  const connectionPath = this.options.connectionPath ?? defaultDaemonConnectionPath(root);
23231
23730
  const lockPath = this.options.lockPath ?? defaultDaemonLockPath(root);
23232
- mkdirSync7(dirname8(connectionPath), { recursive: true });
23233
- mkdirSync7(dirname8(lockPath), { recursive: true });
23731
+ mkdirSync7(dirname9(connectionPath), { recursive: true });
23732
+ mkdirSync7(dirname9(lockPath), { recursive: true });
23234
23733
  this.lockFd = acquireDaemonLock(lockPath, root);
23235
23734
  const token = this.options.token ?? randomBytes(18).toString("base64url");
23236
23735
  const server = createServer((request, response) => {
@@ -23535,7 +24034,7 @@ function readRuntimeRpcConnectionFile(root = process.cwd()) {
23535
24034
  try {
23536
24035
  if (!isPrivateControlFile(path))
23537
24036
  return;
23538
- const parsed = JSON.parse(readFileSync11(path, "utf8"));
24037
+ const parsed = JSON.parse(readFileSync12(path, "utf8"));
23539
24038
  if (!parsed || typeof parsed !== "object")
23540
24039
  return;
23541
24040
  return {
@@ -23576,7 +24075,7 @@ function readRuntimeRpcConnection(root = process.cwd()) {
23576
24075
  try {
23577
24076
  if (!isPrivateControlFile(path))
23578
24077
  return;
23579
- const parsed = JSON.parse(readFileSync11(path, "utf8"));
24078
+ const parsed = JSON.parse(readFileSync12(path, "utf8"));
23580
24079
  return isValidRuntimeRpcConnection(parsed) ? parsed : undefined;
23581
24080
  } catch {
23582
24081
  return;
@@ -23595,7 +24094,7 @@ function recoverStaleDaemonControlFiles(root = process.cwd(), options = {}) {
23595
24094
  rmSync8(connectionPath, { force: true });
23596
24095
  removed.push(connectionReason);
23597
24096
  }
23598
- if (existsSync12(lockPath) && isStaleLock(lockPath)) {
24097
+ if (existsSync13(lockPath) && isStaleLock(lockPath)) {
23599
24098
  rmSync8(lockPath, { force: true });
23600
24099
  removed.push("stale-lock-file");
23601
24100
  }
@@ -23854,8 +24353,8 @@ function schemaVersionKind(schemaVersion) {
23854
24353
  function createDeveloperReviewRunPaths(input) {
23855
24354
  const safeChallengeId = safeControlFileSegment(input.challengeId);
23856
24355
  const runId = `${safeChallengeId}-${randomBytes(6).toString("hex")}`;
23857
- const stateDir = input.stateDir ? resolve15(input.stateDir) : defaultDeveloperReviewRunStateDir(input.sourceRoot);
23858
- const tempParent = input.tempRoot ? resolve15(input.tempRoot) : tmpdir3();
24356
+ const stateDir = input.stateDir ? resolve16(input.stateDir) : defaultDeveloperReviewRunStateDir(input.sourceRoot);
24357
+ const tempParent = input.tempRoot ? resolve16(input.tempRoot) : tmpdir3();
23859
24358
  mkdirSync7(tempParent, { recursive: true });
23860
24359
  const runRoot = mkdtempSync4(join8(tempParent, `archctx-developer-review-${safeChallengeId.slice(0, 32)}-`));
23861
24360
  return {
@@ -24001,7 +24500,7 @@ function readExactPackageVersion(root, packageName) {
24001
24500
  return;
24002
24501
  }
24003
24502
  function readPackageLockExactVersion(root, packageName) {
24004
- const lock = readJsonFile(resolve15(root, "package-lock.json"));
24503
+ const lock = readJsonFile(resolve16(root, "package-lock.json"));
24005
24504
  if (!lock || typeof lock !== "object" || Array.isArray(lock))
24006
24505
  return;
24007
24506
  const packages = lock.packages;
@@ -24019,7 +24518,7 @@ function readPackageLockExactVersion(root, packageName) {
24019
24518
  return;
24020
24519
  }
24021
24520
  function packageManifestPaths(root) {
24022
- const paths = [resolve15(root, "package.json")];
24521
+ const paths = [resolve16(root, "package.json")];
24023
24522
  const rootManifest = readJsonFile(paths[0]);
24024
24523
  for (const pattern of workspacePatternsFromManifest(rootManifest)) {
24025
24524
  for (const path of expandWorkspacePackageJson(root, pattern))
@@ -24042,15 +24541,15 @@ function expandWorkspacePackageJson(root, pattern) {
24042
24541
  if (pattern.includes("**") || pattern.startsWith("/") || pattern.includes("\\"))
24043
24542
  return [];
24044
24543
  if (!pattern.includes("*")) {
24045
- const path = resolve15(root, pattern, "package.json");
24046
- return existsSync12(path) ? [path] : [];
24544
+ const path = resolve16(root, pattern, "package.json");
24545
+ return existsSync13(path) ? [path] : [];
24047
24546
  }
24048
24547
  if (!pattern.endsWith("/*"))
24049
24548
  return [];
24050
- const base = resolve15(root, pattern.slice(0, -2));
24051
- if (!existsSync12(base) || !statSync6(base).isDirectory())
24549
+ const base = resolve16(root, pattern.slice(0, -2));
24550
+ if (!existsSync13(base) || !statSync6(base).isDirectory())
24052
24551
  return [];
24053
- return readdirSync8(base, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => resolve15(base, entry.name, "package.json")).filter((path) => existsSync12(path));
24552
+ return readdirSync8(base, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => resolve16(base, entry.name, "package.json")).filter((path) => existsSync13(path));
24054
24553
  }
24055
24554
  function exactVersionFromManifest(manifest, packageName) {
24056
24555
  if (!manifest || typeof manifest !== "object" || Array.isArray(manifest))
@@ -24066,10 +24565,10 @@ function exactVersionFromManifest(manifest, packageName) {
24066
24565
  return;
24067
24566
  }
24068
24567
  function readJsonFile(path) {
24069
- if (!existsSync12(path))
24568
+ if (!existsSync13(path))
24070
24569
  return;
24071
24570
  try {
24072
- return JSON.parse(readFileSync11(path, "utf8"));
24571
+ return JSON.parse(readFileSync12(path, "utf8"));
24073
24572
  } catch {
24074
24573
  return;
24075
24574
  }
@@ -24078,15 +24577,15 @@ function isExactPackageVersion(value) {
24078
24577
  return /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(value);
24079
24578
  }
24080
24579
  function readContext7Lockfile(root) {
24081
- const path = resolve15(root, CONTEXT7_LOCKFILE);
24082
- if (!existsSync12(path)) {
24580
+ const path = resolve16(root, CONTEXT7_LOCKFILE);
24581
+ if (!existsSync13(path)) {
24083
24582
  return {
24084
24583
  schemaVersion: CONTEXT7_LOCKFILE_SCHEMA_VERSION,
24085
24584
  provider: "context7",
24086
24585
  libraries: []
24087
24586
  };
24088
24587
  }
24089
- const parsed = JSON.parse(readFileSync11(path, "utf8"));
24588
+ const parsed = JSON.parse(readFileSync12(path, "utf8"));
24090
24589
  if (parsed.schemaVersion !== CONTEXT7_LOCKFILE_SCHEMA_VERSION || parsed.provider !== "context7" || !Array.isArray(parsed.libraries)) {
24091
24590
  throw new Error("Invalid Context7 lockfile");
24092
24591
  }
@@ -24107,13 +24606,13 @@ function upsertContext7Pin(lock, pin) {
24107
24606
  };
24108
24607
  }
24109
24608
  function writeContext7Lockfile(root, lock) {
24110
- writePrivateJson3(resolve15(root, CONTEXT7_LOCKFILE), lock);
24609
+ writePrivateJson3(resolve16(root, CONTEXT7_LOCKFILE), lock);
24111
24610
  }
24112
24611
  function writeDeveloperReviewRunManifest(manifest) {
24113
24612
  writePrivateJson3(manifest.manifestPath, manifest);
24114
24613
  }
24115
24614
  function writePrivateJson3(path, value, flag = "w") {
24116
- mkdirSync7(dirname8(path), { recursive: true });
24615
+ mkdirSync7(dirname9(path), { recursive: true });
24117
24616
  writeFileSync6(path, JSON.stringify(value, null, 2), { mode: 384, flag });
24118
24617
  chmodSync3(path, 384);
24119
24618
  }
@@ -24137,7 +24636,7 @@ function readDeveloperReviewRunManifest(path) {
24137
24636
  }
24138
24637
  function readJsonObject(path) {
24139
24638
  try {
24140
- const parsed = JSON.parse(readFileSync11(path, "utf8"));
24639
+ const parsed = JSON.parse(readFileSync12(path, "utf8"));
24141
24640
  return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : undefined;
24142
24641
  } catch {
24143
24642
  return;
@@ -24328,48 +24827,9 @@ function auditApproveResultPayload(runId, status, totalCount, issuedIssues) {
24328
24827
  issuedIssues
24329
24828
  };
24330
24829
  }
24331
- function writeArchitectureProjectionFiles(root, files) {
24332
- for (const file of files) {
24333
- const absolute = resolve15(root, file.path);
24334
- mkdirSync7(dirname8(absolute), { recursive: true });
24335
- writeFileSync6(absolute, file.body.endsWith(`
24336
- `) ? file.body : `${file.body}
24337
- `, "utf8");
24338
- }
24339
- }
24340
- function replaceArchitectureProjectionFilesForYamlRollback(root, projectedFiles, currentFiles, createdAt) {
24341
- const targetPaths = new Set(projectedFiles.map((file) => file.path));
24342
- const backupBase = `.archcontext/backups/ledger-rollback/${safePathSegment(createdAt)}`;
24343
- const backupRelativePath = uniqueBackupPath(root, backupBase);
24344
- const manifestPath = `${backupRelativePath}/manifest.json`;
24345
- const { backup, manifest } = architectureProjectionRollbackBackup(currentFiles, {
24346
- createdAt,
24347
- path: backupRelativePath,
24348
- manifestPath
24349
- });
24350
- for (const file of currentFiles) {
24351
- const backupPath = join8(backupRelativePath, archContextRelativePath(file.path));
24352
- const absolute = resolve15(root, backupPath);
24353
- mkdirSync7(dirname8(absolute), { recursive: true });
24354
- writeFileSync6(absolute, file.body, "utf8");
24355
- }
24356
- const manifestAbsolute = resolve15(root, manifestPath);
24357
- mkdirSync7(dirname8(manifestAbsolute), { recursive: true });
24358
- writeFileSync6(manifestAbsolute, `${JSON.stringify(manifest, null, 2)}
24359
- `, "utf8");
24360
- const removedPaths = [];
24361
- for (const file of currentFiles) {
24362
- if (targetPaths.has(file.path))
24363
- continue;
24364
- rmSync8(resolve15(root, file.path), { force: true });
24365
- removedPaths.push(file.path);
24366
- }
24367
- writeArchitectureProjectionFiles(root, projectedFiles);
24368
- return {
24369
- backup,
24370
- writtenPaths: projectedFiles.map((file) => file.path),
24371
- removedPaths
24372
- };
24830
+ function expectedFileHash(root, path) {
24831
+ const absolute = resolve16(root, path);
24832
+ return existsSync13(absolute) ? digestJson({ body: readFileSync12(absolute, "utf8") }) : "missing";
24373
24833
  }
24374
24834
  function architectureProjectionRollbackBackup(files, options = {}) {
24375
24835
  const manifest = {
@@ -24396,7 +24856,7 @@ function architectureProjectionRollbackBackup(files, options = {}) {
24396
24856
  function uniqueBackupPath(root, backupBase) {
24397
24857
  let candidate = backupBase;
24398
24858
  let suffix = 2;
24399
- while (existsSync12(resolve15(root, candidate))) {
24859
+ while (existsSync13(resolve16(root, candidate))) {
24400
24860
  candidate = `${backupBase}-${suffix}`;
24401
24861
  suffix += 1;
24402
24862
  }
@@ -24405,7 +24865,7 @@ function uniqueBackupPath(root, backupBase) {
24405
24865
  function uniqueRuntimeBackupPath(backupBase) {
24406
24866
  let candidate = backupBase;
24407
24867
  let suffix = 2;
24408
- while (existsSync12(candidate)) {
24868
+ while (existsSync13(candidate)) {
24409
24869
  candidate = backupBase.replace(/\.sqlite$/, `-${suffix}.sqlite`);
24410
24870
  suffix += 1;
24411
24871
  }
@@ -24450,12 +24910,12 @@ function isValidRuntimeRpcConnection(value) {
24450
24910
  return value.schemaVersion === RUNTIME_RPC_VERSION && value.protocol === "http-loopback" && value.version === 1 && typeof value.url === "string" && value.url.startsWith("http://127.0.0.1:") && typeof value.token === "string" && value.token.length > 0 && typeof value.pid === "number" && typeof value.connectionPath === "string" && typeof value.lockPath === "string";
24451
24911
  }
24452
24912
  function staleConnectionFileReason(path, removeUnhealthyConnection) {
24453
- if (!existsSync12(path))
24913
+ if (!existsSync13(path))
24454
24914
  return;
24455
24915
  try {
24456
24916
  if (!isPrivateControlFile(path))
24457
24917
  return "insecure-connection-file";
24458
- const parsed = JSON.parse(readFileSync11(path, "utf8"));
24918
+ const parsed = JSON.parse(readFileSync12(path, "utf8"));
24459
24919
  if (!isValidRuntimeRpcConnection(parsed))
24460
24920
  return "invalid-connection-file";
24461
24921
  if (!isProcessAlive3(parsed.pid))
@@ -24473,7 +24933,7 @@ function isPrivateControlFile(path) {
24473
24933
  }
24474
24934
  function isStaleLock(lockPath) {
24475
24935
  try {
24476
- const lock = JSON.parse(readFileSync11(lockPath, "utf8"));
24936
+ const lock = JSON.parse(readFileSync12(lockPath, "utf8"));
24477
24937
  if (typeof lock.pid !== "number" || lock.pid <= 0)
24478
24938
  return true;
24479
24939
  return !isProcessAlive3(lock.pid);
@@ -24512,7 +24972,7 @@ function parsePracticeCheckpointBaselineState(state, repositoryId, taskSessionId
24512
24972
  return record;
24513
24973
  }
24514
24974
  function completeTaskProjectionDrift(root) {
24515
- if (!existsSync12(resolve15(root, "docs/architecture/.projection-manifest.json")))
24975
+ if (!existsSync13(resolve16(root, "docs/architecture/.projection-manifest.json")))
24516
24976
  return;
24517
24977
  const loaded = loadArchitectureDocumentationInputs(root);
24518
24978
  const sourceDigest = architectureDocumentationSourceDigest({
@@ -24788,10 +25248,10 @@ init_src();
24788
25248
  // packages/local-runtime/runtime-daemon/src/index.ts
24789
25249
  import { randomBytes as randomBytes2 } from "node:crypto";
24790
25250
  import { execFileSync as execFileSync7 } from "node:child_process";
24791
- import { chmodSync as chmodSync4, closeSync as closeSync6, existsSync as existsSync13, mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync5, openSync as openSync6, readdirSync as readdirSync9, readFileSync as readFileSync12, rmSync as rmSync9, statSync as statSync7, writeFileSync as writeFileSync7 } from "node:fs";
25251
+ import { chmodSync as chmodSync4, closeSync as closeSync6, existsSync as existsSync14, mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync5, openSync as openSync6, readdirSync as readdirSync9, readFileSync as readFileSync13, rmSync as rmSync9, statSync as statSync7, writeFileSync as writeFileSync7 } from "node:fs";
24792
25252
  import { createServer as createServer2 } from "node:http";
24793
25253
  import { tmpdir as tmpdir4 } from "node:os";
24794
- import { dirname as dirname9, join as join9, resolve as resolve16 } from "node:path";
25254
+ import { dirname as dirname10, join as join9, resolve as resolve17 } from "node:path";
24795
25255
  init_src();
24796
25256
  var RUNTIME_AGENT_HOOK_DEFAULT_MAX_QUEUED_JOBS2 = 32;
24797
25257
  var RUNTIME_AGENT_HOOK_DEFAULT_PRIORITY2 = 0;
@@ -24901,7 +25361,8 @@ class ArchitectureLedgerReadModelStore2 {
24901
25361
  return this.fallback.writeChangeSetPreview(changeSet);
24902
25362
  }
24903
25363
  async loadLedgerModel(workspace) {
24904
- const state = await this.localStore.readArchitectureLedgerState(architectureLedgerScopeForWorkspace2(workspace));
25364
+ const scope = await this.localStore.resolveArchitectureLedgerScope(architectureLedgerScopeForWorkspace2(workspace));
25365
+ const state = await this.localStore.readArchitectureLedgerState(scope);
24905
25366
  const projectedFiles = projectArchitectureLedgerStateToYamlFiles(state).map((file) => ({
24906
25367
  path: file.path,
24907
25368
  body: file.body,
@@ -24985,7 +25446,7 @@ class ArchctxDaemon2 {
24985
25446
  this.readModelStore = new ArchitectureLedgerReadModelStore2(this.modelStore, this.localStore, this.architectureLedger);
24986
25447
  this.changeSetEngine = deps.changeSetEngine ?? new ChangeSetEngine({
24987
25448
  modelStore: this.modelStore,
24988
- projection: { rebuildGeneratedProjection },
25449
+ projection: { planGeneratedProjection },
24989
25450
  journal: this.localStore
24990
25451
  });
24991
25452
  this.devicePrivateKeySigner = deps.devicePrivateKeySigner;
@@ -25934,10 +26395,10 @@ class ArchctxDaemon2 {
25934
26395
  return errorEnvelope("practices.waive", "AC_SCHEMA_INVALID", error instanceof Error ? error.message : String(error));
25935
26396
  }
25936
26397
  const path = `.archcontext/waivers/${waiverId}.json`;
25937
- const absolute = resolve16(session.workspace.root, path);
26398
+ const absolute = resolve17(session.workspace.root, path);
25938
26399
  const body = `${JSON.stringify(waiver, null, 2)}
25939
26400
  `;
25940
- const expectedHash = existsSync13(absolute) ? digestJson({ body: readFileSync12(absolute, "utf8") }) : "missing";
26401
+ const expectedHash = existsSync14(absolute) ? digestJson({ body: readFileSync13(absolute, "utf8") }) : "missing";
25941
26402
  const draft = this.changeSetEngine.plan({
25942
26403
  id: input.id ?? `changeset.practice-waiver-${waiverId.replace(/[^A-Za-z0-9_-]/g, "-")}`,
25943
26404
  base: {
@@ -26276,6 +26737,16 @@ class ArchctxDaemon2 {
26276
26737
  const draft = this.changesets.get(input.id);
26277
26738
  if (!draft)
26278
26739
  throw new Error(`Unknown ChangeSet: ${input.id}`);
26740
+ if (draft.base.headSha !== session.workspace.headSha)
26741
+ throw new Error("ChangeSet HEAD changed before apply");
26742
+ if (draft.base.worktreeDigest !== current)
26743
+ throw new Error("ChangeSet worktree digest changed before apply");
26744
+ const currentModel = await this.readModelStore.validateModel(session.workspace);
26745
+ if (!currentModel.valid) {
26746
+ throw new Error(`ChangeSet base model is invalid: ${currentModel.errors.join("; ") || "unknown validation error"}`);
26747
+ }
26748
+ if (draft.base.modelDigest !== currentModel.modelDigest)
26749
+ throw new Error("ChangeSet model digest changed before apply");
26279
26750
  const approved = input.approved ? this.changeSetEngine.approve(draft) : draft;
26280
26751
  let ledgerAppend;
26281
26752
  const writesLedger = architectureLedgerWriteAppendsEvents2(this.architectureLedger.writeMode);
@@ -26292,6 +26763,7 @@ class ArchctxDaemon2 {
26292
26763
  relationCount: appended.relationCount,
26293
26764
  constraintCount: appended.constraintCount
26294
26765
  };
26766
+ return { journalCommitted: Boolean(journalId) };
26295
26767
  } : undefined
26296
26768
  });
26297
26769
  return okEnvelope("apply_update", {
@@ -26325,14 +26797,72 @@ class ArchctxDaemon2 {
26325
26797
  });
26326
26798
  if (journalId)
26327
26799
  await this.localStore.recordChangeSetLedgerPlan(journalId, { event: plan.event });
26328
- const result = await this.localStore.appendArchitectureEvents({
26329
- writer: "runtime-daemon",
26330
- events: [plan.event]
26331
- });
26332
- if (journalId)
26333
- await this.localStore.recordChangeSetLedgerAppend(journalId, { result });
26800
+ const appendInput = { writer: "runtime-daemon", events: [plan.event] };
26801
+ const result = journalId ? await this.localStore.appendArchitectureEventsAndCommitChangeSet(journalId, appendInput) : await this.localStore.appendArchitectureEvents(appendInput);
26334
26802
  return result;
26335
26803
  }
26804
+ async applyArchitectureProjectionChangeSet(root, input) {
26805
+ const session = await this.openSession(root);
26806
+ const model = await this.modelStore.validateModel(session.workspace);
26807
+ if (!model.valid)
26808
+ throw new Error(`Architecture projection ChangeSet base model is invalid: ${model.errors.join("; ")}`);
26809
+ const projectionFiles = input.files.map((file) => ({
26810
+ path: file.path,
26811
+ body: file.body.endsWith(`
26812
+ `) ? file.body : `${file.body}
26813
+ `,
26814
+ expectedHash: expectedFileHash2(root, file.path)
26815
+ }));
26816
+ const operations = [
26817
+ ...projectionFiles.length > 0 ? [{ op: "render_projection", expectedHash: "missing", projectionFiles }] : [],
26818
+ ...input.removedPaths.map((path) => ({
26819
+ op: "delete_entity",
26820
+ path,
26821
+ expectedHash: expectedFileHash2(root, path)
26822
+ }))
26823
+ ];
26824
+ const draft = this.changeSetEngine.approve(this.changeSetEngine.plan({
26825
+ id: input.id,
26826
+ base: {
26827
+ headSha: session.workspace.headSha,
26828
+ worktreeDigest: session.snapshot.worktreeDigest,
26829
+ modelDigest: model.modelDigest
26830
+ },
26831
+ reason: { taskSessionId: input.id },
26832
+ operations
26833
+ }));
26834
+ await this.changeSetEngine.apply(root, draft, { approved: true });
26835
+ }
26836
+ async applyArchitectureProjectionRollbackChangeSet(root, projectedFiles, currentFiles, createdAt) {
26837
+ const targetPaths = new Set(projectedFiles.map((file) => file.path));
26838
+ const backupBase = `.archcontext/backups/ledger-rollback/${safePathSegment2(createdAt)}`;
26839
+ const backupRelativePath = uniqueBackupPath2(root, backupBase);
26840
+ const manifestPath = `${backupRelativePath}/manifest.json`;
26841
+ const { backup, manifest } = architectureProjectionRollbackBackup2(currentFiles, {
26842
+ createdAt,
26843
+ path: backupRelativePath,
26844
+ manifestPath
26845
+ });
26846
+ const removedPaths = currentFiles.filter((file) => !targetPaths.has(file.path)).map((file) => file.path);
26847
+ await this.applyArchitectureProjectionChangeSet(root, {
26848
+ id: `changeset.ledger-rollback-${shortDigest4(digestJson({ createdAt, projectionDigest: architectureLedgerProjectionDigest(projectedFiles) }))}`,
26849
+ files: [
26850
+ ...currentFiles.map((file) => ({
26851
+ path: join9(backupRelativePath, archContextRelativePath2(file.path)),
26852
+ body: file.body
26853
+ })),
26854
+ { path: manifestPath, body: `${JSON.stringify(manifest, null, 2)}
26855
+ ` },
26856
+ ...projectedFiles.map(({ path, body }) => ({ path, body }))
26857
+ ],
26858
+ removedPaths
26859
+ });
26860
+ return {
26861
+ backup,
26862
+ writtenPaths: projectedFiles.map((file) => file.path),
26863
+ removedPaths
26864
+ };
26865
+ }
26336
26866
  async ledgerState(root) {
26337
26867
  this.assertRunning();
26338
26868
  return okEnvelope("ledger.state", await this.architectureLedgerReadback(root));
@@ -26686,8 +27216,13 @@ class ArchctxDaemon2 {
26686
27216
  const scope = await this.architectureLedgerScope(root);
26687
27217
  const state = await this.localStore.readArchitectureLedgerState(scope);
26688
27218
  const projectedFiles = projectArchitectureLedgerStateToYamlFiles(state);
26689
- if (writes)
26690
- writeArchitectureProjectionFiles2(root, projectedFiles);
27219
+ if (writes) {
27220
+ await this.applyArchitectureProjectionChangeSet(root, {
27221
+ id: `changeset.ledger-project-${shortDigest4(architectureLedgerProjectionDigest(projectedFiles))}`,
27222
+ files: projectedFiles.map(({ path, body }) => ({ path, body })),
27223
+ removedPaths: []
27224
+ });
27225
+ }
26691
27226
  const drift = compareArchitectureLedgerStateToYaml({
26692
27227
  state,
26693
27228
  files: listModelFiles(root),
@@ -26837,7 +27372,7 @@ class ArchctxDaemon2 {
26837
27372
  const projectedFiles = projectArchitectureLedgerStateToYamlFiles(state);
26838
27373
  const currentManagedFiles = listModelFiles(root).filter((file) => isArchitectureLedgerManagedModelPath2(file.path));
26839
27374
  const backupPlan = architectureProjectionRollbackBackup2(currentManagedFiles);
26840
- const writeResult = writes ? replaceArchitectureProjectionFilesForYamlRollback2(root, projectedFiles, currentManagedFiles, this.clock()) : { backup: backupPlan.backup, writtenPaths: [], removedPaths: [] };
27375
+ const writeResult = writes ? await this.applyArchitectureProjectionRollbackChangeSet(root, projectedFiles, currentManagedFiles, this.clock()) : { backup: backupPlan.backup, writtenPaths: [], removedPaths: [] };
26841
27376
  const drift = compareArchitectureLedgerStateToYaml({
26842
27377
  state,
26843
27378
  files: listModelFiles(root),
@@ -26876,9 +27411,12 @@ class ArchctxDaemon2 {
26876
27411
  return errorEnvelope("ledger.rebuild", "AC_SCHEMA_INVALID", "ledger rebuild currently requires --from-git");
26877
27412
  return this.withWriter(async () => {
26878
27413
  this.assertFreshWorktree(root, input.expectedWorktreeDigest, "ledger rebuild --from-git");
26879
- const scope = await this.architectureLedgerScope(root);
27414
+ const scope = await this.architectureLedgerGitScope(root);
26880
27415
  const files = listModelFiles(root);
26881
- const previousState = await this.localStore.readArchitectureLedgerState(scope);
27416
+ const exactReplay = await this.localStore.replayArchitectureLedger(scope);
27417
+ const exactScopeHasEvents = exactReplay.events.length > 0;
27418
+ const authorityScope = exactScopeHasEvents ? scope : await this.localStore.resolveLatestArchitectureLedgerScope(scope);
27419
+ const previousState = exactScopeHasEvents ? exactReplay.state : await this.localStore.readArchitectureLedgerState(authorityScope);
26882
27420
  const previousGraphDigest = architectureLedgerStateDigest(previousState);
26883
27421
  const rebuildCommand = input.acceptExternalProjection ? "archctx ledger rebuild --from-git --accept-external-projection" : "archctx ledger rebuild --from-git";
26884
27422
  const plan = planYamlToArchitectureLedgerRebuild({
@@ -26888,12 +27426,18 @@ class ArchctxDaemon2 {
26888
27426
  command: rebuildCommand,
26889
27427
  previousState
26890
27428
  });
27429
+ const importPlan = planYamlToArchitectureLedgerImport({
27430
+ ...scope,
27431
+ files,
27432
+ createdAt: this.clock(),
27433
+ command: rebuildCommand
27434
+ });
26891
27435
  if (plan.unsupportedFiles.length > 0) {
26892
27436
  return errorEnvelope("ledger.rebuild", "AC_SCHEMA_INVALID", "ledger rebuild requires supported YAML model files");
26893
27437
  }
26894
27438
  const cursor = architectureLedgerGitCursorFromPlan({ ...scope, plan });
26895
27439
  const previousCursor = await this.localStore.readArchitectureLedgerSourceCursor({
26896
- ...scope,
27440
+ ...authorityScope,
26897
27441
  cursorId: ARCHITECTURE_LEDGER_GIT_CURSOR_ID
26898
27442
  });
26899
27443
  const cursorChanged = previousCursor?.cursorDigest !== cursor.cursorDigest;
@@ -26909,7 +27453,13 @@ class ArchctxDaemon2 {
26909
27453
  let rebuildStatus = "unchanged";
26910
27454
  let proposedExternalProjectionChange;
26911
27455
  if (previousGraphDigest === plan.graphDigest) {
26912
- if (cursorChanged) {
27456
+ if (!exactScopeHasEvents) {
27457
+ append = await this.localStore.appendArchitectureEvents({
27458
+ writer: "runtime-daemon",
27459
+ events: [importPlan.event]
27460
+ });
27461
+ rebuildStatus = isEmptyArchitectureLedgerState2(previousState) ? "rebuilt" : "cursor-refreshed";
27462
+ } else if (cursorChanged) {
26913
27463
  const cursorPlan = planGitCursorRefreshToArchitectureLedgerEvent({
26914
27464
  ...scope,
26915
27465
  cursor,
@@ -26925,7 +27475,7 @@ class ArchctxDaemon2 {
26925
27475
  }
26926
27476
  } else if (!previousStateEmpty && !input.acceptExternalProjection) {
26927
27477
  const proposal = planExternalProjectionChangeToArchitectureLedgerEvent({
26928
- ...scope,
27478
+ ...authorityScope,
26929
27479
  files,
26930
27480
  createdAt: this.clock(),
26931
27481
  command: rebuildCommand,
@@ -26948,11 +27498,12 @@ class ArchctxDaemon2 {
26948
27498
  } else {
26949
27499
  append = await this.localStore.appendArchitectureEvents({
26950
27500
  writer: "runtime-daemon",
26951
- events: [plan.event]
27501
+ events: [exactScopeHasEvents ? plan.event : importPlan.event]
26952
27502
  });
26953
27503
  rebuildStatus = previousStateEmpty ? "rebuilt" : "external-projection-accepted";
26954
27504
  }
26955
- const replay = await this.localStore.rebuildArchitectureLedgerCurrentState(scope);
27505
+ const replayScope = rebuildStatus === "external-projection-proposed" ? authorityScope : scope;
27506
+ const replay = await this.localStore.rebuildArchitectureLedgerCurrentState(replayScope);
26956
27507
  const drift = compareArchitectureLedgerStateToYaml({
26957
27508
  state: replay.state,
26958
27509
  files: listModelFiles(root),
@@ -27098,6 +27649,9 @@ class ArchctxDaemon2 {
27098
27649
  return this.running ? this.architectureLedgerContextPort(root) : undefined;
27099
27650
  }
27100
27651
  async architectureLedgerScope(root) {
27652
+ return this.localStore.resolveArchitectureLedgerScope(await this.architectureLedgerGitScope(root));
27653
+ }
27654
+ async architectureLedgerGitScope(root) {
27101
27655
  const session = await this.openSession(root);
27102
27656
  const paths = runtimeStatePaths2(root);
27103
27657
  return {
@@ -27160,7 +27714,7 @@ class ArchctxDaemon2 {
27160
27714
  cleanup: "remove-run-root"
27161
27715
  }
27162
27716
  };
27163
- if (existsSync13(paths.lockPath) || existsSync13(paths.manifestPath)) {
27717
+ if (existsSync14(paths.lockPath) || existsSync14(paths.manifestPath)) {
27164
27718
  removePathWithRetry(paths.runRoot);
27165
27719
  throw new Error(`developer-review-run-already-active: ${input.challenge.challengeId}`);
27166
27720
  }
@@ -27217,7 +27771,7 @@ class ArchctxDaemon2 {
27217
27771
  const errors = [];
27218
27772
  if (run.worktree) {
27219
27773
  try {
27220
- const hadWorktree = existsSync13(run.worktree.worktreeRoot);
27774
+ const hadWorktree = existsSync14(run.worktree.worktreeRoot);
27221
27775
  removeDetachedReviewWorktree(run.worktree);
27222
27776
  if (hadWorktree)
27223
27777
  removed.push("worktree");
@@ -27231,7 +27785,7 @@ class ArchctxDaemon2 {
27231
27785
  ["lock", run.lockPath]
27232
27786
  ]) {
27233
27787
  try {
27234
- const existed = existsSync13(path);
27788
+ const existed = existsSync14(path);
27235
27789
  removePathWithRetry(path);
27236
27790
  if (existed)
27237
27791
  removed.push(kind);
@@ -27251,7 +27805,7 @@ class ArchctxDaemon2 {
27251
27805
  recoverDeveloperReviewRuns(input) {
27252
27806
  this.assertRunning();
27253
27807
  const sourceRoot = findRepositoryRoot2(input.repositoryRoot);
27254
- const stateDir = input.stateDir ? resolve16(input.stateDir) : defaultDeveloperReviewRunStateDir2(sourceRoot);
27808
+ const stateDir = input.stateDir ? resolve17(input.stateDir) : defaultDeveloperReviewRunStateDir2(sourceRoot);
27255
27809
  const recovery = {
27256
27810
  schemaVersion: "archcontext.developer-review-run-recovery/v1",
27257
27811
  sourceRoot,
@@ -27260,7 +27814,7 @@ class ArchctxDaemon2 {
27260
27814
  removedLocks: [],
27261
27815
  skippedActive: []
27262
27816
  };
27263
- if (!existsSync13(stateDir))
27817
+ if (!existsSync14(stateDir))
27264
27818
  return recovery;
27265
27819
  for (const entry of readdirSync9(stateDir).sort()) {
27266
27820
  if (!entry.endsWith(".json"))
@@ -27628,7 +28182,7 @@ class ArchctxDaemon2 {
27628
28182
  }
27629
28183
  async restoreRepositorySessions() {
27630
28184
  for (const record of await this.localStore.listRepositorySessions()) {
27631
- if (!record.root || !existsSync13(record.root))
28185
+ if (!record.root || !existsSync14(record.root))
27632
28186
  continue;
27633
28187
  if (repositoryFingerprint2(record.root) !== record.repositoryId)
27634
28188
  continue;
@@ -28082,7 +28636,7 @@ function readRuntimeRpcConnection2(root = process.cwd()) {
28082
28636
  try {
28083
28637
  if (!isPrivateControlFile2(path))
28084
28638
  return;
28085
- const parsed = JSON.parse(readFileSync12(path, "utf8"));
28639
+ const parsed = JSON.parse(readFileSync13(path, "utf8"));
28086
28640
  return isValidRuntimeRpcConnection2(parsed) ? parsed : undefined;
28087
28641
  } catch {
28088
28642
  return;
@@ -28345,8 +28899,8 @@ function schemaVersionKind2(schemaVersion) {
28345
28899
  function createDeveloperReviewRunPaths2(input) {
28346
28900
  const safeChallengeId = safeControlFileSegment2(input.challengeId);
28347
28901
  const runId = `${safeChallengeId}-${randomBytes2(6).toString("hex")}`;
28348
- const stateDir = input.stateDir ? resolve16(input.stateDir) : defaultDeveloperReviewRunStateDir2(input.sourceRoot);
28349
- const tempParent = input.tempRoot ? resolve16(input.tempRoot) : tmpdir4();
28902
+ const stateDir = input.stateDir ? resolve17(input.stateDir) : defaultDeveloperReviewRunStateDir2(input.sourceRoot);
28903
+ const tempParent = input.tempRoot ? resolve17(input.tempRoot) : tmpdir4();
28350
28904
  mkdirSync8(tempParent, { recursive: true });
28351
28905
  const runRoot = mkdtempSync5(join9(tempParent, `archctx-developer-review-${safeChallengeId.slice(0, 32)}-`));
28352
28906
  return {
@@ -28492,7 +29046,7 @@ function readExactPackageVersion2(root, packageName) {
28492
29046
  return;
28493
29047
  }
28494
29048
  function readPackageLockExactVersion2(root, packageName) {
28495
- const lock = readJsonFile2(resolve16(root, "package-lock.json"));
29049
+ const lock = readJsonFile2(resolve17(root, "package-lock.json"));
28496
29050
  if (!lock || typeof lock !== "object" || Array.isArray(lock))
28497
29051
  return;
28498
29052
  const packages = lock.packages;
@@ -28510,7 +29064,7 @@ function readPackageLockExactVersion2(root, packageName) {
28510
29064
  return;
28511
29065
  }
28512
29066
  function packageManifestPaths2(root) {
28513
- const paths = [resolve16(root, "package.json")];
29067
+ const paths = [resolve17(root, "package.json")];
28514
29068
  const rootManifest = readJsonFile2(paths[0]);
28515
29069
  for (const pattern of workspacePatternsFromManifest2(rootManifest)) {
28516
29070
  for (const path of expandWorkspacePackageJson2(root, pattern))
@@ -28533,15 +29087,15 @@ function expandWorkspacePackageJson2(root, pattern) {
28533
29087
  if (pattern.includes("**") || pattern.startsWith("/") || pattern.includes("\\"))
28534
29088
  return [];
28535
29089
  if (!pattern.includes("*")) {
28536
- const path = resolve16(root, pattern, "package.json");
28537
- return existsSync13(path) ? [path] : [];
29090
+ const path = resolve17(root, pattern, "package.json");
29091
+ return existsSync14(path) ? [path] : [];
28538
29092
  }
28539
29093
  if (!pattern.endsWith("/*"))
28540
29094
  return [];
28541
- const base = resolve16(root, pattern.slice(0, -2));
28542
- if (!existsSync13(base) || !statSync7(base).isDirectory())
29095
+ const base = resolve17(root, pattern.slice(0, -2));
29096
+ if (!existsSync14(base) || !statSync7(base).isDirectory())
28543
29097
  return [];
28544
- return readdirSync9(base, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => resolve16(base, entry.name, "package.json")).filter((path) => existsSync13(path));
29098
+ return readdirSync9(base, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => resolve17(base, entry.name, "package.json")).filter((path) => existsSync14(path));
28545
29099
  }
28546
29100
  function exactVersionFromManifest2(manifest, packageName) {
28547
29101
  if (!manifest || typeof manifest !== "object" || Array.isArray(manifest))
@@ -28557,10 +29111,10 @@ function exactVersionFromManifest2(manifest, packageName) {
28557
29111
  return;
28558
29112
  }
28559
29113
  function readJsonFile2(path) {
28560
- if (!existsSync13(path))
29114
+ if (!existsSync14(path))
28561
29115
  return;
28562
29116
  try {
28563
- return JSON.parse(readFileSync12(path, "utf8"));
29117
+ return JSON.parse(readFileSync13(path, "utf8"));
28564
29118
  } catch {
28565
29119
  return;
28566
29120
  }
@@ -28569,15 +29123,15 @@ function isExactPackageVersion2(value) {
28569
29123
  return /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(value);
28570
29124
  }
28571
29125
  function readContext7Lockfile2(root) {
28572
- const path = resolve16(root, CONTEXT7_LOCKFILE2);
28573
- if (!existsSync13(path)) {
29126
+ const path = resolve17(root, CONTEXT7_LOCKFILE2);
29127
+ if (!existsSync14(path)) {
28574
29128
  return {
28575
29129
  schemaVersion: CONTEXT7_LOCKFILE_SCHEMA_VERSION,
28576
29130
  provider: "context7",
28577
29131
  libraries: []
28578
29132
  };
28579
29133
  }
28580
- const parsed = JSON.parse(readFileSync12(path, "utf8"));
29134
+ const parsed = JSON.parse(readFileSync13(path, "utf8"));
28581
29135
  if (parsed.schemaVersion !== CONTEXT7_LOCKFILE_SCHEMA_VERSION || parsed.provider !== "context7" || !Array.isArray(parsed.libraries)) {
28582
29136
  throw new Error("Invalid Context7 lockfile");
28583
29137
  }
@@ -28598,13 +29152,13 @@ function upsertContext7Pin2(lock, pin) {
28598
29152
  };
28599
29153
  }
28600
29154
  function writeContext7Lockfile2(root, lock) {
28601
- writePrivateJson4(resolve16(root, CONTEXT7_LOCKFILE2), lock);
29155
+ writePrivateJson4(resolve17(root, CONTEXT7_LOCKFILE2), lock);
28602
29156
  }
28603
29157
  function writeDeveloperReviewRunManifest2(manifest) {
28604
29158
  writePrivateJson4(manifest.manifestPath, manifest);
28605
29159
  }
28606
29160
  function writePrivateJson4(path, value, flag = "w") {
28607
- mkdirSync8(dirname9(path), { recursive: true });
29161
+ mkdirSync8(dirname10(path), { recursive: true });
28608
29162
  writeFileSync7(path, JSON.stringify(value, null, 2), { mode: 384, flag });
28609
29163
  chmodSync4(path, 384);
28610
29164
  }
@@ -28628,7 +29182,7 @@ function readDeveloperReviewRunManifest2(path) {
28628
29182
  }
28629
29183
  function readJsonObject2(path) {
28630
29184
  try {
28631
- const parsed = JSON.parse(readFileSync12(path, "utf8"));
29185
+ const parsed = JSON.parse(readFileSync13(path, "utf8"));
28632
29186
  return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : undefined;
28633
29187
  } catch {
28634
29188
  return;
@@ -28799,48 +29353,9 @@ function auditApproveResultPayload2(runId, status, totalCount, issuedIssues) {
28799
29353
  issuedIssues
28800
29354
  };
28801
29355
  }
28802
- function writeArchitectureProjectionFiles2(root, files) {
28803
- for (const file of files) {
28804
- const absolute = resolve16(root, file.path);
28805
- mkdirSync8(dirname9(absolute), { recursive: true });
28806
- writeFileSync7(absolute, file.body.endsWith(`
28807
- `) ? file.body : `${file.body}
28808
- `, "utf8");
28809
- }
28810
- }
28811
- function replaceArchitectureProjectionFilesForYamlRollback2(root, projectedFiles, currentFiles, createdAt) {
28812
- const targetPaths = new Set(projectedFiles.map((file) => file.path));
28813
- const backupBase = `.archcontext/backups/ledger-rollback/${safePathSegment2(createdAt)}`;
28814
- const backupRelativePath = uniqueBackupPath2(root, backupBase);
28815
- const manifestPath = `${backupRelativePath}/manifest.json`;
28816
- const { backup, manifest } = architectureProjectionRollbackBackup2(currentFiles, {
28817
- createdAt,
28818
- path: backupRelativePath,
28819
- manifestPath
28820
- });
28821
- for (const file of currentFiles) {
28822
- const backupPath = join9(backupRelativePath, archContextRelativePath2(file.path));
28823
- const absolute = resolve16(root, backupPath);
28824
- mkdirSync8(dirname9(absolute), { recursive: true });
28825
- writeFileSync7(absolute, file.body, "utf8");
28826
- }
28827
- const manifestAbsolute = resolve16(root, manifestPath);
28828
- mkdirSync8(dirname9(manifestAbsolute), { recursive: true });
28829
- writeFileSync7(manifestAbsolute, `${JSON.stringify(manifest, null, 2)}
28830
- `, "utf8");
28831
- const removedPaths = [];
28832
- for (const file of currentFiles) {
28833
- if (targetPaths.has(file.path))
28834
- continue;
28835
- rmSync9(resolve16(root, file.path), { force: true });
28836
- removedPaths.push(file.path);
28837
- }
28838
- writeArchitectureProjectionFiles2(root, projectedFiles);
28839
- return {
28840
- backup,
28841
- writtenPaths: projectedFiles.map((file) => file.path),
28842
- removedPaths
28843
- };
29356
+ function expectedFileHash2(root, path) {
29357
+ const absolute = resolve17(root, path);
29358
+ return existsSync14(absolute) ? digestJson({ body: readFileSync13(absolute, "utf8") }) : "missing";
28844
29359
  }
28845
29360
  function architectureProjectionRollbackBackup2(files, options = {}) {
28846
29361
  const manifest = {
@@ -28867,7 +29382,7 @@ function architectureProjectionRollbackBackup2(files, options = {}) {
28867
29382
  function uniqueBackupPath2(root, backupBase) {
28868
29383
  let candidate = backupBase;
28869
29384
  let suffix = 2;
28870
- while (existsSync13(resolve16(root, candidate))) {
29385
+ while (existsSync14(resolve17(root, candidate))) {
28871
29386
  candidate = `${backupBase}-${suffix}`;
28872
29387
  suffix += 1;
28873
29388
  }
@@ -28876,7 +29391,7 @@ function uniqueBackupPath2(root, backupBase) {
28876
29391
  function uniqueRuntimeBackupPath2(backupBase) {
28877
29392
  let candidate = backupBase;
28878
29393
  let suffix = 2;
28879
- while (existsSync13(candidate)) {
29394
+ while (existsSync14(candidate)) {
28880
29395
  candidate = backupBase.replace(/\.sqlite$/, `-${suffix}.sqlite`);
28881
29396
  suffix += 1;
28882
29397
  }
@@ -28938,7 +29453,7 @@ function parsePracticeCheckpointBaselineState2(state, repositoryId, taskSessionI
28938
29453
  return record;
28939
29454
  }
28940
29455
  function completeTaskProjectionDrift2(root) {
28941
- if (!existsSync13(resolve16(root, "docs/architecture/.projection-manifest.json")))
29456
+ if (!existsSync14(resolve17(root, "docs/architecture/.projection-manifest.json")))
28942
29457
  return;
28943
29458
  const loaded = loadArchitectureDocumentationInputs(root);
28944
29459
  const sourceDigest = architectureDocumentationSourceDigest({
@@ -29524,7 +30039,7 @@ async function runCliUnchecked(command2 = "help", args2 = [], cwd, deps = {}) {
29524
30039
  const subcommand = args2[0] ?? "list";
29525
30040
  if (subcommand === "add") {
29526
30041
  const root = readFlag(args2, "--root") ?? cwd;
29527
- if (resolve17(root) !== resolve17(cwd)) {
30042
+ if (resolve18(root) !== resolve18(cwd)) {
29528
30043
  return errorEnvelope("repo.add", "AC_CAPABILITY_UNSUPPORTED", "Multi-repo architecture context is outside the Local Core MVP; run archctx inside one Git repository.");
29529
30044
  }
29530
30045
  const daemon2 = await runtime();
@@ -30197,37 +30712,10 @@ function buildArchitectureDocsProjection(root, generatedAt) {
30197
30712
  sourceDigest,
30198
30713
  generatedAt
30199
30714
  });
30200
- const manifestBody = `${JSON.stringify({
30201
- schemaVersion: "archcontext.architecture-docs-projection-manifest/v1",
30202
- rendererVersion: plan.rendererVersion,
30203
- sourceDigest: plan.sourceDigest,
30204
- projectionDigest: plan.projectionDigest,
30205
- targetCount: plan.targets.length,
30206
- fileCount: plan.files.length,
30207
- targets: plan.targets.map((target) => ({
30208
- targetId: target.targetId,
30209
- type: target.type,
30210
- scope: target.scope,
30211
- path: target.path,
30212
- ownership: target.ownership,
30213
- rendererVersion: target.rendererVersion,
30214
- format: target.format,
30215
- sourceDigest: target.sourceDigest,
30216
- outputDigest: target.outputDigest
30217
- }))
30218
- }, null, 2)}
30219
- `;
30220
- const manifest = {
30221
- path: "docs/architecture/.projection-manifest.json",
30222
- body: manifestBody,
30223
- digest: digestJson({ path: "docs/architecture/.projection-manifest.json", body: manifestBody }),
30224
- target: plan.targets[0],
30225
- generatedBodyDigest: digestJson({ body: manifestBody })
30226
- };
30227
30715
  return {
30228
30716
  plan,
30229
- manifest,
30230
- files: [...plan.files, manifest]
30717
+ manifest: plan.manifest,
30718
+ files: [...plan.files, plan.manifest]
30231
30719
  };
30232
30720
  }
30233
30721
  function architectureDocsRenderProjectionOperation(root, files) {
@@ -30242,8 +30730,8 @@ function architectureDocsRenderProjectionOperation(root, files) {
30242
30730
  };
30243
30731
  }
30244
30732
  function currentBodyHash(root, path) {
30245
- const absolute = resolve17(root, path);
30246
- return existsSync14(absolute) ? digestJson({ body: readFileSync13(absolute, "utf8") }) : "missing";
30733
+ const absolute = resolve18(root, path);
30734
+ return existsSync15(absolute) ? digestJson({ body: readFileSync14(absolute, "utf8") }) : "missing";
30247
30735
  }
30248
30736
  async function runPracticesCommand(args2, cwd, daemon) {
30249
30737
  const subcommand = args2[0] ?? "list";
@@ -30590,12 +31078,12 @@ function auditManifestGateRoot(cwd) {
30590
31078
  }
30591
31079
  }
30592
31080
  function auditGithubIssuesEnabled(cwd) {
30593
- const manifestPath = resolve17(auditManifestGateRoot(cwd), ".archcontext/manifest.yaml");
30594
- if (!existsSync14(manifestPath))
31081
+ const manifestPath = resolve18(auditManifestGateRoot(cwd), ".archcontext/manifest.yaml");
31082
+ if (!existsSync15(manifestPath))
30595
31083
  return false;
30596
31084
  let raw;
30597
31085
  try {
30598
- raw = readFileSync13(manifestPath, "utf8");
31086
+ raw = readFileSync14(manifestPath, "utf8");
30599
31087
  } catch {
30600
31088
  return false;
30601
31089
  }
@@ -31190,11 +31678,11 @@ async function digestReviewChallenge(challenge) {
31190
31678
  }
31191
31679
  function defaultGithubDeveloperReviewStatePath(cwd, pullRequestNumber) {
31192
31680
  const suffix = pullRequestNumber ? `github-developer-review-pr-${pullRequestNumber}.json` : "github-developer-review.json";
31193
- return join10(dirname10(defaultDaemonConnectionPath(cwd)), suffix);
31681
+ return join10(dirname11(defaultDaemonConnectionPath(cwd)), suffix);
31194
31682
  }
31195
31683
  async function writeGithubDeveloperReviewState(cwd, state) {
31196
31684
  const path = defaultGithubDeveloperReviewStatePath(cwd, state.challenge.pullRequestNumber);
31197
- mkdirSync9(dirname10(path), { recursive: true });
31685
+ mkdirSync9(dirname11(path), { recursive: true });
31198
31686
  const serialized = `${JSON.stringify(state, null, 2)}
31199
31687
  `;
31200
31688
  assertNoCliSecretMaterial(serialized);
@@ -31204,10 +31692,10 @@ async function writeGithubDeveloperReviewState(cwd, state) {
31204
31692
  return { state, path };
31205
31693
  }
31206
31694
  function readGithubDeveloperReviewState(path) {
31207
- if (!existsSync14(path))
31695
+ if (!existsSync15(path))
31208
31696
  return;
31209
31697
  try {
31210
- const parsed = JSON.parse(readFileSync13(path, "utf8"));
31698
+ const parsed = JSON.parse(readFileSync14(path, "utf8"));
31211
31699
  if (parsed.schemaVersion !== "archcontext.github-developer-review-state/v1")
31212
31700
  return;
31213
31701
  if (!parsed.challenge || typeof parsed.challenge.pullRequestNumber !== "number")
@@ -31253,13 +31741,13 @@ function sanitizeGithubDeveloperReviewState(state, statePath) {
31253
31741
  return data;
31254
31742
  }
31255
31743
  function defaultGithubConnectionPath(cwd) {
31256
- return join10(dirname10(defaultDaemonConnectionPath(cwd)), "github-connection.json");
31744
+ return join10(dirname11(defaultDaemonConnectionPath(cwd)), "github-connection.json");
31257
31745
  }
31258
31746
  function readGithubConnection(path) {
31259
- if (!existsSync14(path))
31747
+ if (!existsSync15(path))
31260
31748
  return;
31261
31749
  try {
31262
- const parsed = JSON.parse(readFileSync13(path, "utf8"));
31750
+ const parsed = JSON.parse(readFileSync14(path, "utf8"));
31263
31751
  if (parsed.schemaVersion !== "archcontext.github-connection/v1" || parsed.status !== "connected")
31264
31752
  return;
31265
31753
  return parsed;
@@ -31268,7 +31756,7 @@ function readGithubConnection(path) {
31268
31756
  }
31269
31757
  }
31270
31758
  function writeGithubConnection(path, record) {
31271
- mkdirSync9(dirname10(path), { recursive: true });
31759
+ mkdirSync9(dirname11(path), { recursive: true });
31272
31760
  const serialized = `${JSON.stringify(record, null, 2)}
31273
31761
  `;
31274
31762
  assertNoCliSecretMaterial(serialized);
@@ -31303,7 +31791,7 @@ async function readReviewChallengeV2Arg(args2, cwd, commandName = "github verify
31303
31791
  if (!inline && !challengePath)
31304
31792
  return { ok: false, message: `${commandName} requires --challenge-json or --challenge-path` };
31305
31793
  try {
31306
- const raw = inline ?? readFileSync13(resolve17(cwd, challengePath), "utf8");
31794
+ const raw = inline ?? readFileSync14(resolve18(cwd, challengePath), "utf8");
31307
31795
  const parsed = JSON.parse(raw);
31308
31796
  const attestation = await Promise.resolve().then(() => (init_src7(), exports_src3));
31309
31797
  const assertReviewChallengeV23 = attestation.assertReviewChallengeV2;
@@ -31770,16 +32258,16 @@ function doctorSqlite(cwd) {
31770
32258
  const legacyLocalStore = inspectLegacyLocalStoreMigration(cwd);
31771
32259
  return {
31772
32260
  path,
31773
- exists: existsSync14(path),
32261
+ exists: existsSync15(path),
31774
32262
  migrations: productVersionManifest().runtime.sqliteMigrations,
31775
32263
  legacyPath: paths.legacyLocalStorePath,
31776
- legacyExists: existsSync14(paths.legacyLocalStorePath),
32264
+ legacyExists: existsSync15(paths.legacyLocalStorePath),
31777
32265
  legacyLocalStore
31778
32266
  };
31779
32267
  }
31780
32268
  function doctorPermissions(cwd) {
31781
32269
  const paths = runtimeStatePaths(cwd);
31782
- const controlDir = dirname10(defaultDaemonConnectionPath(cwd));
32270
+ const controlDir = dirname11(defaultDaemonConnectionPath(cwd));
31783
32271
  return {
31784
32272
  workspace: pathAccess(cwd),
31785
32273
  stateRoot: pathAccess(paths.stateRoot),
@@ -31804,7 +32292,7 @@ function runtimePathsReport(cwd) {
31804
32292
  };
31805
32293
  }
31806
32294
  function pathAccess(path) {
31807
- const exists = existsSync14(path);
32295
+ const exists = existsSync15(path);
31808
32296
  return {
31809
32297
  path,
31810
32298
  exists,
@@ -31815,7 +32303,7 @@ function pathAccess(path) {
31815
32303
  }
31816
32304
  function canAccess(path, mode) {
31817
32305
  try {
31818
- accessSync(path, mode);
32306
+ accessSync2(path, mode);
31819
32307
  return true;
31820
32308
  } catch {
31821
32309
  return false;
@@ -32024,7 +32512,7 @@ async function startBackgroundDaemon(args2, cwd) {
32024
32512
  }
32025
32513
  const recovery = discovered.recovery;
32026
32514
  const connectionPath = defaultDaemonConnectionPath(cwd);
32027
- const controlDir = dirname10(connectionPath);
32515
+ const controlDir = dirname11(connectionPath);
32028
32516
  const logPath = join10(controlDir, "archctxd.log");
32029
32517
  mkdirSync9(controlDir, { recursive: true });
32030
32518
  const logFd = openSync7(logPath, "a", 384);
@@ -32223,11 +32711,11 @@ async function waitForDaemonReady(cwd, timeoutMs, shouldStop) {
32223
32711
  return;
32224
32712
  }
32225
32713
  function sleep(ms) {
32226
- return new Promise((resolve18) => setTimeout(resolve18, ms));
32714
+ return new Promise((resolve19) => setTimeout(resolve19, ms));
32227
32715
  }
32228
32716
  function readFileTail(path, maxBytes = 4096) {
32229
32717
  try {
32230
- const content = readFileSync13(path, "utf8");
32718
+ const content = readFileSync14(path, "utf8");
32231
32719
  return content.slice(Math.max(0, content.length - maxBytes)).replace(/\s+/g, " ").trim();
32232
32720
  } catch {
32233
32721
  return "<unavailable>";
@@ -32236,8 +32724,8 @@ function readFileTail(path, maxBytes = 4096) {
32236
32724
  async function runForegroundDaemon(cwd, args2) {
32237
32725
  const daemon = await createStartedProductionDaemon({ root: cwd });
32238
32726
  let resolveStopped;
32239
- const stopped = new Promise((resolve18) => {
32240
- resolveStopped = resolve18;
32727
+ const stopped = new Promise((resolve19) => {
32728
+ resolveStopped = resolve19;
32241
32729
  });
32242
32730
  const idleTimeoutFlag = readFlag(args2, "--idle-timeout-ms");
32243
32731
  const server = new ArchctxRuntimeRpcServer(daemon, {