@astrosheep/keiyaku 4.5.18 → 4.5.20

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 (115) hide show
  1. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +11 -4
  2. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +33 -14
  3. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-task/SKILL.md +0 -1
  4. package/build/src/akuma/akuma-handle.js +10 -10
  5. package/build/src/akuma/akuma-observe.d.ts +2 -0
  6. package/build/src/akuma/akuma-observe.js +35 -25
  7. package/build/src/akuma/akuma-product.d.ts +1 -0
  8. package/build/src/akuma/akuma-product.js +3 -0
  9. package/build/src/akuma/akuma.d.ts +20 -3
  10. package/build/src/akuma/akuma.js +3 -5
  11. package/build/src/akuma/allowed.d.ts +20 -0
  12. package/build/src/akuma/allowed.js +11 -0
  13. package/build/src/akuma/archetype.js +3 -0
  14. package/build/src/akuma/body.js +11 -36
  15. package/build/src/akuma/call-request.d.ts +3 -0
  16. package/build/src/akuma/call-request.js +12 -4
  17. package/build/src/akuma/fleet-execution.js +35 -11
  18. package/build/src/akuma/fleet-observation.d.ts +54 -0
  19. package/build/src/akuma/fleet-request.js +2 -1
  20. package/build/src/akuma/heart/index.d.ts +11 -17
  21. package/build/src/akuma/heart/index.js +91 -81
  22. package/build/src/akuma/heart/request-authority.js +19 -19
  23. package/build/src/akuma/heart/schema.d.ts +1 -1
  24. package/build/src/akuma/heart/schema.js +19 -6
  25. package/build/src/akuma/heart/storage.d.ts +2 -4
  26. package/build/src/akuma/heart/storage.js +63 -63
  27. package/build/src/akuma/heart/tells.d.ts +9 -19
  28. package/build/src/akuma/heart/tells.js +76 -76
  29. package/build/src/akuma/heart/timeline.d.ts +7 -1
  30. package/build/src/akuma/heart/timeline.js +62 -53
  31. package/build/src/akuma/provider-recipe.d.ts +1 -0
  32. package/build/src/akuma/provider-recipe.js +5 -0
  33. package/build/src/akuma/providers/acp/index.js +3 -0
  34. package/build/src/akuma/providers/claude/index.js +3 -0
  35. package/build/src/akuma/providers/codex-app-server/index.js +8 -0
  36. package/build/src/akuma/providers/grok-build/index.js +3 -0
  37. package/build/src/akuma/providers/opencode-sdk/index.js +2 -0
  38. package/build/src/akuma/providers/pi/index.js +2 -0
  39. package/build/src/akuma/request-observation.d.ts +45 -0
  40. package/build/src/akuma/request-observation.js +162 -0
  41. package/build/src/akuma/request-rendezvous.d.ts +6 -3
  42. package/build/src/akuma/request-rendezvous.js +75 -14
  43. package/build/src/akuma/request-serve.js +27 -4
  44. package/build/src/akuma/request-wire.d.ts +5 -0
  45. package/build/src/akuma/request-wire.js +2 -0
  46. package/build/src/akuma-body.js +3 -0
  47. package/build/src/cli/commands/akuma-invoke.d.ts +1 -1
  48. package/build/src/cli/commands/akuma-invoke.js +1 -1
  49. package/build/src/cli/commands/akuma.js +13 -8
  50. package/build/src/cli/commands/contract-help.d.ts +15 -15
  51. package/build/src/cli/commands/contract-help.js +28 -15
  52. package/build/src/cli/commands/contract-invoke.d.ts +3 -0
  53. package/build/src/cli/commands/contract-invoke.js +25 -11
  54. package/build/src/cli/commands/install.d.ts +1 -1
  55. package/build/src/cli/commands/install.js +1 -1
  56. package/build/src/cli/commands/task.js +18 -18
  57. package/build/src/cli/invoke.d.ts +3 -0
  58. package/build/src/cli/invoke.js +58 -23
  59. package/build/src/cli/parse.d.ts +1 -0
  60. package/build/src/cli/parse.js +21 -4
  61. package/build/src/cli/render/akuma-activity.d.ts +1 -0
  62. package/build/src/cli/render/akuma-activity.js +1 -0
  63. package/build/src/cli/render/akuma.js +4 -1
  64. package/build/src/cli/render/execution-progress.d.ts +4 -0
  65. package/build/src/cli/render/execution-progress.js +55 -0
  66. package/build/src/cli/render/receipt.js +1 -1
  67. package/build/src/cli/runtime.js +27 -1
  68. package/build/src/git/hooks.d.ts +13 -1
  69. package/build/src/git/hooks.js +2 -1
  70. package/build/src/git/integration.js +4 -4
  71. package/build/src/git/process.d.ts +2 -0
  72. package/build/src/git/process.js +106 -0
  73. package/build/src/git/read-observation.js +79 -56
  74. package/build/src/git/target-placement.d.ts +13 -2
  75. package/build/src/git/target-placement.js +119 -68
  76. package/build/src/git/verification-environment.d.ts +10 -0
  77. package/build/src/git/verification-environment.js +281 -0
  78. package/build/src/library/akuma-creation.js +5 -3
  79. package/build/src/library/audit.d.ts +2 -0
  80. package/build/src/library/audit.js +1 -0
  81. package/build/src/library/composition.d.ts +1 -0
  82. package/build/src/library/contract-execution.d.ts +2 -0
  83. package/build/src/library/contract-execution.js +15 -1
  84. package/build/src/library/contract-forwarding.d.ts +4 -0
  85. package/build/src/library/contract-forwarding.js +3 -0
  86. package/build/src/library/contract-handle.d.ts +7 -0
  87. package/build/src/library/contract-handle.js +25 -0
  88. package/build/src/library/contract-operations.d.ts +5 -0
  89. package/build/src/library/contract-operations.js +20 -0
  90. package/build/src/library/execution-result.d.ts +2 -2
  91. package/build/src/library/execution.d.ts +8 -0
  92. package/build/src/library/execution.js +102 -0
  93. package/build/src/library/keiyaku.d.ts +2 -0
  94. package/build/src/library/mutation.js +15 -7
  95. package/build/src/plugin/akuma-signals.js +1 -1
  96. package/build/src/plugin/public.d.ts +2 -2
  97. package/build/src/plugin/runtime.d.ts +1 -3
  98. package/build/src/plugin/runtime.js +91 -88
  99. package/build/src/protocol/completion.js +28 -10
  100. package/build/src/protocol/execution-observation.d.ts +185 -0
  101. package/build/src/protocol/execution-observation.js +45 -0
  102. package/build/src/protocol/intent.d.ts +3 -2
  103. package/build/src/protocol/intent.js +44 -34
  104. package/build/src/protocol/placement.js +47 -29
  105. package/build/src/protocol/progress.d.ts +5 -1
  106. package/build/src/protocol/progress.js +10 -1
  107. package/build/src/protocol/result-codec.js +18 -4
  108. package/build/src/runtime/proc/run.d.ts +16 -4
  109. package/build/src/runtime/proc/run.js +64 -22
  110. package/build/src/runtime/proc/windows-launch.exe +0 -0
  111. package/build/src/verification/execution.d.ts +11 -2
  112. package/build/src/verification/execution.js +167 -42
  113. package/build/src/verification/observation.d.ts +45 -0
  114. package/build/src/verification/observation.js +30 -0
  115. package/package.json +7 -5
