archctx 0.6.1 → 0.6.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/assets/catalog.yaml +2 -2
- package/bin/archctx.mjs +593 -501
- package/package.json +1 -1
- package/schemas/repo/architecture-flow.schema.json +2 -2
package/bin/archctx.mjs
CHANGED
|
@@ -1331,7 +1331,7 @@ function productVersionManifest() {
|
|
|
1331
1331
|
}
|
|
1332
1332
|
};
|
|
1333
1333
|
}
|
|
1334
|
-
var ARCHCONTEXT_PRODUCT_NAME = "archctx", ARCHCONTEXT_PRODUCT_VERSION = "0.6.
|
|
1334
|
+
var ARCHCONTEXT_PRODUCT_NAME = "archctx", ARCHCONTEXT_PRODUCT_VERSION = "0.6.3", ARCHCONTEXT_PACKAGE_MANAGER = "bun@1.4.0", ARCHCONTEXT_NODE_RANGE = ">=22.22 <26", LOCAL_RUNTIME_RPC_SCHEMA_VERSION = "archcontext.runtime-rpc/v1", ARCHCONTEXT_SCHEMA_SET_VERSION = "2026-07-11.explorer-view-v2";
|
|
1335
1335
|
|
|
1336
1336
|
// packages/contracts/src/refactor.ts
|
|
1337
1337
|
function refactorProposalDigest(proposal) {
|
|
@@ -18114,6 +18114,14 @@ ${wrapped}`;
|
|
|
18114
18114
|
}
|
|
18115
18115
|
return `${existing.slice(0, region.start)}${wrapped}${existing.slice(region.end)}`;
|
|
18116
18116
|
}
|
|
18117
|
+
function comparableExpectedManifestDigest(expected, actual) {
|
|
18118
|
+
const baseline = actual && typeof actual === "object" && !Array.isArray(actual) ? actual.semanticBaseline : undefined;
|
|
18119
|
+
if (!baseline || typeof baseline !== "object" || Array.isArray(baseline) || "evidence" in baseline)
|
|
18120
|
+
return expected.digest;
|
|
18121
|
+
const value = JSON.parse(expected.body);
|
|
18122
|
+
const { evidence: _evidence, ...semanticBaseline } = value.semanticBaseline;
|
|
18123
|
+
return digestJson({ ...value, semanticBaseline });
|
|
18124
|
+
}
|
|
18117
18125
|
function architectureDocumentationProjectionDrift(input) {
|
|
18118
18126
|
const existingByPath = new Map(input.existingFiles.map((file) => [file.path, file]));
|
|
18119
18127
|
const expectedByPath = new Map(input.expectedFiles.map((file) => [file.path, file]));
|
|
@@ -18130,7 +18138,7 @@ function architectureDocumentationProjectionDrift(input) {
|
|
|
18130
18138
|
try {
|
|
18131
18139
|
const parsed = JSON.parse(existingManifest.body);
|
|
18132
18140
|
const actualDigest = digestJson(parsed);
|
|
18133
|
-
if (actualDigest !== input.expectedManifest
|
|
18141
|
+
if (actualDigest !== comparableExpectedManifestDigest(input.expectedManifest, parsed)) {
|
|
18134
18142
|
diffs.push({
|
|
18135
18143
|
path: input.expectedManifest.path,
|
|
18136
18144
|
reasonCode: "projection-manifest-stale",
|
|
@@ -19981,8 +19989,8 @@ function localEgressStatus(env = process.env, live = {}) {
|
|
|
19981
19989
|
|
|
19982
19990
|
// packages/surfaces/cli/src/main.ts
|
|
19983
19991
|
import { spawn as spawn2, spawnSync as spawnSync3 } from "child_process";
|
|
19984
|
-
import { accessSync as accessSync2, closeSync as closeSync10, constants as constants2, existsSync as
|
|
19985
|
-
import { dirname as dirname15, join as join18, resolve as
|
|
19992
|
+
import { accessSync as accessSync2, closeSync as closeSync10, constants as constants2, existsSync as existsSync26, mkdirSync as mkdirSync13, openSync as openSync10, readFileSync as readFileSync27, rmSync as rmSync16, statSync as statSync10 } from "fs";
|
|
19993
|
+
import { dirname as dirname15, join as join18, resolve as resolve27 } from "path";
|
|
19986
19994
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
19987
19995
|
|
|
19988
19996
|
// packages/contracts/src/index.ts
|
|
@@ -46938,6 +46946,12 @@ function recommendationActorKind(input) {
|
|
|
46938
46946
|
return "cli";
|
|
46939
46947
|
}
|
|
46940
46948
|
|
|
46949
|
+
// packages/local-runtime/runtime-daemon/src/changeset-authority.ts
|
|
46950
|
+
import { randomBytes as randomBytes5 } from "node:crypto";
|
|
46951
|
+
import { existsSync as existsSync24, readFileSync as readFileSync26 } from "node:fs";
|
|
46952
|
+
import { resolve as resolve25 } from "node:path";
|
|
46953
|
+
init_src();
|
|
46954
|
+
|
|
46941
46955
|
// packages/local-runtime/runtime-daemon/src/committed-change-acceptance.ts
|
|
46942
46956
|
import { execFileSync as execFileSync10 } from "node:child_process";
|
|
46943
46957
|
import { closeSync as closeSync8, constants as fsConstants3, fstatSync as fstatSync3, lstatSync as lstatSync11, openSync as openSync9, readdirSync as readdirSync12, readFileSync as readFileSync25 } from "node:fs";
|
|
@@ -47369,12 +47383,546 @@ function sameStrings(left, right) {
|
|
|
47369
47383
|
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
47370
47384
|
}
|
|
47371
47385
|
|
|
47386
|
+
// packages/local-runtime/runtime-daemon/src/changeset-authority.ts
|
|
47387
|
+
class RuntimeUpdateInputError extends Error {
|
|
47388
|
+
}
|
|
47389
|
+
function decodeRuntimeWorktreeDigestProfile(value, field) {
|
|
47390
|
+
switch (value) {
|
|
47391
|
+
case "repository":
|
|
47392
|
+
case "architecture-documentation-projection":
|
|
47393
|
+
return value;
|
|
47394
|
+
default:
|
|
47395
|
+
throw new RuntimeUpdateInputError(`${field} must be repository or architecture-documentation-projection`);
|
|
47396
|
+
}
|
|
47397
|
+
}
|
|
47398
|
+
function decodeRuntimePlanUpdateInput(value) {
|
|
47399
|
+
const input = runtimeUpdateInputRecord(value, "plan_update input");
|
|
47400
|
+
if (typeof input.id !== "string" || input.id.length === 0) {
|
|
47401
|
+
throw new RuntimeUpdateInputError("plan_update id must be a non-empty string");
|
|
47402
|
+
}
|
|
47403
|
+
if (!Array.isArray(input.operations)) {
|
|
47404
|
+
throw new RuntimeUpdateInputError("plan_update operations must be an array");
|
|
47405
|
+
}
|
|
47406
|
+
if (input.approvalChannel !== undefined && input.approvalChannel !== "mcp") {
|
|
47407
|
+
throw new RuntimeUpdateInputError("plan_update approvalChannel must be mcp");
|
|
47408
|
+
}
|
|
47409
|
+
let worktreeDigestPrecondition;
|
|
47410
|
+
if (input.worktreeDigestPrecondition !== undefined) {
|
|
47411
|
+
const precondition = runtimeUpdateInputRecord(input.worktreeDigestPrecondition, "plan_update worktreeDigestPrecondition");
|
|
47412
|
+
const profile = decodeRuntimeWorktreeDigestProfile(precondition.profile, "plan_update worktreeDigestPrecondition.profile");
|
|
47413
|
+
if (profile !== "architecture-documentation-projection") {
|
|
47414
|
+
throw new RuntimeUpdateInputError("plan_update worktreeDigestPrecondition.profile must be architecture-documentation-projection");
|
|
47415
|
+
}
|
|
47416
|
+
if (typeof precondition.expectedDigest !== "string" || precondition.expectedDigest.length === 0) {
|
|
47417
|
+
throw new RuntimeUpdateInputError("plan_update worktreeDigestPrecondition.expectedDigest must be a non-empty string");
|
|
47418
|
+
}
|
|
47419
|
+
worktreeDigestPrecondition = { profile, expectedDigest: precondition.expectedDigest };
|
|
47420
|
+
}
|
|
47421
|
+
return {
|
|
47422
|
+
id: input.id,
|
|
47423
|
+
operations: input.operations,
|
|
47424
|
+
...input.approvalChannel === "mcp" ? { approvalChannel: "mcp" } : {},
|
|
47425
|
+
...input.reason === undefined ? {} : { reason: input.reason },
|
|
47426
|
+
...worktreeDigestPrecondition === undefined ? {} : { worktreeDigestPrecondition }
|
|
47427
|
+
};
|
|
47428
|
+
}
|
|
47429
|
+
function decodeRuntimeApplyUpdateInput(value) {
|
|
47430
|
+
const input = runtimeUpdateInputRecord(value, "apply_update input");
|
|
47431
|
+
if (typeof input.id !== "string" || input.id.length === 0) {
|
|
47432
|
+
throw new RuntimeUpdateInputError("apply_update id must be a non-empty string");
|
|
47433
|
+
}
|
|
47434
|
+
if (typeof input.approved !== "boolean") {
|
|
47435
|
+
throw new RuntimeUpdateInputError("apply_update approved must be a boolean");
|
|
47436
|
+
}
|
|
47437
|
+
if (typeof input.expectedWorktreeDigest !== "string" || input.expectedWorktreeDigest.length === 0) {
|
|
47438
|
+
throw new RuntimeUpdateInputError("apply_update expectedWorktreeDigest must be a non-empty string");
|
|
47439
|
+
}
|
|
47440
|
+
const worktreeDigestProfile = input.worktreeDigestProfile === undefined ? undefined : decodeRuntimeWorktreeDigestProfile(input.worktreeDigestProfile, "apply_update worktreeDigestProfile");
|
|
47441
|
+
return {
|
|
47442
|
+
id: input.id,
|
|
47443
|
+
approved: input.approved,
|
|
47444
|
+
expectedWorktreeDigest: input.expectedWorktreeDigest,
|
|
47445
|
+
...worktreeDigestProfile === undefined ? {} : { worktreeDigestProfile },
|
|
47446
|
+
...input.projectionApplyReceipt === undefined ? {} : { projectionApplyReceipt: input.projectionApplyReceipt }
|
|
47447
|
+
};
|
|
47448
|
+
}
|
|
47449
|
+
function runtimeUpdateInputRecord(value, field) {
|
|
47450
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
47451
|
+
throw new RuntimeUpdateInputError(`${field} must be an object`);
|
|
47452
|
+
}
|
|
47453
|
+
return value;
|
|
47454
|
+
}
|
|
47455
|
+
|
|
47456
|
+
class ChangeSetAuthorityService {
|
|
47457
|
+
context;
|
|
47458
|
+
readModelStore;
|
|
47459
|
+
localStore;
|
|
47460
|
+
changeSetEngine;
|
|
47461
|
+
architectureLedger;
|
|
47462
|
+
changesets = new Map;
|
|
47463
|
+
changeSetRoots = new Map;
|
|
47464
|
+
mcpChangeSets = new Set;
|
|
47465
|
+
mcpApprovals = new Map;
|
|
47466
|
+
changeSetWorktreeDigestProfiles = new Map;
|
|
47467
|
+
constructor(context) {
|
|
47468
|
+
this.context = context;
|
|
47469
|
+
this.readModelStore = context.readModelStore;
|
|
47470
|
+
this.localStore = context.localStore;
|
|
47471
|
+
this.changeSetEngine = context.changeSetEngine;
|
|
47472
|
+
this.architectureLedger = context.architectureLedger;
|
|
47473
|
+
}
|
|
47474
|
+
clearApprovals() {
|
|
47475
|
+
this.mcpApprovals.clear();
|
|
47476
|
+
}
|
|
47477
|
+
async planPracticeWaiver(root, input) {
|
|
47478
|
+
this.assertRunning();
|
|
47479
|
+
const session = await this.openSession(root);
|
|
47480
|
+
const model = await this.readModelStore.validateModel(session.workspace);
|
|
47481
|
+
let ownerRegistry;
|
|
47482
|
+
try {
|
|
47483
|
+
ownerRegistry = loadPracticeWaiverOwnerRegistry(session.workspace.root);
|
|
47484
|
+
} catch (error) {
|
|
47485
|
+
return errorEnvelope("practices.waive", "AC_SCHEMA_INVALID", error instanceof Error ? error.message : String(error));
|
|
47486
|
+
}
|
|
47487
|
+
const waiver = {
|
|
47488
|
+
schemaVersion: "archcontext.practice-waiver/v1",
|
|
47489
|
+
practiceId: input.practiceId,
|
|
47490
|
+
...input.checkId === undefined ? {} : { checkId: input.checkId },
|
|
47491
|
+
scope: {
|
|
47492
|
+
...input.pathGlobs && input.pathGlobs.length > 0 ? { pathGlobs: input.pathGlobs } : {},
|
|
47493
|
+
...input.subjects && input.subjects.length > 0 ? { subjects: input.subjects } : {}
|
|
47494
|
+
},
|
|
47495
|
+
owner: input.owner,
|
|
47496
|
+
reason: input.reason,
|
|
47497
|
+
createdAt: input.createdAt ?? this.clock(),
|
|
47498
|
+
reviewAt: input.reviewAt,
|
|
47499
|
+
expiresAt: input.expiresAt,
|
|
47500
|
+
evidenceDigest: input.evidenceDigest
|
|
47501
|
+
};
|
|
47502
|
+
let waiverId;
|
|
47503
|
+
try {
|
|
47504
|
+
validatePracticeWaiver(waiver, "practice waiver input", { allowedOwners: ownerRegistry.owners });
|
|
47505
|
+
waiverId = safePracticeWaiverId(input.waiverId, waiver);
|
|
47506
|
+
} catch (error) {
|
|
47507
|
+
return errorEnvelope("practices.waive", "AC_SCHEMA_INVALID", error instanceof Error ? error.message : String(error));
|
|
47508
|
+
}
|
|
47509
|
+
const path = `.archcontext/waivers/${waiverId}.json`;
|
|
47510
|
+
const absolute = resolve25(session.workspace.root, path);
|
|
47511
|
+
const body = `${JSON.stringify(waiver, null, 2)}
|
|
47512
|
+
`;
|
|
47513
|
+
const expectedHash = existsSync24(absolute) ? digestJson({ body: readFileSync26(absolute, "utf8") }) : "missing";
|
|
47514
|
+
const draft = this.changeSetEngine.plan({
|
|
47515
|
+
id: input.id ?? `changeset.practice-waiver-${waiverId.replace(/[^A-Za-z0-9_-]/g, "-")}`,
|
|
47516
|
+
base: {
|
|
47517
|
+
headSha: session.workspace.headSha,
|
|
47518
|
+
worktreeDigest: session.snapshot.worktreeDigest,
|
|
47519
|
+
modelDigest: model.modelDigest
|
|
47520
|
+
},
|
|
47521
|
+
reason: { taskSessionId: input.taskSessionId ?? "task_runtime" },
|
|
47522
|
+
operations: [{ op: "write_waiver", path, expectedHash, body }]
|
|
47523
|
+
});
|
|
47524
|
+
this.changesets.set(draft.id, draft);
|
|
47525
|
+
this.changeSetRoots.set(draft.id, canonicalRepositoryRoot(root));
|
|
47526
|
+
this.changeSetWorktreeDigestProfiles.set(draft.id, "repository");
|
|
47527
|
+
this.mcpChangeSets.delete(draft.id);
|
|
47528
|
+
return okEnvelope("practices.waive", {
|
|
47529
|
+
schemaVersion: "archcontext.practice-waiver-plan/v1",
|
|
47530
|
+
waiver,
|
|
47531
|
+
waiverDigest: digestJson(waiver),
|
|
47532
|
+
ownerRegistry,
|
|
47533
|
+
path,
|
|
47534
|
+
draft,
|
|
47535
|
+
preview: this.changeSetEngine.preview(session.workspace.root, draft)
|
|
47536
|
+
});
|
|
47537
|
+
}
|
|
47538
|
+
async planUpdate(root, rawInput) {
|
|
47539
|
+
this.assertRunning();
|
|
47540
|
+
let input;
|
|
47541
|
+
try {
|
|
47542
|
+
input = decodeRuntimePlanUpdateInput(rawInput);
|
|
47543
|
+
} catch (error) {
|
|
47544
|
+
if (error instanceof RuntimeUpdateInputError) {
|
|
47545
|
+
return errorEnvelope("plan_update", "AC_SCHEMA_INVALID", error.message);
|
|
47546
|
+
}
|
|
47547
|
+
throw error;
|
|
47548
|
+
}
|
|
47549
|
+
const session = await this.openSession(root);
|
|
47550
|
+
const model = await this.readModelStore.validateModel(session.workspace);
|
|
47551
|
+
const worktreeDigestProfile = input.worktreeDigestPrecondition?.profile ?? "repository";
|
|
47552
|
+
const worktreeDigest = runtimeWorktreeDigest(root, worktreeDigestProfile);
|
|
47553
|
+
if (input.worktreeDigestPrecondition && input.worktreeDigestPrecondition.expectedDigest !== worktreeDigest) {
|
|
47554
|
+
throw new Error("Worktree digest changed before plan");
|
|
47555
|
+
}
|
|
47556
|
+
const draft = this.changeSetEngine.plan({
|
|
47557
|
+
id: input.id,
|
|
47558
|
+
base: {
|
|
47559
|
+
headSha: session.workspace.headSha,
|
|
47560
|
+
worktreeDigest,
|
|
47561
|
+
modelDigest: model.modelDigest
|
|
47562
|
+
},
|
|
47563
|
+
reason: input.reason ?? { taskSessionId: "task_runtime" },
|
|
47564
|
+
operations: input.operations
|
|
47565
|
+
});
|
|
47566
|
+
this.changesets.set(draft.id, draft);
|
|
47567
|
+
this.changeSetRoots.set(draft.id, canonicalRepositoryRoot(root));
|
|
47568
|
+
this.changeSetWorktreeDigestProfiles.set(draft.id, worktreeDigestProfile);
|
|
47569
|
+
if (input.approvalChannel === "mcp")
|
|
47570
|
+
this.mcpChangeSets.add(draft.id);
|
|
47571
|
+
else
|
|
47572
|
+
this.mcpChangeSets.delete(draft.id);
|
|
47573
|
+
return okEnvelope("plan_update", {
|
|
47574
|
+
draft,
|
|
47575
|
+
changeSetDigest: digestJson(draft),
|
|
47576
|
+
preview: this.changeSetEngine.preview(root, draft)
|
|
47577
|
+
});
|
|
47578
|
+
}
|
|
47579
|
+
async approveMcpProjection(root, input) {
|
|
47580
|
+
this.assertRunning();
|
|
47581
|
+
try {
|
|
47582
|
+
validateProjectionInvocation(input);
|
|
47583
|
+
if (!projectionInvocationWrites(input))
|
|
47584
|
+
throw new Error("Projection approval requires an apply, adopt or recover invocation");
|
|
47585
|
+
assertProjectionInvocationSnapshot(root, input);
|
|
47586
|
+
} catch (error) {
|
|
47587
|
+
return errorEnvelope("projection.approve", "AC_PRECONDITION_FAILED", error instanceof Error ? error.message : String(error));
|
|
47588
|
+
}
|
|
47589
|
+
const now = Date.parse(this.clock());
|
|
47590
|
+
if (!Number.isFinite(now))
|
|
47591
|
+
return errorEnvelope("projection.approve", "AC_PRECONDITION_FAILED", "Approval clock is invalid");
|
|
47592
|
+
for (const [key, grant] of this.mcpApprovals)
|
|
47593
|
+
if (grant.expiresAt <= now)
|
|
47594
|
+
this.mcpApprovals.delete(key);
|
|
47595
|
+
if (this.mcpApprovals.size >= 256)
|
|
47596
|
+
return errorEnvelope("projection.approve", "AC_PRECONDITION_FAILED", "Too many outstanding approvals; consume an approval or wait for expiry");
|
|
47597
|
+
const approvalToken = randomBytes5(32).toString("hex");
|
|
47598
|
+
const expiresAt = now + 5 * 60000;
|
|
47599
|
+
this.mcpApprovals.set(digestJson(approvalToken), { scope: "projection", root: canonicalRepositoryRoot(root), invocationDigest: digestJson(input), expiresAt });
|
|
47600
|
+
return okEnvelope("projection.approve", { approvalToken, expiresAt: new Date(expiresAt).toISOString() });
|
|
47601
|
+
}
|
|
47602
|
+
async mcpProjection(root, input, approvalToken) {
|
|
47603
|
+
this.assertRunning();
|
|
47604
|
+
try {
|
|
47605
|
+
validateProjectionInvocation(input);
|
|
47606
|
+
} catch (error) {
|
|
47607
|
+
return errorEnvelope("projection", "AC_SCHEMA_INVALID", error instanceof Error ? error.message : String(error));
|
|
47608
|
+
}
|
|
47609
|
+
if (projectionInvocationWrites(input)) {
|
|
47610
|
+
const denied = () => errorEnvelope("projection", "AC_USER_CONFIRMATION_REQUIRED", "A fresh one-time token from archctx projection approve is required");
|
|
47611
|
+
if (typeof approvalToken !== "string" || !/^[a-f0-9]{64}$/.test(approvalToken))
|
|
47612
|
+
return denied();
|
|
47613
|
+
const key = digestJson(approvalToken);
|
|
47614
|
+
const grant = this.mcpApprovals.get(key);
|
|
47615
|
+
this.mcpApprovals.delete(key);
|
|
47616
|
+
const now = Date.parse(this.clock());
|
|
47617
|
+
if (!grant || grant.scope !== "projection" || !Number.isFinite(now) || grant.expiresAt <= now || grant.root !== canonicalRepositoryRoot(root) || grant.invocationDigest !== digestJson(input))
|
|
47618
|
+
return denied();
|
|
47619
|
+
}
|
|
47620
|
+
return this.projection(root, input);
|
|
47621
|
+
}
|
|
47622
|
+
async approveMcpUpdate(root, input) {
|
|
47623
|
+
this.assertRunning();
|
|
47624
|
+
const draft = this.changesets.get(input?.id);
|
|
47625
|
+
const canonicalRoot = canonicalRepositoryRoot(root);
|
|
47626
|
+
const now = Date.parse(this.clock());
|
|
47627
|
+
if (!draft || this.changeSetRoots.get(input.id) !== canonicalRoot || this.changeSetWorktreeDigestProfiles.get(input.id) !== "repository" || input.expectedChangeSetDigest !== digestJson(draft) || input.expectedWorktreeDigest !== draft.base.worktreeDigest || input.expectedWorktreeDigest !== runtimeWorktreeDigest(root, "repository") || !Number.isFinite(now)) {
|
|
47628
|
+
return errorEnvelope("approve_mcp_update", "AC_PRECONDITION_FAILED", "Approval must match the current ChangeSet preview and repository");
|
|
47629
|
+
}
|
|
47630
|
+
for (const [key, grant] of this.mcpApprovals)
|
|
47631
|
+
if (grant.expiresAt <= now)
|
|
47632
|
+
this.mcpApprovals.delete(key);
|
|
47633
|
+
if (this.mcpApprovals.size >= 256)
|
|
47634
|
+
return errorEnvelope("approve_mcp_update", "AC_PRECONDITION_FAILED", "Too many outstanding approvals; consume an approval or wait for expiry");
|
|
47635
|
+
const approvalToken = randomBytes5(32).toString("hex");
|
|
47636
|
+
const expiresAt = now + 5 * 60000;
|
|
47637
|
+
this.mcpApprovals.set(digestJson(approvalToken), {
|
|
47638
|
+
scope: "changeset",
|
|
47639
|
+
root: canonicalRoot,
|
|
47640
|
+
id: input.id,
|
|
47641
|
+
draftDigest: input.expectedChangeSetDigest,
|
|
47642
|
+
worktreeDigest: input.expectedWorktreeDigest,
|
|
47643
|
+
expiresAt
|
|
47644
|
+
});
|
|
47645
|
+
return okEnvelope("approve_mcp_update", { approvalToken, expiresAt: new Date(expiresAt).toISOString() });
|
|
47646
|
+
}
|
|
47647
|
+
async applyMcpUpdate(root, input) {
|
|
47648
|
+
this.assertRunning();
|
|
47649
|
+
const denied = () => errorEnvelope("apply_update", "AC_USER_CONFIRMATION_REQUIRED", "A fresh one-time token from archctx approve is required");
|
|
47650
|
+
if (typeof input?.approvalToken !== "string" || !/^[a-f0-9]{64}$/.test(input.approvalToken))
|
|
47651
|
+
return denied();
|
|
47652
|
+
const key = digestJson(input.approvalToken);
|
|
47653
|
+
const grant = this.mcpApprovals.get(key);
|
|
47654
|
+
if (!grant)
|
|
47655
|
+
return denied();
|
|
47656
|
+
this.mcpApprovals.delete(key);
|
|
47657
|
+
const draft = this.changesets.get(input.id);
|
|
47658
|
+
const now = Date.parse(this.clock());
|
|
47659
|
+
if (grant.scope !== "changeset" || !Number.isFinite(now) || grant.expiresAt <= now || grant.root !== canonicalRepositoryRoot(root) || grant.id !== input.id || grant.worktreeDigest !== input.expectedWorktreeDigest || !draft || grant.draftDigest !== digestJson(draft))
|
|
47660
|
+
return denied();
|
|
47661
|
+
return this.applyAuthorizedUpdate(root, { id: input.id, expectedWorktreeDigest: input.expectedWorktreeDigest, approved: true });
|
|
47662
|
+
}
|
|
47663
|
+
async applyUpdate(root, rawInput) {
|
|
47664
|
+
this.assertRunning();
|
|
47665
|
+
if (this.mcpChangeSets.has(rawInput?.id)) {
|
|
47666
|
+
return errorEnvelope("apply_update", "AC_USER_CONFIRMATION_REQUIRED", "MCP ChangeSets require a one-time approval token through applyMcpUpdate");
|
|
47667
|
+
}
|
|
47668
|
+
return this.applyAuthorizedUpdate(root, rawInput);
|
|
47669
|
+
}
|
|
47670
|
+
async applyAuthorizedUpdate(root, rawInput) {
|
|
47671
|
+
this.assertRunning();
|
|
47672
|
+
let input;
|
|
47673
|
+
try {
|
|
47674
|
+
input = decodeRuntimeApplyUpdateInput(rawInput);
|
|
47675
|
+
} catch (error) {
|
|
47676
|
+
if (error instanceof RuntimeUpdateInputError) {
|
|
47677
|
+
return errorEnvelope("apply_update", "AC_SCHEMA_INVALID", error.message);
|
|
47678
|
+
}
|
|
47679
|
+
throw error;
|
|
47680
|
+
}
|
|
47681
|
+
return this.withWriter(async () => {
|
|
47682
|
+
const draft = this.changesets.get(input.id);
|
|
47683
|
+
if (!draft)
|
|
47684
|
+
throw new Error(`Unknown ChangeSet: ${input.id}`);
|
|
47685
|
+
const plannedProfile = this.changeSetWorktreeDigestProfiles.get(input.id);
|
|
47686
|
+
if (!plannedProfile)
|
|
47687
|
+
throw new Error("ChangeSet worktree digest profile missing before apply");
|
|
47688
|
+
const requestedProfile = input.worktreeDigestProfile ?? "repository";
|
|
47689
|
+
if (requestedProfile !== plannedProfile)
|
|
47690
|
+
throw new Error("ChangeSet worktree digest profile changed before apply");
|
|
47691
|
+
const current = runtimeWorktreeDigest(root, plannedProfile);
|
|
47692
|
+
if (current !== input.expectedWorktreeDigest)
|
|
47693
|
+
throw new Error("Worktree digest changed before apply");
|
|
47694
|
+
const session = await this.openSession(root);
|
|
47695
|
+
if (draft.base.headSha !== session.workspace.headSha)
|
|
47696
|
+
throw new Error("ChangeSet HEAD changed before apply");
|
|
47697
|
+
if (draft.base.worktreeDigest !== current)
|
|
47698
|
+
throw new Error("ChangeSet worktree digest changed before apply");
|
|
47699
|
+
const currentModel = await this.readModelStore.validateModel(session.workspace);
|
|
47700
|
+
const baseErrors = baseModelBlockingErrors(currentModel);
|
|
47701
|
+
if (baseErrors.length > 0) {
|
|
47702
|
+
throw new Error(`ChangeSet base model is invalid: ${baseErrors.join("; ")}`);
|
|
47703
|
+
}
|
|
47704
|
+
if (draft.base.modelDigest !== currentModel.modelDigest)
|
|
47705
|
+
throw new Error("ChangeSet model digest changed before apply");
|
|
47706
|
+
if (input.projectionApplyReceipt && await this.localStore.inspectProjectionApplyReceipt(input.projectionApplyReceipt.identity.lookupKey)) {
|
|
47707
|
+
return errorEnvelope("apply_update", "AC_PRECONDITION_FAILED", "committed projection receipt requires explicit projection recover");
|
|
47708
|
+
}
|
|
47709
|
+
const approved = input.approved ? this.changeSetEngine.approve(draft) : draft;
|
|
47710
|
+
const transitionBase = captureModelTransitionBase(root, approved);
|
|
47711
|
+
let ledgerAppend;
|
|
47712
|
+
let appliedJournalId;
|
|
47713
|
+
const writesLedger = architectureLedgerWriteAppendsEvents(this.architectureLedger.writeMode);
|
|
47714
|
+
const result = await this.changeSetEngine.apply(root, approved, {
|
|
47715
|
+
approved: input.approved,
|
|
47716
|
+
afterModelValidatedBeforeCommit: async ({ journalId }) => {
|
|
47717
|
+
appliedJournalId = journalId;
|
|
47718
|
+
if (transitionBase && journalId)
|
|
47719
|
+
await recordModelTransitionEvidence(this.localStore, root, journalId, transitionBase);
|
|
47720
|
+
if (journalId && this.context.projectionPlannedDrafts.has(draft))
|
|
47721
|
+
await this.localStore.recordChangeSetProjectionOwner(journalId);
|
|
47722
|
+
if (input.projectionApplyReceipt) {
|
|
47723
|
+
if (!journalId)
|
|
47724
|
+
throw new Error("projection apply receipt requires a durable ChangeSet journal");
|
|
47725
|
+
await this.localStore.recordProjectionApplyReceipt(journalId, input.projectionApplyReceipt);
|
|
47726
|
+
}
|
|
47727
|
+
if (writesLedger) {
|
|
47728
|
+
const appended = await this.appendAppliedChangeSetToArchitectureLedger(root, session, approved, journalId);
|
|
47729
|
+
ledgerAppend = {
|
|
47730
|
+
status: "appended",
|
|
47731
|
+
appendedEventCount: appended.appendedEvents.length,
|
|
47732
|
+
duplicateEventCount: appended.duplicateEvents.length,
|
|
47733
|
+
graphDigest: appended.graphDigest,
|
|
47734
|
+
entityCount: appended.entityCount,
|
|
47735
|
+
relationCount: appended.relationCount,
|
|
47736
|
+
constraintCount: appended.constraintCount
|
|
47737
|
+
};
|
|
47738
|
+
}
|
|
47739
|
+
return { journalCommitted: writesLedger && Boolean(journalId) };
|
|
47740
|
+
}
|
|
47741
|
+
});
|
|
47742
|
+
return okEnvelope("apply_update", {
|
|
47743
|
+
...result,
|
|
47744
|
+
...appliedJournalId ? { journalId: appliedJournalId } : {},
|
|
47745
|
+
architectureLedger: {
|
|
47746
|
+
...this.architectureLedger,
|
|
47747
|
+
append: writesLedger ? ledgerAppend ?? { status: "not-appended" } : { status: "not-applicable" }
|
|
47748
|
+
}
|
|
47749
|
+
});
|
|
47750
|
+
});
|
|
47751
|
+
}
|
|
47752
|
+
async appendAppliedChangeSetToArchitectureLedger(root, session, draft, journalId) {
|
|
47753
|
+
const paths = runtimeStatePaths(root);
|
|
47754
|
+
const scope = {
|
|
47755
|
+
repository: {
|
|
47756
|
+
repositoryId: session.workspace.repositoryId,
|
|
47757
|
+
storageRepositoryId: paths.storageRepositoryId
|
|
47758
|
+
},
|
|
47759
|
+
worktree: {
|
|
47760
|
+
workspaceId: paths.workspaceId,
|
|
47761
|
+
storageWorkspaceId: paths.storageWorkspaceId,
|
|
47762
|
+
branch: readCurrentBranch(root),
|
|
47763
|
+
headSha: session.workspace.headSha,
|
|
47764
|
+
worktreeDigest: computeWorktreeDigest(root)
|
|
47765
|
+
}
|
|
47766
|
+
};
|
|
47767
|
+
const plan = planChangeSetApplyToArchitectureLedgerEvent({
|
|
47768
|
+
...scope,
|
|
47769
|
+
draft,
|
|
47770
|
+
files: listModelFiles(root),
|
|
47771
|
+
previousEvidenceState: await this.localStore.replayArchitectureLedgerEvidence(scope),
|
|
47772
|
+
createdAt: this.clock(),
|
|
47773
|
+
writeMode: this.architectureLedger.writeMode === "ledger-with-projection" ? "ledger-with-projection" : "dual",
|
|
47774
|
+
command: "archctx apply"
|
|
47775
|
+
});
|
|
47776
|
+
if (journalId)
|
|
47777
|
+
await this.localStore.recordChangeSetLedgerPlan(journalId, { event: plan.event });
|
|
47778
|
+
const appendInput = { writer: "runtime-daemon", events: [plan.event] };
|
|
47779
|
+
const result = await this.appendArchitectureEventsWithFeed(root, appendInput, journalId);
|
|
47780
|
+
return result;
|
|
47781
|
+
}
|
|
47782
|
+
async acceptCommittedChange(root, rawInput) {
|
|
47783
|
+
this.assertRunning();
|
|
47784
|
+
let input;
|
|
47785
|
+
try {
|
|
47786
|
+
input = decodeAcceptCommittedChangeInput(rawInput);
|
|
47787
|
+
} catch (error) {
|
|
47788
|
+
if (error instanceof AcceptCommittedChangeInputError)
|
|
47789
|
+
return errorEnvelope("ledger.accept-committed", "AC_SCHEMA_INVALID", error.message);
|
|
47790
|
+
throw error;
|
|
47791
|
+
}
|
|
47792
|
+
if (this.architectureLedger.readMode !== "yaml" || this.architectureLedger.writeMode !== "yaml") {
|
|
47793
|
+
return errorEnvelope("ledger.accept-committed", "AC_PRECONDITION_FAILED", "committed YAML acceptance requires YAML read and write authority");
|
|
47794
|
+
}
|
|
47795
|
+
return this.withWriter(async () => {
|
|
47796
|
+
try {
|
|
47797
|
+
const canonicalRoot = canonicalRepositoryRoot(root);
|
|
47798
|
+
const journals = [];
|
|
47799
|
+
for (const ref of input.journals)
|
|
47800
|
+
journals.push(await this.localStore.readCommittedChangeSet(canonicalRoot, ref.journalId));
|
|
47801
|
+
const model = loadNativeModelFromArchContext(canonicalRoot);
|
|
47802
|
+
const worktreeDigest = architectureDocumentationProjectionWorktreeDigest(canonicalRoot, model);
|
|
47803
|
+
const projection3 = buildArchitectureDocsProjection(this.projectionHost(), canonicalRoot, new Date(0).toISOString(), "repo-harness/v1");
|
|
47804
|
+
const scope = acceptedCommittedChangeScope(canonicalRoot, worktreeDigest, resolveAcceptanceHeadSha(canonicalRoot));
|
|
47805
|
+
const manifestWrite = await this.localStore.readLatestCommittedChangeSetFile(canonicalRoot, "docs/architecture/.projection-manifest.json");
|
|
47806
|
+
const acceptance = planCommittedChangeAcceptance(canonicalRoot, {
|
|
47807
|
+
requested: input.journals,
|
|
47808
|
+
journals,
|
|
47809
|
+
model,
|
|
47810
|
+
existingFiles: projection3.loaded.existingFiles,
|
|
47811
|
+
latestJournaledManifest: manifestWrite,
|
|
47812
|
+
projection: projection3.plan,
|
|
47813
|
+
currentEvidence: projection3.snapshotEvidence,
|
|
47814
|
+
projectionWorktreeDigest: worktreeDigest,
|
|
47815
|
+
scope
|
|
47816
|
+
});
|
|
47817
|
+
if (!input.approved) {
|
|
47818
|
+
return okEnvelope("ledger.accept-committed", { status: "preview", plan: acceptance.plan, acceptancePlanId: acceptance.acceptancePlanId, expectedWorktreeDigest: worktreeDigest });
|
|
47819
|
+
}
|
|
47820
|
+
if (input.expectedWorktreeDigest !== worktreeDigest)
|
|
47821
|
+
throw new Error("accepted ChangeSet expected worktree digest mismatch");
|
|
47822
|
+
if (input.acceptancePlanId !== acceptance.acceptancePlanId)
|
|
47823
|
+
throw new Error("accepted ChangeSet acceptance plan changed since preview");
|
|
47824
|
+
const accepted = (event2, replayed) => {
|
|
47825
|
+
const recorded = acceptedChangeFromEventV2(event2);
|
|
47826
|
+
if (!recorded || !event2.eventHash)
|
|
47827
|
+
throw new Error("committed ChangeSet acceptance readback failed");
|
|
47828
|
+
return okEnvelope("ledger.accept-committed", {
|
|
47829
|
+
status: "accepted",
|
|
47830
|
+
replayed,
|
|
47831
|
+
acceptedChange: recorded.acceptedChange,
|
|
47832
|
+
acceptancePlanId: recorded.acceptancePlanId,
|
|
47833
|
+
journalIds: input.journals.map((journal) => journal.journalId),
|
|
47834
|
+
eventHash: event2.eventHash,
|
|
47835
|
+
fileSetDigest: acceptance.plan.fileSetDigest
|
|
47836
|
+
});
|
|
47837
|
+
};
|
|
47838
|
+
const existing = await this.localStore.readArchitectureEvent({ ...scope, eventId: acceptance.acceptedChange.eventId });
|
|
47839
|
+
if (existing) {
|
|
47840
|
+
if (acceptedChangeFromEventV2(existing)?.acceptancePlanId === acceptance.acceptancePlanId)
|
|
47841
|
+
return accepted(existing, true);
|
|
47842
|
+
throw new Error(`committed ChangeSet already has a different acceptance event at this snapshot: ${existing.eventId}`);
|
|
47843
|
+
}
|
|
47844
|
+
const ledgerGraphDigest = architectureLedgerStateDigest(await this.localStore.readArchitectureLedgerState(scope));
|
|
47845
|
+
const event = acceptedCommittedChangeEventV2({ acceptance, scope, ledgerGraphDigest, timestamp: this.clock() });
|
|
47846
|
+
const appended = await this.appendArchitectureEventsWithFeed(canonicalRoot, { writer: "runtime-daemon", events: [event] });
|
|
47847
|
+
if (appended.appendedEvents.length !== 1)
|
|
47848
|
+
throw new Error("committed ChangeSet acceptance event was not appended");
|
|
47849
|
+
const readback = await this.localStore.readArchitectureEvent({ ...scope, eventId: event.eventId });
|
|
47850
|
+
if (!readback || readback.eventType !== event.eventType || readback.payloadVersion !== event.payloadVersion) {
|
|
47851
|
+
throw new Error("committed ChangeSet acceptance readback failed");
|
|
47852
|
+
}
|
|
47853
|
+
return accepted(readback, false);
|
|
47854
|
+
} catch (error) {
|
|
47855
|
+
return errorEnvelope("ledger.accept-committed", "AC_PRECONDITION_FAILED", error instanceof Error ? error.message : String(error));
|
|
47856
|
+
}
|
|
47857
|
+
});
|
|
47858
|
+
}
|
|
47859
|
+
assertRunning() {
|
|
47860
|
+
this.context.assertRunning();
|
|
47861
|
+
}
|
|
47862
|
+
clock() {
|
|
47863
|
+
return this.context.clock();
|
|
47864
|
+
}
|
|
47865
|
+
openSession(root) {
|
|
47866
|
+
return this.context.openSession(root);
|
|
47867
|
+
}
|
|
47868
|
+
withWriter(fn) {
|
|
47869
|
+
return this.context.withWriter(fn);
|
|
47870
|
+
}
|
|
47871
|
+
appendArchitectureEventsWithFeed(root, input, journalId) {
|
|
47872
|
+
return this.context.appendArchitectureEventsWithFeed(root, input, journalId);
|
|
47873
|
+
}
|
|
47874
|
+
projectionHost() {
|
|
47875
|
+
return this.context.projectionHost();
|
|
47876
|
+
}
|
|
47877
|
+
projection(root, input) {
|
|
47878
|
+
return this.context.projection(root, input);
|
|
47879
|
+
}
|
|
47880
|
+
}
|
|
47881
|
+
function runtimeWorktreeDigest(root, profile) {
|
|
47882
|
+
switch (profile) {
|
|
47883
|
+
case "repository":
|
|
47884
|
+
return computeWorktreeDigest(root);
|
|
47885
|
+
case "architecture-documentation-projection":
|
|
47886
|
+
return architectureDocumentationProjectionWorktreeDigest(root, loadNativeModelFromArchContext(root));
|
|
47887
|
+
default:
|
|
47888
|
+
throw new RuntimeUpdateInputError(`unsupported worktree digest profile: ${String(profile)}`);
|
|
47889
|
+
}
|
|
47890
|
+
}
|
|
47891
|
+
function acceptedCommittedChangeScope(root, worktreeDigest, headSha) {
|
|
47892
|
+
const paths = runtimeStatePaths(root);
|
|
47893
|
+
return {
|
|
47894
|
+
repository: { repositoryId: repositoryFingerprint(root), storageRepositoryId: paths.storageRepositoryId },
|
|
47895
|
+
worktree: {
|
|
47896
|
+
workspaceId: projectionWorkspaceId(root),
|
|
47897
|
+
storageWorkspaceId: paths.storageWorkspaceId,
|
|
47898
|
+
branch: readCurrentBranch(root),
|
|
47899
|
+
headSha,
|
|
47900
|
+
worktreeDigest
|
|
47901
|
+
}
|
|
47902
|
+
};
|
|
47903
|
+
}
|
|
47904
|
+
function safePracticeWaiverId(explicit, waiver) {
|
|
47905
|
+
const explicitTrimmed = explicit?.trim();
|
|
47906
|
+
if (explicitTrimmed && (explicitTrimmed === "." || explicitTrimmed === ".." || explicitTrimmed.includes("/") || explicitTrimmed.includes("\\"))) {
|
|
47907
|
+
throw new Error("practice-waiver-id-invalid");
|
|
47908
|
+
}
|
|
47909
|
+
const evidencePrefix = waiver.evidenceDigest.startsWith("sha256:") ? waiver.evidenceDigest.slice("sha256:".length, "sha256:".length + 12) : waiver.evidenceDigest.slice(0, 12);
|
|
47910
|
+
const candidate = (explicitTrimmed || [waiver.practiceId.replace(/\./g, "-"), waiver.checkId ?? "all", evidencePrefix].join("-")).replace(/[^A-Za-z0-9_.-]/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "").slice(0, 96);
|
|
47911
|
+
if (!candidate || candidate === "." || candidate === ".." || candidate.includes("/") || candidate.includes("\\")) {
|
|
47912
|
+
throw new Error("practice-waiver-id-invalid");
|
|
47913
|
+
}
|
|
47914
|
+
return candidate;
|
|
47915
|
+
}
|
|
47916
|
+
function architectureLedgerWriteAppendsEvents(mode) {
|
|
47917
|
+
return mode === "dual" || mode === "ledger-with-projection";
|
|
47918
|
+
}
|
|
47919
|
+
|
|
47372
47920
|
// packages/local-runtime/runtime-daemon/src/rpc-server.ts
|
|
47373
47921
|
init_src();
|
|
47374
47922
|
import { createServer as createServer2 } from "node:http";
|
|
47375
47923
|
import { closeSync as closeSync9, mkdirSync as mkdirSync12, rmSync as rmSync15 } from "node:fs";
|
|
47376
47924
|
import { dirname as dirname14 } from "node:path";
|
|
47377
|
-
import { randomBytes as
|
|
47925
|
+
import { randomBytes as randomBytes6 } from "node:crypto";
|
|
47378
47926
|
var DEFAULT_DAEMON_IDLE_TIMEOUT_MS = 30 * 60000;
|
|
47379
47927
|
var DAEMON_IDLE_TIMEOUT_ENV = "ARCHCONTEXT_DAEMON_IDLE_TIMEOUT_MS";
|
|
47380
47928
|
function resolveDaemonIdleTimeoutMs(explicit) {
|
|
@@ -47444,7 +47992,7 @@ class ArchctxRuntimeRpcServer {
|
|
|
47444
47992
|
mkdirSync12(dirname14(connectionPath), { recursive: true });
|
|
47445
47993
|
mkdirSync12(dirname14(lockPath), { recursive: true });
|
|
47446
47994
|
this.lockFd = acquireDaemonLock(lockPath, root);
|
|
47447
|
-
const token = this.options.token ??
|
|
47995
|
+
const token = this.options.token ?? randomBytes6(18).toString("base64url");
|
|
47448
47996
|
const server = createServer2((request, response) => {
|
|
47449
47997
|
this.handleRequest(request, response).catch((error) => {
|
|
47450
47998
|
writeJson(response, 500, { schemaVersion: RUNTIME_RPC_VERSION, ok: false, error: error instanceof Error ? error.message : String(error) });
|
|
@@ -47710,10 +48258,9 @@ async function readRpcRequestBody(request, limits) {
|
|
|
47710
48258
|
}
|
|
47711
48259
|
|
|
47712
48260
|
// packages/local-runtime/runtime-daemon/src/index.ts
|
|
47713
|
-
import { randomBytes as randomBytes6 } from "node:crypto";
|
|
47714
48261
|
import { execFileSync as execFileSync11 } from "node:child_process";
|
|
47715
|
-
import { existsSync as
|
|
47716
|
-
import { resolve as
|
|
48262
|
+
import { existsSync as existsSync25 } from "node:fs";
|
|
48263
|
+
import { resolve as resolve26 } from "node:path";
|
|
47717
48264
|
init_src();
|
|
47718
48265
|
|
|
47719
48266
|
// packages/local-runtime/runtime-daemon/src/egress.ts
|
|
@@ -47783,75 +48330,6 @@ function localEgressStatus2(env = process.env, live = {}) {
|
|
|
47783
48330
|
}
|
|
47784
48331
|
|
|
47785
48332
|
// packages/local-runtime/runtime-daemon/src/index.ts
|
|
47786
|
-
class RuntimeUpdateInputError extends Error {
|
|
47787
|
-
}
|
|
47788
|
-
function decodeRuntimeWorktreeDigestProfile(value, field) {
|
|
47789
|
-
switch (value) {
|
|
47790
|
-
case "repository":
|
|
47791
|
-
case "architecture-documentation-projection":
|
|
47792
|
-
return value;
|
|
47793
|
-
default:
|
|
47794
|
-
throw new RuntimeUpdateInputError(`${field} must be repository or architecture-documentation-projection`);
|
|
47795
|
-
}
|
|
47796
|
-
}
|
|
47797
|
-
function decodeRuntimePlanUpdateInput(value) {
|
|
47798
|
-
const input = runtimeUpdateInputRecord(value, "plan_update input");
|
|
47799
|
-
if (typeof input.id !== "string" || input.id.length === 0) {
|
|
47800
|
-
throw new RuntimeUpdateInputError("plan_update id must be a non-empty string");
|
|
47801
|
-
}
|
|
47802
|
-
if (!Array.isArray(input.operations)) {
|
|
47803
|
-
throw new RuntimeUpdateInputError("plan_update operations must be an array");
|
|
47804
|
-
}
|
|
47805
|
-
if (input.approvalChannel !== undefined && input.approvalChannel !== "mcp") {
|
|
47806
|
-
throw new RuntimeUpdateInputError("plan_update approvalChannel must be mcp");
|
|
47807
|
-
}
|
|
47808
|
-
let worktreeDigestPrecondition;
|
|
47809
|
-
if (input.worktreeDigestPrecondition !== undefined) {
|
|
47810
|
-
const precondition = runtimeUpdateInputRecord(input.worktreeDigestPrecondition, "plan_update worktreeDigestPrecondition");
|
|
47811
|
-
const profile = decodeRuntimeWorktreeDigestProfile(precondition.profile, "plan_update worktreeDigestPrecondition.profile");
|
|
47812
|
-
if (profile !== "architecture-documentation-projection") {
|
|
47813
|
-
throw new RuntimeUpdateInputError("plan_update worktreeDigestPrecondition.profile must be architecture-documentation-projection");
|
|
47814
|
-
}
|
|
47815
|
-
if (typeof precondition.expectedDigest !== "string" || precondition.expectedDigest.length === 0) {
|
|
47816
|
-
throw new RuntimeUpdateInputError("plan_update worktreeDigestPrecondition.expectedDigest must be a non-empty string");
|
|
47817
|
-
}
|
|
47818
|
-
worktreeDigestPrecondition = { profile, expectedDigest: precondition.expectedDigest };
|
|
47819
|
-
}
|
|
47820
|
-
return {
|
|
47821
|
-
id: input.id,
|
|
47822
|
-
operations: input.operations,
|
|
47823
|
-
...input.approvalChannel === "mcp" ? { approvalChannel: "mcp" } : {},
|
|
47824
|
-
...input.reason === undefined ? {} : { reason: input.reason },
|
|
47825
|
-
...worktreeDigestPrecondition === undefined ? {} : { worktreeDigestPrecondition }
|
|
47826
|
-
};
|
|
47827
|
-
}
|
|
47828
|
-
function decodeRuntimeApplyUpdateInput(value) {
|
|
47829
|
-
const input = runtimeUpdateInputRecord(value, "apply_update input");
|
|
47830
|
-
if (typeof input.id !== "string" || input.id.length === 0) {
|
|
47831
|
-
throw new RuntimeUpdateInputError("apply_update id must be a non-empty string");
|
|
47832
|
-
}
|
|
47833
|
-
if (typeof input.approved !== "boolean") {
|
|
47834
|
-
throw new RuntimeUpdateInputError("apply_update approved must be a boolean");
|
|
47835
|
-
}
|
|
47836
|
-
if (typeof input.expectedWorktreeDigest !== "string" || input.expectedWorktreeDigest.length === 0) {
|
|
47837
|
-
throw new RuntimeUpdateInputError("apply_update expectedWorktreeDigest must be a non-empty string");
|
|
47838
|
-
}
|
|
47839
|
-
const worktreeDigestProfile = input.worktreeDigestProfile === undefined ? undefined : decodeRuntimeWorktreeDigestProfile(input.worktreeDigestProfile, "apply_update worktreeDigestProfile");
|
|
47840
|
-
return {
|
|
47841
|
-
id: input.id,
|
|
47842
|
-
approved: input.approved,
|
|
47843
|
-
expectedWorktreeDigest: input.expectedWorktreeDigest,
|
|
47844
|
-
...worktreeDigestProfile === undefined ? {} : { worktreeDigestProfile },
|
|
47845
|
-
...input.projectionApplyReceipt === undefined ? {} : { projectionApplyReceipt: input.projectionApplyReceipt }
|
|
47846
|
-
};
|
|
47847
|
-
}
|
|
47848
|
-
function runtimeUpdateInputRecord(value, field) {
|
|
47849
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
47850
|
-
throw new RuntimeUpdateInputError(`${field} must be an object`);
|
|
47851
|
-
}
|
|
47852
|
-
return value;
|
|
47853
|
-
}
|
|
47854
|
-
|
|
47855
48333
|
class ArchitectureLedgerReadModelStore {
|
|
47856
48334
|
fallback;
|
|
47857
48335
|
localStore;
|
|
@@ -47940,12 +48418,8 @@ class ArchctxDaemon {
|
|
|
47940
48418
|
maxRepoSessions;
|
|
47941
48419
|
composition;
|
|
47942
48420
|
sessions = new Map;
|
|
47943
|
-
|
|
47944
|
-
changeSetRoots = new Map;
|
|
47945
|
-
mcpChangeSets = new Set;
|
|
48421
|
+
changeSetAuthority;
|
|
47946
48422
|
projectionPlannedDrafts = new WeakSet;
|
|
47947
|
-
mcpApprovals = new Map;
|
|
47948
|
-
changeSetWorktreeDigestProfiles = new Map;
|
|
47949
48423
|
auditRunAbortControllers = new Map;
|
|
47950
48424
|
landscapes;
|
|
47951
48425
|
explorerServer;
|
|
@@ -48077,6 +48551,20 @@ class ArchctxDaemon {
|
|
|
48077
48551
|
worktreeDigest: runtimeWorktreeDigest,
|
|
48078
48552
|
loadSourceChanges: loadCapabilitySourceChangesSinceStamps
|
|
48079
48553
|
});
|
|
48554
|
+
this.changeSetAuthority = new ChangeSetAuthorityService({
|
|
48555
|
+
projectionPlannedDrafts: this.projectionPlannedDrafts,
|
|
48556
|
+
assertRunning: () => this.assertRunning(),
|
|
48557
|
+
clock: this.clock,
|
|
48558
|
+
openSession: (root) => this.openSession(root),
|
|
48559
|
+
withWriter: (run) => this.withWriter(run),
|
|
48560
|
+
appendArchitectureEventsWithFeed: (root, input, journalId) => this.appendArchitectureEventsWithFeed(root, input, journalId),
|
|
48561
|
+
projectionHost: () => this.projectionHost(),
|
|
48562
|
+
projection: (root, input) => this.projection(root, input),
|
|
48563
|
+
readModelStore: this.readModelStore,
|
|
48564
|
+
localStore: this.localStore,
|
|
48565
|
+
changeSetEngine: this.changeSetEngine,
|
|
48566
|
+
architectureLedger: this.architectureLedger
|
|
48567
|
+
});
|
|
48080
48568
|
this.developerReviewRuns = new DeveloperReviewRunService(() => this.assertRunning(), this.clock);
|
|
48081
48569
|
this.externalDocumentation = deps.externalDocumentation ?? new Context7ExternalDocumentationAdapter({
|
|
48082
48570
|
enabled: process.env[CONTEXT7_ENABLED_ENV] === "1",
|
|
@@ -48146,7 +48634,7 @@ class ArchctxDaemon {
|
|
|
48146
48634
|
try {
|
|
48147
48635
|
for (const controller of this.auditRunAbortControllers.values())
|
|
48148
48636
|
controller.abort();
|
|
48149
|
-
this.
|
|
48637
|
+
this.changeSetAuthority.clearApprovals();
|
|
48150
48638
|
this.sessions.clear();
|
|
48151
48639
|
this.practiceCheckpoints.clear();
|
|
48152
48640
|
this.explorerProjections.clearDeferredChangeFeedFailures();
|
|
@@ -48290,65 +48778,7 @@ class ArchctxDaemon {
|
|
|
48290
48778
|
return this.practiceCheckpoints.practiceWaivers(root);
|
|
48291
48779
|
}
|
|
48292
48780
|
async planPracticeWaiver(root, input) {
|
|
48293
|
-
this.
|
|
48294
|
-
const session = await this.openSession(root);
|
|
48295
|
-
const model = await this.readModelStore.validateModel(session.workspace);
|
|
48296
|
-
let ownerRegistry;
|
|
48297
|
-
try {
|
|
48298
|
-
ownerRegistry = loadPracticeWaiverOwnerRegistry(session.workspace.root);
|
|
48299
|
-
} catch (error) {
|
|
48300
|
-
return errorEnvelope("practices.waive", "AC_SCHEMA_INVALID", error instanceof Error ? error.message : String(error));
|
|
48301
|
-
}
|
|
48302
|
-
const waiver = {
|
|
48303
|
-
schemaVersion: "archcontext.practice-waiver/v1",
|
|
48304
|
-
practiceId: input.practiceId,
|
|
48305
|
-
...input.checkId === undefined ? {} : { checkId: input.checkId },
|
|
48306
|
-
scope: {
|
|
48307
|
-
...input.pathGlobs && input.pathGlobs.length > 0 ? { pathGlobs: input.pathGlobs } : {},
|
|
48308
|
-
...input.subjects && input.subjects.length > 0 ? { subjects: input.subjects } : {}
|
|
48309
|
-
},
|
|
48310
|
-
owner: input.owner,
|
|
48311
|
-
reason: input.reason,
|
|
48312
|
-
createdAt: input.createdAt ?? this.clock(),
|
|
48313
|
-
reviewAt: input.reviewAt,
|
|
48314
|
-
expiresAt: input.expiresAt,
|
|
48315
|
-
evidenceDigest: input.evidenceDigest
|
|
48316
|
-
};
|
|
48317
|
-
let waiverId;
|
|
48318
|
-
try {
|
|
48319
|
-
validatePracticeWaiver(waiver, "practice waiver input", { allowedOwners: ownerRegistry.owners });
|
|
48320
|
-
waiverId = safePracticeWaiverId(input.waiverId, waiver);
|
|
48321
|
-
} catch (error) {
|
|
48322
|
-
return errorEnvelope("practices.waive", "AC_SCHEMA_INVALID", error instanceof Error ? error.message : String(error));
|
|
48323
|
-
}
|
|
48324
|
-
const path = `.archcontext/waivers/${waiverId}.json`;
|
|
48325
|
-
const absolute = resolve25(session.workspace.root, path);
|
|
48326
|
-
const body = `${JSON.stringify(waiver, null, 2)}
|
|
48327
|
-
`;
|
|
48328
|
-
const expectedHash = existsSync24(absolute) ? digestJson({ body: readFileSync26(absolute, "utf8") }) : "missing";
|
|
48329
|
-
const draft = this.changeSetEngine.plan({
|
|
48330
|
-
id: input.id ?? `changeset.practice-waiver-${waiverId.replace(/[^A-Za-z0-9_-]/g, "-")}`,
|
|
48331
|
-
base: {
|
|
48332
|
-
headSha: session.workspace.headSha,
|
|
48333
|
-
worktreeDigest: session.snapshot.worktreeDigest,
|
|
48334
|
-
modelDigest: model.modelDigest
|
|
48335
|
-
},
|
|
48336
|
-
reason: { taskSessionId: input.taskSessionId ?? "task_runtime" },
|
|
48337
|
-
operations: [{ op: "write_waiver", path, expectedHash, body }]
|
|
48338
|
-
});
|
|
48339
|
-
this.changesets.set(draft.id, draft);
|
|
48340
|
-
this.changeSetRoots.set(draft.id, canonicalRepositoryRoot(root));
|
|
48341
|
-
this.changeSetWorktreeDigestProfiles.set(draft.id, "repository");
|
|
48342
|
-
this.mcpChangeSets.delete(draft.id);
|
|
48343
|
-
return okEnvelope("practices.waive", {
|
|
48344
|
-
schemaVersion: "archcontext.practice-waiver-plan/v1",
|
|
48345
|
-
waiver,
|
|
48346
|
-
waiverDigest: digestJson(waiver),
|
|
48347
|
-
ownerRegistry,
|
|
48348
|
-
path,
|
|
48349
|
-
draft,
|
|
48350
|
-
preview: this.changeSetEngine.preview(session.workspace.root, draft)
|
|
48351
|
-
});
|
|
48781
|
+
return this.changeSetAuthority.planPracticeWaiver(root, input);
|
|
48352
48782
|
}
|
|
48353
48783
|
async docs(root, input) {
|
|
48354
48784
|
return this.externalDocumentationService.docs(root, input);
|
|
@@ -48357,45 +48787,7 @@ class ArchctxDaemon {
|
|
|
48357
48787
|
return this.externalDocumentationService.readResource(root, uri);
|
|
48358
48788
|
}
|
|
48359
48789
|
async planUpdate(root, rawInput) {
|
|
48360
|
-
this.
|
|
48361
|
-
let input;
|
|
48362
|
-
try {
|
|
48363
|
-
input = decodeRuntimePlanUpdateInput(rawInput);
|
|
48364
|
-
} catch (error) {
|
|
48365
|
-
if (error instanceof RuntimeUpdateInputError) {
|
|
48366
|
-
return errorEnvelope("plan_update", "AC_SCHEMA_INVALID", error.message);
|
|
48367
|
-
}
|
|
48368
|
-
throw error;
|
|
48369
|
-
}
|
|
48370
|
-
const session = await this.openSession(root);
|
|
48371
|
-
const model = await this.readModelStore.validateModel(session.workspace);
|
|
48372
|
-
const worktreeDigestProfile = input.worktreeDigestPrecondition?.profile ?? "repository";
|
|
48373
|
-
const worktreeDigest = runtimeWorktreeDigest(root, worktreeDigestProfile);
|
|
48374
|
-
if (input.worktreeDigestPrecondition && input.worktreeDigestPrecondition.expectedDigest !== worktreeDigest) {
|
|
48375
|
-
throw new Error("Worktree digest changed before plan");
|
|
48376
|
-
}
|
|
48377
|
-
const draft = this.changeSetEngine.plan({
|
|
48378
|
-
id: input.id,
|
|
48379
|
-
base: {
|
|
48380
|
-
headSha: session.workspace.headSha,
|
|
48381
|
-
worktreeDigest,
|
|
48382
|
-
modelDigest: model.modelDigest
|
|
48383
|
-
},
|
|
48384
|
-
reason: input.reason ?? { taskSessionId: "task_runtime" },
|
|
48385
|
-
operations: input.operations
|
|
48386
|
-
});
|
|
48387
|
-
this.changesets.set(draft.id, draft);
|
|
48388
|
-
this.changeSetRoots.set(draft.id, canonicalRepositoryRoot(root));
|
|
48389
|
-
this.changeSetWorktreeDigestProfiles.set(draft.id, worktreeDigestProfile);
|
|
48390
|
-
if (input.approvalChannel === "mcp")
|
|
48391
|
-
this.mcpChangeSets.add(draft.id);
|
|
48392
|
-
else
|
|
48393
|
-
this.mcpChangeSets.delete(draft.id);
|
|
48394
|
-
return okEnvelope("plan_update", {
|
|
48395
|
-
draft,
|
|
48396
|
-
changeSetDigest: digestJson(draft),
|
|
48397
|
-
preview: this.changeSetEngine.preview(root, draft)
|
|
48398
|
-
});
|
|
48790
|
+
return this.changeSetAuthority.planUpdate(root, rawInput);
|
|
48399
48791
|
}
|
|
48400
48792
|
async completeTask(root, input = {}) {
|
|
48401
48793
|
this.assertRunning();
|
|
@@ -48504,177 +48896,19 @@ class ArchctxDaemon {
|
|
|
48504
48896
|
return runProjectionProtocolCommand(input, root, this.projectionHost());
|
|
48505
48897
|
}
|
|
48506
48898
|
async approveMcpProjection(root, input) {
|
|
48507
|
-
this.
|
|
48508
|
-
try {
|
|
48509
|
-
validateProjectionInvocation(input);
|
|
48510
|
-
if (!projectionInvocationWrites(input))
|
|
48511
|
-
throw new Error("Projection approval requires an apply, adopt or recover invocation");
|
|
48512
|
-
assertProjectionInvocationSnapshot(root, input);
|
|
48513
|
-
} catch (error) {
|
|
48514
|
-
return errorEnvelope("projection.approve", "AC_PRECONDITION_FAILED", error instanceof Error ? error.message : String(error));
|
|
48515
|
-
}
|
|
48516
|
-
const now = Date.parse(this.clock());
|
|
48517
|
-
if (!Number.isFinite(now))
|
|
48518
|
-
return errorEnvelope("projection.approve", "AC_PRECONDITION_FAILED", "Approval clock is invalid");
|
|
48519
|
-
for (const [key, grant] of this.mcpApprovals)
|
|
48520
|
-
if (grant.expiresAt <= now)
|
|
48521
|
-
this.mcpApprovals.delete(key);
|
|
48522
|
-
if (this.mcpApprovals.size >= 256)
|
|
48523
|
-
return errorEnvelope("projection.approve", "AC_PRECONDITION_FAILED", "Too many outstanding approvals; consume an approval or wait for expiry");
|
|
48524
|
-
const approvalToken = randomBytes6(32).toString("hex");
|
|
48525
|
-
const expiresAt = now + 5 * 60000;
|
|
48526
|
-
this.mcpApprovals.set(digestJson(approvalToken), { scope: "projection", root: canonicalRepositoryRoot(root), invocationDigest: digestJson(input), expiresAt });
|
|
48527
|
-
return okEnvelope("projection.approve", { approvalToken, expiresAt: new Date(expiresAt).toISOString() });
|
|
48899
|
+
return this.changeSetAuthority.approveMcpProjection(root, input);
|
|
48528
48900
|
}
|
|
48529
48901
|
async mcpProjection(root, input, approvalToken) {
|
|
48530
|
-
this.
|
|
48531
|
-
try {
|
|
48532
|
-
validateProjectionInvocation(input);
|
|
48533
|
-
} catch (error) {
|
|
48534
|
-
return errorEnvelope("projection", "AC_SCHEMA_INVALID", error instanceof Error ? error.message : String(error));
|
|
48535
|
-
}
|
|
48536
|
-
if (projectionInvocationWrites(input)) {
|
|
48537
|
-
const denied = () => errorEnvelope("projection", "AC_USER_CONFIRMATION_REQUIRED", "A fresh one-time token from archctx projection approve is required");
|
|
48538
|
-
if (typeof approvalToken !== "string" || !/^[a-f0-9]{64}$/.test(approvalToken))
|
|
48539
|
-
return denied();
|
|
48540
|
-
const key = digestJson(approvalToken);
|
|
48541
|
-
const grant = this.mcpApprovals.get(key);
|
|
48542
|
-
this.mcpApprovals.delete(key);
|
|
48543
|
-
const now = Date.parse(this.clock());
|
|
48544
|
-
if (!grant || grant.scope !== "projection" || !Number.isFinite(now) || grant.expiresAt <= now || grant.root !== canonicalRepositoryRoot(root) || grant.invocationDigest !== digestJson(input))
|
|
48545
|
-
return denied();
|
|
48546
|
-
}
|
|
48547
|
-
return this.projection(root, input);
|
|
48902
|
+
return this.changeSetAuthority.mcpProjection(root, input, approvalToken);
|
|
48548
48903
|
}
|
|
48549
48904
|
async approveMcpUpdate(root, input) {
|
|
48550
|
-
this.
|
|
48551
|
-
const draft = this.changesets.get(input?.id);
|
|
48552
|
-
const canonicalRoot = canonicalRepositoryRoot(root);
|
|
48553
|
-
const now = Date.parse(this.clock());
|
|
48554
|
-
if (!draft || this.changeSetRoots.get(input.id) !== canonicalRoot || this.changeSetWorktreeDigestProfiles.get(input.id) !== "repository" || input.expectedChangeSetDigest !== digestJson(draft) || input.expectedWorktreeDigest !== draft.base.worktreeDigest || input.expectedWorktreeDigest !== runtimeWorktreeDigest(root, "repository") || !Number.isFinite(now)) {
|
|
48555
|
-
return errorEnvelope("approve_mcp_update", "AC_PRECONDITION_FAILED", "Approval must match the current ChangeSet preview and repository");
|
|
48556
|
-
}
|
|
48557
|
-
for (const [key, grant] of this.mcpApprovals)
|
|
48558
|
-
if (grant.expiresAt <= now)
|
|
48559
|
-
this.mcpApprovals.delete(key);
|
|
48560
|
-
if (this.mcpApprovals.size >= 256)
|
|
48561
|
-
return errorEnvelope("approve_mcp_update", "AC_PRECONDITION_FAILED", "Too many outstanding approvals; consume an approval or wait for expiry");
|
|
48562
|
-
const approvalToken = randomBytes6(32).toString("hex");
|
|
48563
|
-
const expiresAt = now + 5 * 60000;
|
|
48564
|
-
this.mcpApprovals.set(digestJson(approvalToken), {
|
|
48565
|
-
scope: "changeset",
|
|
48566
|
-
root: canonicalRoot,
|
|
48567
|
-
id: input.id,
|
|
48568
|
-
draftDigest: input.expectedChangeSetDigest,
|
|
48569
|
-
worktreeDigest: input.expectedWorktreeDigest,
|
|
48570
|
-
expiresAt
|
|
48571
|
-
});
|
|
48572
|
-
return okEnvelope("approve_mcp_update", { approvalToken, expiresAt: new Date(expiresAt).toISOString() });
|
|
48905
|
+
return this.changeSetAuthority.approveMcpUpdate(root, input);
|
|
48573
48906
|
}
|
|
48574
48907
|
async applyMcpUpdate(root, input) {
|
|
48575
|
-
this.
|
|
48576
|
-
const denied = () => errorEnvelope("apply_update", "AC_USER_CONFIRMATION_REQUIRED", "A fresh one-time token from archctx approve is required");
|
|
48577
|
-
if (typeof input?.approvalToken !== "string" || !/^[a-f0-9]{64}$/.test(input.approvalToken))
|
|
48578
|
-
return denied();
|
|
48579
|
-
const key = digestJson(input.approvalToken);
|
|
48580
|
-
const grant = this.mcpApprovals.get(key);
|
|
48581
|
-
if (!grant)
|
|
48582
|
-
return denied();
|
|
48583
|
-
this.mcpApprovals.delete(key);
|
|
48584
|
-
const draft = this.changesets.get(input.id);
|
|
48585
|
-
const now = Date.parse(this.clock());
|
|
48586
|
-
if (grant.scope !== "changeset" || !Number.isFinite(now) || grant.expiresAt <= now || grant.root !== canonicalRepositoryRoot(root) || grant.id !== input.id || grant.worktreeDigest !== input.expectedWorktreeDigest || !draft || grant.draftDigest !== digestJson(draft))
|
|
48587
|
-
return denied();
|
|
48588
|
-
return this.applyAuthorizedUpdate(root, { id: input.id, expectedWorktreeDigest: input.expectedWorktreeDigest, approved: true });
|
|
48908
|
+
return this.changeSetAuthority.applyMcpUpdate(root, input);
|
|
48589
48909
|
}
|
|
48590
48910
|
async applyUpdate(root, rawInput) {
|
|
48591
|
-
this.
|
|
48592
|
-
if (this.mcpChangeSets.has(rawInput?.id)) {
|
|
48593
|
-
return errorEnvelope("apply_update", "AC_USER_CONFIRMATION_REQUIRED", "MCP ChangeSets require a one-time approval token through applyMcpUpdate");
|
|
48594
|
-
}
|
|
48595
|
-
return this.applyAuthorizedUpdate(root, rawInput);
|
|
48596
|
-
}
|
|
48597
|
-
async applyAuthorizedUpdate(root, rawInput) {
|
|
48598
|
-
this.assertRunning();
|
|
48599
|
-
let input;
|
|
48600
|
-
try {
|
|
48601
|
-
input = decodeRuntimeApplyUpdateInput(rawInput);
|
|
48602
|
-
} catch (error) {
|
|
48603
|
-
if (error instanceof RuntimeUpdateInputError) {
|
|
48604
|
-
return errorEnvelope("apply_update", "AC_SCHEMA_INVALID", error.message);
|
|
48605
|
-
}
|
|
48606
|
-
throw error;
|
|
48607
|
-
}
|
|
48608
|
-
return this.withWriter(async () => {
|
|
48609
|
-
const draft = this.changesets.get(input.id);
|
|
48610
|
-
if (!draft)
|
|
48611
|
-
throw new Error(`Unknown ChangeSet: ${input.id}`);
|
|
48612
|
-
const plannedProfile = this.changeSetWorktreeDigestProfiles.get(input.id);
|
|
48613
|
-
if (!plannedProfile)
|
|
48614
|
-
throw new Error("ChangeSet worktree digest profile missing before apply");
|
|
48615
|
-
const requestedProfile = input.worktreeDigestProfile ?? "repository";
|
|
48616
|
-
if (requestedProfile !== plannedProfile)
|
|
48617
|
-
throw new Error("ChangeSet worktree digest profile changed before apply");
|
|
48618
|
-
const current = runtimeWorktreeDigest(root, plannedProfile);
|
|
48619
|
-
if (current !== input.expectedWorktreeDigest)
|
|
48620
|
-
throw new Error("Worktree digest changed before apply");
|
|
48621
|
-
const session = await this.openSession(root);
|
|
48622
|
-
if (draft.base.headSha !== session.workspace.headSha)
|
|
48623
|
-
throw new Error("ChangeSet HEAD changed before apply");
|
|
48624
|
-
if (draft.base.worktreeDigest !== current)
|
|
48625
|
-
throw new Error("ChangeSet worktree digest changed before apply");
|
|
48626
|
-
const currentModel = await this.readModelStore.validateModel(session.workspace);
|
|
48627
|
-
const baseErrors = baseModelBlockingErrors(currentModel);
|
|
48628
|
-
if (baseErrors.length > 0) {
|
|
48629
|
-
throw new Error(`ChangeSet base model is invalid: ${baseErrors.join("; ")}`);
|
|
48630
|
-
}
|
|
48631
|
-
if (draft.base.modelDigest !== currentModel.modelDigest)
|
|
48632
|
-
throw new Error("ChangeSet model digest changed before apply");
|
|
48633
|
-
if (input.projectionApplyReceipt && await this.localStore.inspectProjectionApplyReceipt(input.projectionApplyReceipt.identity.lookupKey)) {
|
|
48634
|
-
return errorEnvelope("apply_update", "AC_PRECONDITION_FAILED", "committed projection receipt requires explicit projection recover");
|
|
48635
|
-
}
|
|
48636
|
-
const approved = input.approved ? this.changeSetEngine.approve(draft) : draft;
|
|
48637
|
-
const transitionBase = captureModelTransitionBase(root, approved);
|
|
48638
|
-
let ledgerAppend;
|
|
48639
|
-
let appliedJournalId;
|
|
48640
|
-
const writesLedger = architectureLedgerWriteAppendsEvents(this.architectureLedger.writeMode);
|
|
48641
|
-
const result = await this.changeSetEngine.apply(root, approved, {
|
|
48642
|
-
approved: input.approved,
|
|
48643
|
-
afterModelValidatedBeforeCommit: async ({ journalId }) => {
|
|
48644
|
-
appliedJournalId = journalId;
|
|
48645
|
-
if (transitionBase && journalId)
|
|
48646
|
-
await recordModelTransitionEvidence(this.localStore, root, journalId, transitionBase);
|
|
48647
|
-
if (journalId && this.projectionPlannedDrafts.has(draft))
|
|
48648
|
-
await this.localStore.recordChangeSetProjectionOwner(journalId);
|
|
48649
|
-
if (input.projectionApplyReceipt) {
|
|
48650
|
-
if (!journalId)
|
|
48651
|
-
throw new Error("projection apply receipt requires a durable ChangeSet journal");
|
|
48652
|
-
await this.localStore.recordProjectionApplyReceipt(journalId, input.projectionApplyReceipt);
|
|
48653
|
-
}
|
|
48654
|
-
if (writesLedger) {
|
|
48655
|
-
const appended = await this.appendAppliedChangeSetToArchitectureLedger(root, session, approved, journalId);
|
|
48656
|
-
ledgerAppend = {
|
|
48657
|
-
status: "appended",
|
|
48658
|
-
appendedEventCount: appended.appendedEvents.length,
|
|
48659
|
-
duplicateEventCount: appended.duplicateEvents.length,
|
|
48660
|
-
graphDigest: appended.graphDigest,
|
|
48661
|
-
entityCount: appended.entityCount,
|
|
48662
|
-
relationCount: appended.relationCount,
|
|
48663
|
-
constraintCount: appended.constraintCount
|
|
48664
|
-
};
|
|
48665
|
-
}
|
|
48666
|
-
return { journalCommitted: writesLedger && Boolean(journalId) };
|
|
48667
|
-
}
|
|
48668
|
-
});
|
|
48669
|
-
return okEnvelope("apply_update", {
|
|
48670
|
-
...result,
|
|
48671
|
-
...appliedJournalId ? { journalId: appliedJournalId } : {},
|
|
48672
|
-
architectureLedger: {
|
|
48673
|
-
...this.architectureLedger,
|
|
48674
|
-
append: writesLedger ? ledgerAppend ?? { status: "not-appended" } : { status: "not-applicable" }
|
|
48675
|
-
}
|
|
48676
|
-
});
|
|
48677
|
-
});
|
|
48911
|
+
return this.changeSetAuthority.applyUpdate(root, rawInput);
|
|
48678
48912
|
}
|
|
48679
48913
|
async inspectProjectionApplyReceipt(root, lookupKey) {
|
|
48680
48914
|
return this.projectionApplies.inspectProjectionApplyReceipt(root, lookupKey);
|
|
@@ -48688,112 +48922,8 @@ class ArchctxDaemon {
|
|
|
48688
48922
|
async recoverProjectionApply(root, intent) {
|
|
48689
48923
|
return this.projectionApplies.recoverProjectionApply(root, intent);
|
|
48690
48924
|
}
|
|
48691
|
-
async appendAppliedChangeSetToArchitectureLedger(root, session, draft, journalId) {
|
|
48692
|
-
const paths = runtimeStatePaths(root);
|
|
48693
|
-
const scope = {
|
|
48694
|
-
repository: {
|
|
48695
|
-
repositoryId: session.workspace.repositoryId,
|
|
48696
|
-
storageRepositoryId: paths.storageRepositoryId
|
|
48697
|
-
},
|
|
48698
|
-
worktree: {
|
|
48699
|
-
workspaceId: paths.workspaceId,
|
|
48700
|
-
storageWorkspaceId: paths.storageWorkspaceId,
|
|
48701
|
-
branch: readCurrentBranch(root),
|
|
48702
|
-
headSha: session.workspace.headSha,
|
|
48703
|
-
worktreeDigest: computeWorktreeDigest(root)
|
|
48704
|
-
}
|
|
48705
|
-
};
|
|
48706
|
-
const plan = planChangeSetApplyToArchitectureLedgerEvent({
|
|
48707
|
-
...scope,
|
|
48708
|
-
draft,
|
|
48709
|
-
files: listModelFiles(root),
|
|
48710
|
-
previousEvidenceState: await this.localStore.replayArchitectureLedgerEvidence(scope),
|
|
48711
|
-
createdAt: this.clock(),
|
|
48712
|
-
writeMode: this.architectureLedger.writeMode === "ledger-with-projection" ? "ledger-with-projection" : "dual",
|
|
48713
|
-
command: "archctx apply"
|
|
48714
|
-
});
|
|
48715
|
-
if (journalId)
|
|
48716
|
-
await this.localStore.recordChangeSetLedgerPlan(journalId, { event: plan.event });
|
|
48717
|
-
const appendInput = { writer: "runtime-daemon", events: [plan.event] };
|
|
48718
|
-
const result = await this.appendArchitectureEventsWithFeed(root, appendInput, journalId);
|
|
48719
|
-
return result;
|
|
48720
|
-
}
|
|
48721
48925
|
async acceptCommittedChange(root, rawInput) {
|
|
48722
|
-
this.
|
|
48723
|
-
let input;
|
|
48724
|
-
try {
|
|
48725
|
-
input = decodeAcceptCommittedChangeInput(rawInput);
|
|
48726
|
-
} catch (error) {
|
|
48727
|
-
if (error instanceof AcceptCommittedChangeInputError)
|
|
48728
|
-
return errorEnvelope("ledger.accept-committed", "AC_SCHEMA_INVALID", error.message);
|
|
48729
|
-
throw error;
|
|
48730
|
-
}
|
|
48731
|
-
if (this.architectureLedger.readMode !== "yaml" || this.architectureLedger.writeMode !== "yaml") {
|
|
48732
|
-
return errorEnvelope("ledger.accept-committed", "AC_PRECONDITION_FAILED", "committed YAML acceptance requires YAML read and write authority");
|
|
48733
|
-
}
|
|
48734
|
-
return this.withWriter(async () => {
|
|
48735
|
-
try {
|
|
48736
|
-
const canonicalRoot = canonicalRepositoryRoot(root);
|
|
48737
|
-
const journals = [];
|
|
48738
|
-
for (const ref of input.journals)
|
|
48739
|
-
journals.push(await this.localStore.readCommittedChangeSet(canonicalRoot, ref.journalId));
|
|
48740
|
-
const model = loadNativeModelFromArchContext(canonicalRoot);
|
|
48741
|
-
const worktreeDigest = architectureDocumentationProjectionWorktreeDigest(canonicalRoot, model);
|
|
48742
|
-
const projection3 = buildArchitectureDocsProjection(this.projectionHost(), canonicalRoot, new Date(0).toISOString(), "repo-harness/v1");
|
|
48743
|
-
const scope = acceptedCommittedChangeScope(canonicalRoot, worktreeDigest, resolveAcceptanceHeadSha(canonicalRoot));
|
|
48744
|
-
const manifestWrite = await this.localStore.readLatestCommittedChangeSetFile(canonicalRoot, "docs/architecture/.projection-manifest.json");
|
|
48745
|
-
const acceptance = planCommittedChangeAcceptance(canonicalRoot, {
|
|
48746
|
-
requested: input.journals,
|
|
48747
|
-
journals,
|
|
48748
|
-
model,
|
|
48749
|
-
existingFiles: projection3.loaded.existingFiles,
|
|
48750
|
-
latestJournaledManifest: manifestWrite,
|
|
48751
|
-
projection: projection3.plan,
|
|
48752
|
-
currentEvidence: projection3.snapshotEvidence,
|
|
48753
|
-
projectionWorktreeDigest: worktreeDigest,
|
|
48754
|
-
scope
|
|
48755
|
-
});
|
|
48756
|
-
if (!input.approved) {
|
|
48757
|
-
return okEnvelope("ledger.accept-committed", { status: "preview", plan: acceptance.plan, acceptancePlanId: acceptance.acceptancePlanId, expectedWorktreeDigest: worktreeDigest });
|
|
48758
|
-
}
|
|
48759
|
-
if (input.expectedWorktreeDigest !== worktreeDigest)
|
|
48760
|
-
throw new Error("accepted ChangeSet expected worktree digest mismatch");
|
|
48761
|
-
if (input.acceptancePlanId !== acceptance.acceptancePlanId)
|
|
48762
|
-
throw new Error("accepted ChangeSet acceptance plan changed since preview");
|
|
48763
|
-
const accepted = (event2, replayed) => {
|
|
48764
|
-
const recorded = acceptedChangeFromEventV2(event2);
|
|
48765
|
-
if (!recorded || !event2.eventHash)
|
|
48766
|
-
throw new Error("committed ChangeSet acceptance readback failed");
|
|
48767
|
-
return okEnvelope("ledger.accept-committed", {
|
|
48768
|
-
status: "accepted",
|
|
48769
|
-
replayed,
|
|
48770
|
-
acceptedChange: recorded.acceptedChange,
|
|
48771
|
-
acceptancePlanId: recorded.acceptancePlanId,
|
|
48772
|
-
journalIds: input.journals.map((journal) => journal.journalId),
|
|
48773
|
-
eventHash: event2.eventHash,
|
|
48774
|
-
fileSetDigest: acceptance.plan.fileSetDigest
|
|
48775
|
-
});
|
|
48776
|
-
};
|
|
48777
|
-
const existing = await this.localStore.readArchitectureEvent({ ...scope, eventId: acceptance.acceptedChange.eventId });
|
|
48778
|
-
if (existing) {
|
|
48779
|
-
if (acceptedChangeFromEventV2(existing)?.acceptancePlanId === acceptance.acceptancePlanId)
|
|
48780
|
-
return accepted(existing, true);
|
|
48781
|
-
throw new Error(`committed ChangeSet already has a different acceptance event at this snapshot: ${existing.eventId}`);
|
|
48782
|
-
}
|
|
48783
|
-
const ledgerGraphDigest = architectureLedgerStateDigest(await this.localStore.readArchitectureLedgerState(scope));
|
|
48784
|
-
const event = acceptedCommittedChangeEventV2({ acceptance, scope, ledgerGraphDigest, timestamp: this.clock() });
|
|
48785
|
-
const appended = await this.appendArchitectureEventsWithFeed(canonicalRoot, { writer: "runtime-daemon", events: [event] });
|
|
48786
|
-
if (appended.appendedEvents.length !== 1)
|
|
48787
|
-
throw new Error("committed ChangeSet acceptance event was not appended");
|
|
48788
|
-
const readback = await this.localStore.readArchitectureEvent({ ...scope, eventId: event.eventId });
|
|
48789
|
-
if (!readback || readback.eventType !== event.eventType || readback.payloadVersion !== event.payloadVersion) {
|
|
48790
|
-
throw new Error("committed ChangeSet acceptance readback failed");
|
|
48791
|
-
}
|
|
48792
|
-
return accepted(readback, false);
|
|
48793
|
-
} catch (error) {
|
|
48794
|
-
return errorEnvelope("ledger.accept-committed", "AC_PRECONDITION_FAILED", error instanceof Error ? error.message : String(error));
|
|
48795
|
-
}
|
|
48796
|
-
});
|
|
48926
|
+
return this.changeSetAuthority.acceptCommittedChange(root, rawInput);
|
|
48797
48927
|
}
|
|
48798
48928
|
async ledgerState(root) {
|
|
48799
48929
|
this.assertRunning();
|
|
@@ -49073,7 +49203,7 @@ class ArchctxDaemon {
|
|
|
49073
49203
|
}
|
|
49074
49204
|
async restoreRepositorySessions() {
|
|
49075
49205
|
for (const record3 of await this.localStore.listRepositorySessions()) {
|
|
49076
|
-
if (!record3.root || !
|
|
49206
|
+
if (!record3.root || !existsSync25(record3.root))
|
|
49077
49207
|
continue;
|
|
49078
49208
|
if (repositoryFingerprint(record3.root) !== record3.repositoryId)
|
|
49079
49209
|
continue;
|
|
@@ -49135,16 +49265,6 @@ class ArchctxDaemon {
|
|
|
49135
49265
|
function shortDigest3(digest2) {
|
|
49136
49266
|
return digest2.replace(/^sha256:/, "").slice(0, 16);
|
|
49137
49267
|
}
|
|
49138
|
-
function runtimeWorktreeDigest(root, profile) {
|
|
49139
|
-
switch (profile) {
|
|
49140
|
-
case "repository":
|
|
49141
|
-
return computeWorktreeDigest(root);
|
|
49142
|
-
case "architecture-documentation-projection":
|
|
49143
|
-
return architectureDocumentationProjectionWorktreeDigest(root, loadNativeModelFromArchContext(root));
|
|
49144
|
-
default:
|
|
49145
|
-
throw new RuntimeUpdateInputError(`unsupported worktree digest profile: ${String(profile)}`);
|
|
49146
|
-
}
|
|
49147
|
-
}
|
|
49148
49268
|
function codeFactsDigest(snapshot) {
|
|
49149
49269
|
return digestJson({
|
|
49150
49270
|
schemaVersion: "archcontext.code-facts-digest/v1",
|
|
@@ -49170,19 +49290,6 @@ function architectureLedgerScopeForWorkspace(workspace) {
|
|
|
49170
49290
|
}
|
|
49171
49291
|
};
|
|
49172
49292
|
}
|
|
49173
|
-
function acceptedCommittedChangeScope(root, worktreeDigest, headSha) {
|
|
49174
|
-
const paths = runtimeStatePaths(root);
|
|
49175
|
-
return {
|
|
49176
|
-
repository: { repositoryId: repositoryFingerprint(root), storageRepositoryId: paths.storageRepositoryId },
|
|
49177
|
-
worktree: {
|
|
49178
|
-
workspaceId: projectionWorkspaceId(root),
|
|
49179
|
-
storageWorkspaceId: paths.storageWorkspaceId,
|
|
49180
|
-
branch: readCurrentBranch(root),
|
|
49181
|
-
headSha,
|
|
49182
|
-
worktreeDigest
|
|
49183
|
-
}
|
|
49184
|
-
};
|
|
49185
|
-
}
|
|
49186
49293
|
function validateModelFiles(files) {
|
|
49187
49294
|
const errors2 = [];
|
|
49188
49295
|
const paths = new Set(files.map((file) => file.path));
|
|
@@ -49227,18 +49334,6 @@ function schemaVersionFromModelBody(body) {
|
|
|
49227
49334
|
} catch {}
|
|
49228
49335
|
return "";
|
|
49229
49336
|
}
|
|
49230
|
-
function safePracticeWaiverId(explicit, waiver) {
|
|
49231
|
-
const explicitTrimmed = explicit?.trim();
|
|
49232
|
-
if (explicitTrimmed && (explicitTrimmed === "." || explicitTrimmed === ".." || explicitTrimmed.includes("/") || explicitTrimmed.includes("\\"))) {
|
|
49233
|
-
throw new Error("practice-waiver-id-invalid");
|
|
49234
|
-
}
|
|
49235
|
-
const evidencePrefix = waiver.evidenceDigest.startsWith("sha256:") ? waiver.evidenceDigest.slice("sha256:".length, "sha256:".length + 12) : waiver.evidenceDigest.slice(0, 12);
|
|
49236
|
-
const candidate = (explicitTrimmed || [waiver.practiceId.replace(/\./g, "-"), waiver.checkId ?? "all", evidencePrefix].join("-")).replace(/[^A-Za-z0-9_.-]/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "").slice(0, 96);
|
|
49237
|
-
if (!candidate || candidate === "." || candidate === ".." || candidate.includes("/") || candidate.includes("\\")) {
|
|
49238
|
-
throw new Error("practice-waiver-id-invalid");
|
|
49239
|
-
}
|
|
49240
|
-
return candidate;
|
|
49241
|
-
}
|
|
49242
49337
|
async function createStartedDaemon(deps = {}) {
|
|
49243
49338
|
const daemon = new ArchctxDaemon(deps);
|
|
49244
49339
|
await daemon.start();
|
|
@@ -49359,9 +49454,6 @@ function readRuntimeArchitectureLedgerWriteMode(value) {
|
|
|
49359
49454
|
function architectureLedgerReadAuthority(mode) {
|
|
49360
49455
|
return mode === "ledger" ? "ledger" : "yaml";
|
|
49361
49456
|
}
|
|
49362
|
-
function architectureLedgerWriteAppendsEvents(mode) {
|
|
49363
|
-
return mode === "dual" || mode === "ledger-with-projection";
|
|
49364
|
-
}
|
|
49365
49457
|
var GIT_OBJECT_NAME_PATTERN = /^[0-9a-f]{7,64}$/;
|
|
49366
49458
|
function readChangedPathsSince(root, commit) {
|
|
49367
49459
|
if (!GIT_OBJECT_NAME_PATTERN.test(commit)) {
|
|
@@ -49400,7 +49492,7 @@ function blockedProductionInjections(deps) {
|
|
|
49400
49492
|
].filter((key) => (key in deps));
|
|
49401
49493
|
}
|
|
49402
49494
|
function completeTaskProjectionDrift(root) {
|
|
49403
|
-
if (!
|
|
49495
|
+
if (!existsSync25(resolve26(root, "docs/architecture/.projection-manifest.json")))
|
|
49404
49496
|
return;
|
|
49405
49497
|
const profile = loadArchitectureDocumentationProfile(root);
|
|
49406
49498
|
const loaded = loadArchitectureDocumentationInputs(root, profile);
|
|
@@ -50280,7 +50372,7 @@ async function runCliUnchecked(command2 = "help", args2 = [], cwd, deps = {}) {
|
|
|
50280
50372
|
const subcommand = args2[0] ?? "list";
|
|
50281
50373
|
if (subcommand === "add") {
|
|
50282
50374
|
const root = readFlag(args2, "--root") ?? cwd;
|
|
50283
|
-
if (
|
|
50375
|
+
if (resolve27(root) !== resolve27(cwd)) {
|
|
50284
50376
|
return errorEnvelope("repo.add", "AC_CAPABILITY_UNSUPPORTED", "Multi-repo architecture context is outside the Local Core MVP; run archctx inside one Git repository.");
|
|
50285
50377
|
}
|
|
50286
50378
|
const daemon2 = await runtime();
|
|
@@ -51517,8 +51609,8 @@ function auditManifestGateRoot(cwd) {
|
|
|
51517
51609
|
}
|
|
51518
51610
|
}
|
|
51519
51611
|
function auditGithubIssuesEnabled(cwd) {
|
|
51520
|
-
const manifestPath =
|
|
51521
|
-
if (!
|
|
51612
|
+
const manifestPath = resolve27(auditManifestGateRoot(cwd), ".archcontext/manifest.yaml");
|
|
51613
|
+
if (!existsSync26(manifestPath))
|
|
51522
51614
|
return false;
|
|
51523
51615
|
let raw;
|
|
51524
51616
|
try {
|
|
@@ -51904,7 +51996,7 @@ async function runGithubCommand(args2, cwd, deps) {
|
|
|
51904
51996
|
}
|
|
51905
51997
|
if (subcommand === "disconnect") {
|
|
51906
51998
|
const connection = await deps.githubConnectionReader?.readVerifiedConnection({ repositoryRoot: cwd });
|
|
51907
|
-
const localRecordRemoved =
|
|
51999
|
+
const localRecordRemoved = existsSync26(connectionPath);
|
|
51908
52000
|
rmSync16(connectionPath, { force: true });
|
|
51909
52001
|
return okEnvelope("github.disconnect", {
|
|
51910
52002
|
connected: connection !== undefined,
|
|
@@ -52216,7 +52308,7 @@ async function readReviewChallengeV2Arg(args2, cwd, commandName = "github verify
|
|
|
52216
52308
|
if (!inline && !challengePath)
|
|
52217
52309
|
return { ok: false, message: `${commandName} requires --challenge-json or --challenge-path` };
|
|
52218
52310
|
try {
|
|
52219
|
-
const raw = inline ?? readFileSync27(
|
|
52311
|
+
const raw = inline ?? readFileSync27(resolve27(cwd, challengePath), "utf8");
|
|
52220
52312
|
const parsed = JSON.parse(raw);
|
|
52221
52313
|
const attestation = await Promise.resolve().then(() => (init_src6(), exports_src2));
|
|
52222
52314
|
const assertReviewChallengeV23 = attestation.assertReviewChallengeV2;
|
|
@@ -52691,10 +52783,10 @@ function doctorSqlite(cwd) {
|
|
|
52691
52783
|
const legacyLocalStore = inspectLegacyLocalStoreMigration(cwd);
|
|
52692
52784
|
return {
|
|
52693
52785
|
path,
|
|
52694
|
-
exists:
|
|
52786
|
+
exists: existsSync26(path),
|
|
52695
52787
|
migrations: productVersionManifest().runtime.sqliteMigrations,
|
|
52696
52788
|
legacyPath: paths.legacyLocalStorePath,
|
|
52697
|
-
legacyExists:
|
|
52789
|
+
legacyExists: existsSync26(paths.legacyLocalStorePath),
|
|
52698
52790
|
legacyLocalStore
|
|
52699
52791
|
};
|
|
52700
52792
|
}
|
|
@@ -52725,7 +52817,7 @@ function runtimePathsReport(cwd) {
|
|
|
52725
52817
|
};
|
|
52726
52818
|
}
|
|
52727
52819
|
function pathAccess(path) {
|
|
52728
|
-
const exists =
|
|
52820
|
+
const exists = existsSync26(path);
|
|
52729
52821
|
return {
|
|
52730
52822
|
path,
|
|
52731
52823
|
exists,
|
|
@@ -53183,7 +53275,7 @@ async function waitForDaemonReady(cwd, timeoutMs, shouldStop) {
|
|
|
53183
53275
|
return;
|
|
53184
53276
|
}
|
|
53185
53277
|
function sleep(ms) {
|
|
53186
|
-
return new Promise((
|
|
53278
|
+
return new Promise((resolve28) => setTimeout(resolve28, ms));
|
|
53187
53279
|
}
|
|
53188
53280
|
function readFileTail(path, maxBytes = 4096) {
|
|
53189
53281
|
try {
|
|
@@ -53196,8 +53288,8 @@ function readFileTail(path, maxBytes = 4096) {
|
|
|
53196
53288
|
async function runForegroundDaemon(cwd, args2) {
|
|
53197
53289
|
const daemon = await createStartedProductionDaemon({ root: cwd });
|
|
53198
53290
|
let resolveStopped;
|
|
53199
|
-
const stopped = new Promise((
|
|
53200
|
-
resolveStopped =
|
|
53291
|
+
const stopped = new Promise((resolve28) => {
|
|
53292
|
+
resolveStopped = resolve28;
|
|
53201
53293
|
});
|
|
53202
53294
|
const idleTimeoutFlag = readFlag(args2, "--idle-timeout-ms");
|
|
53203
53295
|
const server = new ArchctxRuntimeRpcServer(daemon, {
|