@astrosheep/keiyaku 4.6.13 → 4.6.14

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 (56) hide show
  1. package/build/plugins/claude/skills/keiyaku/SKILL.md +3 -0
  2. package/build/plugins/claude/skills/keiyaku-akuma/SKILL.md +20 -0
  3. package/build/plugins/codex/skills/keiyaku/SKILL.md +3 -0
  4. package/build/plugins/codex/skills/keiyaku-akuma/SKILL.md +20 -0
  5. package/build/plugins/opencode/skills/keiyaku/SKILL.md +3 -0
  6. package/build/plugins/opencode/skills/keiyaku-akuma/SKILL.md +20 -0
  7. package/build/skills/keiyaku/SKILL.md +3 -0
  8. package/build/skills/keiyaku-akuma/SKILL.md +20 -0
  9. package/build/src/akuma/akuma-instance.js +7 -5
  10. package/build/src/akuma/akuma-observe.js +1 -0
  11. package/build/src/akuma/akuma-owner.js +3 -2
  12. package/build/src/akuma/akuma-wait.d.ts +1 -2
  13. package/build/src/akuma/akuma-wait.js +1 -4
  14. package/build/src/akuma/akuma.d.ts +9 -4
  15. package/build/src/akuma/akuma.js +208 -35
  16. package/build/src/akuma/body.js +4 -0
  17. package/build/src/akuma/caller.d.ts +2 -0
  18. package/build/src/akuma/caller.js +6 -0
  19. package/build/src/akuma/custody.d.ts +11 -0
  20. package/build/src/akuma/custody.js +102 -0
  21. package/build/src/akuma/lineage-cache.d.ts +39 -0
  22. package/build/src/akuma/lineage-cache.js +162 -0
  23. package/build/src/akuma/nuke.d.ts +1 -3
  24. package/build/src/akuma/nuke.js +11 -102
  25. package/build/src/akuma/retention.d.ts +44 -0
  26. package/build/src/akuma/retention.js +106 -0
  27. package/build/src/akuma/selection-owner-port.d.ts +1 -1
  28. package/build/src/akuma/selection-owner-port.js +3 -6
  29. package/build/src/akuma/selection-request.d.ts +131 -4
  30. package/build/src/akuma/selection-request.js +25 -15
  31. package/build/src/alias/index.d.ts +11 -0
  32. package/build/src/alias/index.js +20 -1
  33. package/build/src/cli/commands/contract-grammar.d.ts +12 -0
  34. package/build/src/cli/commands/contract-grammar.js +65 -13
  35. package/build/src/cli/commands/contract-help.d.ts +3 -1
  36. package/build/src/cli/commands/contract-help.js +5 -2
  37. package/build/src/cli/commands/contract.js +31 -4
  38. package/build/src/cli/render/catalog.d.ts +2 -1
  39. package/build/src/cli/render/catalog.js +8 -2
  40. package/build/src/cli/square-identity.d.ts +0 -5
  41. package/build/src/cli/square-identity.js +51 -101
  42. package/build/src/index.d.ts +1 -1
  43. package/build/src/library/akuma-retention.d.ts +11 -0
  44. package/build/src/library/akuma-retention.js +131 -0
  45. package/build/src/library/akumas.d.ts +8 -0
  46. package/build/src/library/akumas.js +42 -2
  47. package/build/src/library/selection.js +22 -9
  48. package/build/src/plugin/akuma-signals.d.ts +1 -0
  49. package/build/src/plugin/akuma-signals.js +17 -0
  50. package/build/src/plugin/public.d.ts +7 -0
  51. package/build/src/plugin/runtime.d.ts +1 -0
  52. package/build/src/plugin/runtime.js +85 -13
  53. package/build/src/runtime/proc/windows-launch.exe +0 -0
  54. package/package.json +3 -3
  55. package/skills/keiyaku/SKILL.md +3 -0
  56. package/skills/keiyaku-akuma/SKILL.md +20 -0
@@ -36,6 +36,9 @@ selects the Contract repository; it never retargets that World. `call
36
36
  cwd). Without it, the call uses the invocation cwd; `--contract` associates a
37
37
  Contract without selecting an execution directory.
38
38
 
39
+ An Akuma caller can find its own delegation tree with `keiyaku ls --mine` (or an
40
+ explicit `--caller`); see `keiyaku-akuma` for the roster and its text labels.
41
+
39
42
  `deliver` and `review` may take time after their leading admission.
40
43
  `deliver --dry-run` only observes the prospective candidate, standing evidence,
41
44
  and target followability; it never runs Verification or hooks or admits work.
@@ -87,16 +87,36 @@ keiyaku status
87
87
  keiyaku status @reviewer
88
88
  keiyaku ls aku/
89
89
  keiyaku ls "aku/*/*"
90
+ keiyaku ls --mine
90
91
  keiyaku history @reviewer --last
91
92
  keiyaku history @reviewer --id turn/3
92
93
  ```
93
94
 