@@ -4,7 +4,7 @@ import { refuseRequest, reserveRequest } from "./heart/index.js";
4
4
  import { archetypeName, parseAkuId } from "./identity.js";
5
5
  import { publishAkuma } from "./publication.js";
6
6
  import { decodeProviderOptions, decodeReadonlyRestraint } from "./provider-recipe.js";
7
- import { decodeProviderExecution } from "./providers/index.js";
7
+ import { decodeProviderExecution, resolveProviderExecution } from "./providers/index.js";
8
8
  import { requestBodyCommand } from "./request-rendezvous.js";
9
9
  import { eraseRequestCommand, } from "./request-wire.js";
10
10
  import { z } from "zod";
@@ -67,6 +67,7 @@ const akumaCallPayloadSchema = z
67
67
  world: absolutePathSchema,
68
68
  archetype: archetypeSchema,
69
69
  body: z.string().optional(),
70
+ awaitAsleep: z.literal(true).optional(),
70
71
  cwd: absolutePathSchema.optional(),
71
72
  recipe: akumaCallRecipeSchema,
72
73
  })
@@ -86,17 +87,24 @@ async function executeAkumaCall(request, facts, capabilities) {
86
87
  await refuseRequest(paths, facts.id, `request world ${requestWorld} does not match ${world}`);
87
88
  throw new Error(`request world ${requestWorld} does not match ${world}`);
88
89
  }
90
+ const selected = await resolveProviderExecution(request.recipe.provider);
91
+ const admission = selected.adapter.admitOptions(request.recipe.options);
92
+ if (admission.kind === "refused") {
93
+ await refuseRequest(paths, facts.id, admission.diagnostic);
94
+ throw new Error(admission.diagnostic);
95
+ }
89
96
  const recipe = {
90
97
  ...(request.recipe.description === undefined ? {} : { description: request.recipe.description }),
91
98
  allowed: clipAllowedActions(request.recipe.allowed, parent.allowed),
92
- provider: request.recipe.provider,
93
- options: request.recipe.options,
94
- ...(request.recipe.readonly === undefined ? {} : { readonly: request.recipe.readonly }),
99
+ provider: selected.execution,
100
+ options: admission.options,
101
+ ...(admission.readonly === undefined ? {} : { readonly: admission.readonly }),
95
102
  };
