@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,6 +1,8 @@
|
|
|
1
1
|
import { createOathGate } from "./petition-claim-gates.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { assertFoldedPetitionable, } from "./petition.js";
|
|
3
|
+
import { captureFoldedSeal } from "../seal.js";
|
|
4
|
+
import { withContractCarrierSession } from "../contract-carrier.js";
|
|
5
|
+
import { FlowError } from "../../flow-error.js";
|
|
4
6
|
export const PETITION_PREVIEW_GATE_NAMES = [
|
|
5
7
|
"contract",
|
|
6
8
|
"freshness",
|
|
@@ -23,8 +25,8 @@ async function gate(name, evaluate) {
|
|
|
23
25
|
return { name, result: "failed", detail: failureDetail(error) };
|
|
24
26
|
}
|
|
25
27
|
}
|
|
26
|
-
function
|
|
27
|
-
return `${
|
|
28
|
+
function sealFileDetail(files) {
|
|
29
|
+
return `${files} ${files === 1 ? "file" : "files"}`;
|
|
28
30
|
}
|
|
29
31
|
const notEvaluated = (name) => ({
|
|
30
32
|
name,
|
|
@@ -36,62 +38,72 @@ const notEvaluated = (name) => ({
|
|
|
36
38
|
* target/source coordinate and therefore propagates as dry-run exit 2.
|
|
37
39
|
*/
|
|
38
40
|
export async function previewPetition(input) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
41
|
+
return withContractCarrierSession({ cwd: input.cwd, contractId: input.contractId, mode: "observe-only", signal: input.signal }, async (session) => {
|
|
42
|
+
if (session.target.status !== "available")
|
|
43
|
+
throw new FlowError("INTERNAL_STATE", session.target.reason);
|
|
44
|
+
const base = session.lifecycle.pins.base;
|
|
45
|
+
const target = { ref: session.lifecycle.pins.target.ref, branch: session.lifecycle.pins.target.ref.slice("refs/heads/".length) };
|
|
46
|
+
const context = { lifecycle: session.lifecycle, deliveryCwd: session.target.root, base, target };
|
|
47
|
+
const gates = [{ name: "contract", result: "pass" }];
|
|
48
|
+
const freshness = await gate("freshness", async () => {
|
|
49
|
+
if (!session.pins.target.head || context.base !== session.pins.target.head) {
|
|
50
|
+
throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} must renew onto ${context.target.branch} before petition`);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
gates.push(freshness);
|
|
54
|
+
const arc = freshness.result === "pass"
|
|
55
|
+
? await gate("arc", async () => {
|
|
56
|
+
if (context.lifecycle.phase !== "active")
|
|
57
|
+
throw new FlowError("NO_OPEN_ARC", "no active folded arc requires sealing");
|
|
58
|
+
})
|
|
59
|
+
: notEvaluated("arc");
|
|
60
|
+
gates.push(arc);
|
|
61
|
+
const state = await gate("state", () => {
|
|
62
|
+
assertFoldedPetitionable(input.contractId, context.lifecycle);
|
|
63
|
+
});
|
|
64
|
+
gates.push(state);
|
|
65
|
+
let seal;
|
|
66
|
+
if (freshness.result === "pass" && arc.result === "pass" && state.result === "pass") {
|
|
67
|
+
try {
|
|
68
|
+
const capture = await captureFoldedSeal(context.deliveryCwd, context.lifecycle, "HEAD");
|
|
69
|
+
seal = { name: "seal", result: "pass", detail: sealFileDetail(capture?.diffstat.files ?? 0) };
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
seal = { name: "seal", result: "failed", detail: failureDetail(error) };
|
|
73
|
+
}
|
|
60
74
|
}
|
|
61
|
-
|
|
62
|
-
seal =
|
|
75
|
+
else {
|
|
76
|
+
seal = notEvaluated("seal");
|
|
63
77
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
gates.push(seal);
|
|
69
|
-
const oathOwner = createOathGate();
|
|
70
|
-
const oath = await gate("oath", async () => {
|
|
71
|
-
const result = await oathOwner.evaluate({
|
|
72
|
-
cwd: input.cwd,
|
|
73
|
-
contractId: input.contractId,
|
|
74
|
-
input: {
|
|
78
|
+
gates.push(seal);
|
|
79
|
+
const oathOwner = createOathGate();
|
|
80
|
+
const oath = await gate("oath", async () => {
|
|
81
|
+
const result = await oathOwner.evaluate({
|
|
75
82
|
cwd: input.cwd,
|
|
76
83
|
contractId: input.contractId,
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
84
|
+
input: {
|
|
85
|
+
cwd: input.cwd,
|
|
86
|
+
contractId: input.contractId,
|
|
87
|
+
intent: "CLAIM",
|
|
88
|
+
title: `Petition ${input.contractId}`,
|
|
89
|
+
plea: `Settle contract ${input.contractId} through the petition pipeline.`,
|
|
90
|
+
oath: input.oath,
|
|
91
|
+
},
|
|
92
|
+
verificationPlan: [],
|
|
93
|
+
verificationCandidate: { head: "", tree: "" },
|
|
94
|
+
baseBranch: context.target.branch,
|
|
95
|
+
reviewNumber: 0,
|
|
96
|
+
});
|
|
97
|
+
if (!result.pass)
|
|
98
|
+
throw new Error(result.message);
|
|
85
99
|
});
|
|
86
|
-
|
|
87
|
-
|
|
100
|
+
gates.push(oath, notEvaluated("verification"), notEvaluated("review"));
|
|
101
|
+
return {
|
|
102
|
+
contract: input.contractId,
|
|
103
|
+
target: context.target.ref,
|
|
104
|
+
arc: { source: { arc: context.lifecycle.phase === "active" ? context.lifecycle.intent.arc : null, fence: context.lifecycle.pins.delivery } },
|
|
105
|
+
gates,
|
|
106
|
+
verdict: gates.some((item) => item.result === "failed") ? "would_fail" : "would_succeed",
|
|
107
|
+
};
|
|
88
108
|
});
|
|
89
|
-
gates.push(oath, notEvaluated("verification"), notEvaluated("review"));
|
|
90
|
-
return {
|
|
91
|
-
contract: input.contractId,
|
|
92
|
-
target: context.target.ref,
|
|
93
|
-
arc: { source: petitionSealSource(context.ledger.entries) },
|
|
94
|
-
gates,
|
|
95
|
-
verdict: gates.some((item) => item.result === "failed") ? "would_fail" : "would_succeed",
|
|
96
|
-
};
|
|
97
109
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export function inspectCurrentPetition(lifecycle) {
|
|
2
|
+
const petitionObjectId = lifecycle.petition.state === "none" ? undefined : lifecycle.petition.objectId;
|
|
3
|
+
const petitionIndex = petitionObjectId === undefined
|
|
4
|
+
? -1
|
|
5
|
+
: lifecycle.history.findIndex(({ objectId }) => objectId === petitionObjectId);
|
|
6
|
+
if (petitionIndex < 0)
|
|
7
|
+
return undefined;
|
|
8
|
+
const petition = lifecycle.history[petitionIndex]?.entry;
|
|
9
|
+
if (!petition || petition.kind !== "petition")
|
|
10
|
+
return undefined;
|
|
11
|
+
const preceding = lifecycle.history[petitionIndex - 1]?.entry;
|
|
12
|
+
const seal = preceding?.kind === "seal" && preceding.data.sealedBy === "petition"
|
|
13
|
+
? preceding
|
|
14
|
+
: undefined;
|
|
15
|
+
const verdictRow = lifecycle.verdict === null
|
|
16
|
+
? undefined
|
|
17
|
+
: lifecycle.history.find(({ objectId }) => objectId === lifecycle.verdict?.objectId);
|
|
18
|
+
const verdict = verdictRow?.entry.kind === "verdict" ? verdictRow.entry : undefined;
|
|
19
|
+
return {
|
|
20
|
+
petitionIndex,
|
|
21
|
+
petition,
|
|
22
|
+
candidateHead: seal?.data.newFence ?? lifecycle.pins.delivery,
|
|
23
|
+
...(seal ? { seal } : {}),
|
|
24
|
+
...(verdict ? { verdict } : {}),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export function classifyCurrentPetitionWindow(lifecycle, pins) {
|
|
28
|
+
if (lifecycle.phase !== "awaiting-verdict" && lifecycle.phase !== "approved")
|
|
29
|
+
return { status: "none" };
|
|
30
|
+
const current = inspectCurrentPetition(lifecycle);
|
|
31
|
+
if (!current)
|
|
32
|
+
return { status: "none" };
|
|
33
|
+
if (current.verdict?.data.result === "approved") {
|
|
34
|
+
return { status: "approved-awaiting-claim", entries: current };
|
|
35
|
+
}
|
|
36
|
+
if (current.verdict) {
|
|
37
|
+
return { status: "invalid", reason: "petitioned window has a non-approved verdict", entries: current };
|
|
38
|
+
}
|
|
39
|
+
const base = lifecycle.pins.base;
|
|
40
|
+
if (!base || !pins.target.head || !pins.delivery.head) {
|
|
41
|
+
return { status: "invalid", reason: "current petition coordinates do not resolve", entries: current };
|
|
42
|
+
}
|
|
43
|
+
if (base !== pins.target.head) {
|
|
44
|
+
return { status: "unjudged", coordinates: "target-drift", entries: current };
|
|
45
|
+
}
|
|
46
|
+
if (current.candidateHead !== pins.delivery.head) {
|
|
47
|
+
return { status: "unjudged", coordinates: "delivery-drift", entries: current };
|
|
48
|
+
}
|
|
49
|
+
return { status: "unjudged", coordinates: "fresh", entries: current };
|
|
50
|
+
}
|
|
@@ -1,148 +1,269 @@
|
|
|
1
1
|
import { FlowError } from "../../flow-error.js";
|
|
2
|
-
import { createGit, errorContainsAnyPattern, MISSING_HEAD_PATTERNS, wrapGitError } from "../../git/core.js";
|
|
3
2
|
import { parseCommandMarkdown } from "../command-markdown.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { commissionTarget, latestBase } from "../status/drift.js";
|
|
9
|
-
import { findContractWorktreePath } from "../worktree-path.js";
|
|
3
|
+
import { runLifecycle } from "../lifecycle-runner.js";
|
|
4
|
+
import { queueMembership, readQueueReadModel } from "./queue.js";
|
|
5
|
+
import { captureFoldedSeal } from "../seal.js";
|
|
6
|
+
import { currentLifecycleState } from "../repository-ledger/read-model.js";
|
|
10
7
|
import { assertCleanTrackedWorkingTree } from "../../git/worktree.js";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return (await git.raw(["rev-parse", "--verify", `${ref}^{commit}`])).trim();
|
|
15
|
-
}
|
|
16
|
-
catch (error) {
|
|
17
|
-
if (errorContainsAnyPattern(error, MISSING_HEAD_PATTERNS))
|
|
18
|
-
return null;
|
|
19
|
-
throw wrapGitError(`rev-parse ${ref}`, error, cwd);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
export async function loadPetitionPreflightContext(cwd, contractId) {
|
|
23
|
-
const ledger = await readLedger(cwd, contractId);
|
|
24
|
-
if (!ledger) {
|
|
25
|
-
throw new FlowError("INTERNAL_STATE", `contract ${contractId} not found or not active`);
|
|
26
|
-
}
|
|
27
|
-
const base = latestBase(ledger.entries);
|
|
28
|
-
if (!base)
|
|
29
|
-
throw new FlowError("INTERNAL_STATE", "ledger must contain bind entry");
|
|
30
|
-
const target = commissionTarget(ledger.entries);
|
|
31
|
-
if (!target) {
|
|
32
|
-
throw new FlowError("INTERNAL_STATE", `contract ${contractId} missing commission target`);
|
|
33
|
-
}
|
|
34
|
-
return {
|
|
35
|
-
ledger,
|
|
36
|
-
deliveryCwd: (await findContractWorktreePath(cwd, contractId)) ?? cwd,
|
|
37
|
-
base,
|
|
38
|
-
target,
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
export async function assertPetitionFresh(cwd, contractId, entries, base) {
|
|
42
|
-
const target = commissionTarget(entries);
|
|
43
|
-
if (!target) {
|
|
44
|
-
throw new FlowError("INTERNAL_STATE", `contract ${contractId} missing commission target`);
|
|
45
|
-
}
|
|
46
|
-
const head = await resolveCommit(cwd, target.ref);
|
|
47
|
-
if (!head) {
|
|
48
|
-
throw new FlowError("INTERNAL_STATE", `contract ${contractId} commission target ${target.ref} does not resolve`);
|
|
49
|
-
}
|
|
50
|
-
if (base !== head) {
|
|
51
|
-
throw new FlowError("INTERNAL_STATE", `contract ${contractId} must renew onto ${target.branch} before petition`, { facts: { kind: "needs_renew", contractId } });
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
export async function assertPetitionArcReady(deliveryCwd, contractId, entries) {
|
|
55
|
-
if (getMaxArc(entries) <= 0 || findOpenArc(entries))
|
|
56
|
-
return;
|
|
57
|
-
const loose = await receiptsAfterFence(deliveryCwd, entries);
|
|
58
|
-
if (loose.length > 0) {
|
|
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" } });
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
export function assertPetitionable(contractId, entries) {
|
|
63
|
-
const state = deriveContractState(entries);
|
|
64
|
-
// bound is legal for derived a0 (sealCurrentArc appends seal 0 before petition).
|
|
8
|
+
import { normalizeScopePattern } from "../scope.js";
|
|
9
|
+
export function assertFoldedPetitionable(contractId, model) {
|
|
10
|
+
const state = currentLifecycleState(model);
|
|
65
11
|
if (state.state !== "active" && state.state !== "bound") {
|
|
66
12
|
throw new FlowError("INTERNAL_STATE", `contract ${contractId} cannot petition from state ${state.state}`);
|
|
67
13
|
}
|
|
68
14
|
}
|
|
15
|
+
function nextSettlementSeat(read, target) {
|
|
16
|
+
let floor = 0;
|
|
17
|
+
for (const model of read.contracts) {
|
|
18
|
+
for (const { entry } of model.history) {
|
|
19
|
+
if (entry.kind !== "petition" || !("target" in entry.data) || entry.data.target !== target)
|
|
20
|
+
continue;
|
|
21
|
+
floor = Math.max(floor, entry.data.seat);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const seat = floor + 1;
|
|
25
|
+
if (!Number.isSafeInteger(seat) || seat <= 0)
|
|
26
|
+
throw new FlowError("INTERNAL_STATE", `settlement seats exhausted for ${target}`);
|
|
27
|
+
return seat;
|
|
28
|
+
}
|
|
69
29
|
export async function petitionContract(input) {
|
|
70
30
|
const at = new Date(input.nowMs).toISOString();
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
31
|
+
const patterns = input.intent === "claim" ? (input.appendScope ?? []).map(normalizeScopePattern) : [];
|
|
32
|
+
const generatedAmendment = "Scope extended by lifecycle command.";
|
|
33
|
+
const matchesAccepted = (history) => {
|
|
34
|
+
const petitionIndex = history.length - 1;
|
|
35
|
+
const accepted = history[petitionIndex]?.entry;
|
|
36
|
+
if (accepted?.kind !== "petition" || accepted.data.intent !== input.intent)
|
|
37
|
+
return false;
|
|
38
|
+
if (input.intent === "claim" && (accepted.data.intent !== "claim" || accepted.data.oath !== input.oath))
|
|
39
|
+
return false;
|
|
40
|
+
let prefixIndex = petitionIndex - 1;
|
|
41
|
+
const preceding = history[prefixIndex]?.entry;
|
|
42
|
+
if (preceding?.kind === "seal" && preceding.data.sealedBy === "petition")
|
|
43
|
+
prefixIndex -= 1;
|
|
44
|
+
const acceptedAmend = history[prefixIndex]?.entry;
|
|
45
|
+
const hasGeneratedAmend = acceptedAmend?.kind === "amend" && acceptedAmend.data.amendment === generatedAmendment;
|
|
46
|
+
if (patterns.length === 0)
|
|
47
|
+
return !hasGeneratedAmend;
|
|
48
|
+
return hasGeneratedAmend
|
|
49
|
+
&& JSON.stringify(acceptedAmend.data.scopeDelta ?? null) === JSON.stringify({ add: patterns });
|
|
50
|
+
};
|
|
51
|
+
const execution = await runLifecycle({
|
|
77
52
|
cwd: input.cwd,
|
|
78
|
-
renderCwd: deliveryCwd,
|
|
79
|
-
contractId: input.contractId,
|
|
80
|
-
patterns: input.intent === "claim" ? input.appendScope : undefined,
|
|
81
|
-
actor: input.actor,
|
|
82
|
-
nowMs: input.nowMs,
|
|
83
|
-
});
|
|
84
|
-
const sealed = await sealCurrentArc({
|
|
85
|
-
cwd: deliveryCwd,
|
|
86
|
-
renderCwd: deliveryCwd,
|
|
87
53
|
contractId: input.contractId,
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
intent: "claim",
|
|
105
|
-
oath: input.oath,
|
|
106
|
-
target: target.ref,
|
|
107
|
-
seat: allocated.seat,
|
|
54
|
+
mutation: { verb: "petition", matchesAccepted: (transaction) => matchesAccepted(transaction.entries.map((entry, index) => ({ objectId: transaction.entryObjectIds[index], entry }))) },
|
|
55
|
+
signal: input.signal,
|
|
56
|
+
admit: async (session, replay) => {
|
|
57
|
+
if (replay.state === "verb-committed")
|
|
58
|
+
return { lifecycle: session.lifecycle };
|
|
59
|
+
if (session.target.status !== "available")
|
|
60
|
+
throw new FlowError("INTERNAL_STATE", session.target.reason);
|
|
61
|
+
const deliveryCwd = session.target.root;
|
|
62
|
+
const base = session.lifecycle.pins.base;
|
|
63
|
+
const target = session.lifecycle.pins.target.ref;
|
|
64
|
+
await assertCleanTrackedWorkingTree(deliveryCwd);
|
|
65
|
+
if (!session.pins.target.head)
|
|
66
|
+
throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} commission target ${session.pins.target.ref} does not resolve`);
|
|
67
|
+
if (base !== session.pins.target.head) {
|
|
68
|
+
const branch = target.slice("refs/heads/".length);
|
|
69
|
+
throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} must renew onto ${branch} before petition`, { facts: { kind: "needs_renew", contractId: input.contractId } });
|
|
108
70
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
71
|
+
assertFoldedPetitionable(input.contractId, session.lifecycle);
|
|
72
|
+
return { lifecycle: session.lifecycle, deliveryCwd };
|
|
73
|
+
},
|
|
74
|
+
prepare: async (admission) => {
|
|
75
|
+
if (admission.replay.state === "verb-committed") {
|
|
76
|
+
const transaction = admission.replay.transaction;
|
|
77
|
+
const accepted = transaction.entries.at(-1);
|
|
78
|
+
if (!accepted || accepted.kind !== "petition")
|
|
79
|
+
throw new FlowError("INTERNAL_STATE", `accepted transaction ${transaction.transactionObjectId} is not a petition`);
|
|
80
|
+
let prefixIndex = transaction.entries.length - 2;
|
|
81
|
+
const acceptedSeal = transaction.entries[prefixIndex];
|
|
82
|
+
let capture = null;
|
|
83
|
+
if (acceptedSeal?.kind === "seal" && acceptedSeal.data.sealedBy === "petition") {
|
|
84
|
+
capture = {
|
|
85
|
+
arc: acceptedSeal.data.arc,
|
|
86
|
+
oldFence: acceptedSeal.data.oldFence,
|
|
87
|
+
newFence: acceptedSeal.data.newFence,
|
|
88
|
+
receipts: [...acceptedSeal.data.receipts],
|
|
89
|
+
diffstat: { ...acceptedSeal.data.diffstat },
|
|
90
|
+
scopeAudit: { violations: [...acceptedSeal.data.scopeAudit.violations] },
|
|
91
|
+
scopeViolationEvidence: [],
|
|
92
|
+
};
|
|
93
|
+
prefixIndex -= 1;
|
|
94
|
+
}
|
|
95
|
+
const acceptedAmend = transaction.entries[prefixIndex];
|
|
96
|
+
if (patterns.length > 0) {
|
|
97
|
+
if (acceptedAmend?.kind !== "amend" || acceptedAmend.data.amendment !== generatedAmendment) {
|
|
98
|
+
throw new FlowError("INTERNAL_STATE", `accepted petition transaction ${transaction.transactionObjectId} is missing its scope amendment`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
additions: [],
|
|
103
|
+
capture,
|
|
104
|
+
seat: accepted.data.seat,
|
|
105
|
+
targetHead: accepted.data.intent === "claim" ? accepted.data.targetHead : admission.pins.target.head ?? "",
|
|
106
|
+
existing: { transactionObjectId: transaction.transactionObjectId },
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
const scopeEntry = patterns.length === 0 ? null : {
|
|
110
|
+
v: 1,
|
|
111
|
+
kind: "amend",
|
|
112
|
+
contract: input.contractId,
|
|
113
|
+
at,
|
|
114
|
+
actor: input.actor,
|
|
115
|
+
data: { amendment: generatedAmendment, scopeDelta: { add: patterns } },
|
|
113
116
|
};
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
117
|
+
const deliveryCwd = admission.business.deliveryCwd;
|
|
118
|
+
if (!deliveryCwd)
|
|
119
|
+
throw new FlowError("INTERNAL_STATE", `petition ${input.contractId} is missing its delivery worktree`);
|
|
120
|
+
const lifecycle = admission.business.lifecycle;
|
|
121
|
+
const followsRenewWithoutInterveningFact = lifecycle.phase === "active"
|
|
122
|
+
&& lifecycle.seal === null
|
|
123
|
+
&& lifecycle.history.at(-1)?.entry.kind === "renew";
|
|
124
|
+
const capture = followsRenewWithoutInterveningFact
|
|
125
|
+
? null
|
|
126
|
+
: await captureFoldedSeal(deliveryCwd, lifecycle, "HEAD", patterns);
|
|
127
|
+
let sealEntry = null;
|
|
128
|
+
if (capture) {
|
|
129
|
+
const { scopeViolationEvidence: _evidence, ...durableCapture } = capture;
|
|
130
|
+
sealEntry = {
|
|
131
|
+
v: 2,
|
|
132
|
+
kind: "seal",
|
|
133
|
+
contract: input.contractId,
|
|
134
|
+
at,
|
|
135
|
+
actor: "engine",
|
|
136
|
+
data: { ...durableCapture, sealedBy: "petition" },
|
|
137
|
+
};
|
|
130
138
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
139
|
+
const additions = scopeEntry
|
|
140
|
+
? sealEntry ? [scopeEntry, sealEntry] : [scopeEntry]
|
|
141
|
+
: sealEntry ? [sealEntry] : [];
|
|
142
|
+
const targetHead = admission.pins.target.head;
|
|
143
|
+
if (!targetHead)
|
|
144
|
+
throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} commission target does not resolve`);
|
|
145
|
+
return {
|
|
146
|
+
additions,
|
|
147
|
+
capture,
|
|
148
|
+
seat: nextSettlementSeat(admission.repository, admission.pins.target.ref),
|
|
149
|
+
targetHead,
|
|
150
|
+
};
|
|
151
|
+
},
|
|
152
|
+
revalidate: async ({ session, admission, prepared }) => {
|
|
153
|
+
if (prepared.existing) {
|
|
154
|
+
return session.repository.topology.nodes.some((node) => node.objectId === prepared.existing.transactionObjectId)
|
|
155
|
+
? undefined
|
|
156
|
+
: { code: "PETITION_STATE_CHANGED", message: "accepted petition changed during replay preparation" };
|
|
157
|
+
}
|
|
158
|
+
if (session.target.status !== "available" || session.target.root !== admission.business.deliveryCwd)
|
|
159
|
+
return { code: "DELIVERY_WORKTREE_CHANGED", message: "registered delivery worktree changed during petition preparation" };
|
|
160
|
+
await assertCleanTrackedWorkingTree(session.target.root);
|
|
161
|
+
if (input.reviewPersistence && session.pins.delivery.head !== input.reviewPersistence.reviewedHead) {
|
|
162
|
+
return {
|
|
163
|
+
code: "REVIEWED_DELIVERY_CHANGED",
|
|
164
|
+
message: "delivery HEAD changed after independent review",
|
|
165
|
+
pinned: { deliveryHead: input.reviewPersistence.reviewedHead },
|
|
166
|
+
current: { deliveryHead: session.pins.delivery.head },
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
const base = session.lifecycle.pins.base;
|
|
170
|
+
if (base !== session.pins.target.head)
|
|
171
|
+
return { code: "PETITION_TARGET_CHANGED", message: "petition target/base facts changed during preparation" };
|
|
172
|
+
assertFoldedPetitionable(input.contractId, session.lifecycle);
|
|
173
|
+
return undefined;
|
|
174
|
+
},
|
|
175
|
+
effect: async ({ context, prepared }) => {
|
|
176
|
+
let reviewHistoryPath;
|
|
177
|
+
if (!prepared.existing)
|
|
178
|
+
await input.afterSeat?.(prepared.seat);
|
|
179
|
+
const data = input.intent === "claim"
|
|
180
|
+
? {
|
|
181
|
+
intent: "claim",
|
|
182
|
+
oath: input.oath,
|
|
183
|
+
target: context.session.pins.target.ref,
|
|
184
|
+
targetHead: prepared.targetHead,
|
|
185
|
+
seat: prepared.seat,
|
|
186
|
+
}
|
|
187
|
+
: {
|
|
188
|
+
intent: "forfeit",
|
|
189
|
+
target: context.session.pins.target.ref,
|
|
190
|
+
seat: prepared.seat,
|
|
191
|
+
};
|
|
192
|
+
const entry = {
|
|
193
|
+
v: 2,
|
|
194
|
+
kind: "petition",
|
|
195
|
+
contract: input.contractId,
|
|
196
|
+
at,
|
|
197
|
+
actor: input.actor,
|
|
198
|
+
data,
|
|
199
|
+
};
|
|
200
|
+
const additions = prepared.additions.length === 0
|
|
201
|
+
? [entry]
|
|
202
|
+
: prepared.additions[0].kind === "seal"
|
|
203
|
+
? [prepared.additions[0], entry]
|
|
204
|
+
: prepared.additions.length === 1
|
|
205
|
+
? [prepared.additions[0], entry]
|
|
206
|
+
: [prepared.additions[0], prepared.additions[1], entry];
|
|
207
|
+
const result = prepared.existing
|
|
208
|
+
? await context.replayCommitted({ transactionObjectId: prepared.existing.transactionObjectId })
|
|
209
|
+
: await context.publishAuthoritative({
|
|
210
|
+
verb: "petition",
|
|
211
|
+
admittedLedgerHead: context.session.lifecycle.observedLedgerHead,
|
|
212
|
+
acceptedLifecycleTail: context.session.lifecycle.history.at(-1).objectId,
|
|
213
|
+
entries: additions,
|
|
214
|
+
});
|
|
215
|
+
if (result.status === "indeterminate") {
|
|
216
|
+
return { kind: "precommit-effects-only", authoritativeHead: result.currentHead ?? context.session.lifecycle.observedLedgerHead };
|
|
217
|
+
}
|
|
218
|
+
if (result.status !== "committed") {
|
|
219
|
+
const authoritativeHead = result.currentHead ?? context.session.lifecycle.observedLedgerHead;
|
|
220
|
+
return { kind: "refused-before-effects", authoritativeHead, refusal: { code: "LEDGER_CAS_CONFLICT", message: `contract ${input.contractId} changed during petition` } };
|
|
221
|
+
}
|
|
222
|
+
if (input.reviewPersistence) {
|
|
223
|
+
await context.journal.run({
|
|
224
|
+
effect: "review-response-persistence",
|
|
225
|
+
...(context.journal.all().some((row) => row.effect === "ledger-cas" && row.status === "succeeded")
|
|
226
|
+
? { dependency: `${input.contractId}:${result.head}:ledger-cas` }
|
|
227
|
+
: {}),
|
|
228
|
+
run: async () => {
|
|
229
|
+
const persisted = await input.reviewPersistence.persist();
|
|
230
|
+
reviewHistoryPath = persisted.responsePath;
|
|
231
|
+
return { artifactId: persisted.artifactId, responsePath: persisted.responsePath };
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
let position = 0;
|
|
236
|
+
await context.journal.run({
|
|
237
|
+
effect: "settlement-queue-observation",
|
|
238
|
+
...(context.journal.all().some((row) => row.effect === "ledger-cas" && row.status === "succeeded")
|
|
239
|
+
? { dependency: `${input.contractId}:${result.head}:ledger-cas` }
|
|
240
|
+
: {}),
|
|
241
|
+
run: async () => {
|
|
242
|
+
const model = await readQueueReadModel(context.session.stableRoot);
|
|
243
|
+
position = queueMembership(model, input.contractId)?.position ?? 0;
|
|
244
|
+
if (position <= 0)
|
|
245
|
+
throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} missing target-local queue position after petition`);
|
|
246
|
+
return { position };
|
|
247
|
+
},
|
|
248
|
+
});
|
|
249
|
+
return {
|
|
250
|
+
kind: "committed",
|
|
251
|
+
commit: result,
|
|
252
|
+
entryKind: "petition",
|
|
253
|
+
value: {
|
|
254
|
+
position,
|
|
255
|
+
seat: prepared.seat,
|
|
256
|
+
reviewHistoryPath,
|
|
257
|
+
sealed: prepared.capture ? { ...prepared.capture, sealedBy: "petition", ledgerHead: result.transaction.entryObjectIds[result.transaction.entries.findIndex((entry) => entry.kind === "seal")] } : undefined,
|
|
258
|
+
},
|
|
259
|
+
};
|
|
260
|
+
},
|
|
261
|
+
});
|
|
262
|
+
if (execution.kind === "noncommitted")
|
|
263
|
+
return execution.outcome;
|
|
264
|
+
if (execution.kind === "contained")
|
|
265
|
+
return { ...execution.outcome, contractId: input.contractId };
|
|
266
|
+
return { ...execution.outcome, contractId: input.contractId, ...execution.payload, carrier: execution.carrier };
|
|
146
267
|
}
|
|
147
268
|
export function buildPetitionClaimInput(input) {
|
|
148
269
|
const parts = parseCommandMarkdown(input.stdin ?? "");
|