@astrosheep/keiyaku 4.6.8 → 4.6.9
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 +40 -14
- package/build/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/build/plugins/claude/skills/keiyaku/SKILL.md +5 -6
- package/build/plugins/claude/skills/keiyaku-akuma/SKILL.md +1 -1
- package/build/plugins/claude/skills/keiyaku-akuma/references/automation.md +8 -8
- package/build/plugins/claude/skills/keiyaku-babysit/SKILL.md +1 -1
- package/build/plugins/claude/skills/keiyaku-bind/SKILL.md +9 -9
- package/build/plugins/claude/skills/keiyaku-debt/SKILL.md +91 -0
- package/build/plugins/claude/skills/keiyaku-workflow/SKILL.md +2 -2
- package/build/plugins/codex/.codex-plugin/plugin.json +1 -1
- package/build/plugins/codex/skills/keiyaku/SKILL.md +5 -6
- package/build/plugins/codex/skills/keiyaku-akuma/SKILL.md +1 -1
- package/build/plugins/codex/skills/keiyaku-akuma/references/automation.md +8 -8
- package/build/plugins/codex/skills/keiyaku-babysit/SKILL.md +1 -1
- package/build/plugins/codex/skills/keiyaku-bind/SKILL.md +9 -9
- package/build/plugins/codex/skills/keiyaku-debt/SKILL.md +91 -0
- package/build/plugins/codex/skills/keiyaku-workflow/SKILL.md +2 -2
- package/build/plugins/opencode/index.js +1 -1
- package/build/plugins/opencode/skills/keiyaku/SKILL.md +5 -6
- package/build/plugins/opencode/skills/keiyaku-akuma/SKILL.md +1 -1
- package/build/plugins/opencode/skills/keiyaku-akuma/references/automation.md +8 -8
- package/build/plugins/opencode/skills/keiyaku-babysit/SKILL.md +1 -1
- package/build/plugins/opencode/skills/keiyaku-bind/SKILL.md +9 -9
- package/build/plugins/opencode/skills/keiyaku-debt/SKILL.md +91 -0
- package/build/plugins/opencode/skills/keiyaku-workflow/SKILL.md +2 -2
- package/build/skills/keiyaku/SKILL.md +5 -6
- package/build/skills/keiyaku-akuma/SKILL.md +1 -1
- package/build/skills/keiyaku-akuma/references/automation.md +8 -8
- package/build/skills/keiyaku-babysit/SKILL.md +1 -1
- package/build/skills/keiyaku-bind/SKILL.md +9 -9
- package/build/skills/keiyaku-debt/SKILL.md +91 -0
- package/build/skills/keiyaku-workflow/SKILL.md +2 -2
- package/build/src/akuma/akuma-instance.d.ts +2 -0
- package/build/src/akuma/akuma-instance.js +10 -0
- package/build/src/akuma/akuma-observe.d.ts +33 -8
- package/build/src/akuma/akuma-observe.js +88 -64
- package/build/src/akuma/akuma-owner.d.ts +11 -2
- package/build/src/akuma/akuma-owner.js +45 -35
- package/build/src/akuma/akuma-selection-execution.js +9 -1
- package/build/src/akuma/akuma-stream.d.ts +22 -0
- package/build/src/akuma/akuma-stream.js +116 -0
- package/build/src/akuma/akuma-wait.d.ts +1 -40
- package/build/src/akuma/akuma-wait.js +23 -84
- package/build/src/akuma/akuma.d.ts +17 -11
- package/build/src/akuma/akuma.js +2 -0
- package/build/src/akuma/allowed.d.ts +7 -12
- package/build/src/akuma/allowed.js +13 -4
- package/build/src/akuma/body-supervisor.d.ts +5 -5
- package/build/src/akuma/body-supervisor.js +22 -20
- package/build/src/akuma/body.js +28 -1
- package/build/src/akuma/call-request.d.ts +12 -22
- package/build/src/akuma/heart/activity-schema.d.ts +12 -0
- package/build/src/akuma/heart/activity-schema.js +6 -0
- package/build/src/akuma/heart/index.d.ts +59 -2
- package/build/src/akuma/heart/index.js +87 -8
- package/build/src/akuma/heart/rows.js +5 -3
- package/build/src/akuma/heart/soul.d.ts +9 -0
- package/build/src/akuma/heart/soul.js +29 -0
- package/build/src/akuma/heart/storage.d.ts +15 -0
- package/build/src/akuma/heart/storage.js +55 -0
- package/build/src/akuma/heart/timeline.d.ts +7 -0
- package/build/src/akuma/heart/timeline.js +10 -0
- package/build/src/akuma/index.d.ts +1 -0
- package/build/src/akuma/projection.d.ts +25 -0
- package/build/src/akuma/projection.js +24 -7
- package/build/src/akuma/provider-recipe.d.ts +11 -0
- package/build/src/akuma/provider-recipe.js +23 -0
- package/build/src/akuma/provider.d.ts +25 -0
- package/build/src/akuma/provider.js +99 -7
- package/build/src/akuma/providers/acp/events.js +25 -2
- package/build/src/akuma/providers/claude/events.js +76 -10
- package/build/src/akuma/providers/claude/index.js +2 -5
- package/build/src/akuma/providers/codex-app-server/events.js +17 -2
- package/build/src/akuma/providers/codex-app-server/index.js +15 -9
- package/build/src/akuma/providers/decode.d.ts +8 -0
- package/build/src/akuma/providers/decode.js +21 -0
- package/build/src/akuma/providers/grok-build/index.js +10 -26
- package/build/src/akuma/providers/opencode-sdk/event-stream.d.ts +3 -3
- package/build/src/akuma/providers/opencode-sdk/event-stream.js +2 -25
- package/build/src/akuma/providers/opencode-sdk/events.d.ts +7 -43
- package/build/src/akuma/providers/opencode-sdk/events.js +147 -284
- package/build/src/akuma/providers/opencode-sdk/index.js +267 -421
- package/build/src/akuma/providers/opencode-sdk/native-http.d.ts +9 -0
- package/build/src/akuma/providers/opencode-sdk/native-http.js +69 -0
- package/build/src/akuma/providers/opencode-sdk/session.d.ts +42 -36
- package/build/src/akuma/providers/opencode-sdk/session.js +75 -82
- package/build/src/akuma/providers/paseo/events.d.ts +8 -0
- package/build/src/akuma/providers/paseo/events.js +58 -11
- package/build/src/akuma/providers/paseo/index.js +11 -7
- package/build/src/akuma/providers/pi/events.d.ts +2 -0
- package/build/src/akuma/providers/pi/events.js +32 -24
- package/build/src/akuma/providers/pi/index.js +21 -35
- package/build/src/akuma/request-lifecycle.d.ts +1 -2
- package/build/src/akuma/request-lifecycle.js +2 -8
- package/build/src/akuma/request-rendezvous.js +38 -55
- package/build/src/akuma/request-serve.d.ts +1 -1
- package/build/src/akuma/request-serve.js +77 -113
- package/build/src/akuma/selection-observation.d.ts +57 -51
- package/build/src/akuma/selection-observation.js +3 -3
- package/build/src/akuma/turn-drive.d.ts +3 -0
- package/build/src/akuma/turn-drive.js +26 -5
- package/build/src/akuma-body.js +12 -22
- package/build/src/cli/akuma-caller.js +10 -0
- package/build/src/cli/commands/akuma-grammar.js +3 -4
- package/build/src/cli/commands/akuma.js +13 -51
- package/build/src/cli/commands/contract-grammar.d.ts +7 -6
- package/build/src/cli/commands/contract-grammar.js +27 -32
- package/build/src/cli/commands/contract-help.d.ts +16 -7
- package/build/src/cli/commands/contract-help.js +20 -11
- package/build/src/cli/commands/contract.js +11 -11
- package/build/src/cli/commands/debt-grammar.d.ts +19 -0
- package/build/src/cli/commands/debt-grammar.js +323 -0
- package/build/src/cli/commands/debt-query.d.ts +5 -0
- package/build/src/cli/commands/{task-query.js → debt-query.js} +9 -9
- package/build/src/cli/commands/{task.d.ts → debt.d.ts} +2 -2
- package/build/src/cli/commands/{task.js → debt.js} +83 -143
- package/build/src/cli/coordinates.d.ts +1 -1
- package/build/src/cli/coordinates.js +1 -1
- package/build/src/cli/parse.d.ts +4 -4
- package/build/src/cli/parse.js +17 -16
- package/build/src/cli/render/akuma-activity.d.ts +11 -83
- package/build/src/cli/render/akuma-activity.js +20 -382
- package/build/src/cli/render/catalog.d.ts +2 -2
- package/build/src/cli/render/catalog.js +5 -5
- package/build/src/cli/render/contract.js +2 -2
- package/build/src/cli/render/debt.d.ts +28 -0
- package/build/src/cli/render/{task.js → debt.js} +51 -112
- package/build/src/cli/render/kanshi.js +28 -28
- package/build/src/cli/render/marks.d.ts +5 -5
- package/build/src/cli/render/marks.js +4 -10
- package/build/src/cli/render/nuke.js +1 -1
- package/build/src/cli/runtime.js +3 -3
- package/build/src/cli/scan.d.ts +1 -1
- package/build/src/cli/usage.d.ts +1 -1
- package/build/src/cli/usage.js +4 -4
- package/build/src/contract-worktree.js +3 -3
- package/build/src/debt/board.d.ts +160 -0
- package/build/src/{task → debt}/board.js +89 -89
- package/build/src/debt/catalog.d.ts +11 -0
- package/build/src/debt/catalog.js +59 -0
- package/build/src/{task → debt}/context.d.ts +1 -1
- package/build/src/{task → debt}/context.js +1 -1
- package/build/src/{task → debt}/created-observation.d.ts +6 -10
- package/build/src/{task → debt}/created-observation.js +7 -7
- package/build/src/debt/document.d.ts +37 -0
- package/build/src/{task → debt}/document.js +37 -37
- package/build/src/debt/identity.d.ts +21 -0
- package/build/src/{task → debt}/identity.js +35 -35
- package/build/src/debt/index.d.ts +111 -0
- package/build/src/debt/index.js +203 -0
- package/build/src/{task → debt}/input.d.ts +8 -8
- package/build/src/{task → debt}/input.js +21 -21
- package/build/src/debt/lifecycle-operations.d.ts +6 -0
- package/build/src/{task → debt}/lifecycle-operations.js +27 -31
- package/build/src/debt/mutation-result.d.ts +981 -0
- package/build/src/debt/mutation-result.js +109 -0
- package/build/src/debt/mutation.d.ts +424 -0
- package/build/src/debt/mutation.js +223 -0
- package/build/src/debt/operation-types.d.ts +71 -0
- package/build/src/debt/operations.d.ts +28 -0
- package/build/src/{task → debt}/operations.js +59 -60
- package/build/src/debt/query.d.ts +76 -0
- package/build/src/{task → debt}/query.js +55 -55
- package/build/src/{task → debt}/store.d.ts +13 -13
- package/build/src/{task → debt}/store.js +58 -49
- package/build/src/debt/view.d.ts +4 -0
- package/build/src/debt/view.js +7 -0
- package/build/src/identity/coordinates.d.ts +1 -1
- package/build/src/index.d.ts +13 -13
- package/build/src/index.js +9 -9
- package/build/src/kanshi/index.d.ts +1 -1
- package/build/src/kanshi/read.js +33 -33
- package/build/src/kanshi/report.d.ts +7 -7
- package/build/src/kanshi/select.js +4 -4
- package/build/src/library/akumas.d.ts +2 -3
- package/build/src/library/bind.d.ts +3 -3
- package/build/src/library/bind.js +5 -5
- package/build/src/library/contract-creation.js +5 -5
- package/build/src/library/contract-execution.js +4 -4
- package/build/src/library/contract-types.d.ts +2 -2
- package/build/src/library/input.d.ts +2 -2
- package/build/src/library/input.js +5 -5
- package/build/src/library/invocation.js +2 -2
- package/build/src/library/nuke.js +3 -3
- package/build/src/library/outcome.d.ts +126 -126
- package/build/src/library/outcome.js +3 -3
- package/build/src/library/selection.d.ts +1 -4
- package/build/src/library/selection.js +11 -62
- package/build/src/plugin/public.d.ts +5 -0
- package/build/src/plugin/runtime.js +3 -1
- package/build/src/runtime/proc/windows-launch.exe +0 -0
- package/build/src/settlement/fence.d.ts +3 -3
- package/build/src/settlement/fence.js +4 -4
- package/build/src/settlement/holder.d.ts +16 -16
- package/build/src/settlement/holder.js +69 -63
- package/build/src/settlement/settle.d.ts +5 -5
- package/build/src/settlement/settle.js +53 -53
- package/package.json +12 -6
- package/plugins/paseo/plugin/index.server.ts +17 -0
- package/plugins/paseo/plugin/paseo-plugin.json +4 -0
- package/plugins/paseo/plugin/server/attach.ts +52 -0
- package/plugins/paseo/plugin/server/keiyaku.ts +44 -0
- package/plugins/paseo/plugin/server/message.ts +60 -0
- package/plugins/paseo/plugin/server/mirror.ts +193 -0
- package/plugins/paseo/plugin/server/model.ts +14 -0
- package/plugins/paseo/plugin/server/provider.ts +276 -0
- package/plugins/paseo/plugin/server/sdk-worker.ts +237 -0
- package/plugins/paseo/plugin/server/sdk.ts +289 -0
- package/plugins/paseo/plugin/server/stop.ts +26 -0
- package/plugins/paseo/plugin/server/transport.ts +35 -0
- package/plugins/paseo/plugin/shared/attach-rpc.ts +10 -0
- package/plugins/paseo/plugin/shared/refresh-rpc.ts +6 -0
- package/plugins/paseo/plugin/tsconfig.json +6 -0
- package/skills/keiyaku/SKILL.md +5 -6
- package/skills/keiyaku-akuma/SKILL.md +1 -1
- package/skills/keiyaku-akuma/references/automation.md +8 -8
- package/skills/keiyaku-babysit/SKILL.md +1 -1
- package/skills/keiyaku-bind/SKILL.md +9 -9
- package/skills/keiyaku-debt/SKILL.md +91 -0
- package/skills/keiyaku-workflow/SKILL.md +2 -2
- package/build/plugins/claude/skills/keiyaku-task/SKILL.md +0 -104
- package/build/plugins/codex/skills/keiyaku-task/SKILL.md +0 -104
- package/build/plugins/opencode/skills/keiyaku-task/SKILL.md +0 -104
- package/build/skills/keiyaku-task/SKILL.md +0 -104
- package/build/src/cli/commands/task-grammar.d.ts +0 -19
- package/build/src/cli/commands/task-grammar.js +0 -384
- package/build/src/cli/commands/task-query.d.ts +0 -5
- package/build/src/cli/render/task.d.ts +0 -39
- package/build/src/task/board.d.ts +0 -168
- package/build/src/task/catalog.d.ts +0 -11
- package/build/src/task/catalog.js +0 -59
- package/build/src/task/compose-language.d.ts +0 -57
- package/build/src/task/compose-language.js +0 -364
- package/build/src/task/compose-parser.d.ts +0 -32
- package/build/src/task/compose-parser.js +0 -180
- package/build/src/task/compose.d.ts +0 -16
- package/build/src/task/compose.js +0 -189
- package/build/src/task/document.d.ts +0 -39
- package/build/src/task/identity.d.ts +0 -21
- package/build/src/task/index.d.ts +0 -138
- package/build/src/task/index.js +0 -269
- package/build/src/task/lifecycle-operations.d.ts +0 -6
- package/build/src/task/mutation-result.d.ts +0 -1726
- package/build/src/task/mutation-result.js +0 -198
- package/build/src/task/mutation.d.ts +0 -590
- package/build/src/task/mutation.js +0 -262
- package/build/src/task/operation-types.d.ts +0 -71
- package/build/src/task/operations.d.ts +0 -28
- package/build/src/task/query.d.ts +0 -76
- package/build/src/task/view.d.ts +0 -4
- package/build/src/task/view.js +0 -7
- package/skills/keiyaku-task/SKILL.md +0 -104
- /package/build/src/{task → debt}/operation-types.js +0 -0
|
@@ -22,7 +22,7 @@ import { regionOverlapSchema } from "./region.js";
|
|
|
22
22
|
// One invocation-wide tagged Effect carrier
|
|
23
23
|
// ---------------------------------------------------------------------------
|
|
24
24
|
const reconciliationEffectSchema = z.union([reconcileEffectSchema, contractFileEffectSchema]);
|
|
25
|
-
const resetOwnerSchema = z.enum(["akuma", "git", "
|
|
25
|
+
const resetOwnerSchema = z.enum(["akuma", "git", "debt", "world"]);
|
|
26
26
|
const resetEffectFields = { world: z.string().min(1), owner: resetOwnerSchema, diagnostic: z.string().min(1) };
|
|
27
27
|
const resetEffectSchema = z.union([
|
|
28
28
|
z.object({ kind: z.literal("reset-owner-stopped"), ...resetEffectFields }).strict(),
|
|
@@ -158,7 +158,7 @@ export function project(operation, snapshot, projection) {
|
|
|
158
158
|
if (operation !== "nuke")
|
|
159
159
|
throw new Error("reset projection requires nuke");
|
|
160
160
|
const effects = snapshot.effects.filter((effect) => effect.kind === "reset-owner-stopped" || effect.kind === "reset-residue");
|
|
161
|
-
const value = { removed: snapshot.reset?.removed ?? { refs: 0, worktrees: 0,
|
|
161
|
+
const value = { removed: snapshot.reset?.removed ?? { refs: 0, worktrees: 0, debts: 0 } };
|
|
162
162
|
const base = { operation, world: projection.world, effects, pending: projectPending(operation, value, effects) };
|
|
163
163
|
if (projection.kind === "failed")
|
|
164
164
|
return {
|
|
@@ -404,7 +404,7 @@ const resetCountsSchema = z
|
|
|
404
404
|
.object({
|
|
405
405
|
refs: z.number().int().nonnegative(),
|
|
406
406
|
worktrees: z.number().int().nonnegative(),
|
|
407
|
-
|
|
407
|
+
debts: z.number().int().nonnegative(),
|
|
408
408
|
})
|
|
409
409
|
.strict();
|
|
410
410
|
const resetValueSchema = z.object({ removed: resetCountsSchema }).strict();
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { type AskObserver } from "../akuma/akuma-selection-execution.js";
|
|
2
|
-
import { type WaitObserver } from "../akuma/akuma-wait.js";
|
|
3
2
|
import { type AkumaStatus } from "../akuma/akuma.js";
|
|
4
3
|
import { type ActivityHistory, type OutcomeRow } from "../akuma/projection.js";
|
|
5
4
|
import { type ExecutionContext } from "../akuma/requests.js";
|
|
@@ -28,7 +27,7 @@ type SelectionWaitMode = Readonly<{
|
|
|
28
27
|
export type SelectionSeam = Readonly<{
|
|
29
28
|
addressOne(input: UncheckedSelectionAddress): Promise<AddressedAkuma>;
|
|
30
29
|
addressSet(input: UncheckedSelectionAddress): Promise<AddressedAkumaSet>;
|
|
31
|
-
wait(addressed: AddressedAkumaSet, mode: SelectionWaitMode
|
|
30
|
+
wait(addressed: AddressedAkumaSet, mode: SelectionWaitMode): Promise<AkumaWaitResult>;
|
|
32
31
|
kill(addressed: AddressedAkumaSet, signal?: AbortSignal): Promise<AkumaKillResult>;
|
|
33
32
|
tell(addressed: AddressedAkuma, input: AkumaTellInput, signal?: AbortSignal): Promise<AkumaTellResult>;
|
|
34
33
|
ask<T>(addressed: AddressedAkuma, input: AkumaAskInput<T>, signal?: AbortSignal): Promise<AkumaAskResult<T>>;
|
|
@@ -38,8 +37,6 @@ export type AkumaWaitInput = AkumaSetAddressInput & Readonly<{
|
|
|
38
37
|
completion?: "any" | "all";
|
|
39
38
|
timeoutMs?: number;
|
|
40
39
|
signal?: AbortSignal;
|
|
41
|
-
/** The existing optional live-observation seam, validated here like Ask's. */
|
|
42
|
-
observe?: WaitObserver;
|
|
43
40
|
}>;
|
|
44
41
|
export type AkumaTellInput = AkumaAddressInput & Readonly<{
|
|
45
42
|
body: string;
|
|
@@ -3,16 +3,15 @@ import { withoutReportedChanges } from "../akuma/projection.js";
|
|
|
3
3
|
import { AkumaNotBornError, AkumaObservationError } from "../akuma/akuma-errors.js";
|
|
4
4
|
import { AkumaOwner } from "../akuma/akuma-owner.js";
|
|
5
5
|
import { decodeAskObservation, executeAskAkuma, executeKillAkuma, executeTellAkuma, } from "../akuma/akuma-selection-execution.js";
|
|
6
|
-
import { executeWaitAkuma
|
|
6
|
+
import { executeWaitAkuma } from "../akuma/akuma-wait.js";
|
|
7
7
|
import { parsePublicHistoryId } from "../akuma/identity.js";
|
|
8
8
|
import { executionChannel } from "../akuma/requests.js";
|
|
9
9
|
import { schemaJsonText } from "../akuma/schema.js";
|
|
10
10
|
import { requestForwardedSelectionAsk, requestForwardedSelectionKill, requestForwardedSelectionTell, requestForwardedSelectionWait, } from "../akuma/selection-request.js";
|
|
11
|
-
import { readAliases } from "../alias/index.js";
|
|
12
11
|
import { observeDispatchAssociations } from "../dispatch/index.js";
|
|
13
12
|
import { observeContractAt } from "../git/observe.js";
|
|
14
13
|
import { withGitDecodeChannel } from "../git/read-observation.js";
|
|
15
|
-
import {
|
|
14
|
+
import { observeCreatedDebtObservations } from "../debt/created-observation.js";
|
|
16
15
|
import { outcomePluginObserver, stderrPluginDiagnostic } from "../plugin/akuma-signals.js";
|
|
17
16
|
import { addressAkuma, addressAkumaSet, resolveAkuma, resolveAkumaSet, } from "./address.js";
|
|
18
17
|
import { requireInput } from "./input.js";
|
|
@@ -60,7 +59,7 @@ function localSelection(reportDiagnostic) {
|
|
|
60
59
|
return {
|
|
61
60
|
addressOne: addressAkuma,
|
|
62
61
|
addressSet: addressAkumaSet,
|
|
63
|
-
wait: async (addressed, mode
|
|
62
|
+
wait: async (addressed, mode) => await localWait(addressed, mode, reportDiagnostic),
|
|
64
63
|
kill: async (addressed, abort) => await executeKillAkuma({
|
|
65
64
|
path: addressed.path,
|
|
66
65
|
ids: addressed.orderedIds,
|
|
@@ -136,43 +135,19 @@ function placementDischarged(repo) {
|
|
|
136
135
|
async function composeObservation(status, contract, discharged) {
|
|
137
136
|
return (await discharged(contract)) ? { ...status, timeline: withoutReportedChanges(status.timeline) } : status;
|
|
138
137
|
}
|
|
139
|
-
async function
|
|
140
|
-
return await
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* One wait's identity-fact resolver: the alias bound to an Akuma in its world and
|
|
144
|
-
* its Dispatch association. The alias index is read once, on the first observed
|
|
145
|
-
* Akuma, and each Akuma's alias is the first binding the canonical order offers.
|
|
146
|
-
* Alias bindings and selected Dispatch associations are read once for this live
|
|
147
|
-
* stage. Final result composition owns a separate, fresh Dispatch observation.
|
|
148
|
-
*/
|
|
149
|
-
function waitIdentityFacts(path, repo, selected) {
|
|
150
|
-
let aliases;
|
|
151
|
-
let contracts;
|
|
152
|
-
return async (id) => {
|
|
153
|
-
if (aliases === undefined) {
|
|
154
|
-
const bound = new Map();
|
|
155
|
-
for (const binding of await readAliases(path)) {
|
|
156
|
-
if (!bound.has(binding.akuId))
|
|
157
|
-
bound.set(binding.akuId, binding.alias);
|
|
158
|
-
}
|
|
159
|
-
aliases = bound;
|
|
160
|
-
}
|
|
161
|
-
contracts ??= await dispatchAssociations(repo, selected);
|
|
162
|
-
const alias = aliases.get(id);
|
|
163
|
-
return { ...(alias === undefined ? {} : { alias }), contract: contracts.get(id) };
|
|
164
|
-
};
|
|
138
|
+
async function createdDebtsFor(path, statuses) {
|
|
139
|
+
return await observeCreatedDebtObservations(path, statuses.map((status) => status.id));
|
|
165
140
|
}
|
|
166
141
|
async function observeAkuma(status, path, repo) {
|
|
167
142
|
return (await composeAkumaAssociations([status], path, repo))[0];
|
|
168
143
|
}
|
|
169
144
|
/**
|
|
170
145
|
* The one upper composition of association-free native status evidence into its
|
|
171
|
-
* public observation shape. It reads Alias-independent Dispatch and
|
|
146
|
+
* public observation shape. It reads Alias-independent Dispatch and Debt
|
|
172
147
|
* associations after the lower values, never before or inside them.
|
|
173
148
|
*/
|
|
174
149
|
async function composeAkumaAssociations(statuses, path, repo) {
|
|
175
|
-
const created = await
|
|
150
|
+
const created = await createdDebtsFor(path, statuses);
|
|
176
151
|
const discharged = placementDischarged(repo);
|
|
177
152
|
const contracts = await dispatchAssociations(repo, statuses.map((status) => status.id));
|
|
178
153
|
return await Promise.all(statuses.map(async (status, index) => {
|
|
@@ -180,7 +155,7 @@ async function composeAkumaAssociations(statuses, path, repo) {
|
|
|
180
155
|
return {
|
|
181
156
|
status: await composeObservation(status, contract, discharged),
|
|
182
157
|
contract,
|
|
183
|
-
|
|
158
|
+
createdDebts: created[index],
|
|
184
159
|
};
|
|
185
160
|
}));
|
|
186
161
|
}
|
|
@@ -214,7 +189,7 @@ async function composeWaitResult(path, repo, result) {
|
|
|
214
189
|
}
|
|
215
190
|
/**
|
|
216
191
|
* Re-attach upper associations after a wait result crosses a forwarded
|
|
217
|
-
* boundary; the serving side must observe its own Dispatch and
|
|
192
|
+
* boundary; the serving side must observe its own Dispatch and Debt state.
|
|
218
193
|
*/
|
|
219
194
|
async function attachWaitAssociations(path, repo, result) {
|
|
220
195
|
return await composeWaitResult(path, repo, {
|
|
@@ -273,10 +248,6 @@ async function localWait(addressed, input, reportDiagnostic) {
|
|
|
273
248
|
outcomeObserved: outcomePluginObserver({ world: addressed.path, reportDiagnostic }),
|
|
274
249
|
...(input.timeoutMs === undefined ? {} : { timeoutMs: input.timeoutMs }),
|
|
275
250
|
...(input.signal === undefined ? {} : { signal: input.signal }),
|
|
276
|
-
identity: waitIdentityFacts(addressed.path, input.repo, addressed.orderedIds),
|
|
277
|
-
selectionOrder: addressed.orderedIds,
|
|
278
|
-
...(input.observer?.selected === undefined ? {} : { onSelected: input.observer.selected }),
|
|
279
|
-
...(input.observer?.observe === undefined ? {} : { observe: input.observer.observe }),
|
|
280
251
|
}));
|
|
281
252
|
}
|
|
282
253
|
catch (error) {
|
|
@@ -285,35 +256,13 @@ async function localWait(addressed, input, reportDiagnostic) {
|
|
|
285
256
|
throw observationError(addressed.orderedIds[0], error);
|
|
286
257
|
}
|
|
287
258
|
}
|
|
288
|
-
function waitObserver(value) {
|
|
289
|
-
if (value === undefined)
|
|
290
|
-
return undefined;
|
|
291
|
-
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
292
|
-
throw new TypeError("observe must be a WaitObserver");
|
|
293
|
-
const record = value;
|
|
294
|
-
for (const key of Object.keys(record)) {
|
|
295
|
-
if (key !== "selected" && key !== "observe")
|
|
296
|
-
throw new TypeError(`observe has unknown field: ${key}`);
|
|
297
|
-
if (record[key] !== undefined && typeof record[key] !== "function")
|
|
298
|
-
throw new TypeError(`observe.${key} must be a function`);
|
|
299
|
-
}
|
|
300
|
-
return {
|
|
301
|
-
...(record.selected === undefined
|
|
302
|
-
? {}
|
|
303
|
-
: { selected: record.selected }),
|
|
304
|
-
...(record.observe === undefined
|
|
305
|
-
? {}
|
|
306
|
-
: { observe: record.observe }),
|
|
307
|
-
};
|
|
308
|
-
}
|
|
309
259
|
export async function waitAkumaOn(seam, input) {
|
|
310
260
|
const values = requireInput(input, "Akumas.wait input");
|
|
311
261
|
for (const key of Object.keys(values)) {
|
|
312
|
-
if (!["path", "akuma", "repo", "completion", "timeoutMs", "signal"
|
|
262
|
+
if (!["path", "akuma", "repo", "completion", "timeoutMs", "signal"].includes(key)) {
|
|
313
263
|
throw new TypeError(`Akumas.wait input has unknown field: ${key}`);
|
|
314
264
|
}
|
|
315
265
|
}
|
|
316
|
-
const observer = waitObserver(values.observe);
|
|
317
266
|
const selected = completionMode(values.completion);
|
|
318
267
|
const timeoutMs = timeout(values.timeoutMs);
|
|
319
268
|
const callerSignal = signal(values.signal);
|
|
@@ -324,7 +273,7 @@ export async function waitAkumaOn(seam, input) {
|
|
|
324
273
|
...(timeoutMs === undefined ? {} : { timeoutMs }),
|
|
325
274
|
...(callerSignal === undefined ? {} : { signal: callerSignal }),
|
|
326
275
|
...(repo === undefined ? {} : { repo }),
|
|
327
|
-
}
|
|
276
|
+
});
|
|
328
277
|
}
|
|
329
278
|
export async function killAkumaOn(seam, input) {
|
|
330
279
|
const values = requireInput(input, "Akumas.kill input");
|
|
@@ -54,6 +54,11 @@ export type PluginSignalMap = Readonly<{
|
|
|
54
54
|
historyId: string;
|
|
55
55
|
observer?: string;
|
|
56
56
|
}>;
|
|
57
|
+
"akuma.turn-started": Readonly<{
|
|
58
|
+
akumaId: string;
|
|
59
|
+
turnSequence: number;
|
|
60
|
+
initiator?: string;
|
|
61
|
+
}>;
|
|
57
62
|
"akuma.turn-outcome": Readonly<{
|
|
58
63
|
akumaId: string;
|
|
59
64
|
bodySequence: number;
|
|
@@ -7,6 +7,7 @@ const DIAGNOSTIC_LIMIT = 500;
|
|
|
7
7
|
const PLUGIN_ATTEMPT_TIMEOUT_MS = 5_000;
|
|
8
8
|
const PLUGIN_NAMESPACE = "plugins";
|
|
9
9
|
const SQUARE_PLUGIN_PACKAGE = "@astrosheep/keiyaku-plugin-square";
|
|
10
|
+
const BUNDLED_PLUGIN_PACKAGES = new Set([SQUARE_PLUGIN_PACKAGE, "@astrosheep/keiyaku-plugin-paseo"]);
|
|
10
11
|
const BUILTIN_PLUGINS = Object.freeze([
|
|
11
12
|
Object.freeze({ id: "square", package: SQUARE_PLUGIN_PACKAGE, config: undefined }),
|
|
12
13
|
]);
|
|
@@ -206,6 +207,7 @@ const SIGNAL_KINDS = Object.freeze({
|
|
|
206
207
|
"akuma.initiating": true,
|
|
207
208
|
"akuma.born": true,
|
|
208
209
|
"akuma.outcome-observed": true,
|
|
210
|
+
"akuma.turn-started": true,
|
|
209
211
|
"akuma.turn-outcome": true,
|
|
210
212
|
"akuma.body-ended": true,
|
|
211
213
|
});
|
|
@@ -245,7 +247,7 @@ function sourceUrl(world, packageName) {
|
|
|
245
247
|
return null;
|
|
246
248
|
}
|
|
247
249
|
async function importFromWorld(world, packageName) {
|
|
248
|
-
if (packageName
|
|
250
|
+
if (BUNDLED_PLUGIN_PACKAGES.has(packageName))
|
|
249
251
|
return (await import(packageName));
|
|
250
252
|
const direct = sourceUrl(world, packageName);
|
|
251
253
|
if (direct !== null)
|
|
Binary file
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type HeldSqliteTransactionLock } from "../coordination/sqlite-transaction-lock.js";
|
|
2
2
|
import type { GitRepository } from "../git/process.js";
|
|
3
|
-
import type {
|
|
4
|
-
export declare function settlementFencePath(repository: GitRepository,
|
|
5
|
-
export declare function
|
|
3
|
+
import type { DebtId } from "../debt/identity.js";
|
|
4
|
+
export declare function settlementFencePath(repository: GitRepository, debtId: DebtId): string;
|
|
5
|
+
export declare function acquireDebtSettlementFence(repository: GitRepository, debtId: DebtId): Promise<HeldSqliteTransactionLock>;
|
|
@@ -2,13 +2,13 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { acquireSqliteTransactionLock, } from "../coordination/sqlite-transaction-lock.js";
|
|
4
4
|
import { commonGitDirectory } from "../git/repository.js";
|
|
5
|
-
export function settlementFencePath(repository,
|
|
6
|
-
const locator = createHash("sha256").update(
|
|
5
|
+
export function settlementFencePath(repository, debtId) {
|
|
6
|
+
const locator = createHash("sha256").update(debtId).digest("hex");
|
|
7
7
|
return resolve(commonGitDirectory(repository), "keiyaku", "locks", "settlement", `${locator}.sqlite`);
|
|
8
8
|
}
|
|
9
|
-
export function
|
|
9
|
+
export function acquireDebtSettlementFence(repository, debtId) {
|
|
10
10
|
return acquireSqliteTransactionLock({
|
|
11
|
-
path: settlementFencePath(repository,
|
|
11
|
+
path: settlementFencePath(repository, debtId),
|
|
12
12
|
mode: "immediate",
|
|
13
13
|
});
|
|
14
14
|
}
|
|
@@ -4,31 +4,31 @@ import type { GitDecisionObservation } from "../git/observe.js";
|
|
|
4
4
|
import { type PrivateStatePublicationSeat } from "../git/private-state-seat.js";
|
|
5
5
|
import type { GitRepository } from "../git/process.js";
|
|
6
6
|
import { type GitDecodeChannel, type GitReadObservation, type GitTreeSelection } from "../git/read-observation.js";
|
|
7
|
-
import { type
|
|
8
|
-
export declare function
|
|
9
|
-
export type
|
|
7
|
+
import { type DebtId } from "../debt/identity.js";
|
|
8
|
+
export declare function debtHolderObservationSelection(): GitTreeSelection;
|
|
9
|
+
export type DebtHolder = Readonly<{
|
|
10
10
|
version: 1;
|
|
11
|
-
|
|
11
|
+
debtId: DebtId;
|
|
12
12
|
contractId: ContractId;
|
|
13
13
|
disposition: "held" | "released";
|
|
14
14
|
}>;
|
|
15
|
-
export type
|
|
16
|
-
export declare function
|
|
15
|
+
export type DebtHolderProjection = ReadonlyMap<ContractId, DebtHolder>;
|
|
16
|
+
export declare function claimDebtHolder(debtId: DebtId, owner: ContractId): TreeUpdate;
|
|
17
17
|
type AdmissionOutcome = Readonly<{
|
|
18
18
|
kind: string;
|
|
19
19
|
}>;
|
|
20
|
-
type
|
|
20
|
+
type DebtHolderAdmission<T extends AdmissionOutcome> = Readonly<{
|
|
21
21
|
kind: "completed";
|
|
22
22
|
result: T;
|
|
23
23
|
}> | Readonly<{
|
|
24
24
|
kind: "accepted-release-failed";
|
|
25
25
|
result: T;
|
|
26
|
-
|
|
26
|
+
debtId: DebtId;
|
|
27
27
|
diagnostic: string;
|
|
28
28
|
}>;
|
|
29
|
-
export declare function
|
|
30
|
-
export declare function
|
|
31
|
-
type
|
|
29
|
+
export declare function admitWithDebtHolderFence<T extends AdmissionOutcome>(repository: GitRepository, debtId: DebtId, action: () => T | Promise<T>): Promise<DebtHolderAdmission<T>>;
|
|
30
|
+
export declare function releaseDebtHolder(channel: GitDecodeChannel, observation: GitDecisionObservation, owner: ContractId): Promise<TreeUpdate | null>;
|
|
31
|
+
type DebtHolderReleasePublication = Readonly<{
|
|
32
32
|
kind: "released";
|
|
33
33
|
}> | Readonly<{
|
|
34
34
|
kind: "not-held";
|
|
@@ -37,9 +37,9 @@ type TaskHolderReleasePublication = Readonly<{
|
|
|
37
37
|
diagnostic: string;
|
|
38
38
|
}>;
|
|
39
39
|
/** Publish the released holder against the same frozen observation that decided the release. */
|
|
40
|
-
export declare function
|
|
41
|
-
export declare function
|
|
42
|
-
export declare function
|
|
43
|
-
export declare function
|
|
44
|
-
export declare function
|
|
40
|
+
export declare function publishDebtHolderRelease(repository: GitRepository, channel: GitDecodeChannel, observation: GitDecisionObservation, owner: ContractId, seat: PrivateStatePublicationSeat): Promise<DebtHolderReleasePublication>;
|
|
41
|
+
export declare function readDebtHolderProjectionFromDecision(channel: GitDecodeChannel, observation: GitDecisionObservation): Promise<DebtHolderProjection>;
|
|
42
|
+
export declare function releaseDebtHolderWithFence<T extends AdmissionOutcome>(repository: GitRepository, channel: GitDecodeChannel, owner: ContractId, action: () => T | Promise<T>): Promise<DebtHolderAdmission<T>>;
|
|
43
|
+
export declare function readDebtHoldersAt(observation: Pick<GitReadObservation, "snapshot" | "readBlobs">): Promise<readonly DebtHolder[]>;
|
|
44
|
+
export declare function readDebtHolderProjectionAt(observation: GitReadObservation): Promise<DebtHolderProjection>;
|
|
45
45
|
export {};
|
|
@@ -4,20 +4,26 @@ import { contractId } from "../core/facts/types.js";
|
|
|
4
4
|
import { GIT_REF, updateGitTree, updateRefsAtomically, writeBlob, writeStateCommit } from "../git/repository.js";
|
|
5
5
|
import { confirmPrivateStatePublication } from "../git/private-state-seat.js";
|
|
6
6
|
import { withGitTargetedReadObservation, } from "../git/read-observation.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import { parseDebtId } from "../debt/identity.js";
|
|
8
|
+
import { acquireDebtSettlementFence } from "./fence.js";
|
|
9
|
+
// The private persisted spelling predates the product rename. Keep its bytes
|
|
10
|
+
// and path stable so an existing held association still settles after upgrade.
|
|
9
11
|
const HOLDER_ROOT = "settlement/task-holders";
|
|
10
12
|
const HOLDER_PREFIX = `${HOLDER_ROOT}/`;
|
|
11
13
|
const HOLDER_SUFFIX = ".json";
|
|
12
|
-
export function
|
|
14
|
+
export function debtHolderObservationSelection() {
|
|
13
15
|
return { subtrees: [HOLDER_ROOT] };
|
|
14
16
|
}
|
|
15
|
-
function
|
|
16
|
-
|
|
17
|
+
function storedIdentity(debtId) {
|
|
18
|
+
return `task/${debtId.slice("debt/".length)}`;
|
|
19
|
+
}
|
|
20
|
+
function holderPath(debtId) {
|
|
21
|
+
const digest = createHash("sha256").update(storedIdentity(debtId)).digest("hex");
|
|
17
22
|
return `${HOLDER_PREFIX}${digest}${HOLDER_SUFFIX}`;
|
|
18
23
|
}
|
|
19
24
|
function canonicalBytes(holder) {
|
|
20
|
-
|
|
25
|
+
const { version, debtId, contractId, disposition } = holder;
|
|
26
|
+
return Buffer.from(`${JSON.stringify({ version, taskId: storedIdentity(debtId), contractId, disposition })}\n`);
|
|
21
27
|
}
|
|
22
28
|
function corruption(message, cause) {
|
|
23
29
|
throw new AuthorityCorruptionError(message, cause === undefined ? {} : { cause });
|
|
@@ -27,67 +33,67 @@ function decodeHolder(path, bytes) {
|
|
|
27
33
|
try {
|
|
28
34
|
const text = Buffer.from(bytes).toString("utf8");
|
|
29
35
|
if (!text.endsWith("\n") || text.slice(0, -1).includes("\n"))
|
|
30
|
-
corruption(`
|
|
36
|
+
corruption(`DebtHolder is not one canonical JSON line: ${path}`);
|
|
31
37
|
value = JSON.parse(text);
|
|
32
38
|
}
|
|
33
39
|
catch (error) {
|
|
34
40
|
if (error instanceof AuthorityCorruptionError)
|
|
35
41
|
throw error;
|
|
36
|
-
return corruption(`invalid
|
|
42
|
+
return corruption(`invalid DebtHolder JSON: ${path}`, error);
|
|
37
43
|
}
|
|
38
44
|
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
39
|
-
corruption(`
|
|
45
|
+
corruption(`DebtHolder must be an object: ${path}`);
|
|
40
46
|
const record = value;
|
|
41
47
|
const keys = Object.keys(record);
|
|
42
48
|
if (keys.length !== 4 || keys.some((key) => !["version", "taskId", "contractId", "disposition"].includes(key))) {
|
|
43
|
-
corruption(`
|
|
49
|
+
corruption(`DebtHolder has invalid fields: ${path}`);
|
|
44
50
|
}
|
|
45
51
|
if (record.version !== 1)
|
|
46
|
-
corruption(`
|
|
47
|
-
if (typeof record.taskId !== "string")
|
|
48
|
-
corruption(`
|
|
52
|
+
corruption(`DebtHolder version must be 1: ${path}`);
|
|
53
|
+
if (typeof record.taskId !== "string" || !record.taskId.startsWith("task/"))
|
|
54
|
+
corruption(`DebtHolder identity is invalid: ${path}`);
|
|
49
55
|
if (typeof record.contractId !== "string")
|
|
50
|
-
corruption(`
|
|
51
|
-
let
|
|
56
|
+
corruption(`DebtHolder contractId is invalid: ${path}`);
|
|
57
|
+
let debtId;
|
|
52
58
|
let owner;
|
|
53
59
|
try {
|
|
54
|
-
|
|
55
|
-
|
|
60
|
+
debtId = `debt/${record.taskId.slice("task/".length)}`;
|
|
61
|
+
parseDebtId(debtId);
|
|
56
62
|
owner = contractId(record.contractId);
|
|
57
63
|
}
|
|
58
64
|
catch (error) {
|
|
59
|
-
return corruption(`
|
|
65
|
+
return corruption(`DebtHolder identity is invalid: ${path}`, error);
|
|
60
66
|
}
|
|
61
67
|
if (record.disposition !== "held" && record.disposition !== "released") {
|
|
62
|
-
corruption(`
|
|
68
|
+
corruption(`DebtHolder disposition is invalid: ${path}`);
|
|
63
69
|
}
|
|
64
|
-
const holder = { version: 1,
|
|
65
|
-
if (holderPath(
|
|
66
|
-
corruption(`
|
|
70
|
+
const holder = { version: 1, debtId, contractId: owner, disposition: record.disposition };
|
|
71
|
+
if (holderPath(debtId) !== path)
|
|
72
|
+
corruption(`DebtHolder path does not match debtId: ${path}`);
|
|
67
73
|
if (!Buffer.from(canonicalBytes(holder)).equals(Buffer.from(bytes)))
|
|
68
|
-
corruption(`
|
|
74
|
+
corruption(`DebtHolder bytes are not canonical: ${path}`);
|
|
69
75
|
return holder;
|
|
70
76
|
}
|
|
71
|
-
function
|
|
77
|
+
function projectDebtHolders(holders) {
|
|
72
78
|
const projection = new Map();
|
|
73
79
|
for (const holder of holders) {
|
|
74
80
|
if (projection.has(holder.contractId)) {
|
|
75
|
-
corruption(`Contract has multiple current
|
|
81
|
+
corruption(`Contract has multiple current DebtHolders: ${holder.contractId}`);
|
|
76
82
|
}
|
|
77
83
|
projection.set(holder.contractId, holder);
|
|
78
84
|
}
|
|
79
85
|
return projection;
|
|
80
86
|
}
|
|
81
87
|
function update(holder) {
|
|
82
|
-
return { path: holderPath(holder.
|
|
88
|
+
return { path: holderPath(holder.debtId), bytes: canonicalBytes(holder) };
|
|
83
89
|
}
|
|
84
|
-
export function
|
|
85
|
-
return update({ version: 1,
|
|
90
|
+
export function claimDebtHolder(debtId, owner) {
|
|
91
|
+
return update({ version: 1, debtId, contractId: owner, disposition: "held" });
|
|
86
92
|
}
|
|
87
93
|
function detail(error) {
|
|
88
94
|
return error instanceof Error ? error.message : String(error);
|
|
89
95
|
}
|
|
90
|
-
function
|
|
96
|
+
function finishDebtHolderAdmission(debtId, result, close) {
|
|
91
97
|
try {
|
|
92
98
|
close();
|
|
93
99
|
return { kind: "completed", result };
|
|
@@ -95,11 +101,11 @@ function finishTaskHolderAdmission(taskId, result, close) {
|
|
|
95
101
|
catch (error) {
|
|
96
102
|
if (result.kind !== "accepted")
|
|
97
103
|
throw error;
|
|
98
|
-
return { kind: "accepted-release-failed", result,
|
|
104
|
+
return { kind: "accepted-release-failed", result, debtId, diagnostic: detail(error) };
|
|
99
105
|
}
|
|
100
106
|
}
|
|
101
|
-
export async function
|
|
102
|
-
const held = await
|
|
107
|
+
export async function admitWithDebtHolderFence(repository, debtId, action) {
|
|
108
|
+
const held = await acquireDebtSettlementFence(repository, debtId);
|
|
103
109
|
let result;
|
|
104
110
|
try {
|
|
105
111
|
result = await action();
|
|
@@ -113,19 +119,19 @@ export async function admitWithTaskHolderFence(repository, taskId, action) {
|
|
|
113
119
|
}
|
|
114
120
|
throw error;
|
|
115
121
|
}
|
|
116
|
-
return
|
|
122
|
+
return finishDebtHolderAdmission(debtId, result, () => held.close());
|
|
117
123
|
}
|
|
118
|
-
export async function
|
|
119
|
-
const current = (await
|
|
124
|
+
export async function releaseDebtHolder(channel, observation, owner) {
|
|
125
|
+
const current = (await readDebtHolderProjectionFromDecision(channel, observation)).get(owner) ?? null;
|
|
120
126
|
return current === null || current.disposition !== "held" ? null : update({ ...current, disposition: "released" });
|
|
121
127
|
}
|
|
122
128
|
async function releasedHolderIsCurrent(repository, channel, owner, expected) {
|
|
123
|
-
const current = (await
|
|
129
|
+
const current = (await observeDebtHolderProjection(repository, channel)).get(owner) ?? null;
|
|
124
130
|
return current?.disposition === "released" && Buffer.from(canonicalBytes(current)).equals(expected);
|
|
125
131
|
}
|
|
126
132
|
/** Publish the released holder against the same frozen observation that decided the release. */
|
|
127
|
-
export async function
|
|
128
|
-
const release = await
|
|
133
|
+
export async function publishDebtHolderRelease(repository, channel, observation, owner, seat) {
|
|
134
|
+
const release = await releaseDebtHolder(channel, observation, owner);
|
|
129
135
|
if (release === null)
|
|
130
136
|
return { kind: "not-held" };
|
|
131
137
|
const tree = await updateGitTree(repository, observation.admission.snapshot.tree, new Map([[release.path, { oid: await writeBlob(repository, release.bytes) }]]));
|
|
@@ -133,7 +139,7 @@ export async function publishTaskHolderRelease(repository, channel, observation,
|
|
|
133
139
|
repository,
|
|
134
140
|
tree,
|
|
135
141
|
parent: observation.admission.snapshot.commit,
|
|
136
|
-
message: `release held
|
|
142
|
+
message: `release held Debt completion: ${owner}`,
|
|
137
143
|
});
|
|
138
144
|
const publication = await updateRefsAtomically(repository, [
|
|
139
145
|
{
|
|
@@ -154,10 +160,10 @@ export async function publishTaskHolderRelease(repository, channel, observation,
|
|
|
154
160
|
kind: "non-published",
|
|
155
161
|
diagnostic: publication.kind === "non-published"
|
|
156
162
|
? detail(publication.error)
|
|
157
|
-
: "
|
|
163
|
+
: "Debt holder release publication state is unknown",
|
|
158
164
|
};
|
|
159
165
|
}
|
|
160
|
-
export async function
|
|
166
|
+
export async function readDebtHolderProjectionFromDecision(channel, observation) {
|
|
161
167
|
const read = {
|
|
162
168
|
snapshot: observation.admission.snapshot,
|
|
163
169
|
readBlobs: async (oids) => {
|
|
@@ -168,29 +174,29 @@ export async function readTaskHolderProjectionFromDecision(channel, observation)
|
|
|
168
174
|
blobs.set(oid, object);
|
|
169
175
|
else {
|
|
170
176
|
if (object.type !== "blob")
|
|
171
|
-
corruption(`
|
|
177
|
+
corruption(`DebtHolder Git object is not a blob: ${oid}`);
|
|
172
178
|
blobs.set(oid, { kind: "present", bytes: object.bytes });
|
|
173
179
|
}
|
|
174
180
|
}
|
|
175
181
|
return blobs;
|
|
176
182
|
},
|
|
177
183
|
};
|
|
178
|
-
return
|
|
184
|
+
return projectDebtHolders(await readDebtHoldersAt(read));
|
|
179
185
|
}
|
|
180
|
-
function
|
|
181
|
-
return withGitTargetedReadObservation(repository, channel,
|
|
186
|
+
function observeDebtHolderProjection(repository, channel) {
|
|
187
|
+
return withGitTargetedReadObservation(repository, channel, debtHolderObservationSelection(), readDebtHolderProjectionAt);
|
|
182
188
|
}
|
|
183
|
-
async function
|
|
184
|
-
const holder = (await
|
|
185
|
-
return holder?.disposition === "held" ? holder.
|
|
189
|
+
async function heldDebtForContract(repository, channel, owner) {
|
|
190
|
+
const holder = (await observeDebtHolderProjection(repository, channel)).get(owner) ?? null;
|
|
191
|
+
return holder?.disposition === "held" ? holder.debtId : null;
|
|
186
192
|
}
|
|
187
|
-
export async function
|
|
188
|
-
const
|
|
189
|
-
return
|
|
193
|
+
export async function releaseDebtHolderWithFence(repository, channel, owner, action) {
|
|
194
|
+
const debtId = await heldDebtForContract(repository, channel, owner);
|
|
195
|
+
return debtId === null
|
|
190
196
|
? { kind: "completed", result: await action() }
|
|
191
|
-
:
|
|
197
|
+
: admitWithDebtHolderFence(repository, debtId, action);
|
|
192
198
|
}
|
|
193
|
-
export async function
|
|
199
|
+
export async function readDebtHoldersAt(observation) {
|
|
194
200
|
const entries = [...observation.snapshot.paths].filter(([path, entry]) => path.startsWith(HOLDER_PREFIX) && entry.type === "blob");
|
|
195
201
|
const blobs = await observation.readBlobs(entries.map(([, entry]) => entry.oid));
|
|
196
202
|
const holders = [];
|
|
@@ -198,26 +204,26 @@ export async function readTaskHoldersAt(observation) {
|
|
|
198
204
|
for (const [path, entry] of observation.snapshot.paths) {
|
|
199
205
|
if (path === HOLDER_ROOT) {
|
|
200
206
|
if (entry.type !== "tree")
|
|
201
|
-
corruption(`
|
|
207
|
+
corruption(`DebtHolder authority root is not a tree: ${path}`);
|
|
202
208
|
continue;
|
|
203
209
|
}
|
|
204
210
|
if (!path.startsWith(HOLDER_PREFIX))
|
|
205
211
|
continue;
|
|
206
212
|
if (!path.endsWith(HOLDER_SUFFIX))
|
|
207
|
-
corruption(`unexpected
|
|
213
|
+
corruption(`unexpected DebtHolder authority path: ${path}`);
|
|
208
214
|
if (entry.type !== "blob")
|
|
209
|
-
corruption(`
|
|
215
|
+
corruption(`DebtHolder path is not a blob: ${path}`);
|
|
210
216
|
const result = blobs.get(entry.oid);
|
|
211
217
|
if (result?.kind !== "present")
|
|
212
|
-
corruption(`missing
|
|
218
|
+
corruption(`missing DebtHolder Git object: ${path}`);
|
|
213
219
|
const holder = decodeHolder(path, result.bytes);
|
|
214
|
-
if (seen.has(holder.
|
|
215
|
-
corruption(`duplicate
|
|
216
|
-
seen.add(holder.
|
|
220
|
+
if (seen.has(holder.debtId))
|
|
221
|
+
corruption(`duplicate DebtHolder identity: ${holder.debtId}`);
|
|
222
|
+
seen.add(holder.debtId);
|
|
217
223
|
holders.push(holder);
|
|
218
224
|
}
|
|
219
|
-
return holders.sort((left, right) => Buffer.compare(Buffer.from(left.
|
|
225
|
+
return holders.sort((left, right) => Buffer.compare(Buffer.from(left.debtId), Buffer.from(right.debtId)));
|
|
220
226
|
}
|
|
221
|
-
export async function
|
|
222
|
-
return
|
|
227
|
+
export async function readDebtHolderProjectionAt(observation) {
|
|
228
|
+
return projectDebtHolders(await readDebtHoldersAt(observation));
|
|
223
229
|
}
|
|
@@ -5,19 +5,19 @@ import type { GitDecodeChannel } from "../git/read-observation.js";
|
|
|
5
5
|
import type { Effect } from "../git/reconcile.js";
|
|
6
6
|
import { type GitRepository } from "../git/process.js";
|
|
7
7
|
export declare const settlementActionSchema: z.ZodObject<{
|
|
8
|
-
kind: z.ZodLiteral<"
|
|
9
|
-
|
|
8
|
+
kind: z.ZodLiteral<"debt">;
|
|
9
|
+
debtId: z.ZodPipe<z.ZodString, z.ZodTransform<`debt/${string}`, string>>;
|
|
10
10
|
action: z.ZodLiteral<"done">;
|
|
11
11
|
}, z.core.$strict>;
|
|
12
12
|
export type SettlementAction = z.infer<typeof settlementActionSchema>;
|
|
13
13
|
export declare const settlementLagSchema: z.ZodObject<{
|
|
14
14
|
kind: z.ZodLiteral<"settlement-failed">;
|
|
15
15
|
surface: z.ZodEnum<{
|
|
16
|
-
|
|
17
|
-
"
|
|
16
|
+
debt: "debt";
|
|
17
|
+
"debt-holder": "debt-holder";
|
|
18
18
|
}>;
|
|
19
19
|
contractId: z.ZodPipe<z.ZodString, z.ZodTransform<ContractId, string>>;
|
|
20
|
-
|
|
20
|
+
debtId: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<`debt/${string}`, string>>>;
|
|
21
21
|
path: z.ZodOptional<z.ZodString>;
|
|
22
22
|
diagnostic: z.ZodString;
|
|
23
23
|
}, z.core.$strict>;
|