94
95
  `status` shows the roster or one Akuma's workdir and effective actions.
95
96
  `ls aku/` shows callable names; `ls "aku/*/*"` shows born workers.
97
+ `ls --mine` narrows that roster to Akumas this process's caller called and their
98
+ transitive descendants in this World; `--caller <integration>:<id>` or
99
+ `--caller aku/<name>/<suffix>` names the same selection explicitly. Descendant
100
+ rows show their immediate caller as `· by <akuId>`. The selection is World-local
101
+ and refuses for Contract, Debt, and Akuma-name catalogues.
96
102
  `history --last` reads the latest complete answer; `history --id` reads an
97
103
  exact result named by status or wait. Use the complete AkuId when an alias may
98
104
  move; see `history --help` for paging.
99
105
 
106
+ ## Retention
107
+
108
+ An Akuma whose latest durable life or activity is older than fifteen days may be
109
+ retired after a later successful `call` or `fork`. Retention keeps any running
110
+ Akuma, any current alias target, any Akuma dispatched to a Contract that is not
111
+ yet `claimed` or `abandoned`, unborn or stillborn allocations, and unreadable
112
+ entries. It removes only the Akuma's recognized runtime custody; it never kills
113
+ a worker and never rewrites another Akuma, Contract, Debt, or alias. Its final
114
+ Heart re-read keeps a pending Tell it observes; a Tell admitted immediately
115
+ after that read is best-effort, not atomic. There is no timer and no prune
116
+ command. Observation and Tell commands (`status`, `ls`, `history`, `wait`,
117
+ `tell`, `ask`) never trigger retention; without a later successful
118
+ `call`/`fork`, old entries simply remain.
119
+
100
120
  ## Stop and branch
101
121
 
102
122
  ```bash
@@ -36,6 +36,9 @@ selects the Contract repository; it never retargets that World. `call
36
36
  cwd). Without it, the call uses the invocation cwd; `--contract` associates a
37
37
  Contract without selecting an execution directory.
38
38
 
39
+ An Akuma caller can find its own delegation tree with `keiyaku ls --mine` (or an
40
+ explicit `--caller`); see `keiyaku-akuma` for the roster and its text labels.
41
+
39
42
  `deliver` and `review` may take time after their leading admission.
40
43
  `deliver --dry-run` only observes the prospective candidate, standing evidence,
41
44
  and target followability; it never runs Verification or hooks or admits work.
@@ -87,16 +87,36 @@ keiyaku status
87
87
  keiyaku status @reviewer
88
88
  keiyaku ls aku/
89
89
  keiyaku ls "aku/*/*"
90
+ keiyaku ls --mine
90
91
  keiyaku history @reviewer --last
91
92
  keiyaku history @reviewer --id turn/3
92
93
  ```
93
94
 
94
95
  `status` shows the roster or one Akuma's workdir and effective actions.
95
96
  `ls aku/` shows callable names; `ls "aku/*/*"` shows born workers.
97
+ `ls --mine` narrows that roster to Akumas this process's caller called and their
98
+ transitive descendants in this World; `--caller <integration>:<id>` or
99
+ `--caller aku/<name>/<suffix>` names the same selection explicitly. Descendant
100
+ rows show their immediate caller as `· by <akuId>`. The selection is World-local
101
+ and refuses for Contract, Debt, and Akuma-name catalogues.
96
102
  `history --last` reads the latest complete answer; `history --id` reads an
97
103
  exact result named by status or wait. Use the complete AkuId when an alias may
98
104
  move; see `history --help` for paging.
99
105
 
106
+ ## Retention
107
+
108
+ An Akuma whose latest durable life or activity is older than fifteen days may be
109
+ retired after a later successful `call` or `fork`. Retention keeps any running
110
+ Akuma, any current alias target, any Akuma dispatched to a Contract that is not
111
+ yet `claimed` or `abandoned`, unborn or stillborn allocations, and unreadable
112
+ entries. It removes only the Akuma's recognized runtime custody; it never kills
113
+ a worker and never rewrites another Akuma, Contract, Debt, or alias. Its final
114
+ Heart re-read keeps a pending Tell it observes; a Tell admitted immediately
115
+ after that read is best-effort, not atomic. There is no timer and no prune
116
+ command. Observation and Tell commands (`status`, `ls`, `history`, `wait`,
117
+ `tell`, `ask`) never trigger retention; without a later successful
118
+ `call`/`fork`, old entries simply remain.
119
+
100
120
  ## Stop and branch
101
121
 
102
122
  ```bash
