opencode-swarm 7.199.4 → 7.199.5
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/.opencode/skills/swarm-pr-feedback/references/followup-dispositions.md +1 -0
- package/.opencode/skills/swarm-pr-review/SKILL.md +3 -3
- package/binaries/win32-arm64/swarm-sandbox-runner.exe +0 -0
- package/binaries/win32-x64/swarm-sandbox-runner.exe +0 -0
- package/dist/agents/architect.d.ts +1 -1
- package/dist/background/pending-delegations.d.ts +46 -0
- package/dist/cli/{auto-checkpoint-75n3s0qj.js → auto-checkpoint-tje4xvtw.js} +12 -11
- package/dist/cli/{checkpoint-vdjfn78h.js → checkpoint-wyh8nkhb.js} +21 -21
- package/dist/cli/{coder-settlement-wwgdrzcb.js → coder-settlement-d0ca5fb3.js} +18 -18
- package/dist/cli/{config-doctor-x8ke4n9r.js → config-doctor-swg02esj.js} +19 -19
- package/dist/cli/{core-2zmr3718.js → core-yh8ae69m.js} +7 -7
- package/dist/cli/{curation-policy-0smbd33w.js → curation-policy-w9y0db1n.js} +8 -8
- package/dist/cli/{curator-7pnzsqe8.js → curator-9vzqhesg.js} +51 -51
- package/dist/cli/{curator-drift-2rtecv79.js → curator-drift-85qect05.js} +12 -12
- package/dist/cli/curator-llm-factory-dwvftpvm.js +82 -0
- package/dist/cli/{dispatch-1pbmmj8z.js → dispatch-sftetpzv.js} +4 -4
- package/dist/cli/{doctor-knowledge-receipt-phase-id-0yqb8t1e.js → doctor-knowledge-receipt-phase-id-eqzsh3b7.js} +12 -12
- package/dist/cli/{evidence-summary-service-59r4wnbz.js → evidence-summary-service-hc513kyq.js} +27 -27
- package/dist/cli/{gate-evidence-7xp8a4s3.js → gate-evidence-a10epye0.js} +13 -13
- package/dist/cli/guardrail-explain-rqqhmb44.js +83 -0
- package/dist/cli/guardrail-log-caspzygc.js +23 -0
- package/dist/cli/{guardrail-reset-2edjt0zn.js → guardrail-reset-48r2n8mz.js} +51 -51
- package/dist/cli/hive-promoter-sd3a61mq.js +97 -0
- package/dist/cli/{index-h69d3ksz.js → index-0aj9tvms.js} +51 -51
- package/dist/cli/{index-dyp5807d.js → index-0je0mees.js} +10 -8
- package/dist/cli/{index-eh12k044.js → index-0ra0vs2p.js} +13 -11
- package/dist/cli/{index-2mx2gn97.js → index-0veqvgny.js} +1 -1
- package/dist/cli/{index-h44592hj.js → index-10wwrgj2.js} +6 -6
- package/dist/cli/{index-5mfahvm2.js → index-26dmsdx3.js} +4884 -5746
- package/dist/cli/{index-ywb5er8g.js → index-270zfq1f.js} +2 -2
- package/dist/cli/{index-ry1jgeqa.js → index-34jat4f2.js} +2 -2
- package/dist/cli/{index-97cpeqb2.js → index-3ckvw3j2.js} +2 -2
- package/dist/cli/{index-2wsh6d15.js → index-3fz5w2wb.js} +15 -6
- package/dist/cli/{index-h349p91m.js → index-3mzc5rqc.js} +2 -2
- package/dist/cli/{index-bkscmz4z.js → index-4cyc18bc.js} +11 -11
- package/dist/cli/{index-hxav7nr0.js → index-4pbzepgw.js} +1645 -124
- package/dist/cli/{index-vnmadnbs.js → index-6ssraqaa.js} +15 -11
- package/dist/cli/{index-9gfymmr8.js → index-6xv2hz0e.js} +5 -5
- package/dist/cli/{index-dradkxhb.js → index-6xyt6d5m.js} +13 -13
- package/dist/cli/{index-t94vcqg6.js → index-76skvj8z.js} +7 -7
- package/dist/cli/{index-ffkd1zwc.js → index-85vd6sxy.js} +10 -10
- package/dist/cli/{index-k0er6f51.js → index-9c0vp5yj.js} +2 -2
- package/dist/cli/{index-sxh861jr.js → index-9n40q82y.js} +4 -4
- package/dist/cli/{index-nr1pj3hy.js → index-9y6yk1n0.js} +1 -1
- package/dist/cli/{index-j2vv0h13.js → index-axqyjreb.js} +6 -4
- package/dist/cli/{index-aaqgsmxr.js → index-b0kr8720.js} +53 -53
- package/dist/cli/{index-820pzqd6.js → index-b3ws03kp.js} +7 -5
- package/dist/cli/{index-3chx0ed6.js → index-bgydmx8k.js} +11 -11
- package/dist/cli/{index-bh6j8qxz.js → index-d7y3cafq.js} +5 -5
- package/dist/cli/{index-g1wra8ky.js → index-dengtwpp.js} +5 -4
- package/dist/cli/{index-6dtt0brw.js → index-dtwj4hsk.js} +4 -4
- package/dist/cli/{index-4s6kdsfr.js → index-dz15f98h.js} +1 -1
- package/dist/cli/{index-s41r5s5p.js → index-esakwcr3.js} +1 -1
- package/dist/cli/{index-hz2609qt.js → index-eyq1q1xn.js} +9 -7
- package/dist/cli/{index-aayv19fb.js → index-gyz59cbh.js} +25 -21
- package/dist/cli/{index-7c432abn.js → index-hx2f3j9w.js} +2 -2
- package/dist/cli/{index-p5pgjp7n.js → index-j3rfbs45.js} +1 -1
- package/dist/cli/{index-w7d8gcj0.js → index-jmhn6ab5.js} +2 -2
- package/dist/cli/{index-jhz7qpb4.js → index-my753e0g.js} +21 -17
- package/dist/cli/{index-2bc7zfw3.js → index-n1ddn12j.js} +2 -2
- package/dist/cli/{index-hfpa3hrz.js → index-p5d32v0x.js} +11 -11
- package/dist/cli/{index-2s7dvzff.js → index-qbxk2rn6.js} +42 -41
- package/dist/cli/{index-w00dj8p5.js → index-snx2vb1x.js} +2 -2
- package/dist/cli/{index-zxdawwc7.js → index-sp2cxpaq.js} +131 -8
- package/dist/cli/{index-g4fs6z4e.js → index-spggmr30.js} +6 -2
- package/dist/cli/{index-pxdc0qn1.js → index-tsg61z0r.js} +2 -2
- package/dist/cli/{index-jwg4wy3g.js → index-v4mremxw.js} +6 -5
- package/dist/cli/{index-cjapb4hy.js → index-x5jwegqw.js} +7 -5
- package/dist/cli/{index-h3nns8py.js → index-xtda4xsn.js} +403 -170
- package/dist/cli/{index-3gr7a9j8.js → index-xyg7des8.js} +4 -4
- package/dist/cli/{index-65syfqe1.js → index-ybvnf7rt.js} +8 -8
- package/dist/cli/{index-nfpfbce0.js → index-yg5xd4am.js} +18 -16
- package/dist/cli/{index-hc6wa3yg.js → index-zbygz217.js} +3 -3
- package/dist/cli/{index-x1j0838n.js → index-zdfzrwbk.js} +7 -7
- package/dist/cli/{index-4f5ejd03.js → index-zte3djpf.js} +2 -2
- package/dist/cli/index.js +52 -52
- package/dist/cli/{knowledge-escalator-2kphhk52.js → knowledge-escalator-9wqe0m8j.js} +18 -18
- package/dist/cli/{knowledge-events-xw4qjnts.js → knowledge-events-m5vdchaa.js} +16 -16
- package/dist/cli/{knowledge-link-1f91bzcc.js → knowledge-link-t0ce3mw7.js} +7 -7
- package/dist/cli/{knowledge-store-1cm7y9e6.js → knowledge-store-ff4hy057.js} +8 -8
- package/dist/cli/{knowledge-validator-q4r4ng23.js → knowledge-validator-erj5thtq.js} +10 -10
- package/dist/cli/{mcp-0548g7fk.js → mcp-d0hz5gfp.js} +1 -1
- package/dist/cli/{model-preflight-pdewpzmm.js → model-preflight-p34x0k25.js} +51 -51
- package/dist/cli/{pending-delegations-zv9ffy5b.js → pending-delegations-1j6dx1mf.js} +18 -15
- package/dist/cli/{pr-subscriptions-0ktv4qaq.js → pr-subscriptions-b1g41jce.js} +15 -15
- package/dist/cli/{pr-workflow-gate-71nxjz6p.js → pr-workflow-gate-zjwt050h.js} +59 -59
- package/dist/cli/{runner-7sd43gan.js → runner-wq954cda.js} +9 -9
- package/dist/cli/{scan-cursor-ty5dqwhm.js → scan-cursor-enz1rp21.js} +11 -10
- package/dist/cli/{schema-tnrvn8h1.js → schema-9r8sggpb.js} +1 -1
- package/dist/cli/{scope-persistence-camnvcaq.js → scope-persistence-c5n4g9f0.js} +20 -19
- package/dist/cli/{server-cr9kvgzz.js → server-8z7pd66d.js} +56 -56
- package/dist/cli/{skill-generator-nx2g9975.js → skill-generator-nezzfa0t.js} +20 -20
- package/dist/cli/{snapshot-coordination-init-ymsjgsqf.js → snapshot-coordination-init-2j06c7ck.js} +51 -51
- package/dist/cli/{speckit-checkoff-1pyx7y0d.js → speckit-checkoff-805cqmrf.js} +7 -6
- package/dist/cli/{telemetry-6yv06xyz.js → telemetry-gena5typ.js} +3 -3
- package/dist/cli/{workspace-snapshot-4zfj5wnd.js → workspace-snapshot-hzhz0rxw.js} +7 -7
- package/dist/cli/{worktree-collision-ownership-twe4r9f2.js → worktree-collision-ownership-xpr0yv7h.js} +22 -21
- package/dist/cli/{worktree-isolation-yj3vfp1h.js → worktree-isolation-z6ytes0j.js} +51 -51
- package/dist/commands/close/dry-run.d.ts +2 -1
- package/dist/commands/close/orchestrator.d.ts +16 -5
- package/dist/commands/destructive-purge.d.ts +53 -12
- package/dist/commands/git-status-read.d.ts +76 -0
- package/dist/commands/rollback-gate.d.ts +24 -5
- package/dist/config/schema.d.ts +5 -3
- package/dist/evaluation/contracts.d.ts +5 -5
- package/dist/hooks/lane-liveness-watchdog.d.ts +6 -1
- package/dist/hooks/pr-workflow-gate.d.ts +61 -1
- package/dist/index.js +254 -252
- package/dist/memory/schema.d.ts +3 -3
- package/dist/pr-review/completion.d.ts +63 -2
- package/dist/services/diagnose-service.d.ts +7 -8
- package/dist/tools/dispatch-lanes.d.ts +2 -1
- package/dist/utils/git-executable.d.ts +4 -0
- package/opencode-swarm.schema.json +3 -2
- package/package.json +1 -1
- package/dist/cli/curator-llm-factory-aybwqak6.js +0 -82
- package/dist/cli/guardrail-explain-jahptqqd.js +0 -83
- package/dist/cli/guardrail-log-csp211g7.js +0 -23
- package/dist/cli/hive-promoter-17q1qg8e.js +0 -97
- package/dist/cli/{index-fzpt2djt.js → index-te9hq2f4.js} +3 -3
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import {
|
|
3
3
|
validateSwarmPath
|
|
4
|
-
} from "./index-
|
|
5
|
-
import {
|
|
6
|
-
bunWrite
|
|
7
|
-
} from "./index-2s7dvzff.js";
|
|
4
|
+
} from "./index-b3ws03kp.js";
|
|
8
5
|
import {
|
|
9
6
|
MAX_COORDINATION_STATE_LIST_ROWS,
|
|
10
7
|
acquireCoordinationLease,
|
|
@@ -13,55 +10,1410 @@ import {
|
|
|
13
10
|
deleteCoordinationStateWithinTransaction,
|
|
14
11
|
getCoordinationLease,
|
|
15
12
|
getCoordinationState,
|
|
13
|
+
getCoordinationStateRaw,
|
|
16
14
|
importCoordinationOnce,
|
|
17
15
|
listCoordinationStates,
|
|
18
16
|
releaseCoordinationLease,
|
|
19
17
|
transitionCoordinationState,
|
|
20
18
|
withCoordinationTransaction
|
|
21
|
-
} from "./index-
|
|
19
|
+
} from "./index-9y6yk1n0.js";
|
|
22
20
|
import {
|
|
23
21
|
getProjectDb,
|
|
24
22
|
projectDbExists
|
|
25
|
-
} from "./index-
|
|
23
|
+
} from "./index-snx2vb1x.js";
|
|
24
|
+
import {
|
|
25
|
+
appendCoreEventSync
|
|
26
|
+
} from "./index-3mzc5rqc.js";
|
|
26
27
|
import {
|
|
27
28
|
observeDelegationLedgerPressure
|
|
28
|
-
} from "./index-
|
|
29
|
+
} from "./index-zbygz217.js";
|
|
29
30
|
import {
|
|
30
31
|
EvidenceLockTimeoutError,
|
|
31
32
|
withEvidenceLock
|
|
32
|
-
} from "./index-
|
|
33
|
+
} from "./index-dz15f98h.js";
|
|
33
34
|
import {
|
|
34
35
|
telemetry
|
|
35
|
-
} from "./index-
|
|
36
|
+
} from "./index-dtwj4hsk.js";
|
|
36
37
|
import {
|
|
37
38
|
canonicalRootKeyFresh,
|
|
38
39
|
sameProjectRoot
|
|
39
|
-
} from "./index-
|
|
40
|
+
} from "./index-te9hq2f4.js";
|
|
40
41
|
import {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
} from "./index-
|
|
42
|
+
bunWrite,
|
|
43
|
+
init_bun_compat
|
|
44
|
+
} from "./index-qbxk2rn6.js";
|
|
44
45
|
import {
|
|
45
46
|
criticalWarn,
|
|
46
47
|
init_logger,
|
|
47
48
|
warn
|
|
48
49
|
} from "./index-zgwm4ryv.js";
|
|
50
|
+
import {
|
|
51
|
+
exports_external,
|
|
52
|
+
init_zod
|
|
53
|
+
} from "./index-gknz3bv1.js";
|
|
49
54
|
import {
|
|
50
55
|
__require
|
|
51
56
|
} from "./index-a76rekgs.js";
|
|
52
57
|
|
|
53
|
-
// src/background/pending-delegations.ts
|
|
58
|
+
// src/background/pending-delegations.ts
|
|
59
|
+
init_zod();
|
|
60
|
+
import { createHash as createHash5 } from "crypto";
|
|
61
|
+
import * as fs3 from "fs";
|
|
62
|
+
import * as path4 from "path";
|
|
63
|
+
import { isDeepStrictEqual } from "util";
|
|
64
|
+
|
|
65
|
+
// src/pr-review/persistence.ts
|
|
66
|
+
import { createHash, randomUUID } from "crypto";
|
|
67
|
+
import * as fsp from "fs/promises";
|
|
68
|
+
import * as path from "path";
|
|
69
|
+
function normalizeSessionID(sessionID) {
|
|
70
|
+
const normalized = sessionID.trim();
|
|
71
|
+
if (!normalized) {
|
|
72
|
+
throw new Error("BLOCKED: PR workflow gate requires a non-empty sessionID");
|
|
73
|
+
}
|
|
74
|
+
return normalized;
|
|
75
|
+
}
|
|
76
|
+
function prWorkflowSessionFileStem(sessionID) {
|
|
77
|
+
const normalized = normalizeSessionID(sessionID);
|
|
78
|
+
const slug = normalized.replace(/[^A-Za-z0-9_.-]/g, "_").slice(0, 80) || "session";
|
|
79
|
+
const digest = createHash("sha256").update(normalized).digest("hex").slice(0, 12);
|
|
80
|
+
return `${slug}-${digest}`;
|
|
81
|
+
}
|
|
82
|
+
var WORKFLOW_GATE_DIR = "pr-workflow-gates";
|
|
83
|
+
var WORKFLOW_STATE_COORDINATION_PREFIX = "pr-workflow.state";
|
|
84
|
+
var WORKFLOW_STATE_ENTITY_KEY = "state";
|
|
85
|
+
function workflowGateStateRelativePath(sessionID) {
|
|
86
|
+
return path.join(WORKFLOW_GATE_DIR, `${prWorkflowSessionFileStem(sessionID)}.json`);
|
|
87
|
+
}
|
|
88
|
+
function workflowGateStateLockRelativePath(sessionID) {
|
|
89
|
+
return path.join(WORKFLOW_GATE_DIR, `${prWorkflowSessionFileStem(sessionID)}.lock`);
|
|
90
|
+
}
|
|
91
|
+
function workflowCheckoutMutationLockRelativePath() {
|
|
92
|
+
return path.join(WORKFLOW_GATE_DIR, "checkout.lock");
|
|
93
|
+
}
|
|
94
|
+
function workflowGateStatePath(directory, sessionID) {
|
|
95
|
+
return validateSwarmPath(directory, workflowGateStateRelativePath(sessionID));
|
|
96
|
+
}
|
|
97
|
+
function workflowGateStateImportedPath(directory, sessionID) {
|
|
98
|
+
return `${workflowGateStatePath(directory, sessionID)}.imported`;
|
|
99
|
+
}
|
|
100
|
+
function workflowGateStateProjectionMarkerPath(directory, sessionID) {
|
|
101
|
+
return `${workflowGateStatePath(directory, sessionID)}.sqlite-projection`;
|
|
102
|
+
}
|
|
103
|
+
function workflowGateStateCoordinationNamespace(sessionID) {
|
|
104
|
+
return `${WORKFLOW_STATE_COORDINATION_PREFIX}:${prWorkflowSessionFileStem(sessionID)}`;
|
|
105
|
+
}
|
|
106
|
+
function workflowGateStateLockPath(directory, sessionID) {
|
|
107
|
+
return validateSwarmPath(directory, workflowGateStateLockRelativePath(sessionID));
|
|
108
|
+
}
|
|
109
|
+
function workflowCheckoutMutationLockPath(directory) {
|
|
110
|
+
return validateSwarmPath(directory, workflowCheckoutMutationLockRelativePath());
|
|
111
|
+
}
|
|
112
|
+
var MAX_TRACKED_SESSIONS = 200;
|
|
113
|
+
var MAX_COMPLETED_CHECKOUT_LOCK_OWNERS = 64;
|
|
114
|
+
var WINDOWS_RENAME_MAX_RETRIES = 3;
|
|
115
|
+
var RENAME_RETRY_DELAY_MS = 10;
|
|
116
|
+
var STATE_MUTATION_LOCK_MAX_ATTEMPTS = 50;
|
|
117
|
+
var STATE_MUTATION_LOCK_RETRY_DELAY_MS = 10;
|
|
118
|
+
var STATE_MUTATION_LOCK_UNINITIALIZED_STALE_MS = 30000;
|
|
119
|
+
var CHECKOUT_MUTATION_ACTION_TIMEOUT_MS = 5 * 60000;
|
|
120
|
+
var MAX_COORDINATION_PAYLOAD_CHARS = 1048576;
|
|
121
|
+
var trackedStatesByProjectSession = new Map;
|
|
122
|
+
var pendingStateMutationsByProjectSession = new Map;
|
|
123
|
+
var pendingCheckoutMutationsByProject = new Map;
|
|
124
|
+
var completedCheckoutLockOwners = new Map;
|
|
125
|
+
function isoNow() {
|
|
126
|
+
return new Date().toISOString();
|
|
127
|
+
}
|
|
128
|
+
function delay(ms) {
|
|
129
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
130
|
+
}
|
|
131
|
+
function sameBigIntFileIdentity(left, right) {
|
|
132
|
+
return left.dev === right.dev && left.ino === right.ino;
|
|
133
|
+
}
|
|
134
|
+
function normalizeComparableFsPath(value) {
|
|
135
|
+
const normalized = path.normalize(path.resolve(value));
|
|
136
|
+
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
137
|
+
}
|
|
138
|
+
function stateCacheKey(directory, sessionID) {
|
|
139
|
+
return `${canonicalRootKeyFresh(directory)}\x00${normalizeSessionID(sessionID)}`;
|
|
140
|
+
}
|
|
141
|
+
function rememberState(directory, state) {
|
|
142
|
+
const cacheKey = stateCacheKey(directory, state.sessionID);
|
|
143
|
+
trackedStatesByProjectSession.delete(cacheKey);
|
|
144
|
+
trackedStatesByProjectSession.set(cacheKey, state);
|
|
145
|
+
while (trackedStatesByProjectSession.size > MAX_TRACKED_SESSIONS) {
|
|
146
|
+
const oldestKey = trackedStatesByProjectSession.keys().next().value;
|
|
147
|
+
if (!oldestKey)
|
|
148
|
+
break;
|
|
149
|
+
trackedStatesByProjectSession.delete(oldestKey);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function forgetTrackedPrWorkflowState(directory, sessionID) {
|
|
153
|
+
trackedStatesByProjectSession.delete(stateCacheKey(directory, sessionID));
|
|
154
|
+
}
|
|
155
|
+
function resetPrReviewPersistenceCaches() {
|
|
156
|
+
trackedStatesByProjectSession.clear();
|
|
157
|
+
pendingStateMutationsByProjectSession.clear();
|
|
158
|
+
pendingCheckoutMutationsByProject.clear();
|
|
159
|
+
completedCheckoutLockOwners.clear();
|
|
160
|
+
}
|
|
161
|
+
function defaultIsProcessAlive(pid) {
|
|
162
|
+
try {
|
|
163
|
+
process.kill(pid, 0);
|
|
164
|
+
return true;
|
|
165
|
+
} catch (error) {
|
|
166
|
+
return error.code !== "ESRCH";
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
var defaultPersistenceHooks = {
|
|
170
|
+
nowMs: () => Date.now(),
|
|
171
|
+
isProcessAlive: defaultIsProcessAlive,
|
|
172
|
+
openCheckoutLock: (lockPath) => fsp.open(lockPath, "wx"),
|
|
173
|
+
removeCheckoutLock: (lockPath) => fsp.rm(lockPath),
|
|
174
|
+
checkoutMutationActionTimeoutMs: CHECKOUT_MUTATION_ACTION_TIMEOUT_MS,
|
|
175
|
+
rename: (from, to) => fsp.rename(from, to)
|
|
176
|
+
};
|
|
177
|
+
var hooks = defaultPersistenceHooks;
|
|
178
|
+
function bindPrReviewPersistenceHooks(nextHooks) {
|
|
179
|
+
hooks = nextHooks;
|
|
180
|
+
}
|
|
181
|
+
var codec;
|
|
182
|
+
var observabilityFieldNames = [];
|
|
183
|
+
function bindPrReviewStateCodec(nextCodec, nextObservabilityFieldNames) {
|
|
184
|
+
codec = nextCodec;
|
|
185
|
+
observabilityFieldNames = nextObservabilityFieldNames;
|
|
186
|
+
}
|
|
187
|
+
function requireCodec() {
|
|
188
|
+
if (!codec) {
|
|
189
|
+
throw new Error("BLOCKED: PR workflow persistence codec is not bound (the gate must bind it at module init)");
|
|
190
|
+
}
|
|
191
|
+
return codec;
|
|
192
|
+
}
|
|
193
|
+
async function ensurePrWorkflowSafeParentDirectory(directory, filePath) {
|
|
194
|
+
const swarmRoot = path.resolve(directory, ".swarm");
|
|
195
|
+
const parentPath = path.dirname(path.resolve(filePath));
|
|
196
|
+
const relativeParent = path.relative(swarmRoot, parentPath);
|
|
197
|
+
if (relativeParent === ".." || relativeParent.startsWith(`..${path.sep}`) || path.isAbsolute(relativeParent)) {
|
|
198
|
+
throw new Error("BLOCKED: PR workflow atomic destination escapes the project .swarm directory");
|
|
199
|
+
}
|
|
200
|
+
await fsp.mkdir(swarmRoot, { recursive: true });
|
|
201
|
+
let currentPath = swarmRoot;
|
|
202
|
+
let currentIdentity = await assertSafeDirectory(currentPath, undefined);
|
|
203
|
+
for (const segment of relativeParent.split(path.sep).filter(Boolean)) {
|
|
204
|
+
const nextPath = path.join(currentPath, segment);
|
|
205
|
+
await hooks.beforeSafeDirectoryCreate?.(currentPath, nextPath);
|
|
206
|
+
await assertSafeDirectory(currentPath, currentIdentity);
|
|
207
|
+
try {
|
|
208
|
+
await fsp.mkdir(nextPath);
|
|
209
|
+
} catch (error) {
|
|
210
|
+
if (error.code !== "EEXIST")
|
|
211
|
+
throw error;
|
|
212
|
+
}
|
|
213
|
+
await assertSafeDirectory(currentPath, currentIdentity);
|
|
214
|
+
const nextIdentity = await assertSafeDirectory(nextPath, undefined);
|
|
215
|
+
const [realCurrent, realNext] = await Promise.all([
|
|
216
|
+
fsp.realpath(currentPath),
|
|
217
|
+
fsp.realpath(nextPath)
|
|
218
|
+
]);
|
|
219
|
+
if (normalizeComparableFsPath(path.dirname(realNext)) !== normalizeComparableFsPath(realCurrent)) {
|
|
220
|
+
throw new Error("BLOCKED: PR workflow directory creation escaped the project .swarm tree");
|
|
221
|
+
}
|
|
222
|
+
currentPath = nextPath;
|
|
223
|
+
currentIdentity = nextIdentity;
|
|
224
|
+
}
|
|
225
|
+
const realRoot = await fsp.realpath(swarmRoot);
|
|
226
|
+
const realParent = await fsp.realpath(parentPath);
|
|
227
|
+
const relativeRealParent = path.relative(realRoot, realParent);
|
|
228
|
+
if (relativeRealParent === ".." || relativeRealParent.startsWith(`..${path.sep}`) || path.isAbsolute(relativeRealParent)) {
|
|
229
|
+
throw new Error("BLOCKED: PR workflow atomic parent escapes the project .swarm directory");
|
|
230
|
+
}
|
|
231
|
+
return realParent;
|
|
232
|
+
}
|
|
233
|
+
async function assertSafeDirectory(directoryPath, expectedIdentity) {
|
|
234
|
+
const stat2 = await fsp.lstat(directoryPath, { bigint: true });
|
|
235
|
+
if (stat2.isSymbolicLink() || !stat2.isDirectory() || expectedIdentity && !sameBigIntFileIdentity(stat2, expectedIdentity)) {
|
|
236
|
+
throw new Error("BLOCKED: PR workflow .swarm path must be a real directory and must not change during the operation");
|
|
237
|
+
}
|
|
238
|
+
return { dev: stat2.dev, ino: stat2.ino };
|
|
239
|
+
}
|
|
240
|
+
async function assertOpenedSwarmFileIdentity(directory, filePath, handle, expectedParent, label) {
|
|
241
|
+
const openedStat = await handle.stat({ bigint: true });
|
|
242
|
+
if (!openedStat.isFile())
|
|
243
|
+
throw new Error(`BLOCKED: ${label} is not a file`);
|
|
244
|
+
await assertClosedSwarmFileIdentity(directory, filePath, openedStat, expectedParent, label);
|
|
245
|
+
return { dev: openedStat.dev, ino: openedStat.ino };
|
|
246
|
+
}
|
|
247
|
+
async function assertClosedSwarmFileIdentity(directory, filePath, expectedIdentity, expectedParent, label) {
|
|
248
|
+
if (!expectedIdentity)
|
|
249
|
+
throw new Error(`BLOCKED: ${label} has no identity`);
|
|
250
|
+
const [stat2, realRoot, realFile, realParent] = await Promise.all([
|
|
251
|
+
fsp.lstat(filePath, { bigint: true }),
|
|
252
|
+
fsp.realpath(path.resolve(directory, ".swarm")),
|
|
253
|
+
fsp.realpath(filePath),
|
|
254
|
+
fsp.realpath(path.dirname(filePath))
|
|
255
|
+
]);
|
|
256
|
+
const relativeFile = path.relative(realRoot, realFile);
|
|
257
|
+
if (stat2.isSymbolicLink() || !stat2.isFile() || !sameBigIntFileIdentity(stat2, expectedIdentity) || normalizeComparableFsPath(realParent) !== normalizeComparableFsPath(expectedParent) || relativeFile === "" || relativeFile === ".." || relativeFile.startsWith(`..${path.sep}`) || path.isAbsolute(relativeFile)) {
|
|
258
|
+
throw new Error(`BLOCKED: ${label} changed or escaped .swarm`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
async function writeAtomicJson(directory, filePath, value) {
|
|
262
|
+
const safeParent = await ensurePrWorkflowSafeParentDirectory(directory, filePath);
|
|
263
|
+
const tempPath = `${filePath}.tmp.${process.pid}.${randomUUID()}`;
|
|
264
|
+
let tempIdentity;
|
|
265
|
+
let lastError;
|
|
266
|
+
try {
|
|
267
|
+
const handle = await fsp.open(tempPath, "wx");
|
|
268
|
+
try {
|
|
269
|
+
await hooks.beforeAtomicTempWrite?.();
|
|
270
|
+
tempIdentity = await assertOpenedSwarmFileIdentity(directory, tempPath, handle, safeParent, "PR workflow atomic temporary file");
|
|
271
|
+
await handle.writeFile(JSON.stringify(value, null, 2), "utf-8");
|
|
272
|
+
await handle.sync();
|
|
273
|
+
} finally {
|
|
274
|
+
await handle.close();
|
|
275
|
+
}
|
|
276
|
+
await hooks.beforeAtomicRename?.();
|
|
277
|
+
await assertClosedSwarmFileIdentity(directory, tempPath, tempIdentity, safeParent, "PR workflow atomic temporary file");
|
|
278
|
+
for (let attempt = 0;attempt < WINDOWS_RENAME_MAX_RETRIES; attempt++) {
|
|
279
|
+
try {
|
|
280
|
+
await hooks.rename(tempPath, filePath);
|
|
281
|
+
lastError = undefined;
|
|
282
|
+
break;
|
|
283
|
+
} catch (error) {
|
|
284
|
+
lastError = error;
|
|
285
|
+
const code = error.code;
|
|
286
|
+
if (code !== "EEXIST" && code !== "EPERM" && code !== "EBUSY") {
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
if (attempt < WINDOWS_RENAME_MAX_RETRIES - 1) {
|
|
290
|
+
await delay(RENAME_RETRY_DELAY_MS);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (lastError) {
|
|
295
|
+
throw lastError;
|
|
296
|
+
}
|
|
297
|
+
await assertClosedSwarmFileIdentity(directory, filePath, tempIdentity, safeParent, "PR workflow atomic destination file");
|
|
298
|
+
} finally {
|
|
299
|
+
try {
|
|
300
|
+
await fsp.rm(tempPath, { force: true });
|
|
301
|
+
} catch {}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
async function writePrWorkflowAtomicJson(directory, filePath, value) {
|
|
305
|
+
await writeAtomicJson(directory, filePath, value);
|
|
306
|
+
}
|
|
307
|
+
async function readPrWorkflowGateStateFromDisk(directory, sessionID, options = {}) {
|
|
308
|
+
const authoritative = await readPrWorkflowGateStateFromCoordination(directory, sessionID);
|
|
309
|
+
if (authoritative) {
|
|
310
|
+
await repairImportedWorkflowGateShadow(directory, sessionID, authoritative);
|
|
311
|
+
return authoritative;
|
|
312
|
+
}
|
|
313
|
+
return importLegacyPrWorkflowGateStateIfNeeded(directory, sessionID, options.allowSalvagedImport === true);
|
|
314
|
+
}
|
|
315
|
+
async function readPrWorkflowGateStateFileRawFromDisk(filePath, stateLabel) {
|
|
316
|
+
let raw;
|
|
317
|
+
try {
|
|
318
|
+
raw = await fsp.readFile(filePath, "utf-8");
|
|
319
|
+
} catch (error) {
|
|
320
|
+
if (error.code === "ENOENT")
|
|
321
|
+
return null;
|
|
322
|
+
throw error;
|
|
323
|
+
}
|
|
324
|
+
let parsedJson;
|
|
325
|
+
try {
|
|
326
|
+
parsedJson = JSON.parse(raw);
|
|
327
|
+
} catch {
|
|
328
|
+
throw new Error(`BLOCKED: PR workflow gate state for session "${stateLabel}" is not valid JSON`);
|
|
329
|
+
}
|
|
330
|
+
const bound = requireCodec();
|
|
331
|
+
const parsed = bound.safeParse(parsedJson);
|
|
332
|
+
if (!parsed.success) {
|
|
333
|
+
throw new Error(`BLOCKED: PR workflow gate state for session "${stateLabel}" is invalid`);
|
|
334
|
+
}
|
|
335
|
+
return parsed.data;
|
|
336
|
+
}
|
|
337
|
+
async function readPrWorkflowGateStateFileFromDisk(filePath, stateLabel) {
|
|
338
|
+
return readPrWorkflowGateStateFileRawFromDisk(filePath, stateLabel);
|
|
339
|
+
}
|
|
340
|
+
async function readPrWorkflowGateStateFromCoordination(directory, sessionID) {
|
|
341
|
+
const decoded = readPrWorkflowGateStateCoordinationRow(directory, sessionID);
|
|
342
|
+
return decoded?.state ?? null;
|
|
343
|
+
}
|
|
344
|
+
function readPrWorkflowGateStateCoordinationForRecovery(directory, sessionID) {
|
|
345
|
+
const normalizedSessionID = normalizeSessionID(sessionID);
|
|
346
|
+
const row = getCoordinationStateRaw(directory, workflowGateStateCoordinationNamespace(normalizedSessionID), WORKFLOW_STATE_ENTITY_KEY);
|
|
347
|
+
if (!row)
|
|
348
|
+
return { kind: "absent" };
|
|
349
|
+
if (row.payload.length > MAX_COORDINATION_PAYLOAD_CHARS) {
|
|
350
|
+
return {
|
|
351
|
+
kind: "corrupt",
|
|
352
|
+
rowRevision: row.revision,
|
|
353
|
+
generation: row.generation,
|
|
354
|
+
reason: "coordination payload exceeds the maximum size"
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
let parsedJson;
|
|
358
|
+
try {
|
|
359
|
+
parsedJson = JSON.parse(row.payload);
|
|
360
|
+
} catch {
|
|
361
|
+
return {
|
|
362
|
+
kind: "corrupt",
|
|
363
|
+
rowRevision: row.revision,
|
|
364
|
+
generation: row.generation,
|
|
365
|
+
reason: "coordination payload is not valid JSON"
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
const bound = requireCodec();
|
|
369
|
+
const parsed = bound.safeParse(parsedJson);
|
|
370
|
+
if (!parsed.success) {
|
|
371
|
+
return {
|
|
372
|
+
kind: "corrupt",
|
|
373
|
+
rowRevision: row.revision,
|
|
374
|
+
generation: row.generation,
|
|
375
|
+
reason: "coordination payload failed the workflow state schema"
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
if (parsed.data.sessionID !== normalizedSessionID) {
|
|
379
|
+
return {
|
|
380
|
+
kind: "invalid",
|
|
381
|
+
reason: "coordination payload session identity does not match the requested session"
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
if (parsed.data.revision !== row.generation) {
|
|
385
|
+
return {
|
|
386
|
+
kind: "corrupt",
|
|
387
|
+
rowRevision: row.revision,
|
|
388
|
+
generation: row.generation,
|
|
389
|
+
reason: "coordination payload revision does not match its generation"
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
return {
|
|
393
|
+
kind: "valid",
|
|
394
|
+
rowRevision: row.revision,
|
|
395
|
+
generation: row.generation,
|
|
396
|
+
state: parsed.data
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
function readPrWorkflowGateStateCoordinationRow(directory, sessionID) {
|
|
400
|
+
const row = getCoordinationState(directory, workflowGateStateCoordinationNamespace(sessionID), WORKFLOW_STATE_ENTITY_KEY);
|
|
401
|
+
if (!row)
|
|
402
|
+
return null;
|
|
403
|
+
let parsedJson;
|
|
404
|
+
try {
|
|
405
|
+
parsedJson = JSON.parse(row.payload);
|
|
406
|
+
} catch {
|
|
407
|
+
throw new Error(`BLOCKED: PR workflow gate state for session "${sessionID}" is not valid JSON`);
|
|
408
|
+
}
|
|
409
|
+
const bound = requireCodec();
|
|
410
|
+
const parsed = bound.safeParse(parsedJson);
|
|
411
|
+
if (!parsed.success || parsed.data.sessionID !== normalizeSessionID(sessionID) || parsed.data.revision !== row.generation) {
|
|
412
|
+
throw new Error(`BLOCKED: PR workflow gate state for session "${sessionID}" is invalid`);
|
|
413
|
+
}
|
|
414
|
+
return { rowRevision: row.revision, state: parsed.data };
|
|
415
|
+
}
|
|
416
|
+
function workflowStateStatus(state) {
|
|
417
|
+
const mode = state.mode;
|
|
418
|
+
return typeof mode === "string" && mode.trim().length > 0 ? mode : "active";
|
|
419
|
+
}
|
|
420
|
+
async function fileExists(filePath) {
|
|
421
|
+
try {
|
|
422
|
+
await fsp.stat(filePath);
|
|
423
|
+
return true;
|
|
424
|
+
} catch (error) {
|
|
425
|
+
if (error.code === "ENOENT")
|
|
426
|
+
return false;
|
|
427
|
+
throw error;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
function serializeWorkflowGateState(state) {
|
|
431
|
+
return JSON.stringify(state, null, 2);
|
|
432
|
+
}
|
|
433
|
+
async function readTextFileIfExists(filePath) {
|
|
434
|
+
try {
|
|
435
|
+
return await fsp.readFile(filePath, "utf8");
|
|
436
|
+
} catch (error) {
|
|
437
|
+
if (error.code === "ENOENT")
|
|
438
|
+
return null;
|
|
439
|
+
throw error;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
async function writeProjectionMarker(directory, filePath) {
|
|
443
|
+
await ensurePrWorkflowSafeParentDirectory(directory, filePath);
|
|
444
|
+
await fsp.writeFile(filePath, `sqlite-projection
|
|
445
|
+
`, "utf8");
|
|
446
|
+
}
|
|
447
|
+
async function renameWithRetry(from, to) {
|
|
448
|
+
let lastError;
|
|
449
|
+
for (let attempt = 0;attempt < WINDOWS_RENAME_MAX_RETRIES; attempt++) {
|
|
450
|
+
try {
|
|
451
|
+
await hooks.rename(from, to);
|
|
452
|
+
return;
|
|
453
|
+
} catch (error) {
|
|
454
|
+
lastError = error;
|
|
455
|
+
const code = error.code;
|
|
456
|
+
if (code !== "EEXIST" && code !== "EPERM" && code !== "EBUSY") {
|
|
457
|
+
throw error;
|
|
458
|
+
}
|
|
459
|
+
if (attempt < WINDOWS_RENAME_MAX_RETRIES - 1) {
|
|
460
|
+
await delay(RENAME_RETRY_DELAY_MS);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
throw lastError;
|
|
465
|
+
}
|
|
466
|
+
async function collisionSafeImportedPath(filePath) {
|
|
467
|
+
const canonical = `${filePath}.imported`;
|
|
468
|
+
if (!await fileExists(canonical))
|
|
469
|
+
return canonical;
|
|
470
|
+
for (let suffix = 1;suffix <= 1e4; suffix += 1) {
|
|
471
|
+
const candidate = `${canonical}.${suffix}`;
|
|
472
|
+
if (!await fileExists(candidate))
|
|
473
|
+
return candidate;
|
|
474
|
+
}
|
|
475
|
+
throw new Error(`No collision-safe archive path available for ${path.basename(filePath)}`);
|
|
476
|
+
}
|
|
477
|
+
async function archiveShadowSource(filePath) {
|
|
478
|
+
if (!await fileExists(filePath))
|
|
479
|
+
return;
|
|
480
|
+
await renameWithRetry(filePath, await collisionSafeImportedPath(filePath));
|
|
481
|
+
}
|
|
482
|
+
async function syncWorkflowGateShadowProjection(directory, sessionID, state, options) {
|
|
483
|
+
const filePath = workflowGateStatePath(directory, sessionID);
|
|
484
|
+
if (options.archiveLegacy && await fileExists(filePath)) {
|
|
485
|
+
await archiveShadowSource(filePath);
|
|
486
|
+
}
|
|
487
|
+
await writeAtomicJson(directory, filePath, state);
|
|
488
|
+
await writeProjectionMarker(directory, workflowGateStateProjectionMarkerPath(directory, sessionID));
|
|
489
|
+
}
|
|
490
|
+
async function repairImportedWorkflowGateShadow(directory, sessionID, state) {
|
|
491
|
+
const filePath = workflowGateStatePath(directory, sessionID);
|
|
492
|
+
const markerPath = workflowGateStateProjectionMarkerPath(directory, sessionID);
|
|
493
|
+
const live = await readTextFileIfExists(filePath);
|
|
494
|
+
if (live === null) {
|
|
495
|
+
await syncWorkflowGateShadowProjection(directory, sessionID, state, {
|
|
496
|
+
archiveLegacy: false
|
|
497
|
+
});
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
const canonicalImportedExists = await fileExists(workflowGateStateImportedPath(directory, sessionID));
|
|
501
|
+
const markerExists = await fileExists(markerPath);
|
|
502
|
+
const projected = serializeWorkflowGateState(state);
|
|
503
|
+
if (canonicalImportedExists && markerExists && live === projected)
|
|
504
|
+
return;
|
|
505
|
+
if (canonicalImportedExists && live === projected) {
|
|
506
|
+
await writeProjectionMarker(directory, markerPath);
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
await syncWorkflowGateShadowProjection(directory, sessionID, state, {
|
|
510
|
+
archiveLegacy: true
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
async function removeWorkflowGateShadowProjection(directory, sessionID) {
|
|
514
|
+
for (const filePath of [
|
|
515
|
+
workflowGateStatePath(directory, sessionID),
|
|
516
|
+
workflowGateStateProjectionMarkerPath(directory, sessionID)
|
|
517
|
+
]) {
|
|
518
|
+
try {
|
|
519
|
+
await (hooks.removeShadowProjection ?? ((target) => fsp.rm(target, { force: true })))(filePath);
|
|
520
|
+
} catch (error) {
|
|
521
|
+
if (error.code !== "ENOENT")
|
|
522
|
+
throw error;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
function appendSessionTerminalizationEvent(directory, sessionID, rowRevision) {
|
|
527
|
+
try {
|
|
528
|
+
appendCoreEventSync(directory, {
|
|
529
|
+
type: "pr_workflow_session_terminalized",
|
|
530
|
+
timestamp: isoNow(),
|
|
531
|
+
sessionID,
|
|
532
|
+
rowRevision
|
|
533
|
+
});
|
|
534
|
+
} catch {}
|
|
535
|
+
}
|
|
536
|
+
async function importLegacyPrWorkflowGateStateIfNeeded(directory, sessionID, allowSalvagedImport = false) {
|
|
537
|
+
const filePath = workflowGateStatePath(directory, sessionID);
|
|
538
|
+
let raw;
|
|
539
|
+
try {
|
|
540
|
+
raw = await fsp.readFile(filePath, "utf-8");
|
|
541
|
+
} catch (error) {
|
|
542
|
+
if (error.code === "ENOENT")
|
|
543
|
+
return null;
|
|
544
|
+
throw error;
|
|
545
|
+
}
|
|
546
|
+
const legacy = allowSalvagedImport ? (await (await import("./pr-workflow-gate-zjwt050h.js")).readPrWorkflowGateStateForRecovery(directory, sessionID))?.state ?? null : await readPrWorkflowGateStateFileFromDisk(filePath, sessionID);
|
|
547
|
+
if (!legacy)
|
|
548
|
+
return null;
|
|
549
|
+
const namespace = workflowGateStateCoordinationNamespace(sessionID);
|
|
550
|
+
const outcome = importCoordinationOnce(directory, {
|
|
551
|
+
source: workflowGateStateRelativePath(sessionID),
|
|
552
|
+
sourceDigest: createHash("sha256").update(raw).digest("hex"),
|
|
553
|
+
rowCount: 1,
|
|
554
|
+
emptyNamespace: namespace
|
|
555
|
+
}, () => {
|
|
556
|
+
const result = transitionCoordinationState(directory, {
|
|
557
|
+
namespace,
|
|
558
|
+
entityKey: WORKFLOW_STATE_ENTITY_KEY,
|
|
559
|
+
expectedRevision: null,
|
|
560
|
+
generation: legacy.revision,
|
|
561
|
+
status: workflowStateStatus(legacy),
|
|
562
|
+
payload: JSON.stringify(legacy)
|
|
563
|
+
});
|
|
564
|
+
if (result.outcome !== "applied") {
|
|
565
|
+
throw new Error(`PR workflow gate state import failed: ${result.outcome}`);
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
const authoritative = await readPrWorkflowGateStateFromCoordination(directory, sessionID);
|
|
569
|
+
if (authoritative && outcome === "imported") {
|
|
570
|
+
await syncWorkflowGateShadowProjection(directory, sessionID, authoritative, {
|
|
571
|
+
archiveLegacy: true
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
return authoritative;
|
|
575
|
+
}
|
|
576
|
+
function withDurableObservabilityFields(incoming, current) {
|
|
577
|
+
if (!current || observabilityFieldNames.length === 0)
|
|
578
|
+
return incoming;
|
|
579
|
+
const merged = {
|
|
580
|
+
...{ ...incoming }
|
|
581
|
+
};
|
|
582
|
+
const durable = current;
|
|
583
|
+
const instanceReplaced = current.workflowInstanceId !== undefined && current.workflowInstanceId !== incoming.workflowInstanceId;
|
|
584
|
+
for (const field of observabilityFieldNames) {
|
|
585
|
+
if (instanceReplaced)
|
|
586
|
+
delete merged[field];
|
|
587
|
+
else if (durable[field] !== undefined)
|
|
588
|
+
merged[field] = durable[field];
|
|
589
|
+
else
|
|
590
|
+
delete merged[field];
|
|
591
|
+
}
|
|
592
|
+
return merged;
|
|
593
|
+
}
|
|
594
|
+
function enforceObservabilityOnlyMutation(validated, current) {
|
|
595
|
+
if (!current)
|
|
596
|
+
return;
|
|
597
|
+
const incoming = validated;
|
|
598
|
+
const durable = current;
|
|
599
|
+
const skip = new Set(observabilityFieldNames);
|
|
600
|
+
skip.add("updatedAt");
|
|
601
|
+
skip.add("revision");
|
|
602
|
+
const keys = new Set([...Object.keys(incoming), ...Object.keys(durable)]);
|
|
603
|
+
for (const key of keys) {
|
|
604
|
+
if (skip.has(key))
|
|
605
|
+
continue;
|
|
606
|
+
if (JSON.stringify(incoming[key]) !== JSON.stringify(durable[key])) {
|
|
607
|
+
throw new Error(`BLOCKED: appendObservabilityStateWhileLocked may change only observability fields and updatedAt; field "${key}" also differs from the durable state`);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
async function persistStateRowWhileLocked(directory, validated, bumpRevision, options = {}) {
|
|
612
|
+
const bound = requireCodec();
|
|
613
|
+
let currentAuthoritative = readPrWorkflowGateStateCoordinationRow(directory, validated.sessionID);
|
|
614
|
+
let current = currentAuthoritative?.state ?? null;
|
|
615
|
+
if (!current) {
|
|
616
|
+
current = await readPrWorkflowGateStateFromDisk(directory, validated.sessionID);
|
|
617
|
+
currentAuthoritative = readPrWorkflowGateStateCoordinationRow(directory, validated.sessionID);
|
|
618
|
+
}
|
|
619
|
+
let effective = validated;
|
|
620
|
+
if (bumpRevision) {
|
|
621
|
+
effective = withDurableObservabilityFields(validated, current);
|
|
622
|
+
} else {
|
|
623
|
+
enforceObservabilityOnlyMutation(validated, current);
|
|
624
|
+
}
|
|
625
|
+
if (current ? current.revision !== validated.revision : validated.revision !== 0) {
|
|
626
|
+
throw new Error("BLOCKED: PR workflow gate state changed concurrently; reload the active session state before retrying");
|
|
627
|
+
}
|
|
628
|
+
if (current?.workflowInstanceId && validated.workflowInstanceId !== current.workflowInstanceId && options.replaceWorkflowInstanceId !== current.workflowInstanceId) {
|
|
629
|
+
throw new Error("BLOCKED: PR workflow gate state changed concurrently; reload the active session state before retrying");
|
|
630
|
+
}
|
|
631
|
+
const nextState = bound.parse(bumpRevision ? { ...effective, revision: validated.revision + 1 } : effective);
|
|
632
|
+
const result = transitionCoordinationState(directory, {
|
|
633
|
+
namespace: workflowGateStateCoordinationNamespace(validated.sessionID),
|
|
634
|
+
entityKey: WORKFLOW_STATE_ENTITY_KEY,
|
|
635
|
+
expectedRevision: currentAuthoritative?.rowRevision ?? null,
|
|
636
|
+
generation: nextState.revision,
|
|
637
|
+
status: workflowStateStatus(nextState),
|
|
638
|
+
payload: JSON.stringify(nextState)
|
|
639
|
+
});
|
|
640
|
+
if (result.outcome !== "applied") {
|
|
641
|
+
throw new Error("BLOCKED: PR workflow gate state changed concurrently; reload the active session state before retrying");
|
|
642
|
+
}
|
|
643
|
+
await syncWorkflowGateShadowProjection(directory, validated.sessionID, nextState, { archiveLegacy: false });
|
|
644
|
+
rememberState(directory, nextState);
|
|
645
|
+
return nextState;
|
|
646
|
+
}
|
|
647
|
+
async function writeStateWhileLocked(directory, state, options = {}) {
|
|
648
|
+
const bound = requireCodec();
|
|
649
|
+
const validated = bound.parse(state);
|
|
650
|
+
return persistStateRowWhileLocked(directory, validated, true, options);
|
|
651
|
+
}
|
|
652
|
+
async function appendObservabilityStateWhileLocked(directory, state) {
|
|
653
|
+
const bound = requireCodec();
|
|
654
|
+
const validated = bound.parse(state);
|
|
655
|
+
return persistStateRowWhileLocked(directory, validated, false);
|
|
656
|
+
}
|
|
657
|
+
async function deleteStateWhileLocked(directory, sessionID, options = {}) {
|
|
658
|
+
const normalizedSessionID = normalizeSessionID(sessionID);
|
|
659
|
+
let currentAuthoritative = null;
|
|
660
|
+
let corruptCoordinationRowRevision;
|
|
661
|
+
if (options.allowSalvagedRead) {
|
|
662
|
+
const coordination = readPrWorkflowGateStateCoordinationForRecovery(directory, normalizedSessionID);
|
|
663
|
+
if (coordination.kind === "invalid") {
|
|
664
|
+
throw new Error(`BLOCKED: PR workflow gate state for session "${normalizedSessionID}" is invalid (${coordination.reason})`);
|
|
665
|
+
}
|
|
666
|
+
if (coordination.kind === "valid") {
|
|
667
|
+
currentAuthoritative = {
|
|
668
|
+
rowRevision: coordination.rowRevision,
|
|
669
|
+
state: coordination.state
|
|
670
|
+
};
|
|
671
|
+
} else if (coordination.kind === "corrupt") {
|
|
672
|
+
corruptCoordinationRowRevision = coordination.rowRevision;
|
|
673
|
+
}
|
|
674
|
+
} else {
|
|
675
|
+
currentAuthoritative = readPrWorkflowGateStateCoordinationRow(directory, normalizedSessionID);
|
|
676
|
+
}
|
|
677
|
+
let recoveryState = null;
|
|
678
|
+
if (!currentAuthoritative) {
|
|
679
|
+
if (options.allowSalvagedRead) {
|
|
680
|
+
const { readPrWorkflowGateStateForRecovery } = await import("./pr-workflow-gate-zjwt050h.js");
|
|
681
|
+
recoveryState = (await readPrWorkflowGateStateForRecovery(directory, normalizedSessionID))?.state ?? null;
|
|
682
|
+
}
|
|
683
|
+
if (!recoveryState) {
|
|
684
|
+
await readPrWorkflowGateStateFromDisk(directory, normalizedSessionID, {
|
|
685
|
+
allowSalvagedImport: options.allowSalvagedRead === true
|
|
686
|
+
});
|
|
687
|
+
currentAuthoritative = readPrWorkflowGateStateCoordinationRow(directory, normalizedSessionID);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
const currentRevision = currentAuthoritative?.state.revision ?? recoveryState?.revision;
|
|
691
|
+
if (options.expectedStateRevision !== undefined && currentRevision !== options.expectedStateRevision) {
|
|
692
|
+
throw new Error("BLOCKED: PR workflow gate state changed concurrently; reload the active session state before retrying");
|
|
693
|
+
}
|
|
694
|
+
const authoritativeRowRevision = currentAuthoritative?.rowRevision ?? (recoveryState ? corruptCoordinationRowRevision : undefined);
|
|
695
|
+
if (authoritativeRowRevision !== undefined) {
|
|
696
|
+
const deleted = deleteCoordinationState(directory, workflowGateStateCoordinationNamespace(normalizedSessionID), WORKFLOW_STATE_ENTITY_KEY, authoritativeRowRevision);
|
|
697
|
+
if (!deleted) {
|
|
698
|
+
throw new Error("BLOCKED: PR workflow gate state changed concurrently; reload the active session state before retrying");
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
await removeWorkflowGateShadowProjection(directory, normalizedSessionID);
|
|
702
|
+
forgetTrackedPrWorkflowState(directory, normalizedSessionID);
|
|
703
|
+
}
|
|
704
|
+
async function terminalizePrWorkflowGateForSession(directory, sessionID) {
|
|
705
|
+
const normalizedSessionID = normalizeSessionID(sessionID);
|
|
706
|
+
await withSessionStateMutation(directory, normalizedSessionID, async () => {
|
|
707
|
+
const row = getCoordinationStateRaw(directory, workflowGateStateCoordinationNamespace(normalizedSessionID), WORKFLOW_STATE_ENTITY_KEY);
|
|
708
|
+
await removeWorkflowGateShadowProjection(directory, normalizedSessionID);
|
|
709
|
+
await hooks.beforeTerminalizationDelete?.();
|
|
710
|
+
if (row) {
|
|
711
|
+
const deleted = deleteCoordinationState(directory, workflowGateStateCoordinationNamespace(normalizedSessionID), WORKFLOW_STATE_ENTITY_KEY, row.revision);
|
|
712
|
+
if (!deleted) {
|
|
713
|
+
throw new Error("BLOCKED: PR workflow gate state changed concurrently; retry session terminalization");
|
|
714
|
+
}
|
|
715
|
+
appendSessionTerminalizationEvent(directory, normalizedSessionID, row.revision);
|
|
716
|
+
}
|
|
717
|
+
forgetTrackedPrWorkflowState(directory, normalizedSessionID);
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
function checkoutMutationProjectKey(directory) {
|
|
721
|
+
return canonicalRootKeyFresh(directory);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
class PrWorkflowCheckoutMutationTimeoutError extends Error {
|
|
725
|
+
phase;
|
|
726
|
+
code = "PR_WORKFLOW_CHECKOUT_MUTATION_TIMEOUT";
|
|
727
|
+
retryable = false;
|
|
728
|
+
constructor(phase) {
|
|
729
|
+
super(phase === "queue" ? "BLOCKED: timed out waiting for the active PR workflow checkout mutation; the existing owner still holds serialization and must settle before retrying" : "BLOCKED: PR workflow checkout mutation exceeded its execution deadline; serialization remains held until the in-flight action actually settles");
|
|
730
|
+
this.phase = phase;
|
|
731
|
+
this.name = "PrWorkflowCheckoutMutationTimeoutError";
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
async function withCheckoutMutationDeadline(promise, phase) {
|
|
735
|
+
let timeout;
|
|
736
|
+
try {
|
|
737
|
+
return await Promise.race([
|
|
738
|
+
promise,
|
|
739
|
+
new Promise((resolve2) => {
|
|
740
|
+
timeout = setTimeout(() => {
|
|
741
|
+
resolve2({
|
|
742
|
+
status: "timeout",
|
|
743
|
+
error: new PrWorkflowCheckoutMutationTimeoutError(phase)
|
|
744
|
+
});
|
|
745
|
+
}, hooks.checkoutMutationActionTimeoutMs);
|
|
746
|
+
})
|
|
747
|
+
]);
|
|
748
|
+
} finally {
|
|
749
|
+
if (timeout)
|
|
750
|
+
clearTimeout(timeout);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
async function withPrWorkflowCheckoutMutationLock(directory, action) {
|
|
754
|
+
const key = checkoutMutationProjectKey(directory);
|
|
755
|
+
const previous = pendingCheckoutMutationsByProject.get(key) ?? Promise.resolve();
|
|
756
|
+
let release;
|
|
757
|
+
const current = new Promise((resolve2) => {
|
|
758
|
+
release = resolve2;
|
|
759
|
+
});
|
|
760
|
+
const queued = previous.then(() => current);
|
|
761
|
+
pendingCheckoutMutationsByProject.set(key, queued);
|
|
762
|
+
const previousResult = await withCheckoutMutationDeadline(previous.then(() => ({ status: "ready" })), "queue");
|
|
763
|
+
if (previousResult.status === "timeout") {
|
|
764
|
+
release();
|
|
765
|
+
queued.then(() => {
|
|
766
|
+
if (pendingCheckoutMutationsByProject.get(key) === queued) {
|
|
767
|
+
pendingCheckoutMutationsByProject.delete(key);
|
|
768
|
+
}
|
|
769
|
+
});
|
|
770
|
+
throw previousResult.error;
|
|
771
|
+
}
|
|
772
|
+
let lock;
|
|
773
|
+
try {
|
|
774
|
+
lock = await acquireCheckoutMutationLock(directory);
|
|
775
|
+
} catch (error) {
|
|
776
|
+
release();
|
|
777
|
+
if (pendingCheckoutMutationsByProject.get(key) === queued) {
|
|
778
|
+
pendingCheckoutMutationsByProject.delete(key);
|
|
779
|
+
}
|
|
780
|
+
throw error;
|
|
781
|
+
}
|
|
782
|
+
const actionOutcome = Promise.resolve().then(action).then((value) => ({ status: "fulfilled", value }), (error) => ({ status: "rejected", error }));
|
|
783
|
+
const outcome = await withCheckoutMutationDeadline(actionOutcome, "action");
|
|
784
|
+
if (outcome.status === "timeout") {
|
|
785
|
+
actionOutcome.then(async () => {
|
|
786
|
+
try {
|
|
787
|
+
await releaseCheckoutMutationLock(lock);
|
|
788
|
+
} catch {} finally {
|
|
789
|
+
release();
|
|
790
|
+
if (pendingCheckoutMutationsByProject.get(key) === queued) {
|
|
791
|
+
pendingCheckoutMutationsByProject.delete(key);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
});
|
|
795
|
+
throw outcome.error;
|
|
796
|
+
}
|
|
797
|
+
try {
|
|
798
|
+
if (outcome.status === "rejected")
|
|
799
|
+
throw outcome.error;
|
|
800
|
+
return outcome.value;
|
|
801
|
+
} finally {
|
|
802
|
+
try {
|
|
803
|
+
await releaseCheckoutMutationLock(lock);
|
|
804
|
+
} finally {
|
|
805
|
+
release();
|
|
806
|
+
if (pendingCheckoutMutationsByProject.get(key) === queued) {
|
|
807
|
+
pendingCheckoutMutationsByProject.delete(key);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
async function acquireCheckoutMutationLock(directory) {
|
|
813
|
+
const lockPath = workflowCheckoutMutationLockPath(directory);
|
|
814
|
+
const verifiedParent = await ensurePrWorkflowSafeParentDirectory(directory, lockPath);
|
|
815
|
+
for (let attempt = 0;attempt < STATE_MUTATION_LOCK_MAX_ATTEMPTS; attempt++) {
|
|
816
|
+
try {
|
|
817
|
+
const handle = await hooks.openCheckoutLock(lockPath);
|
|
818
|
+
const lock = {
|
|
819
|
+
ownerToken: randomUUID(),
|
|
820
|
+
pid: process.pid,
|
|
821
|
+
createdAtMs: hooks.nowMs()
|
|
822
|
+
};
|
|
823
|
+
let lockIdentity;
|
|
824
|
+
let writeError;
|
|
825
|
+
try {
|
|
826
|
+
const openedStat = await handle.stat({ bigint: true });
|
|
827
|
+
lockIdentity = { dev: openedStat.dev, ino: openedStat.ino };
|
|
828
|
+
lockIdentity = await assertOpenedSwarmFileIdentity(directory, lockPath, handle, verifiedParent, "PR workflow checkout mutation lock");
|
|
829
|
+
await hooks.beforeCheckoutLockWrite?.();
|
|
830
|
+
await handle.writeFile(JSON.stringify(lock), "utf-8");
|
|
831
|
+
} catch (error) {
|
|
832
|
+
writeError = error;
|
|
833
|
+
} finally {
|
|
834
|
+
await handle.close().catch(() => {
|
|
835
|
+
return;
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
if (writeError) {
|
|
839
|
+
if (lockIdentity) {
|
|
840
|
+
await removeCheckoutMutationLockByIdentity(directory, lockPath, lockIdentity, verifiedParent);
|
|
841
|
+
}
|
|
842
|
+
throw writeError;
|
|
843
|
+
}
|
|
844
|
+
return { path: lockPath, ownerToken: lock.ownerToken };
|
|
845
|
+
} catch (error) {
|
|
846
|
+
if (error.code !== "EEXIST")
|
|
847
|
+
throw error;
|
|
848
|
+
if (await reclaimAbandonedCheckoutMutationLock(lockPath))
|
|
849
|
+
continue;
|
|
850
|
+
if (attempt < STATE_MUTATION_LOCK_MAX_ATTEMPTS - 1) {
|
|
851
|
+
await delay(STATE_MUTATION_LOCK_RETRY_DELAY_MS);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
throw new Error("BLOCKED: PR workflow checkout mutation is being handled by another process; retry after that checkout settles");
|
|
856
|
+
}
|
|
857
|
+
async function releaseCheckoutMutationLock(lock) {
|
|
858
|
+
let lastError;
|
|
859
|
+
for (let attempt = 0;attempt < WINDOWS_RENAME_MAX_RETRIES; attempt++) {
|
|
860
|
+
try {
|
|
861
|
+
if (await removeCheckoutMutationLockIfOwned(lock.path, lock.ownerToken)) {
|
|
862
|
+
completedCheckoutLockOwners.delete(lock.path);
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
lastError = new Error("PR workflow checkout mutation lock ownership changed before release");
|
|
866
|
+
} catch (error) {
|
|
867
|
+
lastError = error;
|
|
868
|
+
}
|
|
869
|
+
if (attempt < WINDOWS_RENAME_MAX_RETRIES - 1) {
|
|
870
|
+
await delay(RENAME_RETRY_DELAY_MS);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
rememberCompletedCheckoutLockOwner(lock.path, lock.ownerToken);
|
|
874
|
+
throw lastError instanceof Error ? lastError : new Error("PR workflow checkout mutation lock release failed");
|
|
875
|
+
}
|
|
876
|
+
async function reclaimAbandonedCheckoutMutationLock(lockPath) {
|
|
877
|
+
const lock = await readCheckoutMutationLock(lockPath);
|
|
878
|
+
if (lock) {
|
|
879
|
+
if (lock.pid === process.pid && completedCheckoutLockOwners.get(lockPath) === lock.ownerToken) {
|
|
880
|
+
const removed = await removeCheckoutMutationLockIfOwned(lockPath, lock.ownerToken);
|
|
881
|
+
if (removed)
|
|
882
|
+
completedCheckoutLockOwners.delete(lockPath);
|
|
883
|
+
return removed;
|
|
884
|
+
}
|
|
885
|
+
if (hooks.isProcessAlive(lock.pid))
|
|
886
|
+
return false;
|
|
887
|
+
return removeCheckoutMutationLockIfOwned(lockPath, lock.ownerToken);
|
|
888
|
+
}
|
|
889
|
+
try {
|
|
890
|
+
const stat2 = await fsp.stat(lockPath);
|
|
891
|
+
if (hooks.nowMs() - stat2.mtimeMs < STATE_MUTATION_LOCK_UNINITIALIZED_STALE_MS) {
|
|
892
|
+
return false;
|
|
893
|
+
}
|
|
894
|
+
await fsp.rm(lockPath, { force: true });
|
|
895
|
+
return true;
|
|
896
|
+
} catch (error) {
|
|
897
|
+
if (error.code === "ENOENT")
|
|
898
|
+
return true;
|
|
899
|
+
throw error;
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
async function readCheckoutMutationLock(lockPath) {
|
|
903
|
+
let raw;
|
|
904
|
+
try {
|
|
905
|
+
raw = await fsp.readFile(lockPath, "utf-8");
|
|
906
|
+
} catch (error) {
|
|
907
|
+
if (error.code === "ENOENT")
|
|
908
|
+
return null;
|
|
909
|
+
throw error;
|
|
910
|
+
}
|
|
911
|
+
try {
|
|
912
|
+
const parsed = JSON.parse(raw);
|
|
913
|
+
if (typeof parsed === "object" && parsed !== null && typeof parsed.ownerToken === "string" && parsed.ownerToken.length > 0 && typeof parsed.pid === "number" && Number.isInteger(parsed.pid) && parsed.pid > 0 && typeof parsed.createdAtMs === "number" && Number.isFinite(parsed.createdAtMs)) {
|
|
914
|
+
return parsed;
|
|
915
|
+
}
|
|
916
|
+
} catch {}
|
|
917
|
+
return null;
|
|
918
|
+
}
|
|
919
|
+
async function removeCheckoutMutationLockIfOwned(lockPath, ownerToken) {
|
|
920
|
+
const lock = await readCheckoutMutationLock(lockPath);
|
|
921
|
+
if (!lock || lock.ownerToken !== ownerToken)
|
|
922
|
+
return false;
|
|
923
|
+
try {
|
|
924
|
+
await hooks.removeCheckoutLock(lockPath);
|
|
925
|
+
return true;
|
|
926
|
+
} catch (error) {
|
|
927
|
+
if (error.code === "ENOENT")
|
|
928
|
+
return false;
|
|
929
|
+
throw error;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
async function removeCheckoutMutationLockByIdentity(directory, lockPath, identity, verifiedParent) {
|
|
933
|
+
let lastError;
|
|
934
|
+
for (let attempt = 0;attempt < WINDOWS_RENAME_MAX_RETRIES; attempt++) {
|
|
935
|
+
try {
|
|
936
|
+
await assertClosedSwarmFileIdentity(directory, lockPath, identity, verifiedParent, "PR workflow checkout mutation lock");
|
|
937
|
+
await hooks.removeCheckoutLock(lockPath);
|
|
938
|
+
return;
|
|
939
|
+
} catch (error) {
|
|
940
|
+
if (error.code === "ENOENT")
|
|
941
|
+
return;
|
|
942
|
+
lastError = error;
|
|
943
|
+
if (attempt < WINDOWS_RENAME_MAX_RETRIES - 1) {
|
|
944
|
+
await delay(RENAME_RETRY_DELAY_MS);
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
throw lastError instanceof Error ? lastError : new Error("PR workflow checkout mutation lock cleanup failed");
|
|
949
|
+
}
|
|
950
|
+
function rememberCompletedCheckoutLockOwner(lockPath, ownerToken) {
|
|
951
|
+
completedCheckoutLockOwners.delete(lockPath);
|
|
952
|
+
completedCheckoutLockOwners.set(lockPath, ownerToken);
|
|
953
|
+
while (completedCheckoutLockOwners.size > MAX_COMPLETED_CHECKOUT_LOCK_OWNERS) {
|
|
954
|
+
const oldest = completedCheckoutLockOwners.keys().next().value;
|
|
955
|
+
if (oldest === undefined)
|
|
956
|
+
break;
|
|
957
|
+
completedCheckoutLockOwners.delete(oldest);
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
async function withSessionStateMutation(directory, sessionID, action) {
|
|
961
|
+
const key = stateCacheKey(directory, sessionID);
|
|
962
|
+
const previous = pendingStateMutationsByProjectSession.get(key) ?? Promise.resolve();
|
|
963
|
+
let release;
|
|
964
|
+
const current = new Promise((resolve2) => {
|
|
965
|
+
release = resolve2;
|
|
966
|
+
});
|
|
967
|
+
const queued = previous.then(() => current);
|
|
968
|
+
pendingStateMutationsByProjectSession.set(key, queued);
|
|
969
|
+
await previous;
|
|
970
|
+
try {
|
|
971
|
+
const lock = await acquireSessionStateMutationLock(directory, sessionID);
|
|
972
|
+
try {
|
|
973
|
+
return await action();
|
|
974
|
+
} finally {
|
|
975
|
+
await releaseSessionStateMutationLock(lock);
|
|
976
|
+
}
|
|
977
|
+
} finally {
|
|
978
|
+
release();
|
|
979
|
+
if (pendingStateMutationsByProjectSession.get(key) === queued) {
|
|
980
|
+
pendingStateMutationsByProjectSession.delete(key);
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
async function acquireSessionStateMutationLock(directory, sessionID) {
|
|
985
|
+
const lockPath = workflowGateStateLockPath(directory, sessionID);
|
|
986
|
+
const verifiedParent = await ensurePrWorkflowSafeParentDirectory(directory, lockPath);
|
|
987
|
+
for (let attempt = 0;attempt < STATE_MUTATION_LOCK_MAX_ATTEMPTS; attempt++) {
|
|
988
|
+
try {
|
|
989
|
+
const handle = await fsp.open(lockPath, "wx");
|
|
990
|
+
const lock = {
|
|
991
|
+
ownerToken: randomUUID(),
|
|
992
|
+
pid: process.pid,
|
|
993
|
+
createdAtMs: hooks.nowMs()
|
|
994
|
+
};
|
|
995
|
+
let writeError;
|
|
996
|
+
try {
|
|
997
|
+
await hooks.beforeSessionStateLockWrite?.();
|
|
998
|
+
await assertOpenedSwarmFileIdentity(directory, lockPath, handle, verifiedParent, "PR workflow state mutation lock");
|
|
999
|
+
await handle.writeFile(JSON.stringify(lock), "utf-8");
|
|
1000
|
+
} catch (error) {
|
|
1001
|
+
writeError = error;
|
|
1002
|
+
} finally {
|
|
1003
|
+
await handle.close().catch(() => {
|
|
1004
|
+
return;
|
|
1005
|
+
});
|
|
1006
|
+
}
|
|
1007
|
+
if (writeError) {
|
|
1008
|
+
await removeSessionStateMutationLockIfOwned(lockPath, lock.ownerToken);
|
|
1009
|
+
throw writeError;
|
|
1010
|
+
}
|
|
1011
|
+
return { path: lockPath, ownerToken: lock.ownerToken };
|
|
1012
|
+
} catch (error) {
|
|
1013
|
+
if (error.code !== "EEXIST")
|
|
1014
|
+
throw error;
|
|
1015
|
+
if (await reclaimAbandonedSessionStateMutationLock(lockPath))
|
|
1016
|
+
continue;
|
|
1017
|
+
if (attempt < STATE_MUTATION_LOCK_MAX_ATTEMPTS - 1) {
|
|
1018
|
+
await delay(STATE_MUTATION_LOCK_RETRY_DELAY_MS);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
throw new Error("BLOCKED: PR workflow gate state is being mutated by another process; retry after that session transition finishes");
|
|
1023
|
+
}
|
|
1024
|
+
async function releaseSessionStateMutationLock(lock) {
|
|
1025
|
+
try {
|
|
1026
|
+
await removeSessionStateMutationLockIfOwned(lock.path, lock.ownerToken);
|
|
1027
|
+
} catch {}
|
|
1028
|
+
}
|
|
1029
|
+
async function reclaimAbandonedSessionStateMutationLock(lockPath) {
|
|
1030
|
+
const lock = await readSessionStateMutationLock(lockPath);
|
|
1031
|
+
if (lock) {
|
|
1032
|
+
if (hooks.isProcessAlive(lock.pid))
|
|
1033
|
+
return false;
|
|
1034
|
+
return removeSessionStateMutationLockIfOwned(lockPath, lock.ownerToken);
|
|
1035
|
+
}
|
|
1036
|
+
try {
|
|
1037
|
+
const stat2 = await fsp.stat(lockPath);
|
|
1038
|
+
if (hooks.nowMs() - stat2.mtimeMs < STATE_MUTATION_LOCK_UNINITIALIZED_STALE_MS) {
|
|
1039
|
+
return false;
|
|
1040
|
+
}
|
|
1041
|
+
await fsp.rm(lockPath, { force: true });
|
|
1042
|
+
return true;
|
|
1043
|
+
} catch (error) {
|
|
1044
|
+
if (error.code === "ENOENT")
|
|
1045
|
+
return true;
|
|
1046
|
+
throw error;
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
async function readSessionStateMutationLock(lockPath) {
|
|
1050
|
+
let raw;
|
|
1051
|
+
try {
|
|
1052
|
+
raw = await fsp.readFile(lockPath, "utf-8");
|
|
1053
|
+
} catch (error) {
|
|
1054
|
+
if (error.code === "ENOENT")
|
|
1055
|
+
return null;
|
|
1056
|
+
throw error;
|
|
1057
|
+
}
|
|
1058
|
+
try {
|
|
1059
|
+
const parsed = JSON.parse(raw);
|
|
1060
|
+
if (typeof parsed === "object" && parsed !== null && typeof parsed.ownerToken === "string" && parsed.ownerToken.length > 0 && typeof parsed.pid === "number" && Number.isInteger(parsed.pid) && parsed.pid > 0 && typeof parsed.createdAtMs === "number" && Number.isFinite(parsed.createdAtMs)) {
|
|
1061
|
+
return parsed;
|
|
1062
|
+
}
|
|
1063
|
+
} catch {}
|
|
1064
|
+
return null;
|
|
1065
|
+
}
|
|
1066
|
+
async function removeSessionStateMutationLockIfOwned(lockPath, ownerToken) {
|
|
1067
|
+
const lock = await readSessionStateMutationLock(lockPath);
|
|
1068
|
+
if (!lock || lock.ownerToken !== ownerToken)
|
|
1069
|
+
return false;
|
|
1070
|
+
try {
|
|
1071
|
+
await fsp.rm(lockPath);
|
|
1072
|
+
return true;
|
|
1073
|
+
} catch (error) {
|
|
1074
|
+
if (error.code === "ENOENT")
|
|
1075
|
+
return false;
|
|
1076
|
+
throw error;
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
// src/pr-review/verdict-settlement.ts
|
|
54
1081
|
init_zod();
|
|
55
|
-
import { createHash as
|
|
56
|
-
import * as
|
|
1082
|
+
import { createHash as createHash2 } from "crypto";
|
|
1083
|
+
import * as fs from "fs";
|
|
57
1084
|
import * as path2 from "path";
|
|
58
|
-
|
|
1085
|
+
var PR_REVIEW_VERDICT_RETRY_BUDGET = 2;
|
|
1086
|
+
var VERDICT_SETTLEMENT_SCHEMA_VERSION = 1;
|
|
1087
|
+
var VERDICT_SETTLEMENT_MAX_RECEIPT_BYTES = 65536;
|
|
1088
|
+
var VERDICT_SETTLEMENT_MAX_RECEIPT_ITEMS = 128;
|
|
1089
|
+
var PrReviewVerdictSettlementPhaseSchema = exports_external.enum([
|
|
1090
|
+
"reviewer",
|
|
1091
|
+
"critic"
|
|
1092
|
+
]);
|
|
1093
|
+
var PrReviewVerdictSettlementItemSchema = exports_external.object({
|
|
1094
|
+
itemId: exports_external.string().trim().min(1),
|
|
1095
|
+
sourceBatchId: exports_external.string().trim().min(1),
|
|
1096
|
+
sourceLaneId: exports_external.string().trim().min(1),
|
|
1097
|
+
disposition: exports_external.enum([
|
|
1098
|
+
"liveness_dead",
|
|
1099
|
+
"verdict_contract_dead",
|
|
1100
|
+
"resource_dead"
|
|
1101
|
+
]),
|
|
1102
|
+
evidenceClass: exports_external.enum(["liveness", "verdict_contract", "resource"]),
|
|
1103
|
+
terminalStatus: exports_external.enum(["stale", "cancelled", "error"])
|
|
1104
|
+
}).strict();
|
|
1105
|
+
var PrReviewVerdictSettlementReceiptSchema = exports_external.object({
|
|
1106
|
+
schemaVersion: exports_external.literal(VERDICT_SETTLEMENT_SCHEMA_VERSION),
|
|
1107
|
+
runId: exports_external.string().trim().min(1),
|
|
1108
|
+
prHeadSha: exports_external.string().trim().min(1),
|
|
1109
|
+
revisionDigest: exports_external.string().trim().min(1),
|
|
1110
|
+
phase: PrReviewVerdictSettlementPhaseSchema,
|
|
1111
|
+
items: exports_external.array(PrReviewVerdictSettlementItemSchema).min(1).max(VERDICT_SETTLEMENT_MAX_RECEIPT_ITEMS)
|
|
1112
|
+
}).strict();
|
|
1113
|
+
function verdictSettlementRelativePath(runId, phase) {
|
|
1114
|
+
return path2.join("pr-review", runId, `verdict-settlement.${phase}.json`);
|
|
1115
|
+
}
|
|
1116
|
+
function typedDeadDisclosureClassForRecord(record) {
|
|
1117
|
+
if (record.status !== "cancelled" && record.status !== "stale" && record.status !== "error") {
|
|
1118
|
+
return null;
|
|
1119
|
+
}
|
|
1120
|
+
const terminalClass = record.terminalResult?.result.workflowLaneFailureClass ?? null;
|
|
1121
|
+
const resultClass = record.result?.workflowLaneFailureClass ?? null;
|
|
1122
|
+
for (const cls of [terminalClass, resultClass]) {
|
|
1123
|
+
switch (cls) {
|
|
1124
|
+
case "liveness":
|
|
1125
|
+
return "liveness";
|
|
1126
|
+
case "verdict_contract":
|
|
1127
|
+
case "contract":
|
|
1128
|
+
return "verdict_contract";
|
|
1129
|
+
case "resource":
|
|
1130
|
+
return "resource";
|
|
1131
|
+
default:
|
|
1132
|
+
break;
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
return null;
|
|
1136
|
+
}
|
|
1137
|
+
function deriveVerdictSettlementAdmission(args) {
|
|
1138
|
+
const { directory, prHeadSha, phase, window, unclaimed } = args;
|
|
1139
|
+
if (unclaimed.length === 0) {
|
|
1140
|
+
return {
|
|
1141
|
+
admitted: false,
|
|
1142
|
+
reason: "no unclaimed items",
|
|
1143
|
+
items: []
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1146
|
+
const expectedMode = `swarm-pr-review:${phase}`;
|
|
1147
|
+
const items = [];
|
|
1148
|
+
const batchReads = new Map;
|
|
1149
|
+
for (const itemId of unclaimed) {
|
|
1150
|
+
const owners = window.flatMap((batch) => batch.lanes.filter((lane) => (lane.reviewItemIds ?? []).includes(itemId)).map((lane) => ({ batch, lane })));
|
|
1151
|
+
if (owners.length === 0) {
|
|
1152
|
+
return {
|
|
1153
|
+
admitted: false,
|
|
1154
|
+
reason: `item ${itemId} has no declared owner lane in the phase window (absence is not terminality evidence)`,
|
|
1155
|
+
items: []
|
|
1156
|
+
};
|
|
1157
|
+
}
|
|
1158
|
+
const itemAttemptBatchIds = new Set;
|
|
1159
|
+
const dead = [];
|
|
1160
|
+
for (const { batch, lane } of owners) {
|
|
1161
|
+
itemAttemptBatchIds.add(batch.batchId);
|
|
1162
|
+
let read = batchReads.get(batch.batchId);
|
|
1163
|
+
if (!read) {
|
|
1164
|
+
read = _internals.findByBatchIdDetailed(directory, batch.batchId, {
|
|
1165
|
+
parentSessionId: args.parentSessionId
|
|
1166
|
+
});
|
|
1167
|
+
batchReads.set(batch.batchId, read);
|
|
1168
|
+
}
|
|
1169
|
+
if (read.status === "uncertain") {
|
|
1170
|
+
return {
|
|
1171
|
+
admitted: false,
|
|
1172
|
+
reason: `delegation store read was uncertain for batch ${batch.batchId}`,
|
|
1173
|
+
items: []
|
|
1174
|
+
};
|
|
1175
|
+
}
|
|
1176
|
+
const record = read.value.find((candidate) => candidate.batchId === batch.batchId && candidate.laneId === lane.laneId && candidate.mode === expectedMode && candidate.workspace?.prHeadSha === prHeadSha);
|
|
1177
|
+
if (!record) {
|
|
1178
|
+
return {
|
|
1179
|
+
admitted: false,
|
|
1180
|
+
reason: `item ${itemId} still has a non-admissible owner lane ${lane.laneId} (missing delegation record)`,
|
|
1181
|
+
items: []
|
|
1182
|
+
};
|
|
1183
|
+
}
|
|
1184
|
+
if (record.status === "completed") {
|
|
1185
|
+
continue;
|
|
1186
|
+
}
|
|
1187
|
+
const disclosure = typedDeadDisclosureClassForRecord(record);
|
|
1188
|
+
if (!disclosure) {
|
|
1189
|
+
const blockingClass = record.terminalResult?.result.workflowLaneFailureClass ?? record.result?.workflowLaneFailureClass ?? "classless";
|
|
1190
|
+
return {
|
|
1191
|
+
admitted: false,
|
|
1192
|
+
reason: `item ${itemId} still has a non-admissible owner lane ${lane.laneId} (${blockingClass})`,
|
|
1193
|
+
items: []
|
|
1194
|
+
};
|
|
1195
|
+
}
|
|
1196
|
+
dead.push({
|
|
1197
|
+
batchId: batch.batchId,
|
|
1198
|
+
laneId: lane.laneId,
|
|
1199
|
+
status: record.status === "cancelled" || record.status === "error" ? record.status : "stale",
|
|
1200
|
+
disclosure
|
|
1201
|
+
});
|
|
1202
|
+
}
|
|
1203
|
+
if (itemAttemptBatchIds.size < 1 + PR_REVIEW_VERDICT_RETRY_BUDGET) {
|
|
1204
|
+
return {
|
|
1205
|
+
admitted: false,
|
|
1206
|
+
reason: `item ${itemId} verdict retry budget not exhausted (${itemAttemptBatchIds.size} of ${1 + PR_REVIEW_VERDICT_RETRY_BUDGET} required attempts)`,
|
|
1207
|
+
items: []
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1210
|
+
if (dead.length === 0) {
|
|
1211
|
+
return {
|
|
1212
|
+
admitted: false,
|
|
1213
|
+
reason: `item ${itemId} has no typed-dead owner lane to cite (completed owners consume attempts but are never terminality evidence)`,
|
|
1214
|
+
items: []
|
|
1215
|
+
};
|
|
1216
|
+
}
|
|
1217
|
+
const first = dead[0];
|
|
1218
|
+
items.push({
|
|
1219
|
+
itemId,
|
|
1220
|
+
sourceBatchId: first.batchId,
|
|
1221
|
+
sourceLaneId: first.laneId,
|
|
1222
|
+
disposition: `${first.disclosure}_dead`,
|
|
1223
|
+
evidenceClass: first.disclosure,
|
|
1224
|
+
terminalStatus: first.status
|
|
1225
|
+
});
|
|
1226
|
+
}
|
|
1227
|
+
return { admitted: true, reason: "admitted", items };
|
|
1228
|
+
}
|
|
1229
|
+
function deriveVerdictAttemptCounts(args) {
|
|
1230
|
+
const attemptsRequired = 1 + PR_REVIEW_VERDICT_RETRY_BUDGET;
|
|
1231
|
+
return args.unclaimed.map((itemId) => {
|
|
1232
|
+
const batches = new Set;
|
|
1233
|
+
for (const batch of args.window) {
|
|
1234
|
+
if (batch.lanes.some((lane) => (lane.reviewItemIds ?? []).includes(itemId))) {
|
|
1235
|
+
batches.add(batch.batchId);
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
const attemptsUsed = batches.size;
|
|
1239
|
+
return {
|
|
1240
|
+
itemId,
|
|
1241
|
+
attemptsUsed,
|
|
1242
|
+
attemptsRequired,
|
|
1243
|
+
remaining: Math.max(0, attemptsRequired - attemptsUsed)
|
|
1244
|
+
};
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
function buildVerdictSettlementReceipt(args) {
|
|
1248
|
+
return {
|
|
1249
|
+
schemaVersion: VERDICT_SETTLEMENT_SCHEMA_VERSION,
|
|
1250
|
+
runId: args.runId,
|
|
1251
|
+
prHeadSha: args.prHeadSha,
|
|
1252
|
+
revisionDigest: args.revisionDigest,
|
|
1253
|
+
phase: args.phase,
|
|
1254
|
+
items: [...args.items]
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1257
|
+
async function persistVerdictSettlementReceipt(directory, receipt) {
|
|
1258
|
+
if (receipt.items.length > VERDICT_SETTLEMENT_MAX_RECEIPT_ITEMS) {
|
|
1259
|
+
throw new Error(`verdict-settlement receipt exceeds the ${VERDICT_SETTLEMENT_MAX_RECEIPT_ITEMS}-item bounded-reader cap (${receipt.items.length} items); narrow the disclosed dead set before settling`);
|
|
1260
|
+
}
|
|
1261
|
+
const serialized = JSON.stringify(receipt, null, 2);
|
|
1262
|
+
const serializedBytes = Buffer.byteLength(serialized, "utf8");
|
|
1263
|
+
if (serializedBytes > VERDICT_SETTLEMENT_MAX_RECEIPT_BYTES) {
|
|
1264
|
+
throw new Error(`verdict-settlement receipt exceeds the ${VERDICT_SETTLEMENT_MAX_RECEIPT_BYTES}-byte bounded-reader cap (${serializedBytes} bytes); narrow the disclosed dead set before settling`);
|
|
1265
|
+
}
|
|
1266
|
+
const absolute = path2.join(directory, ".swarm", verdictSettlementRelativePath(receipt.runId, receipt.phase));
|
|
1267
|
+
await writeAtomicJson(directory, absolute, receipt);
|
|
1268
|
+
}
|
|
1269
|
+
function boundedReadErrno(error) {
|
|
1270
|
+
if (error?.code === "ENOENT") {
|
|
1271
|
+
return { status: "absent" };
|
|
1272
|
+
}
|
|
1273
|
+
const code = error?.code ?? "unknown";
|
|
1274
|
+
return {
|
|
1275
|
+
status: "invalid",
|
|
1276
|
+
reason: `receipt is unreadable (${String(code)})`
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1279
|
+
function readBoundedSwarmRegularFileBytes(swarmRoot, absolute, maxBytes) {
|
|
1280
|
+
const rootStat = fs.lstatSync(swarmRoot, {
|
|
1281
|
+
bigint: true,
|
|
1282
|
+
throwIfNoEntry: false
|
|
1283
|
+
});
|
|
1284
|
+
if (!rootStat)
|
|
1285
|
+
return { status: "absent" };
|
|
1286
|
+
if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) {
|
|
1287
|
+
return {
|
|
1288
|
+
status: "invalid",
|
|
1289
|
+
reason: "receipt root must be a real project .swarm directory"
|
|
1290
|
+
};
|
|
1291
|
+
}
|
|
1292
|
+
const fileStat = fs.lstatSync(absolute, {
|
|
1293
|
+
bigint: true,
|
|
1294
|
+
throwIfNoEntry: false
|
|
1295
|
+
});
|
|
1296
|
+
if (!fileStat)
|
|
1297
|
+
return { status: "absent" };
|
|
1298
|
+
if (fileStat.isSymbolicLink() || !fileStat.isFile()) {
|
|
1299
|
+
return {
|
|
1300
|
+
status: "invalid",
|
|
1301
|
+
reason: "receipt must be a bounded regular file"
|
|
1302
|
+
};
|
|
1303
|
+
}
|
|
1304
|
+
if (fileStat.size > BigInt(maxBytes)) {
|
|
1305
|
+
return { status: "invalid", reason: "receipt exceeds size bound" };
|
|
1306
|
+
}
|
|
1307
|
+
let realRoot;
|
|
1308
|
+
let realFile;
|
|
1309
|
+
try {
|
|
1310
|
+
realRoot = fs.realpathSync(swarmRoot);
|
|
1311
|
+
realFile = fs.realpathSync(absolute);
|
|
1312
|
+
} catch (error) {
|
|
1313
|
+
return boundedReadErrno(error);
|
|
1314
|
+
}
|
|
1315
|
+
const relative3 = path2.relative(realRoot, realFile);
|
|
1316
|
+
if (relative3 === "" || relative3 === ".." || relative3.startsWith(`..${path2.sep}`) || path2.isAbsolute(relative3)) {
|
|
1317
|
+
return {
|
|
1318
|
+
status: "invalid",
|
|
1319
|
+
reason: "receipt escapes the project .swarm directory"
|
|
1320
|
+
};
|
|
1321
|
+
}
|
|
1322
|
+
let fd;
|
|
1323
|
+
try {
|
|
1324
|
+
fd = fs.openSync(realFile, "r");
|
|
1325
|
+
} catch (error) {
|
|
1326
|
+
return boundedReadErrno(error);
|
|
1327
|
+
}
|
|
1328
|
+
try {
|
|
1329
|
+
const openedStat = fs.fstatSync(fd, { bigint: true });
|
|
1330
|
+
if (!openedStat.isFile() || openedStat.size > BigInt(maxBytes) || openedStat.dev !== fileStat.dev || openedStat.ino !== fileStat.ino) {
|
|
1331
|
+
return {
|
|
1332
|
+
status: "invalid",
|
|
1333
|
+
reason: "receipt changed or grew during its bounded read"
|
|
1334
|
+
};
|
|
1335
|
+
}
|
|
1336
|
+
const buffer = Buffer.allocUnsafe(maxBytes + 1);
|
|
1337
|
+
const bytesRead = fs.readSync(fd, buffer, 0, maxBytes + 1, 0);
|
|
1338
|
+
if (bytesRead > maxBytes) {
|
|
1339
|
+
return { status: "invalid", reason: "receipt exceeds size bound" };
|
|
1340
|
+
}
|
|
1341
|
+
const postStat = fs.lstatSync(absolute, {
|
|
1342
|
+
bigint: true,
|
|
1343
|
+
throwIfNoEntry: false
|
|
1344
|
+
});
|
|
1345
|
+
if (!postStat || postStat.isSymbolicLink() || !postStat.isFile() || postStat.dev !== fileStat.dev || postStat.ino !== fileStat.ino) {
|
|
1346
|
+
return {
|
|
1347
|
+
status: "invalid",
|
|
1348
|
+
reason: "receipt changed during its bounded read"
|
|
1349
|
+
};
|
|
1350
|
+
}
|
|
1351
|
+
return { status: "ok", bytes: buffer.subarray(0, bytesRead) };
|
|
1352
|
+
} catch (error) {
|
|
1353
|
+
return boundedReadErrno(error);
|
|
1354
|
+
} finally {
|
|
1355
|
+
fs.closeSync(fd);
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
function readVerdictSettlementReceipt(directory, runId, phase) {
|
|
1359
|
+
const relative3 = verdictSettlementRelativePath(runId, phase);
|
|
1360
|
+
const absolute = path2.join(directory, ".swarm", relative3);
|
|
1361
|
+
const read = readBoundedSwarmRegularFileBytes(path2.join(directory, ".swarm"), absolute, VERDICT_SETTLEMENT_MAX_RECEIPT_BYTES);
|
|
1362
|
+
if (read.status !== "ok")
|
|
1363
|
+
return read;
|
|
1364
|
+
let parsed;
|
|
1365
|
+
try {
|
|
1366
|
+
parsed = JSON.parse(read.bytes.toString("utf-8"));
|
|
1367
|
+
} catch {
|
|
1368
|
+
return { status: "invalid", reason: "receipt is not valid JSON" };
|
|
1369
|
+
}
|
|
1370
|
+
const validated = PrReviewVerdictSettlementReceiptSchema.safeParse(parsed);
|
|
1371
|
+
if (!validated.success) {
|
|
1372
|
+
return { status: "invalid", reason: "receipt fails schema validation" };
|
|
1373
|
+
}
|
|
1374
|
+
return {
|
|
1375
|
+
status: "ok",
|
|
1376
|
+
receipt: validated.data,
|
|
1377
|
+
rawSha256: createHash2("sha256").update(read.bytes).digest("hex")
|
|
1378
|
+
};
|
|
1379
|
+
}
|
|
1380
|
+
function effectiveVerdictSettlementItems(receipt, currentUnclaimed) {
|
|
1381
|
+
if (!receipt || currentUnclaimed.length === 0)
|
|
1382
|
+
return [];
|
|
1383
|
+
const unclaimed = new Set(currentUnclaimed);
|
|
1384
|
+
return receipt.items.filter((item) => unclaimed.has(item.itemId));
|
|
1385
|
+
}
|
|
1386
|
+
function verdictSettlementDegradationActive(args) {
|
|
1387
|
+
const { directory, runId, prHeadSha, revisionDigest } = args;
|
|
1388
|
+
if (!runId || !prHeadSha || !revisionDigest)
|
|
1389
|
+
return false;
|
|
1390
|
+
const phases = [
|
|
1391
|
+
{ phase: "reviewer", unclaimed: args.reviewerUnclaimed },
|
|
1392
|
+
{ phase: "critic", unclaimed: args.criticUnclaimed }
|
|
1393
|
+
];
|
|
1394
|
+
for (const { phase, unclaimed } of phases) {
|
|
1395
|
+
if (unclaimed.length === 0)
|
|
1396
|
+
continue;
|
|
1397
|
+
const read = readVerdictSettlementReceipt(directory, runId, phase);
|
|
1398
|
+
if (read.status !== "ok" || read.receipt.prHeadSha !== prHeadSha || read.receipt.revisionDigest !== revisionDigest) {
|
|
1399
|
+
continue;
|
|
1400
|
+
}
|
|
1401
|
+
if (effectiveVerdictSettlementItems(read.receipt, unclaimed).length > 0) {
|
|
1402
|
+
return true;
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
return false;
|
|
1406
|
+
}
|
|
1407
|
+
var _internals = { findByBatchIdDetailed };
|
|
1408
|
+
|
|
1409
|
+
// src/background/pending-delegations.ts
|
|
1410
|
+
init_bun_compat();
|
|
59
1411
|
init_logger();
|
|
60
1412
|
|
|
61
1413
|
// src/background/delegation-health.ts
|
|
62
1414
|
init_zod();
|
|
63
|
-
import * as
|
|
64
|
-
import * as
|
|
1415
|
+
import * as fs2 from "fs";
|
|
1416
|
+
import * as path3 from "path";
|
|
65
1417
|
var BACKGROUND_DELEGATIONS_HEALTH_FILE = "background-delegations-health.json";
|
|
66
1418
|
var MAX_RECOVERY_LEDGER_BYTES = 4 * 1024 * 1024;
|
|
67
1419
|
var DelegationCheckpointAuditSchema = exports_external.object({
|
|
@@ -169,7 +1521,7 @@ function sleepSync(ms) {
|
|
|
169
1521
|
}
|
|
170
1522
|
var _healthInternals = {
|
|
171
1523
|
renameOnce: (from, to) => {
|
|
172
|
-
|
|
1524
|
+
fs2.renameSync(from, to);
|
|
173
1525
|
}
|
|
174
1526
|
};
|
|
175
1527
|
function healthArtifactPath(directory) {
|
|
@@ -178,7 +1530,7 @@ function healthArtifactPath(directory) {
|
|
|
178
1530
|
function readDelegationHealthArtifact(directory) {
|
|
179
1531
|
let raw;
|
|
180
1532
|
try {
|
|
181
|
-
raw =
|
|
1533
|
+
raw = fs2.readFileSync(healthArtifactPath(directory), "utf-8");
|
|
182
1534
|
} catch {
|
|
183
1535
|
return null;
|
|
184
1536
|
}
|
|
@@ -228,10 +1580,10 @@ function writeDelegationHealthArtifact(directory, update) {
|
|
|
228
1580
|
if (!parsed.success)
|
|
229
1581
|
return null;
|
|
230
1582
|
try {
|
|
231
|
-
|
|
1583
|
+
fs2.mkdirSync(path3.resolve(directory, ".swarm"), { recursive: true });
|
|
232
1584
|
const target = healthArtifactPath(directory);
|
|
233
1585
|
const tmp = `${target}.tmp-${process.pid}-${Math.random().toString(36).slice(2, 10)}`;
|
|
234
|
-
|
|
1586
|
+
fs2.writeFileSync(tmp, `${JSON.stringify(parsed.data)}
|
|
235
1587
|
`, "utf-8");
|
|
236
1588
|
let lastError;
|
|
237
1589
|
for (let attempt = 0;attempt < 5; attempt++) {
|
|
@@ -251,7 +1603,7 @@ function writeDelegationHealthArtifact(directory, update) {
|
|
|
251
1603
|
}
|
|
252
1604
|
if (lastError !== undefined) {
|
|
253
1605
|
try {
|
|
254
|
-
|
|
1606
|
+
fs2.rmSync(tmp, { force: true });
|
|
255
1607
|
} catch {}
|
|
256
1608
|
return null;
|
|
257
1609
|
}
|
|
@@ -285,7 +1637,7 @@ function collectDelegationLedgerHealth(directory, options) {
|
|
|
285
1637
|
let ledgerBytes = 0;
|
|
286
1638
|
let ledgerExists = false;
|
|
287
1639
|
try {
|
|
288
|
-
ledgerBytes =
|
|
1640
|
+
ledgerBytes = fs2.statSync(validateSwarmPath(directory, "background-delegations.jsonl")).size;
|
|
289
1641
|
ledgerExists = true;
|
|
290
1642
|
} catch {
|
|
291
1643
|
ledgerExists = false;
|
|
@@ -320,7 +1672,7 @@ function collectDelegationLedgerHealth(directory, options) {
|
|
|
320
1672
|
|
|
321
1673
|
// src/background/pr-review-collection-receipt.ts
|
|
322
1674
|
init_zod();
|
|
323
|
-
import { createHash } from "crypto";
|
|
1675
|
+
import { createHash as createHash3 } from "crypto";
|
|
324
1676
|
var PR_REVIEW_COLLECTION_RECEIPT_PREFIX = "[PR_REVIEW_COLLECTION_RECEIPT_V1] ";
|
|
325
1677
|
var PR_REVIEW_COLLECTION_RECEIPT_SHED_PREFIX = "[PR_REVIEW_COLLECTION_RECEIPT_SHED_V1] ";
|
|
326
1678
|
var MAX_PR_REVIEW_COLLECTION_RECEIPT_CHARS = 64 * 1024;
|
|
@@ -357,7 +1709,7 @@ var PrReviewCollectionReceiptShedMarkerSchema = exports_external.object({
|
|
|
357
1709
|
reviewItemDigest: exports_external.string().regex(/^[a-f0-9]{64}$/)
|
|
358
1710
|
}).strict();
|
|
359
1711
|
function reviewItemDigest(reviewItemIds) {
|
|
360
|
-
return
|
|
1712
|
+
return createHash3("sha256").update(JSON.stringify(reviewItemIds)).digest("hex");
|
|
361
1713
|
}
|
|
362
1714
|
function encodePrReviewCollectionReceiptShedMarker(payload) {
|
|
363
1715
|
const marker = {
|
|
@@ -419,7 +1771,7 @@ function parsePrReviewCollectionReceiptFooter(binding, result) {
|
|
|
419
1771
|
|
|
420
1772
|
// src/background/pr-review-contract.ts
|
|
421
1773
|
init_zod();
|
|
422
|
-
import { createHash as
|
|
1774
|
+
import { createHash as createHash4 } from "crypto";
|
|
423
1775
|
|
|
424
1776
|
// src/pr-review/critic-routing.ts
|
|
425
1777
|
function prReviewFindingRequiresCritic(input) {
|
|
@@ -541,18 +1893,18 @@ var PrReviewResultLaneIdSchema = exports_external.string().trim().min(1).max(120
|
|
|
541
1893
|
var PrReviewResultChildSessionSchema = exports_external.string().trim().min(1).max(256);
|
|
542
1894
|
var PrReviewResultShaSchema = exports_external.string().trim().regex(/^[0-9a-f]{6,64}$/i);
|
|
543
1895
|
var PrReviewResultDigestSchema = exports_external.string().trim().regex(/^[0-9a-f]{64}$/i);
|
|
544
|
-
function addDuplicateIssue(ctx,
|
|
1896
|
+
function addDuplicateIssue(ctx, path4, label, value) {
|
|
545
1897
|
ctx.addIssue({
|
|
546
1898
|
code: exports_external.ZodIssueCode.custom,
|
|
547
|
-
path:
|
|
1899
|
+
path: path4,
|
|
548
1900
|
message: `${label} must not contain duplicate value "${value}"`
|
|
549
1901
|
});
|
|
550
1902
|
}
|
|
551
|
-
function assertUniqueStrings(values, ctx,
|
|
1903
|
+
function assertUniqueStrings(values, ctx, path4, label) {
|
|
552
1904
|
const seen = new Set;
|
|
553
1905
|
for (const value of values) {
|
|
554
1906
|
if (seen.has(value)) {
|
|
555
|
-
addDuplicateIssue(ctx,
|
|
1907
|
+
addDuplicateIssue(ctx, path4, label, value);
|
|
556
1908
|
return;
|
|
557
1909
|
}
|
|
558
1910
|
seen.add(value);
|
|
@@ -747,7 +2099,7 @@ function serializedPrReviewResultReceiptBytes(value) {
|
|
|
747
2099
|
return new TextEncoder().encode(JSON.stringify(value)).byteLength;
|
|
748
2100
|
}
|
|
749
2101
|
function prReviewLaneResultEnvelopeDigest(value) {
|
|
750
|
-
return
|
|
2102
|
+
return createHash4("sha256").update(JSON.stringify(value)).digest("hex");
|
|
751
2103
|
}
|
|
752
2104
|
var PrReviewResultReceiptSchema = exports_external.object({
|
|
753
2105
|
schemaVersion: exports_external.literal(1),
|
|
@@ -1509,15 +2861,15 @@ function coordinationRowsToDelegations(directory) {
|
|
|
1509
2861
|
}
|
|
1510
2862
|
}
|
|
1511
2863
|
function archiveShadowFileBestEffort(filePath) {
|
|
1512
|
-
if (!
|
|
2864
|
+
if (!fs3.existsSync(filePath))
|
|
1513
2865
|
return;
|
|
1514
2866
|
const importedPath = importedShadowPath(filePath);
|
|
1515
2867
|
try {
|
|
1516
|
-
if (
|
|
2868
|
+
if (fs3.existsSync(importedPath))
|
|
1517
2869
|
return;
|
|
1518
|
-
|
|
2870
|
+
fs3.renameSync(filePath, importedPath);
|
|
1519
2871
|
} catch (error) {
|
|
1520
|
-
warn(`[background] shadow archive failed for ${
|
|
2872
|
+
warn(`[background] shadow archive failed for ${path4.basename(filePath)}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1521
2873
|
}
|
|
1522
2874
|
}
|
|
1523
2875
|
function writeDelegationShadowProjection(directory, records) {
|
|
@@ -1525,8 +2877,8 @@ function writeDelegationShadowProjection(directory, records) {
|
|
|
1525
2877
|
const payload = records.map((record) => JSON.stringify(record)).join(`
|
|
1526
2878
|
`) + (records.length > 0 ? `
|
|
1527
2879
|
` : "");
|
|
1528
|
-
|
|
1529
|
-
|
|
2880
|
+
fs3.writeFileSync(storePath(directory), payload, "utf-8");
|
|
2881
|
+
fs3.writeFileSync(projectionMarkerPath(storePath(directory)), "", "utf-8");
|
|
1530
2882
|
}
|
|
1531
2883
|
function loadLegacyDelegationsStrict(directory) {
|
|
1532
2884
|
const load = loadFoldedState(directory, { strict: true });
|
|
@@ -1543,17 +2895,17 @@ function delegationShadowMatches(directory, records) {
|
|
|
1543
2895
|
return isDeepStrictEqual([...load.records.values()].sort((left, right) => left.correlationId.localeCompare(right.correlationId)), [...records].sort((left, right) => left.correlationId.localeCompare(right.correlationId)));
|
|
1544
2896
|
}
|
|
1545
2897
|
function delegationShadowDigest(directory, records) {
|
|
1546
|
-
const hash =
|
|
2898
|
+
const hash = createHash5("sha256");
|
|
1547
2899
|
hash.update(JSON.stringify(records));
|
|
1548
2900
|
for (const filePath of [
|
|
1549
2901
|
storePath(directory),
|
|
1550
2902
|
checkpointPath(directory),
|
|
1551
2903
|
manifestPath(directory)
|
|
1552
2904
|
]) {
|
|
1553
|
-
if (!
|
|
2905
|
+
if (!fs3.existsSync(filePath))
|
|
1554
2906
|
continue;
|
|
1555
|
-
hash.update(
|
|
1556
|
-
hash.update(
|
|
2907
|
+
hash.update(path4.basename(filePath));
|
|
2908
|
+
hash.update(fs3.readFileSync(filePath));
|
|
1557
2909
|
}
|
|
1558
2910
|
return hash.digest("hex");
|
|
1559
2911
|
}
|
|
@@ -1567,7 +2919,7 @@ function ensureDelegationCoordinationImported(directory, createIfNeeded) {
|
|
|
1567
2919
|
if (existing && existing.length > 0) {
|
|
1568
2920
|
if (createIfNeeded)
|
|
1569
2921
|
return "ready";
|
|
1570
|
-
const markerPresent =
|
|
2922
|
+
const markerPresent = fs3.existsSync(projectionMarkerPath(storePath(directory)));
|
|
1571
2923
|
const shadowMatches = markerPresent && delegationShadowMatches(directory, existing);
|
|
1572
2924
|
if (!shadowMatches) {
|
|
1573
2925
|
for (const filePath of [
|
|
@@ -1584,7 +2936,7 @@ function ensureDelegationCoordinationImported(directory, createIfNeeded) {
|
|
|
1584
2936
|
const legacyRecords = loadLegacyDelegationsStrict(directory);
|
|
1585
2937
|
if (legacyRecords === null)
|
|
1586
2938
|
return "uncertain";
|
|
1587
|
-
if (legacyRecords.length === 0 && !
|
|
2939
|
+
if (legacyRecords.length === 0 && !fs3.existsSync(storePath(directory)) && !fs3.existsSync(checkpointPath(directory)) && !fs3.existsSync(manifestPath(directory))) {
|
|
1588
2940
|
if (!createIfNeeded)
|
|
1589
2941
|
return "absent";
|
|
1590
2942
|
withCoordinationTransaction(directory, () => {
|
|
@@ -1682,44 +3034,44 @@ var _checkpointInternals = {
|
|
|
1682
3034
|
throw lastError;
|
|
1683
3035
|
},
|
|
1684
3036
|
renameOnce: (from, to) => {
|
|
1685
|
-
|
|
3037
|
+
fs3.renameSync(from, to);
|
|
1686
3038
|
},
|
|
1687
3039
|
syncSleep: portableSyncSleep
|
|
1688
3040
|
};
|
|
1689
3041
|
function writeDurableFileSync(target, contents) {
|
|
1690
3042
|
const tmp = `${target}.tmp-${process.pid}-${Math.random().toString(36).slice(2, 10)}`;
|
|
1691
3043
|
try {
|
|
1692
|
-
const fd =
|
|
3044
|
+
const fd = fs3.openSync(tmp, "w");
|
|
1693
3045
|
try {
|
|
1694
3046
|
if (typeof contents === "string") {
|
|
1695
|
-
|
|
3047
|
+
fs3.writeSync(fd, contents);
|
|
1696
3048
|
} else {
|
|
1697
|
-
|
|
3049
|
+
fs3.writeSync(fd, contents, 0, contents.length);
|
|
1698
3050
|
}
|
|
1699
|
-
|
|
3051
|
+
fs3.fsyncSync(fd);
|
|
1700
3052
|
} finally {
|
|
1701
|
-
|
|
3053
|
+
fs3.closeSync(fd);
|
|
1702
3054
|
}
|
|
1703
3055
|
_checkpointInternals.renameWithRetry(tmp, target);
|
|
1704
3056
|
} catch (err) {
|
|
1705
3057
|
try {
|
|
1706
|
-
|
|
3058
|
+
fs3.rmSync(tmp, { force: true });
|
|
1707
3059
|
} catch {}
|
|
1708
3060
|
throw err;
|
|
1709
3061
|
}
|
|
1710
3062
|
try {
|
|
1711
|
-
const dfd =
|
|
3063
|
+
const dfd = fs3.openSync(path4.dirname(target), "r");
|
|
1712
3064
|
try {
|
|
1713
|
-
|
|
3065
|
+
fs3.fsyncSync(dfd);
|
|
1714
3066
|
} finally {
|
|
1715
|
-
|
|
3067
|
+
fs3.closeSync(dfd);
|
|
1716
3068
|
}
|
|
1717
3069
|
} catch {}
|
|
1718
3070
|
}
|
|
1719
3071
|
function readJsonFileWithRetry(target) {
|
|
1720
3072
|
let raw;
|
|
1721
3073
|
try {
|
|
1722
|
-
raw =
|
|
3074
|
+
raw = fs3.readFileSync(target, "utf-8");
|
|
1723
3075
|
} catch (err) {
|
|
1724
3076
|
const code = err.code;
|
|
1725
3077
|
if (code === "ENOENT")
|
|
@@ -1753,7 +3105,7 @@ function readDelegationManifest(directory) {
|
|
|
1753
3105
|
function readDelegationCheckpoint(directory) {
|
|
1754
3106
|
let bytes = 0;
|
|
1755
3107
|
try {
|
|
1756
|
-
bytes =
|
|
3108
|
+
bytes = fs3.statSync(checkpointPath(directory)).size;
|
|
1757
3109
|
} catch {}
|
|
1758
3110
|
if (bytes > MAX_CHECKPOINT_BYTES) {
|
|
1759
3111
|
return {
|
|
@@ -1773,7 +3125,7 @@ function readDelegationCheckpoint(directory) {
|
|
|
1773
3125
|
return { kind: "ok", checkpoint: parsed.data, bytes };
|
|
1774
3126
|
}
|
|
1775
3127
|
function sha256Hex(data) {
|
|
1776
|
-
return
|
|
3128
|
+
return createHash5("sha256").update(data).digest("hex");
|
|
1777
3129
|
}
|
|
1778
3130
|
function checkpointPayloadChecksum(checkpoint) {
|
|
1779
3131
|
return sha256Hex(JSON.stringify([
|
|
@@ -1844,9 +3196,9 @@ function loadFoldedState(directory, options) {
|
|
|
1844
3196
|
repairHint: CHECKPOINT_RESET_HINT
|
|
1845
3197
|
};
|
|
1846
3198
|
}
|
|
1847
|
-
let
|
|
3199
|
+
let stat2;
|
|
1848
3200
|
try {
|
|
1849
|
-
|
|
3201
|
+
stat2 = fs3.statSync(storePath(directory));
|
|
1850
3202
|
} catch (error) {
|
|
1851
3203
|
return {
|
|
1852
3204
|
status: "uncertain",
|
|
@@ -1855,10 +3207,10 @@ function loadFoldedState(directory, options) {
|
|
|
1855
3207
|
}
|
|
1856
3208
|
let tail;
|
|
1857
3209
|
let mode;
|
|
1858
|
-
if (
|
|
3210
|
+
if (stat2.size >= checkpoint.cutLedgerBytes && checkpoint.cutLedgerBytes > 0) {
|
|
1859
3211
|
const prefix = readLedgerRegion(directory, 0, checkpoint.cutLedgerBytes);
|
|
1860
3212
|
if (sha256Hex(prefix) === checkpoint.cutLedgerDigest) {
|
|
1861
|
-
const suffixBytes =
|
|
3213
|
+
const suffixBytes = stat2.size - checkpoint.cutLedgerBytes;
|
|
1862
3214
|
if (suffixBytes > MAX_RECOVERY_LEDGER_BYTES) {
|
|
1863
3215
|
return {
|
|
1864
3216
|
status: "uncertain",
|
|
@@ -1868,11 +3220,11 @@ function loadFoldedState(directory, options) {
|
|
|
1868
3220
|
tail = suffixBytes === 0 ? Buffer.alloc(0) : readLedgerRegion(directory, checkpoint.cutLedgerBytes, suffixBytes);
|
|
1869
3221
|
mode = "checkpoint+ledger-suffix";
|
|
1870
3222
|
} else {
|
|
1871
|
-
tail = readBoundedRecentLedgerWindow(directory,
|
|
3223
|
+
tail = readBoundedRecentLedgerWindow(directory, stat2.size);
|
|
1872
3224
|
mode = "checkpoint+tail";
|
|
1873
3225
|
}
|
|
1874
3226
|
} else {
|
|
1875
|
-
tail = readBoundedRecentLedgerWindow(directory,
|
|
3227
|
+
tail = readBoundedRecentLedgerWindow(directory, stat2.size);
|
|
1876
3228
|
mode = "checkpoint+tail";
|
|
1877
3229
|
}
|
|
1878
3230
|
const records = new Map;
|
|
@@ -1898,19 +3250,19 @@ function loadFoldedState(directory, options) {
|
|
|
1898
3250
|
};
|
|
1899
3251
|
}
|
|
1900
3252
|
function readLedgerRegion(directory, offset, length) {
|
|
1901
|
-
const fd =
|
|
3253
|
+
const fd = fs3.openSync(storePath(directory), "r");
|
|
1902
3254
|
try {
|
|
1903
3255
|
const buffer = Buffer.alloc(length);
|
|
1904
3256
|
let read = 0;
|
|
1905
3257
|
while (read < length) {
|
|
1906
|
-
const n =
|
|
3258
|
+
const n = fs3.readSync(fd, buffer, read, length - read, offset + read);
|
|
1907
3259
|
if (n <= 0)
|
|
1908
3260
|
break;
|
|
1909
3261
|
read += n;
|
|
1910
3262
|
}
|
|
1911
3263
|
return read === length ? buffer : buffer.subarray(0, read);
|
|
1912
3264
|
} finally {
|
|
1913
|
-
|
|
3265
|
+
fs3.closeSync(fd);
|
|
1914
3266
|
}
|
|
1915
3267
|
}
|
|
1916
3268
|
function readBoundedRecentLedgerWindow(directory, size) {
|
|
@@ -1963,7 +3315,7 @@ function loadLegacyLedger(directory, strict) {
|
|
|
1963
3315
|
if (!strict) {
|
|
1964
3316
|
let raw2;
|
|
1965
3317
|
try {
|
|
1966
|
-
raw2 =
|
|
3318
|
+
raw2 = fs3.readFileSync(storePath(directory), "utf-8");
|
|
1967
3319
|
} catch (err) {
|
|
1968
3320
|
if (err.code !== "ENOENT") {
|
|
1969
3321
|
warn(`[background] readDelegations failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -1991,8 +3343,8 @@ function loadLegacyLedger(directory, strict) {
|
|
|
1991
3343
|
let absolutePath;
|
|
1992
3344
|
try {
|
|
1993
3345
|
absolutePath = storePath(directory);
|
|
1994
|
-
const
|
|
1995
|
-
if (
|
|
3346
|
+
const stat2 = fs3.statSync(absolutePath);
|
|
3347
|
+
if (stat2.size > MAX_RECOVERY_LEDGER_BYTES) {
|
|
1996
3348
|
return {
|
|
1997
3349
|
status: "uncertain",
|
|
1998
3350
|
reason: `background delegation ledger exceeds the ${MAX_RECOVERY_LEDGER_BYTES}-byte recovery bound`
|
|
@@ -2015,7 +3367,7 @@ function loadLegacyLedger(directory, strict) {
|
|
|
2015
3367
|
}
|
|
2016
3368
|
let raw;
|
|
2017
3369
|
try {
|
|
2018
|
-
raw =
|
|
3370
|
+
raw = fs3.readFileSync(absolutePath, "utf-8");
|
|
2019
3371
|
} catch (error) {
|
|
2020
3372
|
return {
|
|
2021
3373
|
status: "uncertain",
|
|
@@ -2366,7 +3718,7 @@ function compactDelegationsLocked(directory, options) {
|
|
|
2366
3718
|
}
|
|
2367
3719
|
let ledgerBytes;
|
|
2368
3720
|
try {
|
|
2369
|
-
ledgerBytes =
|
|
3721
|
+
ledgerBytes = fs3.readFileSync(storePath(directory));
|
|
2370
3722
|
} catch (err) {
|
|
2371
3723
|
if (err.code === "ENOENT") {
|
|
2372
3724
|
return { status: "skipped", reason: "no ledger to compact" };
|
|
@@ -2407,7 +3759,7 @@ function compactDelegationsLocked(directory, options) {
|
|
|
2407
3759
|
schemaVersion: 1,
|
|
2408
3760
|
sequence: 1,
|
|
2409
3761
|
writerId: WRITER_ID,
|
|
2410
|
-
rootPath:
|
|
3762
|
+
rootPath: path4.resolve(directory),
|
|
2411
3763
|
createdAt: now,
|
|
2412
3764
|
cutLedgerBytes,
|
|
2413
3765
|
cutLedgerDigest: sha256Hex(ledgerBytes),
|
|
@@ -2459,7 +3811,7 @@ function compactDelegationsLocked(directory, options) {
|
|
|
2459
3811
|
schemaVersion: 1,
|
|
2460
3812
|
sequence,
|
|
2461
3813
|
writerId: WRITER_ID,
|
|
2462
|
-
rootPath:
|
|
3814
|
+
rootPath: path4.resolve(directory),
|
|
2463
3815
|
createdAt: now,
|
|
2464
3816
|
cutLedgerBytes,
|
|
2465
3817
|
cutLedgerDigest: sha256Hex(ledgerBytes),
|
|
@@ -2499,7 +3851,7 @@ function compactDelegationsLocked(directory, options) {
|
|
|
2499
3851
|
`);
|
|
2500
3852
|
let currentSize;
|
|
2501
3853
|
try {
|
|
2502
|
-
currentSize =
|
|
3854
|
+
currentSize = fs3.statSync(storePath(directory)).size;
|
|
2503
3855
|
} catch (err) {
|
|
2504
3856
|
return {
|
|
2505
3857
|
status: "uncertain",
|
|
@@ -2563,8 +3915,8 @@ function countCompleteLines(buffer) {
|
|
|
2563
3915
|
}
|
|
2564
3916
|
function maybeCompactDelegationsLocked(directory) {
|
|
2565
3917
|
try {
|
|
2566
|
-
const
|
|
2567
|
-
if (
|
|
3918
|
+
const stat2 = fs3.statSync(storePath(directory));
|
|
3919
|
+
if (stat2.size <= DELEGATION_COMPACTION_HIGH_WATER_BYTES)
|
|
2568
3920
|
return;
|
|
2569
3921
|
const outcome = compactDelegationsLocked(directory, {});
|
|
2570
3922
|
if (outcome.status === "uncertain") {
|
|
@@ -2575,7 +3927,7 @@ function maybeCompactDelegationsLocked(directory) {
|
|
|
2575
3927
|
}
|
|
2576
3928
|
}
|
|
2577
3929
|
function ensureSwarmDir(directory) {
|
|
2578
|
-
|
|
3930
|
+
fs3.mkdirSync(path4.resolve(directory, ".swarm"), { recursive: true });
|
|
2579
3931
|
}
|
|
2580
3932
|
var DELEGATION_READ_RETRY_DELAY_MS = 25;
|
|
2581
3933
|
var MAX_TRACKED_DELEGATION_READ_UNCERTAIN_ROOTS = 32;
|
|
@@ -2627,7 +3979,7 @@ function emitDelegationReadUncertainBounded(directory, outcome) {
|
|
|
2627
3979
|
const rootKey = canonicalRootKeyFresh(directory);
|
|
2628
3980
|
const now = Date.now();
|
|
2629
3981
|
const last = delegationReadUncertainEmitAtByRoot.get(rootKey);
|
|
2630
|
-
if (last !== undefined && now - last <
|
|
3982
|
+
if (last !== undefined && now - last < _internals2.delegationReadUncertainEmitCooldownMs) {
|
|
2631
3983
|
return;
|
|
2632
3984
|
}
|
|
2633
3985
|
delegationReadUncertainEmitAtByRoot.delete(rootKey);
|
|
@@ -2658,7 +4010,7 @@ function readDelegationsDetailed(directory) {
|
|
|
2658
4010
|
const first = readDelegationLoadAttempt(directory);
|
|
2659
4011
|
if (first.status === "ok")
|
|
2660
4012
|
return first;
|
|
2661
|
-
portableSyncSleep(
|
|
4013
|
+
portableSyncSleep(_internals2.readRetryDelayMs);
|
|
2662
4014
|
const second = readDelegationLoadAttempt(directory);
|
|
2663
4015
|
if (second.status === "ok")
|
|
2664
4016
|
return second;
|
|
@@ -2735,7 +4087,7 @@ function appendRecord(directory, record) {
|
|
|
2735
4087
|
pruneCoordinationDelegationRows(directory);
|
|
2736
4088
|
const current = getCoordinationState(directory, DELEGATION_COORDINATION_NAMESPACE, record.correlationId);
|
|
2737
4089
|
const eventPayload = JSON.stringify(record);
|
|
2738
|
-
const eventDigest =
|
|
4090
|
+
const eventDigest = createHash5("sha256").update(eventPayload).digest("hex");
|
|
2739
4091
|
const result = transitionCoordinationState(directory, {
|
|
2740
4092
|
namespace: DELEGATION_COORDINATION_NAMESPACE,
|
|
2741
4093
|
entityKey: record.correlationId,
|
|
@@ -2753,7 +4105,7 @@ function appendRecord(directory, record) {
|
|
|
2753
4105
|
if (result.outcome !== "applied" && result.outcome !== "duplicate") {
|
|
2754
4106
|
throw new Error(`coordination append rejected for ${record.correlationId}: ${result.outcome}`);
|
|
2755
4107
|
}
|
|
2756
|
-
if (!
|
|
4108
|
+
if (!fs3.existsSync(projectionMarkerPath(storePath(directory)))) {
|
|
2757
4109
|
const records = coordinationRowsToDelegations(directory);
|
|
2758
4110
|
if (records)
|
|
2759
4111
|
writeDelegationShadowProjection(directory, records);
|
|
@@ -2761,7 +4113,7 @@ function appendRecord(directory, record) {
|
|
|
2761
4113
|
return;
|
|
2762
4114
|
}
|
|
2763
4115
|
ensureSwarmDir(directory);
|
|
2764
|
-
|
|
4116
|
+
fs3.appendFileSync(storePath(directory), `${JSON.stringify(record)}
|
|
2765
4117
|
`, "utf-8");
|
|
2766
4118
|
}
|
|
2767
4119
|
function buildPendingRecord(input, now) {
|
|
@@ -2879,7 +4231,7 @@ function buildPromptSnapshot(text, maxChars) {
|
|
|
2879
4231
|
text: bounded,
|
|
2880
4232
|
chars: text.length,
|
|
2881
4233
|
truncated,
|
|
2882
|
-
digest:
|
|
4234
|
+
digest: createHash5("sha256").update(text).digest("hex")
|
|
2883
4235
|
};
|
|
2884
4236
|
}
|
|
2885
4237
|
function isTypedTerminalStatus(status) {
|
|
@@ -2987,11 +4339,19 @@ async function publishPrReviewResultReceipt(directory, input) {
|
|
|
2987
4339
|
};
|
|
2988
4340
|
return;
|
|
2989
4341
|
}
|
|
4342
|
+
const duplicateFindingIdOwner = findCrossLaneFindingIdOwner(records, current, parsed.data);
|
|
4343
|
+
if (duplicateFindingIdOwner) {
|
|
4344
|
+
outcome = {
|
|
4345
|
+
status: "conflict",
|
|
4346
|
+
reason: `duplicate id ${duplicateFindingIdOwner.findingId} already published by lane ${duplicateFindingIdOwner.ownerLaneId}`
|
|
4347
|
+
};
|
|
4348
|
+
return;
|
|
4349
|
+
}
|
|
2990
4350
|
const staleContractAdmissionResult = {
|
|
2991
4351
|
...current.result ?? {
|
|
2992
4352
|
chars: 0,
|
|
2993
4353
|
truncated: false,
|
|
2994
|
-
digest:
|
|
4354
|
+
digest: createHash5("sha256").update("").digest("hex")
|
|
2995
4355
|
},
|
|
2996
4356
|
prReviewResultReceipt: parsed.data
|
|
2997
4357
|
};
|
|
@@ -3005,7 +4365,7 @@ async function publishPrReviewResultReceipt(directory, input) {
|
|
|
3005
4365
|
...current.result ?? {
|
|
3006
4366
|
chars: 0,
|
|
3007
4367
|
truncated: false,
|
|
3008
|
-
digest:
|
|
4368
|
+
digest: createHash5("sha256").update("").digest("hex")
|
|
3009
4369
|
},
|
|
3010
4370
|
prReviewResultReceipt: parsed.data
|
|
3011
4371
|
};
|
|
@@ -3036,6 +4396,142 @@ async function publishPrReviewResultReceipt(directory, input) {
|
|
|
3036
4396
|
};
|
|
3037
4397
|
}
|
|
3038
4398
|
}
|
|
4399
|
+
async function supersedeStaleLivenessVerdictLane(directory, input) {
|
|
4400
|
+
let outcome = {
|
|
4401
|
+
status: "refused",
|
|
4402
|
+
reason: "liveness supersession did not complete"
|
|
4403
|
+
};
|
|
4404
|
+
try {
|
|
4405
|
+
await withEvidenceLock(directory, BACKGROUND_DELEGATIONS_FILE, STORE_LOCK_AGENT, STORE_LOCK_TASK, async () => {
|
|
4406
|
+
const records = loadRecordsForWrite(directory);
|
|
4407
|
+
if (records === null) {
|
|
4408
|
+
outcome = {
|
|
4409
|
+
status: "refused",
|
|
4410
|
+
reason: "delegation state is uncertain"
|
|
4411
|
+
};
|
|
4412
|
+
return;
|
|
4413
|
+
}
|
|
4414
|
+
const record = findRecordForWrite(records, input.correlationId);
|
|
4415
|
+
if (!record) {
|
|
4416
|
+
outcome = {
|
|
4417
|
+
status: "refused",
|
|
4418
|
+
reason: `no delegation record for correlationId ${input.correlationId}`
|
|
4419
|
+
};
|
|
4420
|
+
return;
|
|
4421
|
+
}
|
|
4422
|
+
if (record.mode !== "swarm-pr-review:reviewer" && record.mode !== "swarm-pr-review:critic") {
|
|
4423
|
+
outcome = {
|
|
4424
|
+
status: "refused",
|
|
4425
|
+
reason: `mode ${record.mode ?? "<none>"} is not a verdict lane (swarm-pr-review:reviewer|critic only)`
|
|
4426
|
+
};
|
|
4427
|
+
return;
|
|
4428
|
+
}
|
|
4429
|
+
if (record.status !== "stale") {
|
|
4430
|
+
outcome = {
|
|
4431
|
+
status: "refused",
|
|
4432
|
+
reason: `record status is ${record.status}, not the liveness sweep's stale`
|
|
4433
|
+
};
|
|
4434
|
+
return;
|
|
4435
|
+
}
|
|
4436
|
+
const terminalClass = record.terminalResult?.result.workflowLaneFailureClass ?? record.result?.workflowLaneFailureClass;
|
|
4437
|
+
if (terminalClass === "operator_cancelled") {
|
|
4438
|
+
outcome = {
|
|
4439
|
+
status: "refused",
|
|
4440
|
+
reason: "terminal failure class operator_cancelled is never superseded by a later transcript"
|
|
4441
|
+
};
|
|
4442
|
+
return;
|
|
4443
|
+
}
|
|
4444
|
+
if (terminalClass !== "liveness") {
|
|
4445
|
+
outcome = {
|
|
4446
|
+
status: "refused",
|
|
4447
|
+
reason: `terminal failure class ${terminalClass ?? "<none>"} is not liveness`
|
|
4448
|
+
};
|
|
4449
|
+
return;
|
|
4450
|
+
}
|
|
4451
|
+
let parentState;
|
|
4452
|
+
try {
|
|
4453
|
+
parentState = await readPrWorkflowGateStateFromDisk(directory, record.parentSessionId);
|
|
4454
|
+
} catch (error) {
|
|
4455
|
+
outcome = {
|
|
4456
|
+
status: "refused",
|
|
4457
|
+
reason: `parent gate state read failed (${error instanceof Error ? error.message : String(error)}); cannot verify verdict-settlement receipts`
|
|
4458
|
+
};
|
|
4459
|
+
return;
|
|
4460
|
+
}
|
|
4461
|
+
if (!parentState) {
|
|
4462
|
+
outcome = {
|
|
4463
|
+
status: "refused",
|
|
4464
|
+
reason: `no PR_REVIEW gate state for parent session ${record.parentSessionId}; cannot verify verdict-settlement receipts`
|
|
4465
|
+
};
|
|
4466
|
+
return;
|
|
4467
|
+
}
|
|
4468
|
+
const runId = parentState.prReviewArtifactRunId ?? parentState.prReviewReservedRunId ?? parentState.prReviewTriggerEvalRunId;
|
|
4469
|
+
if (!runId) {
|
|
4470
|
+
outcome = {
|
|
4471
|
+
status: "refused",
|
|
4472
|
+
reason: "no resolvable pr-review run id in parent gate state; cannot verify verdict-settlement receipts"
|
|
4473
|
+
};
|
|
4474
|
+
return;
|
|
4475
|
+
}
|
|
4476
|
+
for (const phase of ["reviewer", "critic"]) {
|
|
4477
|
+
const receiptRead = readVerdictSettlementReceipt(directory, runId, phase);
|
|
4478
|
+
if (receiptRead.status === "absent")
|
|
4479
|
+
continue;
|
|
4480
|
+
if (receiptRead.status === "invalid") {
|
|
4481
|
+
outcome = {
|
|
4482
|
+
status: "refused",
|
|
4483
|
+
reason: `verdict-settlement receipt for phase ${phase} under run ${runId} is unreadable (${receiptRead.reason}); cannot prove the lane uncited`
|
|
4484
|
+
};
|
|
4485
|
+
return;
|
|
4486
|
+
}
|
|
4487
|
+
if (receiptRead.receipt.runId !== runId) {
|
|
4488
|
+
outcome = {
|
|
4489
|
+
status: "refused",
|
|
4490
|
+
reason: `verdict-settlement receipt under run ${runId} carries runId ${receiptRead.receipt.runId}; cannot prove the lane uncited`
|
|
4491
|
+
};
|
|
4492
|
+
return;
|
|
4493
|
+
}
|
|
4494
|
+
const cited = receiptRead.receipt.items.find((item) => item.sourceBatchId === record.batchId && item.sourceLaneId === record.laneId);
|
|
4495
|
+
if (cited) {
|
|
4496
|
+
outcome = {
|
|
4497
|
+
status: "refused",
|
|
4498
|
+
reason: `verdict-settlement receipt for phase ${phase} under run ${runId} cites lane ${record.laneId ?? record.correlationId} (batch ${record.batchId ?? "<none>"}, item ${cited.itemId}, ${cited.disposition}); a disclosed dead lane is never resurrected by a later transcript`
|
|
4499
|
+
};
|
|
4500
|
+
return;
|
|
4501
|
+
}
|
|
4502
|
+
}
|
|
4503
|
+
const now = Date.now();
|
|
4504
|
+
const supersedeResult = {
|
|
4505
|
+
...input.validatedResult
|
|
4506
|
+
};
|
|
4507
|
+
delete supersedeResult.error;
|
|
4508
|
+
delete supersedeResult.workflowLaneFailureClass;
|
|
4509
|
+
const terminal = buildTypedDelegationTerminal(record, "completed", supersedeResult, now);
|
|
4510
|
+
const next = {
|
|
4511
|
+
...record,
|
|
4512
|
+
schemaVersion: record.schemaVersion === 4 ? 4 : 3,
|
|
4513
|
+
status: "completed",
|
|
4514
|
+
updatedAt: now,
|
|
4515
|
+
result: supersedeResult,
|
|
4516
|
+
terminalResult: terminal,
|
|
4517
|
+
completedAt: terminal.recordedAt
|
|
4518
|
+
};
|
|
4519
|
+
appendRecord(directory, next);
|
|
4520
|
+
maybeCompactDelegationsLocked(directory);
|
|
4521
|
+
outcome = {
|
|
4522
|
+
status: "superseded",
|
|
4523
|
+
reason: `lane ${record.laneId ?? record.correlationId} superseded its stale/liveness terminal with validated rows`
|
|
4524
|
+
};
|
|
4525
|
+
});
|
|
4526
|
+
return outcome;
|
|
4527
|
+
} catch (error) {
|
|
4528
|
+
warn(`[background] supersedeStaleLivenessVerdictLane failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
4529
|
+
return {
|
|
4530
|
+
status: "refused",
|
|
4531
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
4532
|
+
};
|
|
4533
|
+
}
|
|
4534
|
+
}
|
|
3039
4535
|
function buildBackgroundCompletionEventId(input) {
|
|
3040
4536
|
const canonical = JSON.stringify([
|
|
3041
4537
|
input.correlationId,
|
|
@@ -3043,7 +4539,7 @@ function buildBackgroundCompletionEventId(input) {
|
|
|
3043
4539
|
input.status,
|
|
3044
4540
|
input.resultDigest
|
|
3045
4541
|
]);
|
|
3046
|
-
return `bgc1:${
|
|
4542
|
+
return `bgc1:${createHash5("sha256").update(canonical).digest("hex")}`;
|
|
3047
4543
|
}
|
|
3048
4544
|
function buildTypedDelegationTerminal(record, status, result, now) {
|
|
3049
4545
|
return {
|
|
@@ -3110,6 +4606,31 @@ function findReceiptPublicationTarget(records, input) {
|
|
|
3110
4606
|
}
|
|
3111
4607
|
return { count, record: count === 1 ? record : null };
|
|
3112
4608
|
}
|
|
4609
|
+
function findCrossLaneFindingIdOwner(records, incoming, receipt) {
|
|
4610
|
+
const ownedByLane = new Map;
|
|
4611
|
+
for (const sibling of records) {
|
|
4612
|
+
if (sibling.correlationId === incoming.correlationId)
|
|
4613
|
+
continue;
|
|
4614
|
+
const siblingReceipt = recordedPrReviewResultReceipt(sibling);
|
|
4615
|
+
if (!siblingReceipt)
|
|
4616
|
+
continue;
|
|
4617
|
+
if (siblingReceipt.workflowInstanceId !== receipt.workflowInstanceId) {
|
|
4618
|
+
continue;
|
|
4619
|
+
}
|
|
4620
|
+
for (const finding of siblingReceipt.envelope.findings) {
|
|
4621
|
+
if (!ownedByLane.has(finding.id)) {
|
|
4622
|
+
ownedByLane.set(finding.id, siblingReceipt.laneId);
|
|
4623
|
+
}
|
|
4624
|
+
}
|
|
4625
|
+
}
|
|
4626
|
+
for (const finding of receipt.envelope.findings) {
|
|
4627
|
+
const ownerLaneId = ownedByLane.get(finding.id);
|
|
4628
|
+
if (ownerLaneId !== undefined) {
|
|
4629
|
+
return { findingId: finding.id, ownerLaneId };
|
|
4630
|
+
}
|
|
4631
|
+
}
|
|
4632
|
+
return null;
|
|
4633
|
+
}
|
|
3113
4634
|
function settlementProvenanceFor(record) {
|
|
3114
4635
|
const baseline = record.taskChangeContext?.baseline;
|
|
3115
4636
|
if (!baseline)
|
|
@@ -3289,10 +4810,10 @@ function normalizeObservedFile(value) {
|
|
|
3289
4810
|
if (typeof value !== "string")
|
|
3290
4811
|
return null;
|
|
3291
4812
|
const trimmed = value.trim().replace(/\\/g, "/");
|
|
3292
|
-
if (trimmed.length === 0 || trimmed.length > 4096 || trimmed.includes("\x00") ||
|
|
4813
|
+
if (trimmed.length === 0 || trimmed.length > 4096 || trimmed.includes("\x00") || path4.posix.isAbsolute(trimmed) || /^[A-Za-z]:\//.test(trimmed)) {
|
|
3293
4814
|
return null;
|
|
3294
4815
|
}
|
|
3295
|
-
const normalized =
|
|
4816
|
+
const normalized = path4.posix.normalize(trimmed).replace(/^\.\//, "");
|
|
3296
4817
|
if (normalized === "." || normalized === ".." || normalized.startsWith("../") || normalized.endsWith("/")) {
|
|
3297
4818
|
return null;
|
|
3298
4819
|
}
|
|
@@ -3426,7 +4947,7 @@ function registerLegacyCoderSettlementReconciler(directory, reconciler) {
|
|
|
3426
4947
|
function getLegacyCoderSettlementReconciler(directory) {
|
|
3427
4948
|
return legacyCoderSettlementReconcilers.get(canonicalRootKeyFresh(directory));
|
|
3428
4949
|
}
|
|
3429
|
-
var
|
|
4950
|
+
var _internals2 = {
|
|
3430
4951
|
readRetryDelayMs: DELEGATION_READ_RETRY_DELAY_MS,
|
|
3431
4952
|
delegationReadUncertainEmitCooldownMs: DELEGATION_READ_UNCERTAIN_EMIT_COOLDOWN_MS,
|
|
3432
4953
|
resetDelegationReadUncertainRegistry: () => {
|
|
@@ -3535,7 +5056,7 @@ async function claimDelegationIngestion(directory, correlationId, options) {
|
|
|
3535
5056
|
}
|
|
3536
5057
|
const disposition = current.status === "ingestion_error" || current.ingestion?.state === "claimed" ? "retry" : "claimed";
|
|
3537
5058
|
const attempt = (current.ingestion?.attempt ?? 0) + 1;
|
|
3538
|
-
const claimToken =
|
|
5059
|
+
const claimToken = createHash5("sha256").update(JSON.stringify([correlationId, options.claimantId, attempt, now])).digest("hex");
|
|
3539
5060
|
const next = {
|
|
3540
5061
|
...current,
|
|
3541
5062
|
schemaVersion: current.schemaVersion === 4 ? 4 : 3,
|
|
@@ -3880,7 +5401,7 @@ function sweepStaleLocked(directory, timeoutMs, now, statuses = DEFAULT_SWEEPABL
|
|
|
3880
5401
|
error: staleReason,
|
|
3881
5402
|
chars: staleReason.length,
|
|
3882
5403
|
truncated: false,
|
|
3883
|
-
digest:
|
|
5404
|
+
digest: createHash5("sha256").update(staleReason).digest("hex"),
|
|
3884
5405
|
workflowLaneFailureClass: failureClass
|
|
3885
5406
|
};
|
|
3886
5407
|
const staleTerminal = buildTypedDelegationTerminal(record, "stale", livenessResult, now);
|
|
@@ -3922,24 +5443,24 @@ async function sweepStaleDelegations(directory, timeoutMs, options = {}) {
|
|
|
3922
5443
|
}
|
|
3923
5444
|
}
|
|
3924
5445
|
function fallbackRelativePath(correlationId) {
|
|
3925
|
-
const digest =
|
|
3926
|
-
return
|
|
5446
|
+
const digest = createHash5("sha256").update(correlationId).digest("hex");
|
|
5447
|
+
return path4.join(BACKGROUND_DELEGATION_FALLBACK_DIR, `${digest}.json`);
|
|
3927
5448
|
}
|
|
3928
5449
|
function fallbackPath(directory, correlationId) {
|
|
3929
5450
|
return validateSwarmPath(directory, fallbackRelativePath(correlationId));
|
|
3930
5451
|
}
|
|
3931
5452
|
function fallbackDirectoryPath(directory) {
|
|
3932
|
-
return
|
|
5453
|
+
return path4.dirname(validateSwarmPath(directory, path4.join(BACKGROUND_DELEGATION_FALLBACK_DIR, ".containment-anchor")));
|
|
3933
5454
|
}
|
|
3934
5455
|
function readFallbackDirectory(directory) {
|
|
3935
|
-
return
|
|
5456
|
+
return fs3.readdirSync(fallbackDirectoryPath(directory), {
|
|
3936
5457
|
withFileTypes: true
|
|
3937
5458
|
});
|
|
3938
5459
|
}
|
|
3939
5460
|
async function readFallbackFile(directory, correlationId) {
|
|
3940
5461
|
for (let attempt = 0;attempt < 5; attempt++) {
|
|
3941
5462
|
try {
|
|
3942
|
-
const raw = await
|
|
5463
|
+
const raw = await fs3.promises.readFile(fallbackPath(directory, correlationId), "utf-8");
|
|
3943
5464
|
const parsedJson = JSON.parse(raw);
|
|
3944
5465
|
const parsed = FallbackArtifactSchema.safeParse(parsedJson);
|
|
3945
5466
|
if (!parsed.success || parsed.data.correlationId !== correlationId) {
|
|
@@ -3949,7 +5470,7 @@ async function readFallbackFile(directory, correlationId) {
|
|
|
3949
5470
|
} catch (err) {
|
|
3950
5471
|
const code = err.code;
|
|
3951
5472
|
if (code === "ENOENT" && attempt < 4) {
|
|
3952
|
-
await new Promise((
|
|
5473
|
+
await new Promise((resolve4) => setTimeout(resolve4, 10));
|
|
3953
5474
|
continue;
|
|
3954
5475
|
}
|
|
3955
5476
|
return null;
|
|
@@ -3965,14 +5486,14 @@ async function readDelegationFallback(directory, correlationId) {
|
|
|
3965
5486
|
async function listDelegationFallbacks(directory) {
|
|
3966
5487
|
let entries;
|
|
3967
5488
|
try {
|
|
3968
|
-
entries =
|
|
5489
|
+
entries = _internals2.readFallbackDirectory(directory).sort((a, b) => compareCodeUnits(a.name, b.name)).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).slice(0, MAX_LIVE_BACKGROUND_FALLBACKS);
|
|
3969
5490
|
} catch {
|
|
3970
5491
|
return [];
|
|
3971
5492
|
}
|
|
3972
5493
|
const artifacts = [];
|
|
3973
5494
|
for (const entry of entries) {
|
|
3974
5495
|
try {
|
|
3975
|
-
const raw = await
|
|
5496
|
+
const raw = await fs3.promises.readFile(path4.join(fallbackDirectoryPath(directory), entry.name), "utf-8");
|
|
3976
5497
|
const parsed = FallbackArtifactSchema.safeParse(JSON.parse(raw));
|
|
3977
5498
|
if (parsed.success)
|
|
3978
5499
|
artifacts.push(parsed.data);
|
|
@@ -3985,7 +5506,7 @@ async function scanDelegationFallbacksForRecovery(directory) {
|
|
|
3985
5506
|
let entries;
|
|
3986
5507
|
try {
|
|
3987
5508
|
fallbackDir = fallbackDirectoryPath(directory);
|
|
3988
|
-
entries =
|
|
5509
|
+
entries = _internals2.readFallbackDirectory(directory).sort((a, b) => compareCodeUnits(a.name, b.name)).filter((entry) => entry.isFile() && entry.name.endsWith(".json"));
|
|
3989
5510
|
} catch (error) {
|
|
3990
5511
|
if (error.code === "ENOENT") {
|
|
3991
5512
|
return { status: "ok", owners: [] };
|
|
@@ -4003,17 +5524,17 @@ async function scanDelegationFallbacksForRecovery(directory) {
|
|
|
4003
5524
|
}
|
|
4004
5525
|
const artifacts = [];
|
|
4005
5526
|
for (const entry of entries) {
|
|
4006
|
-
const artifactPath =
|
|
5527
|
+
const artifactPath = path4.join(fallbackDir, entry.name);
|
|
4007
5528
|
let raw;
|
|
4008
5529
|
try {
|
|
4009
|
-
const
|
|
4010
|
-
if (
|
|
5530
|
+
const stat2 = fs3.statSync(artifactPath);
|
|
5531
|
+
if (stat2.size > MAX_RECOVERY_FALLBACK_BYTES) {
|
|
4011
5532
|
return {
|
|
4012
5533
|
status: "uncertain",
|
|
4013
5534
|
reason: `background fallback artifact "${entry.name}" exceeds the recovery size bound`
|
|
4014
5535
|
};
|
|
4015
5536
|
}
|
|
4016
|
-
raw = await
|
|
5537
|
+
raw = await fs3.promises.readFile(artifactPath, "utf-8");
|
|
4017
5538
|
} catch (error) {
|
|
4018
5539
|
return {
|
|
4019
5540
|
status: "uncertain",
|
|
@@ -4048,7 +5569,7 @@ async function scanDelegationFallbacksForRecovery(directory) {
|
|
|
4048
5569
|
}
|
|
4049
5570
|
function countFallbackFiles(directory) {
|
|
4050
5571
|
try {
|
|
4051
|
-
return
|
|
5572
|
+
return fs3.readdirSync(fallbackDirectoryPath(directory), {
|
|
4052
5573
|
withFileTypes: true
|
|
4053
5574
|
}).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).length;
|
|
4054
5575
|
} catch (err) {
|
|
@@ -4083,7 +5604,7 @@ async function writeDelegationFallback(directory, input, options = {}) {
|
|
|
4083
5604
|
if (countFallbackFiles(directory) >= maxLive)
|
|
4084
5605
|
return null;
|
|
4085
5606
|
const absPath = fallbackPath(directory, input.correlationId);
|
|
4086
|
-
|
|
5607
|
+
fs3.mkdirSync(path4.dirname(absPath), { recursive: true });
|
|
4087
5608
|
await bunWrite(absPath, `${JSON.stringify(parsed.data)}
|
|
4088
5609
|
`);
|
|
4089
5610
|
return parsed.data;
|
|
@@ -4099,7 +5620,7 @@ async function removeDelegationFallback(directory, correlationId) {
|
|
|
4099
5620
|
try {
|
|
4100
5621
|
return await withEvidenceLock(directory, BACKGROUND_DELEGATION_FALLBACK_DIR, STORE_LOCK_AGENT, FALLBACK_LOCK_TASK, async () => {
|
|
4101
5622
|
try {
|
|
4102
|
-
await
|
|
5623
|
+
await fs3.promises.unlink(fallbackPath(directory, correlationId));
|
|
4103
5624
|
return true;
|
|
4104
5625
|
} catch (err) {
|
|
4105
5626
|
return err.code === "ENOENT";
|
|
@@ -4171,7 +5692,7 @@ async function promoteDelegationFallback(directory, correlationId) {
|
|
|
4171
5692
|
if (!promoted)
|
|
4172
5693
|
return null;
|
|
4173
5694
|
try {
|
|
4174
|
-
await
|
|
5695
|
+
await fs3.promises.unlink(fallbackPath(directory, correlationId));
|
|
4175
5696
|
} catch (err) {
|
|
4176
5697
|
if (err.code !== "ENOENT") {
|
|
4177
5698
|
return null;
|
|
@@ -4189,11 +5710,11 @@ function reservationStorePath(directory) {
|
|
|
4189
5710
|
return validateSwarmPath(directory, BACKGROUND_CODER_RESERVATIONS_FILE);
|
|
4190
5711
|
}
|
|
4191
5712
|
function reservationShadowDigest(directory, reservations) {
|
|
4192
|
-
const hash =
|
|
5713
|
+
const hash = createHash5("sha256");
|
|
4193
5714
|
hash.update(JSON.stringify(reservations));
|
|
4194
5715
|
const filePath = reservationStorePath(directory);
|
|
4195
|
-
if (
|
|
4196
|
-
hash.update(
|
|
5716
|
+
if (fs3.existsSync(filePath)) {
|
|
5717
|
+
hash.update(fs3.readFileSync(filePath));
|
|
4197
5718
|
}
|
|
4198
5719
|
return hash.digest("hex");
|
|
4199
5720
|
}
|
|
@@ -4202,9 +5723,9 @@ function writeReservationShadowProjection(directory, reservations) {
|
|
|
4202
5723
|
schemaVersion: 1,
|
|
4203
5724
|
reservations
|
|
4204
5725
|
});
|
|
4205
|
-
|
|
5726
|
+
fs3.writeFileSync(reservationStorePath(directory), `${JSON.stringify(parsed)}
|
|
4206
5727
|
`, "utf-8");
|
|
4207
|
-
|
|
5728
|
+
fs3.writeFileSync(projectionMarkerPath(reservationStorePath(directory)), "", "utf-8");
|
|
4208
5729
|
}
|
|
4209
5730
|
function coordinationRowsToReservations(directory) {
|
|
4210
5731
|
if (!projectDbExists(directory))
|
|
@@ -4231,14 +5752,14 @@ function scanLegacyBackgroundCoderReservationsForAdmission(directory) {
|
|
|
4231
5752
|
let raw;
|
|
4232
5753
|
try {
|
|
4233
5754
|
absolutePath = reservationStorePath(directory);
|
|
4234
|
-
const
|
|
4235
|
-
if (
|
|
5755
|
+
const stat2 = fs3.statSync(absolutePath);
|
|
5756
|
+
if (stat2.size > MAX_BACKGROUND_CODER_RESERVATION_STORE_BYTES) {
|
|
4236
5757
|
return {
|
|
4237
5758
|
status: "uncertain",
|
|
4238
5759
|
reason: `background coder reservation store exceeds the ${MAX_BACKGROUND_CODER_RESERVATION_STORE_BYTES}-byte safety bound`
|
|
4239
5760
|
};
|
|
4240
5761
|
}
|
|
4241
|
-
raw =
|
|
5762
|
+
raw = fs3.readFileSync(absolutePath, "utf-8");
|
|
4242
5763
|
} catch (error) {
|
|
4243
5764
|
if (error.code === "ENOENT") {
|
|
4244
5765
|
return { status: "ok", reservations: [] };
|
|
@@ -4281,7 +5802,7 @@ function ensureReservationCoordinationImported(directory, createIfNeeded) {
|
|
|
4281
5802
|
return "uncertain";
|
|
4282
5803
|
if (existing && existing.length > 0) {
|
|
4283
5804
|
const legacy2 = scanLegacyBackgroundCoderReservationsForAdmission(directory);
|
|
4284
|
-
if (!
|
|
5805
|
+
if (!fs3.existsSync(projectionMarkerPath(reservationStorePath(directory))) || legacy2.status !== "ok" || !isDeepStrictEqual(legacy2.reservations.sort((left, right) => left.reservationId.localeCompare(right.reservationId)), [...existing].sort((left, right) => left.reservationId.localeCompare(right.reservationId)))) {
|
|
4285
5806
|
archiveShadowFileBestEffort(reservationStorePath(directory));
|
|
4286
5807
|
}
|
|
4287
5808
|
writeReservationShadowProjection(directory, existing);
|
|
@@ -4292,7 +5813,7 @@ function ensureReservationCoordinationImported(directory, createIfNeeded) {
|
|
|
4292
5813
|
recordLedgerUncertainty(directory, legacy.reason, "reservation-legacy-import");
|
|
4293
5814
|
return "uncertain";
|
|
4294
5815
|
}
|
|
4295
|
-
if (legacy.reservations.length === 0 && !
|
|
5816
|
+
if (legacy.reservations.length === 0 && !fs3.existsSync(reservationStorePath(directory))) {
|
|
4296
5817
|
if (!createIfNeeded)
|
|
4297
5818
|
return "absent";
|
|
4298
5819
|
withCoordinationTransaction(directory, () => {
|
|
@@ -4341,7 +5862,7 @@ function ensureReservationCoordinationImported(directory, createIfNeeded) {
|
|
|
4341
5862
|
}
|
|
4342
5863
|
function buildBackgroundCoderReservationId(input) {
|
|
4343
5864
|
const ownerIdentity = input.planTaskId === null ? ["call", input.parentSessionId, input.callID] : ["task", input.parentSessionId, input.planTaskId];
|
|
4344
|
-
return `bgcr1:${
|
|
5865
|
+
return `bgcr1:${createHash5("sha256").update(JSON.stringify(ownerIdentity)).digest("hex")}`;
|
|
4345
5866
|
}
|
|
4346
5867
|
function scanBackgroundCoderReservationsForAdmission(directory) {
|
|
4347
5868
|
const importState = ensureReservationCoordinationImported(directory, false);
|
|
@@ -4375,7 +5896,7 @@ async function writeBackgroundCoderReservations(directory, reservations) {
|
|
|
4375
5896
|
if (importState === "ready") {
|
|
4376
5897
|
try {
|
|
4377
5898
|
withCoordinationTransaction(directory, () => {
|
|
4378
|
-
const safetyBound =
|
|
5899
|
+
const safetyBound = _internals2.reservationCoordinationRowLimit;
|
|
4379
5900
|
const authoritativeCount = countCoordinationStates(directory, RESERVATION_COORDINATION_NAMESPACE);
|
|
4380
5901
|
if (authoritativeCount > safetyBound) {
|
|
4381
5902
|
throw new Error(`reservation coordination write exceeded its ${safetyBound}-row safety bound`);
|
|
@@ -4407,7 +5928,7 @@ async function writeBackgroundCoderReservations(directory, reservations) {
|
|
|
4407
5928
|
payload: JSON.stringify(reservation),
|
|
4408
5929
|
event: {
|
|
4409
5930
|
streamId: reservation.reservationId,
|
|
4410
|
-
idempotencyKey:
|
|
5931
|
+
idempotencyKey: createHash5("sha256").update(JSON.stringify(reservation)).digest("hex"),
|
|
4411
5932
|
eventType: reservation.state,
|
|
4412
5933
|
payload: JSON.stringify(reservation)
|
|
4413
5934
|
}
|
|
@@ -4745,7 +6266,7 @@ async function reconcileLegacyCoderSettlements(directory, maxRecords, onReconcil
|
|
|
4745
6266
|
const candidates = readDelegations(directory).filter((record) => record.normalizedAgent === "coder" && Boolean(record.legacyCoderSettlementTransfer) && Boolean(record.terminalResult)).slice(0, maxRecords);
|
|
4746
6267
|
if (candidates.length === 0)
|
|
4747
6268
|
return 0;
|
|
4748
|
-
const { transferCoderSettlementToBackground } = await import("./coder-settlement-
|
|
6269
|
+
const { transferCoderSettlementToBackground } = await import("./coder-settlement-d0ca5fb3.js");
|
|
4749
6270
|
let transferred = 0;
|
|
4750
6271
|
for (const record of candidates) {
|
|
4751
6272
|
try {
|
|
@@ -5102,4 +6623,4 @@ async function maintainBackgroundDelegations(directory, options = {}) {
|
|
|
5102
6623
|
return emitObservation("ok");
|
|
5103
6624
|
}
|
|
5104
6625
|
|
|
5105
|
-
export { prReviewFindingRequiresCritic, PR_REVIEW_BASE_DIMENSION_IDS, PR_REVIEW_RISK_IMPACTS, PR_REVIEW_RISK_TAGS, prReviewLegacyTranscriptCompatibilityEnabled, PrReviewLaneResultEnvelopeSchema, decodePrReviewWorkflowBinding, prReviewLaneResultEnvelopeDigest, PrReviewResultReceiptSchema, PR_REVIEW_FINDINGS_MAX_BYTES, PR_REVIEW_HANDOFF_MAX_BYTES, PR_REVIEW_DISCARDED_EXAMPLE_ITEM_ID, PrReviewRunIdSchema, parsePrReviewRiskTagsField, PrReviewReviewerVerdictFieldsSchema, PrReviewCriticVerdictFieldsSchema, PR_REVIEW_VERDICT_ROW_DESCRIPTORS, parsePrReviewVerdictRow, generatePrReviewRunId, MAX_RECOVERY_LEDGER_BYTES, readDelegationHealthArtifact, collectDelegationLedgerHealth, MAX_PR_REVIEW_COLLECTION_RECEIPT_ITEM_IDS, BACKGROUND_DELEGATIONS_FILE, BACKGROUND_DELEGATION_FALLBACK_DIR, BACKGROUND_CODER_RESERVATIONS_FILE, BACKGROUND_DELEGATIONS_CHECKPOINT_FILE, BACKGROUND_DELEGATIONS_MANIFEST_FILE, MAX_LIVE_BACKGROUND_FALLBACKS, MAX_LIVE_BACKGROUND_CODER_RESERVATIONS, MAX_BACKGROUND_OBSERVED_FILES, MAX_BACKGROUND_ADVISORY_CHARS, LEGACY_CODER_SETTLEMENT_PENDING_ADVISORY_MARKER, DELEGATION_COMPACTION_LOW_WATER_BYTES, DELEGATION_COMPACTION_HIGH_WATER_BYTES, MAX_CHECKPOINT_BYTES, MAX_CHECKPOINT_RECORDS, TOMBSTONE_MIN_AGE_MS, BACKGROUND_INGESTION_LEASE_MS, BACKGROUND_CODER_RESERVATION_LEASE_MS, BACKGROUND_CODER_RESERVATION_LEASE_MAX_MS, BACKGROUND_CODER_RESERVATION_LEASE_MIN_MS, DEFAULT_STALE_DELEGATION_TIMEOUT_MS, DEFAULT_SWEEPABLE_DELEGATION_STATUSES, _checkpointInternals, isUnsettledWorktreeOwner, compactBackgroundDelegations, DELEGATION_READ_RETRY_DELAY_MS, readDelegationsDetailed, readDelegations, scanDelegationsForRecovery, findByCorrelationIdDetailed, findByCorrelationId, recordPendingDelegationDetailed, recordPendingDelegation, buildPromptSnapshot, appendDelegationTransition, publishPrReviewResultReceipt, buildBackgroundCompletionEventId, buildTypedDelegationTerminal, recordedPrReviewResultReceipt, claimTerminalResult, claimCoderSettlement, normalizeBackgroundObservedFiles, updateCoderSettlement, registerLegacyCoderSettlementReconciler, _internals, markLegacyCoderSettlementTransferPending, clearLegacyCoderSettlementTransferPending, claimDelegationIngestion, recordDelegationIngestionResult, putPendingBackgroundAdvisory, replacePendingBackgroundAdvisory, preparePendingBackgroundAdvisories, acknowledgeObservedBackgroundAdvisories, releasePreparedBackgroundAdvisories, findByBatchIdDetailed, findByBatchId, findOpenAsyncLaneBatches, isTerminalDelegationStatus, registerStaleSweepObserver, sweepStaleDelegations, readDelegationFallback, listDelegationFallbacks, scanDelegationFallbacksForRecovery, writeDelegationFallback, removeDelegationFallback, findDelegationForCompletion, promoteDelegationFallback, buildBackgroundCoderReservationId, scanBackgroundCoderReservationsForAdmission, reserveBackgroundCoderSlot, bindBackgroundCoderReservation, releaseBackgroundCoderReservation, maintainBackgroundDelegations };
|
|
6626
|
+
export { prReviewFindingRequiresCritic, PR_REVIEW_BASE_DIMENSION_IDS, PR_REVIEW_RISK_IMPACTS, PR_REVIEW_RISK_TAGS, prReviewLegacyTranscriptCompatibilityEnabled, PrReviewLaneResultEnvelopeSchema, decodePrReviewWorkflowBinding, prReviewLaneResultEnvelopeDigest, PrReviewResultReceiptSchema, PR_REVIEW_FINDINGS_MAX_BYTES, PR_REVIEW_HANDOFF_MAX_BYTES, PR_REVIEW_DISCARDED_EXAMPLE_ITEM_ID, PrReviewRunIdSchema, parsePrReviewRiskTagsField, PrReviewReviewerVerdictFieldsSchema, PrReviewCriticVerdictFieldsSchema, PR_REVIEW_VERDICT_ROW_DESCRIPTORS, parsePrReviewVerdictRow, generatePrReviewRunId, normalizeSessionID, prWorkflowSessionFileStem, WORKFLOW_GATE_DIR, workflowGateStateRelativePath, workflowGateStateLockRelativePath, workflowCheckoutMutationLockRelativePath, workflowGateStatePath, MAX_TRACKED_SESSIONS, CHECKOUT_MUTATION_ACTION_TIMEOUT_MS, isoNow, sameBigIntFileIdentity, normalizeComparableFsPath, rememberState, forgetTrackedPrWorkflowState, resetPrReviewPersistenceCaches, defaultPersistenceHooks, bindPrReviewPersistenceHooks, bindPrReviewStateCodec, ensurePrWorkflowSafeParentDirectory, writeAtomicJson, writePrWorkflowAtomicJson, readPrWorkflowGateStateFromDisk, readPrWorkflowGateStateFileFromDisk, readPrWorkflowGateStateCoordinationForRecovery, writeStateWhileLocked, appendObservabilityStateWhileLocked, deleteStateWhileLocked, terminalizePrWorkflowGateForSession, PrWorkflowCheckoutMutationTimeoutError, withPrWorkflowCheckoutMutationLock, withSessionStateMutation, deriveVerdictSettlementAdmission, deriveVerdictAttemptCounts, buildVerdictSettlementReceipt, persistVerdictSettlementReceipt, readVerdictSettlementReceipt, effectiveVerdictSettlementItems, verdictSettlementDegradationActive, MAX_RECOVERY_LEDGER_BYTES, readDelegationHealthArtifact, collectDelegationLedgerHealth, MAX_PR_REVIEW_COLLECTION_RECEIPT_ITEM_IDS, BACKGROUND_DELEGATIONS_FILE, BACKGROUND_DELEGATION_FALLBACK_DIR, BACKGROUND_CODER_RESERVATIONS_FILE, BACKGROUND_DELEGATIONS_CHECKPOINT_FILE, BACKGROUND_DELEGATIONS_MANIFEST_FILE, MAX_LIVE_BACKGROUND_FALLBACKS, MAX_LIVE_BACKGROUND_CODER_RESERVATIONS, MAX_BACKGROUND_OBSERVED_FILES, MAX_BACKGROUND_ADVISORY_CHARS, LEGACY_CODER_SETTLEMENT_PENDING_ADVISORY_MARKER, DELEGATION_COMPACTION_LOW_WATER_BYTES, DELEGATION_COMPACTION_HIGH_WATER_BYTES, MAX_CHECKPOINT_BYTES, MAX_CHECKPOINT_RECORDS, TOMBSTONE_MIN_AGE_MS, BACKGROUND_INGESTION_LEASE_MS, BACKGROUND_CODER_RESERVATION_LEASE_MS, BACKGROUND_CODER_RESERVATION_LEASE_MAX_MS, BACKGROUND_CODER_RESERVATION_LEASE_MIN_MS, DEFAULT_STALE_DELEGATION_TIMEOUT_MS, DEFAULT_SWEEPABLE_DELEGATION_STATUSES, _checkpointInternals, isUnsettledWorktreeOwner, compactBackgroundDelegations, DELEGATION_READ_RETRY_DELAY_MS, readDelegationsDetailed, readDelegations, scanDelegationsForRecovery, findByCorrelationIdDetailed, findByCorrelationId, recordPendingDelegationDetailed, recordPendingDelegation, buildPromptSnapshot, appendDelegationTransition, publishPrReviewResultReceipt, supersedeStaleLivenessVerdictLane, buildBackgroundCompletionEventId, buildTypedDelegationTerminal, recordedPrReviewResultReceipt, claimTerminalResult, claimCoderSettlement, normalizeBackgroundObservedFiles, updateCoderSettlement, registerLegacyCoderSettlementReconciler, _internals2 as _internals, markLegacyCoderSettlementTransferPending, clearLegacyCoderSettlementTransferPending, claimDelegationIngestion, recordDelegationIngestionResult, putPendingBackgroundAdvisory, replacePendingBackgroundAdvisory, preparePendingBackgroundAdvisories, acknowledgeObservedBackgroundAdvisories, releasePreparedBackgroundAdvisories, findByBatchIdDetailed, findByBatchId, findOpenAsyncLaneBatches, isTerminalDelegationStatus, registerStaleSweepObserver, sweepStaleDelegations, readDelegationFallback, listDelegationFallbacks, scanDelegationFallbacksForRecovery, writeDelegationFallback, removeDelegationFallback, findDelegationForCompletion, promoteDelegationFallback, buildBackgroundCoderReservationId, scanBackgroundCoderReservationsForAdmission, reserveBackgroundCoderSlot, bindBackgroundCoderReservation, releaseBackgroundCoderReservation, maintainBackgroundDelegations };
|