@astrosheep/keiyaku 2.9.8 → 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 +10 -7
- 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 +242 -79
- 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 +65 -29
- package/build/cli/render/projection-history.js +64 -0
- package/build/cli/render/shared.js +23 -9
- package/build/cli/render/status.js +13 -35
- package/build/cli/render/task.js +51 -26
- package/build/cli/render/terminal-failure.js +3 -1
- package/build/cli/render/wait.js +45 -69
- package/build/cli/subagent-guard.js +1 -1
- package/build/cli/types.js +1 -1
- package/build/config/env-keys.js +3 -1
- package/build/config/env.js +24 -1
- 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 +13 -6
- package/build/core/projection/index.js +5 -4
- package/build/core/projection/projection-activity.js +29 -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 +35 -337
- 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 +125 -127
- 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
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import * as fs from "node:fs/promises";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
5
|
+
import { DatabaseSync } from "node:sqlite";
|
|
6
|
+
import { FlowError } from "../flow-error.js";
|
|
7
|
+
import { createGit, errorContainsAnyPattern, isFullGitObjectId, MISSING_HEAD_PATTERNS, runGitProcess, wrapGitError } from "../git/core.js";
|
|
8
|
+
import { KEIYAKU_FILE } from "../keiyaku.js";
|
|
9
|
+
import { renderCurrentContractView } from "./contract-view.js";
|
|
10
|
+
import { isCommissionId } from "./ids.js";
|
|
11
|
+
import { activeWorkspaceHereContracts } from "./repository-ledger/inventory.js";
|
|
12
|
+
import { lookupCurrentContract, readRepositoryLifecycle, } from "./repository-ledger/read-model.js";
|
|
13
|
+
import { REPOSITORY_LEDGER_REF, } from "./repository-ledger/codec.js";
|
|
14
|
+
import { resolveContractCarrierTarget, stableRepoRoot, } from "./worktree-path.js";
|
|
15
|
+
import { installCarrierRuntimePorts } from "./contract-carrier-runtime.js";
|
|
16
|
+
export { convergeContractCarrier, reconcileContractCarrierProjection, withContractCarrierSession } from "./contract-carrier-runtime.js";
|
|
17
|
+
const BUSY_SLICE_MS = 25;
|
|
18
|
+
const CARRIER_CONTRACT = /^id: (\S+)$/m;
|
|
19
|
+
const CARRIER_PROVENANCE = /^ledger: (\S+)$/m;
|
|
20
|
+
export function acceptedBind(lifecycle) {
|
|
21
|
+
for (const { entry } of lifecycle.history) {
|
|
22
|
+
if (entry.kind === "bind")
|
|
23
|
+
return entry;
|
|
24
|
+
}
|
|
25
|
+
throw new FlowError("INTERNAL_STATE", `contract ${lifecycle.contract} lifecycle is missing bind entry`);
|
|
26
|
+
}
|
|
27
|
+
function diagnostic(error) {
|
|
28
|
+
return error instanceof Error ? error.message : String(error);
|
|
29
|
+
}
|
|
30
|
+
export async function projectRenewDeliveryWorktree(target, oldHead, newHead) {
|
|
31
|
+
if (target.status !== "available")
|
|
32
|
+
throw new FlowError("CARRIER_TARGET_UNAVAILABLE", target.reason);
|
|
33
|
+
await projectRenewDeliveryRoot(target.root, oldHead, newHead);
|
|
34
|
+
}
|
|
35
|
+
async function projectRenewDeliveryRoot(root, oldHead, newHead) {
|
|
36
|
+
const update = await runGitProcess(root, ["read-tree", "-m", "-u", oldHead, newHead]);
|
|
37
|
+
if (update.status !== 0 || update.error) {
|
|
38
|
+
throw new FlowError("INTERNAL_STATE", update.stderr.trim() || update.error?.message || `read-tree exited ${String(update.status)}`, { cause: update.error });
|
|
39
|
+
}
|
|
40
|
+
const [head, status] = await Promise.all([
|
|
41
|
+
runGitProcess(root, ["rev-parse", "--verify", "HEAD^{commit}"]),
|
|
42
|
+
runGitProcess(root, ["status", "--porcelain=v1", "--untracked-files=no"]),
|
|
43
|
+
]);
|
|
44
|
+
if (head.status !== 0 || head.error || head.stdout.trim() !== newHead || status.status !== 0 || status.error || status.stdout !== "") {
|
|
45
|
+
throw new FlowError("INTERNAL_STATE", "delivery worktree did not align to the committed renew head");
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/** Reconstruct the folded delivery projection without creating lifecycle facts. */
|
|
49
|
+
export async function reconcileFoldedDeliveryWorktree(input) {
|
|
50
|
+
const renew = [...input.lifecycle.history].reverse().find(({ entry }) => (entry.kind === "renew" && entry.data.newHead === input.lifecycle.pins.delivery));
|
|
51
|
+
if (!renew || renew.entry.kind !== "renew")
|
|
52
|
+
return "already-present";
|
|
53
|
+
const [index, worktree] = await Promise.all([
|
|
54
|
+
runGitProcess(input.targetRoot, ["write-tree"]),
|
|
55
|
+
runGitProcess(input.targetRoot, ["diff-files", "--quiet"]),
|
|
56
|
+
]);
|
|
57
|
+
if (index.status !== 0 || index.error) {
|
|
58
|
+
throw new FlowError("INTERNAL_STATE", index.stderr.trim() || index.error?.message || "cannot inspect delivery worktree index", { cause: index.error });
|
|
59
|
+
}
|
|
60
|
+
const indexTree = index.stdout.trim();
|
|
61
|
+
const newTree = await resolveCommitTree(input.targetRoot, renew.entry.data.newHead);
|
|
62
|
+
if (indexTree === newTree)
|
|
63
|
+
return "already-present";
|
|
64
|
+
const oldTree = await resolveCommitTree(input.targetRoot, renew.entry.data.oldHead);
|
|
65
|
+
if (indexTree !== oldTree || worktree.status !== 0 || worktree.error) {
|
|
66
|
+
throw new FlowError("INTERNAL_STATE", `delivery worktree cannot be safely reconstructed for accepted renew ${renew.objectId}`);
|
|
67
|
+
}
|
|
68
|
+
await projectRenewDeliveryRoot(input.targetRoot, renew.entry.data.oldHead, renew.entry.data.newHead);
|
|
69
|
+
return "created";
|
|
70
|
+
}
|
|
71
|
+
async function resolveCommitTree(cwd, commit) {
|
|
72
|
+
const result = await runGitProcess(cwd, ["rev-parse", "--verify", `${commit}^{tree}`]);
|
|
73
|
+
if (result.status !== 0 || result.error) {
|
|
74
|
+
throw new FlowError("INTERNAL_STATE", result.stderr.trim() || result.error?.message || `cannot resolve tree for ${commit}`, { cause: result.error });
|
|
75
|
+
}
|
|
76
|
+
return result.stdout.trim();
|
|
77
|
+
}
|
|
78
|
+
export function observedLedgerHead(observations) {
|
|
79
|
+
const observation = observations.find((item) => item.ref === REPOSITORY_LEDGER_REF)?.observed;
|
|
80
|
+
return observation?.status === "present" ? observation.objectId : null;
|
|
81
|
+
}
|
|
82
|
+
function isBusy(error) {
|
|
83
|
+
const code = error?.code;
|
|
84
|
+
return code === "SQLITE_BUSY" || code === "SQLITE_LOCKED" || /database is (?:locked|busy)/i.test(diagnostic(error));
|
|
85
|
+
}
|
|
86
|
+
function throwCancelled() {
|
|
87
|
+
throw new FlowError("CARRIER_GATE_CANCELLED", "contract carrier operation cancelled before durable effects");
|
|
88
|
+
}
|
|
89
|
+
export function checkCancellation(signal) {
|
|
90
|
+
if (signal?.aborted)
|
|
91
|
+
throwCancelled();
|
|
92
|
+
}
|
|
93
|
+
export async function acquireGate(stableRoot, signal) {
|
|
94
|
+
checkCancellation(signal);
|
|
95
|
+
const directory = path.join(stableRoot, ".keiyaku");
|
|
96
|
+
await fs.mkdir(directory, { recursive: true });
|
|
97
|
+
checkCancellation(signal);
|
|
98
|
+
let database;
|
|
99
|
+
try {
|
|
100
|
+
database = new DatabaseSync(path.join(directory, "contract-carrier.sqlite"));
|
|
101
|
+
database.exec(`PRAGMA busy_timeout = ${BUSY_SLICE_MS}`);
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
throw new FlowError("CARRIER_GATE_UNAVAILABLE", "contract carrier gate is unavailable", { cause: error });
|
|
105
|
+
}
|
|
106
|
+
for (;;) {
|
|
107
|
+
checkCancellation(signal);
|
|
108
|
+
try {
|
|
109
|
+
database.exec("BEGIN IMMEDIATE");
|
|
110
|
+
return database;
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
if (!isBusy(error)) {
|
|
114
|
+
database.close();
|
|
115
|
+
throw new FlowError("CARRIER_GATE_UNAVAILABLE", "contract carrier gate is unavailable", { cause: error });
|
|
116
|
+
}
|
|
117
|
+
await delay(BUSY_SLICE_MS, undefined, signal ? { signal } : undefined).catch(() => throwCancelled());
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
export function releaseGate(database) {
|
|
122
|
+
try {
|
|
123
|
+
database.exec("ROLLBACK");
|
|
124
|
+
}
|
|
125
|
+
finally {
|
|
126
|
+
database.close();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/** Serialize cross-process committed replay decisions without exposing gate coordinates. */
|
|
130
|
+
export async function withContractReplayGate(cwd, signal, action) {
|
|
131
|
+
const stableRoot = await stableRepoRoot(cwd);
|
|
132
|
+
const gate = await acquireGate(stableRoot, signal);
|
|
133
|
+
try {
|
|
134
|
+
return await action(stableRoot);
|
|
135
|
+
}
|
|
136
|
+
finally {
|
|
137
|
+
releaseGate(gate);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
export async function readCarrierIdentity(target) {
|
|
141
|
+
try {
|
|
142
|
+
return parseCarrierIdentity(await fs.readFile(target, "utf8"));
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
export function parseCarrierIdentity(source) {
|
|
149
|
+
const contractId = source.match(CARRIER_CONTRACT)?.[1] ?? "";
|
|
150
|
+
const ledgerHead = source.match(CARRIER_PROVENANCE)?.[1] ?? "";
|
|
151
|
+
return isCommissionId(contractId) && isFullGitObjectId(ledgerHead) ? { contractId, ledgerHead } : null;
|
|
152
|
+
}
|
|
153
|
+
export function carrierMatches(identity, contractId, ledgerHead) {
|
|
154
|
+
return identity?.contractId === contractId && identity.ledgerHead === ledgerHead;
|
|
155
|
+
}
|
|
156
|
+
export async function retireTerminalHereCarrier(lifecycle, target) {
|
|
157
|
+
if (target.status !== "available" || target.workspace !== "here")
|
|
158
|
+
return "already-present";
|
|
159
|
+
let source;
|
|
160
|
+
try {
|
|
161
|
+
source = await fs.readFile(target.path, "utf8");
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
165
|
+
return "already-present";
|
|
166
|
+
throw error;
|
|
167
|
+
}
|
|
168
|
+
if (parseCarrierIdentity(source)?.contractId !== lifecycle.contract)
|
|
169
|
+
return "already-present";
|
|
170
|
+
try {
|
|
171
|
+
await fs.unlink(target.path);
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
175
|
+
return "already-present";
|
|
176
|
+
throw error;
|
|
177
|
+
}
|
|
178
|
+
return "retired";
|
|
179
|
+
}
|
|
180
|
+
export async function publishCarrier(input) {
|
|
181
|
+
if (input.target.status === "unavailable") {
|
|
182
|
+
return { status: "target-unavailable", ledgerHead: input.lifecycle.observedLedgerHead, reason: input.target.reason };
|
|
183
|
+
}
|
|
184
|
+
if (input.target.workspace === "here") {
|
|
185
|
+
const occupants = activeWorkspaceHereContracts(input.repository);
|
|
186
|
+
if (occupants.length !== 1 || occupants[0] !== input.contractId) {
|
|
187
|
+
return {
|
|
188
|
+
status: "failed",
|
|
189
|
+
ledgerHead: input.lifecycle.observedLedgerHead,
|
|
190
|
+
target: input.target.path,
|
|
191
|
+
cause: `workspace-here carrier occupancy does not admit ${input.contractId}`,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (carrierMatches(await readCarrierIdentity(input.target.path), input.contractId, input.lifecycle.observedLedgerHead)) {
|
|
196
|
+
return { status: "current", ledgerHead: input.lifecycle.observedLedgerHead, target: input.target.path };
|
|
197
|
+
}
|
|
198
|
+
const directory = path.dirname(input.target.path);
|
|
199
|
+
const temporary = path.join(directory, `.${path.basename(KEIYAKU_FILE)}.${process.pid}.${randomUUID()}.tmp`);
|
|
200
|
+
try {
|
|
201
|
+
await fs.mkdir(directory, { recursive: true });
|
|
202
|
+
await fs.writeFile(temporary, renderCurrentContractView(input.lifecycle), { encoding: "utf8", flag: "wx" });
|
|
203
|
+
if ((await resolveCommitOrNull(input.stableRoot, REPOSITORY_LEDGER_REF)) !== input.lifecycle.observedLedgerHead) {
|
|
204
|
+
await fs.unlink(temporary);
|
|
205
|
+
return { status: "failed", ledgerHead: input.lifecycle.observedLedgerHead, target: input.target.path, cause: "authoritative ledger head changed before carrier replacement" };
|
|
206
|
+
}
|
|
207
|
+
await (input.contractViewWriter?.replace ?? fs.rename)(temporary, input.target.path);
|
|
208
|
+
const [activeHead, carrierIdentity] = await Promise.all([
|
|
209
|
+
resolveCommitOrNull(input.stableRoot, REPOSITORY_LEDGER_REF),
|
|
210
|
+
readCarrierIdentity(input.target.path),
|
|
211
|
+
]);
|
|
212
|
+
if (activeHead !== input.lifecycle.observedLedgerHead ||
|
|
213
|
+
!carrierMatches(carrierIdentity, input.contractId, input.lifecycle.observedLedgerHead)) {
|
|
214
|
+
return { status: "failed", ledgerHead: input.lifecycle.observedLedgerHead, target: input.target.path, cause: "carrier post-check did not prove the published contract identity and ledger head" };
|
|
215
|
+
}
|
|
216
|
+
return { status: "materialized", ledgerHead: input.lifecycle.observedLedgerHead, target: input.target.path };
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
await fs.unlink(temporary).catch(() => undefined);
|
|
220
|
+
return { status: "failed", ledgerHead: input.lifecycle.observedLedgerHead, target: input.target.path, cause: diagnostic(error) };
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
export async function observe(stableRoot, contractId) {
|
|
224
|
+
const repository = await readRepositoryLifecycle(stableRoot);
|
|
225
|
+
const outcome = lookupCurrentContract(repository, contractId);
|
|
226
|
+
if (outcome.status !== "healthy") {
|
|
227
|
+
const detail = outcome.status === "absent" ? "not found" : `unavailable (${outcome.status})`;
|
|
228
|
+
const diagnosticDetail = outcome.status === "absent"
|
|
229
|
+
? ""
|
|
230
|
+
: `: ${outcome.diagnostics.map(({ code, detail: message }) => `${code}: ${message}`).join("; ")}`;
|
|
231
|
+
throw new FlowError("INTERNAL_STATE", `contract ${contractId} is ${detail} in the repository lifecycle read model${diagnosticDetail}`, {
|
|
232
|
+
...(outcome.status === "absent" ? {} : { facts: { kind: "repository_lifecycle_disease", contractId, status: outcome.status, diagnostics: outcome.diagnostics.map((item) => ({ ...item })) } }),
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
const lifecycle = outcome.model;
|
|
236
|
+
const bind = acceptedBind(lifecycle);
|
|
237
|
+
const target = await resolveContractCarrierTarget(stableRoot, contractId, bind);
|
|
238
|
+
const targetRef = lifecycle.pins.target.ref;
|
|
239
|
+
const deliveryRef = lifecycle.resources.deliveryRef;
|
|
240
|
+
const [targetHead, deliveryHead] = await Promise.all([
|
|
241
|
+
resolveCommitOrNull(stableRoot, targetRef),
|
|
242
|
+
resolveCommitOrNull(stableRoot, deliveryRef),
|
|
243
|
+
]);
|
|
244
|
+
return { repository, lifecycle, target, pins: { ledgerHead: lifecycle.observedLedgerHead, target: { ref: targetRef, head: targetHead }, delivery: { ref: deliveryRef, head: deliveryHead } } };
|
|
245
|
+
}
|
|
246
|
+
export async function resolveCommitOrNull(cwd, ref) {
|
|
247
|
+
try {
|
|
248
|
+
return (await createGit(cwd).raw(["rev-parse", "--verify", `${ref}^{commit}`])).trim();
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
if (errorContainsAnyPattern(error, MISSING_HEAD_PATTERNS))
|
|
252
|
+
return null;
|
|
253
|
+
throw wrapGitError(`rev-parse ${ref}`, error, cwd);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
installCarrierRuntimePorts({
|
|
257
|
+
acceptedBind,
|
|
258
|
+
acquireGate,
|
|
259
|
+
carrierMatches,
|
|
260
|
+
checkCancellation,
|
|
261
|
+
observe,
|
|
262
|
+
observedLedgerHead,
|
|
263
|
+
projectRenewDeliveryWorktree,
|
|
264
|
+
publishCarrier,
|
|
265
|
+
readCarrierIdentity,
|
|
266
|
+
reconcileFoldedDeliveryWorktree,
|
|
267
|
+
releaseGate,
|
|
268
|
+
resolveCommitOrNull,
|
|
269
|
+
retireTerminalHereCarrier,
|
|
270
|
+
});
|
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
import * as fs from "node:fs/promises";
|
|
2
|
-
import * as path from "node:path";
|
|
3
|
-
import { FlowError } from "../flow-error.js";
|
|
4
1
|
import { shortCommitHash } from "../git/commits.js";
|
|
5
|
-
import {
|
|
6
|
-
import { readLedger } from "./ledger.js";
|
|
7
|
-
import { deriveContractState } from "./status/lifecycle.js";
|
|
2
|
+
import { effectiveCriteria } from "./lifecycle-history.js";
|
|
8
3
|
import { renderScopeFragment } from "./scope.js";
|
|
4
|
+
import { currentLifecycleState, } from "./repository-ledger/read-model.js";
|
|
5
|
+
export { currentLifecycleState } from "./repository-ledger/read-model.js";
|
|
9
6
|
const VERB_COLUMN_WIDTH = 11;
|
|
10
7
|
const TIME_COLUMN_WIDTH = 6;
|
|
11
8
|
const ACTOR_COLUMN_WIDTH = 11;
|
|
@@ -73,7 +70,7 @@ function renderSealFacts(entry) {
|
|
|
73
70
|
}
|
|
74
71
|
function bootstrapTimeoutMs(entries) {
|
|
75
72
|
const bind = entries.find((entry) => entry.kind === "bind");
|
|
76
|
-
return bind
|
|
73
|
+
return bind && "bootstrap" in bind.data ? bind.data.bootstrap?.timeoutMs : undefined;
|
|
77
74
|
}
|
|
78
75
|
function renderBootstrapFacts(entry, entries) {
|
|
79
76
|
const duration = `duration ${entry.data.durationMs}ms`;
|
|
@@ -114,7 +111,7 @@ function renderEventFacts(entry, entries) {
|
|
|
114
111
|
return [
|
|
115
112
|
joinFactClusters([
|
|
116
113
|
`workspace ${entry.data.workspace}`,
|
|
117
|
-
entry.data.
|
|
114
|
+
entry.data.workspace === "worktree" ? `place ${entry.data.place}` : null,
|
|
118
115
|
`base ${shortCommitHash(entry.data.base)}`,
|
|
119
116
|
`scope ${entry.data.scope.length}`,
|
|
120
117
|
entry.data.after && entry.data.after.length > 0 ? `after ${entry.data.after.length}` : null,
|
|
@@ -147,7 +144,7 @@ function renderEventFacts(entry, entries) {
|
|
|
147
144
|
joinFactClusters([
|
|
148
145
|
entry.data.reason === "manual" ? "裁决人" : entry.data.reason,
|
|
149
146
|
entry.data.note ? `「${compactWhitespace(entry.data.note)}」` : null,
|
|
150
|
-
|
|
147
|
+
`by ${entry.data.actor}`,
|
|
151
148
|
]),
|
|
152
149
|
];
|
|
153
150
|
}
|
|
@@ -212,21 +209,9 @@ export function renderVerticalLog(input) {
|
|
|
212
209
|
}
|
|
213
210
|
return renderLedgerRows(input.entries).join("\n");
|
|
214
211
|
}
|
|
215
|
-
/**
|
|
216
|
-
* Sole post-mutation KEIYAKU.md refresh: read the authoritative ledger at
|
|
217
|
-
* ledgerCwd, then write the projection at renderCwd. ledgerCwd and renderCwd
|
|
218
|
-
* are distinct coordinates (hub vs linked delivery worktree).
|
|
219
|
-
*/
|
|
220
|
-
export async function refreshContractView(input) {
|
|
221
|
-
const ledger = await readLedger(input.ledgerCwd, input.contractId);
|
|
222
|
-
if (!ledger) {
|
|
223
|
-
throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} active ledger disappeared before render`);
|
|
224
|
-
}
|
|
225
|
-
await fs.writeFile(path.join(input.renderCwd, KEIYAKU_FILE), renderContractView({ id: input.contractId, entries: ledger.entries }), "utf8");
|
|
226
|
-
}
|
|
227
212
|
export function renderContractView(input) {
|
|
228
213
|
const bind = input.entries.find((entry) => entry.kind === "bind");
|
|
229
|
-
const state =
|
|
214
|
+
const state = input.state;
|
|
230
215
|
if (!bind) {
|
|
231
216
|
return [
|
|
232
217
|
"# Keiyaku Contract",
|
|
@@ -235,6 +220,7 @@ export function renderContractView(input) {
|
|
|
235
220
|
"",
|
|
236
221
|
`id: ${input.id}`,
|
|
237
222
|
`state: ${state.state}`,
|
|
223
|
+
...(input.ledgerHead ? [`ledger: ${input.ledgerHead}`] : []),
|
|
238
224
|
"",
|
|
239
225
|
"log:",
|
|
240
226
|
renderVerticalLog(input),
|
|
@@ -242,26 +228,36 @@ export function renderContractView(input) {
|
|
|
242
228
|
].join("\n");
|
|
243
229
|
}
|
|
244
230
|
return [
|
|
245
|
-
`# ${bind.data.
|
|
231
|
+
`# ${bind.data.title}`,
|
|
246
232
|
"",
|
|
247
233
|
"generated by keiyaku - edits are not read back",
|
|
248
234
|
"",
|
|
249
235
|
`id: ${input.id}`,
|
|
250
236
|
`state: ${state.state}`,
|
|
237
|
+
...(input.ledgerHead ? [`ledger: ${input.ledgerHead}`] : []),
|
|
251
238
|
`base: ${bind.data.base}`,
|
|
252
239
|
`workspace: ${bind.data.workspace}`,
|
|
253
|
-
`place: ${bind.data.place
|
|
240
|
+
`place: ${bind.data.workspace === "worktree" ? bind.data.place : "(none)"}`,
|
|
254
241
|
`after: ${(bind.data.after ?? []).join(", ") || "(none)"}`,
|
|
255
242
|
"",
|
|
243
|
+
"## Context",
|
|
244
|
+
bind.data.context,
|
|
245
|
+
"",
|
|
256
246
|
"## Objective",
|
|
257
247
|
bind.data.objective,
|
|
258
248
|
"",
|
|
249
|
+
"## Design",
|
|
250
|
+
bind.data.design,
|
|
251
|
+
"",
|
|
259
252
|
"## Scope",
|
|
260
|
-
...renderScopeFragment(
|
|
253
|
+
...renderScopeFragment(input.effectiveScope).split("\n"),
|
|
261
254
|
"",
|
|
262
|
-
"##
|
|
263
|
-
...
|
|
255
|
+
"## Criteria",
|
|
256
|
+
...effectiveCriteria(input.entries).map((criterion) => `- ${criterion}`),
|
|
264
257
|
"",
|
|
258
|
+
...(bind.data.verification.length > 0
|
|
259
|
+
? ["## Verification", ...bind.data.verification.map((declaration) => `- ${declaration}`), ""]
|
|
260
|
+
: []),
|
|
265
261
|
...(bind.data.extensions.length > 0
|
|
266
262
|
? [
|
|
267
263
|
...bind.data.extensions.flatMap((extension) => extension.content.length > 0
|
|
@@ -274,3 +270,13 @@ export function renderContractView(input) {
|
|
|
274
270
|
"",
|
|
275
271
|
].join("\n");
|
|
276
272
|
}
|
|
273
|
+
export function renderCurrentContractView(model) {
|
|
274
|
+
const entries = model.history.map(({ entry }) => entry);
|
|
275
|
+
return renderContractView({
|
|
276
|
+
id: model.contract,
|
|
277
|
+
entries,
|
|
278
|
+
ledgerHead: model.observedLedgerHead,
|
|
279
|
+
state: currentLifecycleState(model),
|
|
280
|
+
effectiveScope: model.effectiveScope,
|
|
281
|
+
});
|
|
282
|
+
}
|
package/build/core/contract.js
CHANGED
|
@@ -3,9 +3,13 @@ export const contractStateSchema = z.enum(["bound", "active", "petitioned", "cla
|
|
|
3
3
|
export const contractRecordSchema = z
|
|
4
4
|
.object({
|
|
5
5
|
id: z.string().trim().min(1),
|
|
6
|
-
|
|
6
|
+
title: z.string().trim().min(1),
|
|
7
|
+
context: z.string().trim().min(1),
|
|
7
8
|
objective: z.string().trim().min(1),
|
|
9
|
+
design: z.string().trim().min(1),
|
|
8
10
|
scope: z.array(z.string().trim().min(1)).min(1),
|
|
11
|
+
criteria: z.array(z.string().trim().min(1)).min(1),
|
|
12
|
+
verification: z.array(z.string().trim().min(1)).min(1),
|
|
9
13
|
base: z.string().trim().min(1),
|
|
10
14
|
place: z.string().trim().min(1).nullable().optional(),
|
|
11
15
|
after: z.array(z.string().trim().min(1)),
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import * as fs from "node:fs/promises";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { FlowError } from "../flow-error.js";
|
|
5
|
+
import { runGitProcess } from "../git/core.js";
|
|
6
|
+
import { KEIYAKU_FILE } from "../keiyaku.js";
|
|
7
|
+
import { inventoryIgnorePatternsForWorktree } from "./repository-ledger/inventory.js";
|
|
8
|
+
import { readTaskBoardGeneration, reconcileTaskForfeit, replayClaimTasks, replayCommittedTaskBind, } from "./task/index.js";
|
|
9
|
+
function terminalEntry(model) {
|
|
10
|
+
return [...model.history].reverse().find(({ entry }) => (entry.kind === "claim" || entry.kind === "forfeit"));
|
|
11
|
+
}
|
|
12
|
+
function receipt(model, surface, status, facts) {
|
|
13
|
+
return {
|
|
14
|
+
surface,
|
|
15
|
+
status,
|
|
16
|
+
authoritativeHead: model.observedLedgerHead,
|
|
17
|
+
...(facts ? { facts } : {}),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
async function gitExit(cwd, args) {
|
|
21
|
+
const result = await runGitProcess(cwd, args);
|
|
22
|
+
if (result.error || (result.status !== 0 && result.status !== 1)) {
|
|
23
|
+
throw new FlowError("INTERNAL_STATE", result.stderr.trim() || result.error?.message || `git ${args.join(" ")} exited ${String(result.status)}`, {
|
|
24
|
+
cause: result.error,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return result.status;
|
|
28
|
+
}
|
|
29
|
+
/** Materialize exact per-worktree ignore bytes admitted by typed inventory. */
|
|
30
|
+
export async function materializeInventoryIgnore(targetRoot, patterns) {
|
|
31
|
+
const ignorePath = path.join(targetRoot, ".gitignore");
|
|
32
|
+
const expected = patterns.length === 0 ? "" : `${patterns.join("\n")}\n`;
|
|
33
|
+
const tracked = await gitExit(targetRoot, ["ls-files", "--error-unmatch", "--", ".gitignore"]) === 0;
|
|
34
|
+
if (tracked) {
|
|
35
|
+
if (patterns.length === 0)
|
|
36
|
+
return "already-present";
|
|
37
|
+
const ignored = await gitExit(targetRoot, ["check-ignore", "-q", "--", KEIYAKU_FILE]) === 0;
|
|
38
|
+
if (!ignored)
|
|
39
|
+
throw new FlowError("CARRIER_MATERIALIZATION_FAILED", "tracked .gitignore does not ignore the generated contract view");
|
|
40
|
+
return "already-present";
|
|
41
|
+
}
|
|
42
|
+
let existing = null;
|
|
43
|
+
try {
|
|
44
|
+
existing = await fs.readFile(ignorePath, "utf8");
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT")
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
if (patterns.length === 0) {
|
|
51
|
+
if (existing === null)
|
|
52
|
+
return "already-present";
|
|
53
|
+
if (existing !== `${[".gitignore", KEIYAKU_FILE].join("\n")}\n`)
|
|
54
|
+
return "already-present";
|
|
55
|
+
await fs.unlink(ignorePath);
|
|
56
|
+
return "created";
|
|
57
|
+
}
|
|
58
|
+
if (existing === expected)
|
|
59
|
+
return "already-present";
|
|
60
|
+
if (existing !== null)
|
|
61
|
+
throw new FlowError("CARRIER_MATERIALIZATION_FAILED", "untracked .gitignore is not a proven generated rendering");
|
|
62
|
+
const temporary = `${ignorePath}.${process.pid}.${randomUUID()}.tmp`;
|
|
63
|
+
try {
|
|
64
|
+
await fs.writeFile(temporary, expected, { encoding: "utf8", flag: "wx" });
|
|
65
|
+
await fs.rename(temporary, ignorePath);
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
await fs.unlink(temporary).catch(() => undefined);
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
return "created";
|
|
72
|
+
}
|
|
73
|
+
async function replayTaskBoard(cwd, model, adapters) {
|
|
74
|
+
const taskIds = model.resources.tasks.map(({ taskId }) => taskId);
|
|
75
|
+
const terminal = terminalEntry(model);
|
|
76
|
+
if (terminal?.entry.kind === "claim") {
|
|
77
|
+
const replay = taskIds.length === 0
|
|
78
|
+
? { commit: terminal.entry.data.mergeCommit, taskCount: 0, worktree: "ref-only" }
|
|
79
|
+
: await replayClaimTasks({
|
|
80
|
+
cwd,
|
|
81
|
+
contractId: model.contract,
|
|
82
|
+
targetRef: terminal.entry.data.target,
|
|
83
|
+
taskIds,
|
|
84
|
+
updatedAt: terminal.entry.at,
|
|
85
|
+
});
|
|
86
|
+
const warnings = await adapters.replayClaimTarget({
|
|
87
|
+
target: terminal.entry.data.target,
|
|
88
|
+
targetHead: terminal.entry.data.targetHead,
|
|
89
|
+
mergeCommit: replay.commit,
|
|
90
|
+
});
|
|
91
|
+
return {
|
|
92
|
+
status: taskIds.length === 0 ? "already-present" : replay.worktree === "ref-only" ? "ref-only" : "created",
|
|
93
|
+
facts: { taskCount: replay.taskCount, commit: replay.commit, worktree: replay.worktree, warnings: [...warnings] },
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
if (terminal?.entry.kind === "forfeit") {
|
|
97
|
+
if (taskIds.length === 0)
|
|
98
|
+
return { status: "already-present", facts: { taskCount: 0 } };
|
|
99
|
+
const generation = await readTaskBoardGeneration(cwd);
|
|
100
|
+
await reconcileTaskForfeit({
|
|
101
|
+
cwd,
|
|
102
|
+
contractId: model.contract,
|
|
103
|
+
expectedGeneration: generation,
|
|
104
|
+
publishLedger: async () => undefined,
|
|
105
|
+
});
|
|
106
|
+
return { status: "created", facts: { taskCount: taskIds.length } };
|
|
107
|
+
}
|
|
108
|
+
if (taskIds.length === 0)
|
|
109
|
+
return { status: "already-present", facts: { taskCount: 0 } };
|
|
110
|
+
const bind = model.history.find(({ entry }) => entry.kind === "bind")?.entry;
|
|
111
|
+
if (!bind || bind.kind !== "bind")
|
|
112
|
+
throw new FlowError("INTERNAL_STATE", `contract ${model.contract} has no typed bind`);
|
|
113
|
+
const status = await replayCommittedTaskBind(cwd, bind.data.base, taskIds);
|
|
114
|
+
return { status, facts: { taskCount: taskIds.length } };
|
|
115
|
+
}
|
|
116
|
+
function recordSurface(journal, model, surface, run) {
|
|
117
|
+
return journal.run({
|
|
118
|
+
effect: `${surface}-projection`,
|
|
119
|
+
idempotencyKey: `${model.contract}:${model.observedLedgerHead}:${surface}`,
|
|
120
|
+
run: async () => structuredClone(await run()),
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
/** Sole phase-driven owner for replaying projections from an accepted folded head. */
|
|
124
|
+
export async function replayCommittedDerivedState(input) {
|
|
125
|
+
const { lifecycle: model, journal } = input;
|
|
126
|
+
const terminal = terminalEntry(model);
|
|
127
|
+
let projectedTarget = input.target;
|
|
128
|
+
const receipts = [];
|
|
129
|
+
const run = async (surface, operation) => {
|
|
130
|
+
const row = await recordSurface(journal, model, surface, operation);
|
|
131
|
+
if (row.status === "succeeded")
|
|
132
|
+
receipts.push(row.receipt);
|
|
133
|
+
};
|
|
134
|
+
await run("status", async () => receipt(model, "status", "already-present", { phase: model.phase }));
|
|
135
|
+
await run("registry", async () => receipt(model, "registry", terminal ? "retired" : "already-present", {
|
|
136
|
+
source: "repository-ledger-fold",
|
|
137
|
+
effectiveScope: [...model.effectiveScope],
|
|
138
|
+
}));
|
|
139
|
+
await run("task-board", async () => {
|
|
140
|
+
const replay = await replayTaskBoard(input.cwd, model, input.adapters);
|
|
141
|
+
return receipt(model, "task-board", replay.status, replay.facts);
|
|
142
|
+
});
|
|
143
|
+
if (terminal) {
|
|
144
|
+
let carrierRetired = false;
|
|
145
|
+
await run("carrier", async () => {
|
|
146
|
+
const status = await input.adapters.retireContractView({ lifecycle: model, target: input.target });
|
|
147
|
+
carrierRetired = true;
|
|
148
|
+
return receipt(model, "carrier", status);
|
|
149
|
+
});
|
|
150
|
+
await run("narrow-ignore", async () => {
|
|
151
|
+
if (!carrierRetired) {
|
|
152
|
+
throw new FlowError("CARRIER_MATERIALIZATION_FAILED", "cannot retire narrow ignore before carrier retirement");
|
|
153
|
+
}
|
|
154
|
+
if (input.target.status !== "available") {
|
|
155
|
+
return receipt(model, "narrow-ignore", "already-present", { patterns: [] });
|
|
156
|
+
}
|
|
157
|
+
const patterns = inventoryIgnorePatternsForWorktree(input.repository, model);
|
|
158
|
+
return receipt(model, "narrow-ignore", await materializeInventoryIgnore(input.target.root, patterns), { patterns });
|
|
159
|
+
});
|
|
160
|
+
await run("worktree", async () => {
|
|
161
|
+
const cleanup = await input.adapters.cleanupWorkspace(input.cwd, model);
|
|
162
|
+
if (cleanup.worktreeRemoveFailure)
|
|
163
|
+
throw new FlowError("INTERNAL_STATE", cleanup.worktreeRemoveFailure.reason);
|
|
164
|
+
return receipt(model, "worktree", cleanup.absent.includes("worktree") ? "already-present" : "retired", {
|
|
165
|
+
absent: cleanup.absent,
|
|
166
|
+
...(cleanup.removedWorktreePath ? { removedWorktreePath: cleanup.removedWorktreePath } : {}),
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
await run("delivery-ref", async () => {
|
|
170
|
+
const observation = await input.adapters.observeDeliveryRef(input.cwd, model.contract);
|
|
171
|
+
return observation.status === "aligned"
|
|
172
|
+
? receipt(model, "delivery-ref", "already-present", observation)
|
|
173
|
+
: receipt(model, "delivery-ref", "mismatch", observation);
|
|
174
|
+
});
|
|
175
|
+
return receipts;
|
|
176
|
+
}
|
|
177
|
+
await run("worktree", async () => {
|
|
178
|
+
const replay = await input.adapters.reconcileActiveWorktree();
|
|
179
|
+
projectedTarget = replay.target;
|
|
180
|
+
return receipt(model, "worktree", replay.status);
|
|
181
|
+
});
|
|
182
|
+
await run("narrow-ignore", async () => {
|
|
183
|
+
if (projectedTarget.status !== "available")
|
|
184
|
+
throw new FlowError("CARRIER_TARGET_UNAVAILABLE", projectedTarget.reason);
|
|
185
|
+
const patterns = inventoryIgnorePatternsForWorktree(input.repository, model);
|
|
186
|
+
return receipt(model, "narrow-ignore", await materializeInventoryIgnore(projectedTarget.root, patterns), {
|
|
187
|
+
patterns,
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
let viewStatus = "already-present";
|
|
191
|
+
let viewProjected = false;
|
|
192
|
+
await run("contract-view", async () => {
|
|
193
|
+
const incomplete = journal.all().find((row) => row.status !== "succeeded");
|
|
194
|
+
if (incomplete) {
|
|
195
|
+
throw new FlowError("CARRIER_MATERIALIZATION_FAILED", `cannot advance carrier checkpoint while ${incomplete.effect} is ${incomplete.status}`);
|
|
196
|
+
}
|
|
197
|
+
viewStatus = await input.adapters.materializeContractView({ lifecycle: model, target: projectedTarget });
|
|
198
|
+
viewProjected = true;
|
|
199
|
+
return receipt(model, "contract-view", viewStatus, { path: projectedTarget.status === "available" ? path.join(projectedTarget.root, KEIYAKU_FILE) : null });
|
|
200
|
+
});
|
|
201
|
+
await run("carrier", async () => {
|
|
202
|
+
if (!viewProjected)
|
|
203
|
+
throw new FlowError("CARRIER_MATERIALIZATION_FAILED", "contract view was not materialized");
|
|
204
|
+
return receipt(model, "carrier", viewStatus, { checkpoint: model.observedLedgerHead });
|
|
205
|
+
});
|
|
206
|
+
return receipts;
|
|
207
|
+
}
|