@astrosheep/keiyaku 4.0.0 → 4.0.2
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 +133 -8
- package/build/integrations/marketplace/plugins/keiyaku/.claude-plugin/plugin.json +2 -2
- package/build/integrations/marketplace/plugins/keiyaku/.codex-plugin/plugin.json +4 -4
- package/build/integrations/marketplace/plugins/keiyaku/opencode.js +2 -2
- package/build/integrations/marketplace/plugins/keiyaku/package.json +2 -2
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +13 -5
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +91 -32
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +134 -0
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/agents/openai.yaml +4 -0
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-task/SKILL.md +41 -6
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +262 -46
- package/build/src/akuma/abort.d.ts +2 -0
- package/build/src/akuma/abort.js +50 -0
- package/build/src/akuma/akuma.d.ts +50 -33
- package/build/src/akuma/akuma.js +175 -127
- package/build/src/akuma/archetype.d.ts +14 -5
- package/build/src/akuma/archetype.js +85 -39
- package/build/src/akuma/body.d.ts +3 -5
- package/build/src/akuma/body.js +490 -183
- package/build/src/akuma/coordinate.d.ts +9 -0
- package/build/src/akuma/coordinate.js +22 -0
- package/build/src/akuma/heart/facts.d.ts +88 -45
- package/build/src/akuma/heart/facts.js +19 -8
- package/build/src/akuma/heart/index.d.ts +64 -55
- package/build/src/akuma/heart/index.js +172 -252
- package/build/src/akuma/heart/rows.d.ts +38 -83
- package/build/src/akuma/heart/rows.js +117 -155
- package/build/src/akuma/heart/schema.d.ts +1 -1
- package/build/src/akuma/heart/schema.js +61 -22
- package/build/src/akuma/heart/soul.d.ts +8 -0
- package/build/src/akuma/heart/soul.js +136 -0
- package/build/src/akuma/heart/storage.d.ts +47 -0
- package/build/src/akuma/heart/storage.js +224 -0
- package/build/src/akuma/heart/tells.d.ts +12 -0
- package/build/src/akuma/heart/tells.js +79 -0
- package/build/src/akuma/heart/timeline.d.ts +15 -0
- package/build/src/akuma/heart/timeline.js +86 -0
- package/build/src/akuma/identity.d.ts +2 -2
- package/build/src/akuma/identity.js +9 -9
- package/build/src/akuma/index.d.ts +10 -3
- package/build/src/akuma/index.js +7 -1
- package/build/src/akuma/projection.d.ts +176 -0
- package/build/src/akuma/projection.js +256 -0
- package/build/src/akuma/provider-recipe.d.ts +24 -0
- package/build/src/akuma/provider-recipe.js +100 -0
- package/build/src/akuma/provider.d.ts +67 -13
- package/build/src/akuma/provider.js +172 -105
- package/build/src/akuma/providers/acp/core.d.ts +19 -0
- package/build/src/akuma/providers/acp/core.js +159 -0
- package/build/src/akuma/providers/acp/events.d.ts +20 -0
- package/build/src/akuma/providers/acp/events.js +73 -0
- package/build/src/akuma/providers/acp/index.d.ts +12 -0
- package/build/src/akuma/providers/acp/index.js +94 -0
- package/build/src/akuma/providers/{claude.d.ts → claude/events.d.ts} +9 -41
- package/build/src/akuma/providers/claude/events.js +238 -0
- package/build/src/akuma/providers/claude/index.d.ts +47 -0
- package/build/src/akuma/providers/claude/index.js +279 -0
- package/build/src/akuma/providers/claude/input.d.ts +13 -0
- package/build/src/akuma/providers/claude/input.js +109 -0
- package/build/src/akuma/providers/codex-app-server/events.d.ts +1 -1
- package/build/src/akuma/providers/codex-app-server/events.js +15 -12
- package/build/src/akuma/providers/codex-app-server/index.d.ts +15 -13
- package/build/src/akuma/providers/codex-app-server/index.js +77 -33
- package/build/src/akuma/providers/grok-build/index.d.ts +4 -0
- package/build/src/akuma/providers/grok-build/index.js +70 -0
- package/build/src/akuma/providers/index.d.ts +5 -3
- package/build/src/akuma/providers/index.js +35 -56
- package/build/src/akuma/providers/opencode-sdk/events.d.ts +54 -0
- package/build/src/akuma/providers/opencode-sdk/events.js +235 -0
- package/build/src/akuma/providers/opencode-sdk/index.d.ts +33 -0
- package/build/src/akuma/providers/opencode-sdk/index.js +308 -0
- package/build/src/akuma/providers/opencode-sdk/session.d.ts +26 -0
- package/build/src/akuma/providers/opencode-sdk/session.js +63 -0
- package/build/src/akuma/providers/pi/events.d.ts +33 -0
- package/build/src/akuma/providers/pi/events.js +166 -0
- package/build/src/akuma/providers/pi/index.d.ts +11 -0
- package/build/src/akuma/providers/pi/index.js +191 -0
- package/build/src/akuma/publication.d.ts +3 -3
- package/build/src/akuma/publication.js +28 -40
- package/build/src/akuma/requests.d.ts +9 -6
- package/build/src/akuma/requests.js +95 -65
- package/build/src/alias/index.d.ts +4 -3
- package/build/src/alias/index.js +12 -13
- package/build/src/body/decode.js +7 -1
- package/build/src/body/region.d.ts +2 -0
- package/build/src/body/region.js +77 -7
- package/build/src/body/render.js +2 -1
- package/build/src/body/shape.js +7 -1
- package/build/src/body/verification.js +19 -4
- package/build/src/cli/accepted.d.ts +18 -12
- package/build/src/cli/accepted.js +73 -34
- package/build/src/cli/actor.js +3 -3
- package/build/src/cli/commands/akuma-invoke.d.ts +44 -19
- package/build/src/cli/commands/akuma-invoke.js +101 -52
- package/build/src/cli/commands/akuma.d.ts +27 -9
- package/build/src/cli/commands/akuma.js +151 -80
- package/build/src/cli/commands/contract.d.ts +155 -0
- package/build/src/cli/commands/contract.js +20 -0
- package/build/src/cli/commands/install.js +2 -0
- package/build/src/cli/commands/task-invoke.d.ts +19 -4
- package/build/src/cli/commands/task-invoke.js +65 -17
- package/build/src/cli/commands/task-query.d.ts +5 -0
- package/build/src/cli/commands/task-query.js +247 -0
- package/build/src/cli/commands/task.d.ts +3 -1
- package/build/src/cli/commands/task.js +52 -14
- package/build/src/cli/coordinates.d.ts +18 -0
- package/build/src/cli/coordinates.js +120 -0
- package/build/src/cli/draft.d.ts +8 -0
- package/build/src/cli/draft.js +96 -0
- package/build/src/cli/invoke.d.ts +9 -3
- package/build/src/cli/invoke.js +287 -108
- package/build/src/cli/main.js +54 -7
- package/build/src/cli/parse.d.ts +21 -117
- package/build/src/cli/parse.js +79 -84
- package/build/src/cli/render/akuma-tool-command.d.ts +6 -0
- package/build/src/cli/render/akuma-tool-command.js +144 -0
- package/build/src/cli/render/akuma-tool.d.ts +4 -2
- package/build/src/cli/render/akuma-tool.js +35 -5
- package/build/src/cli/render/akuma.d.ts +3 -1
- package/build/src/cli/render/akuma.js +247 -212
- package/build/src/cli/render/audit.d.ts +3 -0
- package/build/src/cli/render/audit.js +104 -0
- package/build/src/cli/render/catalog.d.ts +2 -0
- package/build/src/cli/render/catalog.js +16 -0
- package/build/src/cli/render/contract.d.ts +3 -3
- package/build/src/cli/render/contract.js +200 -54
- package/build/src/cli/render/kanshi.js +272 -129
- package/build/src/cli/render/receipt.d.ts +19 -0
- package/build/src/cli/render/receipt.js +106 -0
- package/build/src/cli/render/refusal.d.ts +16 -2
- package/build/src/cli/render/refusal.js +88 -3
- package/build/src/cli/render/region.d.ts +2 -0
- package/build/src/cli/render/region.js +17 -0
- package/build/src/cli/render/task.d.ts +2 -1
- package/build/src/cli/render/task.js +223 -60
- package/build/src/cli/render/terminal.d.ts +20 -0
- package/build/src/cli/render/terminal.js +121 -0
- package/build/src/cli/render/text.js +11 -3
- package/build/src/cli/result.d.ts +126 -19
- package/build/src/cli/usage.d.ts +1 -0
- package/build/src/cli/usage.js +3 -0
- package/build/src/contract-worktree.d.ts +44 -0
- package/build/src/contract-worktree.js +251 -0
- package/build/src/coordination/durable-file.d.ts +5 -1
- package/build/src/coordination/durable-file.js +57 -10
- package/build/src/coordination/sqlite-transaction-lock.d.ts +4 -0
- package/build/src/coordination/sqlite-transaction-lock.js +22 -5
- package/build/src/core/facts/codec.js +23 -4
- package/build/src/core/facts/eligibility.d.ts +1 -10
- package/build/src/core/facts/eligibility.js +0 -49
- package/build/src/core/facts/fold.js +1 -6
- package/build/src/core/facts/gate.d.ts +1 -0
- package/build/src/core/facts/gate.js +1 -0
- package/build/src/core/facts/observation.d.ts +3 -0
- package/build/src/core/facts/observation.js +12 -0
- package/build/src/core/facts/types.d.ts +10 -3
- package/build/src/core/subject.js +2 -2
- package/build/src/core/verbs/amend.d.ts +1 -1
- package/build/src/core/verbs/amend.js +3 -18
- package/build/src/core/verbs/bind.js +2 -16
- package/build/src/core/verbs/deliver.d.ts +1 -1
- package/build/src/core/verbs/deliver.js +14 -4
- package/build/src/core/verbs/placement.d.ts +1 -1
- package/build/src/core/verbs/placement.js +7 -9
- package/build/src/dispatch/index.d.ts +5 -3
- package/build/src/dispatch/index.js +30 -21
- package/build/src/duration.d.ts +10 -0
- package/build/src/duration.js +22 -0
- package/build/src/git/admission.d.ts +2 -2
- package/build/src/git/admission.js +33 -23
- package/build/src/git/hooks.d.ts +15 -0
- package/build/src/git/hooks.js +70 -18
- package/build/src/git/identity.d.ts +5 -1
- package/build/src/git/identity.js +23 -4
- package/build/src/git/integration.d.ts +48 -0
- package/build/src/git/integration.js +250 -0
- package/build/src/git/observe.d.ts +29 -15
- package/build/src/git/observe.js +185 -76
- package/build/src/git/read-observation.d.ts +41 -0
- package/build/src/git/read-observation.js +361 -0
- package/build/src/git/reconcile.d.ts +17 -5
- package/build/src/git/reconcile.js +305 -109
- package/build/src/git/repository.d.ts +41 -18
- package/build/src/git/repository.js +220 -114
- package/build/src/git/scratch.d.ts +17 -0
- package/build/src/git/scratch.js +73 -0
- package/build/src/git/target-placement.d.ts +92 -0
- package/build/src/git/target-placement.js +375 -0
- package/build/src/git/tender.d.ts +49 -0
- package/build/src/git/tender.js +63 -0
- package/build/src/git/terminal-seal.d.ts +24 -0
- package/build/src/git/terminal-seal.js +89 -0
- package/build/src/git/tree.d.ts +2 -2
- package/build/src/git/tree.js +4 -0
- package/build/src/git/workspace.d.ts +53 -0
- package/build/src/git/workspace.js +139 -0
- package/build/src/identity/selector.d.ts +6 -0
- package/build/src/identity/selector.js +19 -0
- package/build/src/index.d.ts +4 -2
- package/build/src/index.js +2 -1
- package/build/src/kanshi/index.d.ts +2 -2
- package/build/src/kanshi/index.js +1 -1
- package/build/src/kanshi/read.d.ts +7 -3
- package/build/src/kanshi/read.js +245 -45
- package/build/src/kanshi/report.d.ts +81 -6
- package/build/src/kanshi/select.d.ts +5 -0
- package/build/src/kanshi/select.js +33 -1
- package/build/src/library/address.d.ts +57 -0
- package/build/src/library/address.js +165 -0
- package/build/src/library/akuma-creation.d.ts +16 -7
- package/build/src/library/akuma-creation.js +96 -22
- package/build/src/library/audit.d.ts +18 -0
- package/build/src/library/audit.js +44 -0
- package/build/src/library/bind.d.ts +21 -0
- package/build/src/library/bind.js +56 -0
- package/build/src/library/catalog.d.ts +58 -0
- package/build/src/library/catalog.js +69 -0
- package/build/src/library/configuration.d.ts +6 -15
- package/build/src/library/configuration.js +14 -17
- package/build/src/library/contract.d.ts +23 -49
- package/build/src/library/contract.js +171 -185
- package/build/src/library/delivery.d.ts +24 -0
- package/build/src/library/delivery.js +21 -0
- package/build/src/library/fleet.d.ts +65 -0
- package/build/src/library/fleet.js +153 -0
- package/build/src/library/input.d.ts +3 -0
- package/build/src/library/input.js +19 -0
- package/build/src/library/keiyaku.d.ts +18 -3
- package/build/src/library/keiyaku.js +12 -1
- package/build/src/library/mutation.d.ts +39 -0
- package/build/src/library/mutation.js +37 -0
- package/build/src/library/reconcile.d.ts +29 -0
- package/build/src/library/reconcile.js +179 -0
- package/build/src/library/refusal.d.ts +21 -0
- package/build/src/library/refusal.js +29 -0
- package/build/src/library/region.d.ts +14 -1
- package/build/src/library/region.js +18 -4
- package/build/src/library/repo.d.ts +4 -13
- package/build/src/library/repo.js +14 -24
- package/build/src/protocol/attempt.d.ts +16 -3
- package/build/src/protocol/attempt.js +41 -10
- package/build/src/protocol/bind.d.ts +9 -2
- package/build/src/protocol/bind.js +48 -40
- package/build/src/protocol/intent.d.ts +57 -20
- package/build/src/protocol/intent.js +55 -79
- package/build/src/protocol/operations.d.ts +152 -43
- package/build/src/protocol/operations.js +482 -166
- package/build/src/protocol/outcome.d.ts +14 -3
- package/build/src/protocol/outcome.js +11 -3
- package/build/src/protocol/placement.d.ts +41 -0
- package/build/src/protocol/placement.js +147 -0
- package/build/src/protocol/read/documents.d.ts +2 -2
- package/build/src/protocol/read/documents.js +4 -4
- package/build/src/protocol/read/status.d.ts +18 -5
- package/build/src/protocol/read/status.js +73 -13
- package/build/src/protocol/run.d.ts +49 -7
- package/build/src/protocol/run.js +53 -25
- package/build/src/runtime/proc/line-rpc.d.ts +4 -8
- package/build/src/runtime/proc/line-rpc.js +22 -28
- package/build/src/runtime/proc/run.d.ts +23 -21
- package/build/src/runtime/proc/run.js +123 -89
- package/build/src/runtime/proc/stdio.d.ts +18 -0
- package/build/src/runtime/proc/stdio.js +58 -0
- package/build/src/settings.d.ts +7 -1
- package/build/src/settings.js +32 -17
- package/build/src/settlement/fence.d.ts +5 -0
- package/build/src/settlement/fence.js +14 -0
- package/build/src/settlement/holder.d.ts +35 -4
- package/build/src/settlement/holder.js +127 -28
- package/build/src/settlement/settle.d.ts +9 -1
- package/build/src/settlement/settle.js +74 -37
- package/build/src/task/board.d.ts +23 -5
- package/build/src/task/board.js +17 -10
- package/build/src/task/compose.d.ts +2 -2
- package/build/src/task/compose.js +18 -10
- package/build/src/task/context.d.ts +3 -3
- package/build/src/task/context.js +15 -20
- package/build/src/task/document.d.ts +2 -1
- package/build/src/task/document.js +15 -5
- package/build/src/task/index.d.ts +27 -15
- package/build/src/task/index.js +57 -35
- package/build/src/task/operations.d.ts +38 -13
- package/build/src/task/operations.js +80 -46
- package/build/src/task/query.d.ts +67 -0
- package/build/src/task/query.js +279 -0
- package/build/src/task/store.d.ts +5 -7
- package/build/src/task/store.js +43 -28
- package/build/src/verification/declaration.d.ts +1 -0
- package/build/src/verification/execution.d.ts +51 -0
- package/build/src/verification/execution.js +108 -0
- package/build/src/workspace-place.d.ts +41 -0
- package/build/src/workspace-place.js +386 -0
- package/build/src/world.d.ts +24 -0
- package/build/src/world.js +136 -0
- package/package.json +8 -4
- package/build/src/akuma/activity.d.ts +0 -66
- package/build/src/akuma/activity.js +0 -139
- package/build/src/akuma/providers/claude.js +0 -305
- package/build/src/context-root.d.ts +0 -4
- package/build/src/context-root.js +0 -15
- package/build/src/git/delivery.d.ts +0 -20
- package/build/src/git/delivery.js +0 -109
- package/build/src/git/verification.d.ts +0 -14
- package/build/src/git/verification.js +0 -56
- package/build/src/protocol/read/audit.d.ts +0 -25
- package/build/src/protocol/read/audit.js +0 -39
- package/build/src/verification/producer.d.ts +0 -23
- package/build/src/verification/producer.js +0 -54
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { AuthorityCorruptionError } from "../core/facts/errors.js";
|
|
2
|
+
import { reconcileAllOperation, reconcileObservationFailure, reconcileOperation, stateOperation, worldContractStates, } from "../protocol/operations.js";
|
|
3
|
+
import { settle, settleAll } from "../settlement/settle.js";
|
|
4
|
+
import { projectContractWorktree, } from "../contract-worktree.js";
|
|
5
|
+
import { appointManagedWorktrees, placeRegisterPath, releaseManagedWorktrees, } from "../workspace-place.js";
|
|
6
|
+
function registerLag(scope, error) {
|
|
7
|
+
return {
|
|
8
|
+
kind: "contract-file-failed",
|
|
9
|
+
worktree: scope.primaryWorktree,
|
|
10
|
+
path: placeRegisterPath(scope),
|
|
11
|
+
diagnostic: error instanceof Error ? error.message : String(error),
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function emptySettlement() {
|
|
15
|
+
return { actions: [], lags: [] };
|
|
16
|
+
}
|
|
17
|
+
function isManagedWorktree(state) {
|
|
18
|
+
return state.coordinates.workspace === "worktree";
|
|
19
|
+
}
|
|
20
|
+
function isManagedTerminal(state) {
|
|
21
|
+
return state !== null && isManagedWorktree(state) && state.terminal !== null;
|
|
22
|
+
}
|
|
23
|
+
function appointableManagedContracts(states) {
|
|
24
|
+
return states
|
|
25
|
+
.filter((state) => isManagedWorktree(state) && state.terminal === null)
|
|
26
|
+
.map((state) => state.id);
|
|
27
|
+
}
|
|
28
|
+
async function appointPlaces(scope, states) {
|
|
29
|
+
return await appointManagedWorktrees(scope, appointableManagedContracts(states));
|
|
30
|
+
}
|
|
31
|
+
function releaseEligible(state, cleanup, appointed) {
|
|
32
|
+
return cleanup !== undefined
|
|
33
|
+
&& cleanup.lag.length === 0
|
|
34
|
+
&& isManagedTerminal(state)
|
|
35
|
+
&& appointed;
|
|
36
|
+
}
|
|
37
|
+
async function releaseAppointments(scope, contracts) {
|
|
38
|
+
try {
|
|
39
|
+
await releaseManagedWorktrees(scope, contracts);
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
if (error instanceof AuthorityCorruptionError || error instanceof TypeError)
|
|
44
|
+
throw error;
|
|
45
|
+
return registerLag(scope, error);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async function observeState(scope, channel, contractId) {
|
|
49
|
+
try {
|
|
50
|
+
return { state: await stateOperation({ scope, channel, contractId }) };
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
if (error instanceof AuthorityCorruptionError || error instanceof TypeError)
|
|
54
|
+
throw error;
|
|
55
|
+
return { failed: reconcileObservationFailure(error) };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async function appointForContract(scope, state) {
|
|
59
|
+
if (!isManagedWorktree(state))
|
|
60
|
+
return {};
|
|
61
|
+
try {
|
|
62
|
+
const register = await appointPlaces(scope, [state]);
|
|
63
|
+
const place = register.byContract.get(state.id)?.place;
|
|
64
|
+
if (place === undefined) {
|
|
65
|
+
if (state.terminal !== null)
|
|
66
|
+
return { register };
|
|
67
|
+
throw new Error(`Place appointment was not recorded: ${state.id}`);
|
|
68
|
+
}
|
|
69
|
+
return { place, register };
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
if (error instanceof AuthorityCorruptionError || error instanceof TypeError)
|
|
73
|
+
throw error;
|
|
74
|
+
return { lag: registerLag(scope, error) };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
export async function completeReconcile(input) {
|
|
78
|
+
const observed = await observeState(input.scope, input.channel, input.contractId);
|
|
79
|
+
if ("failed" in observed) {
|
|
80
|
+
return { effects: observed.failed.effects, lag: observed.failed.lag, settlement: emptySettlement() };
|
|
81
|
+
}
|
|
82
|
+
const appointment = await appointForContract(input.scope, observed.state);
|
|
83
|
+
if ("lag" in appointment) {
|
|
84
|
+
return { effects: [], lag: [appointment.lag], settlement: emptySettlement() };
|
|
85
|
+
}
|
|
86
|
+
const appointed = appointment.place === undefined ? {} : { place: appointment.place };
|
|
87
|
+
const retained = await reconcileOperation({
|
|
88
|
+
...input,
|
|
89
|
+
retainTerminalWorktree: true,
|
|
90
|
+
...appointed,
|
|
91
|
+
});
|
|
92
|
+
const projection = await projectContractWorktree(input.scope, retained.state, appointment.register);
|
|
93
|
+
const settlement = await settle({
|
|
94
|
+
repository: input.scope,
|
|
95
|
+
channel: input.channel,
|
|
96
|
+
state: retained.state,
|
|
97
|
+
effects: retained.report.effects,
|
|
98
|
+
});
|
|
99
|
+
const cleanup = isManagedTerminal(retained.state)
|
|
100
|
+
? await reconcileOperation({ ...input, ...appointed })
|
|
101
|
+
: null;
|
|
102
|
+
const release = releaseEligible(retained.state, cleanup?.report, appointment.place !== undefined)
|
|
103
|
+
? await releaseAppointments(input.scope, [input.contractId])
|
|
104
|
+
: undefined;
|
|
105
|
+
return {
|
|
106
|
+
effects: [...retained.report.effects, ...projection.effects, ...(cleanup?.report.effects ?? [])],
|
|
107
|
+
lag: [...retained.report.lag, ...projection.lag, ...(cleanup?.report.lag ?? []), ...(release === undefined ? [] : [release])],
|
|
108
|
+
settlement,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function attachReleaseLag(contracts, released, lag) {
|
|
112
|
+
const affected = new Set(released);
|
|
113
|
+
return {
|
|
114
|
+
contracts: contracts.map((contract) => affected.has(contract.contractId)
|
|
115
|
+
? { ...contract, report: { ...contract.report, lag: [...contract.report.lag, lag] } }
|
|
116
|
+
: contract),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
export async function completeRepoReconcile(input) {
|
|
120
|
+
const states = await worldContractStates(input);
|
|
121
|
+
let appointed;
|
|
122
|
+
try {
|
|
123
|
+
appointed = await appointPlaces(input.scope, states);
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
if (error instanceof AuthorityCorruptionError || error instanceof TypeError)
|
|
127
|
+
throw error;
|
|
128
|
+
const lag = registerLag(input.scope, error);
|
|
129
|
+
const contracts = [];
|
|
130
|
+
for (const state of states) {
|
|
131
|
+
contracts.push({
|
|
132
|
+
contractId: state.id,
|
|
133
|
+
report: isManagedWorktree(state)
|
|
134
|
+
? { effects: [], lag: [lag], settlement: emptySettlement() }
|
|
135
|
+
: await completeReconcile({ ...input, contractId: state.id }),
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
return { contracts };
|
|
139
|
+
}
|
|
140
|
+
const places = new Map(appointed.appointments.map((appointment) => [appointment.contract, appointment.place]));
|
|
141
|
+
const retained = await reconcileAllOperation({
|
|
142
|
+
...input,
|
|
143
|
+
retainTerminalWorktree: true,
|
|
144
|
+
places,
|
|
145
|
+
});
|
|
146
|
+
const settlements = await settleAll({
|
|
147
|
+
repository: input.scope,
|
|
148
|
+
channel: input.channel,
|
|
149
|
+
contracts: retained.contracts.map((contract) => ({
|
|
150
|
+
state: contract.state,
|
|
151
|
+
effects: contract.report.effects,
|
|
152
|
+
})),
|
|
153
|
+
});
|
|
154
|
+
const cleanup = retained.contracts.some((contract) => isManagedTerminal(contract.state))
|
|
155
|
+
? await reconcileAllOperation({ ...input, places })
|
|
156
|
+
: null;
|
|
157
|
+
const later = cleanup === null
|
|
158
|
+
? null
|
|
159
|
+
: new Map(cleanup.contracts.map((contract) => [contract.contractId, contract.report]));
|
|
160
|
+
const released = [];
|
|
161
|
+
const contracts = [];
|
|
162
|
+
for (const [index, contract] of retained.contracts.entries()) {
|
|
163
|
+
const report = later?.get(contract.contractId);
|
|
164
|
+
if (releaseEligible(contract.state, report, places.has(contract.contractId))) {
|
|
165
|
+
released.push(contract.contractId);
|
|
166
|
+
}
|
|
167
|
+
const projection = await projectContractWorktree(input.scope, contract.state, appointed);
|
|
168
|
+
contracts.push({
|
|
169
|
+
contractId: contract.contractId,
|
|
170
|
+
report: {
|
|
171
|
+
effects: [...contract.report.effects, ...projection.effects, ...(report?.effects ?? [])],
|
|
172
|
+
lag: [...contract.report.lag, ...projection.lag, ...(report?.lag ?? [])],
|
|
173
|
+
settlement: settlements[index],
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
const lag = await releaseAppointments(input.scope, released);
|
|
178
|
+
return lag === undefined ? { contracts } : attachReleaseLag(contracts, released, lag);
|
|
179
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ContractId } from "../core/facts/types.js";
|
|
2
|
+
import type { IntentOutcome, IntentRefusal, IntentRetry } from "../protocol/operations.js";
|
|
3
|
+
import type { AcceptedIntent } from "./mutation.js";
|
|
4
|
+
export type ContractAppointmentRefusal = Readonly<{
|
|
5
|
+
kind: "here-worktree-appointed";
|
|
6
|
+
path: string;
|
|
7
|
+
contract?: ContractId;
|
|
8
|
+
}>;
|
|
9
|
+
export type KeiyakuRefusal = IntentRefusal | ContractAppointmentRefusal;
|
|
10
|
+
export type KeiyakuRetryReason = IntentRetry;
|
|
11
|
+
export declare class KeiyakuRefused extends Error {
|
|
12
|
+
readonly refusal: KeiyakuRefusal;
|
|
13
|
+
constructor(refusal: KeiyakuRefusal);
|
|
14
|
+
get code(): KeiyakuRefusal["kind"];
|
|
15
|
+
}
|
|
16
|
+
export declare class KeiyakuRetry extends Error {
|
|
17
|
+
readonly reason: KeiyakuRetryReason;
|
|
18
|
+
constructor(reason: KeiyakuRetryReason);
|
|
19
|
+
get code(): KeiyakuRetryReason["kind"];
|
|
20
|
+
}
|
|
21
|
+
export declare function requireAccepted<Value, Refusal extends KeiyakuRefusal>(result: IntentOutcome<Value, Refusal>): AcceptedIntent<Value>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export class KeiyakuRefused extends Error {
|
|
2
|
+
refusal;
|
|
3
|
+
constructor(refusal) {
|
|
4
|
+
super(`Keiyaku refused: ${refusal.kind}`);
|
|
5
|
+
this.refusal = refusal;
|
|
6
|
+
this.name = "KeiyakuRefused";
|
|
7
|
+
}
|
|
8
|
+
get code() {
|
|
9
|
+
return this.refusal.kind;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export class KeiyakuRetry extends Error {
|
|
13
|
+
reason;
|
|
14
|
+
constructor(reason) {
|
|
15
|
+
super(reason.kind === "publication-failed" ? reason.diagnostic : `Keiyaku retry required: ${reason.kind}`);
|
|
16
|
+
this.reason = reason;
|
|
17
|
+
this.name = "KeiyakuRetry";
|
|
18
|
+
}
|
|
19
|
+
get code() {
|
|
20
|
+
return this.reason.kind;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function requireAccepted(result) {
|
|
24
|
+
if (result.kind === "refused")
|
|
25
|
+
throw new KeiyakuRefused(result.refusal);
|
|
26
|
+
if (result.kind === "retry")
|
|
27
|
+
throw new KeiyakuRetry(result.reason);
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { ContractId } from "../core/facts/types.js";
|
|
2
|
+
import type { GitDecodeChannel } from "../git/read-observation.js";
|
|
2
3
|
import { type RepositoryScope } from "../protocol/operations.js";
|
|
4
|
+
import type { ContractDocumentProjection } from "../protocol/operations.js";
|
|
3
5
|
export type RegionOverlap = Readonly<{
|
|
4
6
|
contract: ContractId;
|
|
5
7
|
patterns: readonly Readonly<{
|
|
@@ -14,4 +16,15 @@ export type RegionObservation = Readonly<{
|
|
|
14
16
|
overlapFailure: string;
|
|
15
17
|
overlaps?: never;
|
|
16
18
|
}>;
|
|
17
|
-
export
|
|
19
|
+
export type RegionDeclarationRead = Readonly<{
|
|
20
|
+
contract: ContractId;
|
|
21
|
+
patterns: readonly string[];
|
|
22
|
+
}>;
|
|
23
|
+
export declare function readRegionDeclarations(documents: readonly ContractDocumentProjection[]): readonly RegionDeclarationRead[];
|
|
24
|
+
export declare function regionIntersections(left: readonly string[], right: readonly string[]): readonly Readonly<{
|
|
25
|
+
left: string;
|
|
26
|
+
right: string;
|
|
27
|
+
}>[];
|
|
28
|
+
export declare function regionPathMatches(patterns: readonly string[], path: string): readonly string[];
|
|
29
|
+
export declare function validateRegionPath(path: unknown): asserts path is string;
|
|
30
|
+
export declare function observeRegion(scope: RepositoryScope, channel: GitDecodeChannel, self: ContractId, mine: readonly string[]): Promise<RegionObservation>;
|
|
@@ -1,13 +1,27 @@
|
|
|
1
1
|
import { decodeContractDocument } from "../body/decode.js";
|
|
2
|
-
import { regionsOverlap } from "../body/region.js";
|
|
3
|
-
import {
|
|
2
|
+
import { assertCanonicalRegionPath, regionPatternsMatchPath, regionsOverlap } from "../body/region.js";
|
|
3
|
+
import { documentsOperationAt } from "../protocol/operations.js";
|
|
4
|
+
export function readRegionDeclarations(documents) {
|
|
5
|
+
return documents.map((document) => ({ contract: document.contract, patterns: decodeContractDocument(document.documentBytes).region }));
|
|
6
|
+
}
|
|
7
|
+
export function regionIntersections(left, right) {
|
|
8
|
+
return regionsOverlap(left, right).map(([leftPattern, rightPattern]) => ({ left: leftPattern, right: rightPattern }));
|
|
9
|
+
}
|
|
10
|
+
export function regionPathMatches(patterns, path) {
|
|
11
|
+
return regionPatternsMatchPath(patterns, path);
|
|
12
|
+
}
|
|
13
|
+
export function validateRegionPath(path) {
|
|
14
|
+
if (typeof path !== "string")
|
|
15
|
+
throw new Error("Region path must be a string");
|
|
16
|
+
assertCanonicalRegionPath(path);
|
|
17
|
+
}
|
|
4
18
|
function diagnostic(error) {
|
|
5
19
|
return error instanceof Error ? error.message : String(error);
|
|
6
20
|
}
|
|
7
|
-
export function observeRegion(scope, self, mine) {
|
|
21
|
+
export async function observeRegion(scope, channel, self, mine) {
|
|
8
22
|
try {
|
|
9
23
|
const overlaps = [];
|
|
10
|
-
for (const peer of
|
|
24
|
+
for (const peer of await documentsOperationAt(scope, channel)) {
|
|
11
25
|
if (peer.contract === self)
|
|
12
26
|
continue;
|
|
13
27
|
try {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { NoGitWorldError, type
|
|
2
|
-
import { type SettlementReport } from "../settlement/settle.js";
|
|
1
|
+
import { NoGitWorldError, type RepositoryScope } from "../protocol/operations.js";
|
|
3
2
|
import { type WorktreeHooks } from "./configuration.js";
|
|
4
|
-
import type
|
|
3
|
+
import { type RepoContractReconcileReport, type RepoReconcileReport } from "./reconcile.js";
|
|
5
4
|
export { NoGitWorldError };
|
|
5
|
+
export type { RepoContractReconcileReport, RepoReconcileReport };
|
|
6
6
|
export type RepoAtInput = Readonly<{
|
|
7
7
|
path?: string;
|
|
8
8
|
}>;
|
|
@@ -10,15 +10,6 @@ export type ReconcileInput = Readonly<{
|
|
|
10
10
|
hooks?: WorktreeHooks;
|
|
11
11
|
retryHooks?: boolean;
|
|
12
12
|
}>;
|
|
13
|
-
export type RepoContractReconcileReport = Readonly<ProtocolReconcileReport & {
|
|
14
|
-
settlement: SettlementReport;
|
|
15
|
-
}>;
|
|
16
|
-
export type RepoReconcileReport = Readonly<{
|
|
17
|
-
contracts: readonly Readonly<{
|
|
18
|
-
contractId: ContractId;
|
|
19
|
-
report: RepoContractReconcileReport;
|
|
20
|
-
}>[];
|
|
21
|
-
}>;
|
|
22
13
|
export declare function reconcileInput(input: ReconcileInput | undefined): Readonly<{
|
|
23
14
|
hooks: WorktreeHooks;
|
|
24
15
|
retryHooks: boolean;
|
|
@@ -26,7 +17,7 @@ export declare function reconcileInput(input: ReconcileInput | undefined): Reado
|
|
|
26
17
|
export declare class Repo {
|
|
27
18
|
readonly root: string;
|
|
28
19
|
private constructor();
|
|
29
|
-
static at(input?: RepoAtInput): Repo
|
|
20
|
+
static at(input?: RepoAtInput): Promise<Repo>;
|
|
30
21
|
currentBranch(): Promise<string | null>;
|
|
31
22
|
reconcile(input?: ReconcileInput): Promise<RepoReconcileReport>;
|
|
32
23
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { currentBranchOperation, NoGitWorldError,
|
|
2
|
-
import { settleAll } from "../settlement/settle.js";
|
|
1
|
+
import { currentBranchOperation, NoGitWorldError, scopeOperation, } from "../protocol/operations.js";
|
|
3
2
|
import { optionalNonblank, requireInput } from "./input.js";
|
|
4
3
|
import { worktreeHooksOption } from "./configuration.js";
|
|
4
|
+
import { withGitDecodeChannel } from "../git/read-observation.js";
|
|
5
|
+
import { completeRepoReconcile, } from "./reconcile.js";
|
|
5
6
|
export { NoGitWorldError };
|
|
6
7
|
const REPO_SCOPES = new WeakMap();
|
|
7
|
-
function resolvePinnedScope(path) {
|
|
8
|
-
return scopeOperation({ coordinate: path === undefined ? process.cwd() : path });
|
|
8
|
+
async function resolvePinnedScope(path) {
|
|
9
|
+
return await scopeOperation({ coordinate: path === undefined ? process.cwd() : path });
|
|
9
10
|
}
|
|
10
11
|
export function reconcileInput(input) {
|
|
11
12
|
const values = input === undefined ? undefined : requireInput(input, "reconcile input");
|
|
@@ -23,32 +24,21 @@ export class Repo {
|
|
|
23
24
|
this.root = scope.primaryWorktree;
|
|
24
25
|
REPO_SCOPES.set(this, scope);
|
|
25
26
|
}
|
|
26
|
-
static at(input) {
|
|
27
|
+
static async at(input) {
|
|
27
28
|
const values = input === undefined ? undefined : requireInput(input, "Repo.at input");
|
|
28
|
-
return new Repo(resolvePinnedScope(optionalNonblank(values?.path, "repository path")));
|
|
29
|
+
return new Repo(await resolvePinnedScope(optionalNonblank(values?.path, "repository path")));
|
|
29
30
|
}
|
|
30
31
|
async currentBranch() {
|
|
31
|
-
return currentBranchOperation({ scope: scopeForRepo(this) });
|
|
32
|
+
return await currentBranchOperation({ scope: scopeForRepo(this) });
|
|
32
33
|
}
|
|
33
34
|
async reconcile(input) {
|
|
34
35
|
const scope = scopeForRepo(this);
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
})),
|
|
42
|
-
});
|
|
43
|
-
return {
|
|
44
|
-
contracts: reconciled.contracts.map((contract, index) => ({
|
|
45
|
-
contractId: contract.contractId,
|
|
46
|
-
report: {
|
|
47
|
-
...contract.report,
|
|
48
|
-
settlement: settlements[index],
|
|
49
|
-
},
|
|
50
|
-
})),
|
|
51
|
-
};
|
|
36
|
+
const options = reconcileInput(input);
|
|
37
|
+
return await withGitDecodeChannel(scope, (channel) => completeRepoReconcile({
|
|
38
|
+
scope,
|
|
39
|
+
channel,
|
|
40
|
+
...options,
|
|
41
|
+
}));
|
|
52
42
|
}
|
|
53
43
|
}
|
|
54
44
|
export function scopeForRepo(value) {
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { type PublicationFailed } from "../git/admission.js";
|
|
2
2
|
import { type GitDecisionObservation } from "../git/observe.js";
|
|
3
|
-
import {
|
|
3
|
+
import type { GitDecodeChannel } from "../git/read-observation.js";
|
|
4
|
+
import { type GitRefAssertion, type GitRepository } from "../git/repository.js";
|
|
4
5
|
import type { AttemptContext } from "../core/decide.js";
|
|
5
6
|
import type { Offer } from "../core/facts/offer.js";
|
|
6
|
-
import { type ContractId, type ContractState, type JournalEntry } from "../core/facts/types.js";
|
|
7
|
+
import { type ContractId, type ContractState, type EntryUlid, type JournalEntry } from "../core/facts/types.js";
|
|
8
|
+
export declare function mintEntryUlids(count: number): readonly EntryUlid[];
|
|
9
|
+
export declare function mintAttempts(input: Readonly<{
|
|
10
|
+
entryCount: number;
|
|
11
|
+
}>): readonly AttemptContext[];
|
|
7
12
|
export type UnknownAttemptClassification = Readonly<{
|
|
8
13
|
kind: "accepted";
|
|
9
14
|
}> | Readonly<{
|
|
@@ -29,7 +34,15 @@ export type DecidedOfferResult = AcceptedAdmission | AttemptTerminal | Readonly<
|
|
|
29
34
|
kind: "redecide";
|
|
30
35
|
}>;
|
|
31
36
|
/** Admit one decided offer without making another legal decision. */
|
|
32
|
-
export declare function admitDecidedOffer(
|
|
37
|
+
export declare function admitDecidedOffer(input: Readonly<{
|
|
38
|
+
channel: GitDecodeChannel;
|
|
39
|
+
repository: GitRepository;
|
|
40
|
+
decisionObservation: GitDecisionObservation;
|
|
41
|
+
attempt: AttemptContext;
|
|
42
|
+
offer: Offer;
|
|
43
|
+
primaryContract: ContractId;
|
|
44
|
+
assertions?: readonly GitRefAssertion[];
|
|
45
|
+
}>): Promise<DecidedOfferResult>;
|
|
33
46
|
/** Classify an unknown atomic-publication outcome using only a captured Git observation. */
|
|
34
47
|
export declare function classifyUnknownAttempt(observation: Readonly<{
|
|
35
48
|
contracts: GitDecisionObservation["journals"];
|
|
@@ -1,9 +1,35 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
1
2
|
import { encodeEntry } from "../core/facts/codec.js";
|
|
2
3
|
import { admit } from "../git/admission.js";
|
|
3
|
-
import {
|
|
4
|
+
import { observeContractsForAdmissionAt } from "../git/observe.js";
|
|
4
5
|
import { GIT_REF, readRef } from "../git/repository.js";
|
|
5
6
|
import { foldJournal } from "../core/facts/fold.js";
|
|
6
7
|
import { AuthorityCorruptionError } from "../core/facts/errors.js";
|
|
8
|
+
import { entryUlid, } from "../core/facts/types.js";
|
|
9
|
+
const ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
10
|
+
const MAX_SEMANTIC_ATTEMPTS = 3;
|
|
11
|
+
function nextEntryUlid() {
|
|
12
|
+
let value = "";
|
|
13
|
+
let time = BigInt(Date.now());
|
|
14
|
+
for (let index = 0; index < 10; index += 1) {
|
|
15
|
+
value = ALPHABET[Number(time & 31n)] + value;
|
|
16
|
+
time >>= 5n;
|
|
17
|
+
}
|
|
18
|
+
let random = BigInt(`0x${randomBytes(10).toString("hex")}`);
|
|
19
|
+
for (let index = 0; index < 16; index += 1) {
|
|
20
|
+
value += ALPHABET[Number(random & 31n)];
|
|
21
|
+
random >>= 5n;
|
|
22
|
+
}
|
|
23
|
+
return entryUlid(value);
|
|
24
|
+
}
|
|
25
|
+
export function mintEntryUlids(count) {
|
|
26
|
+
return Array.from({ length: count }, nextEntryUlid);
|
|
27
|
+
}
|
|
28
|
+
export function mintAttempts(input) {
|
|
29
|
+
return Array.from({ length: MAX_SEMANTIC_ATTEMPTS }, () => ({
|
|
30
|
+
entryUlids: mintEntryUlids(input.entryCount),
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
7
33
|
function offerEntries(offer) {
|
|
8
34
|
return offer.facts.flatMap((append) => append.entries);
|
|
9
35
|
}
|
|
@@ -51,17 +77,22 @@ function recoveredAcceptance(observation, offer, primaryContract) {
|
|
|
51
77
|
journal: record.entries,
|
|
52
78
|
};
|
|
53
79
|
}
|
|
54
|
-
function publicationPremiseMoved(repository, observation, offer) {
|
|
55
|
-
if (readRef(repository, GIT_REF) !== observation.admission.snapshot.commit)
|
|
80
|
+
async function publicationPremiseMoved(repository, observation, offer, assertions) {
|
|
81
|
+
if (await readRef(repository, GIT_REF) !== observation.admission.snapshot.commit)
|
|
56
82
|
return true;
|
|
83
|
+
for (const assertion of assertions)
|
|
84
|
+
if (await readRef(repository, assertion.ref) !== assertion.oid)
|
|
85
|
+
return true;
|
|
57
86
|
return offer.target !== undefined
|
|
58
|
-
&& readRef(repository, offer.target.target) !== offer.target.expectedOid;
|
|
87
|
+
&& await readRef(repository, offer.target.target) !== offer.target.expectedOid;
|
|
59
88
|
}
|
|
60
89
|
/** Admit one decided offer without making another legal decision. */
|
|
61
|
-
export function admitDecidedOffer(
|
|
90
|
+
export async function admitDecidedOffer(input) {
|
|
91
|
+
const { channel, repository, decisionObservation, attempt, offer, primaryContract } = input;
|
|
92
|
+
const assertions = input.assertions ?? [];
|
|
62
93
|
validateOffer(offer, attempt);
|
|
63
94
|
const primary = primaryAppend(offer, primaryContract);
|
|
64
|
-
const admission = admit(repository, offer, decisionObservation.admission);
|
|
95
|
+
const admission = await admit(repository, offer, decisionObservation.admission, assertions);
|
|
65
96
|
if (admission.kind === "accepted") {
|
|
66
97
|
return {
|
|
67
98
|
kind: "accepted",
|
|
@@ -71,14 +102,14 @@ export function admitDecidedOffer(repository, decisionObservation, attempt, offe
|
|
|
71
102
|
};
|
|
72
103
|
}
|
|
73
104
|
if (admission.kind === "publication-failed") {
|
|
74
|
-
return publicationPremiseMoved(repository, decisionObservation, offer)
|
|
105
|
+
return await publicationPremiseMoved(repository, decisionObservation, offer, assertions)
|
|
75
106
|
? { kind: "redecide" }
|
|
76
107
|
: admission;
|
|
77
108
|
}
|
|
78
|
-
const recovered =
|
|
79
|
-
const classification = classifyUnknownAttempt(recovered, offer);
|
|
109
|
+
const recovered = await observeContractsForAdmissionAt(repository, channel, offer.facts.map((append) => append.contractId));
|
|
110
|
+
const classification = classifyUnknownAttempt({ contracts: recovered.journals }, offer);
|
|
80
111
|
if (classification.kind === "accepted")
|
|
81
|
-
return recoveredAcceptance(recovered, offer, primaryContract);
|
|
112
|
+
return recoveredAcceptance({ contracts: recovered.journals }, offer, primaryContract);
|
|
82
113
|
return classification.kind === "collision" ? { kind: "collision" } : { kind: "redecide" };
|
|
83
114
|
}
|
|
84
115
|
/** Classify an unknown atomic-publication outcome using only a captured Git observation. */
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { GitRepository } from "../git/repository.js";
|
|
2
|
+
import type { GitDecodeChannel } from "../git/read-observation.js";
|
|
2
3
|
import type { BindData, ActorId, ContractId } from "../core/facts/types.js";
|
|
3
4
|
import { type BindRefusal } from "../core/verbs/bind.js";
|
|
4
5
|
export type { BindRefusal } from "../core/verbs/bind.js";
|
|
@@ -9,9 +10,14 @@ export type TargetInputRefusal = Readonly<{
|
|
|
9
10
|
kind: "invalid-target";
|
|
10
11
|
}> | Readonly<{
|
|
11
12
|
kind: "target-missing";
|
|
13
|
+
}> | Readonly<{
|
|
14
|
+
kind: "here-target-mismatch";
|
|
15
|
+
target: string;
|
|
16
|
+
branch: string | null;
|
|
12
17
|
}>;
|
|
13
18
|
type BindOperationInput = Readonly<{
|
|
14
19
|
scope: GitRepository;
|
|
20
|
+
channel: GitDecodeChannel;
|
|
15
21
|
title: string;
|
|
16
22
|
terms: BindData["terms"];
|
|
17
23
|
verification: VerificationDeclarationPreparation;
|
|
@@ -19,7 +25,8 @@ type BindOperationInput = Readonly<{
|
|
|
19
25
|
workspace: "worktree" | "here";
|
|
20
26
|
actor?: ActorId;
|
|
21
27
|
decorateOffer?: CompanionDecorator;
|
|
28
|
+
contractId?: ContractId;
|
|
22
29
|
}>;
|
|
23
|
-
export declare function bindOperation(input: BindOperationInput): IntentOutcome<Readonly<{
|
|
30
|
+
export declare function bindOperation(input: BindOperationInput): Promise<IntentOutcome<Readonly<{
|
|
24
31
|
contractId: ContractId;
|
|
25
|
-
}>, BindRefusal | TargetInputRefusal | VerificationDeclarationRefusal
|
|
32
|
+
}>, BindRefusal | TargetInputRefusal | VerificationDeclarationRefusal>>;
|
|
@@ -1,62 +1,70 @@
|
|
|
1
|
-
import { randomBytes } from "node:crypto";
|
|
2
1
|
import { normalizeTargetBranch, observeBindCoordinates } from "../git/observe.js";
|
|
2
|
+
import { gitObjectIdForSnapshot } from "../git/identity.js";
|
|
3
3
|
import { contractIdFromSegment } from "../core/facts/types.js";
|
|
4
4
|
import { decideBind } from "../core/verbs/bind.js";
|
|
5
|
-
import { fitIdentityStem, normalizeIdentityStem } from "../identity/normalize.js";
|
|
6
5
|
import { admitIntent } from "./intent.js";
|
|
7
6
|
import { complete } from "./outcome.js";
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
function
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
for (let index = 0; index < 8; index += 1) {
|
|
14
|
-
suffix = CROCKFORD_BASE32[Number(random & 31n)] + suffix;
|
|
15
|
-
random >>= 5n;
|
|
16
|
-
}
|
|
17
|
-
return suffix.toLowerCase();
|
|
7
|
+
import { fitIdentityStem, normalizeIdentityStem } from "../identity/normalize.js";
|
|
8
|
+
const COLLISION_ATTEMPTS = 3;
|
|
9
|
+
function contractIdFor(title, suffix) {
|
|
10
|
+
const stem = fitIdentityStem({ stem: normalizeIdentityStem({ source: title }) || "contract", maxBytes: 48 });
|
|
11
|
+
return contractIdFromSegment(suffix === undefined ? stem : `${stem}-${suffix}`);
|
|
18
12
|
}
|
|
19
|
-
function
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
13
|
+
async function bindPreparation(input, target, seed) {
|
|
14
|
+
if (input.verification.kind === "refused") {
|
|
15
|
+
return { kind: "prepared", input: { ...seed, preparation: input.verification } };
|
|
16
|
+
}
|
|
17
|
+
const observed = await observeBindCoordinates(input.scope, target);
|
|
18
|
+
if (observed === null)
|
|
19
|
+
return { kind: "refused", refusal: { kind: "target-missing" } };
|
|
20
|
+
if (input.workspace === "here" && target !== undefined && observed.branch !== target) {
|
|
21
|
+
return { kind: "refused", refusal: { kind: "here-target-mismatch", target, branch: observed.branch } };
|
|
22
|
+
}
|
|
23
|
+
const oid = gitObjectIdForSnapshot(observed.start);
|
|
24
|
+
const assertions = [{ ref: target ?? "HEAD", oid }];
|
|
25
|
+
return {
|
|
26
|
+
kind: "prepared",
|
|
27
|
+
input: {
|
|
28
|
+
...seed,
|
|
29
|
+
preparation: {
|
|
30
|
+
kind: "prepared",
|
|
31
|
+
data: {
|
|
32
|
+
coordinates: {
|
|
33
|
+
start: observed.start,
|
|
34
|
+
...(observed.target === undefined ? {} : { target: observed.target }),
|
|
35
|
+
workspace: input.workspace,
|
|
36
|
+
},
|
|
37
|
+
terms: input.terms,
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
assertions,
|
|
42
|
+
};
|
|
25
43
|
}
|
|
26
|
-
export function bindOperation(input) {
|
|
44
|
+
export async function bindOperation(input) {
|
|
27
45
|
let target;
|
|
28
46
|
if (input.target !== undefined) {
|
|
29
|
-
const normalized = normalizeTargetBranch(input.scope, input.target);
|
|
47
|
+
const normalized = await normalizeTargetBranch(input.scope, input.target);
|
|
30
48
|
if (normalized === null)
|
|
31
49
|
return { kind: "refused", refusal: { kind: "invalid-target" } };
|
|
32
50
|
target = normalized;
|
|
33
51
|
}
|
|
34
|
-
const observed = observeBindCoordinates(input.scope, target);
|
|
35
|
-
if (observed === null)
|
|
36
|
-
return { kind: "refused", refusal: { kind: "target-missing" } };
|
|
37
|
-
const data = {
|
|
38
|
-
coordinates: {
|
|
39
|
-
start: observed.start,
|
|
40
|
-
...(observed.target === undefined ? {} : { target: observed.target }),
|
|
41
|
-
workspace: input.workspace,
|
|
42
|
-
},
|
|
43
|
-
terms: input.terms,
|
|
44
|
-
};
|
|
45
|
-
const stem = normalizeIdentityStem({ source: input.title }) || "contract";
|
|
46
52
|
const at = new Date().toISOString();
|
|
47
|
-
const attempt = (id) => complete(admitIntent(input.scope, {
|
|
53
|
+
const attempt = async (id) => complete(await admitIntent(input.channel, input.scope, {
|
|
48
54
|
contractId: id,
|
|
49
55
|
...(input.actor === undefined ? {} : { actor: input.actor }),
|
|
50
56
|
at,
|
|
51
|
-
preparation: input.verification.kind === "prepared"
|
|
52
|
-
? { kind: "prepared", data }
|
|
53
|
-
: { kind: "refused", refusal: input.verification.refusal },
|
|
54
57
|
}, decideBind, {
|
|
55
58
|
observedContracts: [id, ...input.terms.after],
|
|
59
|
+
prepareInput: async (_observation, original) => bindPreparation(input, target, original),
|
|
56
60
|
...(input.decorateOffer === undefined ? {} : { decorateOffer: input.decorateOffer }),
|
|
57
61
|
}), { contractId: id });
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
+
if (input.contractId !== undefined)
|
|
63
|
+
return attempt(input.contractId);
|
|
64
|
+
let result = await attempt(contractIdFor(input.title));
|
|
65
|
+
for (let collision = 0; collision < COLLISION_ATTEMPTS && result.kind === "refused" && result.refusal.kind === "contract-exists"; collision += 1) {
|
|
66
|
+
result = await attempt(contractIdFor(input.title, randomBytes(8).toString("hex")));
|
|
67
|
+
}
|
|
68
|
+
return result;
|
|
62
69
|
}
|
|
70
|
+
import { randomBytes } from "node:crypto";
|