@astrosheep/keiyaku 2.9.9 → 2.9.10
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 +50 -182
- package/build/.tsbuildinfo +1 -1
- package/build/agents/harness/control-types.js +18 -1
- package/build/agents/harness/index.js +2 -0
- package/build/agents/harness/outcome-error.js +81 -0
- package/build/agents/harness/projection.js +2 -1
- package/build/agents/harness/router.js +5 -1
- package/build/agents/providers/claude-agent-sdk/adapter.js +18 -4
- package/build/agents/providers/claude-agent-sdk/registration.js +11 -1
- package/build/agents/providers/claude-agent-sdk/session.js +19 -5
- package/build/agents/providers/codex-app-server/adapter.js +11 -6
- package/build/agents/providers/codex-app-server/events.js +67 -6
- package/build/agents/providers/codex-app-server/registration.js +11 -1
- package/build/agents/providers/opencode-sdk/adapter.js +8 -1
- package/build/agents/providers/opencode-sdk/events.js +24 -2
- package/build/agents/providers/opencode-sdk/registration.js +11 -1
- package/build/agents/providers/pi/checkpoint.js +9 -1
- package/build/agents/providers/pi/events.js +2 -0
- package/build/agents/providers/pi/registration.js +14 -1
- package/build/cli/commands/akuma/akuma/handler.js +1 -1
- package/build/cli/commands/akuma/akuma/meta.js +3 -3
- package/build/cli/commands/akuma/catalog.js +2 -2
- package/build/cli/commands/akuma/{view → show}/handler.js +5 -5
- package/build/cli/commands/akuma/{view → show}/meta.js +3 -3
- package/build/cli/commands/akuma.js +1 -1
- package/build/cli/commands/contract/amend/handler.js +5 -4
- package/build/cli/commands/contract/arc/handler.js +5 -4
- package/build/cli/commands/contract/audit/handler.js +2 -2
- package/build/cli/commands/contract/bind/handler.js +12 -3
- package/build/cli/commands/contract/bind/meta.js +45 -15
- package/build/cli/commands/contract/forfeit/handler.js +9 -5
- package/build/cli/commands/contract/log/handler.js +2 -2
- package/build/cli/commands/contract/petition/handler.js +34 -7
- package/build/cli/commands/contract/renew/handler.js +7 -7
- package/build/cli/commands/contract/renew/meta.js +3 -3
- package/build/cli/commands/metadata.js +10 -6
- package/build/cli/commands/projection/call/meta.js +1 -1
- package/build/cli/commands/projection/catalog.js +2 -0
- package/build/cli/commands/projection/history/handler.js +21 -0
- package/build/cli/commands/projection/history/meta.js +9 -0
- package/build/cli/commands/projection/shared.js +3 -4
- package/build/cli/commands/projection/status/handler.js +3 -3
- package/build/cli/commands/projection/wait/handler.js +20 -11
- package/build/cli/commands/task/add/handler.js +14 -5
- package/build/cli/commands/task/add/meta.js +2 -2
- package/build/cli/commands/task/catalog.js +4 -0
- package/build/cli/commands/task/done/handler.js +1 -4
- package/build/cli/commands/task/done/meta.js +1 -1
- package/build/cli/commands/task/drop/meta.js +1 -1
- package/build/cli/commands/task/hold/handler.js +3 -0
- package/build/cli/commands/task/hold/meta.js +8 -0
- package/build/cli/commands/task/log/handler.js +1 -1
- package/build/cli/commands/task/log/meta.js +2 -2
- package/build/cli/commands/task/resume/handler.js +3 -0
- package/build/cli/commands/task/resume/meta.js +8 -0
- package/build/cli/commands/task/shared.js +39 -14
- package/build/cli/commands/task/show/handler.js +1 -1
- package/build/cli/commands/task/show/meta.js +1 -1
- package/build/cli/commands/task/start/meta.js +1 -1
- package/build/cli/commands/task/stop/handler.js +1 -4
- package/build/cli/commands/task/stop/meta.js +1 -1
- package/build/cli/commands/task/update/handler.js +11 -3
- package/build/cli/commands/task/update/meta.js +3 -3
- package/build/cli/completion.js +3 -2
- package/build/cli/flags.js +51 -14
- package/build/cli/help.js +4 -1
- package/build/cli/index.js +40 -25
- package/build/cli/parse-flags.js +40 -7
- package/build/cli/parse-metadata.js +5 -2
- package/build/cli/parse-selectors.js +2 -3
- package/build/cli/parse.js +40 -6
- package/build/cli/projection-address.js +12 -10
- package/build/cli/render/amendment-warning.js +8 -0
- package/build/cli/render/arc.js +6 -2
- package/build/cli/render/audit.js +400 -81
- package/build/cli/render/kanshi.js +6 -1
- package/build/cli/render/lifecycle.js +56 -0
- package/build/cli/render/misc.js +24 -28
- package/build/cli/render/petition.js +5 -0
- package/build/cli/render/projection-activity.js +3 -2
- package/build/cli/render/projection-history.js +64 -0
- package/build/cli/render/shared.js +23 -9
- package/build/cli/render/status.js +9 -16
- package/build/cli/render/task.js +51 -26
- package/build/cli/render/terminal-failure.js +3 -1
- package/build/cli/render/wait.js +3 -5
- package/build/cli/subagent-guard.js +1 -1
- package/build/cli/types.js +1 -1
- package/build/config/env-keys.js +2 -0
- package/build/config/env.js +23 -0
- package/build/config/settings/schema.js +3 -1
- package/build/core/address-carrier.js +89 -0
- package/build/core/addressing.js +255 -196
- package/build/core/amend.js +115 -38
- package/build/core/amendment-criteria.js +17 -0
- package/build/core/amendment-warning.js +10 -0
- package/build/core/arc.js +184 -83
- package/build/core/audit/candidate.js +110 -9
- package/build/core/audit/coordinates.js +120 -65
- package/build/core/audit/evidence.js +19 -23
- package/build/core/audit/facade.js +34 -11
- package/build/core/audit/report.js +5 -5
- package/build/core/bind-reconciliation.js +390 -0
- package/build/core/bind-workspace.js +12 -20
- package/build/core/bind.js +242 -273
- package/build/core/call/call.js +1 -0
- package/build/core/call/context.js +46 -71
- package/build/core/call/prompt.js +6 -3
- package/build/core/context.js +49 -35
- package/build/core/contract-carrier-runtime.js +408 -0
- package/build/core/contract-carrier.js +270 -0
- package/build/core/contract-view.js +33 -27
- package/build/core/contract.js +5 -1
- package/build/core/derived-replay.js +207 -0
- package/build/core/draft.js +125 -57
- package/build/core/forfeit.js +113 -56
- package/build/core/identifier-slug.js +63 -13
- package/build/core/ids.js +4 -4
- package/build/core/lifecycle-history.js +35 -0
- package/build/core/lifecycle-recovery.js +117 -0
- package/build/core/lifecycle-runner.js +377 -0
- package/build/core/log.js +37 -11
- package/build/core/outcome-base.js +181 -0
- package/build/core/process-group.js +139 -0
- package/build/core/projection/generation/database.js +82 -7
- package/build/core/projection/generation/model.js +9 -36
- package/build/core/projection/generation/projection-generation-continuation.js +10 -22
- package/build/core/projection/generation/projection-generation-execution.js +6 -5
- package/build/core/projection/index.js +5 -4
- package/build/core/projection/projection-activity.js +27 -8
- package/build/core/projection/projection-alias.js +23 -10
- package/build/core/projection/projection-core.js +21 -7
- package/build/core/projection/projection-execution-observer.js +91 -39
- package/build/core/projection/projection-history.js +474 -0
- package/build/core/projection/projection-runner-lock.js +12 -0
- package/build/core/projection/projection-status-observation.js +365 -0
- package/build/core/projection/projection-status.js +21 -358
- package/build/core/projection/projection-terminal-failure.js +13 -8
- package/build/core/projection/projection-wait.js +7 -2
- package/build/core/projection/tell/launch-store.js +244 -0
- package/build/core/projection/tell/model.js +3 -3
- package/build/core/projection/tell/store.js +70 -118
- package/build/core/registry.js +22 -87
- package/build/core/renew-build.js +424 -0
- package/build/core/renew-plan.js +239 -0
- package/build/core/renew-rewrite.js +77 -0
- package/build/core/renew.js +295 -520
- package/build/core/repository-ledger/accepted-fold-read.js +19 -0
- package/build/core/repository-ledger/accepted-tail.js +116 -0
- package/build/core/repository-ledger/admission.js +38 -0
- package/build/core/repository-ledger/authoritative-transaction.js +1 -0
- package/build/core/repository-ledger/claim-evidence.js +147 -0
- package/build/core/repository-ledger/codec.js +297 -0
- package/build/core/repository-ledger/current-state-store.js +853 -0
- package/build/core/repository-ledger/fold-repository.js +375 -0
- package/build/core/repository-ledger/fold.js +248 -0
- package/build/core/repository-ledger/git-object-store.js +243 -0
- package/build/core/repository-ledger/history-read.js +15 -0
- package/build/core/repository-ledger/incremental-read.js +153 -0
- package/build/core/repository-ledger/index.js +15 -0
- package/build/core/repository-ledger/inventory-plan.js +75 -0
- package/build/core/repository-ledger/inventory.js +310 -0
- package/build/core/repository-ledger/publication-admission.js +125 -0
- package/build/core/repository-ledger/publication.js +353 -0
- package/build/core/repository-ledger/read-model.js +469 -0
- package/build/core/repository-ledger/transaction-membership.js +78 -0
- package/build/core/repository-ledger/traversal.js +404 -0
- package/build/core/repository-ledger/write-transaction.js +339 -0
- package/build/core/seal.js +168 -161
- package/build/core/settlement/claim-delivery.js +42 -215
- package/build/core/settlement/claim.js +235 -153
- package/build/core/settlement/index.js +4 -4
- package/build/core/settlement/petition-claim-gates.js +52 -33
- package/build/core/settlement/petition-forfeit.js +12 -5
- package/build/core/settlement/petition-head-guard.js +8 -5
- package/build/core/settlement/petition-preview.js +68 -56
- package/build/core/settlement/petition-window.js +50 -0
- package/build/core/settlement/petition.js +252 -131
- package/build/core/settlement/queue-read-model.js +72 -95
- package/build/core/settlement/queue-seat-allocation.js +35 -126
- package/build/core/settlement/queue.js +1 -1
- package/build/core/settlement/settlement.js +172 -80
- package/build/core/settlement/verdict.js +112 -66
- package/build/core/settlement/verification-coordination.js +305 -0
- package/build/core/settlement/verification-supervisor.js +275 -0
- package/build/core/settlement/verification.js +398 -31
- package/build/core/status/board.js +114 -119
- package/build/core/status/lifecycle.js +7 -80
- package/build/core/structured-query.js +23 -0
- package/build/core/target-ref.js +1 -1
- package/build/core/task/board.js +59 -24
- package/build/core/task/commands.js +181 -33
- package/build/core/task/coordinate.js +56 -0
- package/build/core/task/document.js +25 -9
- package/build/core/task/index.js +7 -4
- package/build/core/task/query.js +17 -27
- package/build/core/task/settlement-git.js +164 -179
- package/build/core/task/settlement-policy.js +15 -11
- package/build/core/task/source-board.js +31 -13
- package/build/core/task/task-bind-preparation.js +193 -0
- package/build/core/task/task-contract.js +102 -193
- package/build/core/task/task-file-transaction.js +171 -0
- package/build/core/task/task-git-runtime.js +76 -14
- package/build/core/task/task-git-store.js +68 -169
- package/build/core/task/task-store-repository.js +13 -6
- package/build/core/task/task-worktree.js +6 -33
- package/build/core/task/task.js +1 -1
- package/build/core/task/tree.js +3 -3
- package/build/core/task/validation.js +4 -3
- package/build/core/transcripts.js +93 -1
- package/build/core/worktree-bootstrap.js +44 -36
- package/build/core/worktree-path.js +210 -77
- package/build/flow-error.js +7 -7
- package/build/generated/version.js +2 -2
- package/build/git/core.js +76 -13
- package/build/git/refs.js +168 -46
- package/build/git/streaming-batch.js +73 -13
- package/package.json +1 -1
- package/skills/keiyaku/SKILL.md +1 -1
- package/skills/keiyaku-akuma/SKILL.md +3 -3
- package/skills/keiyaku-task/SKILL.md +4 -2
- package/skills/keiyaku-workflow/SKILL.md +88 -203
- package/build/core/entry.js +0 -305
- package/build/core/ledger-batch.js +0 -194
- package/build/core/ledger.js +0 -84
- package/build/core/projection/leash.js +0 -87
- package/build/core/ref-log.js +0 -119
- package/build/core/renew-session.js +0 -128
- package/build/core/status/ledger-batch.js +0 -1
- package/build/core/status/reconciliation.js +0 -137
|
@@ -1,174 +1,256 @@
|
|
|
1
1
|
import { FlowError } from "../../flow-error.js";
|
|
2
2
|
import { assertCleanTrackedWorkingTree } from "../../git/worktree.js";
|
|
3
|
-
import {
|
|
3
|
+
import { runLifecycle } from "../lifecycle-runner.js";
|
|
4
|
+
import { isJsonObject } from "../outcome-base.js";
|
|
5
|
+
import { terminalCleanupFacts } from "../worktree-path.js";
|
|
4
6
|
import { assertClaimQueueHead, readQueueReadModel } from "./queue.js";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
return
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
7
|
+
import { buildClaimLanding, relativeDisplayPath, } from "./claim-delivery.js";
|
|
8
|
+
export function claimDeliveryCoordinateMismatch(published, delivery) {
|
|
9
|
+
const assertion = published.observations.find((observation) => (observation.ref === delivery.ref
|
|
10
|
+
&& observation.expectedOldOid === delivery.head));
|
|
11
|
+
if (!assertion || assertion.observed.status === "unknown")
|
|
12
|
+
return undefined;
|
|
13
|
+
if (assertion.observed.status === "present" && assertion.observed.objectId === delivery.head)
|
|
14
|
+
return undefined;
|
|
15
|
+
return {
|
|
16
|
+
code: "CONCURRENT_MODIFICATION",
|
|
17
|
+
message: "claim delivery coordinate changed before atomic publication",
|
|
18
|
+
facts: {
|
|
19
|
+
delivery: {
|
|
20
|
+
expected: { ref: delivery.ref, head: delivery.head },
|
|
21
|
+
observed: assertion.observed,
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
};
|
|
17
25
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const entry = entries[index];
|
|
22
|
-
if (entry.kind === "verdict")
|
|
23
|
-
return entry;
|
|
24
|
-
if (entry.kind === "petition")
|
|
25
|
-
return undefined;
|
|
26
|
-
}
|
|
27
|
-
return undefined;
|
|
26
|
+
function acceptedClaim(model) {
|
|
27
|
+
const row = [...model.history].reverse().find(({ entry }) => entry.kind === "claim");
|
|
28
|
+
return row?.entry.kind === "claim" ? { objectId: row.objectId, entry: row.entry } : undefined;
|
|
28
29
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
*/
|
|
33
|
-
function assertClaimableFromLedger(contractId, ledger) {
|
|
34
|
-
if (!ledger) {
|
|
35
|
-
throw new FlowError("INTERNAL_STATE", `contract ${contractId} not found`);
|
|
36
|
-
}
|
|
37
|
-
const state = deriveContractState(ledger.entries);
|
|
38
|
-
const claim = findClaim(ledger.entries);
|
|
39
|
-
// Already-claimed cleanup retry: legal head even if a prior crash left debris.
|
|
40
|
-
if (claim && state.state === "claimed")
|
|
41
|
-
return claim;
|
|
42
|
-
if (state.state === "petitioned") {
|
|
43
|
-
const verdict = latestCurrentPetitionVerdict(ledger.entries);
|
|
44
|
-
if (verdict?.data.result !== "approved") {
|
|
45
|
-
throw new FlowError("INTERNAL_STATE", `contract ${contractId} cannot claim without approved verdict for current petition`);
|
|
46
|
-
}
|
|
30
|
+
function assertClaimable(contractId, model) {
|
|
31
|
+
const claim = acceptedClaim(model);
|
|
32
|
+
if (model.phase === "claimed" && claim)
|
|
47
33
|
return claim;
|
|
34
|
+
if (model.phase === "approved" && model.petition.state === "approved" && model.petition.intent === "claim")
|
|
35
|
+
return undefined;
|
|
36
|
+
if (model.phase === "forfeited") {
|
|
37
|
+
throw new FlowError("CONTRACT_TERMINAL", `contract ${contractId} is terminal (${model.phase})`);
|
|
48
38
|
}
|
|
49
|
-
if (
|
|
50
|
-
throw new FlowError("INTERNAL_STATE", `contract ${contractId}
|
|
51
|
-
}
|
|
52
|
-
throw new FlowError("INTERNAL_STATE", `contract ${contractId} cannot claim from state ${state.state}`);
|
|
53
|
-
}
|
|
54
|
-
async function publishClaimLedger(input) {
|
|
55
|
-
const result = await appendWithRetry(input.cwd, activeLedgerRef(input.contractId), (_head, entries) => {
|
|
56
|
-
if (entries.some((entry) => entry.kind === "claim"))
|
|
57
|
-
return null;
|
|
58
|
-
const entry = {
|
|
59
|
-
v: 1,
|
|
60
|
-
kind: "claim",
|
|
61
|
-
contract: input.contractId,
|
|
62
|
-
at: input.at,
|
|
63
|
-
actor: input.actor,
|
|
64
|
-
data: { mergeCommit: input.mergeCommit },
|
|
65
|
-
};
|
|
66
|
-
return entry;
|
|
67
|
-
});
|
|
68
|
-
if (!result.ok && result.reason !== "aborted") {
|
|
69
|
-
throw new FlowError("INTERNAL_STATE", `failed to append claim for ${input.contractId}: ${result.reason}`);
|
|
39
|
+
if (model.phase === "awaiting-verdict") {
|
|
40
|
+
throw new FlowError("INTERNAL_STATE", `contract ${contractId} cannot claim without approved verdict for current petition`);
|
|
70
41
|
}
|
|
42
|
+
throw new FlowError("INTERNAL_STATE", `contract ${contractId} cannot claim from state ${model.phase}`);
|
|
71
43
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const ledger = await readLedger(input.cwd, input.contractId);
|
|
76
|
-
assertClaimableFromLedger(input.contractId, ledger);
|
|
77
|
-
const entries = ledger.entries;
|
|
78
|
-
const cleanupPath = await findContractWorktreePath(input.cwd, input.contractId);
|
|
79
|
-
const state = deriveContractState(entries);
|
|
80
|
-
const base = latestBase(entries);
|
|
81
|
-
const commission = commissionTarget(entries);
|
|
82
|
-
if (!commission) {
|
|
83
|
-
throw new FlowError("INTERNAL_STATE", `cannot preview claim ${input.contractId}: missing commission target`);
|
|
44
|
+
function freezeClaimCoordinates(contractId, model, pins) {
|
|
45
|
+
if (model.petition.state !== "approved" || model.petition.intent !== "claim" || !model.petition.targetHead) {
|
|
46
|
+
throw new FlowError("INTERNAL_STATE", `contract ${contractId} has no approved claim coordinates`);
|
|
84
47
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const freshness = base
|
|
90
|
-
? base === targetHead ? "fresh" : "stale"
|
|
91
|
-
: "unknown";
|
|
92
|
-
const deliveryBranch = `keiyaku/${input.contractId}`;
|
|
93
|
-
let deliveryHead;
|
|
94
|
-
try {
|
|
95
|
-
deliveryHead = await resolveCommit(input.cwd, deliveryBranch);
|
|
96
|
-
}
|
|
97
|
-
catch {
|
|
98
|
-
deliveryHead = undefined;
|
|
48
|
+
if (pins.target.ref !== model.petition.target ||
|
|
49
|
+
pins.target.head !== model.petition.targetHead ||
|
|
50
|
+
pins.delivery.head !== model.pins.delivery) {
|
|
51
|
+
throw new FlowError("CONCURRENT_MODIFICATION", `claim coordinates for ${contractId} no longer match approved petition`);
|
|
99
52
|
}
|
|
100
53
|
return {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
freshness,
|
|
106
|
-
...(base ? { base } : {}),
|
|
107
|
-
targetBranch: commission.branch,
|
|
108
|
-
targetHead,
|
|
109
|
-
deliveryBranch,
|
|
110
|
-
...(deliveryHead ? { deliveryHead } : {}),
|
|
111
|
-
cleanupEffects: [
|
|
112
|
-
`release registry scope for ${input.contractId}`,
|
|
113
|
-
cleanupPath ? `remove worktree ${relativeDisplayPath(input.cwd, cleanupPath)} when present` : "no linked worktree to remove",
|
|
114
|
-
`delete branch keiyaku/${input.contractId} when present`,
|
|
115
|
-
],
|
|
54
|
+
base: model.pins.base,
|
|
55
|
+
target: model.petition.target,
|
|
56
|
+
targetHead: model.petition.targetHead,
|
|
57
|
+
deliveryHead: model.pins.delivery,
|
|
116
58
|
};
|
|
117
59
|
}
|
|
118
|
-
export async function
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const
|
|
60
|
+
export async function previewClaimContract(input) {
|
|
61
|
+
const { withContractCarrierSession } = await import("../contract-carrier.js");
|
|
62
|
+
return withContractCarrierSession({ cwd: input.cwd, contractId: input.contractId, mode: "observe-only", signal: input.signal }, async (session) => {
|
|
63
|
+
const queue = await readQueueReadModel(session.stableRoot);
|
|
64
|
+
const membership = assertClaimQueueHead(queue, input.contractId);
|
|
65
|
+
assertClaimable(input.contractId, session.lifecycle);
|
|
66
|
+
const targetHead = session.pins.target.head;
|
|
67
|
+
const base = session.lifecycle.pins.base;
|
|
68
|
+
const deliveryBranch = `keiyaku/${input.contractId}`;
|
|
69
|
+
const cleanupPath = session.target.status === "available" ? session.target.root : undefined;
|
|
128
70
|
return {
|
|
129
71
|
contractId: input.contractId,
|
|
130
|
-
|
|
131
|
-
|
|
72
|
+
queueHead: membership.queueHead,
|
|
73
|
+
queuePosition: membership.position,
|
|
74
|
+
state: session.lifecycle.phase,
|
|
75
|
+
freshness: targetHead ? (base === targetHead ? "fresh" : "stale") : "unknown",
|
|
76
|
+
base,
|
|
77
|
+
targetBranch: session.lifecycle.pins.target.ref.slice("refs/heads/".length),
|
|
78
|
+
...(targetHead ? { targetHead } : {}),
|
|
79
|
+
deliveryBranch,
|
|
80
|
+
...(session.pins.delivery.head ? { deliveryHead: session.pins.delivery.head } : {}),
|
|
81
|
+
cleanupEffects: [
|
|
82
|
+
`release registry scope for ${input.contractId}`,
|
|
83
|
+
cleanupPath ? `remove worktree ${relativeDisplayPath(input.cwd, cleanupPath)} when present` : "no linked worktree to remove",
|
|
84
|
+
`delete branch ${deliveryBranch} when present`,
|
|
85
|
+
],
|
|
132
86
|
};
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
export async function claimContract(input) {
|
|
90
|
+
const at = new Date(input.nowMs).toISOString();
|
|
91
|
+
const execution = await runLifecycle({
|
|
92
|
+
cwd: input.cwd,
|
|
93
|
+
contractId: input.contractId,
|
|
94
|
+
mutation: {
|
|
95
|
+
verb: "claim",
|
|
96
|
+
coordinateChangeRefusal: {
|
|
97
|
+
code: "CONCURRENT_MODIFICATION",
|
|
98
|
+
message: "claim target or repository ledger changed during atomic publication preparation",
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
signal: input.signal,
|
|
102
|
+
admit: async (session) => {
|
|
103
|
+
const existing = assertClaimable(input.contractId, session.lifecycle);
|
|
104
|
+
if (existing) {
|
|
105
|
+
return {
|
|
106
|
+
existing,
|
|
107
|
+
coordinates: null,
|
|
108
|
+
closedDelivery: null,
|
|
109
|
+
queuePin: null,
|
|
110
|
+
taskIds: session.lifecycle.resources.tasks.map(({ taskId }) => taskId),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
if (session.target.status !== "available")
|
|
114
|
+
throw new FlowError("INTERNAL_STATE", session.target.reason);
|
|
115
|
+
await assertCleanTrackedWorkingTree(session.target.root);
|
|
116
|
+
const queue = await readQueueReadModel(session.stableRoot);
|
|
117
|
+
assertClaimQueueHead(queue, input.contractId);
|
|
118
|
+
const coordinates = freezeClaimCoordinates(input.contractId, session.lifecycle, session.pins);
|
|
119
|
+
return {
|
|
120
|
+
coordinates,
|
|
121
|
+
closedDelivery: { ref: session.pins.delivery.ref, head: coordinates.deliveryHead },
|
|
122
|
+
targetRoot: session.target.root,
|
|
123
|
+
queuePin: JSON.stringify(queue),
|
|
124
|
+
taskIds: session.lifecycle.resources.tasks.map(({ taskId }) => taskId),
|
|
125
|
+
};
|
|
126
|
+
},
|
|
127
|
+
prepare: async (admission) => {
|
|
128
|
+
if (admission.business.coordinates === null)
|
|
129
|
+
return null;
|
|
130
|
+
const landing = await buildClaimLanding({
|
|
131
|
+
cwd: input.cwd,
|
|
132
|
+
contractId: input.contractId,
|
|
133
|
+
actor: input.actor,
|
|
134
|
+
at,
|
|
135
|
+
coordinates: admission.business.coordinates,
|
|
136
|
+
});
|
|
137
|
+
await input.beforePublication?.({
|
|
138
|
+
cwd: input.cwd,
|
|
139
|
+
contractId: input.contractId,
|
|
140
|
+
repositoryLedgerHead: admission.ledgerHead,
|
|
141
|
+
coordinates: Object.freeze({ ...admission.business.coordinates }),
|
|
142
|
+
landing: Object.freeze({ mergeCommit: landing.mergeCommit, mergeTree: landing.mergeTree }),
|
|
143
|
+
});
|
|
144
|
+
return landing;
|
|
145
|
+
},
|
|
146
|
+
revalidate: async ({ session, admission }) => {
|
|
147
|
+
if (admission.business.existing) {
|
|
148
|
+
const current = acceptedClaim(session.lifecycle);
|
|
149
|
+
return current?.objectId === admission.business.existing.objectId
|
|
150
|
+
? undefined
|
|
151
|
+
: { code: "CLAIM_STATE_CHANGED", message: "accepted claim changed during replay admission" };
|
|
152
|
+
}
|
|
153
|
+
if (session.target.status !== "available" || session.target.root !== admission.business.targetRoot) {
|
|
154
|
+
return { code: "DELIVERY_WORKTREE_CHANGED", message: "registered delivery worktree changed during claim preparation" };
|
|
155
|
+
}
|
|
156
|
+
await assertCleanTrackedWorkingTree(session.target.root);
|
|
157
|
+
const currentCoordinates = freezeClaimCoordinates(input.contractId, session.lifecycle, session.pins);
|
|
158
|
+
if (JSON.stringify(currentCoordinates) !== JSON.stringify(admission.business.coordinates)) {
|
|
159
|
+
return { code: "CLAIM_COORDINATES_CHANGED", message: "claim coordinates changed during preparation" };
|
|
160
|
+
}
|
|
161
|
+
const queue = await readQueueReadModel(session.stableRoot);
|
|
162
|
+
try {
|
|
163
|
+
assertClaimQueueHead(queue, input.contractId);
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
return { code: "QUEUE_HEAD_CHANGED", message: error instanceof Error ? error.message : String(error) };
|
|
167
|
+
}
|
|
168
|
+
return JSON.stringify(queue) === admission.business.queuePin
|
|
169
|
+
? undefined
|
|
170
|
+
: { code: "QUEUE_CHANGED", message: "settlement queue changed during claim preparation" };
|
|
171
|
+
},
|
|
172
|
+
effect: async ({ context, admission, prepared }) => {
|
|
173
|
+
const existing = admission.business.existing;
|
|
174
|
+
const mergeCommit = existing?.entry.data.mergeCommit ?? prepared?.mergeCommit;
|
|
175
|
+
if (!mergeCommit)
|
|
176
|
+
throw new FlowError("INTERNAL_STATE", `claim ${input.contractId} has no merge commit`);
|
|
177
|
+
const published = existing
|
|
178
|
+
? await context.replayCommitted({ transactionObjectId: admission.replay.state === "verb-committed" ? admission.replay.transaction.transactionObjectId : (() => { throw new FlowError("INTERNAL_STATE", "claim replay has no accepted transaction"); })(), carrier: "retire" })
|
|
179
|
+
: await context.publishAuthoritative({
|
|
180
|
+
verb: "claim",
|
|
181
|
+
admittedLedgerHead: admission.ledgerHead,
|
|
182
|
+
acceptedLifecycleTail: context.session.lifecycle.history.at(-1).objectId,
|
|
183
|
+
entries: [{
|
|
184
|
+
v: 2,
|
|
185
|
+
kind: "claim",
|
|
186
|
+
contract: input.contractId,
|
|
187
|
+
at,
|
|
188
|
+
actor: input.actor,
|
|
189
|
+
data: {
|
|
190
|
+
...admission.business.coordinates,
|
|
191
|
+
mergeCommit,
|
|
192
|
+
},
|
|
193
|
+
}],
|
|
194
|
+
target: {
|
|
195
|
+
ref: admission.business.coordinates.target,
|
|
196
|
+
expectedOid: admission.business.coordinates.targetHead,
|
|
197
|
+
},
|
|
198
|
+
delivery: {
|
|
199
|
+
ref: admission.business.closedDelivery.ref,
|
|
200
|
+
expectedOid: admission.business.closedDelivery.head,
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
if (published.status !== "committed") {
|
|
204
|
+
const deliveryMismatch = published.status === "refused-before-effects"
|
|
205
|
+
? claimDeliveryCoordinateMismatch(published, admission.business.closedDelivery)
|
|
206
|
+
: undefined;
|
|
207
|
+
return {
|
|
208
|
+
kind: "refused-before-effects",
|
|
209
|
+
authoritativeHead: published.currentHead ?? admission.ledgerHead,
|
|
210
|
+
refusal: deliveryMismatch ?? { code: "CONCURRENT_MODIFICATION", message: "claim target or repository ledger changed before atomic publication" },
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
await context.journal.run({
|
|
214
|
+
effect: "after-landing-hook",
|
|
215
|
+
...(context.journal.all().some((row) => row.effect === "ledger-cas" && row.status === "succeeded")
|
|
216
|
+
? { dependency: `${input.contractId}:${published.head}:ledger-cas` }
|
|
217
|
+
: {}),
|
|
218
|
+
run: async () => {
|
|
219
|
+
await input.afterPublication?.({ mergeCommit });
|
|
220
|
+
return { mergeCommit };
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
await context.session.completeReplay("retire");
|
|
224
|
+
const taskRow = context.journal.all().find((row) => row.effect === "task-board-projection" && row.status === "succeeded");
|
|
225
|
+
const taskFacts = taskRow?.status === "succeeded" ? taskRow.receipt?.facts : undefined;
|
|
226
|
+
const warnings = isJsonObject(taskFacts) && Array.isArray(taskFacts.warnings)
|
|
227
|
+
? taskFacts.warnings.filter((warning) => typeof warning === "string")
|
|
228
|
+
: [];
|
|
229
|
+
const cleanupRow = context.journal.all().find((row) => row.effect === "worktree-projection" && row.status === "succeeded");
|
|
230
|
+
const cleanupFacts = cleanupRow?.status === "succeeded" ? cleanupRow.receipt?.facts : undefined;
|
|
231
|
+
const deliveryRefRow = context.journal.all().find((row) => row.effect === "delivery-ref-projection" && row.status === "succeeded");
|
|
232
|
+
const deliveryRefFacts = deliveryRefRow?.status === "succeeded" ? deliveryRefRow.receipt?.facts : undefined;
|
|
233
|
+
const cleanup = terminalCleanupFacts(cleanupFacts, deliveryRefFacts);
|
|
234
|
+
if (!cleanup)
|
|
235
|
+
throw new FlowError("INTERNAL_STATE", "terminal replay completed without typed cleanup facts");
|
|
236
|
+
return {
|
|
237
|
+
kind: "committed",
|
|
238
|
+
commit: published,
|
|
239
|
+
entryKind: "claim",
|
|
240
|
+
value: { mergeCommit, ...(warnings.length > 0 ? { warnings } : {}), ...(cleanup ? { cleanup } : {}) },
|
|
241
|
+
};
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
if (execution.kind === "noncommitted")
|
|
245
|
+
return execution.outcome;
|
|
246
|
+
if (execution.kind === "contained")
|
|
247
|
+
return { ...execution.outcome, contractId: input.contractId };
|
|
168
248
|
return {
|
|
249
|
+
...execution.outcome,
|
|
169
250
|
contractId: input.contractId,
|
|
170
|
-
mergeCommit: mergeCommit,
|
|
171
|
-
|
|
172
|
-
...(
|
|
251
|
+
mergeCommit: execution.payload.mergeCommit,
|
|
252
|
+
...(execution.payload.warnings ? { warnings: execution.payload.warnings } : {}),
|
|
253
|
+
...(execution.payload.cleanup ? { cleanup: execution.payload.cleanup } : {}),
|
|
254
|
+
carrier: execution.carrier,
|
|
173
255
|
};
|
|
174
256
|
}
|
|
@@ -7,13 +7,13 @@
|
|
|
7
7
|
* Foundations (queue, claim-delivery) may be depended on downward by stage modules.
|
|
8
8
|
* Stage modules (petition, verdict, claim) do not import one another.
|
|
9
9
|
*/
|
|
10
|
-
export { claimKeiyaku, petitionKeiyaku, } from "./settlement.js";
|
|
11
|
-
export { buildPetitionClaimInput, petitionContract,
|
|
10
|
+
export { claimKeiyaku, petitionKeiyaku, reconcilePetitionRetry, } from "./settlement.js";
|
|
11
|
+
export { buildPetitionClaimInput, petitionContract, } from "./petition.js";
|
|
12
12
|
export { previewPetition, PETITION_PREVIEW_GATE_NAMES, } from "./petition-preview.js";
|
|
13
13
|
export { verdictContract, } from "./verdict.js";
|
|
14
14
|
export { claimContract, previewClaimContract, } from "./claim.js";
|
|
15
|
-
export {
|
|
16
|
-
export {
|
|
15
|
+
export { relativeDisplayPath, resolveCommit, resolveCommitOrNull, } from "./claim-delivery.js";
|
|
16
|
+
export { assertClaimQueueHead, deriveQueueReadModel, queueMembership, readQueueReadModel, } from "./queue.js";
|
|
17
17
|
export { buildClaimGatePipeline, createAgentReviewGate, createOathGate, } from "./petition-claim-gates.js";
|
|
18
18
|
export { assertHeadUnchanged } from "./petition-head-guard.js";
|
|
19
19
|
export { forfeitKeiyaku } from "./petition-forfeit.js";
|
|
@@ -8,13 +8,14 @@ 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 { createGit } from "../../git/core.js";
|
|
11
12
|
import { DIFF_PREVIEW_TEXT_MAX_CHARS } from "../../keiyaku.js";
|
|
12
13
|
import { FlowError } from "../../flow-error.js";
|
|
13
14
|
import { persistResponseHistory } from "../transcripts.js";
|
|
14
15
|
import { logInfo } from "../../telemetry/logger.js";
|
|
15
16
|
import { execSubagent } from "../call/call.js";
|
|
16
17
|
import { readContractLog } from "../log.js";
|
|
17
|
-
import {
|
|
18
|
+
import { runVerificationPlan } from "./verification.js";
|
|
18
19
|
const CLAIM_VERIFICATION_OUTPUT_MAX_CHARS = 1200;
|
|
19
20
|
const REVIEW_PASS_TOKEN = "PASS";
|
|
20
21
|
const REVIEW_REJECTION_MESSAGE = "CLAIM rejected by independent reviewer.";
|
|
@@ -29,7 +30,6 @@ const REVIEW_PROMPT_LINES = [
|
|
|
29
30
|
"You may use read-only repository inspection commands and tools as needed.",
|
|
30
31
|
"",
|
|
31
32
|
"## Workflow Context",
|
|
32
|
-
"- KEIYAKU.md is a workflow protocol file that will be removed before merge. Ignore it.",
|
|
33
33
|
"- Commit messages in this branch are intermediate; the branch will be squash-merged. Do not evaluate commit hygiene.",
|
|
34
34
|
"",
|
|
35
35
|
"## Output Rules",
|
|
@@ -100,25 +100,41 @@ export function createOathGate() {
|
|
|
100
100
|
export function createCommandVerificationGate() {
|
|
101
101
|
return {
|
|
102
102
|
name: "command",
|
|
103
|
-
async evaluate({ cwd,
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
if (result.code !== 0) {
|
|
108
|
-
return {
|
|
109
|
-
pass: false,
|
|
110
|
-
code: "CLOSE_QUALITY_GATE_FAILED",
|
|
111
|
-
message: `CLAIM blocked: verification failed at '${verification.label}' (exit ${result.code}).`,
|
|
112
|
-
facts: {
|
|
113
|
-
kind: "verification_failure",
|
|
114
|
-
label: verification.label,
|
|
115
|
-
stdout: clipCommandOutput(result.stdout.tail),
|
|
116
|
-
stderr: clipCommandOutput(result.stderr.tail),
|
|
117
|
-
},
|
|
118
|
-
};
|
|
119
|
-
}
|
|
103
|
+
async evaluate({ cwd, coordinationRoot, verificationPlan, verificationCandidate, input }) {
|
|
104
|
+
const repositoryRoot = coordinationRoot ?? cwd;
|
|
105
|
+
for (const verification of verificationPlan) {
|
|
106
|
+
logInfo(`[CLAIM] Running ${verification.source}: ${verification.label}`, { cwd, section: "script", progressOnly: true });
|
|
120
107
|
}
|
|
121
|
-
|
|
108
|
+
const evidence = await runVerificationPlan({ cwd, candidate: verificationCandidate, plan: verificationPlan, signal: input.signal, coordinationKey: JSON.stringify({ repository: repositoryRoot, candidate: verificationCandidate, plan: verificationPlan }), coordinationRoot: repositoryRoot });
|
|
109
|
+
const runnerFailure = evidence.find((result) => result.execution.status === "runner_failed");
|
|
110
|
+
if (runnerFailure?.execution.status === "runner_failed") {
|
|
111
|
+
return {
|
|
112
|
+
pass: false,
|
|
113
|
+
code: "CLOSE_QUALITY_GATE_FAILED",
|
|
114
|
+
message: `CLAIM blocked: verification runner uncertainty at ${runnerFailure.source} '${runnerFailure.label}' (${runnerFailure.execution.cause.code}: ${runnerFailure.execution.cause.message}).`,
|
|
115
|
+
facts: {
|
|
116
|
+
kind: "verification_failure",
|
|
117
|
+
label: `${runnerFailure.source} ${runnerFailure.label}`,
|
|
118
|
+
stdout: clipCommandOutput(runnerFailure.execution.stdout.tail),
|
|
119
|
+
stderr: clipCommandOutput(runnerFailure.execution.stderr.tail),
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
const commandFailure = evidence.find((result) => result.execution.status === "executed" && result.execution.code !== 0);
|
|
124
|
+
if (commandFailure?.execution.status === "executed") {
|
|
125
|
+
return {
|
|
126
|
+
pass: false,
|
|
127
|
+
code: "CLOSE_QUALITY_GATE_FAILED",
|
|
128
|
+
message: `CLAIM blocked: verification failed at ${commandFailure.source} '${commandFailure.label}' (exit ${commandFailure.execution.code}).`,
|
|
129
|
+
facts: {
|
|
130
|
+
kind: "verification_failure",
|
|
131
|
+
label: `${commandFailure.source} ${commandFailure.label}`,
|
|
132
|
+
stdout: clipCommandOutput(commandFailure.execution.stdout.tail),
|
|
133
|
+
stderr: clipCommandOutput(commandFailure.execution.stderr.tail),
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
return { pass: true, verification: [...evidence] };
|
|
122
138
|
},
|
|
123
139
|
};
|
|
124
140
|
}
|
|
@@ -177,25 +193,13 @@ export function createAgentReviewGate() {
|
|
|
177
193
|
authority: { kind: "repository" },
|
|
178
194
|
binding: { kind: "commission", commissionId: contractId },
|
|
179
195
|
});
|
|
180
|
-
const reviewHeadBeforeWrite = await
|
|
196
|
+
const reviewHeadBeforeWrite = (await createGit(cwd).revparse(["--verify", "HEAD^{commit}"])).trim();
|
|
181
197
|
const raw = subagentResult.finalMessage.trim();
|
|
182
198
|
const passed = raw.startsWith(REVIEW_PASS_TOKEN);
|
|
183
199
|
const observations = passed ? raw.slice(REVIEW_PASS_TOKEN.length).trim() || undefined : undefined;
|
|
184
|
-
const persisted = await persistResponseHistory({
|
|
185
|
-
cwd,
|
|
186
|
-
tool: "review",
|
|
187
|
-
provenance: { authority: { kind: "repository" }, binding: { kind: "commission", commissionId: contractId }, evidenceCwd: cwd },
|
|
188
|
-
title: String(reviewNumber),
|
|
189
|
-
prompt,
|
|
190
|
-
response: subagentResult.finalMessage,
|
|
191
|
-
session: subagentResult.session,
|
|
192
|
-
subagentName: selectedReviewerAgentName,
|
|
193
|
-
launchSnapshot: subagentResult.launchSnapshot,
|
|
194
|
-
});
|
|
195
200
|
const review = {
|
|
196
201
|
review: reviewNumber,
|
|
197
202
|
result: passed ? "passed" : "rejected",
|
|
198
|
-
historyPath: persisted.responsePath,
|
|
199
203
|
findings: passed ? observations : raw || REVIEW_RESPONSE_FALLBACK,
|
|
200
204
|
};
|
|
201
205
|
if (review.result === "passed") {
|
|
@@ -204,6 +208,21 @@ export function createAgentReviewGate() {
|
|
|
204
208
|
review,
|
|
205
209
|
reviewHeadBeforeWrite,
|
|
206
210
|
reviewerActor: selectedReviewerAgentName,
|
|
211
|
+
reviewPersistence: {
|
|
212
|
+
reviewNumber,
|
|
213
|
+
reviewedHead: reviewHeadBeforeWrite,
|
|
214
|
+
persist: () => persistResponseHistory({
|
|
215
|
+
cwd,
|
|
216
|
+
tool: "review",
|
|
217
|
+
provenance: { authority: { kind: "repository" }, binding: { kind: "commission", commissionId: contractId }, evidenceCwd: cwd },
|
|
218
|
+
title: String(reviewNumber),
|
|
219
|
+
prompt,
|
|
220
|
+
response: subagentResult.finalMessage,
|
|
221
|
+
session: subagentResult.session,
|
|
222
|
+
subagentName: selectedReviewerAgentName,
|
|
223
|
+
launchSnapshot: subagentResult.launchSnapshot,
|
|
224
|
+
}),
|
|
225
|
+
},
|
|
207
226
|
};
|
|
208
227
|
}
|
|
209
228
|
return {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { forfeitContract } from "../forfeit.js";
|
|
2
2
|
import { readContractLog } from "../log.js";
|
|
3
|
+
import { isCompleteLifecycleOutcome } from "../outcome-base.js";
|
|
3
4
|
function maxArc(entries) {
|
|
4
5
|
return entries.filter((entry) => entry.kind === "arc").length;
|
|
5
6
|
}
|
|
@@ -8,32 +9,38 @@ function cleanupWarnings(cleanup) {
|
|
|
8
9
|
...(cleanup.worktreeRemoveFailure
|
|
9
10
|
? [`Could not remove contract worktree ${cleanup.worktreeRemoveFailure.path}: ${cleanup.worktreeRemoveFailure.reason}`]
|
|
10
11
|
: []),
|
|
11
|
-
...(cleanup.
|
|
12
|
-
? [`
|
|
12
|
+
...(cleanup.deliveryRef.status === "mismatch"
|
|
13
|
+
? [`Delivery ref ${cleanup.deliveryRef.mismatch.ref} remains present at ${cleanup.deliveryRef.mismatch.observed.head}; terminal replay preserved it as a projected-surface mismatch.`]
|
|
13
14
|
: []),
|
|
14
15
|
];
|
|
15
16
|
}
|
|
16
|
-
export async function forfeitKeiyaku(context) {
|
|
17
|
+
export async function forfeitKeiyaku(context, signal) {
|
|
17
18
|
const { cwd, baseBranch, contractId } = context;
|
|
18
|
-
const log = await readContractLog({ cwd, contractId });
|
|
19
|
+
const log = await readContractLog({ cwd, contractId, signal });
|
|
19
20
|
const forfeited = await forfeitContract({
|
|
20
21
|
cwd,
|
|
21
22
|
contractId,
|
|
22
23
|
actor: "keiyaku",
|
|
23
24
|
nowMs: Date.now(),
|
|
24
25
|
reason: "manual",
|
|
26
|
+
signal,
|
|
25
27
|
});
|
|
28
|
+
if (!isCompleteLifecycleOutcome(forfeited))
|
|
29
|
+
return forfeited;
|
|
30
|
+
if (!forfeited.cleanup)
|
|
31
|
+
return forfeited;
|
|
26
32
|
return {
|
|
33
|
+
...forfeited,
|
|
27
34
|
status: "success",
|
|
28
35
|
result: "dropped",
|
|
29
36
|
arc: maxArc(log.entries),
|
|
30
37
|
currentBranch: baseBranch,
|
|
31
38
|
baseBranch,
|
|
32
|
-
...(forfeited.cleanup.deletedBranch ? { deletedBranch: forfeited.cleanup.deletedBranch } : {}),
|
|
33
39
|
cleanup: forfeited.cleanup,
|
|
34
40
|
ledgerView: log.markdown,
|
|
35
41
|
diff: "Forfeited without merge.",
|
|
36
42
|
droppedChanges: [],
|
|
43
|
+
carrier: forfeited.carrier,
|
|
37
44
|
...(cleanupWarnings(forfeited.cleanup).length > 0 ? { meta: { warnings: cleanupWarnings(forfeited.cleanup) } } : {}),
|
|
38
45
|
};
|
|
39
46
|
}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { FlowError } from "../../flow-error.js";
|
|
2
|
-
import {
|
|
2
|
+
import { createGit } from "../../git/core.js";
|
|
3
3
|
const CONCURRENT_MODIFICATION_CODE = "CONCURRENT_MODIFICATION";
|
|
4
4
|
export async function assertHeadUnchanged(cwd, expectedHead, operation) {
|
|
5
|
-
const
|
|
6
|
-
|
|
5
|
+
const git = createGit(cwd);
|
|
6
|
+
const [expectedCommit, currentHead] = await Promise.all([
|
|
7
|
+
git.raw(["rev-parse", "--verify", `${expectedHead}^{commit}`]),
|
|
8
|
+
git.raw(["rev-parse", "--verify", "HEAD^{commit}"]),
|
|
9
|
+
]);
|
|
10
|
+
if (currentHead.trim() === expectedCommit.trim())
|
|
7
11
|
return;
|
|
8
|
-
}
|
|
9
|
-
throw new FlowError(CONCURRENT_MODIFICATION_CODE, `concurrent ${operation} modification detected: repository HEAD changed from ${expectedHead} to ${currentHead} while the ${operation} was running`);
|
|
12
|
+
throw new FlowError(CONCURRENT_MODIFICATION_CODE, `concurrent ${operation} modification detected: repository HEAD changed from ${expectedHead} to ${currentHead.trim()} while the ${operation} was running`);
|
|
10
13
|
}
|