@astrosheep/keiyaku 4.0.3 → 4.0.4

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.
Files changed (141) hide show
  1. package/build/integrations/marketplace/plugins/keiyaku/.claude-plugin/plugin.json +1 -1
  2. package/build/integrations/marketplace/plugins/keiyaku/.codex-plugin/plugin.json +1 -1
  3. package/build/integrations/marketplace/plugins/keiyaku/package.json +1 -1
  4. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +1 -1
  5. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +1 -1
  6. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +15 -1
  7. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-task/SKILL.md +3 -1
  8. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +20 -13
  9. package/build/src/akuma/akuma.d.ts +34 -11
  10. package/build/src/akuma/akuma.js +121 -61
  11. package/build/src/akuma/allowed.d.ts +7 -0
  12. package/build/src/akuma/allowed.js +40 -0
  13. package/build/src/akuma/archetype.d.ts +7 -4
  14. package/build/src/akuma/archetype.js +15 -16
  15. package/build/src/akuma/body.d.ts +3 -1
  16. package/build/src/akuma/body.js +9 -6
  17. package/build/src/akuma/heart/facts.d.ts +49 -7
  18. package/build/src/akuma/heart/index.d.ts +4 -8
  19. package/build/src/akuma/heart/index.js +76 -15
  20. package/build/src/akuma/heart/rows.d.ts +7 -5
  21. package/build/src/akuma/heart/rows.js +98 -11
  22. package/build/src/akuma/heart/schema.d.ts +1 -1
  23. package/build/src/akuma/heart/schema.js +15 -10
  24. package/build/src/akuma/heart/soul.js +3 -1
  25. package/build/src/akuma/heart/storage.d.ts +1 -0
  26. package/build/src/akuma/heart/storage.js +15 -0
  27. package/build/src/akuma/heart/timeline.d.ts +1 -5
  28. package/build/src/akuma/heart/timeline.js +8 -17
  29. package/build/src/akuma/index.d.ts +4 -1
  30. package/build/src/akuma/index.js +1 -0
  31. package/build/src/akuma/projection.d.ts +17 -3
  32. package/build/src/akuma/projection.js +40 -3
  33. package/build/src/akuma/provider.d.ts +1 -1
  34. package/build/src/akuma/provider.js +2 -1
  35. package/build/src/akuma/providers/acp/core.d.ts +3 -1
  36. package/build/src/akuma/providers/acp/core.js +4 -4
  37. package/build/src/akuma/providers/acp/events.d.ts +11 -3
  38. package/build/src/akuma/providers/acp/events.js +78 -4
  39. package/build/src/akuma/providers/claude/events.js +62 -3
  40. package/build/src/akuma/providers/codex-app-server/events.js +1 -13
  41. package/build/src/akuma/providers/grok-build/index.d.ts +2 -1
  42. package/build/src/akuma/providers/grok-build/index.js +72 -2
  43. package/build/src/akuma/providers/opencode-sdk/events.js +71 -6
  44. package/build/src/akuma/providers/pi/events.js +17 -3
  45. package/build/src/akuma/providers/unified-patch.d.ts +4 -0
  46. package/build/src/akuma/providers/unified-patch.js +14 -0
  47. package/build/src/akuma/publication.d.ts +1 -1
  48. package/build/src/akuma/publication.js +1 -1
  49. package/build/src/akuma/requests.d.ts +70 -4
  50. package/build/src/akuma/requests.js +288 -77
  51. package/build/src/akuma-body.d.ts +1 -0
  52. package/build/src/akuma-body.js +31 -0
  53. package/build/src/cli/accepted.d.ts +4 -1
  54. package/build/src/cli/accepted.js +26 -4
  55. package/build/src/cli/commands/akuma-invoke.d.ts +8 -7
  56. package/build/src/cli/commands/akuma-invoke.js +18 -10
  57. package/build/src/cli/commands/akuma.d.ts +11 -2
  58. package/build/src/cli/commands/akuma.js +65 -26
  59. package/build/src/cli/commands/amend.d.ts +1 -1
  60. package/build/src/cli/commands/amend.js +1 -1
  61. package/build/src/cli/commands/contract.d.ts +3 -3
  62. package/build/src/cli/commands/contract.js +1 -1
  63. package/build/src/cli/commands/install.js +2 -2
  64. package/build/src/cli/commands/task-query.d.ts +1 -1
  65. package/build/src/cli/commands/task-query.js +11 -5
  66. package/build/src/cli/coordinates.d.ts +1 -0
  67. package/build/src/cli/coordinates.js +5 -1
  68. package/build/src/cli/invoke.d.ts +1 -0
  69. package/build/src/cli/invoke.js +90 -73
  70. package/build/src/cli/main.d.ts +2 -0
  71. package/build/src/cli/main.js +29 -4
  72. package/build/src/cli/parse.d.ts +1 -0
  73. package/build/src/cli/parse.js +4 -0
  74. package/build/src/cli/render/akuma.d.ts +2 -2
  75. package/build/src/cli/render/akuma.js +84 -13
  76. package/build/src/cli/render/audit.js +16 -2
  77. package/build/src/cli/render/contract.d.ts +2 -0
  78. package/build/src/cli/render/contract.js +203 -36
  79. package/build/src/cli/render/kanshi.js +64 -29
  80. package/build/src/cli/render/receipt.d.ts +2 -1
  81. package/build/src/cli/render/receipt.js +9 -1
  82. package/build/src/cli/render/text.js +3 -1
  83. package/build/src/cli/result.d.ts +38 -3
  84. package/build/src/cli/selectors.d.ts +1 -0
  85. package/build/src/cli/selectors.js +9 -6
  86. package/build/src/contract-worktree.js +3 -0
  87. package/build/src/dispatch/index.js +50 -39
  88. package/build/src/git/hooks.d.ts +0 -4
  89. package/build/src/git/hooks.js +0 -36
  90. package/build/src/git/integration.d.ts +2 -6
  91. package/build/src/git/integration.js +8 -8
  92. package/build/src/git/reconcile.d.ts +6 -1
  93. package/build/src/git/reconcile.js +55 -9
  94. package/build/src/git/tender.d.ts +19 -4
  95. package/build/src/git/tender.js +44 -13
  96. package/build/src/git/terminal-seal.d.ts +14 -0
  97. package/build/src/git/terminal-seal.js +15 -2
  98. package/build/src/git/workspace.d.ts +1 -0
  99. package/build/src/git/workspace.js +12 -4
  100. package/build/src/index.d.ts +1 -1
  101. package/build/src/kanshi/index.d.ts +1 -1
  102. package/build/src/kanshi/index.js +1 -1
  103. package/build/src/kanshi/read.d.ts +9 -1
  104. package/build/src/kanshi/read.js +91 -37
  105. package/build/src/kanshi/report.d.ts +1 -0
  106. package/build/src/library/address.d.ts +5 -14
  107. package/build/src/library/address.js +20 -22
  108. package/build/src/library/akuma-creation.d.ts +4 -2
  109. package/build/src/library/akuma-creation.js +43 -26
  110. package/build/src/library/bind.js +3 -3
  111. package/build/src/library/catalog.d.ts +1 -2
  112. package/build/src/library/catalog.js +11 -6
  113. package/build/src/library/configuration.d.ts +4 -0
  114. package/build/src/library/configuration.js +19 -0
  115. package/build/src/library/contract.d.ts +15 -1
  116. package/build/src/library/contract.js +51 -4
  117. package/build/src/library/fleet.d.ts +47 -9
  118. package/build/src/library/fleet.js +184 -44
  119. package/build/src/library/input.js +1 -0
  120. package/build/src/library/keiyaku.d.ts +4 -4
  121. package/build/src/library/keiyaku.js +1 -2
  122. package/build/src/protocol/bind.d.ts +1 -2
  123. package/build/src/protocol/bind.js +2 -16
  124. package/build/src/protocol/operations.d.ts +4 -1
  125. package/build/src/protocol/operations.js +33 -27
  126. package/build/src/protocol/read/status.d.ts +3 -1
  127. package/build/src/protocol/read/status.js +24 -14
  128. package/build/src/runtime/proc/run.d.ts +1 -0
  129. package/build/src/runtime/proc/run.js +9 -2
  130. package/build/src/settlement/settle.d.ts +1 -0
  131. package/build/src/settlement/settle.js +4 -1
  132. package/build/src/task/board.d.ts +18 -3
  133. package/build/src/task/board.js +73 -26
  134. package/build/src/task/index.d.ts +3 -3
  135. package/build/src/task/index.js +7 -5
  136. package/build/src/task/operations.d.ts +2 -19
  137. package/build/src/task/operations.js +29 -7
  138. package/build/src/task/query.d.ts +9 -2
  139. package/build/src/task/query.js +32 -64
  140. package/build/src/verification/execution.js +2 -1
  141. package/package.json +3 -1
