@astrosheep/keiyaku 2.9.11 → 2.9.13
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/README.md +79 -85
- package/build/.tsbuildinfo +1 -1
- package/build/agents/call-terms.js +1 -0
- package/build/agents/providers/claude-agent-sdk/adapter.js +17 -4
- package/build/cli/commands/akuma/akuma/handler.js +2 -1
- package/build/cli/commands/akuma/akuma/meta.js +4 -3
- package/build/cli/commands/akuma/catalog.js +2 -0
- package/build/cli/commands/akuma/list/handler.js +1 -1
- package/build/cli/commands/akuma/list/meta-list.js +12 -0
- package/build/cli/commands/akuma/list/meta-ls.js +2 -2
- package/build/cli/commands/akuma.js +5 -0
- package/build/cli/commands/contract/amend/meta.js +4 -3
- package/build/cli/commands/contract/arc/meta.js +2 -0
- package/build/cli/commands/contract/audit/handler.js +28 -2
- package/build/cli/commands/contract/audit/meta.js +4 -3
- package/build/cli/commands/contract/bind/meta.js +4 -4
- package/build/cli/commands/contract/petition/handler.js +2 -1
- package/build/cli/commands/contract/petition/meta.js +2 -2
- package/build/cli/commands/contract/renew/handler.js +44 -3
- package/build/cli/commands/contract/renew/meta.js +5 -4
- package/build/cli/commands/metadata.js +14 -10
- package/build/cli/commands/projection/call/handler.js +1 -1
- package/build/cli/commands/projection/call/meta.js +1 -1
- package/build/cli/commands/projection/catalog.js +2 -2
- package/build/cli/commands/projection/{revive → fork}/handler.js +10 -5
- package/build/cli/commands/projection/fork/meta.js +12 -0
- package/build/cli/commands/projection/history/handler.js +46 -2
- package/build/cli/commands/projection/history/meta.js +3 -3
- package/build/cli/commands/projection/kill/handler.js +4 -5
- package/build/cli/commands/projection/kill/meta.js +2 -2
- package/build/cli/commands/projection/status/handler.js +29 -13
- package/build/cli/commands/projection/status/meta.js +14 -3
- package/build/cli/commands/projection/tell/handler.js +8 -4
- package/build/cli/commands/projection/tell/meta.js +1 -1
- package/build/cli/commands/projection/wait/handler.js +45 -20
- package/build/cli/commands/projection/wait/meta.js +1 -1
- package/build/cli/commands/shared.js +2 -2
- package/build/cli/commands/system/catalog.js +2 -0
- package/build/cli/commands/task/add/handler.js +28 -12
- package/build/cli/commands/task/add/meta.js +18 -6
- package/build/cli/commands/task/catalog.js +4 -0
- package/build/cli/commands/task/compose/handler.js +18 -0
- package/build/cli/commands/task/compose/meta.js +10 -0
- package/build/cli/commands/task/doctor/handler.js +2 -3
- package/build/cli/commands/task/doctor/meta.js +2 -2
- package/build/cli/commands/task/done/meta.js +2 -2
- package/build/cli/commands/task/drop/handler.js +7 -2
- package/build/cli/commands/task/drop/meta.js +6 -3
- package/build/cli/commands/task/hold/meta.js +2 -2
- package/build/cli/commands/task/log/meta.js +2 -2
- package/build/cli/commands/task/ls/meta.js +2 -2
- package/build/cli/commands/task/note/handler.js +6 -0
- package/build/cli/commands/task/note/meta.js +11 -0
- package/build/cli/commands/task/resume/meta.js +2 -2
- package/build/cli/commands/task/shared.js +29 -15
- package/build/cli/commands/task/show/meta.js +2 -2
- package/build/cli/commands/task/start/meta.js +2 -2
- package/build/cli/commands/task/stop/meta.js +2 -2
- package/build/cli/commands/task/update/meta.js +3 -2
- package/build/cli/commands/verification/handler.js +43 -0
- package/build/cli/commands/verification/meta.js +13 -0
- package/build/cli/completion.js +16 -13
- package/build/cli/flags.js +48 -12
- package/build/cli/help.js +29 -30
- package/build/cli/index.js +14 -8
- package/build/cli/parse-flags.js +60 -21
- package/build/cli/parse-metadata.js +10 -4
- package/build/cli/parse-selectors.js +22 -4
- package/build/cli/parse.js +43 -30
- package/build/cli/projection-address.js +7 -7
- package/build/cli/render/arc.js +12 -3
- package/build/cli/render/audit.js +2 -2
- package/build/cli/render/call.js +4 -4
- package/build/cli/render/kanshi.js +122 -85
- package/build/cli/render/misc.js +2 -2
- package/build/cli/render/petition.js +16 -1
- package/build/cli/render/projection-history.js +17 -4
- package/build/cli/render/shared.js +5 -5
- package/build/cli/render/status-indicator.js +47 -0
- package/build/cli/render/status.js +106 -76
- package/build/cli/render/success-response.js +20 -5
- package/build/cli/render/task.js +33 -5
- package/build/cli/render/tell.js +10 -4
- package/build/cli/render/tool-command-normalization.js +137 -0
- package/build/cli/render/tool-presentation.js +2 -1
- package/build/cli/render/verification.js +23 -0
- package/build/cli/render/wait.js +101 -33
- package/build/cli/types.js +2 -1
- package/build/config/akuma-loader.js +21 -1
- package/build/config/settings/knobs.js +7 -1
- package/build/config/settings/schema.js +10 -5
- package/build/core/addressing.js +3 -3
- package/build/core/amend.js +102 -17
- package/build/core/arc.js +4 -4
- package/build/core/audit/candidate.js +10 -18
- package/build/core/audit/coordinates.js +12 -17
- package/build/core/audit/evidence.js +6 -4
- package/build/core/audit/facade.js +3 -3
- package/build/core/audit/report.js +1 -1
- package/build/core/bind-reconciliation.js +42 -23
- package/build/core/bind.js +182 -81
- package/build/core/call/call.js +6 -7
- package/build/core/call/context.js +46 -33
- package/build/core/call/execution.js +21 -6
- package/build/core/call/prompt.js +11 -42
- package/build/core/contract-carrier-runtime.js +209 -105
- package/build/core/contract-carrier.js +156 -73
- package/build/core/contract-view.js +43 -6
- package/build/core/contract.js +2 -1
- package/build/core/derived-replay.js +168 -35
- package/build/core/draft.js +60 -27
- package/build/core/forfeit.js +1 -1
- package/build/core/lifecycle-history.js +13 -0
- package/build/core/lifecycle-recovery.js +32 -27
- package/build/core/lifecycle-runner.js +14 -14
- package/build/core/log.js +1 -1
- package/build/core/outcome-base.js +2 -2
- package/build/core/projection/akuma-name.js +43 -0
- package/build/core/projection/generation/database.js +141 -79
- package/build/core/projection/generation/ledger.js +91 -0
- package/build/core/projection/generation/model.js +19 -14
- package/build/core/projection/generation/projection-generation-continuation.js +1 -1
- package/build/core/projection/generation/projection-generation-doorbell.js +88 -0
- package/build/core/projection/generation/projection-generation-execution.js +24 -0
- package/build/core/projection/generation/projection-generation-identity.js +4 -0
- package/build/core/projection/generation/projection-generation-launcher.js +72 -138
- package/build/core/projection/generation/projection-generation-process.js +16 -15
- package/build/core/projection/generation/projection-generation-runner.js +132 -76
- package/build/core/projection/generation/projection-generation-runtime.js +10 -61
- package/build/core/projection/generation/protocol.js +68 -0
- package/build/core/projection/generation/store.js +17 -7
- package/build/core/projection/generation/transitions.js +24 -14
- package/build/core/projection/index.js +5 -5
- package/build/core/projection/projection-core.js +6 -2
- package/build/core/projection/projection-execution-observer.js +1 -1
- package/build/core/projection/projection-history.js +8 -8
- package/build/core/projection/projection-kill.js +20 -10
- package/build/core/projection/projection-life-observer.js +8 -8
- package/build/core/projection/projection-mint.js +6 -8
- package/build/core/projection/projection-status-observation.js +77 -8
- package/build/core/projection/projection-status.js +151 -4
- package/build/core/projection/projection-wait.js +48 -29
- package/build/core/projection/projection-wake.js +28 -88
- package/build/core/projection/tell/launch-store.js +1 -1
- package/build/core/projection/tell/store.js +1 -1
- package/build/core/renew-build.js +204 -74
- package/build/core/renew-plan.js +34 -9
- package/build/core/renew-rewrite.js +56 -15
- package/build/core/renew.js +39 -35
- package/build/core/repository-ledger/accepted-fold-read.js +6 -3
- package/build/core/repository-ledger/claim-evidence.js +1 -1
- package/build/core/repository-ledger/codec.js +11 -5
- package/build/core/repository-ledger/current-state-store.js +464 -349
- package/build/core/repository-ledger/fold-repository.js +5 -6
- package/build/core/repository-ledger/identity.js +60 -0
- package/build/core/repository-ledger/inventory.js +3 -3
- package/build/core/repository-ledger/read-model.js +57 -9
- package/build/core/repository-ledger/write-transaction.js +24 -17
- package/build/core/run-control/connection-bound-close.js +41 -0
- package/build/core/run-control/detached-lease-bootstrap.js +208 -0
- package/build/core/run-control/detached-process.js +14 -0
- package/build/core/run-control/index.js +6 -0
- package/build/core/{process-group.js → run-control/process-tree.js} +1 -1
- package/build/core/run-control/runner-lease.js +172 -0
- package/build/core/run-control/sqlite-process-lifetime-lock.js +174 -0
- package/build/core/seal.js +44 -32
- package/build/core/settlement/claim-delivery.js +22 -9
- package/build/core/settlement/claim.js +8 -6
- package/build/core/settlement/petition-claim-gates.js +9 -8
- package/build/core/settlement/petition-head-guard.js +5 -6
- package/build/core/settlement/petition-preview.js +21 -5
- package/build/core/settlement/petition.js +51 -17
- package/build/core/settlement/settlement.js +20 -15
- package/build/core/settlement/verdict.js +2 -2
- package/build/core/settlement/verification.js +492 -364
- package/build/core/status/board.js +184 -75
- package/build/core/status/drift.js +8 -10
- package/build/core/task/board.js +1 -0
- package/build/core/task/commands.js +25 -0
- package/build/core/task/compose.js +328 -0
- package/build/core/task/document.js +102 -39
- package/build/core/task/index.js +4 -3
- package/build/core/task/settlement-git.js +70 -30
- package/build/core/task/settlement-policy.js +27 -14
- package/build/core/task/source-board.js +1 -0
- package/build/core/task/task-bind-preparation.js +59 -9
- package/build/core/task/task-contract.js +2 -2
- package/build/core/task/task-git-store.js +1 -0
- package/build/core/task/task-store-repository.js +2 -0
- package/build/core/task/task.js +2 -2
- package/build/core/transcripts.js +4 -4
- package/build/core/verification-declaration.js +95 -0
- package/build/core/worktree-bootstrap.js +1 -1
- package/build/core/worktree-path.js +7 -11
- package/build/flow-error.js +15 -2
- package/build/generated/version.js +2 -2
- package/build/git/branches.js +41 -70
- package/build/git/commits.js +92 -21
- package/build/git/core.js +9 -161
- package/build/git/diff/preview.js +4 -4
- package/build/git/diff/read.js +4 -4
- package/build/git/diff/structured.js +5 -5
- package/build/git/process.js +229 -0
- package/build/git/refs.js +30 -5
- package/build/git/staging.js +39 -115
- package/build/git/streaming-batch.js +9 -16
- package/build/git/worktree.js +104 -91
- package/build/index.js +7 -0
- package/package.json +2 -2
- package/skills/keiyaku-akuma/SKILL.md +27 -21
- package/skills/keiyaku-task/SKILL.md +61 -5
- package/skills/keiyaku-workflow/SKILL.md +81 -26
- package/build/cli/commands/projection/revive/meta.js +0 -12
- package/build/core/projection/projection-life-protocol.js +0 -115
- package/build/core/projection/projection-runner-lock.js +0 -382
- package/build/core/projection/tell/database.js +0 -127
- package/build/core/settlement/verification-coordination.js +0 -305
- package/build/core/settlement/verification-supervisor.js +0 -275
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { initializeSqliteProcessLifetimeLockDatabase, isSqliteProcessLifetimeLockContention, openSqliteProcessLifetimeLockTransaction, SqliteProcessLifetimeLockError, sqliteProcessLifetimeLockErrorDetail, } from "./sqlite-process-lifetime-lock.js";
|
|
2
|
+
export class RunnerLeaseUnavailableError extends Error {
|
|
3
|
+
code = "RUNNER_LEASE_UNAVAILABLE";
|
|
4
|
+
constructor(message, options) {
|
|
5
|
+
super(message, options);
|
|
6
|
+
this.name = "RunnerLeaseUnavailableError";
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
function errorDetail(error) {
|
|
10
|
+
return sqliteProcessLifetimeLockErrorDetail(error);
|
|
11
|
+
}
|
|
12
|
+
function isContention(error) {
|
|
13
|
+
return isSqliteProcessLifetimeLockContention(error)
|
|
14
|
+
|| (error instanceof SqliteProcessLifetimeLockError && error.reason === "busy");
|
|
15
|
+
}
|
|
16
|
+
function openGuard(lockPath, busyTimeoutMs) {
|
|
17
|
+
return openSqliteProcessLifetimeLockTransaction({ path: lockPath, mode: "deferred", busyTimeoutMs });
|
|
18
|
+
}
|
|
19
|
+
function openReservation(lockPath, busyTimeoutMs) {
|
|
20
|
+
return openSqliteProcessLifetimeLockTransaction({ path: lockPath, mode: "immediate", busyTimeoutMs });
|
|
21
|
+
}
|
|
22
|
+
function closeTransaction(transaction) {
|
|
23
|
+
try {
|
|
24
|
+
transaction.close();
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
return error;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function createHeldLock(lockPath, busyTimeoutMs, guard, initialReservation) {
|
|
31
|
+
let closed = false;
|
|
32
|
+
let reservation = initialReservation;
|
|
33
|
+
return {
|
|
34
|
+
releaseReservationForTransfer() {
|
|
35
|
+
if (closed || reservation === undefined)
|
|
36
|
+
return;
|
|
37
|
+
const released = reservation;
|
|
38
|
+
reservation = undefined;
|
|
39
|
+
const failure = closeTransaction(released);
|
|
40
|
+
if (failure !== undefined)
|
|
41
|
+
throw new RunnerLeaseUnavailableError(`runner lease reservation release failed: ${errorDetail(failure)}`, { cause: failure });
|
|
42
|
+
},
|
|
43
|
+
reclaimReservation() {
|
|
44
|
+
if (closed || reservation !== undefined)
|
|
45
|
+
return;
|
|
46
|
+
try {
|
|
47
|
+
reservation = openReservation(lockPath, busyTimeoutMs);
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
throw new RunnerLeaseUnavailableError(`runner lease reservation reclaim failed: ${errorDetail(error)}`, { cause: error });
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
close() {
|
|
54
|
+
if (closed)
|
|
55
|
+
return;
|
|
56
|
+
closed = true;
|
|
57
|
+
let failure;
|
|
58
|
+
if (reservation !== undefined)
|
|
59
|
+
failure = closeTransaction(reservation);
|
|
60
|
+
failure ??= closeTransaction(guard);
|
|
61
|
+
if (failure !== undefined)
|
|
62
|
+
throw new RunnerLeaseUnavailableError(`runner lease release failed: ${errorDetail(failure)}`, { cause: failure });
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function unavailable(operation, error) {
|
|
67
|
+
return new RunnerLeaseUnavailableError(`${operation}: ${errorDetail(error)}`, { cause: error });
|
|
68
|
+
}
|
|
69
|
+
export function initializeRunnerLeaseDatabase(leasePath) {
|
|
70
|
+
try {
|
|
71
|
+
return initializeSqliteProcessLifetimeLockDatabase(leasePath);
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
throw unavailable("runner lease database could not be initialized", error);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
export function acquireRunnerLease(lockPath, busyTimeoutMs = 0) {
|
|
78
|
+
let guard;
|
|
79
|
+
try {
|
|
80
|
+
guard = openGuard(lockPath, busyTimeoutMs);
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
throw unavailable("runner lease database could not be opened", error);
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
return createHeldLock(lockPath, busyTimeoutMs, guard, openReservation(lockPath, busyTimeoutMs));
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
closeTransaction(guard);
|
|
90
|
+
if (isContention(error))
|
|
91
|
+
throw new RunnerLeaseUnavailableError("runner lease is already held", { cause: error });
|
|
92
|
+
throw unavailable("runner lease acquisition failed", error);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
export function acquireRunnerLeaseTransferTarget(lockPath, busyTimeoutMs = 0) {
|
|
96
|
+
let guard;
|
|
97
|
+
try {
|
|
98
|
+
guard = openGuard(lockPath, busyTimeoutMs);
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
throw unavailable("runner lease transfer acquisition failed", error);
|
|
102
|
+
}
|
|
103
|
+
let closed = false;
|
|
104
|
+
return {
|
|
105
|
+
promote() {
|
|
106
|
+
if (closed)
|
|
107
|
+
throw new RunnerLeaseUnavailableError("runner lease transfer target is closed");
|
|
108
|
+
try {
|
|
109
|
+
const reservation = openReservation(lockPath, busyTimeoutMs);
|
|
110
|
+
closed = true;
|
|
111
|
+
return createHeldLock(lockPath, busyTimeoutMs, guard, reservation);
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
throw unavailable("runner lease transfer promotion failed", error);
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
close() {
|
|
118
|
+
if (closed)
|
|
119
|
+
return;
|
|
120
|
+
closed = true;
|
|
121
|
+
const failure = closeTransaction(guard);
|
|
122
|
+
if (failure !== undefined)
|
|
123
|
+
throw unavailable("runner lease transfer release failed", failure);
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
export function acquireRunnerLostProof(lockPath, busyTimeoutMs = 0) {
|
|
128
|
+
let transaction;
|
|
129
|
+
try {
|
|
130
|
+
transaction = openSqliteProcessLifetimeLockTransaction({ path: lockPath, mode: "exclusive", busyTimeoutMs });
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
if (isContention(error))
|
|
134
|
+
throw new RunnerLeaseUnavailableError("runner lease is already held", { cause: error });
|
|
135
|
+
throw unavailable("runner lost proof acquisition failed", error);
|
|
136
|
+
}
|
|
137
|
+
let closed = false;
|
|
138
|
+
return {
|
|
139
|
+
close() {
|
|
140
|
+
if (closed)
|
|
141
|
+
return;
|
|
142
|
+
closed = true;
|
|
143
|
+
const failure = closeTransaction(transaction);
|
|
144
|
+
if (failure !== undefined)
|
|
145
|
+
throw unavailable("runner lost proof release failed", failure);
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
/** Positive death probe without creating a missing lock database. */
|
|
150
|
+
export function observeRunnerLease(lockPath) {
|
|
151
|
+
let probe;
|
|
152
|
+
try {
|
|
153
|
+
probe = openSqliteProcessLifetimeLockTransaction({ path: lockPath, mode: "exclusive", busyTimeoutMs: 0 });
|
|
154
|
+
return { state: "released" };
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
if (isContention(error))
|
|
158
|
+
return { state: "held" };
|
|
159
|
+
const detail = errorDetail(error);
|
|
160
|
+
return {
|
|
161
|
+
state: "unknown",
|
|
162
|
+
reason: detail.includes("rollback-journal") ? "unsupported-journal-mode" : "probe-failed",
|
|
163
|
+
detail,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
finally {
|
|
167
|
+
try {
|
|
168
|
+
probe?.close();
|
|
169
|
+
}
|
|
170
|
+
catch { /* no probe cleanup result is death evidence */ }
|
|
171
|
+
}
|
|
172
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import { DatabaseSync } from "node:sqlite";
|
|
3
|
+
const SQLITE_BUSY = 5;
|
|
4
|
+
export class SqliteProcessLifetimeLockError extends Error {
|
|
5
|
+
reason;
|
|
6
|
+
code = "SQLITE_PROCESS_LIFETIME_LOCK_UNAVAILABLE";
|
|
7
|
+
constructor(message, reason, options) {
|
|
8
|
+
super(message, options);
|
|
9
|
+
this.reason = reason;
|
|
10
|
+
this.name = "SqliteProcessLifetimeLockError";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export function sqliteProcessLifetimeLockErrorDetail(error) {
|
|
14
|
+
return error instanceof Error ? error.message : String(error);
|
|
15
|
+
}
|
|
16
|
+
export function isSqliteProcessLifetimeLockContention(error) {
|
|
17
|
+
const code = typeof error === "object" && error !== null && "code" in error
|
|
18
|
+
? String(error.code)
|
|
19
|
+
: "";
|
|
20
|
+
const errcode = typeof error === "object" && error !== null && "errcode" in error
|
|
21
|
+
? error.errcode
|
|
22
|
+
: undefined;
|
|
23
|
+
const message = sqliteProcessLifetimeLockErrorDetail(error).toLowerCase();
|
|
24
|
+
return errcode === SQLITE_BUSY || code === "ERR_SQLITE_BUSY" || code === "ERR_SQLITE_LOCKED"
|
|
25
|
+
|| message.includes("database is locked") || message.includes("database is busy");
|
|
26
|
+
}
|
|
27
|
+
function assertBusyTimeout(busyTimeoutMs) {
|
|
28
|
+
if (!Number.isSafeInteger(busyTimeoutMs) || busyTimeoutMs < 0) {
|
|
29
|
+
throw new SqliteProcessLifetimeLockError("SQLite process-lifetime lock busy timeout must be a non-negative safe integer", "invalid");
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function journalMode(database) {
|
|
33
|
+
const row = database.prepare("PRAGMA journal_mode").get();
|
|
34
|
+
return typeof row?.journal_mode === "string" ? row.journal_mode.toLowerCase() : undefined;
|
|
35
|
+
}
|
|
36
|
+
function assertSchema(database) {
|
|
37
|
+
database.prepare("SELECT singleton FROM lock_anchor LIMIT 1").get();
|
|
38
|
+
}
|
|
39
|
+
function closeTransaction(database) {
|
|
40
|
+
let failure;
|
|
41
|
+
try {
|
|
42
|
+
database.exec("ROLLBACK");
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
failure = error;
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
database.close();
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
failure ??= error;
|
|
52
|
+
}
|
|
53
|
+
return failure;
|
|
54
|
+
}
|
|
55
|
+
function throwLockError(prefix, error) {
|
|
56
|
+
if (error instanceof SqliteProcessLifetimeLockError)
|
|
57
|
+
throw error;
|
|
58
|
+
if (isSqliteProcessLifetimeLockContention(error)) {
|
|
59
|
+
throw new SqliteProcessLifetimeLockError(`${prefix} is already held`, "busy", { cause: error });
|
|
60
|
+
}
|
|
61
|
+
throw new SqliteProcessLifetimeLockError(`${prefix} failed: ${sqliteProcessLifetimeLockErrorDetail(error)}`, "open-failed", { cause: error });
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Creates only a rollback-journal schema anchor. The empty database persists no
|
|
65
|
+
* ownership facts; an open transaction is the complete physical ownership signal.
|
|
66
|
+
*/
|
|
67
|
+
export function initializeSqliteProcessLifetimeLockDatabase(lockPath) {
|
|
68
|
+
let database;
|
|
69
|
+
try {
|
|
70
|
+
database = new DatabaseSync(lockPath);
|
|
71
|
+
database.exec("PRAGMA busy_timeout=0");
|
|
72
|
+
database.exec("PRAGMA journal_mode=DELETE");
|
|
73
|
+
database.exec("CREATE TABLE IF NOT EXISTS lock_anchor (singleton INTEGER PRIMARY KEY CHECK (singleton = 1))");
|
|
74
|
+
assertSchema(database);
|
|
75
|
+
if (journalMode(database) !== "delete") {
|
|
76
|
+
throw new SqliteProcessLifetimeLockError("SQLite process-lifetime lock database does not use rollback-journal mode", "invalid");
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
throwLockError("SQLite process-lifetime lock database initialization", error);
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
try {
|
|
84
|
+
database?.close();
|
|
85
|
+
}
|
|
86
|
+
catch { /* the causal initialization error is retained */ }
|
|
87
|
+
}
|
|
88
|
+
return lockPath;
|
|
89
|
+
}
|
|
90
|
+
export function openSqliteProcessLifetimeLockTransaction(input) {
|
|
91
|
+
const busyTimeoutMs = input.busyTimeoutMs ?? 0;
|
|
92
|
+
assertBusyTimeout(busyTimeoutMs);
|
|
93
|
+
if (!input.create) {
|
|
94
|
+
try {
|
|
95
|
+
if (!fs.lstatSync(input.path).isFile()) {
|
|
96
|
+
throw new SqliteProcessLifetimeLockError("SQLite process-lifetime lock database is not a regular file", "invalid");
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
if (error instanceof SqliteProcessLifetimeLockError)
|
|
101
|
+
throw error;
|
|
102
|
+
throw new SqliteProcessLifetimeLockError(`SQLite process-lifetime lock database is not initialized: ${sqliteProcessLifetimeLockErrorDetail(error)}`, "open-failed", { cause: error });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
let database;
|
|
106
|
+
try {
|
|
107
|
+
database = new DatabaseSync(input.path);
|
|
108
|
+
database.exec(`PRAGMA busy_timeout=${busyTimeoutMs}`);
|
|
109
|
+
if (input.create) {
|
|
110
|
+
database.exec("PRAGMA journal_mode=DELETE");
|
|
111
|
+
database.exec("CREATE TABLE IF NOT EXISTS lock_anchor (singleton INTEGER PRIMARY KEY CHECK (singleton = 1))");
|
|
112
|
+
}
|
|
113
|
+
database.exec(`BEGIN ${input.mode.toUpperCase()}`);
|
|
114
|
+
// A DEFERRED transaction becomes a process-lifetime guard only after this read
|
|
115
|
+
// acquires its SHARED lock. Keep this inside every transaction mode.
|
|
116
|
+
assertSchema(database);
|
|
117
|
+
if (journalMode(database) !== "delete") {
|
|
118
|
+
throw new SqliteProcessLifetimeLockError("SQLite process-lifetime lock database does not use rollback-journal mode", "invalid");
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
if (database !== undefined)
|
|
123
|
+
closeTransaction(database);
|
|
124
|
+
throwLockError("SQLite process-lifetime lock acquisition", error);
|
|
125
|
+
}
|
|
126
|
+
let closed = false;
|
|
127
|
+
return {
|
|
128
|
+
path: input.path,
|
|
129
|
+
database,
|
|
130
|
+
close() {
|
|
131
|
+
if (closed)
|
|
132
|
+
return;
|
|
133
|
+
closed = true;
|
|
134
|
+
const failure = closeTransaction(database);
|
|
135
|
+
if (failure !== undefined) {
|
|
136
|
+
throw new SqliteProcessLifetimeLockError(`SQLite process-lifetime lock release failed: ${sqliteProcessLifetimeLockErrorDetail(failure)}`, "release-failed", { cause: failure });
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function waitForRetry(milliseconds, signal) {
|
|
142
|
+
return new Promise((resolve, reject) => {
|
|
143
|
+
const abort = () => {
|
|
144
|
+
clearTimeout(timer);
|
|
145
|
+
reject(signal?.reason ?? new DOMException("operation aborted", "AbortError"));
|
|
146
|
+
};
|
|
147
|
+
const timer = setTimeout(() => {
|
|
148
|
+
signal?.removeEventListener("abort", abort);
|
|
149
|
+
resolve();
|
|
150
|
+
}, milliseconds);
|
|
151
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
/** Waits for the sole BEGIN IMMEDIATE owner. Cancellation is the only time bound. */
|
|
155
|
+
export async function acquireSqliteProcessLifetimeLock(input) {
|
|
156
|
+
const retryMs = input.retryMs ?? 25;
|
|
157
|
+
assertBusyTimeout(retryMs);
|
|
158
|
+
for (;;) {
|
|
159
|
+
input.signal?.throwIfAborted();
|
|
160
|
+
try {
|
|
161
|
+
return openSqliteProcessLifetimeLockTransaction({
|
|
162
|
+
path: input.path,
|
|
163
|
+
mode: "immediate",
|
|
164
|
+
busyTimeoutMs: 0,
|
|
165
|
+
create: true,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
if (!(error instanceof SqliteProcessLifetimeLockError) || error.reason !== "busy")
|
|
170
|
+
throw error;
|
|
171
|
+
await waitForRetry(retryMs, input.signal);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
package/build/core/seal.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { FlowError } from "../flow-error.js";
|
|
2
|
+
import { requireCommitCoordinate } from "../git/commits.js";
|
|
2
3
|
import { createGit, parseRevListOutput, wrapGitError } from "../git/core.js";
|
|
3
4
|
import { effectiveScope } from "./lifecycle-history.js";
|
|
4
5
|
import { scopePathEvidence, } from "./scope.js";
|
|
@@ -42,13 +43,7 @@ export async function receiptsAfterFence(cwd, entries, head = "HEAD") {
|
|
|
42
43
|
return revList(cwd, getFence(entries), await resolveCommit(cwd, head));
|
|
43
44
|
}
|
|
44
45
|
async function resolveCommit(cwd, revision) {
|
|
45
|
-
|
|
46
|
-
try {
|
|
47
|
-
return (await git.raw(["rev-parse", "--verify", `${revision}^{commit}`])).trim();
|
|
48
|
-
}
|
|
49
|
-
catch (error) {
|
|
50
|
-
throw wrapGitError(`rev-parse ${revision}`, error, cwd);
|
|
51
|
-
}
|
|
46
|
+
return (await requireCommitCoordinate(cwd, revision)).commit;
|
|
52
47
|
}
|
|
53
48
|
async function revList(cwd, from, to) {
|
|
54
49
|
const git = createGit(cwd);
|
|
@@ -120,15 +115,18 @@ async function auditScope(cwd, entries, paths) {
|
|
|
120
115
|
...await scopeViolations(cwd, paths, scope),
|
|
121
116
|
...taskNamespaceViolations(paths, findBind(entries).data.scope),
|
|
122
117
|
].filter((row, index, all) => all.findIndex((candidate) => candidate.path === row.path) === index);
|
|
123
|
-
if (scopeViolationEvidence.length > 0) {
|
|
124
|
-
throw new ScopeAuditError(findBind(entries).contract, scope, scopeViolationEvidence);
|
|
125
|
-
}
|
|
126
118
|
const violations = scopeViolationEvidence.map((violation) => violation.path);
|
|
127
119
|
return {
|
|
128
120
|
scopeAudit: { violations },
|
|
129
121
|
scopeViolationEvidence,
|
|
130
122
|
};
|
|
131
123
|
}
|
|
124
|
+
function admitScope(contract, scope, capture, policy) {
|
|
125
|
+
if (policy === "refuse" && capture.scopeViolationEvidence.length > 0) {
|
|
126
|
+
throw new ScopeAuditError(contract, scope, capture.scopeViolationEvidence);
|
|
127
|
+
}
|
|
128
|
+
return capture;
|
|
129
|
+
}
|
|
132
130
|
export class ScopeAuditError extends FlowError {
|
|
133
131
|
scopeViolationEvidence;
|
|
134
132
|
constructor(_contractId, scope, evidence) {
|
|
@@ -155,7 +153,7 @@ function currentFoldedSealArc(model) {
|
|
|
155
153
|
return model.seal === null ? model.intent.arc : null;
|
|
156
154
|
}
|
|
157
155
|
/** Current-read admission capture: scope and fence come only from the folded model. */
|
|
158
|
-
export async function captureFoldedSeal(cwd, model, head = "HEAD", scopeAppend = []) {
|
|
156
|
+
export async function captureFoldedSeal(cwd, model, head = "HEAD", scopeAppend = [], scopePolicy) {
|
|
159
157
|
const arc = currentFoldedSealArc(model);
|
|
160
158
|
if (arc === null)
|
|
161
159
|
return null;
|
|
@@ -168,14 +166,18 @@ export async function captureFoldedSeal(cwd, model, head = "HEAD", scopeAppend =
|
|
|
168
166
|
]);
|
|
169
167
|
const effectiveScope = [...model.effectiveScope, ...scopeAppend];
|
|
170
168
|
const bind = model.history.find((row) => row.entry.kind === "bind")?.entry;
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
if (scopeViolationEvidence.length > 0) {
|
|
176
|
-
throw new ScopeAuditError(model.contract, effectiveScope, scopeViolationEvidence);
|
|
169
|
+
const ordinaryViolations = await scopeViolations(cwd, paths, effectiveScope);
|
|
170
|
+
const namespaceViolations = taskNamespaceViolations(paths, bind?.kind === "bind" ? bind.data.scope : []);
|
|
171
|
+
if (namespaceViolations.length > 0) {
|
|
172
|
+
throw new ScopeAuditError(model.contract, effectiveScope, namespaceViolations);
|
|
177
173
|
}
|
|
178
|
-
|
|
174
|
+
const scopeViolationEvidence = ordinaryViolations
|
|
175
|
+
.filter((row, index, all) => all.findIndex((candidate) => candidate.path === row.path) === index);
|
|
176
|
+
const scopeAudit = admitScope(model.contract, effectiveScope, {
|
|
177
|
+
scopeAudit: { violations: scopeViolationEvidence.map((violation) => violation.path) },
|
|
178
|
+
scopeViolationEvidence,
|
|
179
|
+
}, scopePolicy);
|
|
180
|
+
return { arc, oldFence, newFence, receipts, diffstat: stat, ...scopeAudit };
|
|
179
181
|
}
|
|
180
182
|
/** Current seal intent: open explicit arc, else derived a0 while no explicit arc has ever existed. */
|
|
181
183
|
export function currentSealArc(entries) {
|
|
@@ -219,7 +221,14 @@ export async function previewSeal(cwd, entries, head = "HEAD") {
|
|
|
219
221
|
};
|
|
220
222
|
}
|
|
221
223
|
export async function captureSeal(cwd, entries, head = "HEAD") {
|
|
222
|
-
|
|
224
|
+
const preview = await previewSeal(cwd, entries, head);
|
|
225
|
+
if (!preview.capture)
|
|
226
|
+
return null;
|
|
227
|
+
admitScope("direct seal", effectiveScope(entries), {
|
|
228
|
+
scopeAudit: preview.capture.scopeAudit,
|
|
229
|
+
scopeViolationEvidence: preview.capture.scopeViolationEvidence,
|
|
230
|
+
}, "refuse");
|
|
231
|
+
return preview.capture;
|
|
223
232
|
}
|
|
224
233
|
/** Folded renew capture: preserve receipt hashes while auditing rebased delivery content. */
|
|
225
234
|
export async function captureFoldedRenewSeal(input) {
|
|
@@ -239,21 +248,24 @@ export async function captureFoldedRenewSeal(input) {
|
|
|
239
248
|
]);
|
|
240
249
|
const scope = [...input.effectiveScope, ...input.pendingScopeOverlay];
|
|
241
250
|
const bindScope = await acceptedBindScope(input.cwd, input.contract);
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
if (scopeViolationEvidence.length > 0) {
|
|
247
|
-
throw new ScopeAuditError(input.contract, scope, scopeViolationEvidence);
|
|
251
|
+
const ordinaryViolations = await scopeViolations(input.cwd, paths, scope);
|
|
252
|
+
const namespaceViolations = taskNamespaceViolations(paths, bindScope);
|
|
253
|
+
if (namespaceViolations.length > 0) {
|
|
254
|
+
throw new ScopeAuditError(input.contract, scope, namespaceViolations);
|
|
248
255
|
}
|
|
256
|
+
const scopeViolationEvidence = ordinaryViolations
|
|
257
|
+
.filter((row, index, all) => all.findIndex((candidate) => candidate.path === row.path) === index);
|
|
258
|
+
const scopeAudit = admitScope(input.contract, scope, {
|
|
259
|
+
scopeAudit: { violations: scopeViolationEvidence.map((violation) => violation.path) },
|
|
260
|
+
scopeViolationEvidence,
|
|
261
|
+
}, input.scopePolicy);
|
|
249
262
|
return {
|
|
250
263
|
arc,
|
|
251
264
|
oldFence,
|
|
252
265
|
newFence: receiptHead,
|
|
253
266
|
receipts,
|
|
254
267
|
diffstat: stat,
|
|
255
|
-
scopeAudit
|
|
256
|
-
scopeViolationEvidence,
|
|
268
|
+
...scopeAudit,
|
|
257
269
|
};
|
|
258
270
|
}
|
|
259
271
|
export async function sealCurrentArc(input) {
|
|
@@ -301,7 +313,7 @@ export async function sealCurrentArc(input) {
|
|
|
301
313
|
prepare: async (admission) => {
|
|
302
314
|
if (admission.business.existing)
|
|
303
315
|
return { capture: admission.business.existing.capture, existing: { transactionObjectId: admission.business.existing.transactionObjectId } };
|
|
304
|
-
return { capture: input.capture ?? await captureFoldedSeal(admission.business.targetRoot, admission.business.lifecycle, "HEAD") };
|
|
316
|
+
return { capture: input.capture ?? await captureFoldedSeal(admission.business.targetRoot, admission.business.lifecycle, "HEAD", [], "refuse") };
|
|
305
317
|
},
|
|
306
318
|
revalidate: ({ session, prepared }) => {
|
|
307
319
|
assertOperable(input.contractId, session.lifecycle);
|
|
@@ -319,7 +331,7 @@ export async function sealCurrentArc(input) {
|
|
|
319
331
|
effect: async ({ context, prepared }) => {
|
|
320
332
|
const capture = prepared.capture;
|
|
321
333
|
if (!capture)
|
|
322
|
-
return { kind: "refused-before-effects", authoritativeHead: context.session.
|
|
334
|
+
return { kind: "refused-before-effects", authoritativeHead: context.session.pins.repositoryLedgerHead, refusal: { code: "NO_OPEN_ARC", message: "no current arc requires sealing" } };
|
|
323
335
|
const at = new Date(input.nowMs).toISOString();
|
|
324
336
|
{
|
|
325
337
|
const { scopeViolationEvidence: _scopeViolationEvidence, ...durableCapture } = capture;
|
|
@@ -333,11 +345,11 @@ export async function sealCurrentArc(input) {
|
|
|
333
345
|
};
|
|
334
346
|
const result = prepared.existing
|
|
335
347
|
? await context.replayCommitted({ transactionObjectId: prepared.existing.transactionObjectId })
|
|
336
|
-
: await context.publishAuthoritative({ verb: "seal", admittedLedgerHead: context.session.
|
|
348
|
+
: await context.publishAuthoritative({ verb: "seal", admittedLedgerHead: context.session.pins.repositoryLedgerHead, acceptedLifecycleTail: context.session.lifecycle.contractHead, entries: [entry] });
|
|
337
349
|
if (result.status === "indeterminate")
|
|
338
|
-
return { kind: "precommit-effects-only", authoritativeHead: result.currentHead ?? context.session.
|
|
350
|
+
return { kind: "precommit-effects-only", authoritativeHead: result.currentHead ?? context.session.pins.repositoryLedgerHead };
|
|
339
351
|
if (result.status !== "committed")
|
|
340
|
-
return { kind: "refused-before-effects", authoritativeHead: result.currentHead ?? context.session.
|
|
352
|
+
return { kind: "refused-before-effects", authoritativeHead: result.currentHead ?? context.session.pins.repositoryLedgerHead, refusal: { code: "LEDGER_CAS_CONFLICT", message: `contract ${input.contractId} changed while sealing` } };
|
|
341
353
|
return { kind: "committed", commit: result, entryKind: "seal", value: {
|
|
342
354
|
...capture,
|
|
343
355
|
sealedBy: input.sealedBy,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { FlowError } from "../../flow-error.js";
|
|
2
|
-
import { createGit,
|
|
3
|
-
import { shortCommitHash } from "../../git/commits.js";
|
|
2
|
+
import { createGit, runGitProcess } from "../../git/core.js";
|
|
3
|
+
import { readCommitCoordinate, shortCommitHash } from "../../git/commits.js";
|
|
4
4
|
import { assertHonestClaimEvidence, buildHonestClaimMerge, } from "../repository-ledger/claim-evidence.js";
|
|
5
5
|
async function successfulGit(cwd, args, input) {
|
|
6
6
|
const result = await runGitProcess(cwd, args, input === undefined ? undefined : { input });
|
|
@@ -22,16 +22,29 @@ export async function buildClaimLanding(input) {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
export async function resolveCommit(cwd, rev) {
|
|
25
|
-
|
|
25
|
+
const read = await readCommitCoordinate(cwd, rev);
|
|
26
|
+
if (read.status === "ok")
|
|
27
|
+
return read.coordinate.commit;
|
|
28
|
+
if (read.status === "failed") {
|
|
29
|
+
const detail = read.result.stderr.trim() || read.error.message || `exit ${String(read.result.status)}`;
|
|
30
|
+
throw new FlowError("INTERNAL_STATE", `git rev-parse ${rev} failed: ${detail}`, { cause: read.error });
|
|
31
|
+
}
|
|
32
|
+
if (read.status === "malformed") {
|
|
33
|
+
throw new FlowError("INTERNAL_STATE", `git rev-parse ${rev} failed: ${read.detail}`);
|
|
34
|
+
}
|
|
35
|
+
throw new FlowError("INTERNAL_STATE", `git rev-parse ${rev} failed: revision does not resolve to a commit`);
|
|
26
36
|
}
|
|
27
37
|
export async function resolveCommitOrNull(cwd, rev) {
|
|
28
|
-
const
|
|
29
|
-
if (
|
|
30
|
-
return
|
|
31
|
-
if (
|
|
38
|
+
const read = await readCommitCoordinate(cwd, rev);
|
|
39
|
+
if (read.status === "ok")
|
|
40
|
+
return read.coordinate.commit;
|
|
41
|
+
if (read.status === "missing" || read.status === "non-commit")
|
|
32
42
|
return null;
|
|
33
|
-
|
|
34
|
-
|
|
43
|
+
if (read.status === "malformed") {
|
|
44
|
+
throw new FlowError("INTERNAL_STATE", `git rev-parse ${rev} failed: ${read.detail}`);
|
|
45
|
+
}
|
|
46
|
+
const detail = read.result.stderr.trim() || read.error.message || `exit ${String(read.result.status)}`;
|
|
47
|
+
throw new FlowError("INTERNAL_STATE", `git rev-parse ${rev} failed: ${detail}`, { cause: read.error });
|
|
35
48
|
}
|
|
36
49
|
/** Prove parent order and tree identity independently of mutable refs. */
|
|
37
50
|
export async function proveClaimLanding(cwd, landing) {
|
|
@@ -179,7 +179,7 @@ export async function claimContract(input) {
|
|
|
179
179
|
: await context.publishAuthoritative({
|
|
180
180
|
verb: "claim",
|
|
181
181
|
admittedLedgerHead: admission.ledgerHead,
|
|
182
|
-
acceptedLifecycleTail: context.session.lifecycle.
|
|
182
|
+
acceptedLifecycleTail: context.session.lifecycle.contractHead,
|
|
183
183
|
entries: [{
|
|
184
184
|
v: 2,
|
|
185
185
|
kind: "claim",
|
|
@@ -221,11 +221,13 @@ export async function claimContract(input) {
|
|
|
221
221
|
},
|
|
222
222
|
});
|
|
223
223
|
await context.session.completeReplay("retire");
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
224
|
+
const warnings = ["task-board-projection", "target-worktree-projection"].flatMap((effect) => {
|
|
225
|
+
const row = context.journal.all().find((candidate) => candidate.effect === effect && candidate.status === "succeeded");
|
|
226
|
+
const facts = row?.status === "succeeded" ? row.receipt?.facts : undefined;
|
|
227
|
+
return isJsonObject(facts) && Array.isArray(facts.warnings)
|
|
228
|
+
? facts.warnings.filter((warning) => typeof warning === "string")
|
|
229
|
+
: [];
|
|
230
|
+
});
|
|
229
231
|
const cleanupRow = context.journal.all().find((row) => row.effect === "worktree-projection" && row.status === "succeeded");
|
|
230
232
|
const cleanupFacts = cleanupRow?.status === "succeeded" ? cleanupRow.receipt?.facts : undefined;
|
|
231
233
|
const deliveryRefRow = context.journal.all().find((row) => row.effect === "delivery-ref-projection" && row.status === "succeeded");
|
|
@@ -8,7 +8,7 @@ import { getDiffStats } from "../../git/diff/preview.js";
|
|
|
8
8
|
import { readDiff } from "../../git/diff/read.js";
|
|
9
9
|
import { getCurrentBranch } from "../../git/branches.js";
|
|
10
10
|
import { getLatestCommitHash } from "../../git/staging.js";
|
|
11
|
-
import {
|
|
11
|
+
import { requireCommitCoordinate } from "../../git/commits.js";
|
|
12
12
|
import { DIFF_PREVIEW_TEXT_MAX_CHARS } from "../../keiyaku.js";
|
|
13
13
|
import { FlowError } from "../../flow-error.js";
|
|
14
14
|
import { persistResponseHistory } from "../transcripts.js";
|
|
@@ -100,13 +100,14 @@ export function createOathGate() {
|
|
|
100
100
|
export function createCommandVerificationGate() {
|
|
101
101
|
return {
|
|
102
102
|
name: "command",
|
|
103
|
-
async evaluate({ cwd,
|
|
104
|
-
const repositoryRoot = coordinationRoot ?? cwd;
|
|
103
|
+
async evaluate({ cwd, repositoryRoot, verificationPlan, verificationCandidate, input }) {
|
|
105
104
|
for (const verification of verificationPlan) {
|
|
106
105
|
logInfo(`[CLAIM] Running ${verification.source}: ${verification.label}`, { cwd, section: "script", progressOnly: true });
|
|
107
106
|
}
|
|
108
|
-
const
|
|
109
|
-
|
|
107
|
+
const invocation = await runVerificationPlan({ cwd, candidate: verificationCandidate, plan: verificationPlan, signal: input.signal, root: repositoryRoot ?? cwd });
|
|
108
|
+
// Petition owns the run: abort cancels and awaitTerminal observes cleanup.
|
|
109
|
+
const evidence = await invocation.awaitTerminal();
|
|
110
|
+
const runnerFailure = evidence.results.find((result) => result.execution.status === "runner_failed");
|
|
110
111
|
if (runnerFailure?.execution.status === "runner_failed") {
|
|
111
112
|
return {
|
|
112
113
|
pass: false,
|
|
@@ -120,7 +121,7 @@ export function createCommandVerificationGate() {
|
|
|
120
121
|
},
|
|
121
122
|
};
|
|
122
123
|
}
|
|
123
|
-
const commandFailure = evidence.find((result) => result.execution.status === "executed" && result.execution.code !== 0);
|
|
124
|
+
const commandFailure = evidence.results.find((result) => result.execution.status === "executed" && result.execution.code !== 0);
|
|
124
125
|
if (commandFailure?.execution.status === "executed") {
|
|
125
126
|
return {
|
|
126
127
|
pass: false,
|
|
@@ -134,7 +135,7 @@ export function createCommandVerificationGate() {
|
|
|
134
135
|
},
|
|
135
136
|
};
|
|
136
137
|
}
|
|
137
|
-
return { pass: true, verification: [...evidence] };
|
|
138
|
+
return { pass: true, verification: [...evidence.results] };
|
|
138
139
|
},
|
|
139
140
|
};
|
|
140
141
|
}
|
|
@@ -193,7 +194,7 @@ export function createAgentReviewGate() {
|
|
|
193
194
|
authority: { kind: "repository" },
|
|
194
195
|
binding: { kind: "commission", commissionId: contractId },
|
|
195
196
|
});
|
|
196
|
-
const reviewHeadBeforeWrite = (await
|
|
197
|
+
const reviewHeadBeforeWrite = (await requireCommitCoordinate(cwd, "HEAD")).commit;
|
|
197
198
|
const raw = subagentResult.finalMessage.trim();
|
|
198
199
|
const passed = raw.startsWith(REVIEW_PASS_TOKEN);
|
|
199
200
|
const observations = passed ? raw.slice(REVIEW_PASS_TOKEN.length).trim() || undefined : undefined;
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { FlowError } from "../../flow-error.js";
|
|
2
|
-
import {
|
|
2
|
+
import { requireCommitCoordinate } from "../../git/commits.js";
|
|
3
3
|
const CONCURRENT_MODIFICATION_CODE = "CONCURRENT_MODIFICATION";
|
|
4
4
|
export async function assertHeadUnchanged(cwd, expectedHead, operation) {
|
|
5
|
-
const git = createGit(cwd);
|
|
6
5
|
const [expectedCommit, currentHead] = await Promise.all([
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
requireCommitCoordinate(cwd, expectedHead),
|
|
7
|
+
requireCommitCoordinate(cwd, "HEAD"),
|
|
9
8
|
]);
|
|
10
|
-
if (currentHead.
|
|
9
|
+
if (currentHead.commit === expectedCommit.commit)
|
|
11
10
|
return;
|
|
12
|
-
throw new FlowError(CONCURRENT_MODIFICATION_CODE, `concurrent ${operation} modification detected: repository HEAD changed from ${expectedHead} to ${currentHead.
|
|
11
|
+
throw new FlowError(CONCURRENT_MODIFICATION_CODE, `concurrent ${operation} modification detected: repository HEAD changed from ${expectedHead} to ${currentHead.commit} while the ${operation} was running`);
|
|
13
12
|
}
|