96
103
  const published = await publishAkuma({
97
104
  worldPath: world,
98
105
  archetype: request.archetype,
99
106
  signal: facts.signal,
107
+ ...(request.awaitAsleep === undefined ? {} : { awaitAsleep: request.awaitAsleep }),
100
108
  launch: async (allocated) => {
101
109
  if (!facts.admissionOpen())
102
110
  throw new Error("body closed request admission");
@@ -1,6 +1,6 @@
1
1
  import { AkumaNotBornError, defaultWaitComplete } from "./akuma.js";
2
2
  import { createAkumaProduct } from "./akuma-product.js";
3
- import { readBudgetedStatus } from "./akuma.js";
3
+ import { readBudgetedStatus, readWaitComplete } from "./akuma-observe.js";
4
4
  import { NO_DISPATCH_ASSOCIATION } from "./dispatch-association.js";
5
5
  import { EMPTY_CREATED_TASK_OBSERVATION } from "../task/created-observation.js";
6
6
  import { fleetResultSchemas, parseAkumaObservation, } from "./fleet-observation.js";
@@ -45,6 +45,29 @@ async function observeWaitRound(path, ids, signal) {
45
45
  }
46
46
  return { statuses, unobserved };
47
47
  }
48
+ async function probeWaitRound(input) {
49
+ let observed = 0;
50
+ let complete = 0;
51
+ for (const id of input.ids) {
52
+ input.signal?.throwIfAborted();
53
+ try {
54
+ if (await readWaitComplete(input.path, id))
55
+ complete += 1;
56
+ observed += 1;
57
+ }
58
+ catch (error) {
59
+ if (input.ids.length <= 1 || error instanceof AkumaNotBornError)
60
+ throw error;
61
+ // Plural wait retries unreadable members; final rendering owns diagnostics.
62
+ }
63
+ }
64
+ return observed > 0 && (input.completion === "any" ? complete > 0 : complete === input.ids.length);
65
+ }
66
+ function roundComplete(round, completion) {
67
+ const settled = round.statuses.map(defaultWaitComplete);
68
+ return (settled.length > 0 &&
69
+ (completion === "any" ? settled.some(Boolean) : round.unobserved.length === 0 && settled.every(Boolean)));
70
+ }
48
71
  function delay(milliseconds, signal) {
49
72
  return new Promise((resolve, reject) => {
50
73
  let timer;
@@ -67,16 +90,17 @@ function delay(milliseconds, signal) {
67
90
  export async function executeWaitAkuma(input) {
68
91
  const deadline = input.timeoutMs === undefined ? undefined : performance.now() + input.timeoutMs;
69
92
  for (;;) {
70
- const round = await observeWaitRound(input.path, input.ids, input.signal);
71
- const settled = round.statuses.map(defaultWaitComplete);
72
- const completed = round.statuses.length > 0 &&
73
- (input.completion === "any" ? settled.some(Boolean) : round.unobserved.length === 0 && settled.every(Boolean));
74
- if (completed || (deadline !== undefined && performance.now() >= deadline)) {
75
- return fleetResultSchemas.wait.parse({
76
- completion: input.completion,
77
- observations: round.statuses.map(akumaOnlyObservation),
78
- unobserved: round.unobserved,
79
- });
93
+ if ((deadline !== undefined && performance.now() >= deadline) || (await probeWaitRound(input))) {
94
+ const round = await observeWaitRound(input.path, input.ids, input.signal);
95
+ input.signal?.throwIfAborted();
96
+ // The probe is not a completion receipt. Judge the actual returned values.
97
+ if (roundComplete(round, input.completion) || (deadline !== undefined && performance.now() >= deadline)) {
98
+ return fleetResultSchemas.wait.parse({
99
+ completion: input.completion,
100
+ observations: round.statuses.map(akumaOnlyObservation),
101
+ unobserved: round.unobserved,
102
+ });
103
+ }
80
104
  }
81
105
  await delay(deadline === undefined ? POLL_MS : Math.min(POLL_MS, Math.max(0, deadline - performance.now())), input.signal);
82
106
  }
@@ -10,6 +10,24 @@ declare const akumaObservationSchema: z.ZodObject<{
10
10
  asleep: "asleep";
11
11
  stranded: "stranded";
12
12
  }>;
13
+ allowed: z.ZodPipe<z.ZodArray<z.ZodEnum<{
14
+ "task.add": "task.add";
15
+ "task.addDocument": "task.addDocument";
16
+ "task.compose": "task.compose";
17
+ "task.done": "task.done";
18
+ "task.drop": "task.drop";
19
+ "task.hold": "task.hold";
20
+ "task.resume": "task.resume";
21
+ "task.start": "task.start";
22
+ "task.stop": "task.stop";
23
+ "task.update": "task.update";
24
+ "akuma.call": "akuma.call";
25
+ "akuma.kill": "akuma.kill";
26
+ "akuma.tell": "akuma.tell";
27
+ "contract.audit": "contract.audit";
28
+ "contract.deliver": "contract.deliver";
29
+ "contract.review": "contract.review";
30
+ }>>, z.ZodTransform<import("./allowed.js").AllowedActions, ("task.add" | "task.addDocument" | "task.compose" | "task.done" | "task.drop" | "task.hold" | "task.resume" | "task.start" | "task.stop" | "task.update" | "akuma.call" | "akuma.kill" | "akuma.tell" | "contract.audit" | "contract.deliver" | "contract.review")[]>>;
13
31
  readonly: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
14
32
  enforcement: z.ZodLiteral<"native">;
15
33
  }, z.core.$strict>, z.ZodObject<{
@@ -446,6 +464,24 @@ declare const akumaWaitResultSchema: z.ZodObject<{
446
464
  asleep: "asleep";
447
465
  stranded: "stranded";
448
466
  }>;
467
+ allowed: z.ZodPipe<z.ZodArray<z.ZodEnum<{
468
+ "task.add": "task.add";
469
+ "task.addDocument": "task.addDocument";
470
+ "task.compose": "task.compose";
471
+ "task.done": "task.done";
472
+ "task.drop": "task.drop";
473
+ "task.hold": "task.hold";
474
+ "task.resume": "task.resume";
475
+ "task.start": "task.start";
476
+ "task.stop": "task.stop";
477
+ "task.update": "task.update";
478
+ "akuma.call": "akuma.call";
479
+ "akuma.kill": "akuma.kill";
480
+ "akuma.tell": "akuma.tell";
481
+ "contract.audit": "contract.audit";
482
+ "contract.deliver": "contract.deliver";
483
+ "contract.review": "contract.review";
484
+ }>>, z.ZodTransform<import("./allowed.js").AllowedActions, ("task.add" | "task.addDocument" | "task.compose" | "task.done" | "task.drop" | "task.hold" | "task.resume" | "task.start" | "task.stop" | "task.update" | "akuma.call" | "akuma.kill" | "akuma.tell" | "contract.audit" | "contract.deliver" | "contract.review")[]>>;
449
485
  readonly: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
450
486
  enforcement: z.ZodLiteral<"native">;
451
487
  }, z.core.$strict>, z.ZodObject<{
@@ -1001,6 +1037,24 @@ export declare const fleetResultSchemas: {
1001
1037
  asleep: "asleep";
1002
1038
  stranded: "stranded";
1003
1039
  }>;
1040
+ allowed: z.ZodPipe<z.ZodArray<z.ZodEnum<{
1041
+ "task.add": "task.add";
1042
+ "task.addDocument": "task.addDocument";
1043
+ "task.compose": "task.compose";
1044
+ "task.done": "task.done";
1045
+ "task.drop": "task.drop";
1046
+ "task.hold": "task.hold";
1047
+ "task.resume": "task.resume";
1048
+ "task.start": "task.start";
1049
+ "task.stop": "task.stop";
1050
+ "task.update": "task.update";
1051
+ "akuma.call": "akuma.call";
1052
+ "akuma.kill": "akuma.kill";
1053
+ "akuma.tell": "akuma.tell";
1054
+ "contract.audit": "contract.audit";
1055
+ "contract.deliver": "contract.deliver";
1056
+ "contract.review": "contract.review";
1057
+ }>>, z.ZodTransform<import("./allowed.js").AllowedActions, ("task.add" | "task.addDocument" | "task.compose" | "task.done" | "task.drop" | "task.hold" | "task.resume" | "task.start" | "task.stop" | "task.update" | "akuma.call" | "akuma.kill" | "akuma.tell" | "contract.audit" | "contract.deliver" | "contract.review")[]>>;
1004
1058
  readonly: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
1005
1059
  enforcement: z.ZodLiteral<"native">;
1006
1060
  }, z.core.$strict>, z.ZodObject<{
@@ -100,7 +100,8 @@ export function fleetRequestProtocol(action) {
100
100
  decodeResult: (result) => decodedFleetResult(action, result),
101
101
  decodeReference: (reference) => decodeFleetService(action, reference),
102
102
  isPermitted: (allowed) => action === "akuma.wait" ||
103
- ((action === "akuma.tell" || action === "akuma.tell-answer") && allowed.includes("akuma.tell")),
103
+ ((action === "akuma.tell" || action === "akuma.tell-answer") && allowed.includes("akuma.tell")) ||
104
+ (action === "akuma.kill" && allowed.includes("akuma.kill")),
104
105
  };
105
106
  }
106
107
  export function fleetRequestCommand(action, port) {
@@ -2,7 +2,7 @@ import type { AkumaPaths } from "../identity.js";
2
2
  import type { BodyEnd, BodyFact, ForkPoint, HeartSnapshot, KillFact, 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
- import { type ActivityFactSlice } from "./timeline.js";
5
+ import { type StatusFactInput, type ActivityFactSlice } from "./timeline.js";
6
6
  export { HeartAbsentError, HeldAkumaLeash, classifyHeartSchema, initializeHeart, isHeartAbsent, probeLeash, } from "./storage.js";
7
7
  export { admitRequest, beginRequest, isRequestInputConflict, readNonterminalRequests, readRequest, refuseRequest, reserveRequest, serveRequest, serveUpstreamRequest, unproveRequest, voidRequest, } from "./request-authority.js";
8
8
  export { AkumaBusyError, life, lifeAt, projectTell } from "./facts.js";
@@ -22,6 +22,12 @@ export type ActivitySlice = ActivityFactSlice;
22
22
  export type { ActivityFact };
23
23
  export type { TimelineFact } from "./timeline.js";
24
24
  export declare function activitySlice(paths: AkumaPaths): Promise<ActivitySlice>;
25
+ export declare function readStatusFacts(paths: AkumaPaths, input: StatusFactInput): Promise<ActivitySlice["rows"]>;
26
+ type LifeSnapshot = Pick<HeartSnapshot, "soul" | "latestBody" | "latestSession" | "latestKill"> & Readonly<{
27
+ hasPendingTell: boolean;
28
+ }>;
29
+ /** Lifecycle probes do not load narration, Tell bodies, or delivery histories. */
30
+ export declare function readLifeSnapshot(paths: AkumaPaths): Promise<LifeSnapshot>;
25
31
  export declare function recordTell(paths: AkumaPaths, tell: Omit<TellFact, "sequence" | "state" | "deliveries" | "binding">, options?: Readonly<{
26
32
  interrupt?: boolean;
27
33
  }>): Promise<Readonly<{
@@ -43,29 +49,17 @@ export declare function decidePendingTellDisposition(paths: AkumaPaths, input: R
43
49
  handoff: boolean;
44
50
  }>): Promise<PendingTellDisposition | null>;
45
51
  export declare function readOpenPendingTellDisposition(paths: AkumaPaths): Promise<PendingTellDisposition | null>;
46
- export declare function resolvePendingTellDisposition(paths: AkumaPaths, bodySequence: number, at: string): Promise<void>;
47
- /**
48
- * Heart-owned disposition custody proof. Sequence growth, spawn resolution, and
49
- * an unqualified held leash are never proof. Returns the successor Body sequence
50
- * when that exact Body took the frozen Tell-id snapshot by delivery, or true when
51
- * the snapshot is already fully settled (no longer pending).
52
- */
53
- export declare function provePendingTellDispositionCustody(paths: AkumaPaths, disposition: PendingTellDisposition): Promise<Readonly<{
54
- kind: "proven";
55
- successorBodySequence?: number;
56
- } | {
57
- kind: "unproven";
58
- }>>;
59
- export declare function recordUndeliveredPendingTells(paths: AkumaPaths, at: string, tellIds: readonly string[]): Promise<void>;
52
+ /** Prove and consume the persisted decision in one transaction, never a caller's stale member list. */
53
+ export declare function resolvePendingTellDisposition(paths: AkumaPaths, bodySequence: number, at: string, outcome?: "custody" | "undelivered"): Promise<boolean>;
60
54
  export declare function readKill(paths: AkumaPaths, bodySequence: number): Promise<KillFact | null>;
61
- export declare function requestStop(paths: AkumaPaths, at: string): Promise<Readonly<{
55
+ export declare function requestStop(paths: AkumaPaths, at: string, signal?: AbortSignal): Promise<Readonly<{
62
56
  kind: "requested";
63
57
  body: BodyFact;
64
58
  }> | Readonly<{
65
59
  kind: "already-killed" | "already-stopped";
66
60
  body: BodyFact;
67
61
  }>>;
68
- export declare function requestPause(paths: AkumaPaths, at: string): Promise<Readonly<{
62
+ export declare function requestPause(paths: AkumaPaths, at: string, signal?: AbortSignal): Promise<Readonly<{
69
63
  kind: "not-born";
70
64
  } | {
71
65
  kind: "requested";
@@ -1,10 +1,10 @@
1
1
  import { parsePublicHistoryId } from "../identity.js";
2
2
  import { answeredTurnFact, endBodyFact, finishBodyFact, insertActivityFact, insertTurnEndFact, insertTurnStartFact, insertPauseControl, insertSessionFact, insertStopControl, killFactForBody, latestBodyFact, latestKillFact, latestSessionFact, lastAnsweredTurnFact, pauseFact, sessionFactForCoordinate, stopFact, turnFact, } from "./rows.js";
3
- import { insertTellDeliveryFact, dispositionSnapshotProven, insertTellBindingFact, insertTellDispositionSnapshot, insertTellFact, insertTellReceiptFact, insertUndeliveredTellReceipts, latestOpenTellDisposition, openBoundTurns, openTellDispositionIds, pendingTellFacts, resolveTellDispositionSnapshot, successorBodyHoldingDisposition, tellFact, tellIdsForFence, } from "./tells.js";
4
- import { activityFactSlice, lastActivityAt as readLastActivityAt, pruneActivityFacts, } from "./timeline.js";
5
- import { isHeartAbsent, readSealFromLeash, readTransaction, transaction, withHeart, withReadOnlyHeart, } from "./storage.js";
3
+ import { insertTellDeliveryFact, dispositionSnapshotProven, insertTellBindingFact, insertTellDispositionSnapshot, insertTellFact, insertTellReceiptFact, insertUndeliveredTellReceipts, latestOpenTellDisposition, openBoundTurns, openTellDispositionIds, pendingTellFacts, hasPendingTell, resolveTellDispositionSnapshot, tellDispositionResolved, tellFact, tellIdsForFence, } from "./tells.js";
4
+ import { activityFactSlice, statusFacts, lastActivityAt as readLastActivityAt, pruneActivityFacts, } from "./timeline.js";
5
+ import { isHeartAbsent, readSealFromLeash, withHeartTransaction, withReadOnlyHeart } from "./storage.js";
6
6
  import { soulFact } from "./soul.js";
7
- import { AkumaBusyError } from "./facts.js";
7
+ import { AkumaBusyError, HeartAuthorityCorruptionError } from "./facts.js";
8
8
  export { HeartAbsentError, HeldAkumaLeash, classifyHeartSchema, initializeHeart, isHeartAbsent, probeLeash, } from "./storage.js";
9
9
  export { admitRequest, beginRequest, isRequestInputConflict, readNonterminalRequests, readRequest, refuseRequest, reserveRequest, serveRequest, serveUpstreamRequest, unproveRequest, voidRequest, } from "./request-authority.js";
10
10
  export { AkumaBusyError, life, lifeAt, projectTell } from "./facts.js";
@@ -12,7 +12,7 @@ export { drainPendingTells } from "./tells.js";
12
12
  const ACTIVITY_LIMIT = 5_000;
13
13
  export async function readSoul(paths) {
14
14
  try {
15
- return await withHeart(paths, soulFact);
15
+ return await withReadOnlyHeart(paths, soulFact);
16
16
  }
17
17
  catch (error) {
18
18
  if (isHeartAbsent(error))
@@ -22,7 +22,7 @@ export async function readSoul(paths) {
22
22
  }
23
23
  export async function heartExists(paths) {
24
24
  try {
25
- return await withHeart(paths, () => true);
25
+ return await withReadOnlyHeart(paths, () => true);
26
26
  }
27
27
  catch (error) {
28
28
  if (isHeartAbsent(error))
@@ -41,23 +41,43 @@ export async function readSeal(paths) {
41
41
  }
42
42
  }
43
43
  export async function recordSession(paths, input) {
44
- return await withHeart(paths, (heart) => ({ sequence: insertSessionFact(heart, input), ...input }));
44
+ return await withHeartTransaction(paths, (heart) => ({ sequence: insertSessionFact(heart, input), ...input }));
45
45
  }
46
46
  export async function appendActivity(paths, input) {
47
- return await withHeart(paths, (heart) => transaction(heart, () => {
47
+ return await withHeartTransaction(paths, (heart) => {
48
48
  const sequence = insertActivityFact(heart, input);
49
49
  pruneActivityFacts(heart, ACTIVITY_LIMIT);
50
50
  return sequence;
51
- }));
51
+ });
52
52
  }
53
53
  export async function activitySlice(paths) {
54
- return await withHeart(paths, (heart) => readTransaction(heart, () => activityFactSlice(heart)));
54
+ return await withReadOnlyHeart(paths, (heart) => activityFactSlice(heart));
55
+ }
56
+ export async function readStatusFacts(paths, input) {
57
+ return await withReadOnlyHeart(paths, (heart) => statusFacts(heart, input));
58
+ }
59
+ /** Lifecycle probes do not load narration, Tell bodies, or delivery histories. */
60
+ export async function readLifeSnapshot(paths) {
61
+ try {
62
+ return await withReadOnlyHeart(paths, (heart) => ({
63
+ soul: soulFact(heart),
64
+ latestBody: latestBodyFact(heart),
65
+ latestSession: latestSessionFact(heart),
66
+ latestKill: latestKillFact(heart),
67
+ hasPendingTell: hasPendingTell(heart),
68
+ }));
69
+ }
70
+ catch (error) {
71
+ if (isHeartAbsent(error))
72
+ return { soul: null, latestBody: null, latestSession: null, latestKill: null, hasPendingTell: false };
73
+ throw error;
74
+ }
55
75
  }
56
76
  function sameTellInput(existing, tell) {
57
77
  return (existing.body === tell.body && existing.recordedAt === tell.recordedAt && existing.schemaJson === tell.schemaJson);
58
78
  }
59
79
  export async function recordTell(paths, tell, options = {}) {
60
- return await withHeart(paths, (heart) => transaction(heart, () => {
80
+ return await withHeartTransaction(paths, (heart) => {
61
81
  if (soulFact(heart) === null)
62
82
  return { kind: "not-born" };
63
83
  const existing = tellFact(heart, tell.id);
@@ -73,37 +93,40 @@ export async function recordTell(paths, tell, options = {}) {
73
93
  const sequence = insertTellFact(heart, tell);
74
94
  pruneActivityFacts(heart, ACTIVITY_LIMIT);
75
95
  return { kind: "recorded", tell: { sequence, ...tell, state: "pending", deliveries: [] } };
76
- }));
96
+ });
77
97
  }
78
98
  export async function readTell(paths, id) {
79
- return await withHeart(paths, (heart) => readTransaction(heart, () => tellFact(heart, id)));
99
+ return await withReadOnlyHeart(paths, (heart) => tellFact(heart, id));
80
100
  }
81
101
  export async function recordTellDeliveries(paths, inputs) {
82
- await withHeart(paths, (heart) => transaction(heart, () => {
102
+ await withHeartTransaction(paths, (heart) => {
83
103
  for (const input of inputs) {
84
104
  const current = tellFact(heart, input.tellId);
85
105
  if (current === null)
86
106
  throw new Error(`unknown tell ${input.tellId}`);
87
107
  }
108
+ let changed = false;
88
109
  for (const input of inputs)
89
- insertTellDeliveryFact(heart, input);
90
- pruneActivityFacts(heart, ACTIVITY_LIMIT);
91
- }));
110
+ changed = insertTellDeliveryFact(heart, input) || changed;
111
+ pruneActivityFacts(heart, ACTIVITY_LIMIT, changed);
112
+ });
92
113
  }
93
114
  export async function recordTellReceipt(paths, input) {
94
- await withHeart(paths, (heart) => transaction(heart, () => {
115
+ await withHeartTransaction(paths, (heart) => {
95
116
  const tellIds = input.evidence === "exact" ? [input.tellId] : tellIdsForFence(heart, input.turnSequence, input.fence);
96
117
  if (tellIds.length === 0)
97
118
  throw new Error("tell receipt has no delivery mapping");
98
- insertTellReceiptFact(heart, input);
99
- pruneActivityFacts(heart, ACTIVITY_LIMIT);
100
- }));
119
+ const changed = insertTellReceiptFact(heart, input);
120
+ pruneActivityFacts(heart, ACTIVITY_LIMIT, changed);
121
+ });
101
122
  }
102
123
  export async function decidePendingTellDisposition(paths, input) {
103
- return await withHeart(paths, (heart) => transaction(heart, () => {
124
+ return await withHeartTransaction(paths, (heart) => {
104
125
  const body = latestBodyFact(heart);
105
126
  if (body === null || body.sequence !== input.bodySequence)
106
127
  return null;
128
+ if (tellDispositionResolved(heart, input.bodySequence) === true)
129
+ return null;
107
130
  const existing = openTellDispositionIds(heart, input.bodySequence);
108
131
  if (existing !== null)
109
132
  return { bodySequence: input.bodySequence, tellIds: existing };
@@ -122,46 +145,33 @@ export async function decidePendingTellDisposition(paths, input) {
122
145
  insertTellDispositionSnapshot(heart, input.bodySequence, tellIds, input.at);
123
146
  pruneActivityFacts(heart, ACTIVITY_LIMIT);
124
147
  return { bodySequence: input.bodySequence, tellIds };
125
- }));
148
+ });
126
149
  }
127
150
  export async function readOpenPendingTellDisposition(paths) {
128
- return await withHeart(paths, (heart) => readTransaction(heart, () => latestOpenTellDisposition(heart)));
129
- }
130
- export async function resolvePendingTellDisposition(paths, bodySequence, at) {
131
- await withHeart(paths, (heart) => transaction(heart, () => {
151
+ return await withReadOnlyHeart(paths, (heart) => latestOpenTellDisposition(heart));
152
+ }
153
+ /** Prove and consume the persisted decision in one transaction, never a caller's stale member list. */
154
+ export async function resolvePendingTellDisposition(paths, bodySequence, at, outcome = "custody") {
155
+ return await withHeartTransaction(paths, (heart) => {
156
+ if (tellDispositionResolved(heart, bodySequence) === true)
157
+ return true;
158
+ const tellIds = openTellDispositionIds(heart, bodySequence);
159
+ if (tellIds === null)
160
+ throw new HeartAuthorityCorruptionError(`Akuma disposition ${bodySequence} is missing`);
161
+ if (outcome === "undelivered")
162
+ insertUndeliveredTellReceipts(heart, tellIds, at);
163
+ if (!dispositionSnapshotProven(heart, tellIds))
164
+ return false;
132
165
  resolveTellDispositionSnapshot(heart, bodySequence, at);
133
- pruneActivityFacts(heart, ACTIVITY_LIMIT);
134
- }));
135
- }
136
- /**
137
- * Heart-owned disposition custody proof. Sequence growth, spawn resolution, and
138
- * an unqualified held leash are never proof. Returns the successor Body sequence
139
- * when that exact Body took the frozen Tell-id snapshot by delivery, or true when
140
- * the snapshot is already fully settled (no longer pending).
141
- */
142
- export async function provePendingTellDispositionCustody(paths, disposition) {
143
- return await withHeart(paths, (heart) => readTransaction(heart, () => {
144
- if (!dispositionSnapshotProven(heart, disposition))
145
- return { kind: "unproven" };
146
- const successorBodySequence = successorBodyHoldingDisposition(heart, disposition);
147
- return successorBodySequence === null
148
- ? { kind: "proven" }
149
- : { kind: "proven", successorBodySequence };
150
- }));
151
- }
152
- export async function recordUndeliveredPendingTells(paths, at, tellIds) {
153
- if (tellIds.length === 0)
154
- return;
155
- await withHeart(paths, (heart) => transaction(heart, () => {
156
- insertUndeliveredTellReceipts(heart, tellIds, at);
157
- pruneActivityFacts(heart, ACTIVITY_LIMIT);
158
- }));
166
+ pruneActivityFacts(heart, ACTIVITY_LIMIT, true);
167
+ return true;
168
+ });
159
169
  }
160
170
  export async function readKill(paths, bodySequence) {
161
- return await withHeart(paths, (heart) => killFactForBody(heart, bodySequence));
171
+ return await withReadOnlyHeart(paths, (heart) => killFactForBody(heart, bodySequence));
162
172
  }
163
- export async function requestStop(paths, at) {
164
- return await withHeart(paths, (heart) => transaction(heart, () => {
173
+ export async function requestStop(paths, at, signal) {
174
+ return await withHeartTransaction(paths, (heart) => {
165
175
  const body = latestBodyFact(heart);
166
176
  if (body === null)
167
177
  throw new Error("Akuma has no Body to kill");
@@ -175,10 +185,10 @@ export async function requestStop(paths, at) {
175
185
  }
176
186
  insertStopControl(heart, body.sequence, at);
177
187
  return { kind: "requested", body };
178
- }));
188
+ }, signal);
179
189
  }
180
- export async function requestPause(paths, at) {
181
- return await withHeart(paths, (heart) => transaction(heart, () => {
190
+ export async function requestPause(paths, at, signal) {
191
+ return await withHeartTransaction(paths, (heart) => {
182
192
  if (soulFact(heart) === null)
183
193
  return { kind: "not-born" };
184
194
  const body = latestBodyFact(heart);
@@ -186,45 +196,45 @@ export async function requestPause(paths, at) {
186
196
  throw new Error("Akuma has no Body to interrupt");
187
197
  insertPauseControl(heart, at);
188
198
  return { kind: "requested", body };
189
- }));
199
+ }, signal);
190
200
  }
191
201
  export async function stopRequested(paths, bodySequence) {
192
- return await withHeart(paths, (heart) => {
202
+ return await withReadOnlyHeart(paths, (heart) => {
193
203
  const target = stopFact(heart);
194
204
  return target !== null && (bodySequence === undefined || target.bodySequence === bodySequence);
195
205
  });
196
206
  }
197
207
  export async function pauseRequested(paths, bodySequence) {
198
- return await withHeart(paths, (heart) => {
208
+ return await withReadOnlyHeart(paths, (heart) => {
199
209
  const target = pauseFact(heart);
200
210
  return target !== null && (bodySequence === undefined || target.bodySequence === bodySequence);
201
211
  });
202
212
  }
203
213
  export async function breakBody(paths, input) {
204
- await withHeart(paths, (heart) => endBodyFact(heart, input));
214
+ await withHeartTransaction(paths, (heart) => endBodyFact(heart, input));
205
215
  }
206
216
  export async function beginTurn(paths, input) {
207
- return await withHeart(paths, (heart) => transaction(heart, () => {
217
+ return await withHeartTransaction(paths, (heart) => {
208
218
  const fact = insertTurnStartFact(heart, input);
209
219
  pruneActivityFacts(heart, ACTIVITY_LIMIT);
210
220
  return fact;
211
- }));
221
+ });
212
222
  }
213
223
  export async function bindTellsToTurn(paths, input) {
214
- await withHeart(paths, (heart) => transaction(heart, () => {
224
+ await withHeartTransaction(paths, (heart) => {
215
225
  for (const tellId of input.tellIds) {
216
226
  if (tellFact(heart, tellId) === null)
217
227
  throw new Error(`unknown tell ${tellId}`);
218
228
  insertTellBindingFact(heart, { tellId, turnSequence: input.turnSequence, boundAt: input.boundAt });
219
229
  }
220
230
  pruneActivityFacts(heart, ACTIVITY_LIMIT);
221
- }));
231
+ });
222
232
  }
223
233
  export async function readOpenBoundTurns(paths, bodySequence) {
224
- return await withHeart(paths, (heart) => readTransaction(heart, () => openBoundTurns(heart, bodySequence)));
234
+ return await withReadOnlyHeart(paths, (heart) => openBoundTurns(heart, bodySequence));
225
235
  }
226
236
  export async function failOpenBoundTurns(paths, input) {
227
- return await withHeart(paths, (heart) => transaction(heart, () => {
237
+ return await withHeartTransaction(paths, (heart) => {
228
238
  const sequences = openBoundTurns(heart, input.bodySequence);
229
239
  for (const turnSequence of sequences) {
230
240
  insertTurnEndFact(heart, {
@@ -234,22 +244,22 @@ export async function failOpenBoundTurns(paths, input) {
234
244
  completedAt: input.completedAt,
235
245
  });
236
246
  }
237
- pruneActivityFacts(heart, ACTIVITY_LIMIT);
247
+ pruneActivityFacts(heart, ACTIVITY_LIMIT, sequences.length > 0);
238
248
  return sequences;
239
- }));
249
+ });
240
250
  }
241
251
  export async function readTurn(paths, sequence) {
242
- return await withHeart(paths, (heart) => readTransaction(heart, () => turnFact(heart, sequence)));
252
+ return await withReadOnlyHeart(paths, (heart) => turnFact(heart, sequence));
243
253
  }
244
254
  export async function endTurn(paths, input) {
245
- return await withHeart(paths, (heart) => transaction(heart, () => {
255
+ return await withHeartTransaction(paths, (heart) => {
246
256
  const fact = insertTurnEndFact(heart, { kind: "turn-end", ...input });
247
- pruneActivityFacts(heart, ACTIVITY_LIMIT);
257
+ pruneActivityFacts(heart, ACTIVITY_LIMIT, true);
248
258
  return fact;
249
- }));
259
+ });
250
260
  }
251
261
  export async function finishBodyIfIdle(paths, input) {
252
- return await withHeart(paths, (heart) => transaction(heart, () => {
262
+ return await withHeartTransaction(paths, (heart) => {
253
263
  if (stopFact(heart)?.bodySequence === input.sequence || pauseFact(heart)?.bodySequence === input.sequence) {
254
264
  endBodyFact(heart, { ...input, end: "put-down" });
255
265
  return { kind: "controlled" };
@@ -259,11 +269,11 @@ export async function finishBodyIfIdle(paths, input) {
259
269
  return { kind: "pending", tells: pending.map((tell) => tell.id) };
260
270
  finishBodyFact(heart, input);
261
271
  return { kind: "finished" };
262
- }));
272
+ });
263
273
  }
264
274
  export async function readHeart(paths) {
265
275
  try {
266
- return await withReadOnlyHeart(paths, (heart) => readTransaction(heart, () => ({
276
+ return await withReadOnlyHeart(paths, (heart) => ({
267
277
  soul: soulFact(heart),
268
278
  latestBody: latestBodyFact(heart),
269
279
  latestSession: latestSessionFact(heart),
@@ -272,7 +282,7 @@ export async function readHeart(paths) {
272
282
  stop: stopFact(heart),
273
283
  pause: pauseFact(heart),
274
284
  lastActivityAt: readLastActivityAt(heart),
275
- })));
285
+ }));
276
286
  }
277
287
  catch (error) {
278
288
  if (isHeartAbsent(error)) {
@@ -291,14 +301,14 @@ export async function readHeart(paths) {
291
301
  }
292
302
  }
293
303
  export async function readLastAnsweredTurn(paths) {
294
- return await withHeart(paths, lastAnsweredTurnFact);
304
+ return await withReadOnlyHeart(paths, lastAnsweredTurnFact);
295
305
  }
296
306
  export async function readForkPoint(paths, historyId) {
297
307
  const turnSequence = parsePublicHistoryId(historyId);
298
308
  if (turnSequence === null)
299
309
  return null;
300
310
  try {
301
- return await withHeart(paths, (heart) => {
311
+ return await withReadOnlyHeart(paths, (heart) => {
302
312
  const turn = answeredTurnFact(heart, turnSequence);
303
313
  const outcome = turn?.end?.outcome;
304
314
  if (outcome?.kind !== "answered" || outcome.historyId === undefined)