@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
package/build/core/bind.js
CHANGED
|
@@ -2,78 +2,25 @@ import * as crypto from "node:crypto";
|
|
|
2
2
|
import { assertSettingsKnobUsable } from "../config/settings/disease.js";
|
|
3
3
|
import { loadKeiyakuSettings } from "../config/settings/loader.js";
|
|
4
4
|
import { FlowError } from "../flow-error.js";
|
|
5
|
-
import {
|
|
5
|
+
import { lookupCurrentContract, readRepositoryLifecycle, } from "./repository-ledger/read-model.js";
|
|
6
|
+
import { repositoryDeliveryRef, } from "./repository-ledger/codec.js";
|
|
7
|
+
import { publishBindTransaction, } from "./repository-ledger/write-transaction.js";
|
|
8
|
+
import { bindEffectiveScope } from "./repository-ledger/fold.js";
|
|
9
|
+
import { activeWorkspaceHereContracts } from "./repository-ledger/inventory.js";
|
|
6
10
|
import { describeMissingBindSections, materializeBindDraft, mergeBindDraft, missingBindDraftSections, parseBindDraft, parsePartialBindDraft, renderBindDraft, renderMissingBindSectionsTemplate, } from "./draft.js";
|
|
7
11
|
import { assertValidContractId, buildContractId, isCommissionId } from "./ids.js";
|
|
8
|
-
import {
|
|
12
|
+
import { admitScopeRegistration, } from "./registry.js";
|
|
9
13
|
import { resolveDefaultBranch } from "./status/drift.js";
|
|
10
14
|
import { normalizeTargetRef } from "./target-ref.js";
|
|
11
|
-
import { previewTaskBoardForBind,
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
constructor(contractId) {
|
|
18
|
-
super(`contract ledger already exists for ${contractId}`);
|
|
19
|
-
this.contractId = contractId;
|
|
20
|
-
this.name = "BindLedgerCollisionError";
|
|
21
|
-
}
|
|
22
|
-
}
|
|
15
|
+
import { previewTaskBoardForBind, readTaskNamespaces, replayPreparedTaskBindLocked, withPreparedTaskBind, } from "./task/index.js";
|
|
16
|
+
import { stableRepoRoot } from "./worktree-path.js";
|
|
17
|
+
import { allocateWorktreePlaceFromRead, } from "./bind-workspace.js";
|
|
18
|
+
import { committedOrThrow, reconcileCommittedBind, revalidateBindAuthority } from "./bind-reconciliation.js";
|
|
19
|
+
import { allocateTaskNamespace, taskNamespaceFromScope, taskNamespaceScope } from "./task/coordinate.js";
|
|
20
|
+
export { reconcileBindContract } from "./bind-reconciliation.js";
|
|
23
21
|
function randomBytesDefault() {
|
|
24
22
|
return [...crypto.randomBytes(10)];
|
|
25
23
|
}
|
|
26
|
-
async function appendBindFailedForfeit(cwd, id, actor, at) {
|
|
27
|
-
const forfeit = {
|
|
28
|
-
v: 1,
|
|
29
|
-
kind: "forfeit",
|
|
30
|
-
contract: id,
|
|
31
|
-
at,
|
|
32
|
-
actor,
|
|
33
|
-
data: {
|
|
34
|
-
reason: "bind-failed",
|
|
35
|
-
by: actor,
|
|
36
|
-
},
|
|
37
|
-
};
|
|
38
|
-
const stableRoot = await stableRepoRoot(cwd);
|
|
39
|
-
await reconcileTaskForfeit({
|
|
40
|
-
cwd: stableRoot,
|
|
41
|
-
contractId: id,
|
|
42
|
-
publishLedger: async () => {
|
|
43
|
-
const result = await appendWithRetry(stableRoot, activeLedgerRef(id), () => forfeit, { maxRetries: 1 });
|
|
44
|
-
if (!result.ok) {
|
|
45
|
-
throw new FlowError("INTERNAL_STATE", `BIND_FAILURE_FORFEIT_FAILED: failed to append bind-failed forfeit for ${id}: ${result.reason}`);
|
|
46
|
-
}
|
|
47
|
-
return result;
|
|
48
|
-
},
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
async function appendBootstrapEntry(cwd, id, actor, at, data) {
|
|
52
|
-
const entry = {
|
|
53
|
-
v: 1,
|
|
54
|
-
kind: "bootstrap",
|
|
55
|
-
contract: id,
|
|
56
|
-
at,
|
|
57
|
-
actor,
|
|
58
|
-
data,
|
|
59
|
-
};
|
|
60
|
-
// Bootstrap is legal only at index 1 immediately after the planned bind.
|
|
61
|
-
// If another entry advanced during bootstrap execution, abort rather than
|
|
62
|
-
// CAS-retrying into a late (corrupt) position.
|
|
63
|
-
const result = await appendWithRetry(cwd, activeLedgerRef(id), (_currentHead, currentEntries) => {
|
|
64
|
-
if (currentEntries.length !== 1)
|
|
65
|
-
return null;
|
|
66
|
-
const only = currentEntries[0];
|
|
67
|
-
if (!only || only.kind !== "bind" || only.contract !== id || !only.data.bootstrap) {
|
|
68
|
-
return null;
|
|
69
|
-
}
|
|
70
|
-
return entry;
|
|
71
|
-
});
|
|
72
|
-
if (!result.ok) {
|
|
73
|
-
throw new FlowError("INTERNAL_STATE", `BIND_BOOTSTRAP_APPEND_FAILED: failed to append bootstrap entry for ${id}: ${result.reason}`);
|
|
74
|
-
}
|
|
75
|
-
return result.head;
|
|
76
|
-
}
|
|
77
24
|
function resolvedBootstrapPlan(settings) {
|
|
78
25
|
if (!settings)
|
|
79
26
|
return undefined;
|
|
@@ -82,33 +29,55 @@ function resolvedBootstrapPlan(settings) {
|
|
|
82
29
|
timeoutMs: settings.timeoutMs,
|
|
83
30
|
};
|
|
84
31
|
}
|
|
85
|
-
function
|
|
86
|
-
|
|
32
|
+
function bindDraftWithTaskNamespace(draft, read, persistedNamespaces) {
|
|
33
|
+
const declared = draft.scope.flatMap((term) => {
|
|
34
|
+
const namespace = taskNamespaceFromScope(term);
|
|
35
|
+
return namespace ? [namespace] : [];
|
|
36
|
+
});
|
|
37
|
+
if (declared.length > 1)
|
|
38
|
+
throw new FlowError("INVALID_TARGET", "bind scope must carry exactly one contract task namespace");
|
|
39
|
+
const occupied = new Set(read.contracts.flatMap((contract) => contract.effectiveScope.flatMap((term) => {
|
|
40
|
+
const namespace = taskNamespaceFromScope(term);
|
|
41
|
+
return namespace ? [namespace] : [];
|
|
42
|
+
})));
|
|
43
|
+
for (const namespace of persistedNamespaces)
|
|
44
|
+
occupied.add(namespace);
|
|
45
|
+
if (declared.length === 1) {
|
|
46
|
+
if (occupied.has(declared[0]))
|
|
47
|
+
throw new FlowError("INVALID_TARGET", `task namespace ${declared[0]} is already occupied`);
|
|
48
|
+
return draft;
|
|
49
|
+
}
|
|
50
|
+
const namespace = allocateTaskNamespace(draft.title, occupied);
|
|
51
|
+
return { ...draft, scope: [...draft.scope, taskNamespaceScope(namespace)] };
|
|
87
52
|
}
|
|
88
53
|
function makeBindEntry(input) {
|
|
54
|
+
const common = {
|
|
55
|
+
title: input.draft.title,
|
|
56
|
+
context: input.draft.context,
|
|
57
|
+
objective: input.draft.objective,
|
|
58
|
+
design: input.draft.design,
|
|
59
|
+
scope: input.draft.scope,
|
|
60
|
+
criteria: input.draft.criteria,
|
|
61
|
+
verification: input.draft.verification,
|
|
62
|
+
extensions: input.draft.extensions,
|
|
63
|
+
base: input.base,
|
|
64
|
+
target: input.target,
|
|
65
|
+
...(input.after.length > 0 ? { after: input.after } : {}),
|
|
66
|
+
...(input.taskIds ? { taskIds: [...input.taskIds] } : {}),
|
|
67
|
+
};
|
|
89
68
|
return {
|
|
90
|
-
v:
|
|
69
|
+
v: 2,
|
|
91
70
|
kind: "bind",
|
|
92
71
|
contract: input.id,
|
|
93
72
|
at: input.at,
|
|
94
73
|
actor: input.actor,
|
|
95
|
-
data:
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
scope: input.draft.scope,
|
|
99
|
-
checks: input.draft.checks,
|
|
100
|
-
extensions: input.draft.extensions,
|
|
101
|
-
base: input.base,
|
|
102
|
-
target: input.target,
|
|
103
|
-
workspace: input.workspace,
|
|
104
|
-
after: input.after,
|
|
105
|
-
...(input.workspace === "worktree" ? { place: input.place ?? null } : {}),
|
|
106
|
-
...(input.bootstrap ? { bootstrap: input.bootstrap } : {}),
|
|
107
|
-
},
|
|
74
|
+
data: input.workspace === "worktree"
|
|
75
|
+
? { ...common, workspace: "worktree", place: input.place, ...(input.bootstrap ? { bootstrap: input.bootstrap } : {}) }
|
|
76
|
+
: { ...common, workspace: "here" },
|
|
108
77
|
};
|
|
109
78
|
}
|
|
110
79
|
function taskBindDefaults(task) {
|
|
111
|
-
const defaults = {
|
|
80
|
+
const defaults = { title: task.title };
|
|
112
81
|
const objective = task.body.trim();
|
|
113
82
|
if (objective.length > 0)
|
|
114
83
|
defaults.objective = objective;
|
|
@@ -126,7 +95,7 @@ function incompleteTaskBindError(taskId, missing) {
|
|
|
126
95
|
return new FlowError("BIND_TASK_INCOMPLETE", message);
|
|
127
96
|
}
|
|
128
97
|
/**
|
|
129
|
-
* Section-level merge for `bind --task`: task title/body are
|
|
98
|
+
* Section-level merge for `bind --task`: task title/body are title/objective
|
|
130
99
|
* defaults; stdin partial markdown sections override. Missing required sections
|
|
131
100
|
* fail with a paste-ready partial heredoc template.
|
|
132
101
|
*/
|
|
@@ -138,7 +107,7 @@ export function composeTaskBindDraft(input) {
|
|
|
138
107
|
}
|
|
139
108
|
return materializeBindDraft(merged);
|
|
140
109
|
}
|
|
141
|
-
|
|
110
|
+
function validateBindAfter(read, after) {
|
|
142
111
|
const unique = new Set();
|
|
143
112
|
for (const contractId of after) {
|
|
144
113
|
if (unique.has(contractId)) {
|
|
@@ -148,20 +117,44 @@ async function validateBindAfter(cwd, after) {
|
|
|
148
117
|
if (!isCommissionId(contractId)) {
|
|
149
118
|
throw new FlowError("INVALID_TARGET", `after contract ${contractId} is not a valid contract id`);
|
|
150
119
|
}
|
|
151
|
-
|
|
152
|
-
|
|
120
|
+
const outcome = lookupCurrentContract(read, contractId);
|
|
121
|
+
if (outcome.status !== "healthy") {
|
|
122
|
+
throw new FlowError("INVALID_TARGET", `after contract ${contractId} is not a healthy terminal contract`);
|
|
153
123
|
}
|
|
124
|
+
if (!["claimed", "forfeited"].includes(outcome.model.phase)) {
|
|
125
|
+
throw new FlowError("INVALID_TARGET", `after contract ${contractId} is not terminal`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function assertContractAbsent(read, contractId) {
|
|
130
|
+
const outcome = lookupCurrentContract(read, contractId);
|
|
131
|
+
if (outcome.status === "absent")
|
|
132
|
+
return;
|
|
133
|
+
if (outcome.status === "indeterminate") {
|
|
134
|
+
throw new FlowError("INTERNAL_STATE", `BIND_ADMISSION_INDETERMINATE: cannot prove contract ${contractId} absent`);
|
|
154
135
|
}
|
|
136
|
+
throw new FlowError("CONCURRENT_MODIFICATION", `CONTRACT_ID_COLLISION: contract ${contractId} already exists`);
|
|
137
|
+
}
|
|
138
|
+
function admitWorkspaceOccupancy(read, workspace) {
|
|
139
|
+
if (workspace === "worktree")
|
|
140
|
+
return;
|
|
141
|
+
const occupants = activeWorkspaceHereContracts(read);
|
|
142
|
+
if (occupants.length === 0)
|
|
143
|
+
return;
|
|
144
|
+
throw new FlowError("INVALID_TARGET", `workspace here is occupied by nonterminal contract ${occupants.join(", ")}`);
|
|
155
145
|
}
|
|
156
146
|
/**
|
|
157
147
|
* The common read-only bind admission candidate. Mutation uses this candidate
|
|
158
148
|
* before task settlement; preview stops after it has observed all admission
|
|
159
149
|
* facts and rendered the canonical draft.
|
|
160
150
|
*/
|
|
161
|
-
async function admitBindCandidate(input, draft) {
|
|
162
|
-
|
|
151
|
+
async function admitBindCandidate(input, draft, read) {
|
|
152
|
+
draft = bindDraftWithTaskNamespace(draft, read, await readTaskNamespaces(input.cwd));
|
|
153
|
+
admitWorkspaceOccupancy(read, input.workspace);
|
|
154
|
+
const id = buildContractId({ title: draft.title, nowMs: input.nowMs, randomBytes: input.randomBytes });
|
|
163
155
|
await assertValidContractId(input.cwd, id);
|
|
164
|
-
|
|
156
|
+
assertContractAbsent(read, id);
|
|
157
|
+
validateBindAfter(read, input.after);
|
|
165
158
|
const provisionalEntry = makeBindEntry({
|
|
166
159
|
id,
|
|
167
160
|
at: new Date(input.nowMs).toISOString(),
|
|
@@ -175,14 +168,14 @@ async function admitBindCandidate(input, draft) {
|
|
|
175
168
|
workspace: input.workspace,
|
|
176
169
|
place: input.place,
|
|
177
170
|
bootstrap: input.bootstrapPlan,
|
|
171
|
+
taskIds: input.taskIds ? [...input.taskIds] : undefined,
|
|
178
172
|
});
|
|
179
|
-
const
|
|
180
|
-
const scopeOverlaps = await previewScopeRegistration(input.cwd, {
|
|
173
|
+
const scopeOverlaps = await admitScopeRegistration(input.cwd, {
|
|
181
174
|
contractId: id,
|
|
182
|
-
scope:
|
|
183
|
-
tree,
|
|
175
|
+
scope: bindEffectiveScope(provisionalEntry),
|
|
176
|
+
tree: input.base,
|
|
184
177
|
exclusive: input.exclusive,
|
|
185
|
-
});
|
|
178
|
+
}, read);
|
|
186
179
|
return {
|
|
187
180
|
id,
|
|
188
181
|
draft,
|
|
@@ -209,8 +202,9 @@ export async function previewBindContract(input) {
|
|
|
209
202
|
assertSettingsKnobUsable(loadedSettings, "worktreeBootstrap");
|
|
210
203
|
bootstrapPlan = resolvedBootstrapPlan(loadedSettings.knobs?.worktreeBootstrap);
|
|
211
204
|
}
|
|
205
|
+
const read = await readRepositoryLifecycle(input.cwd);
|
|
212
206
|
const randomBytes = input.randomBytes?.(0) ?? randomBytesDefault();
|
|
213
|
-
const place = workspace === "worktree" ?
|
|
207
|
+
const place = workspace === "worktree" ? allocateWorktreePlaceFromRead(read, randomBytes) : undefined;
|
|
214
208
|
const taskAdmission = isTaskBound
|
|
215
209
|
? await previewTaskBoardForBind({ cwd: input.cwd, taskIds: input.taskIds })
|
|
216
210
|
: { tasks: [] };
|
|
@@ -229,205 +223,180 @@ export async function previewBindContract(input) {
|
|
|
229
223
|
target,
|
|
230
224
|
bootstrapPlan,
|
|
231
225
|
place,
|
|
232
|
-
|
|
226
|
+
...(isTaskBound ? { taskIds: input.taskIds } : {}),
|
|
227
|
+
}, draft, read);
|
|
233
228
|
}
|
|
234
229
|
export async function bindContract(input) {
|
|
230
|
+
const stableRoot = await stableRepoRoot(input.cwd);
|
|
235
231
|
const isTaskBound = input.taskIds !== undefined && input.taskIds.length > 0;
|
|
236
232
|
const ordinaryDraft = !isTaskBound ? parseBindDraft(input.draft) : undefined;
|
|
237
233
|
const workspace = input.workspace ?? "here";
|
|
238
234
|
const at = new Date(input.nowMs).toISOString();
|
|
239
|
-
const
|
|
240
|
-
|
|
235
|
+
const after = input.after ?? [];
|
|
236
|
+
const defaultBranch = await resolveDefaultBranch(stableRoot);
|
|
237
|
+
if (!defaultBranch)
|
|
241
238
|
throw new FlowError("INTERNAL_STATE", "cannot bind: no default branch");
|
|
242
|
-
}
|
|
243
239
|
const target = normalizeTargetRef(defaultBranch.branch);
|
|
244
|
-
const
|
|
240
|
+
const publishBind = input.dependencies?.publishBind ?? publishBindTransaction;
|
|
241
|
+
const persistedTaskNamespaces = await readTaskNamespaces(stableRoot);
|
|
242
|
+
const loadedSettings = await loadKeiyakuSettings(stableRoot);
|
|
245
243
|
let bootstrapPlan;
|
|
246
244
|
if (workspace === "worktree") {
|
|
247
245
|
assertSettingsKnobUsable(loadedSettings, "worktreeBootstrap");
|
|
248
246
|
bootstrapPlan = resolvedBootstrapPlan(loadedSettings.knobs?.worktreeBootstrap);
|
|
249
247
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
base: defaultBranch.head,
|
|
266
|
-
target,
|
|
267
|
-
bootstrapPlan,
|
|
268
|
-
place,
|
|
269
|
-
}, draft);
|
|
270
|
-
const id = candidate.id;
|
|
271
|
-
const ref = activeLedgerRef(id);
|
|
272
|
-
return {
|
|
273
|
-
contractId: id,
|
|
274
|
-
publishLedger: async (base) => {
|
|
275
|
-
const entry = makeBindEntry({
|
|
276
|
-
id,
|
|
277
|
-
at,
|
|
278
|
-
actor: input.actor,
|
|
279
|
-
draft,
|
|
280
|
-
base,
|
|
281
|
-
target,
|
|
282
|
-
after: input.after ?? [],
|
|
283
|
-
workspace,
|
|
284
|
-
place,
|
|
285
|
-
bootstrap: bootstrapPlan,
|
|
286
|
-
});
|
|
287
|
-
const appended = await appendEntry(input.cwd, ref, entry, null);
|
|
288
|
-
if (!appended.ok)
|
|
289
|
-
throw new BindLedgerCollisionError(id);
|
|
290
|
-
return { entry, appended };
|
|
291
|
-
},
|
|
292
|
-
};
|
|
293
|
-
};
|
|
294
|
-
settlement = !isTaskBound
|
|
295
|
-
? await (async () => {
|
|
296
|
-
const prepared = await prepare(ordinaryDraft);
|
|
297
|
-
return settleTaskBoardForBind({ cwd: input.cwd, signal: input.signal, ...prepared });
|
|
298
|
-
})()
|
|
299
|
-
: await (async () => {
|
|
300
|
-
return settleTaskBoardForBind({
|
|
301
|
-
cwd: input.cwd,
|
|
302
|
-
taskIds: input.taskIds,
|
|
303
|
-
signal: input.signal,
|
|
304
|
-
prepare: async (tasks) => prepare(composeTaskBindDraft({ task: tasks[0], draft: input.draft })),
|
|
305
|
-
});
|
|
306
|
-
})();
|
|
307
|
-
}
|
|
308
|
-
catch (error) {
|
|
309
|
-
if (error instanceof BindLedgerCollisionError) {
|
|
310
|
-
// The selected task's locked snapshot chose this attempted identity.
|
|
311
|
-
// Record the concrete id only when preparation reached publication.
|
|
312
|
-
collidedIds.push(error.contractId);
|
|
248
|
+
let selectedDraft;
|
|
249
|
+
let selectedPlace;
|
|
250
|
+
let selectedId;
|
|
251
|
+
const selectCandidate = async (draft, read) => {
|
|
252
|
+
admitWorkspaceOccupancy(read, workspace);
|
|
253
|
+
const collided = [];
|
|
254
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
255
|
+
const randomBytes = input.randomBytes?.(attempt) ?? randomBytesDefault();
|
|
256
|
+
const place = workspace === "worktree" ? allocateWorktreePlaceFromRead(read, randomBytes) : undefined;
|
|
257
|
+
const id = buildContractId({ title: draft.title, nowMs: input.nowMs, randomBytes });
|
|
258
|
+
const outcome = lookupCurrentContract(read, id);
|
|
259
|
+
if (outcome.status === "indeterminate")
|
|
260
|
+
assertContractAbsent(read, id);
|
|
261
|
+
if (outcome.status !== "absent") {
|
|
262
|
+
collided.push(id);
|
|
313
263
|
continue;
|
|
314
264
|
}
|
|
315
|
-
|
|
265
|
+
await assertValidContractId(stableRoot, id);
|
|
266
|
+
selectedDraft = bindDraftWithTaskNamespace(draft, read, persistedTaskNamespaces);
|
|
267
|
+
selectedPlace = place;
|
|
268
|
+
selectedId = id;
|
|
269
|
+
return { contractId: id, at };
|
|
316
270
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
exclusive: input.exclusive,
|
|
337
|
-
});
|
|
338
|
-
registryRegistered = true;
|
|
339
|
-
if (workspace === "worktree") {
|
|
340
|
-
await createContractBranch(input.cwd, branch, base);
|
|
341
|
-
resources.branch = branch;
|
|
342
|
-
await (input.createWorktree ?? defaultCreateWorktree)({
|
|
343
|
-
cwd: input.cwd,
|
|
344
|
-
id,
|
|
345
|
-
branch,
|
|
346
|
-
base,
|
|
347
|
-
worktreePath,
|
|
348
|
-
});
|
|
349
|
-
resources.worktreePath = worktreePath;
|
|
350
|
-
}
|
|
351
|
-
if (bootstrapPlan) {
|
|
352
|
-
input.onProgress?.(bootstrapProgressLine(bootstrapPlan.command));
|
|
353
|
-
const runner = input.bootstrapRunner ?? runWorktreeBootstrap;
|
|
354
|
-
const observation = await runner({ cwd: worktreePath, plan: bootstrapPlan }, input.bootstrapDependencies);
|
|
355
|
-
ledgerHead = await appendBootstrapEntry(input.cwd, id, input.actor, at, bootstrapEntryDataFromObservation(observation));
|
|
356
|
-
bootstrapRecorded = true;
|
|
357
|
-
if (!isBootstrapSuccess(observation)) {
|
|
358
|
-
throw new FlowError("INTERNAL_STATE", `BIND_BOOTSTRAP_FAILED: worktree bootstrap failed with ${observation.result.kind}`);
|
|
271
|
+
throw new FlowError("CONCURRENT_MODIFICATION", `CONTRACT_ID_COLLISION: contract id collision after two attempts: ${collided.join(", ")}`);
|
|
272
|
+
};
|
|
273
|
+
let entry;
|
|
274
|
+
let admittedScopeOverlaps = [];
|
|
275
|
+
let preparedTask;
|
|
276
|
+
if (isTaskBound) {
|
|
277
|
+
let lockedRead;
|
|
278
|
+
const settled = await withPreparedTaskBind({
|
|
279
|
+
cwd: stableRoot,
|
|
280
|
+
taskIds: input.taskIds,
|
|
281
|
+
signal: input.signal,
|
|
282
|
+
prepare: async (tasks) => {
|
|
283
|
+
lockedRead = await readRepositoryLifecycle(stableRoot);
|
|
284
|
+
validateBindAfter(lockedRead, after);
|
|
285
|
+
return await selectCandidate(composeTaskBindDraft({ task: tasks[0], draft: input.draft }), lockedRead);
|
|
286
|
+
},
|
|
287
|
+
transact: async (prepared, taskTransaction) => {
|
|
288
|
+
if (prepared.boardRef !== target) {
|
|
289
|
+
throw new FlowError("CONCURRENT_MODIFICATION", "locked task-board ref does not match the selected bind target");
|
|
359
290
|
}
|
|
291
|
+
let attemptRead = lockedRead;
|
|
292
|
+
let lastRefusal;
|
|
293
|
+
for (let attempt = 0; attempt < 4; attempt += 1) {
|
|
294
|
+
admitWorkspaceOccupancy(attemptRead, workspace);
|
|
295
|
+
validateBindAfter(attemptRead, after);
|
|
296
|
+
assertContractAbsent(attemptRead, selectedId);
|
|
297
|
+
selectedPlace = workspace === "worktree"
|
|
298
|
+
? allocateWorktreePlaceFromRead(attemptRead, input.randomBytes?.(attempt) ?? randomBytesDefault())
|
|
299
|
+
: undefined;
|
|
300
|
+
entry = makeBindEntry({
|
|
301
|
+
id: selectedId, at, actor: input.actor, draft: selectedDraft, base: prepared.entryBase,
|
|
302
|
+
target, after, workspace, place: selectedPlace, bootstrap: bootstrapPlan, taskIds: prepared.taskIds,
|
|
303
|
+
});
|
|
304
|
+
admittedScopeOverlaps = await admitScopeRegistration(stableRoot, {
|
|
305
|
+
contractId: entry.contract,
|
|
306
|
+
scope: bindEffectiveScope(entry),
|
|
307
|
+
tree: entry.data.base,
|
|
308
|
+
...(input.exclusive === undefined ? {} : { exclusive: input.exclusive }),
|
|
309
|
+
}, attemptRead);
|
|
310
|
+
await revalidateBindAuthority({
|
|
311
|
+
cwd: stableRoot,
|
|
312
|
+
target,
|
|
313
|
+
targetHead: prepared.expectedHead,
|
|
314
|
+
deliveryRef: repositoryDeliveryRef(entry.contract),
|
|
315
|
+
});
|
|
316
|
+
const written = await publishBind({
|
|
317
|
+
cwd: stableRoot,
|
|
318
|
+
entry,
|
|
319
|
+
expectedLedgerHead: attemptRead.observedLedgerHead,
|
|
320
|
+
delivery: { ref: repositoryDeliveryRef(entry.contract), head: prepared.entryBase },
|
|
321
|
+
taskBoard: prepared.taskBoard,
|
|
322
|
+
});
|
|
323
|
+
if (written.family === "committed") {
|
|
324
|
+
try {
|
|
325
|
+
await replayPreparedTaskBindLocked(taskTransaction, prepared);
|
|
326
|
+
}
|
|
327
|
+
catch {
|
|
328
|
+
// Authority is already committed. The ordinary derived replay
|
|
329
|
+
// below owns truthful retry/contained classification.
|
|
330
|
+
}
|
|
331
|
+
return written;
|
|
332
|
+
}
|
|
333
|
+
if (written.family === "indeterminate")
|
|
334
|
+
committedOrThrow(written, "BIND_TRANSACTION");
|
|
335
|
+
lastRefusal = written;
|
|
336
|
+
attemptRead = await readRepositoryLifecycle(stableRoot);
|
|
337
|
+
}
|
|
338
|
+
return lastRefusal;
|
|
339
|
+
},
|
|
340
|
+
});
|
|
341
|
+
preparedTask = settled.preparation;
|
|
342
|
+
committedOrThrow(settled.result, "BIND_TRANSACTION");
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
let committed;
|
|
346
|
+
for (let attempt = 0; attempt < 4; attempt += 1) {
|
|
347
|
+
const [read, branch] = await Promise.all([
|
|
348
|
+
readRepositoryLifecycle(stableRoot),
|
|
349
|
+
resolveDefaultBranch(stableRoot),
|
|
350
|
+
]);
|
|
351
|
+
if (!branch || normalizeTargetRef(branch.branch) !== target) {
|
|
352
|
+
throw new FlowError("CONCURRENT_MODIFICATION", "bind target changed during admission");
|
|
360
353
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
id,
|
|
367
|
-
entries: ledger.entries,
|
|
354
|
+
validateBindAfter(read, after);
|
|
355
|
+
await selectCandidate(ordinaryDraft, read);
|
|
356
|
+
entry = makeBindEntry({
|
|
357
|
+
id: selectedId, at, actor: input.actor, draft: selectedDraft, base: branch.head,
|
|
358
|
+
target, after, workspace, place: selectedPlace, bootstrap: bootstrapPlan,
|
|
368
359
|
});
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
360
|
+
admittedScopeOverlaps = await admitScopeRegistration(stableRoot, {
|
|
361
|
+
contractId: entry.contract,
|
|
362
|
+
scope: bindEffectiveScope(entry),
|
|
363
|
+
tree: entry.data.base,
|
|
364
|
+
...(input.exclusive === undefined ? {} : { exclusive: input.exclusive }),
|
|
365
|
+
}, read);
|
|
366
|
+
await revalidateBindAuthority({
|
|
367
|
+
cwd: stableRoot,
|
|
372
368
|
target,
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
if (!bootstrapRecorded) {
|
|
386
|
-
// Non-bootstrap failure: cleanup first remains compatible with prior path,
|
|
387
|
-
// then forfeit. When bootstrap already recorded its failed fact, forfeit
|
|
388
|
-
// must precede cleanup.
|
|
389
|
-
cleanupFailures.push(...(await cleanupCreatedBindResources(input.cwd, resources)));
|
|
390
|
-
try {
|
|
391
|
-
await appendBindFailedForfeit(input.cwd, id, input.actor, at);
|
|
392
|
-
}
|
|
393
|
-
catch (forfeitError) {
|
|
394
|
-
cleanupFailures.push(forfeitError instanceof Error ? forfeitError.message : String(forfeitError));
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
else {
|
|
398
|
-
try {
|
|
399
|
-
await appendBindFailedForfeit(input.cwd, id, input.actor, at);
|
|
400
|
-
}
|
|
401
|
-
catch (forfeitError) {
|
|
402
|
-
cleanupFailures.push(forfeitError instanceof Error ? forfeitError.message : String(forfeitError));
|
|
403
|
-
}
|
|
404
|
-
cleanupFailures.push(...(await cleanupCreatedBindResources(input.cwd, resources)));
|
|
405
|
-
}
|
|
406
|
-
if (registryRegistered) {
|
|
407
|
-
try {
|
|
408
|
-
await releaseScope(input.cwd, { contractId: id, actor: input.actor, at });
|
|
409
|
-
}
|
|
410
|
-
catch (releaseError) {
|
|
411
|
-
cleanupFailures.push(releaseError instanceof Error ? releaseError.message : String(releaseError));
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
if (error instanceof FlowError && error.code === "ACTIVE_KEIYAKU_EXISTS") {
|
|
415
|
-
const message = [
|
|
416
|
-
`bind rejected and forfeited as ${id}: ${error.message}`,
|
|
417
|
-
cleanupFailures.length > 0 ? `cleanup failed: ${cleanupFailures.join("; ")}` : "",
|
|
418
|
-
]
|
|
419
|
-
.filter(Boolean)
|
|
420
|
-
.join("; ");
|
|
421
|
-
throw new FlowError("ACTIVE_KEIYAKU_EXISTS", message, { cause: error });
|
|
369
|
+
targetHead: branch.head,
|
|
370
|
+
deliveryRef: repositoryDeliveryRef(entry.contract),
|
|
371
|
+
});
|
|
372
|
+
const bindWrite = await publishBind({
|
|
373
|
+
cwd: stableRoot,
|
|
374
|
+
entry,
|
|
375
|
+
expectedLedgerHead: read.observedLedgerHead,
|
|
376
|
+
delivery: { ref: repositoryDeliveryRef(entry.contract), head: entry.data.base },
|
|
377
|
+
});
|
|
378
|
+
if (bindWrite.family === "committed") {
|
|
379
|
+
committed = bindWrite;
|
|
380
|
+
break;
|
|
422
381
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
.join("; ");
|
|
429
|
-
throw new FlowError("INTERNAL_STATE", `BIND_WORKSPACE_FAILED: ${message}`, { cause: error });
|
|
382
|
+
if (bindWrite.family === "indeterminate")
|
|
383
|
+
committedOrThrow(bindWrite, "BIND_TRANSACTION");
|
|
384
|
+
}
|
|
385
|
+
if (!committed) {
|
|
386
|
+
throw new FlowError("CONCURRENT_MODIFICATION", "BIND_TRANSACTION_REFUSED: repository authority changed during four bind attempts");
|
|
430
387
|
}
|
|
431
388
|
}
|
|
432
|
-
|
|
389
|
+
return await reconcileCommittedBind({
|
|
390
|
+
cwd: stableRoot,
|
|
391
|
+
contractId: entry.contract,
|
|
392
|
+
actor: input.actor,
|
|
393
|
+
nowMs: input.nowMs,
|
|
394
|
+
...(input.signal ? { signal: input.signal } : {}),
|
|
395
|
+
...(input.createWorktree ? { createWorktree: input.createWorktree } : {}),
|
|
396
|
+
...(input.onProgress ? { onProgress: input.onProgress } : {}),
|
|
397
|
+
...(input.bootstrapRunner ? { bootstrapRunner: input.bootstrapRunner } : {}),
|
|
398
|
+
...(input.bootstrapDependencies ? { bootstrapDependencies: input.bootstrapDependencies } : {}),
|
|
399
|
+
...(preparedTask ? { preparedTask } : {}),
|
|
400
|
+
dependencies: input.dependencies,
|
|
401
|
+
}, admittedScopeOverlaps);
|
|
433
402
|
}
|
package/build/core/call/call.js
CHANGED
|
@@ -83,6 +83,7 @@ export async function runCall(input) {
|
|
|
83
83
|
mode: input.mode ?? "call",
|
|
84
84
|
repoCwd,
|
|
85
85
|
reviveBinding: reviveArtifact?.provenance.binding,
|
|
86
|
+
signal: input.signal,
|
|
86
87
|
});
|
|
87
88
|
return await withResolvedAddress(keiyakuContext.address, async () => {
|
|
88
89
|
const executionCwd = keiyakuContext.effectiveCwd;
|