@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.
- package/build/plugins/claude/skills/keiyaku/SKILL.md +3 -0
- package/build/plugins/claude/skills/keiyaku-akuma/SKILL.md +20 -0
- package/build/plugins/codex/skills/keiyaku/SKILL.md +3 -0
- package/build/plugins/codex/skills/keiyaku-akuma/SKILL.md +20 -0
- package/build/plugins/opencode/skills/keiyaku/SKILL.md +3 -0
- package/build/plugins/opencode/skills/keiyaku-akuma/SKILL.md +20 -0
- package/build/skills/keiyaku/SKILL.md +3 -0
- package/build/skills/keiyaku-akuma/SKILL.md +20 -0
- package/build/src/akuma/akuma-instance.js +7 -5
- package/build/src/akuma/akuma-observe.js +1 -0
- package/build/src/akuma/akuma-owner.js +3 -2
- package/build/src/akuma/akuma-wait.d.ts +1 -2
- package/build/src/akuma/akuma-wait.js +1 -4
- package/build/src/akuma/akuma.d.ts +9 -4
- package/build/src/akuma/akuma.js +208 -35
- package/build/src/akuma/body.js +4 -0
- package/build/src/akuma/caller.d.ts +2 -0
- package/build/src/akuma/caller.js +6 -0
- package/build/src/akuma/custody.d.ts +11 -0
- package/build/src/akuma/custody.js +102 -0
- package/build/src/akuma/lineage-cache.d.ts +39 -0
- package/build/src/akuma/lineage-cache.js +162 -0
- package/build/src/akuma/nuke.d.ts +1 -3
- package/build/src/akuma/nuke.js +11 -102
- package/build/src/akuma/retention.d.ts +44 -0
- package/build/src/akuma/retention.js +106 -0
- package/build/src/akuma/selection-owner-port.d.ts +1 -1
- package/build/src/akuma/selection-owner-port.js +3 -6
- package/build/src/akuma/selection-request.d.ts +131 -4
- package/build/src/akuma/selection-request.js +25 -15
- package/build/src/alias/index.d.ts +11 -0
- package/build/src/alias/index.js +20 -1
- package/build/src/cli/commands/contract-grammar.d.ts +12 -0
- package/build/src/cli/commands/contract-grammar.js +65 -13
- package/build/src/cli/commands/contract-help.d.ts +3 -1
- package/build/src/cli/commands/contract-help.js +5 -2
- package/build/src/cli/commands/contract.js +31 -4
- package/build/src/cli/render/catalog.d.ts +2 -1
- package/build/src/cli/render/catalog.js +8 -2
- package/build/src/cli/square-identity.d.ts +0 -5
- package/build/src/cli/square-identity.js +51 -101
- package/build/src/index.d.ts +1 -1
- package/build/src/library/akuma-retention.d.ts +11 -0
- package/build/src/library/akuma-retention.js +131 -0
- package/build/src/library/akumas.d.ts +8 -0
- package/build/src/library/akumas.js +42 -2
- package/build/src/library/selection.js +22 -9
- package/build/src/plugin/akuma-signals.d.ts +1 -0
- package/build/src/plugin/akuma-signals.js +17 -0
- package/build/src/plugin/public.d.ts +7 -0
- package/build/src/plugin/runtime.d.ts +1 -0
- package/build/src/plugin/runtime.js +85 -13
- package/build/src/runtime/proc/windows-launch.exe +0 -0
- package/package.json +3 -3
- package/skills/keiyaku/SKILL.md +3 -0
- 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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
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.
|
|
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
|
|
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"]>;
|
package/build/src/akuma/akuma.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
110
|
+
const values = [...loaded].filter((row) => row !== null);
|
|
111
|
+
return { rows: values, skipped: loaded.length - values.length };
|
|
104
112
|
}
|
|
105
|
-
function
|
|
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
|
-
|
|
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
|
|
116
|
-
*
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
242
|
+
const observed = await readableRows(batch);
|
|
137
243
|
cursor += batch.length;
|
|
138
|
-
|
|
139
|
-
const
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
|
161
|
-
const
|
|
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
|
|
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
|
}
|
package/build/src/akuma/body.js
CHANGED
|
@@ -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;
|