@@ -36,6 +36,9 @@ selects the Contract repository; it never retargets that World. `call
36
36
  cwd). Without it, the call uses the invocation cwd; `--contract` associates a
37
37
  Contract without selecting an execution directory.
38
38
 
39
+ An Akuma caller can find its own delegation tree with `keiyaku ls --mine` (or an
40
+ explicit `--caller`); see `keiyaku-akuma` for the roster and its text labels.
41
+
39
42
  `deliver` and `review` may take time after their leading admission.
40
43
  `deliver --dry-run` only observes the prospective candidate, standing evidence,
41
44
  and target followability; it never runs Verification or hooks or admits work.
@@ -87,16 +87,36 @@ keiyaku status
87
87
  keiyaku status @reviewer
88
88
  keiyaku ls aku/
89
89
  keiyaku ls "aku/*/*"
90
+ keiyaku ls --mine
90
91
  keiyaku history @reviewer --last
91
92
  keiyaku history @reviewer --id turn/3
92
93
  ```
93
94
 
94
95
  `status` shows the roster or one Akuma's workdir and effective actions.
95
96
  `ls aku/` shows callable names; `ls "aku/*/*"` shows born workers.
97
+ `ls --mine` narrows that roster to Akumas this process's caller called and their
98
+ transitive descendants in this World; `--caller <integration>:<id>` or
99
+ `--caller aku/<name>/<suffix>` names the same selection explicitly. Descendant
100
+ rows show their immediate caller as `· by <akuId>`. The selection is World-local
101
+ and refuses for Contract, Debt, and Akuma-name catalogues.
96
102
  `history --last` reads the latest complete answer; `history --id` reads an
97
103
  exact result named by status or wait. Use the complete AkuId when an alias may
98
104
  move; see `history --help` for paging.
99
105
 
106
+ ## Retention
107
+
108
+ An Akuma whose latest durable life or activity is older than fifteen days may be
109
+ retired after a later successful `call` or `fork`. Retention keeps any running
110
+ Akuma, any current alias target, any Akuma dispatched to a Contract that is not
111
+ yet `claimed` or `abandoned`, unborn or stillborn allocations, and unreadable
112
+ entries. It removes only the Akuma's recognized runtime custody; it never kills
113
+ a worker and never rewrites another Akuma, Contract, Debt, or alias. Its final
114
+ Heart re-read keeps a pending Tell it observes; a Tell admitted immediately
115
+ after that read is best-effort, not atomic. There is no timer and no prune
116
+ command. Observation and Tell commands (`status`, `ls`, `history`, `wait`,
117
+ `tell`, `ask`) never trigger retention; without a later successful
118
+ `call`/`fork`, old entries simply remain.
119
+
100
120
  ## Stop and branch
101
121
 
102
122
  ```bash
@@ -36,6 +36,9 @@ selects the Contract repository; it never retargets that World. `call
36
36
  cwd). Without it, the call uses the invocation cwd; `--contract` associates a
37
37
  Contract without selecting an execution directory.
38
38
 
39
+ An Akuma caller can find its own delegation tree with `keiyaku ls --mine` (or an
40
+ explicit `--caller`); see `keiyaku-akuma` for the roster and its text labels.
41
+
39
42
  `deliver` and `review` may take time after their leading admission.
40
43
  `deliver --dry-run` only observes the prospective candidate, standing evidence,
41
44
  and target followability; it never runs Verification or hooks or admits work.
@@ -87,16 +87,36 @@ keiyaku status
87
87
  keiyaku status @reviewer
88
88
  keiyaku ls aku/
89
89
  keiyaku ls "aku/*/*"
90
+ keiyaku ls --mine
90
91
  keiyaku history @reviewer --last
91
92
  keiyaku history @reviewer --id turn/3
92
93
  ```
93
94
 
94
95
  `status` shows the roster or one Akuma's workdir and effective actions.
95
96
  `ls aku/` shows callable names; `ls "aku/*/*"` shows born workers.
97
+ `ls --mine` narrows that roster to Akumas this process's caller called and their
98
+ transitive descendants in this World; `--caller <integration>:<id>` or
99
+ `--caller aku/<name>/<suffix>` names the same selection explicitly. Descendant
100
+ rows show their immediate caller as `· by <akuId>`. The selection is World-local
101
+ and refuses for Contract, Debt, and Akuma-name catalogues.
96
102
  `history --last` reads the latest complete answer; `history --id` reads an
97
103
  exact result named by status or wait. Use the complete AkuId when an alias may
98
104
  move; see `history --help` for paging.
99
105
 
106
+ ## Retention
107
+
108
+ An Akuma whose latest durable life or activity is older than fifteen days may be
109
+ retired after a later successful `call` or `fork`. Retention keeps any running
110
+ Akuma, any current alias target, any Akuma dispatched to a Contract that is not
111
+ yet `claimed` or `abandoned`, unborn or stillborn allocations, and unreadable
112
+ entries. It removes only the Akuma's recognized runtime custody; it never kills
113
+ a worker and never rewrites another Akuma, Contract, Debt, or alias. Its final
114
+ Heart re-read keeps a pending Tell it observes; a Tell admitted immediately
115
+ after that read is best-effort, not atomic. There is no timer and no prune
116
+ command. Observation and Tell commands (`status`, `ls`, `history`, `wait`,
117
+ `tell`, `ask`) never trigger retention; without a later successful
118
+ `call`/`fork`, old entries simply remain.
119
+
100
120
  ## Stop and branch
101
121
 
102
122
  ```bash