@@ -1,9 +1,11 @@
1
1
  import { readFile, readdir } from "node:fs/promises";
2
- import { dirname, join } from "node:path";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
3
4
  import { parseDocument } from "yaml";
4
5
  import { archetypeName } from "./identity.js";
5
6
  import { decodeProviderOptions, decodeProviderRecipe, } from "./provider-recipe.js";
6
7
  import { resolveProviderExecution } from "./providers/index.js";
8
+ import { effectiveAllowedActions } from "./allowed.js";
7
9
  export class AkumaArchetypeError extends Error {
8
10
  archetype;
9
11
  searched;
@@ -17,14 +19,11 @@ export class AkumaArchetypeError extends Error {
17
19
  this.name = "AkumaArchetypeError";
18
20
  }
19
21
  }
20
- function archetypeDirectory(settings) {
21
- const userPath = settings.scopes.user.path;
22
- return userPath === undefined ? null : join(dirname(userPath), "akuma");
22
+ function archetypeDirectory(home) {
23
+ return join(home ?? join(homedir(), ".keiyaku"), "akuma");
23
24
  }
24
- async function archetypePaths(settings) {
25
- const directory = archetypeDirectory(settings);
26
- if (directory === null)
27
- return [];
25
+ async function archetypePaths(home) {
26
+ const directory = archetypeDirectory(home);
28
27
  try {
29
28
  return (await readdir(directory, { withFileTypes: true }))
30
29
  .flatMap((entry) => {
@@ -46,8 +45,8 @@ async function archetypePaths(settings) {
46
45
  throw error;
47
46
  }
48
47
  }
49
- export async function listArchetypes(input) {
50
- return (await archetypePaths(input.settings)).map(({ name }) => name);
48
+ export async function listArchetypes(input = {}) {
49
+ return (await archetypePaths(input.home)).map(({ name }) => name);
51
50
  }
52
51
  function archetypeField(values, key, required = false) {
53
52
  const value = values[key];
@@ -96,6 +95,7 @@ function decodeArchetype(name, path, markdown) {
96
95
  const readonly = archetypeReadonly(values);
97
96
  const network = archetypeEnum(values, "network", ["disabled", "enabled"]);
98
97
  const description = archetypeField(values, "description");
98
+ const allowed = effectiveAllowedActions(values.allowed);
99
99
  const systemPrompt = lines.slice(closing + 1).join("\n");
100
100
  return Object.freeze({
101
101
  name,
@@ -109,12 +109,13 @@ function decodeArchetype(name, path, markdown) {
109
109
  ...(network === undefined ? {} : { network }),
110
110
  ...(systemPrompt.length === 0 ? {} : { systemPrompt }),
111
111
  }),
112
+ allowed,
112
113
  });
113
114
  }
114
- export async function listArchetypeDefinitions(input) {
115
+ export async function listArchetypeDefinitions(input = {}) {
115
116
  // Reads still run concurrently, but the reported failure is the first invalid
116
117
  // definition in catalog byte order, not whichever read happened to finish first.
117
- const settled = await Promise.allSettled((await archetypePaths(input.settings)).map(async ({ name, path }) => {
118
+ const settled = await Promise.allSettled((await archetypePaths(input.home)).map(async ({ name, path }) => {
118
119
  try {
119
120
  const definition = decodeArchetype(name, path, await readFile(path, "utf8"));
120
121
  return Object.freeze({
@@ -209,11 +210,9 @@ function admitArchetype(archetype, settings) {
209
210
  }
210
211
  export async function loadArchetype(input) {
211
212
  const name = archetypeName(input.name);
212
- const directory = archetypeDirectory(input.settings);
213
- const missing = () => new AkumaArchetypeError(name, directory === null ? [] : [join(directory, `${name}.md`)], "was not found", "use `keiyaku ls aku/` to list available Akuma");
214
- if (directory === null)
215
- throw missing();
213
+ const directory = archetypeDirectory(input.home);
216
214
  const path = join(directory, `${name}.md`);
215
+ const missing = () => new AkumaArchetypeError(name, [path], "was not found", "use `keiyaku ls aku/` to list available Akuma");
217
216
  let markdown;
218
217
  try {
219
218
  markdown = await readFile(path, "utf8");
@@ -1,7 +1,7 @@
1
1
  import { type SessionFact, type Soul } from "./heart/index.js";
2
2
  import type { AkumaPaths } from "./identity.js";
3
3
  import { type ProviderAdapter } from "./provider.js";
4
- import { type RequestChildLaunch } from "./requests.js";
4
+ import { type UpstreamExecutionPort, type RequestChildLaunch } from "./requests.js";
5
5
  export declare const CONTROL_RESPONSE_MS = 1000;
6
6
  export type BodyLaunch = Readonly<{
7
7
  paths: AkumaPaths;
@@ -12,7 +12,9 @@ export type BodyLaunch = Readonly<{
12
12
  type BodyRuntime = Readonly<{
13
13
  now(): string;
14
14
  spawnChild?(launch: RequestChildLaunch): Promise<void>;
15
+ upstream?: UpstreamExecutionPort;
15
16
  }>;
16
17
  export declare function driveAkumaBody(launch: BodyLaunch, adapter?: ProviderAdapter, runtime?: BodyRuntime): Promise<void>;
17
18
  export declare function spawnAkumaBody(launch: BodyLaunch): Promise<void>;
19
+ export declare function runAkumaBody(upstreamFor: (launch: BodyLaunch) => UpstreamExecutionPort): Promise<void>;
18
20
  export {};
@@ -185,6 +185,7 @@ async function startTurnDrive(input) {
185
185
  bodySequence: input.bodySequence,
186
186
  now: input.now,
187
187
  spawn: input.runtimeSpawn,
188
+ ...(input.upstream === undefined ? {} : { upstream: input.upstream }),
188
189
  signal: input.supervisor.signal,
189
190
  })
190
191
  : null;
@@ -538,6 +539,7 @@ async function runBodyTurns(input) {
538
539
  body: initial ?? "",
539
540
  ...(initial === undefined ? {} : { call: initial }),
540
541
  launchTells,
542
+ ...(runtime.upstream === undefined ? {} : { upstream: runtime.upstream }),
541
543
  now: runtime.now,
542
544
  });
543
545
  if (result.kind === "stopped") {
@@ -601,20 +603,21 @@ export async function spawnAkumaBody(launch) {
601
603
  if (actorId === undefined)
602
604
  throw new Error("Akuma wake has no born soul");
603
605
  const owned = await spawnDetachedProcess({
604
- argv: [process.execPath, ...process.execArgv, fileURLToPath(import.meta.url), encoded],
606
+ argv: [process.execPath, ...process.execArgv, fileURLToPath(new URL("../akuma-body.js", import.meta.url)), encoded],
605
607
  cwd: await launchCwd(launch),
606
608
  env: { ...process.env, KEIYAKU_ACTOR_ID: actorId },
607
609
  log: launch.paths.log,
608
610
  });
609
611
  owned.release();
610
612
  }
611
- async function main() {
613
+ export async function runAkumaBody(upstreamFor) {
612
614
  const encoded = process.argv[2];
613
615
  if (encoded === undefined)
614
616
  throw new TypeError("Akuma body launch payload is missing");
615
617
  const launch = JSON.parse(Buffer.from(encoded, "base64url").toString("utf8"));
616
- await driveAkumaBody(launch);
617
- }
618
- if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
619
- await main();
618
+ await driveAkumaBody(launch, undefined, {
619
+ now: () => new Date().toISOString(),
620
+ spawnChild: spawnAkumaBody,
621
+ upstream: upstreamFor(launch),
622
+ });
620
623
  }
@@ -1,6 +1,7 @@
1
1
  import type { AkuId } from "../identity.js";
2
2
  import type { ResumeCoordinate } from "../coordinate.js";
3
3
  import type { ProviderExecution, ProviderOptions, ReadonlyRestraint } from "../provider-recipe.js";
4
+ import type { AllowedActions } from "../allowed.js";
4
5
  export type { ResumeCoordinate } from "../coordinate.js";
5
6
  export type Confinement = Readonly<{
6
7
  kind: "unconfined";
@@ -29,9 +30,10 @@ export type Soul = Readonly<{
29
30
  cwd: string;
30
31
  origin: AkumaOrigin;
31
32
  confinement: Confinement;
33
+ allowed: AllowedActions;
32
34
  createdAt: string;
33
35
  }>;
34
- export type RequestRecipe = Pick<Soul, "description" | "provider" | "options" | "readonly" | "confinement">;
36
+ export type RequestRecipe = Pick<Soul, "description" | "provider" | "options" | "readonly" | "confinement" | "allowed">;
35
37
  export type BodyEnd = "exited" | "broke-off" | "put-down";
36
38
  export type BodyFact = Readonly<{
37
39
  sequence: number;
@@ -129,26 +131,66 @@ export type TellReceiptInput = Readonly<{
129
131
  }>);
130
132
  export type RequestInput = Readonly<{
131
133
  id: string;
134
+ action: "akuma.call";
132
135
  archetype: string;
133
136
  body: string;
134
137
  cwd?: string;
135
138
  world: string;
136
139
  recipe: RequestRecipe;
140
+ }> | Readonly<{
141
+ id: string;
142
+ action: "akuma.wait";
143
+ targets: readonly AkuId[];
144
+ completion: "any" | "all";
145
+ timeoutMs?: number;
146
+ }> | Readonly<{
147
+ id: string;
148
+ action: "akuma.tell";
149
+ target: AkuId;
150
+ body: string;
151
+ }> | Readonly<{
152
+ id: string;
153
+ action: "akuma.kill";
154
+ targets: readonly AkuId[];
137
155
  }>;
138
- export type RequestFact = RequestInput & Readonly<{
156
+ export type UpstreamRequestService = Readonly<{
157
+ action: "akuma.wait";
158
+ }> | Readonly<{
159
+ action: "akuma.tell";
160
+ target: AkuId;
161
+ tellId: string;
162
+ }> | Readonly<{
163
+ action: "akuma.kill";
164
+ results: readonly Readonly<{
165
+ id: AkuId;
166
+ evidence: KillEvidence;
167
+ }>[];
168
+ }>;
169
+ type AdmittedRequest = RequestInput & Readonly<{
170
+ requester: AkuId;
139
171
  admittedAt: string;
140
- }> & (Readonly<{
172
+ }>;
173
+ export type RequestFact = (AdmittedRequest & Readonly<{
141
174
  state: "admitted";
142
- }> | Readonly<{
175
+ }>) | (Extract<AdmittedRequest, {
176
+ action: "akuma.call";
177
+ }> & Readonly<{
143
178
  state: "reserved";
144
179
  child: AkuId;
145
- }> | Readonly<{
180
+ }>) | (Extract<AdmittedRequest, {
181
+ action: "akuma.call";
182
+ }> & Readonly<{
146
183
  state: "served";
147
184
  child: AkuId;
148
- }> | Readonly<{
185
+ }>) | (Exclude<AdmittedRequest, {
186
+ action: "akuma.call";
187
+ }> & Readonly<{
188
+ state: "served";
189
+ service: UpstreamRequestService;
190
+ }>) | (AdmittedRequest & Readonly<{
149
191
  state: "refused";
150
192
  diagnostic: string;
151
- }> | Readonly<{
193
+ }>) | (AdmittedRequest & Readonly<{
152
194
  state: "voided";
153
195
  evidence: string;
154
196
  }>);
@@ -1,11 +1,11 @@
1
1
  import type { AkuId, AkumaPaths } from "../identity.js";
2
- import type { BodyEnd, BodyFact, ForkPoint, HeartSnapshot, KillFact, RequestFact, RequestInput, SealFact, SessionFact, Soul, TellDeliveryInput, TellFact, TellReceiptInput, TurnEndFact, TurnFact, TurnOutcome, TurnStartFact } from "./facts.js";
2
+ import type { BodyEnd, BodyFact, ForkPoint, HeartSnapshot, KillFact, RequestFact, RequestInput, UpstreamRequestService, SealFact, SessionFact, Soul, TellDeliveryInput, TellFact, TellReceiptInput, TurnEndFact, TurnFact, TurnOutcome, TurnStartFact } from "./facts.js";
3
3
  export type { CallFact } from "./facts.js";
4
4
  import type { ActivityFact } from "./rows.js";
5
5
  import { type ActivityFactSlice } from "./timeline.js";
6
6
  export { HeartAbsentError, HeldAkumaLeash, initializeHeart, isHeartAbsent, probeLeash } from "./storage.js";
7
7
  export { life, lifeAt } from "./facts.js";
8
- export type { AkumaLife, AkumaOrigin, BodyEnd, BodyFact, Confinement, HeartSnapshot, ForkPoint, KillEvidence, KillFact, LeashProbe, PauseFact, RequestFact, RequestInput, RequestRecipe, SealFact, SessionFact, Soul, StopFact, TellFact, TellDelivery, TellDeliveryInput, TellReceiptInput, TurnEndFact, TurnFact, TurnOutcome, TurnStartFact, } from "./facts.js";
8
+ export type { AkumaLife, AkumaOrigin, BodyEnd, BodyFact, Confinement, HeartSnapshot, ForkPoint, KillEvidence, KillFact, LeashProbe, PauseFact, RequestFact, RequestInput, RequestRecipe, UpstreamRequestService, SealFact, SessionFact, Soul, StopFact, TellFact, TellDelivery, TellDeliveryInput, TellReceiptInput, TurnEndFact, TurnFact, TurnOutcome, TurnStartFact, } from "./facts.js";
9
9
  export type { ResumeCoordinate } from "../coordinate.js";
10
10
  export declare function readSoul(paths: AkumaPaths): Promise<Soul | null>;
11
11
  export declare function heartExists(paths: AkumaPaths): Promise<boolean>;
@@ -16,15 +16,10 @@ export declare function appendActivity(paths: AkumaPaths, input: Readonly<{
16
16
  event: unknown;
17
17
  at: string;
18
18
  }>): Promise<number>;
19
- export type ActivitySliceInput = Readonly<{
20
- before?: number;
21
- since?: number;
22
- limit?: number;
23
- }>;
24
19
  export type ActivitySlice = ActivityFactSlice;
25
20
  export type { ActivityFact };
26
21
  export type { TimelineFact } from "./timeline.js";
27
- export declare function activitySlice(paths: AkumaPaths, input?: ActivitySliceInput): Promise<ActivitySlice>;
22
+ export declare function activitySlice(paths: AkumaPaths): Promise<ActivitySlice>;
28
23
  export declare function recordTell(paths: AkumaPaths, tell: Omit<TellFact, "sequence" | "state" | "deliveries">): Promise<Readonly<{
29
24
  kind: "not-born";
30
25
  } | {
@@ -38,6 +33,7 @@ export declare function admitRequest(paths: AkumaPaths, input: RequestInput & Re
38
33
  }>): Promise<RequestFact>;
39
34
  export declare function reserveRequest(paths: AkumaPaths, id: string, child: AkuId): Promise<RequestFact>;
40
35
  export declare function serveRequest(paths: AkumaPaths, id: string, child: AkuId): Promise<RequestFact>;
36
+ export declare function serveUpstreamRequest(paths: AkumaPaths, id: string, service: UpstreamRequestService): Promise<RequestFact>;
41
37
  export declare function refuseRequest(paths: AkumaPaths, id: string, diagnostic: string): Promise<RequestFact>;
42
38
  export declare function voidRequest(paths: AkumaPaths, id: string, evidence: string): Promise<RequestFact>;
43
39
  export declare function readRequest(paths: AkumaPaths, id: string): Promise<RequestFact | null>;
@@ -1,8 +1,9 @@
1
- import { answeredTurnFact, endBodyFact, finishBodyFact, insertActivityFact, insertTurnEndFact, insertTurnStartFact, insertPauseControl, insertRequestFact, insertSessionFact, insertStopControl, killFactForBody, latestBodyFact, latestKillFact, latestSessionFact, lastAnsweredTurnFact, nonterminalRequestFacts, pauseFact, requestFact, sessionFactForCoordinate, stopFact, updateRequestRefused, updateRequestReserved, updateRequestServed, updateRequestVoided, } from "./rows.js";
1
+ import { answeredTurnFact, endBodyFact, finishBodyFact, insertActivityFact, insertTurnEndFact, insertTurnStartFact, insertPauseControl, insertRequestFact, insertSessionFact, insertStopControl, killFactForBody, latestBodyFact, latestKillFact, latestSessionFact, lastAnsweredTurnFact, nonterminalRequestFacts, pauseFact, requestFact, sessionFactForCoordinate, stopFact, updateRequestRefused, updateRequestReserved, updateRequestServed, updateRequestVoided, updateUpstreamRequestServed, } from "./rows.js";
2
2
  import { insertTellDeliveryFact, insertTellFact, insertTellReceiptFact, pendingTellFacts, tellFact, tellIdsForFence, } from "./tells.js";
3
3
  import { activityFactSlice, pruneActivityFacts, } from "./timeline.js";
4
- import { isHeartAbsent, readSealFromLeash, transaction, withHeart } from "./storage.js";
4
+ import { isHeartAbsent, readSealFromLeash, readTransaction, transaction, withHeart } from "./storage.js";
5
5
  import { soulFact } from "./soul.js";
6
+ import { clipAllowedActions } from "../allowed.js";
6
7
  export { HeartAbsentError, HeldAkumaLeash, initializeHeart, isHeartAbsent, probeLeash } from "./storage.js";
7
8
  export { life, lifeAt } from "./facts.js";
8
9
  const ACTIVITY_LIMIT = 5_000;
@@ -46,14 +47,20 @@ export async function appendActivity(paths, input) {
46
47
  return sequence;
47
48
  }));
48
49
  }
49
- export async function activitySlice(paths, input = {}) {
50
- const limit = input.limit ?? ACTIVITY_LIMIT;
51
- return await withHeart(paths, (heart) => activityFactSlice(heart, { ...input, limit }));
50
+ export async function activitySlice(paths) {
51
+ return await withHeart(paths, (heart) => readTransaction(heart, () => activityFactSlice(heart)));
52
52
  }
53
53
  export async function recordTell(paths, tell) {
54
54
  return await withHeart(paths, (heart) => transaction(heart, () => {
55
55
  if (soulFact(heart) === null)
56
56
  return { kind: "not-born" };
57
+ const existing = tellFact(heart, tell.id);
58
+ if (existing !== null) {
59
+ if (existing.body !== tell.body || existing.recordedAt !== tell.recordedAt) {
60
+ throw new Error(`tell ${tell.id} reused different input`);
61
+ }
62
+ return { kind: "recorded", tell: existing };
63
+ }
57
64
  const sequence = insertTellFact(heart, tell);
58
65
  pruneActivityFacts(heart, ACTIVITY_LIMIT);
59
66
  return { kind: "recorded", tell: { sequence, ...tell, state: "pending", deliveries: [] } };
@@ -83,21 +90,55 @@ export async function recordTellReceipt(paths, input) {
83
90
  }));
84
91
  }
85
92
  function sameRequestInput(fact, input) {
86
- return fact.id === input.id
87
- && fact.archetype === input.archetype
88
- && fact.body === input.body
89
- && fact.cwd === input.cwd
90
- && fact.world === input.world
91
- && JSON.stringify(fact.recipe) === JSON.stringify(input.recipe);
93
+ if (fact.id !== input.id || fact.action !== input.action)
94
+ return false;
95
+ if (fact.action === "akuma.call" && input.action === "akuma.call") {
96
+ return fact.archetype === input.archetype
97
+ && fact.body === input.body
98
+ && fact.cwd === input.cwd
99
+ && fact.world === input.world
100
+ && JSON.stringify(fact.recipe) === JSON.stringify(input.recipe);
101
+ }
102
+ if (fact.action === "akuma.wait" && input.action === "akuma.wait") {
103
+ return fact.completion === input.completion
104
+ && fact.timeoutMs === input.timeoutMs
105
+ && JSON.stringify(fact.targets) === JSON.stringify(input.targets);
106
+ }
107
+ if (fact.action === "akuma.tell" && input.action === "akuma.tell") {
108
+ return fact.target === input.target && fact.body === input.body;
109
+ }
110
+ return fact.action === "akuma.kill" && input.action === "akuma.kill"
111
+ && JSON.stringify(fact.targets) === JSON.stringify(input.targets);
92
112
  }
93
113
  export async function admitRequest(paths, input) {
94
114
  return await withHeart(paths, (heart) => transaction(heart, () => {
95
- insertRequestFact(heart, input);
115
+ const soul = soulFact(heart);
116
+ if (soul === null)
117
+ throw new Error("Akuma request admission requires a born Soul");
118
+ const action = input.action;
119
+ const parentAllowed = soul.allowed;
120
+ const permitted = action === "akuma.wait" || parentAllowed.includes(action);
121
+ const normalized = input.action === "akuma.call"
122
+ ? {
123
+ ...input,
124
+ recipe: {
125
+ ...input.recipe,
126
+ allowed: permitted ? clipAllowedActions(input.recipe.allowed, parentAllowed) : input.recipe.allowed,
127
+ },
128
+ }
129
+ : input;
130
+ insertRequestFact(heart, {
131
+ ...normalized,
132
+ requester: soul.id,
133
+ admittedAt: input.admittedAt,
134
+ ...(permitted ? {} : { refusal: `not-allowed: ${action}` }),
135
+ });
96
136
  const fact = requestFact(heart, input.id);
97
137
  if (fact === null)
98
138
  throw new Error(`Akuma request ${input.id} was not admitted`);
99
- if (!sameRequestInput(fact, input))
139
+ if (fact.requester !== soul.id || !sameRequestInput(fact, normalized)) {
100
140
  throw new Error(`Akuma request ${input.id} reused different input`);
141
+ }
101
142
  return fact;
102
143
  }));
103
144
  }
@@ -106,6 +147,8 @@ export async function reserveRequest(paths, id, child) {
106
147
  const before = requestFact(heart, id);
107
148
  if (before === null)
108
149
  throw new Error(`unknown Akuma request ${id}`);
150
+ if (before.action !== "akuma.call")
151
+ throw new Error(`Akuma request ${id} cannot reserve a child`);
109
152
  if (before.state === "admitted")
110
153
  updateRequestReserved(heart, id, child);
111
154
  else if ((before.state !== "reserved" && before.state !== "served") || before.child !== child) {
@@ -119,6 +162,8 @@ export async function serveRequest(paths, id, child) {
119
162
  const before = requestFact(heart, id);
120
163
  if (before === null)
121
164
  throw new Error(`unknown Akuma request ${id}`);
165
+ if (before.action !== "akuma.call")
166
+ throw new Error(`Akuma request ${id} cannot serve a child`);
122
167
  if (before.state === "reserved" && before.child === child)
123
168
  updateRequestServed(heart, id, child);
124
169
  else if (before.state !== "served" || before.child !== child) {
@@ -127,6 +172,22 @@ export async function serveRequest(paths, id, child) {
127
172
  return requestFact(heart, id);
128
173
  }));
129
174
  }
175
+ export async function serveUpstreamRequest(paths, id, service) {
176
+ return await withHeart(paths, (heart) => transaction(heart, () => {
177
+ const before = requestFact(heart, id);
178
+ if (before === null)
179
+ throw new Error(`unknown Akuma request ${id}`);
180
+ if (before.action === "akuma.call" || service.action !== before.action) {
181
+ throw new Error(`Akuma request ${id} has a mismatched service reference`);
182
+ }
183
+ if (before.state === "admitted")
184
+ updateUpstreamRequestServed(heart, id, service);
185
+ else if (before.state !== "served" || JSON.stringify(before.service) !== JSON.stringify(service)) {
186
+ throw new Error(`Akuma request ${id} cannot be served as ${service.action}`);
187
+ }
188
+ return requestFact(heart, id);
189
+ }));
190
+ }
130
191
  export async function refuseRequest(paths, id, diagnostic) {
131
192
  return await withHeart(paths, (heart) => transaction(heart, () => {
132
193
  const before = requestFact(heart, id);
@@ -234,7 +295,7 @@ export async function finishBodyIfIdle(paths, input) {
234
295
  }
235
296
  export async function readHeart(paths) {
236
297
  try {
237
- return await withHeart(paths, (heart) => ({
298
+ return await withHeart(paths, (heart) => readTransaction(heart, () => ({
238
299
  soul: soulFact(heart),
239
300
  latestBody: latestBodyFact(heart),
240
301
  latestSession: latestSessionFact(heart),
@@ -242,7 +303,7 @@ export async function readHeart(paths) {
242
303
  latestKill: latestKillFact(heart),
243
304
  stop: stopFact(heart),
244
305
  pause: pauseFact(heart),
245
- }));
306
+ })));
246
307
  }
247
308
  catch (error) {
248
309
  if (isHeartAbsent(error)) {
@@ -42,14 +42,13 @@ export type CallRow = Readonly<{
42
42
  export type RequestRow = Readonly<{
43
43
  sequence: number;
44
44
  id: string;
45
- archetype: string;
46
- body: string;
47
- cwd: string | null;
48
- world: string;
49
- recipe_json: string;
45
+ requester: string;
46
+ action: string;
47
+ payload_json: string;
50
48
  admitted_at: string;
51
49
  state: RequestFact["state"];
52
50
  child: string | null;
51
+ service_json: string | null;
53
52
  diagnostic: string | null;
54
53
  evidence: string | null;
55
54
  }>;
@@ -102,12 +101,15 @@ export declare function insertActivityFact(database: DatabaseSync, input: Readon
102
101
  at: string;
103
102
  }>): number;
104
103
  export declare function insertRequestFact(database: DatabaseSync, input: RequestInput & Readonly<{
104
+ requester: AkuId;
105
105
  admittedAt: string;
106
+ refusal?: string;
106
107
  }>): void;
107
108
  export declare function requestFact(database: DatabaseSync, id: string): RequestFact | null;
108
109
  export declare function nonterminalRequestFacts(database: DatabaseSync): readonly RequestFact[];
109
110
  export declare function updateRequestReserved(database: DatabaseSync, id: string, child: AkuId): void;
110
111
  export declare function updateRequestServed(database: DatabaseSync, id: string, child: AkuId): void;
112
+ export declare function updateUpstreamRequestServed(database: DatabaseSync, id: string, service: unknown): void;
111
113
  export declare function updateRequestRefused(database: DatabaseSync, id: string, diagnostic: string): void;
112
114
  export declare function updateRequestVoided(database: DatabaseSync, id: string, evidence: string): void;
113
115
  export declare function insertStopControl(database: DatabaseSync, bodySequence: number, at: string): void;
@@ -7,6 +7,66 @@ function parsed(value) {
7
7
  throw new Error("Akuma authority contains non-text JSON");
8
8
  return JSON.parse(value);
9
9
  }
10
+ function object(value) {
11
+ return value !== null && typeof value === "object" && !Array.isArray(value)
12
+ ? value
13
+ : null;
14
+ }
15
+ function exactKeys(value, expected) {
16
+ const actual = Object.keys(value).sort();
17
+ return actual.length === expected.length && actual.every((key, index) => key === expected[index]);
18
+ }
19
+ const KILL_EVIDENCE = [
20
+ "already-killed",
21
+ "already-stopped",
22
+ "hung",
23
+ "killed",
24
+ "untidy",
25
+ "unavailable",
26
+ ];
27
+ function decodeRequestService(value, input) {
28
+ const service = object(value);
29
+ if (service === null || service.action !== input.action) {
30
+ throw new Error("Akuma authority contains a mismatched request service reference");
31
+ }
32
+ if (input.action === "akuma.wait") {
33
+ if (!exactKeys(service, ["action"])) {
34
+ throw new Error("Akuma authority contains an invalid wait service reference");
35
+ }
36
+ return { action: input.action };
37
+ }
38
+ if (input.action === "akuma.tell") {
39
+ if (!exactKeys(service, ["action", "target", "tellId"])
40
+ || service.target !== input.target
41
+ || service.tellId !== input.id) {
42
+ throw new Error("Akuma authority contains an invalid tell service reference");
43
+ }
44
+ return { action: input.action, target: input.target, tellId: input.id };
45
+ }
46
+ if (!exactKeys(service, ["action", "results"]) || !Array.isArray(service.results)) {
47
+ throw new Error("Akuma authority contains an invalid kill service reference");
48
+ }
49
+ let targetIndex = -1;
50
+ const results = service.results.map((value) => {
51
+ const result = object(value);
52
+ if (result === null
53
+ || !exactKeys(result, ["evidence", "id"])
54
+ || typeof result.id !== "string"
55
+ || !KILL_EVIDENCE.includes(result.evidence)) {
56
+ throw new Error("Akuma authority contains an invalid kill service reference");
57
+ }
58
+ const index = input.targets.indexOf(result.id);
59
+ if (index <= targetIndex) {
60
+ throw new Error("Akuma authority contains an invalid kill service reference");
61
+ }
62
+ targetIndex = index;
63
+ return {
64
+ id: result.id,
65
+ evidence: result.evidence,
66
+ };
67
+ });
68
+ return { action: input.action, results };
69
+ }
10
70
  function resumeCoordinate(value) {
11
71
  const coordinate = decodeResumeCoordinate(value);
12
72
  if (coordinate === null)
@@ -67,18 +127,39 @@ export function decodeCallRow(row) {
67
127
  return { kind: "call", sequence: row.sequence, turnSequence: row.turn_sequence, body: row.body, at: row.at };
68
128
  }
69
129
  export function decodeRequestRow(row) {
130
+ if (!["akuma.call", "akuma.wait", "akuma.tell", "akuma.kill"].includes(row.action)) {
131
+ throw new Error(`Akuma authority contains an unknown request action: ${row.action}`);
132
+ }
133
+ const payload = parsed(row.payload_json);
70
134
  const input = {
71
135
  id: row.id,
72
- archetype: row.archetype,
73
- body: row.body,
74
- ...(row.cwd === null ? {} : { cwd: row.cwd }),
75
- world: row.world,
76
- recipe: parsed(row.recipe_json),
136
+ requester: row.requester,
137
+ action: row.action,
138
+ ...payload,
77
139
  admittedAt: row.admitted_at,
78
140
  };
79
- if (row.state === "reserved" || row.state === "served") {
141
+ if (row.state === "reserved") {
142
+ if (input.action !== "akuma.call" || row.child === null) {
143
+ throw new Error("Akuma authority contains an invalid reserved request");
144
+ }
80
145
  return { ...input, state: row.state, child: row.child };
81
146
  }
147
+ if (row.state === "served") {
148
+ if (input.action === "akuma.call") {
149
+ if (row.child === null)
150
+ throw new Error("Akuma authority contains a served call without a child");
151
+ return { ...input, state: row.state, child: row.child };
152
+ }
153
+ if (row.service_json === null) {
154
+ throw new Error("Akuma authority contains a served request without a service reference");
155
+ }
156
+ const service = decodeRequestService(parsed(row.service_json), input);
157
+ return {
158
+ ...input,
159
+ state: row.state,
160
+ service,
161
+ };
162
+ }
82
163
  if (row.state === "refused")
83
164
  return { ...input, state: row.state, diagnostic: row.diagnostic };
84
165
  if (row.state === "voided")
@@ -129,11 +210,13 @@ export function insertActivityFact(database, input) {
129
210
  .run(sequence, input.turnSequence, encodeActivityEvent(input.event), input.at);
130
211
  return sequence;
131
212
  }
132
- const REQUEST_COLUMNS = `sequence, id, archetype, body, cwd, world, recipe_json, admitted_at,
133
- state, child, diagnostic, evidence`;
213
+ const REQUEST_COLUMNS = `sequence, id, requester, action, payload_json, admitted_at,
214
+ state, child, service_json, diagnostic, evidence`;
134
215
  export function insertRequestFact(database, input) {
135
- database.prepare(`INSERT OR IGNORE INTO requests(id, archetype, body, cwd, world, recipe_json, admitted_at, state)
136
- VALUES (?, ?, ?, ?, ?, ?, ?, 'admitted')`).run(input.id, input.archetype, input.body, input.cwd ?? null, input.world, json(input.recipe), input.admittedAt);
216
+ const { id, action, requester, admittedAt, refusal, ...payload } = input;
217
+ database.prepare(`INSERT OR IGNORE INTO requests(
218
+ id, requester, action, payload_json, admitted_at, state, diagnostic
219
+ ) VALUES (?, ?, ?, ?, ?, ?, ?)`).run(id, requester, action, json(payload), admittedAt, refusal === undefined ? "admitted" : "refused", refusal ?? null);
137
220
  }
138
221
  export function requestFact(database, id) {
139
222
  const row = database.prepare(`SELECT ${REQUEST_COLUMNS} FROM requests WHERE id = ?`).get(id);
@@ -152,12 +235,16 @@ export function updateRequestServed(database, id, child) {
152
235
  database.prepare(`UPDATE requests SET state = 'served', child = ?
153
236
  WHERE id = ? AND state = 'reserved'`).run(child, id);
154
237
  }
238
+ export function updateUpstreamRequestServed(database, id, service) {
239
+ database.prepare(`UPDATE requests SET state = 'served', service_json = ?
240
+ WHERE id = ? AND state = 'admitted' AND action != 'akuma.call'`).run(json(service), id);
241
+ }
155
242
  export function updateRequestRefused(database, id, diagnostic) {
156
243
  database.prepare(`UPDATE requests SET state = 'refused', diagnostic = ?
157
244
  WHERE id = ? AND state = 'admitted'`).run(diagnostic, id);
158
245
  }
159
246
  export function updateRequestVoided(database, id, evidence) {
160
- database.prepare(`UPDATE requests SET state = 'voided', child = NULL, evidence = ?
247
+ database.prepare(`UPDATE requests SET state = 'voided', child = NULL, service_json = NULL, evidence = ?
161
248
  WHERE id = ? AND state IN ('admitted', 'reserved')`).run(evidence, id);
162
249
  }
163
250
  export function insertStopControl(database, bodySequence, at) {
@@ -1,5 +1,5 @@
1
1
  import type { DatabaseSync } from "node:sqlite";
2
2
  export declare function assertHeartSchemaVersion(database: DatabaseSync): void;
3
3
  export declare function assertLeashSchemaVersion(database: DatabaseSync): void;
4
- export declare const HEART_SCHEMA = "\n CREATE TABLE IF NOT EXISTS akuma_schema (\n singleton INTEGER PRIMARY KEY CHECK (singleton = 1),\n version INTEGER NOT NULL CHECK (version = 14)\n ) STRICT;\n INSERT OR IGNORE INTO akuma_schema(singleton, version) VALUES (1, 14);\n CREATE TABLE IF NOT EXISTS soul (\n singleton INTEGER PRIMARY KEY CHECK (singleton = 1),\n soul_json TEXT NOT NULL CHECK (json_valid(soul_json))\n ) STRICT;\n CREATE TABLE IF NOT EXISTS bodies (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n leash_taken_at TEXT NOT NULL,\n hung_diagnostic TEXT,\n hung_at TEXT,\n end TEXT CHECK (end IN ('exited', 'broke-off', 'put-down')),\n ended_at TEXT,\n CHECK ((hung_diagnostic IS NULL AND hung_at IS NULL) OR (hung_diagnostic IS NOT NULL AND hung_at IS NOT NULL AND end IS NULL))\n ) STRICT;\n CREATE TABLE IF NOT EXISTS sessions (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n provider TEXT NOT NULL,\n coordinate_json TEXT NOT NULL CHECK (json_valid(coordinate_json)),\n cwd TEXT NOT NULL,\n options_json TEXT NOT NULL CHECK (json_valid(options_json)),\n admitted_at TEXT NOT NULL\n ) STRICT;\n CREATE TABLE IF NOT EXISTS turns (\n sequence INTEGER PRIMARY KEY REFERENCES timeline(sequence) ON DELETE CASCADE,\n body_sequence INTEGER NOT NULL REFERENCES bodies(sequence),\n started_at TEXT NOT NULL,\n end_sequence INTEGER UNIQUE REFERENCES timeline(sequence) ON DELETE SET NULL,\n outcome TEXT CHECK (outcome IN ('answered', 'failed')),\n history_id TEXT UNIQUE,\n session_json TEXT CHECK (session_json IS NULL OR json_valid(session_json)),\n answer TEXT,\n diagnostic TEXT,\n completed_at TEXT,\n CHECK (\n (outcome = 'answered' AND session_json IS NOT NULL AND answer IS NOT NULL AND diagnostic IS NULL)\n OR (outcome = 'failed' AND history_id IS NULL AND session_json IS NULL AND answer IS NULL AND diagnostic IS NOT NULL)\n OR (outcome IS NULL AND end_sequence IS NULL AND history_id IS NULL AND session_json IS NULL AND answer IS NULL AND diagnostic IS NULL AND completed_at IS NULL)\n )\n ) STRICT;\n CREATE TABLE IF NOT EXISTS timeline (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n kind TEXT NOT NULL CHECK (kind IN ('turn-start', 'call', 'activity', 'tell', 'turn-end'))\n ) STRICT;\n CREATE TABLE IF NOT EXISTS calls (\n sequence INTEGER PRIMARY KEY REFERENCES timeline(sequence) ON DELETE CASCADE,\n turn_sequence INTEGER NOT NULL REFERENCES turns(sequence) ON DELETE CASCADE,\n body TEXT NOT NULL,\n at TEXT NOT NULL\n ) STRICT;\n CREATE TABLE IF NOT EXISTS activity (\n sequence INTEGER PRIMARY KEY REFERENCES timeline(sequence) ON DELETE CASCADE,\n turn_sequence INTEGER NOT NULL REFERENCES turns(sequence) ON DELETE CASCADE,\n event_json TEXT NOT NULL CHECK (json_valid(event_json)),\n at TEXT NOT NULL\n ) STRICT;\n CREATE TABLE IF NOT EXISTS tells (\n id TEXT PRIMARY KEY,\n sequence INTEGER NOT NULL UNIQUE REFERENCES timeline(sequence) ON DELETE CASCADE,\n body TEXT NOT NULL,\n recorded_at TEXT NOT NULL\n ) STRICT;\n CREATE TABLE IF NOT EXISTS tell_deliveries (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n tell_id TEXT NOT NULL REFERENCES tells(id) ON DELETE CASCADE,\n route TEXT NOT NULL CHECK (route IN ('launch', 'live')),\n turn_sequence INTEGER NOT NULL REFERENCES turns(sequence) ON DELETE CASCADE,\n fence TEXT NOT NULL,\n receipt TEXT CHECK (receipt IN ('unavailable', 'required')),\n delivered_at TEXT NOT NULL,\n CHECK ((route = 'launch' AND receipt IS NULL) OR (route = 'live' AND receipt IS NOT NULL)),\n UNIQUE (tell_id, turn_sequence, fence)\n ) STRICT;\n CREATE TABLE IF NOT EXISTS tell_receipts (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n evidence TEXT NOT NULL CHECK (evidence IN ('exact', 'fence')),\n tell_id TEXT REFERENCES tells(id) ON DELETE CASCADE,\n turn_sequence INTEGER REFERENCES turns(sequence) ON DELETE CASCADE,\n fence TEXT,\n kind TEXT NOT NULL,\n received_at TEXT NOT NULL,\n CHECK (\n (evidence = 'exact' AND tell_id IS NOT NULL AND turn_sequence IS NULL AND fence IS NULL)\n OR (evidence = 'fence' AND tell_id IS NULL AND turn_sequence IS NOT NULL AND fence IS NOT NULL)\n )\n ) STRICT;\n CREATE UNIQUE INDEX IF NOT EXISTS tell_receipts_exact\n ON tell_receipts(tell_id, kind) WHERE evidence = 'exact';\n CREATE UNIQUE INDEX IF NOT EXISTS tell_receipts_fence\n ON tell_receipts(turn_sequence, fence, kind) WHERE evidence = 'fence';\n CREATE TABLE IF NOT EXISTS requests (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n id TEXT NOT NULL UNIQUE,\n archetype TEXT NOT NULL,\n body TEXT NOT NULL,\n cwd TEXT,\n world TEXT NOT NULL,\n recipe_json TEXT NOT NULL CHECK (json_valid(recipe_json)),\n admitted_at TEXT NOT NULL,\n state TEXT NOT NULL CHECK (state IN ('admitted', 'reserved', 'served', 'refused', 'voided')),\n child TEXT,\n diagnostic TEXT,\n evidence TEXT,\n CHECK (\n (state = 'admitted' AND child IS NULL AND diagnostic IS NULL AND evidence IS NULL)\n OR (state IN ('reserved', 'served') AND child IS NOT NULL AND diagnostic IS NULL AND evidence IS NULL)\n OR (state = 'refused' AND child IS NULL AND diagnostic IS NOT NULL AND evidence IS NULL)\n OR (state = 'voided' AND child IS NULL AND diagnostic IS NULL AND evidence IS NOT NULL)\n )\n ) STRICT;\n CREATE TABLE IF NOT EXISTS control (\n kind TEXT PRIMARY KEY CHECK (kind IN ('stop', 'pause')),\n value_json TEXT NOT NULL CHECK (json_valid(value_json)),\n at TEXT NOT NULL\n ) STRICT;\n CREATE TABLE IF NOT EXISTS kills (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n body_sequence INTEGER NOT NULL UNIQUE REFERENCES bodies(sequence),\n evidence TEXT NOT NULL CHECK (evidence = 'killed'),\n at TEXT NOT NULL\n ) STRICT;\n";
4
+ export declare const HEART_SCHEMA = "\n CREATE TABLE IF NOT EXISTS akuma_schema (\n singleton INTEGER PRIMARY KEY CHECK (singleton = 1),\n version INTEGER NOT NULL CHECK (version = 16)\n ) STRICT;\n INSERT OR IGNORE INTO akuma_schema(singleton, version) VALUES (1, 16);\n CREATE TABLE IF NOT EXISTS soul (\n singleton INTEGER PRIMARY KEY CHECK (singleton = 1),\n soul_json TEXT NOT NULL CHECK (json_valid(soul_json))\n ) STRICT;\n CREATE TABLE IF NOT EXISTS bodies (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n leash_taken_at TEXT NOT NULL,\n hung_diagnostic TEXT,\n hung_at TEXT,\n end TEXT CHECK (end IN ('exited', 'broke-off', 'put-down')),\n ended_at TEXT,\n CHECK ((hung_diagnostic IS NULL AND hung_at IS NULL) OR (hung_diagnostic IS NOT NULL AND hung_at IS NOT NULL AND end IS NULL))\n ) STRICT;\n CREATE TABLE IF NOT EXISTS sessions (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n provider TEXT NOT NULL,\n coordinate_json TEXT NOT NULL CHECK (json_valid(coordinate_json)),\n cwd TEXT NOT NULL,\n options_json TEXT NOT NULL CHECK (json_valid(options_json)),\n admitted_at TEXT NOT NULL\n ) STRICT;\n CREATE TABLE IF NOT EXISTS turns (\n sequence INTEGER PRIMARY KEY REFERENCES timeline(sequence) ON DELETE CASCADE,\n body_sequence INTEGER NOT NULL REFERENCES bodies(sequence),\n started_at TEXT NOT NULL,\n end_sequence INTEGER UNIQUE REFERENCES timeline(sequence) ON DELETE SET NULL,\n outcome TEXT CHECK (outcome IN ('answered', 'failed')),\n history_id TEXT UNIQUE,\n session_json TEXT CHECK (session_json IS NULL OR json_valid(session_json)),\n answer TEXT,\n diagnostic TEXT,\n completed_at TEXT,\n CHECK (\n (outcome = 'answered' AND session_json IS NOT NULL AND answer IS NOT NULL AND diagnostic IS NULL)\n OR (outcome = 'failed' AND history_id IS NULL AND session_json IS NULL AND answer IS NULL AND diagnostic IS NOT NULL)\n OR (outcome IS NULL AND end_sequence IS NULL AND history_id IS NULL AND session_json IS NULL AND answer IS NULL AND diagnostic IS NULL AND completed_at IS NULL)\n )\n ) STRICT;\n CREATE TABLE IF NOT EXISTS timeline (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n kind TEXT NOT NULL CHECK (kind IN ('turn-start', 'call', 'activity', 'tell', 'turn-end'))\n ) STRICT;\n CREATE TABLE IF NOT EXISTS calls (\n sequence INTEGER PRIMARY KEY REFERENCES timeline(sequence) ON DELETE CASCADE,\n turn_sequence INTEGER NOT NULL REFERENCES turns(sequence) ON DELETE CASCADE,\n body TEXT NOT NULL,\n at TEXT NOT NULL\n ) STRICT;\n CREATE TABLE IF NOT EXISTS activity (\n sequence INTEGER PRIMARY KEY REFERENCES timeline(sequence) ON DELETE CASCADE,\n turn_sequence INTEGER NOT NULL REFERENCES turns(sequence) ON DELETE CASCADE,\n event_json TEXT NOT NULL CHECK (json_valid(event_json)),\n at TEXT NOT NULL\n ) STRICT;\n CREATE TABLE IF NOT EXISTS tells (\n id TEXT PRIMARY KEY,\n sequence INTEGER NOT NULL UNIQUE REFERENCES timeline(sequence) ON DELETE CASCADE,\n body TEXT NOT NULL,\n recorded_at TEXT NOT NULL\n ) STRICT;\n CREATE TABLE IF NOT EXISTS tell_deliveries (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n tell_id TEXT NOT NULL REFERENCES tells(id) ON DELETE CASCADE,\n route TEXT NOT NULL CHECK (route IN ('launch', 'live')),\n turn_sequence INTEGER NOT NULL REFERENCES turns(sequence) ON DELETE CASCADE,\n fence TEXT NOT NULL,\n receipt TEXT CHECK (receipt IN ('unavailable', 'required')),\n delivered_at TEXT NOT NULL,\n CHECK ((route = 'launch' AND receipt IS NULL) OR (route = 'live' AND receipt IS NOT NULL)),\n UNIQUE (tell_id, turn_sequence, fence)\n ) STRICT;\n CREATE TABLE IF NOT EXISTS tell_receipts (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n evidence TEXT NOT NULL CHECK (evidence IN ('exact', 'fence')),\n tell_id TEXT REFERENCES tells(id) ON DELETE CASCADE,\n turn_sequence INTEGER REFERENCES turns(sequence) ON DELETE CASCADE,\n fence TEXT,\n kind TEXT NOT NULL,\n received_at TEXT NOT NULL,\n CHECK (\n (evidence = 'exact' AND tell_id IS NOT NULL AND turn_sequence IS NULL AND fence IS NULL)\n OR (evidence = 'fence' AND tell_id IS NULL AND turn_sequence IS NOT NULL AND fence IS NOT NULL)\n )\n ) STRICT;\n CREATE UNIQUE INDEX IF NOT EXISTS tell_receipts_exact\n ON tell_receipts(tell_id, kind) WHERE evidence = 'exact';\n CREATE UNIQUE INDEX IF NOT EXISTS tell_receipts_fence\n ON tell_receipts(turn_sequence, fence, kind) WHERE evidence = 'fence';\n CREATE TABLE IF NOT EXISTS requests (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n id TEXT NOT NULL UNIQUE,\n requester TEXT NOT NULL,\n action TEXT NOT NULL CHECK (action IN ('akuma.call', 'akuma.wait', 'akuma.tell', 'akuma.kill')),\n payload_json TEXT NOT NULL CHECK (json_valid(payload_json)),\n admitted_at TEXT NOT NULL,\n state TEXT NOT NULL CHECK (state IN ('admitted', 'reserved', 'served', 'refused', 'voided')),\n child TEXT,\n service_json TEXT CHECK (service_json IS NULL OR json_valid(service_json)),\n diagnostic TEXT,\n evidence TEXT,\n CHECK (\n (state = 'admitted' AND child IS NULL AND service_json IS NULL AND diagnostic IS NULL AND evidence IS NULL)\n OR (state = 'reserved' AND action = 'akuma.call'\n AND child IS NOT NULL AND service_json IS NULL\n AND diagnostic IS NULL AND evidence IS NULL)\n OR (state = 'served'\n AND ((action = 'akuma.call' AND child IS NOT NULL AND service_json IS NULL)\n OR (action != 'akuma.call' AND child IS NULL AND service_json IS NOT NULL))\n AND diagnostic IS NULL AND evidence IS NULL)\n OR (state = 'refused' AND child IS NULL AND service_json IS NULL AND diagnostic IS NOT NULL AND evidence IS NULL)\n OR (state = 'voided' AND child IS NULL AND service_json IS NULL AND diagnostic IS NULL AND evidence IS NOT NULL)\n )\n ) STRICT;\n CREATE TABLE IF NOT EXISTS control (\n kind TEXT PRIMARY KEY CHECK (kind IN ('stop', 'pause')),\n value_json TEXT NOT NULL CHECK (json_valid(value_json)),\n at TEXT NOT NULL\n ) STRICT;\n CREATE TABLE IF NOT EXISTS kills (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n body_sequence INTEGER NOT NULL UNIQUE REFERENCES bodies(sequence),\n evidence TEXT NOT NULL CHECK (evidence = 'killed'),\n at TEXT NOT NULL\n ) STRICT;\n";
5
5
  export declare const LEASH_SCHEMA = "\n PRAGMA journal_mode=DELETE;\n CREATE TABLE IF NOT EXISTS leash_schema (\n singleton INTEGER PRIMARY KEY CHECK (singleton = 1),\n version INTEGER NOT NULL CHECK (version = 4)\n ) STRICT;\n INSERT OR IGNORE INTO leash_schema(singleton, version) VALUES (1, 4);\n CREATE TABLE IF NOT EXISTS seal (\n singleton INTEGER PRIMARY KEY CHECK (singleton = 1),\n evidence TEXT NOT NULL,\n at TEXT NOT NULL\n ) STRICT;\n";