@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,469 @@
|
|
|
1
|
+
import { openTaskAdmissionEvidenceReader, readTaskAdmissionEvidenceAtCommit, } from "../task/settlement-git.js";
|
|
2
|
+
import { repositoryDeliveryRef, } from "./codec.js";
|
|
3
|
+
import { observeRepositoryLedgerHead, readAcceptedRepositoryFold } from "./accepted-fold-read.js";
|
|
4
|
+
import { advanceRepositoryCurrentState, discardRepositoryCurrentState, publishRepositoryCurrentState, readPinnedCurrentContract, readPinnedRepositoryLifecycle, readRepositoryCurrentStateNodeIds, readRepositoryCurrentStatePin } from "./current-state-store.js";
|
|
5
|
+
import { IncrementalLedgerValidationError, readIncrementalRepositorySegment } from "./incremental-read.js";
|
|
6
|
+
import { RepositoryObjectError } from "./git-object-store.js";
|
|
7
|
+
/** Public lifecycle state derived only from the accepted repository-ledger fold. */
|
|
8
|
+
export function currentLifecycleState(model) {
|
|
9
|
+
switch (model.phase) {
|
|
10
|
+
case "active":
|
|
11
|
+
return model.history.every(({ entry }) => entry.kind === "bind" || entry.kind === "bootstrap")
|
|
12
|
+
? { state: "bound" }
|
|
13
|
+
: { state: "active" };
|
|
14
|
+
case "sealed":
|
|
15
|
+
return { state: "active" };
|
|
16
|
+
case "awaiting-verdict":
|
|
17
|
+
case "approved":
|
|
18
|
+
return { state: "petitioned" };
|
|
19
|
+
case "claimed":
|
|
20
|
+
return { state: "claimed" };
|
|
21
|
+
case "forfeited":
|
|
22
|
+
return { state: "forfeited" };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function deepFreeze(value) {
|
|
26
|
+
if (value === null || typeof value !== "object" || Object.isFrozen(value))
|
|
27
|
+
return value;
|
|
28
|
+
Object.freeze(value);
|
|
29
|
+
for (const child of Object.values(value))
|
|
30
|
+
deepFreeze(child);
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
function acceptedHistory(state, nodes) {
|
|
34
|
+
return state.entryObjectIds.map((objectId) => {
|
|
35
|
+
const node = nodes.get(objectId);
|
|
36
|
+
if (node?.type !== "lifecycle") {
|
|
37
|
+
throw new Error(`accepted lifecycle entry ${objectId} is absent from typed traversal`);
|
|
38
|
+
}
|
|
39
|
+
return { objectId, entry: structuredClone(node.entry) };
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
function findLastRow(history, kind) {
|
|
43
|
+
for (let index = history.length - 1; index >= 0; index -= 1) {
|
|
44
|
+
const row = history[index];
|
|
45
|
+
if (row.entry.kind === kind) {
|
|
46
|
+
return row;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
function unavailableHistoryFact(reason) {
|
|
52
|
+
return { retention: "unavailable", reason };
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Projects only accepted lifecycle rows into the retained consequences history
|
|
56
|
+
* may expose. Review responses are not lifecycle records, so they remain
|
|
57
|
+
* explicitly unavailable here rather than being reconstructed from artifacts.
|
|
58
|
+
*/
|
|
59
|
+
export function unavailableContractHistoryConsequences(reason) {
|
|
60
|
+
const unavailable = unavailableHistoryFact(reason);
|
|
61
|
+
return deepFreeze({
|
|
62
|
+
retention: "unavailable",
|
|
63
|
+
reason,
|
|
64
|
+
review: unavailable,
|
|
65
|
+
petition: unavailable,
|
|
66
|
+
verdict: unavailable,
|
|
67
|
+
claim: unavailable,
|
|
68
|
+
landedCommit: unavailable,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
export function historyConsequencesForContract(outcome) {
|
|
72
|
+
if (outcome.status === "absent")
|
|
73
|
+
return unavailableContractHistoryConsequences("contract-absent");
|
|
74
|
+
if (outcome.status === "diseased")
|
|
75
|
+
return unavailableContractHistoryConsequences("lifecycle-diseased");
|
|
76
|
+
if (outcome.status === "isolated")
|
|
77
|
+
return unavailableContractHistoryConsequences("lifecycle-isolated");
|
|
78
|
+
if (outcome.status === "indeterminate")
|
|
79
|
+
return unavailableContractHistoryConsequences("lifecycle-indeterminate");
|
|
80
|
+
return historyConsequencesForAcceptedHistory(outcome.model.history);
|
|
81
|
+
}
|
|
82
|
+
function historyConsequencesForAcceptedHistory(history) {
|
|
83
|
+
const petition = findLastRow(history, "petition");
|
|
84
|
+
const verdict = findLastRow(history, "verdict");
|
|
85
|
+
const claim = findLastRow(history, "claim");
|
|
86
|
+
const missing = unavailableHistoryFact("not-recorded");
|
|
87
|
+
const review = unavailableHistoryFact("not-retained-in-lifecycle");
|
|
88
|
+
const retainedClaim = claim
|
|
89
|
+
? {
|
|
90
|
+
retention: "retained",
|
|
91
|
+
objectId: claim.objectId,
|
|
92
|
+
target: claim.entry.data.target,
|
|
93
|
+
base: claim.entry.data.base,
|
|
94
|
+
targetHead: claim.entry.data.targetHead,
|
|
95
|
+
deliveryHead: claim.entry.data.deliveryHead,
|
|
96
|
+
mergeCommit: claim.entry.data.mergeCommit,
|
|
97
|
+
}
|
|
98
|
+
: missing;
|
|
99
|
+
return deepFreeze({
|
|
100
|
+
retention: "retained",
|
|
101
|
+
review,
|
|
102
|
+
petition: petition
|
|
103
|
+
? {
|
|
104
|
+
retention: "retained",
|
|
105
|
+
objectId: petition.objectId,
|
|
106
|
+
intent: petition.entry.data.intent,
|
|
107
|
+
target: petition.entry.data.target,
|
|
108
|
+
targetHead: petition.entry.data.intent === "claim" ? petition.entry.data.targetHead : null,
|
|
109
|
+
seat: petition.entry.data.seat,
|
|
110
|
+
}
|
|
111
|
+
: missing,
|
|
112
|
+
verdict: verdict
|
|
113
|
+
? {
|
|
114
|
+
retention: "retained",
|
|
115
|
+
objectId: verdict.objectId,
|
|
116
|
+
result: verdict.entry.data.result,
|
|
117
|
+
summary: verdict.entry.data.summary,
|
|
118
|
+
}
|
|
119
|
+
: missing,
|
|
120
|
+
claim: retainedClaim,
|
|
121
|
+
landedCommit: claim
|
|
122
|
+
? { retention: "retained", commit: claim.entry.data.mergeCommit }
|
|
123
|
+
: missing,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
function currentIntent(state, history) {
|
|
127
|
+
const explicit = findLastRow(history, "arc");
|
|
128
|
+
if (explicit) {
|
|
129
|
+
return {
|
|
130
|
+
arc: explicit.entry.data.arc,
|
|
131
|
+
openedBy: explicit.objectId,
|
|
132
|
+
title: explicit.entry.data.title,
|
|
133
|
+
objective: explicit.entry.data.objective,
|
|
134
|
+
brief: explicit.entry.data.brief,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
arc: 0,
|
|
139
|
+
openedBy: state.entryObjectIds[0],
|
|
140
|
+
title: state.bind.data.title,
|
|
141
|
+
objective: state.bind.data.objective,
|
|
142
|
+
brief: null,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function petitionState(state, history) {
|
|
146
|
+
// Terminal rows retain petition/verdict history, but no longer represent a live petition.
|
|
147
|
+
if (state.terminalObjectId !== null)
|
|
148
|
+
return { state: "none" };
|
|
149
|
+
if (!state.petition)
|
|
150
|
+
return { state: "none" };
|
|
151
|
+
const petition = findLastRow(history, "petition");
|
|
152
|
+
if (!petition)
|
|
153
|
+
throw new Error(`folded petition for ${state.contract} is absent from accepted history`);
|
|
154
|
+
const data = state.petition.data;
|
|
155
|
+
const common = {
|
|
156
|
+
objectId: petition.objectId,
|
|
157
|
+
intent: data.intent,
|
|
158
|
+
seat: data.seat,
|
|
159
|
+
target: data.target,
|
|
160
|
+
targetHead: data.intent === "claim" ? data.targetHead : null,
|
|
161
|
+
};
|
|
162
|
+
if (state.phase !== "approved" || !state.verdict)
|
|
163
|
+
return { state: "awaiting-verdict", ...common };
|
|
164
|
+
const verdict = state.verdict ? findLastRow(history, "verdict") : undefined;
|
|
165
|
+
if (!verdict) {
|
|
166
|
+
throw new Error(`folded verdict for ${state.contract} is absent from accepted history`);
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
state: "approved",
|
|
170
|
+
...common,
|
|
171
|
+
verdictObjectId: verdict.objectId,
|
|
172
|
+
summary: state.verdict.data.summary,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function bootstrapReplay(state) {
|
|
176
|
+
const plan = state.bind.data.workspace === "worktree" ? state.bind.data.bootstrap : undefined;
|
|
177
|
+
if (!plan)
|
|
178
|
+
return { state: "not-planned" };
|
|
179
|
+
const immutablePlan = { command: [...plan.command], timeoutMs: plan.timeoutMs };
|
|
180
|
+
if (!state.bootstrap)
|
|
181
|
+
return { state: "pending", plan: immutablePlan };
|
|
182
|
+
const observed = {
|
|
183
|
+
plan: immutablePlan,
|
|
184
|
+
objectId: state.bootstrap.objectId,
|
|
185
|
+
result: structuredClone(state.bootstrap.result),
|
|
186
|
+
};
|
|
187
|
+
if (!state.bootstrap.failed)
|
|
188
|
+
return { state: "succeeded", ...observed };
|
|
189
|
+
if (state.bootstrapFailure === "awaiting-forfeit") {
|
|
190
|
+
return { state: "failed-awaiting-forfeit", ...observed };
|
|
191
|
+
}
|
|
192
|
+
if (state.bootstrapFailure === "interrupted") {
|
|
193
|
+
return { state: "failed-interrupted", ...observed };
|
|
194
|
+
}
|
|
195
|
+
if (state.terminal?.kind === "forfeit" &&
|
|
196
|
+
state.terminal.data.reason === "bind-failed" &&
|
|
197
|
+
state.terminalObjectId !== null) {
|
|
198
|
+
return { state: "failed-reconciled", ...observed, forfeitObjectId: state.terminalObjectId };
|
|
199
|
+
}
|
|
200
|
+
throw new Error(`failed bootstrap for ${state.contract} has no folded replay disposition`);
|
|
201
|
+
}
|
|
202
|
+
function lifecycleModel(observedLedgerHead, state, nodes, taskAdmissionEvidence) {
|
|
203
|
+
const history = acceptedHistory(state, nodes);
|
|
204
|
+
const verdict = findLastRow(history, "verdict");
|
|
205
|
+
const released = state.terminalObjectId !== null;
|
|
206
|
+
const taskReleased = state.terminal?.kind === "forfeit";
|
|
207
|
+
const selectedTasks = [...(state.bind.data.taskIds ?? [])];
|
|
208
|
+
return deepFreeze({
|
|
209
|
+
observedLedgerHead,
|
|
210
|
+
contract: state.contract,
|
|
211
|
+
phase: state.phase,
|
|
212
|
+
intent: currentIntent(state, history),
|
|
213
|
+
receiptSegments: structuredClone(state.segments),
|
|
214
|
+
seal: structuredClone(state.segments.at(-1)?.seal ?? null),
|
|
215
|
+
bootstrapReplay: bootstrapReplay(state),
|
|
216
|
+
petition: petitionState(state, history),
|
|
217
|
+
verdict: verdict
|
|
218
|
+
? { objectId: verdict.objectId, result: verdict.entry.data.result, summary: verdict.entry.data.summary }
|
|
219
|
+
: null,
|
|
220
|
+
pins: {
|
|
221
|
+
base: state.base,
|
|
222
|
+
delivery: state.deliveryHead,
|
|
223
|
+
target: { ref: state.target, approvedHead: state.approvedTargetSnapshot?.targetHead ?? null },
|
|
224
|
+
},
|
|
225
|
+
effectiveScope: [...state.effectiveScope],
|
|
226
|
+
causal: {
|
|
227
|
+
after: [...(state.bind.data.after ?? [])],
|
|
228
|
+
needs: structuredClone(taskAdmissionEvidence),
|
|
229
|
+
},
|
|
230
|
+
resources: {
|
|
231
|
+
deliveryRef: repositoryDeliveryRef(state.contract),
|
|
232
|
+
worktree: state.bind.data.workspace === "worktree"
|
|
233
|
+
? { place: state.bind.data.place, state: released ? "released" : "held" }
|
|
234
|
+
: null,
|
|
235
|
+
tasks: selectedTasks.map((taskId) => ({ taskId, state: taskReleased ? "released" : "held" })),
|
|
236
|
+
},
|
|
237
|
+
history,
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
async function materializeIncrementalContract(cwd, observedLedgerHead, state, history) {
|
|
241
|
+
const nodes = new Map();
|
|
242
|
+
for (const row of history) {
|
|
243
|
+
nodes.set(row.objectId, {
|
|
244
|
+
type: "lifecycle",
|
|
245
|
+
objectId: row.objectId,
|
|
246
|
+
entry: structuredClone(row.entry),
|
|
247
|
+
ledgerParents: [],
|
|
248
|
+
evidenceParent: null,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
let evidence = null;
|
|
252
|
+
const selectedTaskIds = state.bind.data.taskIds ?? [];
|
|
253
|
+
if (selectedTaskIds.length > 0) {
|
|
254
|
+
evidence = await readTaskAdmissionEvidenceAtCommit({
|
|
255
|
+
cwd,
|
|
256
|
+
bindObjectId: state.entryObjectIds[0],
|
|
257
|
+
base: state.bind.data.base,
|
|
258
|
+
contract: state.contract,
|
|
259
|
+
selectedTaskIds,
|
|
260
|
+
});
|
|
261
|
+
if (evidence.bindObjectId !== state.entryObjectIds[0]
|
|
262
|
+
|| evidence.base !== state.bind.data.base
|
|
263
|
+
|| evidence.selected.length !== selectedTaskIds.length
|
|
264
|
+
|| evidence.selected.some((candidate, index) => candidate.taskId !== selectedTaskIds[index]))
|
|
265
|
+
throw new Error("task admission evidence does not match incremental bind state");
|
|
266
|
+
}
|
|
267
|
+
return lifecycleModel(observedLedgerHead, state, nodes, evidence);
|
|
268
|
+
}
|
|
269
|
+
async function recoverRepositoryLifecycle(cwd, options = {}) {
|
|
270
|
+
const accepted = await readAcceptedRepositoryFold(cwd, { observeHead: options.observeHead, signal: options.signal });
|
|
271
|
+
if (accepted.observedLedgerHead === null) {
|
|
272
|
+
options.captureFold?.({ contracts: new Map(), isolatedContracts: new Set(), diseases: [] });
|
|
273
|
+
return deepFreeze({
|
|
274
|
+
observedLedgerHead: null,
|
|
275
|
+
objectFormat: null,
|
|
276
|
+
emptyTree: null,
|
|
277
|
+
contracts: [],
|
|
278
|
+
isolatedContracts: [],
|
|
279
|
+
diagnostics: [],
|
|
280
|
+
topology: { head: null, nodes: [] },
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
const { observedLedgerHead, traversed, folded } = accepted;
|
|
284
|
+
options.captureFold?.(folded);
|
|
285
|
+
const diagnostics = [...folded.diseases];
|
|
286
|
+
const isolatedContracts = new Set(folded.isolatedContracts);
|
|
287
|
+
const contracts = [];
|
|
288
|
+
const states = [...folded.contracts.values()].sort((left, right) => left.contract.localeCompare(right.contract));
|
|
289
|
+
const taskReader = options.readTaskAdmissionEvidence === undefined
|
|
290
|
+
&& states.some((state) => (state.bind.data.taskIds?.length ?? 0) > 0)
|
|
291
|
+
? openTaskAdmissionEvidenceReader(cwd)
|
|
292
|
+
: null;
|
|
293
|
+
const readTaskEvidence = options.readTaskAdmissionEvidence
|
|
294
|
+
?? taskReader?.read
|
|
295
|
+
?? readTaskAdmissionEvidenceAtCommit;
|
|
296
|
+
try {
|
|
297
|
+
for (const state of states) {
|
|
298
|
+
const selectedTaskIds = state.bind.data.taskIds ?? [];
|
|
299
|
+
let taskAdmissionEvidence = null;
|
|
300
|
+
if (selectedTaskIds.length > 0) {
|
|
301
|
+
try {
|
|
302
|
+
taskAdmissionEvidence = await readTaskEvidence({
|
|
303
|
+
cwd,
|
|
304
|
+
bindObjectId: state.entryObjectIds[0],
|
|
305
|
+
base: state.bind.data.base,
|
|
306
|
+
contract: state.contract,
|
|
307
|
+
selectedTaskIds,
|
|
308
|
+
});
|
|
309
|
+
const evidenceIds = taskAdmissionEvidence.selected.map((candidate) => candidate.taskId);
|
|
310
|
+
if (taskAdmissionEvidence.bindObjectId !== state.entryObjectIds[0] ||
|
|
311
|
+
taskAdmissionEvidence.base !== state.bind.data.base ||
|
|
312
|
+
evidenceIds.length !== selectedTaskIds.length ||
|
|
313
|
+
evidenceIds.some((taskId, index) => taskId !== selectedTaskIds[index])) {
|
|
314
|
+
throw new Error("task admission evidence does not match bind identity, base, and selected task order");
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
catch (error) {
|
|
318
|
+
isolatedContracts.add(state.contract);
|
|
319
|
+
diagnostics.push({
|
|
320
|
+
objectId: state.entryObjectIds[0],
|
|
321
|
+
code: "task-admission-evidence",
|
|
322
|
+
contract: state.contract,
|
|
323
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
324
|
+
});
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
contracts.push(lifecycleModel(observedLedgerHead, state, traversed.nodes, taskAdmissionEvidence));
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
finally {
|
|
332
|
+
await taskReader?.close();
|
|
333
|
+
}
|
|
334
|
+
const topologyNodes = [...traversed.nodes.values()].map((node) => {
|
|
335
|
+
if (node.type === "transaction") {
|
|
336
|
+
return {
|
|
337
|
+
objectId: node.objectId,
|
|
338
|
+
type: node.type,
|
|
339
|
+
ledgerParents: [...node.ledgerParents],
|
|
340
|
+
verb: node.transaction.verb,
|
|
341
|
+
contract: node.contract,
|
|
342
|
+
entryObjectIds: [...node.entryObjectIds],
|
|
343
|
+
finalEntryObjectId: node.finalEntryObjectId,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
return { objectId: node.objectId, type: node.type, ledgerParents: [...node.ledgerParents] };
|
|
347
|
+
});
|
|
348
|
+
return deepFreeze({
|
|
349
|
+
observedLedgerHead,
|
|
350
|
+
objectFormat: traversed.coordinates.objectFormat,
|
|
351
|
+
emptyTree: traversed.coordinates.emptyTree,
|
|
352
|
+
contracts,
|
|
353
|
+
isolatedContracts: [...isolatedContracts].sort(),
|
|
354
|
+
diagnostics: structuredClone(diagnostics),
|
|
355
|
+
topology: {
|
|
356
|
+
head: observedLedgerHead,
|
|
357
|
+
nodes: topologyNodes,
|
|
358
|
+
},
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
export async function readRepositoryLifecycle(cwd, options = {}) {
|
|
362
|
+
const observe = options.observeHead ?? observeRepositoryLedgerHead;
|
|
363
|
+
const observedHead = await observe(cwd);
|
|
364
|
+
const injected = options.observeHead !== undefined || options.readTaskAdmissionEvidence !== undefined;
|
|
365
|
+
if (!injected) {
|
|
366
|
+
const pinned = await readPinnedRepositoryLifecycle(cwd, observedHead, options.signal, { includeTopology: options.includeTopology });
|
|
367
|
+
if (pinned)
|
|
368
|
+
return deepFreeze(pinned);
|
|
369
|
+
const priorHead = await readRepositoryCurrentStatePin(cwd, options.signal);
|
|
370
|
+
if (priorHead && observedHead && priorHead !== observedHead) {
|
|
371
|
+
try {
|
|
372
|
+
const acceptedObjectIds = await readRepositoryCurrentStateNodeIds(cwd, priorHead, options.signal);
|
|
373
|
+
const segment = await readIncrementalRepositorySegment(cwd, priorHead, observedHead, options.signal, acceptedObjectIds ?? undefined);
|
|
374
|
+
if (segment && await advanceRepositoryCurrentState(cwd, priorHead, observedHead, segment, {
|
|
375
|
+
signal: options.signal,
|
|
376
|
+
confirmHead: () => observeRepositoryLedgerHead(cwd),
|
|
377
|
+
materialize: (state, history) => materializeIncrementalContract(cwd, observedHead, state, history),
|
|
378
|
+
})) {
|
|
379
|
+
const advanced = await readPinnedRepositoryLifecycle(cwd, observedHead, options.signal, { includeTopology: options.includeTopology });
|
|
380
|
+
if (advanced)
|
|
381
|
+
return deepFreeze(advanced);
|
|
382
|
+
}
|
|
383
|
+
const concurrentlyAdvanced = await readPinnedRepositoryLifecycle(cwd, observedHead, options.signal, { includeTopology: options.includeTopology });
|
|
384
|
+
if (concurrentlyAdvanced)
|
|
385
|
+
return deepFreeze(concurrentlyAdvanced);
|
|
386
|
+
}
|
|
387
|
+
catch (error) {
|
|
388
|
+
if (error instanceof Error && error.name === "AbortError")
|
|
389
|
+
throw error;
|
|
390
|
+
const untrustedDelta = error instanceof IncrementalLedgerValidationError
|
|
391
|
+
|| (error instanceof RepositoryObjectError && error.reason !== "git-failure");
|
|
392
|
+
if (!untrustedDelta)
|
|
393
|
+
throw error;
|
|
394
|
+
await discardRepositoryCurrentState(cwd, options.signal);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
let recoveredFold;
|
|
399
|
+
const recovered = await recoverRepositoryLifecycle(cwd, {
|
|
400
|
+
...options,
|
|
401
|
+
observeHead: async () => observedHead,
|
|
402
|
+
captureFold: (fold) => { recoveredFold = fold; },
|
|
403
|
+
});
|
|
404
|
+
if (!injected) {
|
|
405
|
+
const published = await publishRepositoryCurrentState(cwd, recovered, {
|
|
406
|
+
signal: options.signal,
|
|
407
|
+
confirmHead: () => observeRepositoryLedgerHead(cwd),
|
|
408
|
+
fold: recoveredFold,
|
|
409
|
+
});
|
|
410
|
+
if (!published)
|
|
411
|
+
return readRepositoryLifecycle(cwd, options);
|
|
412
|
+
}
|
|
413
|
+
return recovered;
|
|
414
|
+
}
|
|
415
|
+
/** Current repository summaries for status-like consumers; retained lifecycle history is not materialized. */
|
|
416
|
+
export async function readRepositoryLifecycleSummaries(cwd, options = {}) {
|
|
417
|
+
const observedHead = await observeRepositoryLedgerHead(cwd);
|
|
418
|
+
const pinned = await readPinnedRepositoryLifecycle(cwd, observedHead, options.signal, {
|
|
419
|
+
includeTopology: false,
|
|
420
|
+
summaryOnly: true,
|
|
421
|
+
});
|
|
422
|
+
if (pinned)
|
|
423
|
+
return deepFreeze(pinned);
|
|
424
|
+
await readRepositoryLifecycle(cwd, { signal: options.signal });
|
|
425
|
+
const rebuilt = await readPinnedRepositoryLifecycle(cwd, observedHead, options.signal, {
|
|
426
|
+
includeTopology: false,
|
|
427
|
+
summaryOnly: true,
|
|
428
|
+
});
|
|
429
|
+
if (!rebuilt)
|
|
430
|
+
return readRepositoryLifecycleSummaries(cwd, options);
|
|
431
|
+
return deepFreeze(rebuilt);
|
|
432
|
+
}
|
|
433
|
+
export async function readCurrentContractLifecycle(cwd, contract, options = {}) {
|
|
434
|
+
const observe = options.observeHead ?? observeRepositoryLedgerHead;
|
|
435
|
+
const observedHead = await observe(cwd);
|
|
436
|
+
const injected = options.observeHead !== undefined || options.readTaskAdmissionEvidence !== undefined;
|
|
437
|
+
if (!injected) {
|
|
438
|
+
const pinned = await readPinnedCurrentContract(cwd, observedHead, contract, options.signal);
|
|
439
|
+
if (pinned)
|
|
440
|
+
return deepFreeze(pinned);
|
|
441
|
+
}
|
|
442
|
+
const repository = await readRepositoryLifecycle(cwd, {
|
|
443
|
+
...options,
|
|
444
|
+
includeTopology: false,
|
|
445
|
+
observeHead: injected ? async () => observedHead : undefined,
|
|
446
|
+
});
|
|
447
|
+
if (!injected) {
|
|
448
|
+
const targeted = await readPinnedCurrentContract(cwd, repository.observedLedgerHead, contract, options.signal);
|
|
449
|
+
if (targeted)
|
|
450
|
+
return deepFreeze(targeted);
|
|
451
|
+
}
|
|
452
|
+
return lookupCurrentContract(repository, contract);
|
|
453
|
+
}
|
|
454
|
+
export function lookupCurrentContract(read, contract) {
|
|
455
|
+
const diagnostics = read.diagnostics.filter((disease) => disease.contract === contract);
|
|
456
|
+
if (read.isolatedContracts.includes(contract)) {
|
|
457
|
+
return deepFreeze({ status: "isolated", contract, diagnostics: [...diagnostics] });
|
|
458
|
+
}
|
|
459
|
+
const model = read.contracts.find((candidate) => candidate.contract === contract);
|
|
460
|
+
if (!model) {
|
|
461
|
+
const unattributed = read.diagnostics.filter((disease) => disease.contract === undefined);
|
|
462
|
+
return unattributed.length > 0
|
|
463
|
+
? deepFreeze({ status: "indeterminate", contract, diagnostics: [...unattributed] })
|
|
464
|
+
: deepFreeze({ status: "absent", contract, diagnostics: [] });
|
|
465
|
+
}
|
|
466
|
+
if (diagnostics.length > 0)
|
|
467
|
+
return deepFreeze({ status: "diseased", model, diagnostics: [...diagnostics] });
|
|
468
|
+
return deepFreeze({ status: "healthy", model, diagnostics: [] });
|
|
469
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
function invalid(verb, entries) {
|
|
2
|
+
const kinds = entries.map((entry) => entry.kind).join(",");
|
|
3
|
+
return { status: "invalid", detail: `${verb} transaction has unlawful entry grammar ${kinds || "empty"}` };
|
|
4
|
+
}
|
|
5
|
+
function validateContractContinuity(verb, entries) {
|
|
6
|
+
const contract = entries.at(-1)?.contract;
|
|
7
|
+
if (contract !== undefined && entries.some((entry) => entry.contract !== contract)) {
|
|
8
|
+
return { status: "invalid", detail: `${verb} transaction crosses contract identities` };
|
|
9
|
+
}
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
function isSealFor(entry, verb) {
|
|
13
|
+
return entry?.kind === "seal" && entry.data.sealedBy === verb;
|
|
14
|
+
}
|
|
15
|
+
/** Parse the one current, closed, verb-bound transaction membership grammar. */
|
|
16
|
+
export function validateTransactionMembership(verb, entries) {
|
|
17
|
+
const continuity = validateContractContinuity(verb, entries);
|
|
18
|
+
if (continuity)
|
|
19
|
+
return continuity;
|
|
20
|
+
const [first, second, third] = entries;
|
|
21
|
+
if (verb === "bind" && entries.length === 1 && first?.kind === "bind")
|
|
22
|
+
return { status: "valid", membership: { verb, entries: [first] } };
|
|
23
|
+
if (verb === "bootstrap" && entries.length === 1 && first?.kind === "bootstrap")
|
|
24
|
+
return { status: "valid", membership: { verb, entries: [first] } };
|
|
25
|
+
if (verb === "amend" && entries.length === 1 && first?.kind === "amend")
|
|
26
|
+
return { status: "valid", membership: { verb, entries: [first] } };
|
|
27
|
+
if (verb === "seal" && entries.length === 1 && first?.kind === "seal")
|
|
28
|
+
return { status: "valid", membership: { verb, entries: [first] } };
|
|
29
|
+
if (verb === "verdict" && entries.length === 1 && first?.kind === "verdict")
|
|
30
|
+
return { status: "valid", membership: { verb, entries: [first] } };
|
|
31
|
+
if (verb === "claim" && entries.length === 1 && first?.kind === "claim")
|
|
32
|
+
return { status: "valid", membership: { verb, entries: [first] } };
|
|
33
|
+
if (verb === "forfeit" && entries.length === 1 && first?.kind === "forfeit")
|
|
34
|
+
return { status: "valid", membership: { verb, entries: [first] } };
|
|
35
|
+
if (verb === "arc") {
|
|
36
|
+
if (entries.length === 1 && first?.kind === "arc")
|
|
37
|
+
return { status: "valid", membership: { verb, entries: [first] } };
|
|
38
|
+
if (entries.length === 2 && isSealFor(first, "arc") && second?.kind === "arc") {
|
|
39
|
+
return { status: "valid", membership: { verb, entries: [first, second] } };
|
|
40
|
+
}
|
|
41
|
+
if (entries.length === 2 && first?.kind === "amend" && second?.kind === "arc") {
|
|
42
|
+
return { status: "valid", membership: { verb, entries: [first, second] } };
|
|
43
|
+
}
|
|
44
|
+
if (entries.length === 3 && first?.kind === "amend" && isSealFor(second, "arc") && third?.kind === "arc") {
|
|
45
|
+
return { status: "valid", membership: { verb, entries: [first, second, third] } };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (verb === "petition") {
|
|
49
|
+
if (entries.length === 1 && first?.kind === "petition")
|
|
50
|
+
return { status: "valid", membership: { verb, entries: [first] } };
|
|
51
|
+
if (entries.length === 2 && isSealFor(first, "petition") && second?.kind === "petition") {
|
|
52
|
+
return { status: "valid", membership: { verb, entries: [first, second] } };
|
|
53
|
+
}
|
|
54
|
+
if (entries.length === 2 && first?.kind === "amend" && second?.kind === "petition") {
|
|
55
|
+
return { status: "valid", membership: { verb, entries: [first, second] } };
|
|
56
|
+
}
|
|
57
|
+
if (entries.length === 3 && first?.kind === "amend" && isSealFor(second, "petition") && third?.kind === "petition") {
|
|
58
|
+
return { status: "valid", membership: { verb, entries: [first, second, third] } };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (verb === "renew") {
|
|
62
|
+
if (entries.length === 1 && first?.kind === "amend")
|
|
63
|
+
return { status: "valid", membership: { verb, entries: [first] } };
|
|
64
|
+
if (entries.length === 1 && first?.kind === "renew")
|
|
65
|
+
return { status: "valid", membership: { verb, entries: [first] } };
|
|
66
|
+
if (entries.length === 2 && isSealFor(first, "renew") && second?.kind === "renew") {
|
|
67
|
+
return { status: "valid", membership: { verb, entries: [first, second] } };
|
|
68
|
+
}
|
|
69
|
+
if (entries.length === 3 && first?.kind === "amend" && isSealFor(second, "renew") && third?.kind === "renew") {
|
|
70
|
+
return { status: "valid", membership: { verb, entries: [first, second, third] } };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return invalid(verb, entries);
|
|
74
|
+
}
|
|
75
|
+
export function transactionMembershipError(verb, entries) {
|
|
76
|
+
const validation = validateTransactionMembership(verb, entries);
|
|
77
|
+
return validation.status === "valid" ? null : validation.detail;
|
|
78
|
+
}
|