@@ -1,4 +1,4 @@
1
- import { outcomePluginObserver, stderrPluginDiagnostic } from "../plugin/akuma-signals.js";
1
+ import { outcomePluginObserver, stderrPluginDiagnostic, withAkumaWaitDeferrals } from "../plugin/akuma-signals.js";
2
2
  import { abortable } from "./abort.js";
3
3
  import { admitAkumaCall } from "./akuma-call.js";
4
4
  import { defaultWaitComplete } from "./akuma-observe.js";
@@ -129,11 +129,13 @@ export class Akuma {
129
129
  throw new TypeError("Akuma idle timeoutMs must be a nonnegative finite millisecond duration");
130
130
  }
131
131
  const signal = signalOption(options.signal);
132
- const waited = await this.owner(true).waitReceipt(defaultWaitComplete, {
133
- ...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }),
134
- ...(signal === undefined ? {} : { signal }),
132
+ return await withAkumaWaitDeferrals({ world: this.root, reportDiagnostic: this.reportDiagnostic }, [this.id], signal, async () => {
133
+ const waited = await this.owner(true).waitReceipt(defaultWaitComplete, {
134
+ ...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }),
135
+ ...(signal === undefined ? {} : { signal }),
136
+ });
137
+ return { reason: waited.reason, status: waited.status };
135
138
  });
136
- return { reason: waited.reason, status: waited.status };
137
139
  }
138
140
  async history(options = {}) {
139
141
  if (typeof options !== "object" || options === null || Array.isArray(options)) {
@@ -18,6 +18,7 @@ export async function rosterListRow(paths, expected, aliases = []) {
18
18
  lastActivityAt: observed.snapshot.lastActivityAt,
19
19
  pending: observed.snapshot.pending.map((tell) => tell.id),
20
20
  aliases,
21
+ ...(observed.soul.caller === undefined ? {} : { caller: observed.soul.caller }),
21
22
  };
22
23
  }
23
24
  try {
@@ -179,12 +179,13 @@ export class AkumaOwner {
179
179
  path: this.worldPath,
180
180
  ids: [this.id],
181
181
  completion: "all",
182
- ...(this.outcomeObserved === undefined ? {} : { outcomeObserved: this.outcomeObserved }),
183
182
  ...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }),
184
183
  ...(options.signal === undefined ? {} : { signal: options.signal }),
185
184
  ...(predicate === defaultWaitComplete ? {} : { predicate }),
186
185
  });
187
- return { reason: waited.reason, status: waited.observations[0] };
186
+ const status = waited.observations[0];
187
+ await observeStatusOutcome(status, this.outcomeObserved);
188
+ return { reason: waited.reason, status };
188
189
  }
