@astrosheep/keiyaku 2.9.2 → 2.9.4
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/build/.tsbuildinfo +1 -1
- package/build/agents/call-terms.js +13 -94
- package/build/agents/harness/activity-values.js +16 -0
- package/build/agents/harness/event-persistence.js +218 -0
- package/build/agents/harness/execution-handle.js +25 -222
- package/build/agents/harness/index.js +3 -1
- package/build/agents/harness/outcome.js +47 -4
- package/build/agents/harness/projection.js +42 -59
- package/build/agents/harness/provider-adapter.js +58 -0
- package/build/agents/harness/router.js +11 -187
- package/build/agents/harness/runtime.js +17 -4
- package/build/agents/harness/session.js +15 -0
- package/build/agents/launch-snapshot/model.js +15 -236
- package/build/agents/launch-snapshot/parse-helpers.js +114 -0
- package/build/agents/launch-snapshot/resolve.js +17 -43
- package/build/agents/opencode-sdk.js +45 -12
- package/build/agents/providers/claude-agent-sdk/adapter.js +6 -17
- package/build/agents/providers/claude-agent-sdk/registration.js +214 -0
- package/build/agents/providers/claude-agent-sdk/session-schema.js +2 -0
- package/build/agents/providers/codex-app-server/adapter.js +50 -53
- package/build/agents/providers/codex-app-server/events.js +67 -11
- package/build/agents/providers/codex-app-server/identity.js +1 -0
- package/build/agents/providers/codex-app-server/policy.js +29 -0
- package/build/agents/providers/codex-app-server/registration.js +248 -0
- package/build/agents/providers/codex-app-server/session-schema.js +6 -0
- package/build/agents/providers/codex-app-server/session.js +23 -17
- package/build/agents/providers/codex-app-server/terms.js +1 -0
- package/build/agents/providers/opencode-sdk/adapter.js +94 -50
- package/build/agents/providers/opencode-sdk/events.js +8 -4
- package/build/agents/providers/opencode-sdk/registration.js +135 -0
- package/build/agents/providers/opencode-sdk/session-schema.js +2 -0
- package/build/agents/providers/opencode-sdk/session.js +21 -14
- package/build/agents/providers/opencode-sdk/terms.js +1 -0
- package/build/agents/providers/pi/adapter.js +36 -30
- package/build/agents/providers/pi/registration.js +145 -0
- package/build/agents/providers/pi/session-schema.js +6 -0
- package/build/agents/providers/pi/terms.js +1 -0
- package/build/agents/providers/registration.js +7 -0
- package/build/agents/providers/registry.js +76 -0
- package/build/agents/selector.js +2 -29
- package/build/agents/types.js +8 -0
- package/build/artifact-directories.js +22 -0
- package/build/cli/commands/akuma/akuma/handler.js +1 -1
- package/build/cli/commands/akuma/view/handler.js +5 -4
- package/build/cli/commands/akuma.js +6 -0
- package/build/cli/commands/contract/amend/handler.js +8 -2
- package/build/cli/commands/contract/arc/handler.js +7 -1
- package/build/cli/commands/contract/bind/handler.js +4 -3
- package/build/cli/commands/contract/bind/meta.js +34 -7
- package/build/cli/commands/contract/forfeit/handler.js +4 -4
- package/build/cli/commands/contract/log/handler.js +3 -3
- package/build/cli/commands/contract/petition/handler.js +4 -6
- package/build/cli/commands/contract/renew/handler.js +20 -6
- package/build/cli/commands/contract/renew/meta.js +5 -4
- package/build/cli/commands/metadata.js +58 -0
- package/build/cli/commands/projection/call/handler.js +6 -5
- package/build/cli/commands/projection/kill/handler.js +16 -32
- package/build/cli/commands/projection/shared.js +28 -0
- package/build/cli/commands/projection/status/handler.js +1 -1
- package/build/cli/commands/projection/tell/handler.js +14 -30
- package/build/cli/commands/projection/wait/handler.js +45 -22
- package/build/cli/commands/projection/wait/meta.js +7 -2
- package/build/cli/commands/registry.js +2 -54
- package/build/cli/commands/shared.js +10 -7
- package/build/cli/commands/skills/install/handler.js +2 -5
- package/build/cli/commands/skills/skills/handler.js +1 -1
- package/build/cli/commands/task/add/handler.js +5 -13
- package/build/cli/commands/task/catalog.js +2 -2
- package/build/cli/commands/task/done/handler.js +4 -8
- package/build/cli/commands/task/drop/handler.js +3 -6
- package/build/cli/commands/task/ls/handler.js +3 -5
- package/build/cli/commands/task/shared.js +14 -4
- package/build/cli/commands/task/{view → show}/handler.js +7 -8
- package/build/cli/commands/task/show/meta.js +8 -0
- package/build/cli/commands/task/start/handler.js +3 -6
- package/build/cli/commands/task/stop/handler.js +4 -8
- package/build/cli/commands/task/task/handler.js +1 -1
- package/build/cli/commands/task/update/handler.js +4 -8
- package/build/cli/completion.js +1 -1
- package/build/cli/flags.js +19 -3
- package/build/cli/help.js +4 -11
- package/build/cli/index.js +57 -83
- package/build/cli/missing-subcommand.js +7 -0
- package/build/cli/parse-flags.js +14 -5
- package/build/cli/parse-metadata.js +3 -3
- package/build/cli/parse-selectors.js +1 -1
- package/build/cli/parse.js +21 -0
- package/build/cli/projection-address.js +7 -12
- package/build/cli/render/address.js +41 -37
- package/build/cli/render/arc.js +24 -12
- package/build/cli/render/call.js +11 -5
- package/build/cli/render/errors.js +36 -1
- package/build/cli/render/format.js +4 -0
- package/build/cli/render/kanshi.js +6 -12
- package/build/cli/render/kill.js +9 -0
- package/build/cli/render/misc.js +55 -12
- package/build/cli/render/petition.js +20 -15
- package/build/cli/render/projection-activity.js +3 -0
- package/build/cli/render/shared.js +60 -21
- package/build/cli/render/skills.js +10 -0
- package/build/cli/render/status.js +37 -14
- package/build/cli/render/success-response.js +77 -0
- package/build/cli/render/task.js +54 -14
- package/build/cli/render/tell.js +5 -0
- package/build/cli/render/terminal-failure.js +58 -0
- package/build/cli/render/types.js +1 -0
- package/build/cli/render/wait.js +38 -36
- package/build/cli/types.js +1 -1
- package/build/config/akuma-loader.js +0 -5
- package/build/config/env-keys.js +1 -1
- package/build/config/provider-policy-ownership.js +3 -32
- package/build/config/settings/disease.js +8 -1
- package/build/config/settings/loader.js +11 -3
- package/build/config/settings/provider-instance-fields.js +51 -0
- package/build/config/settings/schema.js +6 -176
- package/build/core/addressing.js +66 -16
- package/build/core/amend.js +135 -18
- package/build/core/arc.js +19 -67
- package/build/core/bind.js +82 -35
- package/build/core/call/call.js +15 -9
- package/build/core/call/context.js +6 -51
- package/build/core/call/execution.js +21 -22
- package/build/core/call/prompt.js +14 -1
- package/build/core/command-markdown.js +45 -0
- package/build/core/context.js +13 -7
- package/build/core/contract-view.js +20 -3
- package/build/core/draft.js +123 -16
- package/build/core/forfeit.js +5 -2
- package/build/core/hints.js +1 -75
- package/build/core/identifier-slug.js +66 -0
- package/build/core/ids.js +25 -11
- package/build/core/ledger.js +20 -68
- package/build/core/markdown/parser.js +0 -68
- package/build/core/markdown/render.js +68 -0
- package/build/core/markdown/sections.js +2 -1
- package/build/core/outcome-base.js +4 -0
- package/build/core/projection/generation/database.js +2 -38
- package/build/core/projection/generation/model.js +42 -1
- package/build/core/{projection-generation-continuation.js → projection/generation/projection-generation-continuation.js} +3 -3
- package/build/core/{projection-generation-execution.js → projection/generation/projection-generation-execution.js} +19 -49
- package/build/core/{projection-generation-identity.js → projection/generation/projection-generation-identity.js} +2 -2
- package/build/core/{projection-generation-launcher.js → projection/generation/projection-generation-launcher.js} +5 -3
- package/build/core/{projection-generation-process.js → projection/generation/projection-generation-process.js} +2 -1
- package/build/core/{projection-generation-runner.js → projection/generation/projection-generation-runner.js} +18 -3
- package/build/core/projection/generation/store.js +2 -2
- package/build/core/projection/generation/transitions.js +1 -1
- package/build/core/projection/index.js +27 -0
- package/build/core/{projection-activity.js → projection/projection-activity.js} +12 -0
- package/build/core/{projection-alias.js → projection/projection-alias.js} +14 -19
- package/build/core/projection/projection-coordinate.js +61 -0
- package/build/core/{projection-core.js → projection/projection-core.js} +9 -9
- package/build/core/{projection-execution-observer.js → projection/projection-execution-observer.js} +2 -57
- package/build/core/{projection-identity.js → projection/projection-identity.js} +1 -1
- package/build/core/{projection-kill.js → projection/projection-kill.js} +1 -1
- package/build/core/{projection-life-observer.js → projection/projection-life-observer.js} +1 -1
- package/build/core/{projection-mint.js → projection/projection-mint.js} +10 -10
- package/build/core/{projection-status.js → projection/projection-status.js} +98 -25
- package/build/core/projection/projection-terminal-failure.js +104 -0
- package/build/core/projection/projection-wait.js +278 -0
- package/build/core/projection/projection-wake.js +203 -0
- package/build/core/provider-loader-ports.js +4 -0
- package/build/core/ref-log.js +65 -40
- package/build/core/registry.js +63 -10
- package/build/core/renew-session.js +128 -0
- package/build/core/renew.js +426 -99
- package/build/core/scope.js +143 -66
- package/build/core/seal.js +139 -35
- package/build/core/{claim-delivery.js → settlement/claim-delivery.js} +9 -14
- package/build/core/{claim.js → settlement/claim.js} +32 -8
- package/build/core/settlement/index.js +19 -0
- package/build/core/{petition-claim-gates.js → settlement/petition-claim-gates.js} +22 -18
- package/build/core/{petition-forfeit.js → settlement/petition-forfeit.js} +2 -2
- package/build/core/{petition-head-guard.js → settlement/petition-head-guard.js} +2 -2
- package/build/core/settlement/petition-io.js +1 -0
- package/build/core/{petition-preview.js → settlement/petition-preview.js} +1 -1
- package/build/core/{petition.js → settlement/petition.js} +12 -10
- package/build/core/settlement/queue-read-model.js +143 -0
- package/build/core/settlement/queue-seat-allocation.js +156 -0
- package/build/core/settlement/queue.js +6 -0
- package/build/core/settlement/settlement.js +295 -0
- package/build/core/{verdict.js → settlement/verdict.js} +9 -37
- package/build/core/status/board.js +24 -23
- package/build/core/status/ledger-batch.js +158 -0
- package/build/core/status/reconciliation.js +10 -4
- package/build/core/stored-agent-event.js +3 -0
- package/build/core/task/board.js +40 -4
- package/build/core/task/commands.js +30 -21
- package/build/core/task/index.js +12 -0
- package/build/core/task/settlement-git.js +157 -65
- package/build/core/task/settlement-policy.js +54 -24
- package/build/core/task/task-contract.js +370 -0
- package/build/core/{task-doctor.js → task/task-doctor.js} +1 -1
- package/build/core/{task-git-actor.js → task/task-git-actor.js} +5 -10
- package/build/core/{task-git-runtime.js → task/task-git-runtime.js} +3 -3
- package/build/core/{task-git-store.js → task/task-git-store.js} +92 -29
- package/build/core/task/task-repository.js +1 -0
- package/build/core/{task-store-repository.js → task/task-store-repository.js} +5 -4
- package/build/core/task/task-store.js +1 -0
- package/build/core/{task-worktree.js → task/task-worktree.js} +2 -1
- package/build/core/task/task.js +5 -0
- package/build/core/task/tree.js +37 -0
- package/build/core/task/validation.js +5 -2
- package/build/core/transcripts.js +15 -3
- package/build/core/worktree-path.js +43 -28
- package/build/flow-error.js +85 -39
- package/build/fs/durability.js +27 -0
- package/build/fs/path-coordinate.js +16 -0
- package/build/generated/version.js +3 -2
- package/build/git/branches.js +2 -1
- package/build/git/core.js +106 -2
- package/build/git/path-coordinate.js +17 -0
- package/build/git/refs.js +57 -0
- package/build/git/streaming-batch.js +301 -0
- package/build/git/worktree.js +65 -0
- package/build/index.js +3 -3
- package/build/keiyaku.js +2 -36
- package/package.json +4 -10
- package/skills/keiyaku/SKILL.md +3 -2
- package/skills/keiyaku-akuma/SKILL.md +10 -6
- package/skills/keiyaku-task/SKILL.md +1 -1
- package/skills/keiyaku-workflow/SKILL.md +25 -4
- package/build/agents/providers/codex-sdk.js +0 -329
- package/build/cli/commands/task/view/meta.js +0 -8
- package/build/core/command-io.js +0 -174
- package/build/core/path-coordinate.js +0 -27
- package/build/core/petition-claim.js +0 -117
- package/build/core/petition-run.js +0 -83
- package/build/core/projection-coordinate.js +0 -63
- package/build/core/projection-wait.js +0 -165
- package/build/core/projection-wake.js +0 -136
- package/build/core/queue.js +0 -343
- package/build/core/task-contract.js +0 -237
- package/build/core/task.js +0 -4
- /package/build/{core/task-repository.js → agents/launch-snapshot/types.js} +0 -0
- /package/build/{core/task-store.js → agents/providers/claude-agent-sdk/terms.js} +0 -0
- /package/build/core/{projection-generation-runtime.js → projection/generation/projection-generation-runtime.js} +0 -0
- /package/build/core/{projection-life-protocol.js → projection/projection-life-protocol.js} +0 -0
- /package/build/core/{projection-runner-lock.js → projection/projection-runner-lock.js} +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { forfeitContract } from "
|
|
2
|
-
import { readContractLog } from "
|
|
1
|
+
import { forfeitContract } from "../forfeit.js";
|
|
2
|
+
import { readContractLog } from "../log.js";
|
|
3
3
|
function maxArc(entries) {
|
|
4
4
|
return entries.filter((entry) => entry.kind === "arc").length;
|
|
5
5
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { FlowError } from "
|
|
2
|
-
import { getLatestCommitHash } from "
|
|
1
|
+
import { FlowError } from "../../flow-error.js";
|
|
2
|
+
import { getLatestCommitHash } from "../../git/staging.js";
|
|
3
3
|
const CONCURRENT_MODIFICATION_CODE = "CONCURRENT_MODIFICATION";
|
|
4
4
|
export async function assertHeadUnchanged(cwd, expectedHead, operation) {
|
|
5
5
|
const currentHead = await getLatestCommitHash(cwd);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createOathGate } from "./petition-claim-gates.js";
|
|
2
2
|
import { assertPetitionArcReady, assertPetitionFresh, assertPetitionable, loadPetitionPreflightContext, } from "./petition.js";
|
|
3
|
-
import { petitionSealSource, previewSeal } from "
|
|
3
|
+
import { petitionSealSource, previewSeal } from "../seal.js";
|
|
4
4
|
export const PETITION_PREVIEW_GATE_NAMES = [
|
|
5
5
|
"contract",
|
|
6
6
|
"freshness",
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { FlowError } from "
|
|
2
|
-
import { createGit, errorContainsAnyPattern, MISSING_HEAD_PATTERNS, wrapGitError } from "
|
|
3
|
-
import { parseCommandMarkdown } from "
|
|
4
|
-
import { activeLedgerRef, appendWithRetry, readLedger } from "
|
|
1
|
+
import { FlowError } from "../../flow-error.js";
|
|
2
|
+
import { createGit, errorContainsAnyPattern, MISSING_HEAD_PATTERNS, wrapGitError } from "../../git/core.js";
|
|
3
|
+
import { parseCommandMarkdown } from "../command-markdown.js";
|
|
4
|
+
import { activeLedgerRef, appendWithRetry, readLedger } from "../ledger.js";
|
|
5
5
|
import { allocateSettlementSeat, queueMembership, readQueueReadModel } from "./queue.js";
|
|
6
|
-
import { findOpenArc, getMaxArc, receiptsAfterFence, sealCurrentArc } from "
|
|
7
|
-
import { deriveContractState } from "
|
|
8
|
-
import { commissionTarget, latestBase } from "
|
|
9
|
-
import { findContractWorktreePath } from "
|
|
6
|
+
import { findOpenArc, getMaxArc, receiptsAfterFence, sealCurrentArc } from "../seal.js";
|
|
7
|
+
import { deriveContractState } from "../status/lifecycle.js";
|
|
8
|
+
import { commissionTarget, latestBase } from "../status/drift.js";
|
|
9
|
+
import { findContractWorktreePath } from "../worktree-path.js";
|
|
10
|
+
import { assertCleanTrackedWorkingTree } from "../../git/worktree.js";
|
|
10
11
|
async function resolveCommit(cwd, ref) {
|
|
11
12
|
const git = createGit(cwd);
|
|
12
13
|
try {
|
|
@@ -47,7 +48,7 @@ export async function assertPetitionFresh(cwd, contractId, entries, base) {
|
|
|
47
48
|
throw new FlowError("INTERNAL_STATE", `contract ${contractId} commission target ${target.ref} does not resolve`);
|
|
48
49
|
}
|
|
49
50
|
if (base !== head) {
|
|
50
|
-
throw new FlowError("INTERNAL_STATE", `contract ${contractId} must renew onto ${target.branch} before petition`, {
|
|
51
|
+
throw new FlowError("INTERNAL_STATE", `contract ${contractId} must renew onto ${target.branch} before petition`, { facts: { kind: "needs_renew", contractId } });
|
|
51
52
|
}
|
|
52
53
|
}
|
|
53
54
|
export async function assertPetitionArcReady(deliveryCwd, contractId, entries) {
|
|
@@ -55,7 +56,7 @@ export async function assertPetitionArcReady(deliveryCwd, contractId, entries) {
|
|
|
55
56
|
return;
|
|
56
57
|
const loose = await receiptsAfterFence(deliveryCwd, entries);
|
|
57
58
|
if (loose.length > 0) {
|
|
58
|
-
throw new FlowError("NO_OPEN_ARC", `${loose.length} commits after fence have no arc to receipt them`, {
|
|
59
|
+
throw new FlowError("NO_OPEN_ARC", `${loose.length} commits after fence have no arc to receipt them`, { facts: { kind: "loose_commits", contractId, nextVerb: "petition" } });
|
|
59
60
|
}
|
|
60
61
|
}
|
|
61
62
|
export function assertPetitionable(contractId, entries) {
|
|
@@ -68,6 +69,7 @@ export function assertPetitionable(contractId, entries) {
|
|
|
68
69
|
export async function petitionContract(input) {
|
|
69
70
|
const at = new Date(input.nowMs).toISOString();
|
|
70
71
|
const { ledger, deliveryCwd, base, target } = await loadPetitionPreflightContext(input.cwd, input.contractId);
|
|
72
|
+
await assertCleanTrackedWorkingTree(deliveryCwd);
|
|
71
73
|
await assertPetitionFresh(input.cwd, input.contractId, ledger.entries, base);
|
|
72
74
|
await assertPetitionArcReady(deliveryCwd, input.contractId, ledger.entries);
|
|
73
75
|
assertPetitionable(input.contractId, ledger.entries);
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { listContractIds, readLedger } from "../ledger.js";
|
|
2
|
+
import { deriveContractState } from "../status/lifecycle.js";
|
|
3
|
+
import { commissionTarget } from "../status/drift.js";
|
|
4
|
+
export function isCurrentPetitionData(data) {
|
|
5
|
+
return "target" in data && "seat" in data;
|
|
6
|
+
}
|
|
7
|
+
function latestPetition(entries) {
|
|
8
|
+
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
9
|
+
const entry = entries[index];
|
|
10
|
+
if (entry.kind === "petition")
|
|
11
|
+
return entry;
|
|
12
|
+
}
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
export function deriveQueueReadModel(ledgers) {
|
|
16
|
+
const seatsByContract = new Map();
|
|
17
|
+
const diagnosticsByContract = new Map();
|
|
18
|
+
const orderedByTarget = new Map();
|
|
19
|
+
for (const ledger of ledgers) {
|
|
20
|
+
if (deriveContractState(ledger.entries).state !== "petitioned")
|
|
21
|
+
continue;
|
|
22
|
+
const petition = latestPetition(ledger.entries);
|
|
23
|
+
if (!petition)
|
|
24
|
+
continue;
|
|
25
|
+
const derived = commissionTarget(ledger.entries);
|
|
26
|
+
if (!derived)
|
|
27
|
+
continue;
|
|
28
|
+
const diagnostics = [];
|
|
29
|
+
let seat;
|
|
30
|
+
if (isCurrentPetitionData(petition.data)) {
|
|
31
|
+
seat = {
|
|
32
|
+
contract: ledger.contractId,
|
|
33
|
+
target: petition.data.target,
|
|
34
|
+
seat: petition.data.seat,
|
|
35
|
+
legacy: false,
|
|
36
|
+
};
|
|
37
|
+
if (petition.data.target !== derived.ref) {
|
|
38
|
+
diagnostics.push({
|
|
39
|
+
kind: "target-mismatch",
|
|
40
|
+
petitioned: petition.data.target,
|
|
41
|
+
derived: derived.ref,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
const bucket = orderedByTarget.get(petition.data.target) ?? [];
|
|
45
|
+
bucket.push(seat);
|
|
46
|
+
orderedByTarget.set(petition.data.target, bucket);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
seat = {
|
|
50
|
+
contract: ledger.contractId,
|
|
51
|
+
target: derived.ref,
|
|
52
|
+
seat: null,
|
|
53
|
+
legacy: true,
|
|
54
|
+
};
|
|
55
|
+
diagnostics.push({ kind: "legacy-petition" });
|
|
56
|
+
}
|
|
57
|
+
seatsByContract.set(ledger.contractId, seat);
|
|
58
|
+
diagnosticsByContract.set(ledger.contractId, diagnostics);
|
|
59
|
+
}
|
|
60
|
+
for (const [target, seats] of orderedByTarget) {
|
|
61
|
+
const bySeat = new Map();
|
|
62
|
+
for (const seat of seats) {
|
|
63
|
+
if (seat.seat === null)
|
|
64
|
+
continue;
|
|
65
|
+
const group = bySeat.get(seat.seat) ?? [];
|
|
66
|
+
group.push(seat);
|
|
67
|
+
bySeat.set(seat.seat, group);
|
|
68
|
+
}
|
|
69
|
+
for (const [seatNumber, group] of bySeat) {
|
|
70
|
+
if (group.length < 2)
|
|
71
|
+
continue;
|
|
72
|
+
const sorted = [...group].sort((a, b) => a.contract.localeCompare(b.contract));
|
|
73
|
+
for (const member of sorted) {
|
|
74
|
+
const others = sorted.filter((other) => other.contract !== member.contract);
|
|
75
|
+
const existing = diagnosticsByContract.get(member.contract) ?? [];
|
|
76
|
+
for (const other of others) {
|
|
77
|
+
existing.push({ kind: "duplicate-seat", seat: seatNumber, with: other.contract });
|
|
78
|
+
}
|
|
79
|
+
diagnosticsByContract.set(member.contract, existing);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
seats.sort((a, b) => {
|
|
83
|
+
const seatA = a.seat ?? Number.POSITIVE_INFINITY;
|
|
84
|
+
const seatB = b.seat ?? Number.POSITIVE_INFINITY;
|
|
85
|
+
if (seatA !== seatB)
|
|
86
|
+
return seatA - seatB;
|
|
87
|
+
return a.contract.localeCompare(b.contract);
|
|
88
|
+
});
|
|
89
|
+
orderedByTarget.set(target, seats);
|
|
90
|
+
}
|
|
91
|
+
const targetQueues = [...orderedByTarget.entries()]
|
|
92
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
93
|
+
.map(([target, ordered]) => ({ target, ordered }));
|
|
94
|
+
return {
|
|
95
|
+
targetQueues,
|
|
96
|
+
seatsByContract,
|
|
97
|
+
diagnosticsByContract,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
export async function readQueueReadModel(cwd) {
|
|
101
|
+
const contractIds = await listContractIds(cwd);
|
|
102
|
+
const ledgers = [];
|
|
103
|
+
for (const id of contractIds) {
|
|
104
|
+
const ledger = await readLedger(cwd, id);
|
|
105
|
+
if (ledger)
|
|
106
|
+
ledgers.push(ledger);
|
|
107
|
+
}
|
|
108
|
+
return deriveQueueReadModel(ledgers);
|
|
109
|
+
}
|
|
110
|
+
export function queueMembership(model, contractId) {
|
|
111
|
+
const seat = model.seatsByContract.get(contractId);
|
|
112
|
+
if (!seat)
|
|
113
|
+
return undefined;
|
|
114
|
+
const diagnostics = model.diagnosticsByContract.get(contractId) ?? [];
|
|
115
|
+
if (seat.legacy) {
|
|
116
|
+
return {
|
|
117
|
+
seat,
|
|
118
|
+
position: null,
|
|
119
|
+
queueLength: 0,
|
|
120
|
+
queueHead: null,
|
|
121
|
+
diagnostics,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
const targetQueue = model.targetQueues.find((queue) => queue.target === seat.target);
|
|
125
|
+
if (!targetQueue) {
|
|
126
|
+
return {
|
|
127
|
+
seat,
|
|
128
|
+
position: null,
|
|
129
|
+
queueLength: 0,
|
|
130
|
+
queueHead: null,
|
|
131
|
+
diagnostics,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
const index = targetQueue.ordered.findIndex((entry) => entry.contract === contractId);
|
|
135
|
+
const position = index < 0 ? null : index + 1;
|
|
136
|
+
return {
|
|
137
|
+
seat,
|
|
138
|
+
position,
|
|
139
|
+
queueLength: targetQueue.ordered.length,
|
|
140
|
+
queueHead: targetQueue.ordered[0]?.contract ?? null,
|
|
141
|
+
diagnostics,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { FlowError } from "../../flow-error.js";
|
|
3
|
+
import { listContractIds, readLedger } from "../ledger.js";
|
|
4
|
+
import { casUpdateRefLog, createRefLogCommit, listRefLogCommits, readRefLogCommitMessage, resolveRefLogHead, } from "../ref-log.js";
|
|
5
|
+
import { isCurrentPetitionData, queueMembership, } from "./queue-read-model.js";
|
|
6
|
+
const ISO_UTC_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/;
|
|
7
|
+
function isPositiveSafeInteger(value) {
|
|
8
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
|
|
9
|
+
}
|
|
10
|
+
export function assertClaimQueueHead(model, contractId) {
|
|
11
|
+
const membership = queueMembership(model, contractId);
|
|
12
|
+
if (!membership) {
|
|
13
|
+
throw new FlowError("INTERNAL_STATE", `contract ${contractId} is not queue head; position=not queued`);
|
|
14
|
+
}
|
|
15
|
+
if (membership.seat.legacy) {
|
|
16
|
+
throw new FlowError("QUEUE_LEGACY_PETITION", `contract ${contractId} petitioned before seat law took effect; reject its petition and petition again`);
|
|
17
|
+
}
|
|
18
|
+
const mismatch = membership.diagnostics.find((diagnostic) => diagnostic.kind === "target-mismatch");
|
|
19
|
+
if (mismatch && mismatch.kind === "target-mismatch") {
|
|
20
|
+
throw new FlowError("QUEUE_TARGET_MISMATCH", `contract ${contractId} petition target ${mismatch.petitioned} disagrees with derived target ${mismatch.derived}; reject its petition and petition again`);
|
|
21
|
+
}
|
|
22
|
+
if (membership.position !== 1) {
|
|
23
|
+
throw new FlowError("INTERNAL_STATE", `contract ${contractId} is not queue head; position=${membership.position ?? "not queued"}`);
|
|
24
|
+
}
|
|
25
|
+
return membership;
|
|
26
|
+
}
|
|
27
|
+
export function seatAllocatorRef(target) {
|
|
28
|
+
const digest = createHash("sha256").update(target, "utf8").digest("hex");
|
|
29
|
+
return `refs/keiyaku/seat/${digest}`;
|
|
30
|
+
}
|
|
31
|
+
function parseSeatAllocatorMessage(message, expectedTarget) {
|
|
32
|
+
const normalized = message.replace(/\r\n/g, "\n");
|
|
33
|
+
const separatorIndex = normalized.indexOf("\n\n");
|
|
34
|
+
if (separatorIndex < 0)
|
|
35
|
+
throw new Error("missing blank line");
|
|
36
|
+
const subject = normalized.slice(0, separatorIndex).trim();
|
|
37
|
+
const body = normalized.slice(separatorIndex + 2).trim();
|
|
38
|
+
const parsed = JSON.parse(body);
|
|
39
|
+
const keys = Object.keys(parsed).sort();
|
|
40
|
+
if (keys.join(",") !== "actor,at,seat,target,v")
|
|
41
|
+
throw new Error("unexpected fields");
|
|
42
|
+
if (parsed.v !== 1)
|
|
43
|
+
throw new Error("bad version");
|
|
44
|
+
if (typeof parsed.target !== "string" || parsed.target !== expectedTarget)
|
|
45
|
+
throw new Error("target mismatch");
|
|
46
|
+
if (!isPositiveSafeInteger(parsed.seat))
|
|
47
|
+
throw new Error("bad seat");
|
|
48
|
+
if (typeof parsed.at !== "string" || !ISO_UTC_RE.test(parsed.at))
|
|
49
|
+
throw new Error("bad at");
|
|
50
|
+
if (typeof parsed.actor !== "string" || parsed.actor.trim().length === 0)
|
|
51
|
+
throw new Error("bad actor");
|
|
52
|
+
if (subject !== `keiyaku seat: ${parsed.seat}`)
|
|
53
|
+
throw new Error("bad subject");
|
|
54
|
+
return {
|
|
55
|
+
v: 1,
|
|
56
|
+
target: parsed.target,
|
|
57
|
+
seat: parsed.seat,
|
|
58
|
+
at: parsed.at,
|
|
59
|
+
actor: parsed.actor,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
async function parseAllocatorChain(cwd, head, expectedTarget) {
|
|
63
|
+
// Intentional degrade: any rev-list or parse failure treats the chain as corrupt.
|
|
64
|
+
let commits;
|
|
65
|
+
try {
|
|
66
|
+
commits = await listRefLogCommits(cwd, head);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
if (commits.length === 0)
|
|
72
|
+
return null;
|
|
73
|
+
const entries = [];
|
|
74
|
+
let previousSeat = 0;
|
|
75
|
+
for (const commit of commits) {
|
|
76
|
+
try {
|
|
77
|
+
const entry = parseSeatAllocatorMessage(await readRefLogCommitMessage(cwd, commit), expectedTarget);
|
|
78
|
+
if (entry.seat <= previousSeat)
|
|
79
|
+
return null;
|
|
80
|
+
previousSeat = entry.seat;
|
|
81
|
+
entries.push(entry);
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return { entries, lastSeat: previousSeat };
|
|
88
|
+
}
|
|
89
|
+
async function ledgerSeatFloor(cwd, target) {
|
|
90
|
+
const contractIds = await listContractIds(cwd);
|
|
91
|
+
let maxSeat = 0;
|
|
92
|
+
for (const id of contractIds) {
|
|
93
|
+
const ledger = await readLedger(cwd, id);
|
|
94
|
+
if (!ledger)
|
|
95
|
+
continue;
|
|
96
|
+
for (const entry of ledger.entries) {
|
|
97
|
+
if (entry.kind !== "petition")
|
|
98
|
+
continue;
|
|
99
|
+
if (!isCurrentPetitionData(entry.data))
|
|
100
|
+
continue;
|
|
101
|
+
if (entry.data.target !== target)
|
|
102
|
+
continue;
|
|
103
|
+
if (entry.data.seat > maxSeat)
|
|
104
|
+
maxSeat = entry.data.seat;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return maxSeat;
|
|
108
|
+
}
|
|
109
|
+
export async function allocateSettlementSeat(cwd, input) {
|
|
110
|
+
const ref = seatAllocatorRef(input.target);
|
|
111
|
+
const maxRetries = 3;
|
|
112
|
+
for (let attempt = 0; attempt <= maxRetries; attempt += 1) {
|
|
113
|
+
// Raw observed-head read: any object id, not commit-only.
|
|
114
|
+
// Non-commit heads are returned so parseAllocatorChain can classify them as corrupt.
|
|
115
|
+
const observedHead = await resolveRefLogHead(cwd, ref, { peel: "object" });
|
|
116
|
+
const floor = await ledgerSeatFloor(cwd, input.target);
|
|
117
|
+
const parsed = observedHead ? await parseAllocatorChain(cwd, observedHead, input.target) : null;
|
|
118
|
+
const healthy = parsed !== null && parsed.lastSeat >= floor;
|
|
119
|
+
let seat;
|
|
120
|
+
let parent;
|
|
121
|
+
let expectedOld;
|
|
122
|
+
if (healthy && parsed) {
|
|
123
|
+
seat = parsed.lastSeat + 1;
|
|
124
|
+
parent = observedHead;
|
|
125
|
+
expectedOld = observedHead;
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
// Missing, parse failure, target mismatch, non-monotonic, or stale-behind-ledger:
|
|
129
|
+
// mint a detached no-parent root at floor+1. Leave the bad chain for Git GC.
|
|
130
|
+
seat = floor + 1;
|
|
131
|
+
parent = null;
|
|
132
|
+
expectedOld = observedHead;
|
|
133
|
+
}
|
|
134
|
+
if (!isPositiveSafeInteger(seat)) {
|
|
135
|
+
throw new FlowError("INTERNAL_STATE", `settlement seats exhausted for ${input.target}`);
|
|
136
|
+
}
|
|
137
|
+
const entry = {
|
|
138
|
+
v: 1,
|
|
139
|
+
target: input.target,
|
|
140
|
+
seat,
|
|
141
|
+
at: input.at,
|
|
142
|
+
actor: input.actor,
|
|
143
|
+
};
|
|
144
|
+
const newHead = await createRefLogCommit(cwd, {
|
|
145
|
+
subject: `keiyaku seat: ${entry.seat}`,
|
|
146
|
+
body: JSON.stringify(entry, null, 2),
|
|
147
|
+
parentHead: parent,
|
|
148
|
+
errorLabel: `seat ${entry.seat}`,
|
|
149
|
+
});
|
|
150
|
+
// Intentional degrade: CAS failure returns false; bounded retry re-reads head.
|
|
151
|
+
const casOk = await casUpdateRefLog(cwd, ref, newHead, expectedOld);
|
|
152
|
+
if (casOk)
|
|
153
|
+
return { head: newHead, seat };
|
|
154
|
+
}
|
|
155
|
+
throw new FlowError("INTERNAL_STATE", `failed to allocate settlement seat for ${input.target}`);
|
|
156
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settlement queue surface: read-model derivation/query and seat allocation/assertion.
|
|
3
|
+
* Implementation lives in sibling files; this module preserves the historical import path.
|
|
4
|
+
*/
|
|
5
|
+
export { deriveQueueReadModel, queueMembership, readQueueReadModel, } from "./queue-read-model.js";
|
|
6
|
+
export { allocateSettlementSeat, assertClaimQueueHead, seatAllocatorRef, } from "./queue-seat-allocation.js";
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { assertSettingsKnobUsable } from "../../config/settings/disease.js";
|
|
2
|
+
import { loadKeiyakuSettings } from "../../config/settings/loader.js";
|
|
3
|
+
import { FlowError } from "../../flow-error.js";
|
|
4
|
+
import { readCurrentContractContext } from "../context.js";
|
|
5
|
+
import { readLedger } from "../ledger.js";
|
|
6
|
+
import { commissionTarget } from "../status/drift.js";
|
|
7
|
+
import { deriveContractState } from "../status/lifecycle.js";
|
|
8
|
+
import { getCurrentBranch, requireGitRepository } from "../../git/branches.js";
|
|
9
|
+
import { readContractLog } from "../log.js";
|
|
10
|
+
import { listRefLogCommits } from "../ref-log.js";
|
|
11
|
+
import { effectiveScope } from "../seal.js";
|
|
12
|
+
import { scopePathEvidence } from "../scope.js";
|
|
13
|
+
import { nextReviewResponseNumber } from "../transcripts.js";
|
|
14
|
+
import { claimContract } from "./claim.js";
|
|
15
|
+
import { buildClaimGatePipeline, } from "./petition-claim-gates.js";
|
|
16
|
+
import { forfeitKeiyaku } from "./petition-forfeit.js";
|
|
17
|
+
import { assertHeadUnchanged } from "./petition-head-guard.js";
|
|
18
|
+
import { petitionContract } from "./petition.js";
|
|
19
|
+
import { verdictContract } from "./verdict.js";
|
|
20
|
+
const VERDICT_DENIED_CODE = "VERDICT_DENIED";
|
|
21
|
+
async function resolveClaimGateConfig(cwd) {
|
|
22
|
+
const merged = {
|
|
23
|
+
verificationCommands: [],
|
|
24
|
+
};
|
|
25
|
+
try {
|
|
26
|
+
const loaded = await loadKeiyakuSettings(cwd);
|
|
27
|
+
assertSettingsKnobUsable(loaded, "gate");
|
|
28
|
+
const gate = loaded.knobs?.gate;
|
|
29
|
+
if (!gate) {
|
|
30
|
+
return merged;
|
|
31
|
+
}
|
|
32
|
+
merged.verificationCommands = gate.commands ?? merged.verificationCommands;
|
|
33
|
+
return merged;
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
if (error instanceof FlowError && error.code === "INVALID_SETTINGS") {
|
|
37
|
+
throw new FlowError(VERDICT_DENIED_CODE, `Configuration Error: ${error.message}`, {
|
|
38
|
+
cause: error,
|
|
39
|
+
facts: error.facts,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async function loadCloseContext(cwd, requestedContractId) {
|
|
46
|
+
await requireGitRepository(cwd, "petition");
|
|
47
|
+
if (requestedContractId) {
|
|
48
|
+
const ledger = await readLedger(cwd, requestedContractId);
|
|
49
|
+
if (!ledger) {
|
|
50
|
+
throw new FlowError("INTERNAL_STATE", `contract ${requestedContractId} not found or not active`);
|
|
51
|
+
}
|
|
52
|
+
const target = commissionTarget(ledger.entries);
|
|
53
|
+
if (!target) {
|
|
54
|
+
throw new FlowError("INTERNAL_STATE", `contract ${requestedContractId} active ledger is missing commission target`);
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
cwd,
|
|
58
|
+
keiyakuBranch: `keiyaku/${requestedContractId}`,
|
|
59
|
+
baseBranch: target.branch,
|
|
60
|
+
contractId: requestedContractId,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
let contractContext;
|
|
64
|
+
try {
|
|
65
|
+
contractContext = await readCurrentContractContext(cwd);
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
if (!(error instanceof FlowError) || error.code !== "EMPTY_PARAM") {
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
throw new FlowError("NOT_ACTIVE_KEIYAKU_BRANCH", "Current workspace is not addressing an active Keiyaku contract.");
|
|
72
|
+
}
|
|
73
|
+
const keiyakuBranch = await getCurrentBranch(cwd);
|
|
74
|
+
return {
|
|
75
|
+
cwd,
|
|
76
|
+
keiyakuBranch,
|
|
77
|
+
baseBranch: contractContext.baseBranch,
|
|
78
|
+
contractId: contractContext.contractId,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
async function throwClaimGateFailure(failure, _input, context, _dependencies) {
|
|
82
|
+
void _input;
|
|
83
|
+
void _dependencies;
|
|
84
|
+
void context.contractId;
|
|
85
|
+
if (failure.reviewHeadBeforeWrite) {
|
|
86
|
+
await assertHeadUnchanged(context.cwd, failure.reviewHeadBeforeWrite, "claim review");
|
|
87
|
+
}
|
|
88
|
+
throw new FlowError(failure.code, failure.message, {
|
|
89
|
+
review: failure.review,
|
|
90
|
+
facts: failure.facts,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
async function runClaimGatePipelineOrThrow(input, context, dependencies) {
|
|
94
|
+
const { verificationCommands } = await dependencies.resolveClaimGateConfig(context.cwd);
|
|
95
|
+
let review;
|
|
96
|
+
let reviewHeadBeforeWrite;
|
|
97
|
+
let reviewerActor;
|
|
98
|
+
for (const gate of buildClaimGatePipeline()) {
|
|
99
|
+
const result = await gate.evaluate({
|
|
100
|
+
cwd: context.cwd,
|
|
101
|
+
contractId: context.contractId,
|
|
102
|
+
input,
|
|
103
|
+
verificationCommands,
|
|
104
|
+
baseBranch: dependencies.baseBranch,
|
|
105
|
+
reviewNumber: dependencies.reviewNumber,
|
|
106
|
+
});
|
|
107
|
+
if (result.review) {
|
|
108
|
+
review = result.review;
|
|
109
|
+
reviewHeadBeforeWrite = result.reviewHeadBeforeWrite;
|
|
110
|
+
}
|
|
111
|
+
if (result.reviewerActor)
|
|
112
|
+
reviewerActor = result.reviewerActor;
|
|
113
|
+
if (!result.pass) {
|
|
114
|
+
await throwClaimGateFailure(result, input, context, dependencies);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return { review, reviewHeadBeforeWrite, reviewerActor };
|
|
118
|
+
}
|
|
119
|
+
function maxArc(entries) {
|
|
120
|
+
return entries.filter((entry) => entry.kind === "arc").length;
|
|
121
|
+
}
|
|
122
|
+
function cleanupWarnings(cleanup) {
|
|
123
|
+
return [
|
|
124
|
+
...(cleanup.worktreeRemoveFailure
|
|
125
|
+
? [`Could not remove contract worktree ${cleanup.worktreeRemoveFailure.path}: ${cleanup.worktreeRemoveFailure.reason}`]
|
|
126
|
+
: []),
|
|
127
|
+
...(cleanup.branchDeleteFailure
|
|
128
|
+
? [`Could not delete contract branch ${cleanup.branchDeleteFailure.name}: ${cleanup.branchDeleteFailure.reason}`]
|
|
129
|
+
: []),
|
|
130
|
+
];
|
|
131
|
+
}
|
|
132
|
+
function findClaimEntry(entries) {
|
|
133
|
+
return entries.find((entry) => entry.kind === "claim");
|
|
134
|
+
}
|
|
135
|
+
/** Verdict for the open petition window (after last petition, before claim). */
|
|
136
|
+
function latestCurrentPetitionVerdict(entries) {
|
|
137
|
+
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
138
|
+
const entry = entries[index];
|
|
139
|
+
if (entry.kind === "verdict")
|
|
140
|
+
return entry;
|
|
141
|
+
if (entry.kind === "petition")
|
|
142
|
+
return undefined;
|
|
143
|
+
}
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Crash-recovery head: petition → approved verdict → no claim.
|
|
148
|
+
* Orchestrator resumes claim delivery only; no gates, petition, or re-judgment.
|
|
149
|
+
*/
|
|
150
|
+
function isApprovedVerdictAwaitingClaim(entries) {
|
|
151
|
+
const state = deriveContractState(entries);
|
|
152
|
+
if (state.state !== "petitioned")
|
|
153
|
+
return false;
|
|
154
|
+
if (findClaimEntry(entries))
|
|
155
|
+
return false;
|
|
156
|
+
return latestCurrentPetitionVerdict(entries)?.data.result === "approved";
|
|
157
|
+
}
|
|
158
|
+
async function recoverCurrentPetitionSeal(cwd, ledger) {
|
|
159
|
+
let petitionIndex = -1;
|
|
160
|
+
for (let index = ledger.entries.length - 1; index >= 0; index -= 1) {
|
|
161
|
+
if (ledger.entries[index]?.kind === "petition") {
|
|
162
|
+
petitionIndex = index;
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (petitionIndex <= 0)
|
|
167
|
+
return undefined;
|
|
168
|
+
const seal = ledger.entries[petitionIndex - 1];
|
|
169
|
+
if (seal.kind !== "seal" || seal.data.sealedBy !== "petition")
|
|
170
|
+
return undefined;
|
|
171
|
+
const commits = await listRefLogCommits(cwd, ledger.head, ledger.ref);
|
|
172
|
+
const ledgerHead = commits[petitionIndex - 1];
|
|
173
|
+
if (!ledgerHead || commits.length !== ledger.entries.length) {
|
|
174
|
+
throw new FlowError("INTERNAL_STATE", `contract ${ledger.contractId} ledger entries do not align with ref-log commits during petition recovery`);
|
|
175
|
+
}
|
|
176
|
+
const scopeViolationEvidence = await scopePathEvidence(cwd, effectiveScope(ledger.entries), seal.data.scopeAudit.violations);
|
|
177
|
+
return {
|
|
178
|
+
...seal.data,
|
|
179
|
+
scopeViolationEvidence,
|
|
180
|
+
ledgerHead,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
async function finishApprovedClaim(context, options) {
|
|
184
|
+
const { cwd, baseBranch, contractId } = context;
|
|
185
|
+
// Capture arc facts before claim cleanup may remove the delivery worktree cwd.
|
|
186
|
+
const log = await readContractLog({ cwd, contractId });
|
|
187
|
+
const claimed = await claimContract({
|
|
188
|
+
cwd,
|
|
189
|
+
contractId,
|
|
190
|
+
actor: "keiyaku",
|
|
191
|
+
nowMs: options.nowMs,
|
|
192
|
+
});
|
|
193
|
+
const cleanup = claimed.cleanup ?? { absent: ["worktree", "branch"] };
|
|
194
|
+
const warnings = [...(claimed.warnings ?? []), ...cleanupWarnings(cleanup)];
|
|
195
|
+
return {
|
|
196
|
+
status: "success",
|
|
197
|
+
result: "merged",
|
|
198
|
+
arc: maxArc(log.entries),
|
|
199
|
+
currentBranch: baseBranch,
|
|
200
|
+
baseBranch,
|
|
201
|
+
commit: claimed.mergeCommit,
|
|
202
|
+
mergedInto: baseBranch,
|
|
203
|
+
...(cleanup.deletedBranch ? { deletedBranch: cleanup.deletedBranch } : {}),
|
|
204
|
+
cleanup,
|
|
205
|
+
diff: `Merged contract ${contractId} with merge-tree/commit-tree CAS.`,
|
|
206
|
+
// Prior public outcome carried sealed facts when petition sealed an open arc.
|
|
207
|
+
...(options.sealed ? { sealed: options.sealed } : {}),
|
|
208
|
+
...(warnings.length > 0 ? { meta: { warnings } } : {}),
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Settlement claim path: gates → petition → pure verdict → claim delivery.
|
|
213
|
+
* Stage modules do not call each other; this orchestrator owns the sequence.
|
|
214
|
+
*
|
|
215
|
+
* Atomicity equivalence: previously verdictContract appended the verdict entry
|
|
216
|
+
* then immediately invoked claimContract. That remains two sequential append
|
|
217
|
+
* windows (verdict then claim). Crash after approved verdict leaves state
|
|
218
|
+
* petitioned with an approved verdict; the orchestrator detects that head and
|
|
219
|
+
* resumes claimContract idempotently without re-running gates or judgment.
|
|
220
|
+
*/
|
|
221
|
+
export async function claimKeiyaku(input, context, dependencies) {
|
|
222
|
+
const { cwd, baseBranch, contractId } = context;
|
|
223
|
+
// Crash recovery: petition → approved verdict → no claim.
|
|
224
|
+
// Resume claim delivery only; do not re-gate, re-petition, or re-judge.
|
|
225
|
+
const existing = await readLedger(cwd, contractId);
|
|
226
|
+
if (existing && isApprovedVerdictAwaitingClaim(existing.entries)) {
|
|
227
|
+
const sealed = await recoverCurrentPetitionSeal(cwd, existing);
|
|
228
|
+
return finishApprovedClaim(context, {
|
|
229
|
+
nowMs: Date.now(),
|
|
230
|
+
...(sealed ? { sealed } : {}),
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
const claimResult = "dangerouslyBypassGates" in input && input.dangerouslyBypassGates
|
|
234
|
+
? { review: undefined, reviewHeadBeforeWrite: undefined, bypassedByArchitect: true }
|
|
235
|
+
: {
|
|
236
|
+
...(await runClaimGatePipelineOrThrow(input, { cwd, contractId }, {
|
|
237
|
+
...dependencies,
|
|
238
|
+
baseBranch,
|
|
239
|
+
reviewNumber: await nextReviewResponseNumber(cwd),
|
|
240
|
+
})),
|
|
241
|
+
bypassedByArchitect: false,
|
|
242
|
+
};
|
|
243
|
+
if (claimResult.review && claimResult.reviewHeadBeforeWrite) {
|
|
244
|
+
await assertHeadUnchanged(cwd, claimResult.reviewHeadBeforeWrite, "claim review");
|
|
245
|
+
}
|
|
246
|
+
const oath = String(input.oath ?? "").trim();
|
|
247
|
+
if (!oath) {
|
|
248
|
+
throw new FlowError("OATH_MISSING", "Oath missing.");
|
|
249
|
+
}
|
|
250
|
+
// 1. Petition: seal + admit onto the target-local settlement queue.
|
|
251
|
+
const petition = await petitionContract({
|
|
252
|
+
cwd,
|
|
253
|
+
contractId,
|
|
254
|
+
actor: "keiyaku",
|
|
255
|
+
nowMs: Date.now(),
|
|
256
|
+
intent: "claim",
|
|
257
|
+
oath,
|
|
258
|
+
waivePaths: input.waivePaths,
|
|
259
|
+
});
|
|
260
|
+
// 2. Pure verdict: judgment facts only; no claim execution.
|
|
261
|
+
// Capture one clock for verdict + claim so ledger timestamps match the prior
|
|
262
|
+
// verdict→claim atomic pair (verdict previously forwarded its own nowMs).
|
|
263
|
+
const judgmentNowMs = Date.now();
|
|
264
|
+
const verdict = await verdictContract({
|
|
265
|
+
cwd,
|
|
266
|
+
contractId,
|
|
267
|
+
actor: claimResult.reviewerActor ?? "keiyaku",
|
|
268
|
+
nowMs: judgmentNowMs,
|
|
269
|
+
result: "approved",
|
|
270
|
+
summary: claimResult.review?.findings?.trim() || "礼成",
|
|
271
|
+
});
|
|
272
|
+
// 3. Execution: approved → claim delivery (same sequential append window as before).
|
|
273
|
+
// Rejected verdict publication is verdictContract itself (state returns active).
|
|
274
|
+
if (verdict.result !== "approved") {
|
|
275
|
+
throw new FlowError("INTERNAL_STATE", `settlement expected approved verdict for ${contractId}, got ${verdict.result}`);
|
|
276
|
+
}
|
|
277
|
+
return finishApprovedClaim(context, {
|
|
278
|
+
nowMs: judgmentNowMs,
|
|
279
|
+
...(petition.sealed ? { sealed: petition.sealed } : {}),
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
/** Public settlement entrypoint for CLI petition (claim or forfeit). */
|
|
283
|
+
export async function petitionKeiyaku(input) {
|
|
284
|
+
const context = await loadCloseContext(input.cwd, input.contractId);
|
|
285
|
+
const { intent } = input;
|
|
286
|
+
if (intent === "FORFEIT") {
|
|
287
|
+
return forfeitKeiyaku(context);
|
|
288
|
+
}
|
|
289
|
+
if (intent === "CLAIM") {
|
|
290
|
+
return claimKeiyaku(input, context, {
|
|
291
|
+
resolveClaimGateConfig,
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
throw new FlowError("INVALID_CLOSE_INTENT", `unsupported close intent: ${intent}`);
|
|
295
|
+
}
|