@astrosheep/keiyaku 4.0.0 → 4.0.1
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 +2 -9
- 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 +11 -3
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +9 -6
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +94 -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 +162 -45
- package/build/src/akuma/akuma.d.ts +33 -25
- package/build/src/akuma/akuma.js +93 -61
- package/build/src/akuma/archetype.d.ts +8 -0
- package/build/src/akuma/archetype.js +28 -9
- package/build/src/akuma/body.js +196 -84
- 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 +67 -15
- package/build/src/akuma/heart/facts.js +3 -3
- package/build/src/akuma/heart/index.d.ts +41 -29
- package/build/src/akuma/heart/index.js +72 -193
- package/build/src/akuma/heart/rows.d.ts +30 -53
- package/build/src/akuma/heart/rows.js +91 -111
- package/build/src/akuma/heart/schema.d.ts +1 -1
- package/build/src/akuma/heart/schema.js +55 -8
- package/build/src/akuma/heart/storage.d.ts +33 -0
- package/build/src/akuma/heart/storage.js +164 -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/index.d.ts +4 -2
- package/build/src/akuma/index.js +1 -1
- package/build/src/akuma/projection.d.ts +97 -0
- package/build/src/akuma/projection.js +145 -0
- package/build/src/akuma/provider.d.ts +57 -11
- package/build/src/akuma/provider.js +57 -37
- package/build/src/akuma/providers/{claude.d.ts → claude/events.d.ts} +9 -41
- package/build/src/akuma/providers/claude/events.js +185 -0
- package/build/src/akuma/providers/claude/index.d.ts +46 -0
- package/build/src/akuma/providers/claude/index.js +265 -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 +12 -12
- package/build/src/akuma/providers/codex-app-server/index.d.ts +13 -11
- package/build/src/akuma/providers/codex-app-server/index.js +73 -22
- package/build/src/akuma/providers/index.js +22 -8
- package/build/src/akuma/providers/opencode-sdk/events.d.ts +48 -0
- package/build/src/akuma/providers/opencode-sdk/events.js +116 -0
- package/build/src/akuma/providers/opencode-sdk/index.d.ts +32 -0
- package/build/src/akuma/providers/opencode-sdk/index.js +203 -0
- package/build/src/akuma/providers/opencode-sdk/session.d.ts +41 -0
- package/build/src/akuma/providers/opencode-sdk/session.js +62 -0
- package/build/src/akuma/providers/pi/events.d.ts +33 -0
- package/build/src/akuma/providers/pi/events.js +128 -0
- package/build/src/akuma/providers/pi/index.d.ts +11 -0
- package/build/src/akuma/providers/pi/index.js +162 -0
- package/build/src/akuma/requests.d.ts +1 -0
- package/build/src/akuma/requests.js +5 -2
- package/build/src/alias/index.d.ts +4 -3
- package/build/src/alias/index.js +2 -3
- 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 +2 -0
- package/build/src/cli/accepted.js +2 -1
- package/build/src/cli/commands/akuma-invoke.d.ts +42 -18
- package/build/src/cli/commands/akuma-invoke.js +94 -50
- package/build/src/cli/commands/akuma.d.ts +15 -7
- package/build/src/cli/commands/akuma.js +94 -56
- package/build/src/cli/commands/contract.d.ts +142 -0
- package/build/src/cli/commands/contract.js +14 -0
- package/build/src/cli/commands/install.js +2 -0
- package/build/src/cli/commands/task-invoke.d.ts +3 -1
- package/build/src/cli/commands/task-invoke.js +27 -2
- package/build/src/cli/commands/task.js +2 -2
- package/build/src/cli/invoke.d.ts +6 -1
- package/build/src/cli/invoke.js +241 -43
- package/build/src/cli/main.js +20 -1
- package/build/src/cli/parse.d.ts +13 -116
- package/build/src/cli/parse.js +51 -78
- package/build/src/cli/render/akuma-tool.d.ts +2 -0
- package/build/src/cli/render/akuma-tool.js +6 -2
- package/build/src/cli/render/akuma.d.ts +2 -1
- package/build/src/cli/render/akuma.js +157 -217
- 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 +1 -1
- package/build/src/cli/render/contract.js +38 -5
- package/build/src/cli/render/kanshi.js +53 -5
- package/build/src/cli/render/refusal.js +17 -0
- package/build/src/cli/render/terminal.d.ts +13 -0
- package/build/src/cli/render/terminal.js +75 -0
- package/build/src/cli/render/text.js +5 -0
- package/build/src/cli/result.d.ts +15 -3
- package/build/src/contract-worktree.d.ts +43 -0
- package/build/src/contract-worktree.js +213 -0
- package/build/src/coordination/durable-file.d.ts +3 -0
- package/build/src/coordination/durable-file.js +44 -1
- 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/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.js +12 -16
- package/build/src/core/verbs/bind.js +2 -16
- package/build/src/core/verbs/deliver.js +20 -4
- package/build/src/core/verbs/placement.js +3 -8
- package/build/src/dispatch/index.d.ts +3 -1
- package/build/src/dispatch/index.js +14 -5
- 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 +26 -16
- package/build/src/git/hooks.d.ts +15 -0
- package/build/src/git/hooks.js +57 -5
- package/build/src/git/identity.d.ts +5 -1
- package/build/src/git/identity.js +23 -4
- package/build/src/git/integration.d.ts +49 -0
- package/build/src/git/integration.js +214 -0
- package/build/src/git/observe.d.ts +25 -13
- package/build/src/git/observe.js +175 -72
- 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 +15 -5
- package/build/src/git/reconcile.js +238 -85
- package/build/src/git/repository.d.ts +19 -4
- package/build/src/git/repository.js +77 -43
- package/build/src/git/scratch.d.ts +13 -0
- package/build/src/git/scratch.js +66 -0
- package/build/src/git/target-placement.d.ts +56 -0
- package/build/src/git/target-placement.js +255 -0
- package/build/src/git/tender.d.ts +48 -0
- package/build/src/git/tender.js +82 -0
- package/build/src/git/terminal-seal.d.ts +24 -0
- package/build/src/git/terminal-seal.js +87 -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 +21 -0
- package/build/src/git/workspace.js +104 -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 +1 -1
- package/build/src/kanshi/read.d.ts +5 -2
- package/build/src/kanshi/read.js +154 -45
- package/build/src/kanshi/report.d.ts +31 -6
- package/build/src/kanshi/select.js +1 -1
- package/build/src/library/address.d.ts +48 -0
- package/build/src/library/address.js +134 -0
- package/build/src/library/akuma-creation.d.ts +10 -6
- package/build/src/library/akuma-creation.js +12 -11
- 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 +72 -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 +21 -46
- package/build/src/library/contract.js +193 -183
- package/build/src/library/delivery.d.ts +23 -0
- package/build/src/library/delivery.js +21 -0
- package/build/src/library/fleet.d.ts +65 -0
- package/build/src/library/fleet.js +189 -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 +16 -2
- package/build/src/library/keiyaku.js +11 -1
- package/build/src/library/mutation.d.ts +38 -0
- package/build/src/library/mutation.js +39 -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 +2 -1
- package/build/src/library/region.js +3 -3
- package/build/src/library/repo.d.ts +4 -1
- package/build/src/library/repo.js +31 -16
- package/build/src/protocol/attempt.d.ts +16 -3
- package/build/src/protocol/attempt.js +39 -8
- package/build/src/protocol/bind.d.ts +9 -2
- package/build/src/protocol/bind.js +47 -39
- package/build/src/protocol/intent.d.ts +42 -18
- package/build/src/protocol/intent.js +36 -75
- package/build/src/protocol/operations.d.ts +93 -35
- package/build/src/protocol/operations.js +235 -123
- package/build/src/protocol/outcome.d.ts +7 -2
- package/build/src/protocol/outcome.js +9 -3
- package/build/src/protocol/placement.d.ts +30 -0
- package/build/src/protocol/placement.js +114 -0
- package/build/src/protocol/read/audit.d.ts +9 -3
- package/build/src/protocol/read/audit.js +25 -9
- 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 +11 -5
- package/build/src/protocol/read/status.js +28 -15
- 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 +1 -0
- package/build/src/runtime/proc/line-rpc.js +21 -0
- package/build/src/runtime/proc/run.d.ts +18 -2
- package/build/src/runtime/proc/run.js +34 -8
- package/build/src/settings.d.ts +6 -0
- package/build/src/settings.js +29 -14
- package/build/src/settlement/fence.d.ts +9 -0
- package/build/src/settlement/fence.js +23 -0
- package/build/src/settlement/holder.d.ts +25 -4
- package/build/src/settlement/holder.js +102 -28
- package/build/src/settlement/settle.d.ts +8 -0
- package/build/src/settlement/settle.js +102 -28
- package/build/src/task/board.d.ts +20 -2
- package/build/src/task/board.js +11 -1
- package/build/src/task/compose.d.ts +2 -2
- package/build/src/task/compose.js +2 -2
- package/build/src/task/context.js +2 -7
- package/build/src/task/index.d.ts +9 -9
- package/build/src/task/index.js +15 -12
- package/build/src/task/operations.d.ts +31 -12
- package/build/src/task/operations.js +8 -4
- package/build/src/task/store.d.ts +4 -6
- package/build/src/task/store.js +3 -3
- package/build/src/verification/declaration.d.ts +1 -0
- package/build/src/verification/execution.d.ts +49 -0
- package/build/src/verification/execution.js +104 -0
- package/build/src/world.d.ts +13 -0
- package/build/src/world.js +103 -0
- package/package.json +7 -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/verification/producer.d.ts +0 -23
- package/build/src/verification/producer.js +0 -54
|
@@ -1,16 +1,7 @@
|
|
|
1
|
-
import type { Offer } from "./offer.js";
|
|
2
1
|
import { type ContractsObservation } from "./observation.js";
|
|
3
|
-
import type { ContractId
|
|
2
|
+
import type { ContractId } from "./types.js";
|
|
4
3
|
type EligibilityObservation = ContractsObservation;
|
|
5
|
-
type EligibilityAttempt = Readonly<{
|
|
6
|
-
entryUlids: readonly EntryUlid[];
|
|
7
|
-
}>;
|
|
8
|
-
type PrerequisiteStatus = "unknown" | "pending" | "claimed";
|
|
9
|
-
/** Resolve prerequisite existence and eligibility from one decision snapshot. */
|
|
10
|
-
export declare function prerequisiteStatus(prerequisites: readonly ContractId[], observation: EligibilityObservation, offeredClaims?: ReadonlySet<ContractId>): PrerequisiteStatus;
|
|
11
4
|
/** Decide whether resulting prerequisites reach the amended contract. */
|
|
12
5
|
export declare function prerequisitesReach(contract: ContractId, prerequisites: readonly ContractId[], observation: EligibilityObservation): boolean;
|
|
13
6
|
export declare function samePrerequisites(left: readonly ContractId[], right: readonly ContractId[]): boolean;
|
|
14
|
-
/** Append bounds made eligible by a claimed offer from its full snapshot. */
|
|
15
|
-
export declare function placeEligibleBounds(offer: Offer, observation: EligibilityObservation, attempt: EligibilityAttempt): Offer;
|
|
16
7
|
export {};
|
|
@@ -1,16 +1,4 @@
|
|
|
1
1
|
import { contractState } from "./observation.js";
|
|
2
|
-
/** Resolve prerequisite existence and eligibility from one decision snapshot. */
|
|
3
|
-
export function prerequisiteStatus(prerequisites, observation, offeredClaims) {
|
|
4
|
-
let pending = false;
|
|
5
|
-
for (const dependency of prerequisites) {
|
|
6
|
-
const state = contractState(observation, dependency);
|
|
7
|
-
if (state === null)
|
|
8
|
-
return "unknown";
|
|
9
|
-
if (state.terminal?.kind !== "claimed" && offeredClaims?.has(dependency) !== true)
|
|
10
|
-
pending = true;
|
|
11
|
-
}
|
|
12
|
-
return pending ? "pending" : "claimed";
|
|
13
|
-
}
|
|
14
2
|
/** Decide whether resulting prerequisites reach the amended contract. */
|
|
15
3
|
export function prerequisitesReach(contract, prerequisites, observation) {
|
|
16
4
|
const pending = [...prerequisites];
|
|
@@ -36,40 +24,3 @@ export function samePrerequisites(left, right) {
|
|
|
36
24
|
return false;
|
|
37
25
|
return left.every((id, index) => id === right[index]);
|
|
38
26
|
}
|
|
39
|
-
/** Append bounds made eligible by a claimed offer from its full snapshot. */
|
|
40
|
-
export function placeEligibleBounds(offer, observation, attempt) {
|
|
41
|
-
const claimedEntry = offer.facts[0]?.entries[0];
|
|
42
|
-
if (offer.facts.length !== 1 || offer.facts[0]?.entries.length !== 1 || claimedEntry?.kind !== "claimed") {
|
|
43
|
-
throw new Error("eligible-bound placement requires exactly one claimed fact");
|
|
44
|
-
}
|
|
45
|
-
const claimed = new Set([claimedEntry.contract]);
|
|
46
|
-
const used = new Set([claimedEntry.entry]);
|
|
47
|
-
const available = attempt.entryUlids.filter((entry) => !used.has(entry));
|
|
48
|
-
let availableIndex = 0;
|
|
49
|
-
const additions = [];
|
|
50
|
-
for (const id of observation.keys()) {
|
|
51
|
-
const state = contractState(observation, id);
|
|
52
|
-
if (!state || state.bound || state.terminal)
|
|
53
|
-
continue;
|
|
54
|
-
if (prerequisiteStatus(state.terms.after, observation, claimed) !== "claimed")
|
|
55
|
-
continue;
|
|
56
|
-
const entry = available[availableIndex];
|
|
57
|
-
if (entry === undefined)
|
|
58
|
-
throw new Error("placement attempt lacks an entry ULID for an eligible contract");
|
|
59
|
-
availableIndex += 1;
|
|
60
|
-
additions.push({
|
|
61
|
-
contractId: id,
|
|
62
|
-
expectedHead: state.head,
|
|
63
|
-
entries: [{
|
|
64
|
-
v: 1,
|
|
65
|
-
kind: "bound",
|
|
66
|
-
contract: id,
|
|
67
|
-
entry,
|
|
68
|
-
at: claimedEntry.at,
|
|
69
|
-
...(claimedEntry.actor === undefined ? {} : { actor: claimedEntry.actor }),
|
|
70
|
-
data: {},
|
|
71
|
-
}],
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
return additions.length === 0 ? offer : { ...offer, facts: [...additions, ...offer.facts] };
|
|
75
|
-
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import type { ContractId, ContractState, DocumentKey } from "./types.js";
|
|
2
2
|
/** The complete state projection consumed by one pure pact decision. */
|
|
3
3
|
export type ContractsObservation = ReadonlyMap<ContractId, ContractState | null>;
|
|
4
|
+
export type PrerequisiteStatus = "unknown" | "pending" | "claimed";
|
|
4
5
|
/** Read one requested identity; a missing key violates the observation contract. */
|
|
5
6
|
export declare function contractState(observation: ContractsObservation, contract: ContractId): ContractState | null;
|
|
7
|
+
/** Project prerequisite existence and terminal satisfaction from one observation. */
|
|
8
|
+
export declare function prerequisiteStatus(prerequisites: readonly ContractId[], observation: ContractsObservation): PrerequisiteStatus;
|
|
6
9
|
export type ActiveContractRefusal = Readonly<{
|
|
7
10
|
kind: "contract-missing" | "terminal";
|
|
8
11
|
contractId: ContractId;
|
|
@@ -9,6 +9,18 @@ export function contractState(observation, contract) {
|
|
|
9
9
|
}
|
|
10
10
|
return state;
|
|
11
11
|
}
|
|
12
|
+
/** Project prerequisite existence and terminal satisfaction from one observation. */
|
|
13
|
+
export function prerequisiteStatus(prerequisites, observation) {
|
|
14
|
+
let pending = false;
|
|
15
|
+
for (const dependency of prerequisites) {
|
|
16
|
+
const state = contractState(observation, dependency);
|
|
17
|
+
if (state === null)
|
|
18
|
+
return "unknown";
|
|
19
|
+
if (state.terminal?.kind !== "claimed")
|
|
20
|
+
pending = true;
|
|
21
|
+
}
|
|
22
|
+
return pending ? "pending" : "claimed";
|
|
23
|
+
}
|
|
12
24
|
/** The single lifecycle guard shared by every operation on an existing contract. */
|
|
13
25
|
export function activeContract(observation, contract) {
|
|
14
26
|
const state = contractState(observation, contract);
|
|
@@ -74,9 +74,16 @@ export type BindData = Readonly<{
|
|
|
74
74
|
export type AmendData = ContractTerms;
|
|
75
75
|
export type BoundData = Readonly<Record<string, never>>;
|
|
76
76
|
export type DeliverData = Readonly<{
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
tenderSnapshot: SnapshotId;
|
|
78
|
+
integration: Readonly<{
|
|
79
|
+
predecessor: SnapshotId;
|
|
80
|
+
snapshot: SnapshotId;
|
|
81
|
+
changeId: ChangeId;
|
|
82
|
+
}>;
|
|
83
|
+
method: "squash";
|
|
84
|
+
policy: Readonly<{
|
|
85
|
+
requireBranchesToBeUpToDate: boolean;
|
|
86
|
+
}>;
|
|
80
87
|
}>;
|
|
81
88
|
export type AttestationData = Readonly<{
|
|
82
89
|
gate: Gate;
|
|
@@ -69,8 +69,8 @@ function currentKeys(state) {
|
|
|
69
69
|
encodeKey({ kind: "document", value: state.terms.document.key }),
|
|
70
70
|
...state.terms.segments.map((value) => encodeKey({ kind: "segment", value })),
|
|
71
71
|
...(state.delivery === null ? [] : [
|
|
72
|
-
encodeKey({ kind: "snapshot", value: state.delivery.data.
|
|
73
|
-
encodeKey({ kind: "change", value: state.delivery.data.
|
|
72
|
+
encodeKey({ kind: "snapshot", value: state.delivery.data.integration.snapshot }),
|
|
73
|
+
encodeKey({ kind: "change", value: state.delivery.data.integration.changeId }),
|
|
74
74
|
]),
|
|
75
75
|
]);
|
|
76
76
|
}
|
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { activeContract } from "../facts/observation.js";
|
|
1
|
+
import { prerequisitesReach, samePrerequisites } from "../facts/eligibility.js";
|
|
2
|
+
import { activeContract, prerequisiteStatus } from "../facts/observation.js";
|
|
3
|
+
function prerequisitesConsumed(current, next, observation) {
|
|
4
|
+
if (samePrerequisites(current.terms.after, next.after))
|
|
5
|
+
return false;
|
|
6
|
+
const status = prerequisiteStatus(current.terms.after, observation);
|
|
7
|
+
if (status === "unknown")
|
|
8
|
+
throw new Error("current prerequisite is absent from the decision observation");
|
|
9
|
+
return current.bound !== null || status === "claimed";
|
|
10
|
+
}
|
|
3
11
|
export function decideAmend({ input, attempt, observation }) {
|
|
4
12
|
const id = input.contractId;
|
|
5
13
|
const current = activeContract(observation, id);
|
|
@@ -21,7 +29,7 @@ export function decideAmend({ input, attempt, observation }) {
|
|
|
21
29
|
if (input.preparation.kind === "refused")
|
|
22
30
|
return { kind: "refused", refusal: input.preparation.refusal };
|
|
23
31
|
const { data } = input.preparation;
|
|
24
|
-
if (
|
|
32
|
+
if (prerequisitesConsumed(current, data, observation)) {
|
|
25
33
|
return { kind: "refused", refusal: { kind: "prerequisites-already-consumed", contractId: id } };
|
|
26
34
|
}
|
|
27
35
|
const prerequisites = prerequisiteStatus(data.after, observation);
|
|
@@ -40,17 +48,5 @@ export function decideAmend({ input, attempt, observation }) {
|
|
|
40
48
|
...(input.actor === undefined ? {} : { actor: input.actor }),
|
|
41
49
|
data,
|
|
42
50
|
};
|
|
43
|
-
|
|
44
|
-
if (current.bound === null && prerequisites === "claimed") {
|
|
45
|
-
entries.push({
|
|
46
|
-
v: 1,
|
|
47
|
-
kind: "bound",
|
|
48
|
-
contract: id,
|
|
49
|
-
entry: attempt.entryUlids[1],
|
|
50
|
-
at: input.at,
|
|
51
|
-
...(input.actor === undefined ? {} : { actor: input.actor }),
|
|
52
|
-
data: {},
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
return { kind: "offer", offer: { facts: [{ contractId: id, expectedHead: current.head, entries }] } };
|
|
51
|
+
return { kind: "offer", offer: { facts: [{ contractId: id, expectedHead: current.head, entries: [entry] }] } };
|
|
56
52
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { prerequisiteStatus } from "../facts/
|
|
2
|
-
import { contractState } from "../facts/observation.js";
|
|
1
|
+
import { contractState, prerequisiteStatus } from "../facts/observation.js";
|
|
3
2
|
export function decideBind({ input, attempt, observation }) {
|
|
4
3
|
const id = input.contractId;
|
|
5
4
|
const current = contractState(observation, id);
|
|
@@ -23,18 +22,5 @@ export function decideBind({ input, attempt, observation }) {
|
|
|
23
22
|
...(input.actor === undefined ? {} : { actor: input.actor }),
|
|
24
23
|
data,
|
|
25
24
|
};
|
|
26
|
-
|
|
27
|
-
if (prerequisites === "claimed") {
|
|
28
|
-
const bound = {
|
|
29
|
-
v: 1,
|
|
30
|
-
kind: "bound",
|
|
31
|
-
contract: id,
|
|
32
|
-
entry: attempt.entryUlids[1],
|
|
33
|
-
at: input.at,
|
|
34
|
-
...(input.actor === undefined ? {} : { actor: input.actor }),
|
|
35
|
-
data: {},
|
|
36
|
-
};
|
|
37
|
-
entries.push(bound);
|
|
38
|
-
}
|
|
39
|
-
return { kind: "offer", offer: { facts: [{ contractId: id, expectedHead: null, entries }] } };
|
|
25
|
+
return { kind: "offer", offer: { facts: [{ contractId: id, expectedHead: null, entries: [bind] }] } };
|
|
40
26
|
}
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import { activeContract, documentIsCurrent } from "../facts/observation.js";
|
|
1
|
+
import { activeContract, documentIsCurrent, prerequisiteStatus } from "../facts/observation.js";
|
|
2
2
|
export function decideDeliver({ input, attempt, observation, }) {
|
|
3
3
|
const state = activeContract(observation, input.contractId);
|
|
4
4
|
if ("kind" in state)
|
|
5
5
|
return { kind: "refused", refusal: state };
|
|
6
|
-
|
|
6
|
+
const prerequisites = state.bound === null
|
|
7
|
+
? prerequisiteStatus(state.terms.after, observation)
|
|
8
|
+
: "claimed";
|
|
9
|
+
if (prerequisites !== "claimed") {
|
|
7
10
|
return { kind: "refused", refusal: { kind: "not-bound", contractId: input.contractId } };
|
|
11
|
+
}
|
|
8
12
|
if (input.preparation.kind === "unavailable") {
|
|
9
13
|
throw new Error("existing contract requires a stamped delivery preparation");
|
|
10
14
|
}
|
|
@@ -13,14 +17,26 @@ export function decideDeliver({ input, attempt, observation, }) {
|
|
|
13
17
|
}
|
|
14
18
|
if (input.preparation.kind === "refused")
|
|
15
19
|
return { kind: "refused", refusal: input.preparation.refusal };
|
|
20
|
+
const entries = [];
|
|
21
|
+
if (state.bound === null)
|
|
22
|
+
entries.push({
|
|
23
|
+
v: 1,
|
|
24
|
+
kind: "bound",
|
|
25
|
+
contract: input.contractId,
|
|
26
|
+
entry: attempt.entryUlids[0],
|
|
27
|
+
at: input.at,
|
|
28
|
+
...(input.actor === undefined ? {} : { actor: input.actor }),
|
|
29
|
+
data: {},
|
|
30
|
+
});
|
|
16
31
|
const deliver = {
|
|
17
32
|
v: 1,
|
|
18
33
|
kind: "deliver",
|
|
19
34
|
contract: input.contractId,
|
|
20
|
-
entry: attempt.entryUlids[0],
|
|
35
|
+
entry: attempt.entryUlids[state.bound === null ? 1 : 0],
|
|
21
36
|
at: input.at,
|
|
22
37
|
...(input.actor === undefined ? {} : { actor: input.actor }),
|
|
23
38
|
data: input.preparation.data,
|
|
24
39
|
};
|
|
25
|
-
|
|
40
|
+
entries.push(deliver);
|
|
41
|
+
return { kind: "offer", offer: { facts: [{ contractId: input.contractId, expectedHead: state.head, entries }] } };
|
|
26
42
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { gatesSatisfied } from "../facts/gate.js";
|
|
2
|
-
import { placeEligibleBounds } from "../facts/eligibility.js";
|
|
3
2
|
import { activeContract } from "../facts/observation.js";
|
|
4
3
|
export function decidePlacement({ input, attempt, observation }) {
|
|
5
4
|
const id = input.contractId;
|
|
@@ -22,21 +21,17 @@ export function decidePlacement({ input, attempt, observation }) {
|
|
|
22
21
|
...(input.actor === undefined ? {} : { actor: input.actor }),
|
|
23
22
|
data: { delivery: delivery.entry },
|
|
24
23
|
};
|
|
25
|
-
|
|
24
|
+
return {
|
|
26
25
|
kind: "offer",
|
|
27
26
|
offer: {
|
|
28
27
|
facts: [{ contractId: id, expectedHead: current.head, entries: [claimed] }],
|
|
29
28
|
...(current.coordinates.target === undefined ? {} : {
|
|
30
29
|
target: {
|
|
31
30
|
target: current.coordinates.target,
|
|
32
|
-
expectedOid: delivery.data.
|
|
33
|
-
newOid: delivery.data.
|
|
31
|
+
expectedOid: delivery.data.integration.predecessor,
|
|
32
|
+
newOid: delivery.data.integration.snapshot,
|
|
34
33
|
},
|
|
35
34
|
}),
|
|
36
35
|
},
|
|
37
36
|
};
|
|
38
|
-
return {
|
|
39
|
-
...offer,
|
|
40
|
-
offer: placeEligibleBounds(offer.offer, observation, attempt),
|
|
41
|
-
};
|
|
42
37
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type ContractId } from "../core/facts/types.js";
|
|
2
2
|
import { type AkuId } from "../akuma/identity.js";
|
|
3
3
|
import { type GitRepository } from "../git/repository.js";
|
|
4
|
+
import { type GitReadObservation } from "../git/read-observation.js";
|
|
4
5
|
export type Dispatch = Readonly<{
|
|
5
6
|
akuId: AkuId;
|
|
6
7
|
contractId: ContractId;
|
|
@@ -23,7 +24,8 @@ export type DispatchPublication = Readonly<{
|
|
|
23
24
|
failure: DispatchFailure;
|
|
24
25
|
}>;
|
|
25
26
|
export declare function readDispatch(repository: GitRepository, value: AkuId): Dispatch | null;
|
|
26
|
-
export declare function
|
|
27
|
+
export declare function readDispatchesAt(observation: GitReadObservation): Promise<readonly Dispatch[]>;
|
|
28
|
+
export declare function readDispatches(repository: GitRepository): Promise<readonly Dispatch[]>;
|
|
27
29
|
export declare function publishDispatch(input: Readonly<{
|
|
28
30
|
repository: GitRepository;
|
|
29
31
|
akuId: AkuId;
|
|
@@ -2,7 +2,8 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { AuthorityCorruptionError } from "../core/facts/errors.js";
|
|
3
3
|
import { contractId } from "../core/facts/types.js";
|
|
4
4
|
import { parseAkuId } from "../akuma/identity.js";
|
|
5
|
-
import { GIT_FORMAT_BYTES, GIT_FORMAT_PATH, GIT_REF, readBlob,
|
|
5
|
+
import { GIT_FORMAT_BYTES, GIT_FORMAT_PATH, GIT_REF, readBlob, readGitPaths, updateGitTree, updateRefsAtomically, writeBlob, writeCommit, } from "../git/repository.js";
|
|
6
|
+
import { withGitDecodeChannel, withGitReadObservation } from "../git/read-observation.js";
|
|
6
7
|
const DISPATCH_ROOT = "dispatch";
|
|
7
8
|
const DISPATCH_PREFIX = `${DISPATCH_ROOT}/`;
|
|
8
9
|
const DISPATCH_SUFFIX = ".json";
|
|
@@ -77,11 +78,13 @@ export function readDispatch(repository, value) {
|
|
|
77
78
|
const path = pathFor(akuId);
|
|
78
79
|
return dispatchFromSnapshot(repository, readGitPaths(repository, [path]), akuId);
|
|
79
80
|
}
|
|
80
|
-
export function
|
|
81
|
-
const
|
|
81
|
+
export async function readDispatchesAt(observation) {
|
|
82
|
+
const entries = [...observation.snapshot.paths]
|
|
83
|
+
.filter(([path, entry]) => path.startsWith(DISPATCH_PREFIX) && entry.type === "blob");
|
|
84
|
+
const blobs = await observation.readBlobs(entries.map(([, entry]) => entry.oid));
|
|
82
85
|
const dispatches = [];
|
|
83
86
|
const seen = new Set();
|
|
84
|
-
for (const [path, entry] of snapshot.paths) {
|
|
87
|
+
for (const [path, entry] of observation.snapshot.paths) {
|
|
85
88
|
if (path === DISPATCH_ROOT)
|
|
86
89
|
corruption(`Dispatch authority root is not a tree: ${path}`);
|
|
87
90
|
if (!path.startsWith(DISPATCH_PREFIX))
|
|
@@ -90,7 +93,10 @@ export function readDispatches(repository) {
|
|
|
90
93
|
corruption(`unexpected Dispatch authority path: ${path}`);
|
|
91
94
|
if (entry.type !== "blob")
|
|
92
95
|
corruption(`Dispatch path is not a blob: ${path}`);
|
|
93
|
-
const
|
|
96
|
+
const result = blobs.get(entry.oid);
|
|
97
|
+
if (result?.kind !== "present")
|
|
98
|
+
corruption(`missing Dispatch Git object: ${path}`);
|
|
99
|
+
const dispatch = decode(path, result.bytes);
|
|
94
100
|
if (seen.has(dispatch.akuId))
|
|
95
101
|
corruption(`duplicate Dispatch identity: ${dispatch.akuId}`);
|
|
96
102
|
seen.add(dispatch.akuId);
|
|
@@ -98,6 +104,9 @@ export function readDispatches(repository) {
|
|
|
98
104
|
}
|
|
99
105
|
return dispatches.sort((left, right) => Buffer.compare(Buffer.from(left.akuId), Buffer.from(right.akuId)));
|
|
100
106
|
}
|
|
107
|
+
export async function readDispatches(repository) {
|
|
108
|
+
return withGitDecodeChannel(repository, (channel) => withGitReadObservation(repository, channel, readDispatchesAt));
|
|
109
|
+
}
|
|
101
110
|
function observedPublication(repository, akuId, owner) {
|
|
102
111
|
const current = readDispatch(repository, akuId);
|
|
103
112
|
if (current === null)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type DurationParseResult = Readonly<{
|
|
2
|
+
kind: "parsed";
|
|
3
|
+
milliseconds: number;
|
|
4
|
+
}> | Readonly<{
|
|
5
|
+
kind: "invalid";
|
|
6
|
+
}> | Readonly<{
|
|
7
|
+
kind: "overflow";
|
|
8
|
+
}>;
|
|
9
|
+
export declare function parseDuration(value: string): DurationParseResult;
|
|
10
|
+
export declare function formatDuration(milliseconds: number): string;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const MULTIPLIERS = Object.freeze({
|
|
2
|
+
ms: 1n,
|
|
3
|
+
s: 1000n,
|
|
4
|
+
m: 60000n,
|
|
5
|
+
h: 3600000n,
|
|
6
|
+
});
|
|
7
|
+
export function parseDuration(value) {
|
|
8
|
+
const match = /^(0|[1-9][0-9]*)(ms|s|m|h)$/u.exec(value);
|
|
9
|
+
if (match === null)
|
|
10
|
+
return { kind: "invalid" };
|
|
11
|
+
const milliseconds = BigInt(match[1]) * MULTIPLIERS[match[2]];
|
|
12
|
+
if (milliseconds > BigInt(Number.MAX_SAFE_INTEGER))
|
|
13
|
+
return { kind: "overflow" };
|
|
14
|
+
return { kind: "parsed", milliseconds: Number(milliseconds) };
|
|
15
|
+
}
|
|
16
|
+
export function formatDuration(milliseconds) {
|
|
17
|
+
for (const [unit, multiplier] of [["h", 3_600_000], ["m", 60_000], ["s", 1_000]]) {
|
|
18
|
+
if (milliseconds >= multiplier && milliseconds % multiplier === 0)
|
|
19
|
+
return `${milliseconds / multiplier}${unit}`;
|
|
20
|
+
}
|
|
21
|
+
return `${milliseconds}ms`;
|
|
22
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Offer } from "../core/facts/offer.js";
|
|
2
2
|
import type { GitAdmissionSnapshot } from "./observe.js";
|
|
3
|
-
import { type GitRepository } from "./repository.js";
|
|
3
|
+
import { type GitRepository, type GitRefAssertion } from "./repository.js";
|
|
4
4
|
import { type ContractHead } from "../core/facts/types.js";
|
|
5
5
|
type Accepted = Readonly<{
|
|
6
6
|
kind: "accepted";
|
|
@@ -14,5 +14,5 @@ type Unknown = Readonly<{
|
|
|
14
14
|
kind: "unknown";
|
|
15
15
|
}>;
|
|
16
16
|
export type Admission = Accepted | PublicationFailed | Unknown;
|
|
17
|
-
export declare function admit(repository: GitRepository, offer: Offer, admission: GitAdmissionSnapshot): Admission;
|
|
17
|
+
export declare function admit(repository: GitRepository, offer: Offer, admission: GitAdmissionSnapshot, assertions?: readonly GitRefAssertion[]): Admission;
|
|
18
18
|
export {};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { encodeEntry, } from "../core/facts/codec.js";
|
|
2
|
-
import { GIT_FORMAT_BYTES, GIT_FORMAT_PATH, GIT_REF,
|
|
3
|
-
import { contractJournalPath, gitObjectIdForSnapshot, mintContractHead, mintSnapshotId, } from "./identity.js";
|
|
1
|
+
import { decodeJournal, encodeEntry, } from "../core/facts/codec.js";
|
|
2
|
+
import { GIT_FORMAT_BYTES, GIT_FORMAT_PATH, GIT_REF, updateGitTreeFromFrozenDirectories, updateRefsAtomically, writeBlob, writeCommit, } from "./repository.js";
|
|
3
|
+
import { contractJournalPath, contractJournalPaths, gitObjectIdForSnapshot, mintContractHead, mintSnapshotId, } from "./identity.js";
|
|
4
4
|
import { validPath } from "./tree.js";
|
|
5
|
-
const CONTRACT_JOURNAL_PATH = /^contracts\/[0-9a-f]{2}\/[0-9a-f]{2}\/[0-9a-f]{60}\.jsonl$/u;
|
|
5
|
+
const CONTRACT_JOURNAL_PATH = /^contracts\/(?:active|terminal)\/[0-9a-f]{2}\/[0-9a-f]{2}\/[0-9a-f]{60}\.jsonl$/u;
|
|
6
6
|
function assertAppendStructure(appends) {
|
|
7
7
|
const seen = new Set();
|
|
8
8
|
for (const append of appends) {
|
|
@@ -20,7 +20,7 @@ function assertAppendStructure(appends) {
|
|
|
20
20
|
return appends;
|
|
21
21
|
}
|
|
22
22
|
function assertCompanionStructure(companions, appends) {
|
|
23
|
-
const reserved = new Set([GIT_FORMAT_PATH, ...appends.
|
|
23
|
+
const reserved = new Set([GIT_FORMAT_PATH, ...appends.flatMap((append) => contractJournalPaths(append.contractId))]);
|
|
24
24
|
const seen = new Set();
|
|
25
25
|
for (const companion of companions) {
|
|
26
26
|
if (!companion || typeof companion !== "object")
|
|
@@ -39,16 +39,19 @@ function assertCompanionStructure(companions, appends) {
|
|
|
39
39
|
}
|
|
40
40
|
function readCanonicalJournal(admission, id) {
|
|
41
41
|
const snapshot = admission.snapshot;
|
|
42
|
-
const
|
|
42
|
+
const matches = contractJournalPaths(id).filter((path) => snapshot.paths.has(path));
|
|
43
|
+
if (matches.length > 1)
|
|
44
|
+
throw new Error(`duplicate contract journal identity: ${id}`);
|
|
45
|
+
const path = matches[0];
|
|
46
|
+
if (path === undefined)
|
|
47
|
+
return { bytes: Buffer.alloc(0), path: null };
|
|
43
48
|
const entry = snapshot.paths.get(path);
|
|
44
|
-
if (entry === undefined)
|
|
45
|
-
return Buffer.alloc(0);
|
|
46
49
|
if (entry.type !== "blob")
|
|
47
50
|
throw new Error(`journal path is not a blob: ${path}`);
|
|
48
51
|
const journal = admission.frozenJournalBytes.get(entry.oid);
|
|
49
52
|
if (journal === undefined)
|
|
50
53
|
throw new Error(`missing frozen journal bytes: ${path}`);
|
|
51
|
-
return journal;
|
|
54
|
+
return { bytes: journal, path };
|
|
52
55
|
}
|
|
53
56
|
function buildOffer(repository, admission, appends, companions) {
|
|
54
57
|
const snapshot = admission.snapshot;
|
|
@@ -65,17 +68,24 @@ function buildOffer(repository, admission, appends, companions) {
|
|
|
65
68
|
});
|
|
66
69
|
}
|
|
67
70
|
for (const append of appends) {
|
|
68
|
-
|
|
71
|
+
const current = readCanonicalJournal(admission, append.contractId);
|
|
72
|
+
let journal = current.bytes;
|
|
69
73
|
for (const entry of append.entries)
|
|
70
74
|
journal = Buffer.concat([journal, Buffer.from(encodeEntry(entry))]);
|
|
71
75
|
const blob = writeBlob(repository, journal);
|
|
72
|
-
|
|
76
|
+
const entries = decodeJournal(journal.toString("utf8"));
|
|
77
|
+
const terminal = entries.some((entry) => entry.kind === "claimed" || entry.kind === "abandoned");
|
|
78
|
+
const destination = contractJournalPath(append.contractId, terminal ? "terminal" : "active");
|
|
79
|
+
if (current.path !== null && current.path !== destination)
|
|
80
|
+
changes.set(current.path, null);
|
|
81
|
+
changes.set(destination, { oid: blob, mode: "100644", type: "blob" });
|
|
73
82
|
heads[append.contractId] = mintContractHead(blob);
|
|
74
83
|
}
|
|
75
84
|
return { changes, heads };
|
|
76
85
|
}
|
|
77
|
-
function publishOffer(repository,
|
|
78
|
-
const
|
|
86
|
+
function publishOffer(repository, admission, target, changes, assertions) {
|
|
87
|
+
const { snapshot } = admission;
|
|
88
|
+
const gitTree = updateGitTreeFromFrozenDirectories(repository, snapshot.tree, admission.treeDirectories, changes);
|
|
79
89
|
const gitCommit = mintSnapshotId(writeCommit({
|
|
80
90
|
repository,
|
|
81
91
|
tree: gitTree,
|
|
@@ -90,9 +100,9 @@ function publishOffer(repository, snapshot, target, changes) {
|
|
|
90
100
|
newOid: gitObjectIdForSnapshot(target.newOid),
|
|
91
101
|
expectedOid: gitObjectIdForSnapshot(target.expectedOid),
|
|
92
102
|
}]),
|
|
93
|
-
]);
|
|
103
|
+
], assertions);
|
|
94
104
|
}
|
|
95
|
-
export function admit(repository, offer, admission) {
|
|
105
|
+
export function admit(repository, offer, admission, assertions = []) {
|
|
96
106
|
if (!Array.isArray(offer.facts) || offer.facts.length === 0) {
|
|
97
107
|
throw new Error("facts must be a nonempty array");
|
|
98
108
|
}
|
|
@@ -100,7 +110,7 @@ export function admit(repository, offer, admission) {
|
|
|
100
110
|
const companions = assertCompanionStructure(offer.companions ?? [], appends);
|
|
101
111
|
const target = offer.target ?? null;
|
|
102
112
|
const attempt = buildOffer(repository, admission, appends, companions);
|
|
103
|
-
const publication = publishOffer(repository, admission
|
|
113
|
+
const publication = publishOffer(repository, admission, target, attempt.changes, assertions);
|
|
104
114
|
if (publication.kind === "published") {
|
|
105
115
|
return {
|
|
106
116
|
kind: "accepted",
|
package/build/src/git/hooks.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type Settings } from "../settings.js";
|
|
1
2
|
export type HookCommand = Readonly<{
|
|
2
3
|
argv: readonly string[];
|
|
3
4
|
timeoutMs: number;
|
|
@@ -28,7 +29,21 @@ export type WorktreeHookLag = Readonly<{
|
|
|
28
29
|
failure: HookFailure;
|
|
29
30
|
}>;
|
|
30
31
|
type HookPhase = "create" | "destroy";
|
|
32
|
+
export declare function worktreeHooksFrom(input: Readonly<{
|
|
33
|
+
settings: Settings;
|
|
34
|
+
}>): WorktreeHooks;
|
|
31
35
|
export declare function hookMarkerPath(administrationDirectory: string): string;
|
|
36
|
+
export type HookCommandRun = Readonly<{
|
|
37
|
+
kind: "ok";
|
|
38
|
+
}> | Readonly<{
|
|
39
|
+
kind: "cancelled";
|
|
40
|
+
}> | Readonly<{
|
|
41
|
+
kind: "failed";
|
|
42
|
+
command: number;
|
|
43
|
+
failure: HookFailure;
|
|
44
|
+
}>;
|
|
45
|
+
/** Execute an ordered command list without lifecycle state. Scratch owns this policy. */
|
|
46
|
+
export declare function runHookCommands(worktree: string, commands: readonly HookCommand[], signal?: AbortSignal): Promise<HookCommandRun>;
|
|
32
47
|
export declare function runCreateHooks(worktree: string, administrationDirectory: string, hooks: WorktreeHooks, retryHooks: boolean): Promise<WorktreeHookLag | null>;
|
|
33
48
|
export declare function runDestroyHooks(worktree: string, administrationDirectory: string, hooks: WorktreeHooks, retryHooks: boolean): Promise<WorktreeHookLag | null>;
|
|
34
49
|
export {};
|
package/build/src/git/hooks.js
CHANGED
|
@@ -4,10 +4,46 @@ import { fileURLToPath } from "node:url";
|
|
|
4
4
|
import { replaceFileDurably } from "../coordination/durable-file.js";
|
|
5
5
|
import { acquireSqliteTransactionLock } from "../coordination/sqlite-transaction-lock.js";
|
|
6
6
|
import { runProcess, runProcessToExit } from "../runtime/proc/run.js";
|
|
7
|
+
import { SettingsError } from "../settings.js";
|
|
7
8
|
const MARKER_VERSION = 1;
|
|
8
9
|
function object(value) {
|
|
9
10
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
10
11
|
}
|
|
12
|
+
export function worktreeHooksFrom(input) {
|
|
13
|
+
if (!object(input))
|
|
14
|
+
throw new TypeError("worktreeHooksFrom input must be an object");
|
|
15
|
+
const view = input.settings.namespace("worktree");
|
|
16
|
+
if (view.kind === "failed") {
|
|
17
|
+
throw new SettingsError(view.failures.map((failure) => `${failure.scope}: ${failure.diagnostic}`).join("; "));
|
|
18
|
+
}
|
|
19
|
+
for (const entry of view.entries) {
|
|
20
|
+
if (entry.name !== "create" && entry.name !== "destroy")
|
|
21
|
+
throw new SettingsError(`worktree has unknown entry: ${entry.name}`);
|
|
22
|
+
}
|
|
23
|
+
const commands = (phase) => {
|
|
24
|
+
const selected = view.entries.find((entry) => entry.name === phase);
|
|
25
|
+
if (selected === undefined)
|
|
26
|
+
return Object.freeze([]);
|
|
27
|
+
if (!Array.isArray(selected.value))
|
|
28
|
+
throw new SettingsError(`worktree.${phase} must be an array`);
|
|
29
|
+
return Object.freeze(selected.value.map((item, index) => {
|
|
30
|
+
if (!object(item))
|
|
31
|
+
throw new SettingsError(`worktree.${phase}[${index}] must be an object`);
|
|
32
|
+
const coordinate = `worktree.${phase}[${index}]`;
|
|
33
|
+
exactKeys(item, ["argv", "timeoutMs"], coordinate);
|
|
34
|
+
if (!Array.isArray(item.argv) || item.argv.length === 0 || !item.argv.every((value) => typeof value === "string")) {
|
|
35
|
+
throw new SettingsError(`${coordinate}.argv must be a nonempty string array`);
|
|
36
|
+
}
|
|
37
|
+
if (item.argv[0].trim().length === 0)
|
|
38
|
+
throw new SettingsError(`${coordinate}.argv[0] must be nonblank`);
|
|
39
|
+
if (!Number.isSafeInteger(item.timeoutMs) || item.timeoutMs < 1 || item.timeoutMs > 2_147_483_647) {
|
|
40
|
+
throw new SettingsError(`${coordinate}.timeoutMs must be an integer from 1 through 2147483647`);
|
|
41
|
+
}
|
|
42
|
+
return Object.freeze({ argv: Object.freeze([...item.argv]), timeoutMs: item.timeoutMs });
|
|
43
|
+
}));
|
|
44
|
+
};
|
|
45
|
+
return Object.freeze({ create: commands("create"), destroy: commands("destroy") });
|
|
46
|
+
}
|
|
11
47
|
function exactKeys(value, keys, coordinate) {
|
|
12
48
|
const actual = Object.keys(value).sort();
|
|
13
49
|
const expected = [...keys].sort();
|
|
@@ -139,6 +175,23 @@ function failedOutcome(outcome) {
|
|
|
139
175
|
}
|
|
140
176
|
return outcome;
|
|
141
177
|
}
|
|
178
|
+
/** Execute an ordered command list without lifecycle state. Scratch owns this policy. */
|
|
179
|
+
export async function runHookCommands(worktree, commands, signal) {
|
|
180
|
+
for (const [command, value] of commands.entries()) {
|
|
181
|
+
const outcome = await runProcess({
|
|
182
|
+
argv: value.argv,
|
|
183
|
+
timeoutMs: value.timeoutMs,
|
|
184
|
+
cwd: worktree,
|
|
185
|
+
...(signal === undefined ? {} : { signal }),
|
|
186
|
+
});
|
|
187
|
+
if (outcome.kind === "cancelled")
|
|
188
|
+
return outcome;
|
|
189
|
+
const failure = failedOutcome(outcome);
|
|
190
|
+
if (failure !== null)
|
|
191
|
+
return { kind: "failed", command, failure };
|
|
192
|
+
}
|
|
193
|
+
return { kind: "ok" };
|
|
194
|
+
}
|
|
142
195
|
function withProgress(marker, phase, progress) {
|
|
143
196
|
return { ...marker, [phase]: progress };
|
|
144
197
|
}
|
|
@@ -183,11 +236,10 @@ async function executePendingCommand(input) {
|
|
|
183
236
|
const command = marker.commands[input.phase][input.command];
|
|
184
237
|
if (command === undefined)
|
|
185
238
|
throw new Error("worktree hook marker pending index is out of range");
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}));
|
|
239
|
+
const result = await runHookCommands(input.worktree, [command]);
|
|
240
|
+
if (result.kind === "cancelled")
|
|
241
|
+
throw new Error("managed hook runner cancelled without a signal");
|
|
242
|
+
const failure = result.kind === "failed" ? result.failure : null;
|
|
191
243
|
const next = input.command + 1;
|
|
192
244
|
writeMarker(input.administrationDirectory, withProgress(marker, input.phase, failure === null
|
|
193
245
|
? next === marker.commands[input.phase].length ? { status: "ok" } : { status: "pending", next }
|
|
@@ -4,7 +4,11 @@ export type GitObjectId = string & {
|
|
|
4
4
|
readonly [gitObjectIdBrand]: "GitObjectId";
|
|
5
5
|
};
|
|
6
6
|
export declare function contractLocator(contract: ContractId): string;
|
|
7
|
-
export declare function
|
|
7
|
+
export declare function gitRefLocator(ref: string): string;
|
|
8
|
+
export declare function contractPhysicalName(contract: ContractId): string;
|
|
9
|
+
export type ContractJournalClass = "active" | "terminal";
|
|
10
|
+
export declare function contractJournalPath(contract: ContractId, journalClass?: ContractJournalClass): string;
|
|
11
|
+
export declare function contractJournalPaths(contract: ContractId): readonly string[];
|
|
8
12
|
export declare function gitObjectId(value: string, label?: string): GitObjectId;
|
|
9
13
|
export declare function mintSnapshotId(value: string): SnapshotId;
|
|
10
14
|
export declare function mintChangeId(value: string): ChangeId;
|