@astrosheep/keiyaku 4.0.0 → 4.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +133 -8
- package/build/integrations/marketplace/plugins/keiyaku/.claude-plugin/plugin.json +2 -2
- package/build/integrations/marketplace/plugins/keiyaku/.codex-plugin/plugin.json +4 -4
- package/build/integrations/marketplace/plugins/keiyaku/opencode.js +2 -2
- package/build/integrations/marketplace/plugins/keiyaku/package.json +2 -2
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +13 -5
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +91 -32
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +134 -0
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/agents/openai.yaml +4 -0
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-task/SKILL.md +41 -6
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +262 -46
- package/build/src/akuma/abort.d.ts +2 -0
- package/build/src/akuma/abort.js +50 -0
- package/build/src/akuma/akuma.d.ts +50 -33
- package/build/src/akuma/akuma.js +175 -127
- package/build/src/akuma/archetype.d.ts +14 -5
- package/build/src/akuma/archetype.js +85 -39
- package/build/src/akuma/body.d.ts +3 -5
- package/build/src/akuma/body.js +490 -183
- package/build/src/akuma/coordinate.d.ts +9 -0
- package/build/src/akuma/coordinate.js +22 -0
- package/build/src/akuma/heart/facts.d.ts +88 -45
- package/build/src/akuma/heart/facts.js +19 -8
- package/build/src/akuma/heart/index.d.ts +64 -55
- package/build/src/akuma/heart/index.js +172 -252
- package/build/src/akuma/heart/rows.d.ts +38 -83
- package/build/src/akuma/heart/rows.js +117 -155
- package/build/src/akuma/heart/schema.d.ts +1 -1
- package/build/src/akuma/heart/schema.js +61 -22
- package/build/src/akuma/heart/soul.d.ts +8 -0
- package/build/src/akuma/heart/soul.js +136 -0
- package/build/src/akuma/heart/storage.d.ts +47 -0
- package/build/src/akuma/heart/storage.js +224 -0
- package/build/src/akuma/heart/tells.d.ts +12 -0
- package/build/src/akuma/heart/tells.js +79 -0
- package/build/src/akuma/heart/timeline.d.ts +15 -0
- package/build/src/akuma/heart/timeline.js +86 -0
- package/build/src/akuma/identity.d.ts +2 -2
- package/build/src/akuma/identity.js +9 -9
- package/build/src/akuma/index.d.ts +10 -3
- package/build/src/akuma/index.js +7 -1
- package/build/src/akuma/projection.d.ts +176 -0
- package/build/src/akuma/projection.js +256 -0
- package/build/src/akuma/provider-recipe.d.ts +24 -0
- package/build/src/akuma/provider-recipe.js +100 -0
- package/build/src/akuma/provider.d.ts +67 -13
- package/build/src/akuma/provider.js +172 -105
- package/build/src/akuma/providers/acp/core.d.ts +19 -0
- package/build/src/akuma/providers/acp/core.js +159 -0
- package/build/src/akuma/providers/acp/events.d.ts +20 -0
- package/build/src/akuma/providers/acp/events.js +73 -0
- package/build/src/akuma/providers/acp/index.d.ts +12 -0
- package/build/src/akuma/providers/acp/index.js +94 -0
- package/build/src/akuma/providers/{claude.d.ts → claude/events.d.ts} +9 -41
- package/build/src/akuma/providers/claude/events.js +238 -0
- package/build/src/akuma/providers/claude/index.d.ts +47 -0
- package/build/src/akuma/providers/claude/index.js +279 -0
- package/build/src/akuma/providers/claude/input.d.ts +13 -0
- package/build/src/akuma/providers/claude/input.js +109 -0
- package/build/src/akuma/providers/codex-app-server/events.d.ts +1 -1
- package/build/src/akuma/providers/codex-app-server/events.js +15 -12
- package/build/src/akuma/providers/codex-app-server/index.d.ts +15 -13
- package/build/src/akuma/providers/codex-app-server/index.js +77 -33
- package/build/src/akuma/providers/grok-build/index.d.ts +4 -0
- package/build/src/akuma/providers/grok-build/index.js +70 -0
- package/build/src/akuma/providers/index.d.ts +5 -3
- package/build/src/akuma/providers/index.js +35 -56
- package/build/src/akuma/providers/opencode-sdk/events.d.ts +54 -0
- package/build/src/akuma/providers/opencode-sdk/events.js +235 -0
- package/build/src/akuma/providers/opencode-sdk/index.d.ts +33 -0
- package/build/src/akuma/providers/opencode-sdk/index.js +308 -0
- package/build/src/akuma/providers/opencode-sdk/session.d.ts +26 -0
- package/build/src/akuma/providers/opencode-sdk/session.js +63 -0
- package/build/src/akuma/providers/pi/events.d.ts +33 -0
- package/build/src/akuma/providers/pi/events.js +166 -0
- package/build/src/akuma/providers/pi/index.d.ts +11 -0
- package/build/src/akuma/providers/pi/index.js +191 -0
- package/build/src/akuma/publication.d.ts +3 -3
- package/build/src/akuma/publication.js +28 -40
- package/build/src/akuma/requests.d.ts +9 -6
- package/build/src/akuma/requests.js +95 -65
- package/build/src/alias/index.d.ts +4 -3
- package/build/src/alias/index.js +12 -13
- package/build/src/body/decode.js +7 -1
- package/build/src/body/region.d.ts +2 -0
- package/build/src/body/region.js +77 -7
- package/build/src/body/render.js +2 -1
- package/build/src/body/shape.js +7 -1
- package/build/src/body/verification.js +19 -4
- package/build/src/cli/accepted.d.ts +18 -12
- package/build/src/cli/accepted.js +73 -34
- package/build/src/cli/actor.js +3 -3
- package/build/src/cli/commands/akuma-invoke.d.ts +44 -19
- package/build/src/cli/commands/akuma-invoke.js +101 -52
- package/build/src/cli/commands/akuma.d.ts +27 -9
- package/build/src/cli/commands/akuma.js +151 -80
- package/build/src/cli/commands/contract.d.ts +155 -0
- package/build/src/cli/commands/contract.js +20 -0
- package/build/src/cli/commands/install.js +2 -0
- package/build/src/cli/commands/task-invoke.d.ts +19 -4
- package/build/src/cli/commands/task-invoke.js +65 -17
- package/build/src/cli/commands/task-query.d.ts +5 -0
- package/build/src/cli/commands/task-query.js +247 -0
- package/build/src/cli/commands/task.d.ts +3 -1
- package/build/src/cli/commands/task.js +52 -14
- package/build/src/cli/coordinates.d.ts +18 -0
- package/build/src/cli/coordinates.js +120 -0
- package/build/src/cli/draft.d.ts +8 -0
- package/build/src/cli/draft.js +96 -0
- package/build/src/cli/invoke.d.ts +9 -3
- package/build/src/cli/invoke.js +287 -108
- package/build/src/cli/main.js +54 -7
- package/build/src/cli/parse.d.ts +21 -117
- package/build/src/cli/parse.js +79 -84
- package/build/src/cli/render/akuma-tool-command.d.ts +6 -0
- package/build/src/cli/render/akuma-tool-command.js +144 -0
- package/build/src/cli/render/akuma-tool.d.ts +4 -2
- package/build/src/cli/render/akuma-tool.js +35 -5
- package/build/src/cli/render/akuma.d.ts +3 -1
- package/build/src/cli/render/akuma.js +247 -212
- package/build/src/cli/render/audit.d.ts +3 -0
- package/build/src/cli/render/audit.js +104 -0
- package/build/src/cli/render/catalog.d.ts +2 -0
- package/build/src/cli/render/catalog.js +16 -0
- package/build/src/cli/render/contract.d.ts +3 -3
- package/build/src/cli/render/contract.js +200 -54
- package/build/src/cli/render/kanshi.js +272 -129
- package/build/src/cli/render/receipt.d.ts +19 -0
- package/build/src/cli/render/receipt.js +106 -0
- package/build/src/cli/render/refusal.d.ts +16 -2
- package/build/src/cli/render/refusal.js +88 -3
- package/build/src/cli/render/region.d.ts +2 -0
- package/build/src/cli/render/region.js +17 -0
- package/build/src/cli/render/task.d.ts +2 -1
- package/build/src/cli/render/task.js +223 -60
- package/build/src/cli/render/terminal.d.ts +20 -0
- package/build/src/cli/render/terminal.js +121 -0
- package/build/src/cli/render/text.js +11 -3
- package/build/src/cli/result.d.ts +126 -19
- package/build/src/cli/usage.d.ts +1 -0
- package/build/src/cli/usage.js +3 -0
- package/build/src/contract-worktree.d.ts +44 -0
- package/build/src/contract-worktree.js +251 -0
- package/build/src/coordination/durable-file.d.ts +5 -1
- package/build/src/coordination/durable-file.js +57 -10
- package/build/src/coordination/sqlite-transaction-lock.d.ts +4 -0
- package/build/src/coordination/sqlite-transaction-lock.js +22 -5
- package/build/src/core/facts/codec.js +23 -4
- package/build/src/core/facts/eligibility.d.ts +1 -10
- package/build/src/core/facts/eligibility.js +0 -49
- package/build/src/core/facts/fold.js +1 -6
- package/build/src/core/facts/gate.d.ts +1 -0
- package/build/src/core/facts/gate.js +1 -0
- package/build/src/core/facts/observation.d.ts +3 -0
- package/build/src/core/facts/observation.js +12 -0
- package/build/src/core/facts/types.d.ts +10 -3
- package/build/src/core/subject.js +2 -2
- package/build/src/core/verbs/amend.d.ts +1 -1
- package/build/src/core/verbs/amend.js +3 -18
- package/build/src/core/verbs/bind.js +2 -16
- package/build/src/core/verbs/deliver.d.ts +1 -1
- package/build/src/core/verbs/deliver.js +14 -4
- package/build/src/core/verbs/placement.d.ts +1 -1
- package/build/src/core/verbs/placement.js +7 -9
- package/build/src/dispatch/index.d.ts +5 -3
- package/build/src/dispatch/index.js +30 -21
- package/build/src/duration.d.ts +10 -0
- package/build/src/duration.js +22 -0
- package/build/src/git/admission.d.ts +2 -2
- package/build/src/git/admission.js +33 -23
- package/build/src/git/hooks.d.ts +15 -0
- package/build/src/git/hooks.js +70 -18
- package/build/src/git/identity.d.ts +5 -1
- package/build/src/git/identity.js +23 -4
- package/build/src/git/integration.d.ts +48 -0
- package/build/src/git/integration.js +250 -0
- package/build/src/git/observe.d.ts +29 -15
- package/build/src/git/observe.js +185 -76
- package/build/src/git/read-observation.d.ts +41 -0
- package/build/src/git/read-observation.js +361 -0
- package/build/src/git/reconcile.d.ts +17 -5
- package/build/src/git/reconcile.js +305 -109
- package/build/src/git/repository.d.ts +41 -18
- package/build/src/git/repository.js +220 -114
- package/build/src/git/scratch.d.ts +17 -0
- package/build/src/git/scratch.js +73 -0
- package/build/src/git/target-placement.d.ts +92 -0
- package/build/src/git/target-placement.js +375 -0
- package/build/src/git/tender.d.ts +49 -0
- package/build/src/git/tender.js +63 -0
- package/build/src/git/terminal-seal.d.ts +24 -0
- package/build/src/git/terminal-seal.js +89 -0
- package/build/src/git/tree.d.ts +2 -2
- package/build/src/git/tree.js +4 -0
- package/build/src/git/workspace.d.ts +53 -0
- package/build/src/git/workspace.js +139 -0
- package/build/src/identity/selector.d.ts +6 -0
- package/build/src/identity/selector.js +19 -0
- package/build/src/index.d.ts +4 -2
- package/build/src/index.js +2 -1
- package/build/src/kanshi/index.d.ts +2 -2
- package/build/src/kanshi/index.js +1 -1
- package/build/src/kanshi/read.d.ts +7 -3
- package/build/src/kanshi/read.js +245 -45
- package/build/src/kanshi/report.d.ts +81 -6
- package/build/src/kanshi/select.d.ts +5 -0
- package/build/src/kanshi/select.js +33 -1
- package/build/src/library/address.d.ts +57 -0
- package/build/src/library/address.js +165 -0
- package/build/src/library/akuma-creation.d.ts +16 -7
- package/build/src/library/akuma-creation.js +96 -22
- package/build/src/library/audit.d.ts +18 -0
- package/build/src/library/audit.js +44 -0
- package/build/src/library/bind.d.ts +21 -0
- package/build/src/library/bind.js +56 -0
- package/build/src/library/catalog.d.ts +58 -0
- package/build/src/library/catalog.js +69 -0
- package/build/src/library/configuration.d.ts +6 -15
- package/build/src/library/configuration.js +14 -17
- package/build/src/library/contract.d.ts +23 -49
- package/build/src/library/contract.js +171 -185
- package/build/src/library/delivery.d.ts +24 -0
- package/build/src/library/delivery.js +21 -0
- package/build/src/library/fleet.d.ts +65 -0
- package/build/src/library/fleet.js +153 -0
- package/build/src/library/input.d.ts +3 -0
- package/build/src/library/input.js +19 -0
- package/build/src/library/keiyaku.d.ts +18 -3
- package/build/src/library/keiyaku.js +12 -1
- package/build/src/library/mutation.d.ts +39 -0
- package/build/src/library/mutation.js +37 -0
- package/build/src/library/reconcile.d.ts +29 -0
- package/build/src/library/reconcile.js +179 -0
- package/build/src/library/refusal.d.ts +21 -0
- package/build/src/library/refusal.js +29 -0
- package/build/src/library/region.d.ts +14 -1
- package/build/src/library/region.js +18 -4
- package/build/src/library/repo.d.ts +4 -13
- package/build/src/library/repo.js +14 -24
- package/build/src/protocol/attempt.d.ts +16 -3
- package/build/src/protocol/attempt.js +41 -10
- package/build/src/protocol/bind.d.ts +9 -2
- package/build/src/protocol/bind.js +48 -40
- package/build/src/protocol/intent.d.ts +57 -20
- package/build/src/protocol/intent.js +55 -79
- package/build/src/protocol/operations.d.ts +152 -43
- package/build/src/protocol/operations.js +482 -166
- package/build/src/protocol/outcome.d.ts +14 -3
- package/build/src/protocol/outcome.js +11 -3
- package/build/src/protocol/placement.d.ts +41 -0
- package/build/src/protocol/placement.js +147 -0
- package/build/src/protocol/read/documents.d.ts +2 -2
- package/build/src/protocol/read/documents.js +4 -4
- package/build/src/protocol/read/status.d.ts +18 -5
- package/build/src/protocol/read/status.js +73 -13
- package/build/src/protocol/run.d.ts +49 -7
- package/build/src/protocol/run.js +53 -25
- package/build/src/runtime/proc/line-rpc.d.ts +4 -8
- package/build/src/runtime/proc/line-rpc.js +22 -28
- package/build/src/runtime/proc/run.d.ts +23 -21
- package/build/src/runtime/proc/run.js +123 -89
- package/build/src/runtime/proc/stdio.d.ts +18 -0
- package/build/src/runtime/proc/stdio.js +58 -0
- package/build/src/settings.d.ts +7 -1
- package/build/src/settings.js +32 -17
- package/build/src/settlement/fence.d.ts +5 -0
- package/build/src/settlement/fence.js +14 -0
- package/build/src/settlement/holder.d.ts +35 -4
- package/build/src/settlement/holder.js +127 -28
- package/build/src/settlement/settle.d.ts +9 -1
- package/build/src/settlement/settle.js +74 -37
- package/build/src/task/board.d.ts +23 -5
- package/build/src/task/board.js +17 -10
- package/build/src/task/compose.d.ts +2 -2
- package/build/src/task/compose.js +18 -10
- package/build/src/task/context.d.ts +3 -3
- package/build/src/task/context.js +15 -20
- package/build/src/task/document.d.ts +2 -1
- package/build/src/task/document.js +15 -5
- package/build/src/task/index.d.ts +27 -15
- package/build/src/task/index.js +57 -35
- package/build/src/task/operations.d.ts +38 -13
- package/build/src/task/operations.js +80 -46
- package/build/src/task/query.d.ts +67 -0
- package/build/src/task/query.js +279 -0
- package/build/src/task/store.d.ts +5 -7
- package/build/src/task/store.js +43 -28
- package/build/src/verification/declaration.d.ts +1 -0
- package/build/src/verification/execution.d.ts +51 -0
- package/build/src/verification/execution.js +108 -0
- package/build/src/workspace-place.d.ts +41 -0
- package/build/src/workspace-place.js +386 -0
- package/build/src/world.d.ts +24 -0
- package/build/src/world.js +136 -0
- package/package.json +8 -4
- package/build/src/akuma/activity.d.ts +0 -66
- package/build/src/akuma/activity.js +0 -139
- package/build/src/akuma/providers/claude.js +0 -305
- package/build/src/context-root.d.ts +0 -4
- package/build/src/context-root.js +0 -15
- package/build/src/git/delivery.d.ts +0 -20
- package/build/src/git/delivery.js +0 -109
- package/build/src/git/verification.d.ts +0 -14
- package/build/src/git/verification.js +0 -56
- package/build/src/protocol/read/audit.d.ts +0 -25
- package/build/src/protocol/read/audit.js +0 -39
- package/build/src/verification/producer.d.ts +0 -23
- package/build/src/verification/producer.js +0 -54
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import {
|
|
2
|
+
import { access, mkdir, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { isAbsolute, join, resolve } from "node:path";
|
|
4
4
|
import { HeldAkumaLeash, admitRequest, readNonterminalRequests, readRequest, readSoul, refuseRequest, reserveRequest, serveRequest, voidRequest, } from "./heart/index.js";
|
|
5
5
|
import { parseAkuId, pathsForAkuId, archetypeName, worldRootForAkumaPaths, } from "./identity.js";
|
|
6
6
|
import { BIRTH_TIMEOUT_MS, publishAkuma } from "./publication.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import { abortableDelay } from "./abort.js";
|
|
8
|
+
import { AKUMA_REQUESTS_ENV } from "./provider.js";
|
|
9
|
+
import { decodeProviderOptions, decodeReadonlyRestraint } from "./provider-recipe.js";
|
|
10
|
+
import { resolveProviderExecution } from "./providers/index.js";
|
|
9
11
|
const POLL_MS = 25;
|
|
10
12
|
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u;
|
|
11
13
|
export class AkumaBodyRequestError extends Error {
|
|
@@ -19,9 +21,6 @@ export class AkumaBodyRequestError extends Error {
|
|
|
19
21
|
this.name = "AkumaBodyRequestError";
|
|
20
22
|
}
|
|
21
23
|
}
|
|
22
|
-
function wait(milliseconds) {
|
|
23
|
-
return new Promise((resolveWait) => setTimeout(resolveWait, milliseconds));
|
|
24
|
-
}
|
|
25
24
|
function diagnostic(error) {
|
|
26
25
|
return error instanceof Error ? error.message : String(error);
|
|
27
26
|
}
|
|
@@ -57,6 +56,7 @@ function decodeRecipe(value, cwd) {
|
|
|
57
56
|
...(recipe.description === undefined ? [] : ["description"]),
|
|
58
57
|
"options",
|
|
59
58
|
"provider",
|
|
59
|
+
...(recipe.readonly === undefined ? [] : ["readonly"]),
|
|
60
60
|
];
|
|
61
61
|
if (!exactKeys(recipe, expectedKeys))
|
|
62
62
|
return null;
|
|
@@ -64,19 +64,21 @@ function decodeRecipe(value, cwd) {
|
|
|
64
64
|
&& (typeof recipe.description !== "string" || recipe.description.trim().length === 0))
|
|
65
65
|
return null;
|
|
66
66
|
try {
|
|
67
|
-
const
|
|
68
|
-
const
|
|
67
|
+
const selected = resolveProviderExecution(recipe.provider);
|
|
68
|
+
const provider = selected.execution;
|
|
69
|
+
const adapter = selected.adapter;
|
|
69
70
|
const decodedOptions = decodeProviderOptions(recipe.options);
|
|
70
|
-
const
|
|
71
|
-
if (
|
|
71
|
+
const readonly = recipe.readonly === undefined ? undefined : decodeReadonlyRestraint(recipe.readonly);
|
|
72
|
+
if ((decodedOptions.readonly === true) !== (readonly !== undefined))
|
|
72
73
|
return null;
|
|
73
|
-
const confinement = adapter.confinement({ cwd, options:
|
|
74
|
+
const confinement = adapter.confinement({ cwd, options: decodedOptions });
|
|
74
75
|
if (!matchingConfinement(recipe.confinement, confinement))
|
|
75
76
|
return null;
|
|
76
77
|
return Object.freeze({
|
|
77
78
|
...(recipe.description === undefined ? {} : { description: recipe.description }),
|
|
78
79
|
provider,
|
|
79
|
-
options:
|
|
80
|
+
options: decodedOptions,
|
|
81
|
+
...(readonly === undefined ? {} : { readonly }),
|
|
80
82
|
confinement,
|
|
81
83
|
});
|
|
82
84
|
}
|
|
@@ -158,14 +160,14 @@ function decodeReceipt(bytes, requestId) {
|
|
|
158
160
|
}
|
|
159
161
|
return null;
|
|
160
162
|
}
|
|
161
|
-
function atomicJson(path, value) {
|
|
163
|
+
async function atomicJson(path, value) {
|
|
162
164
|
const temporary = `${path}.${randomUUID()}.tmp`;
|
|
163
165
|
try {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
+
await writeFile(temporary, `${JSON.stringify(value)}\n`, { flag: "wx" });
|
|
167
|
+
await rename(temporary, path);
|
|
166
168
|
}
|
|
167
169
|
finally {
|
|
168
|
-
|
|
170
|
+
await rm(temporary, { force: true });
|
|
169
171
|
}
|
|
170
172
|
}
|
|
171
173
|
function receiptFor(fact) {
|
|
@@ -177,10 +179,10 @@ function receiptFor(fact) {
|
|
|
177
179
|
return { id: fact.id, state: fact.state, evidence: fact.evidence };
|
|
178
180
|
return null;
|
|
179
181
|
}
|
|
180
|
-
function projectReceipt(directory, fact) {
|
|
182
|
+
async function projectReceipt(directory, fact) {
|
|
181
183
|
const receipt = receiptFor(fact);
|
|
182
184
|
if (receipt !== null)
|
|
183
|
-
atomicJson(join(directory, `${fact.id}.receipt.json`), receipt);
|
|
185
|
+
await atomicJson(join(directory, `${fact.id}.receipt.json`), receipt);
|
|
184
186
|
}
|
|
185
187
|
export function injectedBodyRequests() {
|
|
186
188
|
const directory = process.env[AKUMA_REQUESTS_ENV];
|
|
@@ -191,7 +193,7 @@ export function injectedBodyRequests() {
|
|
|
191
193
|
return directory;
|
|
192
194
|
}
|
|
193
195
|
export async function requestBodyCall(input) {
|
|
194
|
-
atomicJson(join(input.directory, `${input.id}.request.json`), {
|
|
196
|
+
await atomicJson(join(input.directory, `${input.id}.request.json`), {
|
|
195
197
|
id: input.id,
|
|
196
198
|
world: input.world,
|
|
197
199
|
archetype: input.archetype,
|
|
@@ -202,7 +204,7 @@ export async function requestBodyCall(input) {
|
|
|
202
204
|
const receiptPath = join(input.directory, `${input.id}.receipt.json`);
|
|
203
205
|
for (;;) {
|
|
204
206
|
try {
|
|
205
|
-
const receipt = decodeReceipt(
|
|
207
|
+
const receipt = decodeReceipt(await readFile(receiptPath, "utf8"), input.id);
|
|
206
208
|
if (receipt === null)
|
|
207
209
|
throw new Error(`Akuma body request ${input.id} has an invalid receipt`);
|
|
208
210
|
if (receipt.state === "served")
|
|
@@ -215,14 +217,16 @@ export async function requestBodyCall(input) {
|
|
|
215
217
|
if (error.code !== "ENOENT")
|
|
216
218
|
throw error;
|
|
217
219
|
}
|
|
218
|
-
await
|
|
220
|
+
await abortableDelay(POLL_MS);
|
|
219
221
|
}
|
|
220
222
|
}
|
|
221
223
|
async function serveClaim(input) {
|
|
222
|
-
|
|
224
|
+
input.signal.throwIfAborted();
|
|
225
|
+
let fact = await admitRequest(input.paths, { ...input.claim, admittedAt: input.now() });
|
|
223
226
|
const existing = receiptFor(fact);
|
|
224
227
|
if (existing !== null) {
|
|
225
|
-
|
|
228
|
+
if (!input.signal.aborted)
|
|
229
|
+
await projectReceipt(input.directory, fact);
|
|
226
230
|
return;
|
|
227
231
|
}
|
|
228
232
|
if (fact.state !== "admitted")
|
|
@@ -230,17 +234,19 @@ async function serveClaim(input) {
|
|
|
230
234
|
const request = fact;
|
|
231
235
|
const servingWorld = worldRootForAkumaPaths(input.paths);
|
|
232
236
|
if (request.world !== servingWorld) {
|
|
233
|
-
fact = refuseRequest(input.paths, request.id, `request world ${request.world} does not match ${servingWorld}`);
|
|
234
|
-
projectReceipt(input.directory, fact);
|
|
237
|
+
fact = await refuseRequest(input.paths, request.id, `request world ${request.world} does not match ${servingWorld}`);
|
|
238
|
+
await projectReceipt(input.directory, fact);
|
|
235
239
|
return;
|
|
236
240
|
}
|
|
237
241
|
const cwd = resolve(request.cwd ?? servingWorld);
|
|
238
242
|
let child;
|
|
239
243
|
try {
|
|
244
|
+
input.signal.throwIfAborted();
|
|
240
245
|
const published = await publishAkuma({
|
|
241
246
|
worldPath: servingWorld,
|
|
242
247
|
archetype: request.archetype,
|
|
243
|
-
reserve: (allocated) => { fact = reserveRequest(input.paths, request.id, allocated.id); },
|
|
248
|
+
reserve: async (allocated) => { fact = await reserveRequest(input.paths, request.id, allocated.id); },
|
|
249
|
+
signal: input.signal,
|
|
244
250
|
launch: async (allocated) => await input.spawn({
|
|
245
251
|
paths: allocated.paths,
|
|
246
252
|
seed: {
|
|
@@ -249,8 +255,9 @@ async function serveClaim(input) {
|
|
|
249
255
|
...(request.recipe.description === undefined ? {} : { description: request.recipe.description }),
|
|
250
256
|
provider: request.recipe.provider,
|
|
251
257
|
options: request.recipe.options,
|
|
258
|
+
...(request.recipe.readonly === undefined ? {} : { readonly: request.recipe.readonly }),
|
|
252
259
|
cwd,
|
|
253
|
-
origin: { kind: "request",
|
|
260
|
+
origin: { kind: "request", parent: input.parent.id, requestId: request.id },
|
|
254
261
|
confinement: request.recipe.confinement,
|
|
255
262
|
},
|
|
256
263
|
initialBody: request.body,
|
|
@@ -259,21 +266,23 @@ async function serveClaim(input) {
|
|
|
259
266
|
child = published.id;
|
|
260
267
|
}
|
|
261
268
|
catch (error) {
|
|
262
|
-
|
|
269
|
+
if (input.signal.aborted)
|
|
270
|
+
return;
|
|
271
|
+
const current = await readRequest(input.paths, request.id);
|
|
263
272
|
if (current === null)
|
|
264
273
|
throw error;
|
|
265
274
|
fact = current.state === "admitted" || current.state === "reserved"
|
|
266
|
-
? voidRequest(input.paths, request.id, diagnostic(error))
|
|
275
|
+
? await voidRequest(input.paths, request.id, diagnostic(error))
|
|
267
276
|
: current;
|
|
268
|
-
projectReceipt(input.directory, fact);
|
|
277
|
+
await projectReceipt(input.directory, fact);
|
|
269
278
|
return;
|
|
270
279
|
}
|
|
271
|
-
fact = serveRequest(input.paths, request.id, child);
|
|
272
|
-
projectReceipt(input.directory, fact);
|
|
280
|
+
fact = await serveRequest(input.paths, request.id, child);
|
|
281
|
+
await projectReceipt(input.directory, fact);
|
|
273
282
|
}
|
|
274
|
-
function requestFiles(directory) {
|
|
283
|
+
async function requestFiles(directory) {
|
|
275
284
|
try {
|
|
276
|
-
return
|
|
285
|
+
return (await readdir(directory))
|
|
277
286
|
.filter((name) => name.endsWith(".request.json"))
|
|
278
287
|
.sort();
|
|
279
288
|
}
|
|
@@ -287,35 +296,47 @@ export class BodyRequestPump {
|
|
|
287
296
|
input;
|
|
288
297
|
directory;
|
|
289
298
|
failure;
|
|
290
|
-
|
|
299
|
+
closeSignal = new AbortController();
|
|
291
300
|
handled = new Set();
|
|
292
301
|
running;
|
|
302
|
+
cancelAdmission;
|
|
293
303
|
constructor(input) {
|
|
294
304
|
this.input = input;
|
|
295
305
|
this.directory = join(input.paths.directory, "requests", String(input.bodySequence));
|
|
296
|
-
|
|
306
|
+
this.cancelAdmission = () => this.stopAdmission();
|
|
307
|
+
input.signal.addEventListener("abort", this.cancelAdmission, { once: true });
|
|
308
|
+
if (input.signal.aborted)
|
|
309
|
+
this.cancelAdmission();
|
|
297
310
|
this.running = this.run();
|
|
298
311
|
this.failure = this.running.then(() => new Promise(() => { }), (error) => Promise.reject(error));
|
|
299
312
|
}
|
|
313
|
+
static async open(input) {
|
|
314
|
+
const directory = join(input.paths.directory, "requests", String(input.bodySequence));
|
|
315
|
+
await mkdir(directory, { recursive: true });
|
|
316
|
+
return new BodyRequestPump(input);
|
|
317
|
+
}
|
|
300
318
|
async run() {
|
|
301
|
-
while (!this.
|
|
302
|
-
for (const name of requestFiles(this.directory)) {
|
|
303
|
-
if (this.
|
|
319
|
+
while (!this.closeSignal.signal.aborted) {
|
|
320
|
+
for (const name of await requestFiles(this.directory)) {
|
|
321
|
+
if (this.closeSignal.signal.aborted)
|
|
304
322
|
return;
|
|
305
323
|
const id = name.slice(0, -".request.json".length);
|
|
306
324
|
if (this.handled.has(id))
|
|
307
325
|
continue;
|
|
308
|
-
const claim = decodeClaim(
|
|
326
|
+
const claim = decodeClaim(await readFile(join(this.directory, name), "utf8"), id);
|
|
309
327
|
if (claim === null) {
|
|
310
328
|
this.handled.add(id);
|
|
311
329
|
continue;
|
|
312
330
|
}
|
|
313
|
-
|
|
314
|
-
return;
|
|
315
|
-
await serveClaim({ directory: this.directory, claim, ...this.input });
|
|
331
|
+
await serveClaim({ directory: this.directory, claim, ...this.input, signal: this.closeSignal.signal });
|
|
316
332
|
this.handled.add(id);
|
|
317
333
|
}
|
|
318
|
-
|
|
334
|
+
try {
|
|
335
|
+
await abortableDelay(POLL_MS, this.closeSignal.signal);
|
|
336
|
+
}
|
|
337
|
+
catch {
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
319
340
|
}
|
|
320
341
|
}
|
|
321
342
|
async close() {
|
|
@@ -324,45 +345,53 @@ export class BodyRequestPump {
|
|
|
324
345
|
await this.running;
|
|
325
346
|
}
|
|
326
347
|
finally {
|
|
327
|
-
|
|
348
|
+
this.input.signal.removeEventListener("abort", this.cancelAdmission);
|
|
349
|
+
await rm(this.directory, { recursive: true, force: true });
|
|
328
350
|
}
|
|
329
351
|
}
|
|
330
|
-
stopAdmission() {
|
|
352
|
+
stopAdmission() {
|
|
353
|
+
if (!this.closeSignal.signal.aborted)
|
|
354
|
+
this.closeSignal.abort(new Error("Body request pump closed"));
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
export async function clearBodyRequestTransport(paths) {
|
|
358
|
+
await rm(join(paths.directory, "requests"), { recursive: true, force: true });
|
|
331
359
|
}
|
|
332
360
|
function matchingRequestOrigin(soul, parent, requestId) {
|
|
333
361
|
return soul.origin.kind === "request"
|
|
334
|
-
&& soul.origin.
|
|
362
|
+
&& soul.origin.parent === parent
|
|
335
363
|
&& soul.origin.requestId === requestId;
|
|
336
364
|
}
|
|
337
|
-
function settleObservedSoul(paths, parent, request, soul) {
|
|
365
|
+
async function settleObservedSoul(paths, parent, request, soul) {
|
|
338
366
|
if (matchingRequestOrigin(soul, parent.id, request.id))
|
|
339
|
-
serveRequest(paths, request.id, request.child);
|
|
367
|
+
await serveRequest(paths, request.id, request.child);
|
|
340
368
|
else
|
|
341
|
-
voidRequest(paths, request.id, "reserved child origin does not match the request");
|
|
369
|
+
await voidRequest(paths, request.id, "reserved child origin does not match the request");
|
|
342
370
|
}
|
|
343
|
-
async function settleReserved(paths, parent, request, now) {
|
|
371
|
+
async function settleReserved(paths, parent, request, now, signal) {
|
|
344
372
|
const childPaths = pathsForAkuId(request.world, request.child);
|
|
345
373
|
const deadline = performance.now() + BIRTH_TIMEOUT_MS;
|
|
346
374
|
for (;;) {
|
|
347
|
-
|
|
348
|
-
|
|
375
|
+
signal?.throwIfAborted();
|
|
376
|
+
if (!await access(childPaths.directory).then(() => true, () => false)) {
|
|
377
|
+
await voidRequest(paths, request.id, "reserved child directory is absent");
|
|
349
378
|
return true;
|
|
350
379
|
}
|
|
351
|
-
const childSoul = readSoul(childPaths);
|
|
380
|
+
const childSoul = await readSoul(childPaths);
|
|
352
381
|
if (childSoul !== null) {
|
|
353
|
-
settleObservedSoul(paths, parent, request, childSoul);
|
|
382
|
+
await settleObservedSoul(paths, parent, request, childSoul);
|
|
354
383
|
return true;
|
|
355
384
|
}
|
|
356
|
-
const leash = HeldAkumaLeash.try(childPaths);
|
|
385
|
+
const leash = await HeldAkumaLeash.try(childPaths);
|
|
357
386
|
if (leash !== null) {
|
|
358
387
|
try {
|
|
359
|
-
const settledSoul = readSoul(childPaths);
|
|
388
|
+
const settledSoul = await readSoul(childPaths);
|
|
360
389
|
if (settledSoul !== null) {
|
|
361
|
-
settleObservedSoul(paths, parent, request, settledSoul);
|
|
390
|
+
await settleObservedSoul(paths, parent, request, settledSoul);
|
|
362
391
|
}
|
|
363
392
|
else {
|
|
364
|
-
leash.sealIfUnborn(childPaths, { evidence: "request settlement", at: now() });
|
|
365
|
-
voidRequest(paths, request.id, "reserved child was sealed unborn");
|
|
393
|
+
await leash.sealIfUnborn(childPaths, { evidence: "request settlement", at: now() });
|
|
394
|
+
await voidRequest(paths, request.id, "reserved child was sealed unborn");
|
|
366
395
|
}
|
|
367
396
|
}
|
|
368
397
|
finally {
|
|
@@ -372,16 +401,17 @@ async function settleReserved(paths, parent, request, now) {
|
|
|
372
401
|
}
|
|
373
402
|
if (performance.now() >= deadline)
|
|
374
403
|
return false;
|
|
375
|
-
await
|
|
404
|
+
await abortableDelay(POLL_MS, signal);
|
|
376
405
|
}
|
|
377
406
|
}
|
|
378
|
-
export async function settleBodyRequests(paths, parent, now) {
|
|
407
|
+
export async function settleBodyRequests(paths, parent, now, signal) {
|
|
379
408
|
let pending = false;
|
|
380
|
-
for (const request of readNonterminalRequests(paths)) {
|
|
409
|
+
for (const request of await readNonterminalRequests(paths)) {
|
|
410
|
+
signal?.throwIfAborted();
|
|
381
411
|
if (request.state === "admitted") {
|
|
382
|
-
voidRequest(paths, request.id, "body died before serving the request");
|
|
412
|
+
await voidRequest(paths, request.id, "body died before serving the request");
|
|
383
413
|
}
|
|
384
|
-
else if (request.state === "reserved" && !await settleReserved(paths, parent, request, now))
|
|
414
|
+
else if (request.state === "reserved" && !await settleReserved(paths, parent, request, now, signal))
|
|
385
415
|
pending = true;
|
|
386
416
|
}
|
|
387
417
|
return pending ? "pending" : "settled";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type AkuId } from "../akuma/identity.js";
|
|
2
2
|
import { type AkumaAlias } from "../identity/selector.js";
|
|
3
|
+
import type { WorldRoot } from "../world.js";
|
|
3
4
|
export type AliasBinding = Readonly<{
|
|
4
5
|
alias: AkumaAlias;
|
|
5
6
|
akuId: AkuId;
|
|
@@ -8,10 +9,10 @@ export type AliasMove = Readonly<{
|
|
|
8
9
|
alias: AliasBinding;
|
|
9
10
|
previous: AkuId | null;
|
|
10
11
|
}>;
|
|
11
|
-
export declare function readAliases(world:
|
|
12
|
-
export declare function resolveAlias(world:
|
|
12
|
+
export declare function readAliases(world: WorldRoot): Promise<readonly AliasBinding[]>;
|
|
13
|
+
export declare function resolveAlias(world: WorldRoot, value: AkumaAlias): Promise<AkuId | null>;
|
|
13
14
|
export declare function moveAlias(input: Readonly<{
|
|
14
|
-
world:
|
|
15
|
+
world: WorldRoot;
|
|
15
16
|
alias: AkumaAlias;
|
|
16
17
|
akuId: AkuId;
|
|
17
18
|
}>): Promise<AliasMove>;
|
package/build/src/alias/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mkdir, readFile } from "node:fs/promises";
|
|
2
2
|
import { dirname, resolve } from "node:path";
|
|
3
3
|
import { parseAkuId } from "../akuma/identity.js";
|
|
4
4
|
import { replaceFileDurably } from "../coordination/durable-file.js";
|
|
@@ -10,10 +10,9 @@ function paths(world) {
|
|
|
10
10
|
if (typeof world !== "string" || world.trim().length === 0) {
|
|
11
11
|
throw new TypeError("Alias world must be a nonblank path");
|
|
12
12
|
}
|
|
13
|
-
const root = resolve(world);
|
|
14
13
|
return {
|
|
15
|
-
authority: resolve(
|
|
16
|
-
lock: resolve(
|
|
14
|
+
authority: resolve(world, ".keiyaku", "akuma", "alias.json"),
|
|
15
|
+
lock: resolve(world, ".keiyaku", "locks", "akuma-alias.sqlite"),
|
|
17
16
|
};
|
|
18
17
|
}
|
|
19
18
|
function canonical(bindings) {
|
|
@@ -68,9 +67,9 @@ function decode(path, bytes) {
|
|
|
68
67
|
corruption(`Alias bytes are not canonical: ${path}`);
|
|
69
68
|
return bindings;
|
|
70
69
|
}
|
|
71
|
-
function read(path) {
|
|
70
|
+
async function read(path) {
|
|
72
71
|
try {
|
|
73
|
-
return decode(path,
|
|
72
|
+
return decode(path, await readFile(path, "utf8"));
|
|
74
73
|
}
|
|
75
74
|
catch (error) {
|
|
76
75
|
if (error.code === "ENOENT")
|
|
@@ -78,12 +77,12 @@ function read(path) {
|
|
|
78
77
|
throw error;
|
|
79
78
|
}
|
|
80
79
|
}
|
|
81
|
-
export function readAliases(world) {
|
|
82
|
-
return read(paths(world).authority);
|
|
80
|
+
export async function readAliases(world) {
|
|
81
|
+
return await read(paths(world).authority);
|
|
83
82
|
}
|
|
84
|
-
export function resolveAlias(world, value) {
|
|
83
|
+
export async function resolveAlias(world, value) {
|
|
85
84
|
const alias = parseAkumaAlias(value);
|
|
86
|
-
return readAliases(world).find((binding) => binding.alias === alias)?.akuId ?? null;
|
|
85
|
+
return (await readAliases(world)).find((binding) => binding.alias === alias)?.akuId ?? null;
|
|
87
86
|
}
|
|
88
87
|
export async function moveAlias(input) {
|
|
89
88
|
const alias = parseAkumaAlias(input.alias);
|
|
@@ -91,11 +90,11 @@ export async function moveAlias(input) {
|
|
|
91
90
|
const location = paths(input.world);
|
|
92
91
|
const held = await acquireSqliteTransactionLock({ path: location.lock, mode: "immediate" });
|
|
93
92
|
try {
|
|
94
|
-
const current = read(location.authority);
|
|
93
|
+
const current = await read(location.authority);
|
|
95
94
|
const previous = current.find((binding) => binding.alias === alias)?.akuId ?? null;
|
|
96
95
|
const next = [...current.filter((binding) => binding.alias !== alias), { alias, akuId }];
|
|
97
|
-
|
|
98
|
-
replaceFileDurably(location.authority, canonical(next));
|
|
96
|
+
await mkdir(dirname(location.authority), { recursive: true });
|
|
97
|
+
await replaceFileDurably(location.authority, canonical(next));
|
|
99
98
|
return { alias: { alias, akuId }, previous };
|
|
100
99
|
}
|
|
101
100
|
finally {
|
package/build/src/body/decode.js
CHANGED
|
@@ -91,7 +91,13 @@ function verification(document, section) {
|
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
export function decodeContractDocument(source) {
|
|
94
|
-
|
|
94
|
+
let document;
|
|
95
|
+
try {
|
|
96
|
+
document = parseToAST(source);
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
refusal(error instanceof Error ? error.message : String(error));
|
|
100
|
+
}
|
|
95
101
|
if (document.frontmatter !== undefined)
|
|
96
102
|
refusal("contract document may not contain frontmatter");
|
|
97
103
|
const titles = indexedHeadings(indexDocument(document), { level: 1 })
|
|
@@ -2,5 +2,7 @@ import type { DocumentNode, SectionNode } from "../markdown/types.js";
|
|
|
2
2
|
export declare class RegionDocumentError extends Error {
|
|
3
3
|
constructor(message: string);
|
|
4
4
|
}
|
|
5
|
+
export declare function assertCanonicalRegionPath(path: string): void;
|
|
5
6
|
export declare function decodeRegion(document: DocumentNode, section: SectionNode): readonly string[];
|
|
6
7
|
export declare function regionsOverlap(mine: readonly string[], theirs: readonly string[]): readonly [string, string][];
|
|
8
|
+
export declare function regionPatternsMatchPath(patterns: readonly string[], path: string): readonly string[];
|
package/build/src/body/region.js
CHANGED
|
@@ -28,15 +28,79 @@ function compileRegionPattern(pattern) {
|
|
|
28
28
|
refusal(`Region pattern '${pattern}' contains a forbidden glob form`);
|
|
29
29
|
if (pattern.startsWith("/"))
|
|
30
30
|
refusal(`Region pattern '${pattern}' must be repository-relative`);
|
|
31
|
-
if (pattern.endsWith("/"))
|
|
32
|
-
refusal(`Region pattern '${pattern}' may not end with /`);
|
|
33
31
|
if (pattern.includes(".."))
|
|
34
32
|
refusal(`Region pattern '${pattern}' may not contain ..`);
|
|
35
33
|
const segments = pattern.split("/");
|
|
36
|
-
|
|
34
|
+
const directory = pattern.endsWith("/");
|
|
35
|
+
if (segments.some((segment, index) => segment.length === 0 && !(directory && index === segments.length - 1))) {
|
|
37
36
|
refusal(`Region pattern '${pattern}' may not contain an empty segment`);
|
|
38
37
|
}
|
|
39
|
-
|
|
38
|
+
const source = directory ? `${pattern}**` : pattern;
|
|
39
|
+
const canonical = directory ? [...segments.slice(0, -1), "**"] : segments;
|
|
40
|
+
return { source, segments: canonical.map((segment) => compileSegment(segment, pattern)) };
|
|
41
|
+
}
|
|
42
|
+
export function assertCanonicalRegionPath(path) {
|
|
43
|
+
if (path.length === 0 || /[\r\n\0]/u.test(path) || path.startsWith("/") || path.endsWith("/")) {
|
|
44
|
+
refusal(`Region path '${path}' must be a canonical repository-relative path`);
|
|
45
|
+
}
|
|
46
|
+
if (path.split("/").some((segment) => segment.length === 0 || segment === "." || segment === "..")) {
|
|
47
|
+
refusal(`Region path '${path}' must be a canonical repository-relative path`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function segmentMatchesPath(pattern, path) {
|
|
51
|
+
const queue = [[0, 0]];
|
|
52
|
+
const visited = new Set();
|
|
53
|
+
let cursor = 0;
|
|
54
|
+
while (cursor < queue.length) {
|
|
55
|
+
const [patternIndex, pathIndex] = queue[cursor++];
|
|
56
|
+
const key = `${patternIndex}:${pathIndex}`;
|
|
57
|
+
if (visited.has(key))
|
|
58
|
+
continue;
|
|
59
|
+
visited.add(key);
|
|
60
|
+
if (patternIndex === pattern.length && pathIndex === path.length)
|
|
61
|
+
return true;
|
|
62
|
+
const character = pattern[patternIndex];
|
|
63
|
+
if (character === "*") {
|
|
64
|
+
queue.push([patternIndex + 1, pathIndex]);
|
|
65
|
+
if (pathIndex < path.length)
|
|
66
|
+
queue.push([patternIndex, pathIndex + 1]);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (character === "?") {
|
|
70
|
+
if (pathIndex < path.length)
|
|
71
|
+
queue.push([patternIndex + 1, pathIndex + 1]);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (character !== undefined && character === path[pathIndex])
|
|
75
|
+
queue.push([patternIndex + 1, pathIndex + 1]);
|
|
76
|
+
}
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
function patternMatchesPath(pattern, path) {
|
|
80
|
+
const queue = [[0, 0]];
|
|
81
|
+
const visited = new Set();
|
|
82
|
+
let cursor = 0;
|
|
83
|
+
while (cursor < queue.length) {
|
|
84
|
+
const [patternIndex, pathIndex] = queue[cursor++];
|
|
85
|
+
const key = `${patternIndex}:${pathIndex}`;
|
|
86
|
+
if (visited.has(key))
|
|
87
|
+
continue;
|
|
88
|
+
visited.add(key);
|
|
89
|
+
if (patternIndex === pattern.length && pathIndex === path.length)
|
|
90
|
+
return true;
|
|
91
|
+
const segment = pattern[patternIndex];
|
|
92
|
+
if (segment?.kind === "deep") {
|
|
93
|
+
queue.push([patternIndex + 1, pathIndex]);
|
|
94
|
+
if (pathIndex < path.length)
|
|
95
|
+
queue.push([patternIndex, pathIndex + 1]);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (segment === undefined || path[pathIndex] === undefined)
|
|
99
|
+
continue;
|
|
100
|
+
if (segmentMatchesPath(segment.characters, Array.from(path[pathIndex])))
|
|
101
|
+
queue.push([patternIndex + 1, pathIndex + 1]);
|
|
102
|
+
}
|
|
103
|
+
return false;
|
|
40
104
|
}
|
|
41
105
|
function segmentTransition(characters, index) {
|
|
42
106
|
const character = characters[index];
|
|
@@ -120,9 +184,7 @@ export function decodeRegion(document, section) {
|
|
|
120
184
|
const patterns = blocks[0].lines.slice(1, -1).filter((line) => line.trim().length > 0);
|
|
121
185
|
if (patterns.length === 0)
|
|
122
186
|
refusal("Region must declare at least one path pattern");
|
|
123
|
-
|
|
124
|
-
compileRegionPattern(pattern);
|
|
125
|
-
return patterns;
|
|
187
|
+
return patterns.map((pattern) => compileRegionPattern(pattern).source);
|
|
126
188
|
}
|
|
127
189
|
export function regionsOverlap(mine, theirs) {
|
|
128
190
|
const myPatterns = mine.map((pattern) => compileRegionPattern(pattern));
|
|
@@ -137,3 +199,11 @@ export function regionsOverlap(mine, theirs) {
|
|
|
137
199
|
}
|
|
138
200
|
return overlaps;
|
|
139
201
|
}
|
|
202
|
+
export function regionPatternsMatchPath(patterns, path) {
|
|
203
|
+
assertCanonicalRegionPath(path);
|
|
204
|
+
const pathSegments = path.split("/");
|
|
205
|
+
return patterns
|
|
206
|
+
.map((pattern) => compileRegionPattern(pattern))
|
|
207
|
+
.filter((pattern) => patternMatchesPath(pattern.segments, pathSegments))
|
|
208
|
+
.map((pattern) => pattern.source);
|
|
209
|
+
}
|
package/build/src/body/render.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { formatDuration } from "../duration.js";
|
|
1
2
|
import { parseToAST } from "../markdown/parse.js";
|
|
2
3
|
import { indexDocument, indexedHeadings, normalizeTitle, rawSlice } from "../markdown/query.js";
|
|
3
4
|
import { CONTRACT_SECTIONS } from "./shape.js";
|
|
@@ -21,7 +22,7 @@ function fenced(value, info = "") {
|
|
|
21
22
|
return `${boundary}${info}\n${value}\n${boundary}`;
|
|
22
23
|
}
|
|
23
24
|
function verificationFence(declaration) {
|
|
24
|
-
return fenced(declaration.script, declaration.executor);
|
|
25
|
+
return fenced(declaration.script, `${declaration.executor}${declaration.timeoutMs === undefined ? "" : ` timeout=${formatDuration(declaration.timeoutMs)}`}`);
|
|
25
26
|
}
|
|
26
27
|
function arcSection(arc) {
|
|
27
28
|
return [
|
package/build/src/body/shape.js
CHANGED
|
@@ -6,7 +6,13 @@ export const CONTRACT_SECTIONS = {
|
|
|
6
6
|
criteria: { title: "Criteria", required: true },
|
|
7
7
|
verification: { title: "Verification", required: false },
|
|
8
8
|
};
|
|
9
|
-
export const RESERVED_SECTIONS = new Set([
|
|
9
|
+
export const RESERVED_SECTIONS = new Set([
|
|
10
|
+
"gates",
|
|
11
|
+
"pipeline",
|
|
12
|
+
"after",
|
|
13
|
+
"arc",
|
|
14
|
+
"fulfillment",
|
|
15
|
+
]);
|
|
10
16
|
export function contractSectionName(title) {
|
|
11
17
|
for (const name of Object.keys(CONTRACT_SECTIONS)) {
|
|
12
18
|
if (CONTRACT_SECTIONS[name].title === title)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { directChildren, rawSlice } from "../markdown/query.js";
|
|
2
|
+
import { parseDuration } from "../duration.js";
|
|
2
3
|
export class VerificationDocumentError extends Error {
|
|
3
4
|
constructor(message) {
|
|
4
5
|
super(message);
|
|
@@ -13,13 +14,27 @@ export function decodeVerificationDeclarations(document, section) {
|
|
|
13
14
|
if (other.length > 0)
|
|
14
15
|
throw new VerificationDocumentError("Verification may contain only fenced executor declarations");
|
|
15
16
|
return blocks.map((block) => {
|
|
16
|
-
const
|
|
17
|
-
if (!block.closed ||
|
|
18
|
-
throw new VerificationDocumentError("Verification fences must be closed and use bash, zsh, or pwsh");
|
|
17
|
+
const info = /^(bash|zsh|pwsh)(?: timeout=([^ ]+))?$/.exec(block.info);
|
|
18
|
+
if (!block.closed || info === null) {
|
|
19
|
+
throw new VerificationDocumentError("Verification fences must be closed and use bash, zsh, or pwsh with optional timeout=<duration>");
|
|
19
20
|
}
|
|
20
21
|
const script = block.lines.slice(1, -1).join("\n");
|
|
21
22
|
if (script.trim().length === 0)
|
|
22
23
|
throw new VerificationDocumentError("Verification scripts must be nonblank");
|
|
23
|
-
|
|
24
|
+
const duration = info[2] === undefined ? undefined : parseDuration(info[2]);
|
|
25
|
+
if (duration?.kind === "invalid") {
|
|
26
|
+
throw new VerificationDocumentError("Verification timeout must be an integer duration with unit ms, s, m, or h");
|
|
27
|
+
}
|
|
28
|
+
if (duration?.kind === "overflow" || (duration?.kind === "parsed" && duration.milliseconds > 2_147_483_647)) {
|
|
29
|
+
throw new VerificationDocumentError("Verification timeout exceeds the supported process duration");
|
|
30
|
+
}
|
|
31
|
+
if (duration?.kind === "parsed" && duration.milliseconds === 0) {
|
|
32
|
+
throw new VerificationDocumentError("Verification timeout must be positive");
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
executor: info[1],
|
|
36
|
+
script,
|
|
37
|
+
...(duration === undefined ? {} : { timeoutMs: duration.milliseconds }),
|
|
38
|
+
};
|
|
24
39
|
});
|
|
25
40
|
}
|