@astrosheep/keiyaku 2.9.12 → 2.9.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -18
- package/build/.tsbuildinfo +1 -1
- package/build/agents/call-terms.js +1 -0
- package/build/agents/providers/claude-agent-sdk/adapter.js +2 -2
- package/build/cli/commands/akuma/akuma/handler.js +2 -1
- package/build/cli/commands/akuma/akuma/meta.js +4 -3
- package/build/cli/commands/akuma/catalog.js +2 -0
- package/build/cli/commands/akuma/list/handler.js +1 -1
- package/build/cli/commands/akuma/list/meta-list.js +12 -0
- package/build/cli/commands/akuma/list/meta-ls.js +2 -2
- package/build/cli/commands/akuma.js +5 -0
- package/build/cli/commands/contract/amend/meta.js +4 -3
- package/build/cli/commands/contract/arc/meta.js +2 -0
- package/build/cli/commands/contract/audit/handler.js +28 -2
- package/build/cli/commands/contract/audit/meta.js +4 -3
- package/build/cli/commands/contract/petition/handler.js +2 -1
- package/build/cli/commands/contract/petition/meta.js +2 -2
- package/build/cli/commands/contract/renew/handler.js +44 -3
- package/build/cli/commands/contract/renew/meta.js +5 -4
- package/build/cli/commands/metadata.js +12 -9
- package/build/cli/commands/projection/call/handler.js +1 -1
- package/build/cli/commands/projection/call/meta.js +1 -1
- package/build/cli/commands/projection/catalog.js +2 -2
- package/build/cli/commands/projection/{revive → fork}/handler.js +10 -5
- package/build/cli/commands/projection/fork/meta.js +12 -0
- package/build/cli/commands/projection/history/handler.js +46 -2
- package/build/cli/commands/projection/history/meta.js +3 -3
- package/build/cli/commands/projection/kill/meta.js +2 -2
- package/build/cli/commands/projection/status/handler.js +29 -13
- package/build/cli/commands/projection/status/meta.js +14 -3
- package/build/cli/commands/projection/tell/handler.js +8 -4
- package/build/cli/commands/projection/tell/meta.js +1 -1
- package/build/cli/commands/projection/wait/handler.js +45 -20
- package/build/cli/commands/projection/wait/meta.js +1 -1
- package/build/cli/commands/shared.js +1 -1
- package/build/cli/commands/system/catalog.js +2 -0
- package/build/cli/commands/task/add/handler.js +28 -12
- package/build/cli/commands/task/add/meta.js +18 -6
- package/build/cli/commands/task/catalog.js +2 -0
- package/build/cli/commands/task/compose/handler.js +2 -2
- package/build/cli/commands/task/compose/meta.js +2 -2
- package/build/cli/commands/task/doctor/handler.js +2 -3
- package/build/cli/commands/task/doctor/meta.js +2 -2
- package/build/cli/commands/task/done/meta.js +2 -2
- package/build/cli/commands/task/drop/handler.js +7 -2
- package/build/cli/commands/task/drop/meta.js +6 -3
- package/build/cli/commands/task/hold/meta.js +2 -2
- package/build/cli/commands/task/log/meta.js +2 -2
- package/build/cli/commands/task/ls/meta.js +2 -2
- package/build/cli/commands/task/note/handler.js +6 -0
- package/build/cli/commands/task/note/meta.js +11 -0
- package/build/cli/commands/task/resume/meta.js +2 -2
- package/build/cli/commands/task/shared.js +29 -15
- package/build/cli/commands/task/show/meta.js +2 -2
- package/build/cli/commands/task/start/meta.js +2 -2
- package/build/cli/commands/task/stop/meta.js +2 -2
- package/build/cli/commands/task/update/meta.js +3 -2
- package/build/cli/commands/verification/handler.js +43 -0
- package/build/cli/commands/verification/meta.js +13 -0
- package/build/cli/completion.js +16 -13
- package/build/cli/flags.js +43 -11
- package/build/cli/help.js +29 -30
- package/build/cli/index.js +6 -5
- package/build/cli/parse-flags.js +39 -0
- package/build/cli/parse-metadata.js +5 -2
- package/build/cli/parse-selectors.js +18 -0
- package/build/cli/parse.js +21 -8
- package/build/cli/render/arc.js +12 -3
- package/build/cli/render/audit.js +2 -2
- package/build/cli/render/call.js +4 -4
- package/build/cli/render/kanshi.js +122 -85
- package/build/cli/render/misc.js +2 -2
- package/build/cli/render/petition.js +16 -1
- package/build/cli/render/projection-history.js +17 -4
- package/build/cli/render/shared.js +5 -5
- package/build/cli/render/status-indicator.js +47 -0
- package/build/cli/render/status.js +106 -76
- package/build/cli/render/success-response.js +20 -5
- package/build/cli/render/task.js +6 -5
- package/build/cli/render/tell.js +10 -4
- package/build/cli/render/tool-command-normalization.js +137 -0
- package/build/cli/render/tool-presentation.js +2 -1
- package/build/cli/render/verification.js +23 -0
- package/build/cli/render/wait.js +101 -33
- package/build/cli/types.js +2 -1
- package/build/config/akuma-loader.js +21 -1
- package/build/config/settings/knobs.js +7 -1
- package/build/config/settings/schema.js +10 -5
- package/build/core/amend.js +102 -17
- package/build/core/arc.js +4 -4
- package/build/core/audit/candidate.js +10 -18
- package/build/core/audit/coordinates.js +12 -17
- package/build/core/audit/evidence.js +6 -4
- package/build/core/audit/facade.js +3 -3
- package/build/core/audit/report.js +1 -1
- package/build/core/bind-reconciliation.js +42 -23
- package/build/core/bind.js +182 -81
- package/build/core/call/call.js +6 -7
- package/build/core/call/context.js +43 -30
- package/build/core/call/execution.js +21 -6
- package/build/core/call/prompt.js +11 -42
- package/build/core/contract-carrier-runtime.js +209 -105
- package/build/core/contract-carrier.js +156 -73
- package/build/core/contract-view.js +42 -5
- package/build/core/contract.js +2 -1
- package/build/core/derived-replay.js +168 -35
- package/build/core/draft.js +9 -5
- package/build/core/forfeit.js +1 -1
- package/build/core/lifecycle-history.js +13 -0
- package/build/core/lifecycle-recovery.js +32 -27
- package/build/core/lifecycle-runner.js +14 -14
- package/build/core/log.js +1 -1
- package/build/core/outcome-base.js +2 -2
- package/build/core/projection/akuma-name.js +43 -0
- package/build/core/projection/generation/database.js +141 -79
- package/build/core/projection/generation/ledger.js +91 -0
- package/build/core/projection/generation/model.js +18 -13
- package/build/core/projection/generation/projection-generation-continuation.js +1 -1
- package/build/core/projection/generation/projection-generation-doorbell.js +88 -0
- package/build/core/projection/generation/projection-generation-execution.js +24 -0
- package/build/core/projection/generation/projection-generation-identity.js +4 -0
- package/build/core/projection/generation/projection-generation-launcher.js +72 -138
- package/build/core/projection/generation/projection-generation-process.js +16 -15
- package/build/core/projection/generation/projection-generation-runner.js +125 -79
- package/build/core/projection/generation/projection-generation-runtime.js +10 -61
- package/build/core/projection/generation/protocol.js +68 -0
- package/build/core/projection/generation/store.js +17 -7
- package/build/core/projection/generation/transitions.js +14 -14
- package/build/core/projection/index.js +5 -5
- package/build/core/projection/projection-core.js +6 -2
- package/build/core/projection/projection-execution-observer.js +1 -1
- package/build/core/projection/projection-history.js +7 -7
- package/build/core/projection/projection-kill.js +15 -6
- package/build/core/projection/projection-life-observer.js +8 -8
- package/build/core/projection/projection-mint.js +6 -8
- package/build/core/projection/projection-status-observation.js +77 -8
- package/build/core/projection/projection-status.js +151 -4
- package/build/core/projection/projection-wait.js +46 -27
- package/build/core/projection/projection-wake.js +27 -89
- package/build/core/projection/tell/launch-store.js +1 -1
- package/build/core/projection/tell/store.js +1 -1
- package/build/core/renew-build.js +204 -74
- package/build/core/renew-plan.js +34 -9
- package/build/core/renew-rewrite.js +56 -15
- package/build/core/renew.js +39 -35
- package/build/core/repository-ledger/accepted-fold-read.js +6 -3
- package/build/core/repository-ledger/codec.js +11 -5
- package/build/core/repository-ledger/current-state-store.js +13 -16
- package/build/core/repository-ledger/fold-repository.js +5 -6
- package/build/core/repository-ledger/identity.js +60 -0
- package/build/core/repository-ledger/inventory.js +3 -3
- package/build/core/repository-ledger/read-model.js +54 -6
- package/build/core/repository-ledger/write-transaction.js +9 -4
- package/build/core/run-control/connection-bound-close.js +41 -0
- package/build/core/run-control/detached-lease-bootstrap.js +208 -0
- package/build/core/run-control/detached-process.js +14 -0
- package/build/core/run-control/index.js +6 -0
- package/build/core/{process-group.js → run-control/process-tree.js} +1 -1
- package/build/core/run-control/runner-lease.js +172 -0
- package/build/core/{sqlite-process-lifetime-lock.js → run-control/sqlite-process-lifetime-lock.js} +4 -24
- package/build/core/seal.js +44 -32
- package/build/core/settlement/claim-delivery.js +22 -9
- package/build/core/settlement/claim.js +8 -6
- package/build/core/settlement/petition-claim-gates.js +9 -8
- package/build/core/settlement/petition-head-guard.js +5 -6
- package/build/core/settlement/petition-preview.js +21 -5
- package/build/core/settlement/petition.js +51 -17
- package/build/core/settlement/settlement.js +20 -15
- package/build/core/settlement/verdict.js +2 -2
- package/build/core/settlement/verification.js +492 -364
- package/build/core/status/board.js +184 -75
- package/build/core/status/drift.js +8 -10
- package/build/core/task/board.js +1 -0
- package/build/core/task/commands.js +25 -0
- package/build/core/task/compose.js +3 -2
- package/build/core/task/document.js +102 -39
- package/build/core/task/index.js +3 -3
- package/build/core/task/settlement-git.js +1 -4
- package/build/core/task/settlement-policy.js +27 -14
- package/build/core/task/source-board.js +1 -0
- package/build/core/task/task-bind-preparation.js +59 -9
- package/build/core/task/task-contract.js +2 -2
- package/build/core/task/task-store-repository.js +1 -0
- package/build/core/task/task.js +2 -2
- package/build/core/transcripts.js +4 -4
- package/build/core/verification-declaration.js +95 -0
- package/build/core/worktree-bootstrap.js +1 -1
- package/build/core/worktree-path.js +7 -11
- package/build/flow-error.js +9 -1
- package/build/generated/version.js +2 -2
- package/build/git/branches.js +41 -70
- package/build/git/commits.js +92 -21
- package/build/git/core.js +9 -161
- package/build/git/diff/preview.js +4 -4
- package/build/git/diff/read.js +4 -4
- package/build/git/diff/structured.js +5 -5
- package/build/git/process.js +229 -0
- package/build/git/refs.js +30 -5
- package/build/git/staging.js +39 -115
- package/build/git/streaming-batch.js +9 -16
- package/build/git/worktree.js +104 -91
- package/build/index.js +7 -0
- package/package.json +1 -1
- package/skills/keiyaku-akuma/SKILL.md +21 -20
- package/skills/keiyaku-task/SKILL.md +38 -4
- package/skills/keiyaku-workflow/SKILL.md +51 -11
- package/build/cli/commands/projection/revive/meta.js +0 -12
- package/build/core/projection/projection-life-protocol.js +0 -115
- package/build/core/projection/projection-runner-lock.js +0 -211
- package/build/core/projection/tell/database.js +0 -127
- package/build/core/settlement/verification-coordination.js +0 -305
- package/build/core/settlement/verification-supervisor.js +0 -275
|
@@ -1,22 +1,28 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import * as fs from "node:fs/promises";
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { setTimeout as delay } from "node:timers/promises";
|
|
5
5
|
import { DatabaseSync } from "node:sqlite";
|
|
6
6
|
import { FlowError } from "../flow-error.js";
|
|
7
|
-
import {
|
|
7
|
+
import { isCommitAncestor, readCommitCoordinate } from "../git/commits.js";
|
|
8
|
+
import { isFullGitObjectId, runGitProcess } from "../git/core.js";
|
|
9
|
+
import { resolveExactRefHeads } from "../git/refs.js";
|
|
8
10
|
import { KEIYAKU_FILE } from "../keiyaku.js";
|
|
9
|
-
import {
|
|
11
|
+
import { renderCanonicalCarrierBody } from "./contract-view.js";
|
|
10
12
|
import { isCommissionId } from "./ids.js";
|
|
11
13
|
import { activeWorkspaceHereContracts } from "./repository-ledger/inventory.js";
|
|
12
14
|
import { lookupCurrentContract, readRepositoryLifecycle, } from "./repository-ledger/read-model.js";
|
|
13
15
|
import { REPOSITORY_LEDGER_REF, } from "./repository-ledger/codec.js";
|
|
14
16
|
import { resolveContractCarrierTarget, stableRepoRoot, } from "./worktree-path.js";
|
|
17
|
+
import { carrierDigest, carrierInputIdentity, contractHead, } from "./repository-ledger/identity.js";
|
|
15
18
|
import { installCarrierRuntimePorts } from "./contract-carrier-runtime.js";
|
|
16
|
-
export { convergeContractCarrier, reconcileContractCarrierProjection, withContractCarrierSession } from "./contract-carrier-runtime.js";
|
|
19
|
+
export { convergeContractCarrier, reconcileContractCarrierProjection, withContractCarrierSession, withObservedContractCarrierSession } from "./contract-carrier-runtime.js";
|
|
17
20
|
const BUSY_SLICE_MS = 25;
|
|
18
|
-
const
|
|
19
|
-
const
|
|
21
|
+
const CARRIER_HEADER = "<!-- keiyaku-carrier";
|
|
22
|
+
const CARRIER_HEADER_END = "-->";
|
|
23
|
+
const CARRIER_ID_LINE = "contract-id";
|
|
24
|
+
const CARRIER_HEAD_LINE = "contract-head";
|
|
25
|
+
const CARRIER_DIGEST_LINE = "carrier-digest";
|
|
20
26
|
export function acceptedBind(lifecycle) {
|
|
21
27
|
for (const { entry } of lifecycle.history) {
|
|
22
28
|
if (entry.kind === "bind")
|
|
@@ -38,10 +44,10 @@ async function projectRenewDeliveryRoot(root, oldHead, newHead) {
|
|
|
38
44
|
throw new FlowError("INTERNAL_STATE", update.stderr.trim() || update.error?.message || `read-tree exited ${String(update.status)}`, { cause: update.error });
|
|
39
45
|
}
|
|
40
46
|
const [head, status] = await Promise.all([
|
|
41
|
-
|
|
47
|
+
readCommitCoordinate(root, "HEAD"),
|
|
42
48
|
runGitProcess(root, ["status", "--porcelain=v1", "--untracked-files=no"]),
|
|
43
49
|
]);
|
|
44
|
-
if (head.status !==
|
|
50
|
+
if (head.status !== "ok" || head.coordinate.commit !== newHead || status.status !== 0 || status.error || status.stdout !== "") {
|
|
45
51
|
throw new FlowError("INTERNAL_STATE", "delivery worktree did not align to the committed renew head");
|
|
46
52
|
}
|
|
47
53
|
}
|
|
@@ -59,9 +65,15 @@ export async function reconcileFoldedDeliveryWorktree(input) {
|
|
|
59
65
|
}
|
|
60
66
|
const indexTree = index.stdout.trim();
|
|
61
67
|
const newTree = await resolveCommitTree(input.targetRoot, renew.entry.data.newHead);
|
|
62
|
-
if (indexTree === newTree)
|
|
63
|
-
return "already-present";
|
|
64
68
|
const oldTree = await resolveCommitTree(input.targetRoot, renew.entry.data.oldHead);
|
|
69
|
+
if (indexTree === newTree && worktree.status === 0 && !worktree.error)
|
|
70
|
+
return "already-present";
|
|
71
|
+
if (indexTree !== oldTree
|
|
72
|
+
&& worktree.status === 0
|
|
73
|
+
&& !worktree.error
|
|
74
|
+
&& await isCommitAncestor(input.targetRoot, renew.entry.data.newHead, "HEAD")) {
|
|
75
|
+
return "already-present";
|
|
76
|
+
}
|
|
65
77
|
if (indexTree !== oldTree || worktree.status !== 0 || worktree.error) {
|
|
66
78
|
throw new FlowError("INTERNAL_STATE", `delivery worktree cannot be safely reconstructed for accepted renew ${renew.objectId}`);
|
|
67
79
|
}
|
|
@@ -69,11 +81,14 @@ export async function reconcileFoldedDeliveryWorktree(input) {
|
|
|
69
81
|
return "created";
|
|
70
82
|
}
|
|
71
83
|
async function resolveCommitTree(cwd, commit) {
|
|
72
|
-
const
|
|
73
|
-
if (
|
|
74
|
-
|
|
84
|
+
const read = await readCommitCoordinate(cwd, commit);
|
|
85
|
+
if (read.status !== "ok") {
|
|
86
|
+
if (read.status === "failed") {
|
|
87
|
+
throw new FlowError("INTERNAL_STATE", read.result.stderr.trim() || read.error.message || `cannot resolve tree for ${commit}`, { cause: read.error });
|
|
88
|
+
}
|
|
89
|
+
throw new FlowError("INTERNAL_STATE", `cannot resolve tree for ${commit}`);
|
|
75
90
|
}
|
|
76
|
-
return
|
|
91
|
+
return read.coordinate.tree;
|
|
77
92
|
}
|
|
78
93
|
export function observedLedgerHead(observations) {
|
|
79
94
|
const observation = observations.find((item) => item.ref === REPOSITORY_LEDGER_REF)?.observed;
|
|
@@ -90,14 +105,19 @@ export function checkCancellation(signal) {
|
|
|
90
105
|
if (signal?.aborted)
|
|
91
106
|
throwCancelled();
|
|
92
107
|
}
|
|
93
|
-
export
|
|
108
|
+
export function carrierPublicationGatePath(stableRoot, contractId, targetPath) {
|
|
109
|
+
const key = createHash("sha256").update(`${contractId}\0${targetPath}`).digest("hex");
|
|
110
|
+
return path.join(stableRoot, ".keiyaku", "carrier-gates", `${key}.sqlite`);
|
|
111
|
+
}
|
|
112
|
+
export async function acquireGate(stableRoot, contractId, targetPath, signal) {
|
|
94
113
|
checkCancellation(signal);
|
|
95
|
-
const
|
|
114
|
+
const databasePath = carrierPublicationGatePath(stableRoot, contractId, targetPath);
|
|
115
|
+
const directory = path.dirname(databasePath);
|
|
96
116
|
await fs.mkdir(directory, { recursive: true });
|
|
97
117
|
checkCancellation(signal);
|
|
98
118
|
let database;
|
|
99
119
|
try {
|
|
100
|
-
database = new DatabaseSync(
|
|
120
|
+
database = new DatabaseSync(databasePath);
|
|
101
121
|
database.exec(`PRAGMA busy_timeout = ${BUSY_SLICE_MS}`);
|
|
102
122
|
}
|
|
103
123
|
catch (error) {
|
|
@@ -127,9 +147,9 @@ export function releaseGate(database) {
|
|
|
127
147
|
}
|
|
128
148
|
}
|
|
129
149
|
/** Serialize cross-process committed replay decisions without exposing gate coordinates. */
|
|
130
|
-
export async function withContractReplayGate(cwd, signal, action) {
|
|
150
|
+
export async function withContractReplayGate(cwd, contractId, targetPath, signal, action) {
|
|
131
151
|
const stableRoot = await stableRepoRoot(cwd);
|
|
132
|
-
const gate = await acquireGate(stableRoot, signal);
|
|
152
|
+
const gate = await acquireGate(stableRoot, contractId, targetPath, signal);
|
|
133
153
|
try {
|
|
134
154
|
return await action(stableRoot);
|
|
135
155
|
}
|
|
@@ -137,35 +157,102 @@ export async function withContractReplayGate(cwd, signal, action) {
|
|
|
137
157
|
releaseGate(gate);
|
|
138
158
|
}
|
|
139
159
|
}
|
|
140
|
-
|
|
160
|
+
function canonicalBody(lifecycle) {
|
|
161
|
+
return Buffer.from(renderCanonicalCarrierBody(lifecycle), "utf8");
|
|
162
|
+
}
|
|
163
|
+
export function expectedCarrierIdentity(lifecycle) {
|
|
164
|
+
return carrierInputIdentity({
|
|
165
|
+
contractId: lifecycle.contract,
|
|
166
|
+
contractHead: lifecycle.contractHead,
|
|
167
|
+
bodyDigest: carrierDigest(createHash("sha256").update(canonicalBody(lifecycle)).digest("hex")),
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
export function renderCarrier(lifecycle) {
|
|
171
|
+
const identity = expectedCarrierIdentity(lifecycle);
|
|
172
|
+
return [
|
|
173
|
+
CARRIER_HEADER,
|
|
174
|
+
`${CARRIER_ID_LINE}: ${identity.contractId}`,
|
|
175
|
+
`${CARRIER_HEAD_LINE}: ${identity.contractHead}`,
|
|
176
|
+
`${CARRIER_DIGEST_LINE}: ${identity.bodyDigest}`,
|
|
177
|
+
CARRIER_HEADER_END,
|
|
178
|
+
"",
|
|
179
|
+
canonicalBody(lifecycle).toString("utf8"),
|
|
180
|
+
].join("\n");
|
|
181
|
+
}
|
|
182
|
+
function parsedCarrierFromBytes(source) {
|
|
183
|
+
if (source.subarray(0, 3).equals(Buffer.from([0xef, 0xbb, 0xbf])))
|
|
184
|
+
return null;
|
|
185
|
+
const text = source.toString("utf8");
|
|
186
|
+
if (!Buffer.from(text, "utf8").equals(source))
|
|
187
|
+
return null;
|
|
188
|
+
const lines = text.split("\n");
|
|
189
|
+
if (lines.length < 7 || lines[0] !== CARRIER_HEADER || lines[4] !== CARRIER_HEADER_END || lines[5] !== "")
|
|
190
|
+
return null;
|
|
191
|
+
const field = (line, name) => line.startsWith(`${name}: `) ? line.slice(name.length + 2) : null;
|
|
192
|
+
const contractId = field(lines[1], CARRIER_ID_LINE);
|
|
193
|
+
const head = field(lines[2], CARRIER_HEAD_LINE);
|
|
194
|
+
const digest = field(lines[3], CARRIER_DIGEST_LINE);
|
|
195
|
+
if (contractId === null || head === null || digest === null || !isCommissionId(contractId) || !isFullGitObjectId(head))
|
|
196
|
+
return null;
|
|
141
197
|
try {
|
|
142
|
-
return
|
|
198
|
+
return {
|
|
199
|
+
identity: carrierInputIdentity({ contractId, contractHead: contractHead(head), bodyDigest: carrierDigest(digest) }),
|
|
200
|
+
body: Buffer.from(lines.slice(6).join("\n"), "utf8"),
|
|
201
|
+
};
|
|
143
202
|
}
|
|
144
203
|
catch {
|
|
145
204
|
return null;
|
|
146
205
|
}
|
|
147
206
|
}
|
|
207
|
+
/** Strict current-version parser; it intentionally accepts no legacy carrier format. */
|
|
148
208
|
export function parseCarrierIdentity(source) {
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
209
|
+
const parsed = parsedCarrierFromBytes(Buffer.from(source, "utf8"));
|
|
210
|
+
if (!parsed)
|
|
211
|
+
return null;
|
|
212
|
+
const digest = carrierDigest(createHash("sha256").update(parsed.body).digest("hex"));
|
|
213
|
+
return digest === parsed.identity.bodyDigest ? parsed.identity : null;
|
|
214
|
+
}
|
|
215
|
+
export async function verifyCarrier(input) {
|
|
216
|
+
const expected = expectedCarrierIdentity(input.lifecycle);
|
|
217
|
+
let source;
|
|
218
|
+
try {
|
|
219
|
+
source = await fs.readFile(input.target);
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
223
|
+
return { status: "missing", expected, target: input.target };
|
|
224
|
+
return { status: "invalid-header", expected, target: input.target };
|
|
225
|
+
}
|
|
226
|
+
const parsed = parsedCarrierFromBytes(source);
|
|
227
|
+
if (!parsed)
|
|
228
|
+
return { status: "invalid-header", expected, target: input.target };
|
|
229
|
+
const actualBodyDigest = carrierDigest(createHash("sha256").update(parsed.body).digest("hex"));
|
|
230
|
+
if (!parsed.body.equals(canonicalBody(input.lifecycle)) || actualBodyDigest !== parsed.identity.bodyDigest) {
|
|
231
|
+
return { status: "modified", expected, actual: parsed.identity, target: input.target };
|
|
232
|
+
}
|
|
233
|
+
if (parsed.identity.contractId !== expected.contractId
|
|
234
|
+
|| parsed.identity.contractHead !== expected.contractHead
|
|
235
|
+
|| parsed.identity.bodyDigest !== expected.bodyDigest) {
|
|
236
|
+
return { status: "identity-mismatch", expected, actual: parsed.identity, target: input.target };
|
|
237
|
+
}
|
|
238
|
+
return { status: "current", expected, actual: parsed.identity, target: input.target };
|
|
152
239
|
}
|
|
153
|
-
export function carrierMatches(
|
|
154
|
-
return
|
|
240
|
+
export function carrierMatches(verification) {
|
|
241
|
+
return verification.status === "current";
|
|
155
242
|
}
|
|
156
243
|
export async function retireTerminalHereCarrier(lifecycle, target) {
|
|
157
244
|
if (target.status !== "available" || target.workspace !== "here")
|
|
158
245
|
return "already-present";
|
|
159
|
-
let source;
|
|
160
246
|
try {
|
|
161
|
-
|
|
247
|
+
await fs.access(target.path);
|
|
162
248
|
}
|
|
163
249
|
catch (error) {
|
|
164
250
|
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
165
251
|
return "already-present";
|
|
166
252
|
throw error;
|
|
167
253
|
}
|
|
168
|
-
|
|
254
|
+
const verification = await verifyCarrier({ target: target.path, lifecycle });
|
|
255
|
+
if (!carrierMatches(verification))
|
|
169
256
|
return "already-present";
|
|
170
257
|
try {
|
|
171
258
|
await fs.unlink(target.path);
|
|
@@ -179,45 +266,51 @@ export async function retireTerminalHereCarrier(lifecycle, target) {
|
|
|
179
266
|
}
|
|
180
267
|
export async function publishCarrier(input) {
|
|
181
268
|
if (input.target.status === "unavailable") {
|
|
182
|
-
return { status: "target-unavailable",
|
|
269
|
+
return { status: "target-unavailable", contractHead: input.lifecycle.contractHead, reason: input.target.reason };
|
|
183
270
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
271
|
+
const target = input.target;
|
|
272
|
+
const hasExpectedTarget = async () => {
|
|
273
|
+
const observed = await observe(input.stableRoot, input.contractId);
|
|
274
|
+
if (observed.lifecycle.contractHead !== input.lifecycle.contractHead)
|
|
275
|
+
return false;
|
|
276
|
+
if (observed.target.status !== "available" || observed.target.path !== target.path || observed.target.workspace !== target.workspace)
|
|
277
|
+
return false;
|
|
278
|
+
if (observed.target.workspace === "here") {
|
|
279
|
+
const occupants = activeWorkspaceHereContracts(observed.repository);
|
|
280
|
+
return occupants.length === 1 && occupants[0] === input.contractId;
|
|
193
281
|
}
|
|
282
|
+
return true;
|
|
283
|
+
};
|
|
284
|
+
if (target.workspace === "here" && !await hasExpectedTarget()) {
|
|
285
|
+
return {
|
|
286
|
+
status: "failed",
|
|
287
|
+
contractHead: input.lifecycle.contractHead,
|
|
288
|
+
target: target.path,
|
|
289
|
+
cause: `workspace-here carrier occupancy does not admit ${input.contractId}`,
|
|
290
|
+
};
|
|
194
291
|
}
|
|
195
|
-
if (carrierMatches(await
|
|
196
|
-
return { status: "current",
|
|
292
|
+
if (carrierMatches(await verifyCarrier({ target: target.path, lifecycle: input.lifecycle }))) {
|
|
293
|
+
return { status: "current", contractHead: input.lifecycle.contractHead, target: target.path };
|
|
197
294
|
}
|
|
198
|
-
const directory = path.dirname(
|
|
295
|
+
const directory = path.dirname(target.path);
|
|
199
296
|
const temporary = path.join(directory, `.${path.basename(KEIYAKU_FILE)}.${process.pid}.${randomUUID()}.tmp`);
|
|
200
297
|
try {
|
|
201
298
|
await fs.mkdir(directory, { recursive: true });
|
|
202
|
-
await fs.writeFile(temporary,
|
|
203
|
-
if (
|
|
299
|
+
await fs.writeFile(temporary, renderCarrier(input.lifecycle), { encoding: "utf8", flag: "wx" });
|
|
300
|
+
if (!await hasExpectedTarget()) {
|
|
204
301
|
await fs.unlink(temporary);
|
|
205
|
-
return { status: "failed",
|
|
302
|
+
return { status: "failed", contractHead: input.lifecycle.contractHead, target: target.path, cause: "contract carrier target changed before replacement" };
|
|
206
303
|
}
|
|
207
|
-
await (input.contractViewWriter?.replace ?? fs.rename)(temporary,
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
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" };
|
|
304
|
+
await (input.contractViewWriter?.replace ?? fs.rename)(temporary, target.path);
|
|
305
|
+
const verification = await verifyCarrier({ target: target.path, lifecycle: input.lifecycle });
|
|
306
|
+
if (!await hasExpectedTarget() || !carrierMatches(verification)) {
|
|
307
|
+
return { status: "failed", contractHead: input.lifecycle.contractHead, target: target.path, cause: "carrier post-check did not prove the published contract identity" };
|
|
215
308
|
}
|
|
216
|
-
return { status: "materialized",
|
|
309
|
+
return { status: "materialized", contractHead: input.lifecycle.contractHead, target: target.path };
|
|
217
310
|
}
|
|
218
311
|
catch (error) {
|
|
219
312
|
await fs.unlink(temporary).catch(() => undefined);
|
|
220
|
-
return { status: "failed",
|
|
313
|
+
return { status: "failed", contractHead: input.lifecycle.contractHead, target: target.path, cause: diagnostic(error) };
|
|
221
314
|
}
|
|
222
315
|
}
|
|
223
316
|
export async function observe(stableRoot, contractId) {
|
|
@@ -237,34 +330,24 @@ export async function observe(stableRoot, contractId) {
|
|
|
237
330
|
const target = await resolveContractCarrierTarget(stableRoot, contractId, bind);
|
|
238
331
|
const targetRef = lifecycle.pins.target.ref;
|
|
239
332
|
const deliveryRef = lifecycle.resources.deliveryRef;
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
|
-
}
|
|
333
|
+
const heads = await resolveExactRefHeads(stableRoot, [targetRef, deliveryRef]);
|
|
334
|
+
const targetHead = heads.get(targetRef) ?? null;
|
|
335
|
+
const deliveryHead = heads.get(deliveryRef) ?? null;
|
|
336
|
+
if (repository.observedLedgerHead === null)
|
|
337
|
+
throw new FlowError("INTERNAL_STATE", "accepted contract has no repository ledger observation");
|
|
338
|
+
return { repository, lifecycle, target, pins: { repositoryLedgerHead: repository.observedLedgerHead, target: { ref: targetRef, head: targetHead }, delivery: { ref: deliveryRef, head: deliveryHead } } };
|
|
255
339
|
}
|
|
256
340
|
installCarrierRuntimePorts({
|
|
257
341
|
acceptedBind,
|
|
258
342
|
acquireGate,
|
|
259
343
|
carrierMatches,
|
|
260
344
|
checkCancellation,
|
|
345
|
+
expectedCarrierIdentity,
|
|
261
346
|
observe,
|
|
262
347
|
observedLedgerHead,
|
|
263
|
-
projectRenewDeliveryWorktree,
|
|
264
348
|
publishCarrier,
|
|
265
|
-
|
|
349
|
+
verifyCarrier,
|
|
266
350
|
reconcileFoldedDeliveryWorktree,
|
|
267
351
|
releaseGate,
|
|
268
|
-
resolveCommitOrNull,
|
|
269
352
|
retireTerminalHereCarrier,
|
|
270
353
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { shortCommitHash } from "../git/commits.js";
|
|
2
|
-
import { effectiveCriteria } from "./lifecycle-history.js";
|
|
2
|
+
import { effectiveCriteria, effectiveVerification } from "./lifecycle-history.js";
|
|
3
3
|
import { renderScopeFragment } from "./scope.js";
|
|
4
|
+
import { renderVerificationDeclarations } from "./verification-declaration.js";
|
|
4
5
|
import { currentLifecycleState, } from "./repository-ledger/read-model.js";
|
|
5
6
|
export { currentLifecycleState } from "./repository-ledger/read-model.js";
|
|
6
7
|
const VERB_COLUMN_WIDTH = 11;
|
|
@@ -227,6 +228,7 @@ export function renderContractView(input) {
|
|
|
227
228
|
"",
|
|
228
229
|
].join("\n");
|
|
229
230
|
}
|
|
231
|
+
const verification = effectiveVerification(input.entries);
|
|
230
232
|
return [
|
|
231
233
|
`# ${bind.data.title}`,
|
|
232
234
|
"",
|
|
@@ -255,8 +257,8 @@ export function renderContractView(input) {
|
|
|
255
257
|
"## Criteria",
|
|
256
258
|
...effectiveCriteria(input.entries).map((criterion) => `- ${criterion}`),
|
|
257
259
|
"",
|
|
258
|
-
...(
|
|
259
|
-
? ["## Verification",
|
|
260
|
+
...(verification.length > 0
|
|
261
|
+
? ["## Verification", renderVerificationDeclarations(verification), ""]
|
|
260
262
|
: []),
|
|
261
263
|
...(bind.data.extensions.length > 0
|
|
262
264
|
? [
|
|
@@ -270,12 +272,47 @@ export function renderContractView(input) {
|
|
|
270
272
|
"",
|
|
271
273
|
].join("\n");
|
|
272
274
|
}
|
|
273
|
-
|
|
275
|
+
/** Exact UTF-8/LF carrier body; metadata and diagnostic renderings stay outside it. */
|
|
276
|
+
export function renderCanonicalCarrierBody(model) {
|
|
277
|
+
const entries = model.history.map(({ entry }) => entry);
|
|
278
|
+
const bind = entries.find((entry) => entry.kind === "bind");
|
|
279
|
+
if (!bind)
|
|
280
|
+
throw new Error(`contract ${model.contract} has no bind body to render`);
|
|
281
|
+
const verification = effectiveVerification(entries);
|
|
282
|
+
return [
|
|
283
|
+
`# ${bind.data.title}`,
|
|
284
|
+
"",
|
|
285
|
+
"## Context",
|
|
286
|
+
bind.data.context,
|
|
287
|
+
"",
|
|
288
|
+
"## Objective",
|
|
289
|
+
bind.data.objective,
|
|
290
|
+
"",
|
|
291
|
+
"## Design & Approach",
|
|
292
|
+
bind.data.design,
|
|
293
|
+
"",
|
|
294
|
+
"## Scope",
|
|
295
|
+
...renderScopeFragment(model.effectiveScope).split("\n"),
|
|
296
|
+
"",
|
|
297
|
+
"## Criteria",
|
|
298
|
+
...effectiveCriteria(entries).map((criterion) => `- ${criterion}`),
|
|
299
|
+
"",
|
|
300
|
+
...(verification.length > 0
|
|
301
|
+
? ["## Verification", renderVerificationDeclarations(verification), ""]
|
|
302
|
+
: []),
|
|
303
|
+
...(bind.data.extensions.length > 0
|
|
304
|
+
? bind.data.extensions.flatMap((extension) => extension.content.length > 0
|
|
305
|
+
? [`## ${extension.title}`, extension.content, ""]
|
|
306
|
+
: [`## ${extension.title}`, ""])
|
|
307
|
+
: []),
|
|
308
|
+
].join("\n").replace(/\n*$/, "\n");
|
|
309
|
+
}
|
|
310
|
+
export function renderCurrentContractView(model, observedLedgerHead) {
|
|
274
311
|
const entries = model.history.map(({ entry }) => entry);
|
|
275
312
|
return renderContractView({
|
|
276
313
|
id: model.contract,
|
|
277
314
|
entries,
|
|
278
|
-
ledgerHead:
|
|
315
|
+
ledgerHead: observedLedgerHead ?? undefined,
|
|
279
316
|
state: currentLifecycleState(model),
|
|
280
317
|
effectiveScope: model.effectiveScope,
|
|
281
318
|
});
|
package/build/core/contract.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { verificationDeclarationSchema } from "./verification-declaration.js";
|
|
2
3
|
export const contractStateSchema = z.enum(["bound", "active", "petitioned", "claimed", "forfeited"]);
|
|
3
4
|
export const contractRecordSchema = z
|
|
4
5
|
.object({
|
|
@@ -9,7 +10,7 @@ export const contractRecordSchema = z
|
|
|
9
10
|
design: z.string().trim().min(1),
|
|
10
11
|
scope: z.array(z.string().trim().min(1)).min(1),
|
|
11
12
|
criteria: z.array(z.string().trim().min(1)).min(1),
|
|
12
|
-
verification: z.array(
|
|
13
|
+
verification: z.array(verificationDeclarationSchema),
|
|
13
14
|
base: z.string().trim().min(1),
|
|
14
15
|
place: z.string().trim().min(1).nullable().optional(),
|
|
15
16
|
after: z.array(z.string().trim().min(1)),
|