189
190
  async wait(predicate = defaultWaitComplete, options = {}) {
190
191
  return (await this.waitReceipt(predicate, options)).status;
@@ -1,5 +1,5 @@
1
1
  import type { WorldRoot } from "../world.js";
2
- import { type OutcomeObserver, type WaitReason } from "./akuma-observe.js";
2
+ import { type WaitReason } from "./akuma-observe.js";
3
3
  import type { AkumaStatus } from "./akuma.js";
4
4
  import type { AkumaUnobserved } from "./selection-observation.js";
5
5
  type WaitExecutionInput = Readonly<{
@@ -10,7 +10,6 @@ type WaitExecutionInput = Readonly<{
10
10
  signal?: AbortSignal;
11
11
  /** Internal custom completion judge over the full final observation where supplied. */
12
12
  predicate?: (status: AkumaStatus) => boolean;
13
- outcomeObserved?: OutcomeObserver;
14
13
  }>;
15
14
  /** One wait's association-free native evidence; upper composition attaches cross-product context. */
16
15
  export type NativeWaitResult = Readonly<{
@@ -1,5 +1,5 @@
1
1
  import { AkumaNotBornError } from "./akuma-errors.js";
2
- import { createLiveStatusReader, defaultWaitComplete, readWaitComplete, observeStatusOutcome, waitForObservation, } from "./akuma-observe.js";
2
+ import { createLiveStatusReader, defaultWaitComplete, readWaitComplete, waitForObservation, } from "./akuma-observe.js";
3
3
  import { pathsForAkuId } from "./identity.js";
4
4
  const SHARED_ORDINARY_BUDGET = 30;
5
5
  async function observeWaitRound(ids, readerFor, signal) {
@@ -56,9 +56,6 @@ export async function executeWaitAkuma(input) {
56
56
  : {}),
57
57
  observe: async () => await observeWaitRound(input.ids, readerFor, input.signal),
58
58
  complete: (round) => roundComplete(round, input.completion, input.predicate),
59
- onObserve: async (round) => {
60
- await Promise.all(round.observations.map(({ status }) => observeStatusOutcome(status, input.outcomeObserved)));
61
- },
62
59
  });
63
60
  return {
64
61
  mode: input.completion,
@@ -1,3 +1,4 @@
1
+ import { type AkumaCaller } from "./caller.js";
1
2
  import { z } from "zod";
2
3
  import { type BoundedList } from "../bounded-list.js";
3
4
  import type { AkumaAlias } from "../identity/selector.js";
@@ -13,6 +14,7 @@ export type AkumaListRow = Readonly<{
13
14
  lastActivityAt: string | null;
14
15
  pending: readonly string[];
15
16
  aliases: readonly AkumaAlias[];
17
+ caller?: AkumaCaller;
16
18
  }>;
17
19
  export type UnbornAkumaListRow = Readonly<{
18
20
  id: AkuId;
@@ -30,6 +32,7 @@ export type AkumaList = BoundedList<AkumaListRow | UnbornAkumaListRow> & Readonl
30
32
  export type AkumaCompleteList = Omit<AkumaList, "hasMore">;
31
33
  export type AkumaListInput = Readonly<{
32
34
  archetype?: string;
35
+ caller?: AkumaCaller;
33
36
  limit?: number;
34
37
  }>;
35
38
  export declare const akumaStatusSchema: z.ZodObject<{
@@ -480,16 +483,18 @@ export declare function knownAkuma(path: WorldRoot, selected?: string): Promise<
480
483
  runRoot: string;
481
484
  rows: readonly KnownAkuma[];
482
485
  }>>;
486
+ /** The bounded recent-activity roster input: native membership and observed extent. */
487
+ export declare function readAkumaRoster(path: WorldRoot, input?: AkumaListInput): Promise<AkumaList>;
483
488
  /**
484
489
  * The bounded recent-activity roster: native membership, semantic order and
485
- * observed extent. World Alias context is attached by the upper composition.
490
+ * observed extent. A caller-scoped roster filters its World-local closure before
491
+ * this same activity order and bound; World Alias context is attached above.
486
492
  */
487
- export declare function readAkumaRoster(path: WorldRoot, input?: AkumaListInput): Promise<AkumaList>;
488
- /** Internal recent observation with an aperture already validated by the composition. */
489
- export declare function observeAkumaRoster(path: WorldRoot, limit: number, selected?: string): Promise<AkumaList>;
493
+ export declare function observeAkumaRoster(path: WorldRoot, limit: number, selected?: string, caller?: AkumaCaller): Promise<AkumaList>;
490
494
  /** The complete native roster for callers whose semantics require a frozen set. */
491
495
  export declare function readAkumaCompleteRoster(path: WorldRoot, input?: Readonly<{
492
496
  archetype?: string;
497
+ caller?: AkumaCaller;
493
498
  }>): Promise<AkumaCompleteList>;
494
499
  /** Internal timeline observation for composition owners. */
495
500
  export declare function readAkumaTimeline(path: WorldRoot, id: AkuId): Promise<AkumaStatus["timeline"]>;
@@ -1,12 +1,14 @@
1
- import { akumaBirthSchema } from "./caller.js";
1
+ import { akumaBirthSchema, akumaCallerKey, decodeAkumaCaller } from "./caller.js";
2
2
  import { readdir, stat } from "node:fs/promises";
3
3
  import { z } from "zod";
4
4
  import { boundedListLimit, projectBoundedList } from "../bounded-list.js";
5
5
  import { rosterListRow } from "./akuma-observe.js";
6
6
  import { AkumaOwner } from "./akuma-owner.js";
7
7
  import { allowedActionsSchema } from "./allowed.js";
8
- import { akuIdFromDirectoryName, akumaIdSchema, akumaPaths, akumaRunRoot, } from "./identity.js";
8
+ import { readSoul } from "./heart/index.js";
9
+ import { akuIdFromDirectoryName, akumaIdSchema, akumaPaths, akumaRunRoot, parseAkuId, pathsForAkuId, } from "./identity.js";
9
10
  import { validateAkumaName } from "../identity/normalize.js";
11
+ import { readAkumaLineage, readDirectAkumaLineage, invalidateAkumaLineage, rebuildAkumaLineage, } from "./lineage-cache.js";
10
12
  import { activitySnapshotSchema } from "./projection.js";
11
13
  import { PAGE_POOL_SIZE, boundedMap } from "./read-pool.js";
12
14
  export const akumaStatusSchema = z
@@ -91,6 +93,11 @@ export async function knownAkuma(path, selected) {
91
93
  }
92
94
  return { runRoot, rows };
93
95
  }
96
+ /** Preorder candidates by their freshest Heart mtime so paging reads changed rows first. */
97
+ async function freshnessOrdered(rows) {
98
+ const bounded = await boundedMap(rows, async (row) => ({ ...row, bound: await mtimeBound(row.paths) }));
99
+ return [...bounded].sort((left, right) => right.bound - left.bound);
100
+ }
94
101
  async function readableRows(rows) {
95
102
  const loaded = await boundedMap(rows, async ({ id, paths }) => {
96
103
  try {
@@ -100,43 +107,152 @@ async function readableRows(rows) {
100
107
  return null;
101
108
  }
102
109
  });
103
- return [...loaded].filter((row) => row !== null);
110
+ const values = [...loaded].filter((row) => row !== null);
111
+ return { rows: values, skipped: loaded.length - values.length };
104
112
  }
105
- function rosterArchetype(input, allowLimit) {
113
+ function rosterInput(input, allowLimit) {
106
114
  if (typeof input !== "object" || input === null || Array.isArray(input))
107
115
  throw new TypeError("Akuma list input must be an object");
108
- const allowed = allowLimit ? ["archetype", "limit"] : ["archetype"];
116
+ const allowed = allowLimit ? ["archetype", "caller", "limit"] : ["archetype", "caller"];
109
117
  const unknown = Object.keys(input).find((key) => !allowed.includes(key));
110
118
  if (unknown !== undefined)
111
119
  throw new TypeError(`Akuma list input has unknown field: ${unknown}`);
112
- return input.archetype === undefined ? undefined : validateAkumaName(input.archetype);
120
+ const caller = decodeAkumaCaller(input.caller);
121
+ return {
122
+ ...(input.archetype === undefined ? {} : { archetype: validateAkumaName(input.archetype) }),
123
+ ...(caller === undefined ? {} : { caller }),
124
+ };
113
125
  }
114
126
  /**
115
- * The bounded recent-activity roster: native membership, semantic order and
116
- * observed extent. World Alias context is attached by the upper composition.
127
+ * The caller closure follows only frozen Soul edges: the exact root coordinate,
128
+ * then Akuma coordinates inside this selected World. Fork origin is not an edge.
129
+ * Known identities deduplicate repeated and cyclic edges.
117
130
  */
118
- export async function readAkumaRoster(path, input = {}) {
119
- const selected = rosterArchetype(input, true);
120
- const limit = boundedListLimit(input.limit);
121
- return observeAkumaRoster(path, limit, selected);
131
+ function callerClosure(lineage, root, world) {
132
+ if (root.kind === "akuma" && root.world !== world)
133
+ return new Set();
134
+ const children = new Map();
135
+ for (const [akuId, caller] of lineage) {
136
+ if (caller === null)
137
+ continue;
138
+ const key = akumaCallerKey(caller);
139
+ const siblings = children.get(key);
140
+ if (siblings === undefined)
141
+ children.set(key, [akuId]);
142
+ else
143
+ siblings.push(akuId);
144
+ }
145
+ const included = new Set();
146
+ const visited = new Set();
147
+ const queue = [root];
148
+ while (queue.length > 0) {
149
+ const caller = queue.shift();
150
+ const key = akumaCallerKey(caller);
151
+ if (visited.has(key))
152
+ continue;
153
+ visited.add(key);
154
+ for (const akuId of children.get(key) ?? []) {
155
+ if (included.has(akuId))
156
+ continue;
157
+ included.add(akuId);
158
+ queue.push({ kind: "akuma", world, akuId });
159
+ }
160
+ }
161
+ return included;
122
162
  }
123
- /** Internal recent observation with an aperture already validated by the composition. */
124
- export async function observeAkumaRoster(path, limit, selected) {
125
- const observedAt = new Date().toISOString();
126
- const known = await knownAkuma(path, selected);
127
- const candidatesWithBounds = await boundedMap(known.rows, async (row) => ({
128
- ...row,
129
- bound: await mtimeBound(row.paths),
130
- }));
131
- const candidates = [...candidatesWithBounds].sort((left, right) => right.bound - left.bound);
132
- const readable = [];
163
+ function sameCallerEvidence(left, right) {
164
+ if (left === null || right === null)
165
+ return left === right;
166
+ return akumaCallerKey(left) === akumaCallerKey(right);
167
+ }
168
+ async function readCallerSoul(world, akuId) {
169
+ try {
170
+ const soul = await readSoul(pathsForAkuId(world, akuId));
171
+ return soul === null ? { kind: "absent" } : { kind: "born", caller: soul.caller ?? null };
172
+ }
173
+ catch {
174
+ return { kind: "unreadable" };
175
+ }
176
+ }
177
+ async function soulEdgeFor(verification, akuId) {
178
+ const observed = verification.souls.get(akuId);
179
+ if (observed !== undefined)
180
+ return await observed;
181
+ const pending = readCallerSoul(verification.world, akuId);
182
+ verification.souls.set(akuId, pending);
183
+ return await pending;
184
+ }
185
+ /**
186
+ * The cache may propose a member; only the born Soul chain can justify it. Each
187
+ * returned member's own edge is compared with its born Soul, and every parent
188
+ * link on that chain is proven the same way back to the exact query root.
189
+ * Any disagreement means the record cannot be trusted for this answer.
190
+ */
191
+ async function verifyCallerMember(verification, id, observedCaller) {
192
+ const { lineage, root } = verification;
193
+ const rowCaller = observedCaller ?? null;
194
+ const cached = lineage.get(id);
195
+ if (cached === undefined)
196
+ return "invalid-cache";
197
+ if (!sameCallerEvidence(cached, rowCaller))
198
+ return "invalid-cache";
199
+ const rootKey = akumaCallerKey(root);
200
+ const seen = new Set();
201
+ let current = rowCaller;
202
+ for (;;) {
203
+ if (current === null)
204
+ return "excluded";
205
+ const key = akumaCallerKey(current);
206
+ if (key === rootKey)
207
+ return "included";
208
+ if (seen.has(key))
209
+ return "excluded";
210
+ seen.add(key);
211
+ if (current.kind === "external")
212
+ return "excluded";
213
+ if (current.world !== verification.world)
214
+ return "excluded";
215
+ const parentId = current.akuId;
216
+ const parentCached = lineage.get(parentId);
217
+ const edge = await soulEdgeFor(verification, parentId);
218
+ if (edge.kind === "unreadable")
219
+ return "unknown";
220
+ // A cached parent that has no born Soul cannot seed a real chain.
221
+ if (edge.kind === "absent")
222
+ return "invalid-cache";
223
+ if (parentCached === undefined)
224
+ return "excluded";
225
+ if (!sameCallerEvidence(parentCached, edge.caller))
226
+ return "invalid-cache";
227
+ current = edge.caller;
228
+ }
229
+ }
230
+ /**
231
+ * Read only the matching candidates needed for the requested page and its
232
+ * lookahead. `admit` proves each readable member; without it the selected
233
+ * closure's own born Soul edges are the proof. Unprovable members are skipped
234
+ * honestly instead of reported as no more.
235
+ */
236
+ async function pageCallerRows(candidates, limit, admit) {
237
+ const rows = [];
238
+ let skipped = 0;
133
239
  let cursor = 0;
134
240
  while (cursor < candidates.length) {
135
241
  const batch = candidates.slice(cursor, cursor + PAGE_POOL_SIZE);
136
- readable.push(...(await readableRows(batch)));
242
+ const observed = await readableRows(batch);
137
243
  cursor += batch.length;
138
- const ranked = readable.sort(compareRows);
139
- const lookahead = ranked[limit];
244
+ skipped += observed.skipped;
245
+ for (const row of observed.rows) {
246
+ const verdict = admit === undefined ? "included" : await admit(row);
247
+ if (verdict === "invalid-cache")
248
+ return { rows, skipped, invalid: true };
249
+ if (verdict === "included")
250
+ rows.push(row);
251
+ else if (verdict === "unknown")
252
+ skipped += 1;
253
+ }
254
+ rows.sort(compareRows);
255
+ const lookahead = rows[limit];
140
256
  const activity = lookahead === undefined ? null : activityAt(lookahead);
141
257
  const unreadBound = candidates[cursor]?.bound;
142
258
  if (lookahead !== undefined &&
@@ -148,20 +264,77 @@ export async function observeAkumaRoster(path, limit, selected) {
148
264
  break;
149
265
  }
150
266
  }
151
- const ranked = readable.sort(compareRows);
152
- return {
153
- observedAt,
154
- searched: [known.runRoot],
155
- ...projectBoundedList(ranked, limit),
156
- };
267
+ return { rows: rows.sort(compareRows), skipped, invalid: false };
268
+ }
269
+ /**
270
+ * The caller closure across every Archetype, before the requested one narrows
271
+ * it, ordered so paging reads freshly changed members first.
272
+ */
273
+ async function callerCandidates(rows, lineage, root, world, selected) {
274
+ const closure = callerClosure(lineage, root, world);
275
+ return await freshnessOrdered(rows.filter((row) => closure.has(row.id) && (selected === undefined || parseAkuId(row.id).archetype === selected)));
276
+ }
277
+ /**
278
+ * One caller-scoped page. The World-local record only proposes members: each is
279
+ * re-proven against its born Soul chain, and one disagreement discards the
280
+ * record and answers from direct Soul evidence instead of an empty success.
281
+ */
282
+ async function readCallerPage(path, limit, selected, caller) {
283
+ const known = await knownAkuma(path);
284
+ const cached = await readAkumaLineage(path, known.rows);
285
+ const verification = { world: path, root: caller, lineage: cached, souls: new Map() };
286
+ const attempted = await pageCallerRows(await callerCandidates(known.rows, cached, caller, path, selected), limit,
287
+ // A cache-proposed identity without a born Soul cannot justify inclusion.
288
+ async (row) => ("lifeAt" in row ? await verifyCallerMember(verification, row.id, row.caller) : "invalid-cache"));
289
+ if (!attempted.invalid)
290
+ return attempted;
291
+ // One wrong edge makes the whole disposable record untrustworthy.
292
+ await invalidateAkumaLineage(path);
293
+ const direct = await readDirectAkumaLineage(known.rows);
294
+ const repaired = await pageCallerRows(await callerCandidates(known.rows, direct, caller, path, selected), limit);
295
+ await rebuildAkumaLineage(path, direct);
296
+ return repaired;
297
+ }
298
+ /** The bounded recent-activity roster input: native membership and observed extent. */
299
+ export async function readAkumaRoster(path, input = {}) {
300
+ const selection = rosterInput(input, true);
301
+ const limit = boundedListLimit(input.limit);
302
+ return observeAkumaRoster(path, limit, selection.archetype, selection.caller);
303
+ }
304
+ /**
305
+ * The bounded recent-activity roster: native membership, semantic order and
306
+ * observed extent. A caller-scoped roster filters its World-local closure before
307
+ * this same activity order and bound; World Alias context is attached above.
308
+ */
309
+ export async function observeAkumaRoster(path, limit, selected, caller) {
310
+ const observedAt = new Date().toISOString();
311
+ if (caller !== undefined) {
312
+ const page = await readCallerPage(path, limit, selected, caller);
313
+ const bounded = projectBoundedList(page.rows, limit);
314
+ return {
315
+ observedAt,
316
+ // A skipped or unprovable candidate leaves the extent unknowable, never empty.
317
+ hasMore: bounded.hasMore || page.skipped > 0,
318
+ rows: bounded.rows,
319
+ searched: [akumaRunRoot(path)],
320
+ };
321
+ }
322
+ const known = await knownAkuma(path, selected);
323
+ const ranked = await pageCallerRows(await freshnessOrdered(known.rows), limit);
324
+ return { observedAt, searched: [known.runRoot], ...projectBoundedList(ranked.rows, limit) };
157
325
  }
158
326
  /** The complete native roster for callers whose semantics require a frozen set. */
159
327
  export async function readAkumaCompleteRoster(path, input = {}) {
160
- const selected = rosterArchetype(input, false);
161
- const known = await knownAkuma(path, selected);
328
+ const selection = rosterInput(input, false);
329
+ const observedAt = new Date().toISOString();
330
+ if (selection.caller !== undefined) {
331
+ const page = await readCallerPage(path, Number.POSITIVE_INFINITY, selection.archetype, selection.caller);
332
+ return { observedAt, rows: page.rows, searched: [akumaRunRoot(path)] };
333
+ }
334
+ const known = await knownAkuma(path, selection.archetype);
162
335
  return {
163
- observedAt: new Date().toISOString(),
164
- rows: [...(await readableRows(known.rows))].sort(compareRows),
336
+ observedAt,
337
+ rows: [...(await readableRows(known.rows)).rows].sort(compareRows),
165
338
  searched: [known.runRoot],
166
339
  };
167
340
  }
@@ -9,6 +9,7 @@ import { BodySupervisor } from "./body-supervisor.js";
9
9
  import { driveTurn, turnRecipe } from "./turn-drive.js";
10
10
  import { HeldAkumaLeash, BodyControlledError, breakBody, decidePendingTellDisposition, drainPendingTells, endTurn, failOpenBoundTurns, finishBodyIfIdle, heartExists, isHeartAbsent, probeLeash, projectTell, readHeart, readLatestTurnForBody, readOpenBoundTurns, readOpenPendingTellDisposition, readTell, readTurn, readTurnParticipants, readNonterminalRequests, resolvePendingTellDisposition, } from "./heart/index.js";
11
11
  import { akumaPathsSchema, pathsForAkuId, worldRootForAkumaPaths } from "./identity.js";
12
+ import { recordAkumaLineage } from "./lineage-cache.js";
12
13
  import { admitCallInitialTell, } from "./call-initial-tell.js";
13
14
  import { akumaExecutionEnvironment } from "./providers/execution-environment.js";
14
15
  import { emitPluginSignal } from "../plugin/akuma-signals.js";
@@ -247,6 +248,9 @@ async function prepareBodyExecution(launch, leash, runtime, adapter) {
247
248
  const birth = await bornSoul(launch, leash, runtime.now());
248
249
  if (birth === null)
249
250
  return null;
251
+ // The immutable caller edge refreshes after Soul durability, best-effort.
252
+ if (birth.created)
253
+ void recordAkumaLineage(runtime.world, birth.soul);
250
254
  const { soul } = birth;
251
255
  if (!(await prepareBodyStart(launch.paths, leash)))
252
256
  return null;
@@ -37,3 +37,5 @@ export declare const akumaBirthSchema: z.ZodObject<{
37
37
  }, z.core.$strict>;
38
38
  export declare function projectAkumaBirth(soul: Soul): z.infer<typeof akumaBirthSchema>;
39
39
  export declare function decodeAkumaCaller(value: unknown): AkumaCaller | undefined;
40
+ /** One stable comparison coordinate for stored and queried caller attribution. */
41
+ export declare function akumaCallerKey(caller: AkumaCaller): string;