@oh-hai/cli 0.2.3 → 0.3.0
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/CHANGELOG.md +98 -0
- package/README.md +59 -29
- package/dist/commands/ask.js +71 -10
- package/dist/commands/ask.js.map +1 -1
- package/dist/commands/bridge.d.ts +2 -0
- package/dist/commands/bridge.js +502 -0
- package/dist/commands/bridge.js.map +1 -0
- package/dist/commands/context.d.ts +22 -0
- package/dist/commands/doctor.js +12 -1
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/fleet.d.ts +2 -0
- package/dist/commands/fleet.js +164 -0
- package/dist/commands/fleet.js.map +1 -0
- package/dist/commands/handlers.js +8 -0
- package/dist/commands/handlers.js.map +1 -1
- package/dist/commands/messages.d.ts +2 -0
- package/dist/commands/messages.js +131 -0
- package/dist/commands/messages.js.map +1 -0
- package/dist/commands/messaging/build.d.ts +10 -1
- package/dist/commands/messaging/build.js +22 -4
- package/dist/commands/messaging/build.js.map +1 -1
- package/dist/commands/messaging/capability.d.ts +42 -0
- package/dist/commands/messaging/capability.js +164 -0
- package/dist/commands/messaging/capability.js.map +1 -0
- package/dist/commands/messaging/http.d.ts +28 -1
- package/dist/commands/messaging/http.js +234 -13
- package/dist/commands/messaging/http.js.map +1 -1
- package/dist/commands/messaging/inbox-entries.d.ts +83 -0
- package/dist/commands/messaging/inbox-entries.js +362 -0
- package/dist/commands/messaging/inbox-entries.js.map +1 -0
- package/dist/commands/messaging/inbox-stream.d.ts +37 -0
- package/dist/commands/messaging/inbox-stream.js +224 -0
- package/dist/commands/messaging/inbox-stream.js.map +1 -0
- package/dist/commands/messaging/session-drain.d.ts +50 -0
- package/dist/commands/messaging/session-drain.js +163 -0
- package/dist/commands/messaging/session-drain.js.map +1 -0
- package/dist/commands/messaging/session-scope.d.ts +93 -0
- package/dist/commands/messaging/session-scope.js +0 -0
- package/dist/commands/messaging/session-scope.js.map +1 -0
- package/dist/commands/messaging/sessions-http.d.ts +20 -0
- package/dist/commands/messaging/sessions-http.js +153 -0
- package/dist/commands/messaging/sessions-http.js.map +1 -0
- package/dist/commands/messaging/shared.d.ts +19 -1
- package/dist/commands/messaging/shared.js +69 -2
- package/dist/commands/messaging/shared.js.map +1 -1
- package/dist/commands/messaging/validate.d.ts +33 -1
- package/dist/commands/messaging/validate.js +78 -0
- package/dist/commands/messaging/validate.js.map +1 -1
- package/dist/commands/messaging/wire.d.ts +180 -3
- package/dist/commands/notify.js +47 -5
- package/dist/commands/notify.js.map +1 -1
- package/dist/commands/registry.js +128 -3
- package/dist/commands/registry.js.map +1 -1
- package/dist/commands/self-test.d.ts +17 -0
- package/dist/commands/self-test.js +472 -0
- package/dist/commands/self-test.js.map +1 -0
- package/dist/commands/session.d.ts +2 -0
- package/dist/commands/session.js +158 -0
- package/dist/commands/session.js.map +1 -0
- package/dist/commands/setup.js +44 -0
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/task.js +61 -11
- package/dist/commands/task.js.map +1 -1
- package/dist/commands/teach.js +84 -4
- package/dist/commands/teach.js.map +1 -1
- package/dist/commands/whoami.js +14 -1
- package/dist/commands/whoami.js.map +1 -1
- package/dist/exit-codes.d.ts +37 -1
- package/dist/exit-codes.js +42 -1
- package/dist/exit-codes.js.map +1 -1
- package/dist/help.js +5 -1
- package/dist/help.js.map +1 -1
- package/dist/watch-lock.js +7 -0
- package/dist/watch-lock.js.map +1 -1
- package/package.json +4 -3
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
// The END-TO-END SETUP SELF-TEST (docs/specs/cli.md §4.6; issue #660) — the acceptance bar for the
|
|
2
|
+
// inter-agent leg's enablement story: **no green, no done.**
|
|
3
|
+
//
|
|
4
|
+
// WHY THIS EXISTS AT ALL. Every other check in `doctor` proves a PRECONDITION — a token is stored, a
|
|
5
|
+
// URL parses, the Hub answers. None of them proves the thing a human actually depends on: that a
|
|
6
|
+
// message addressed to this agent's running session reaches it. A human who falsely believes their
|
|
7
|
+
// agent got an important message is worse off than one who knows it cannot be reached, so the
|
|
8
|
+
// enablement layer has to be able to demonstrate the loop rather than assert it.
|
|
9
|
+
//
|
|
10
|
+
// WHAT IT ACTUALLY DOES, in order — the same sequence `oh-hai bridge` performs, minus the
|
|
11
|
+
// subprocess:
|
|
12
|
+
//
|
|
13
|
+
// 1. §8.0 capability gates, both directions (addressed send + session-scoped drain).
|
|
14
|
+
// 2. Register a §16.1 session — a real address, minted by the Hub.
|
|
15
|
+
// 3. Send a self-addressed `notify` to `agent:<self>#<sess_…>` carrying a nonce.
|
|
16
|
+
// 4. Drain that session (§8.7.1) and run the FULL §13.4 consuming-agent duties over each entry via
|
|
17
|
+
// `verifyEntry` — the same module the bridge uses, so a green here is evidence about the code
|
|
18
|
+
// that runs in production, not about a parallel test-only path.
|
|
19
|
+
// 5. Ack it (§8.7.2: an unacked entry advances no delivery track — the ack is what makes it
|
|
20
|
+
// *delivered*, so a self-test that skipped it would be testing less than it claims).
|
|
21
|
+
// 6. Re-read the session and report the Hub's OWN reachability conclusion (§15.1) — the same
|
|
22
|
+
// snapshot behind `fleet ls`, the §8.1 submit ack and the web UI.
|
|
23
|
+
// 7. Close the session, always, so the test does not leak a §16.1 live-session slot.
|
|
24
|
+
//
|
|
25
|
+
// WHY A `notify` AND NOT AN `ask`. The §13.4 v0.5 amendment requires an explicit, deployment-declared
|
|
26
|
+
// `--allow-from` policy before ACTING on an addressed ask/task — deliberately with no permissive
|
|
27
|
+
// default. A self-test that needed one would either force a policy decision the operator has not made
|
|
28
|
+
// yet or, worse, model a permissive default and teach the wrong habit. A `notify` exercises the whole
|
|
29
|
+
// transport, verification, ack and reachability path without touching that gate.
|
|
30
|
+
//
|
|
31
|
+
// HONESTY RULES BAKED INTO THE COPY (they are the product here, not decoration):
|
|
32
|
+
// • `queued` is ACCEPTANCE, not delivery. Only the ack makes it delivered.
|
|
33
|
+
// • `unknown` reachability means the Hub has NO SIGNAL — never that the peer is offline.
|
|
34
|
+
// • This client CANNOT verify the `v1=` MAC: it is keyed with the Hub's key, which an agent bearer
|
|
35
|
+
// does not hold. The self-test says what it checked (header shape, freshness, replay, entry
|
|
36
|
+
// shape, addressee) and refuses to imply more.
|
|
37
|
+
import { randomUUID } from "node:crypto";
|
|
38
|
+
import { CliError } from "../envelope.js";
|
|
39
|
+
import { ExitCode } from "../exit-codes.js";
|
|
40
|
+
import { sanitizeForTerminal } from "../terminal.js";
|
|
41
|
+
import { buildNotify } from "./messaging/build.js";
|
|
42
|
+
import { assertHubSpeaksAddressing, assertHubSpeaksSessionDrain, fetchCapability } from "./messaging/capability.js";
|
|
43
|
+
import { submitEnvelope } from "./messaging/http.js";
|
|
44
|
+
import { resolveSubmitIdentity } from "./messaging/identity.js";
|
|
45
|
+
import { DEFAULT_REPLAY_WINDOW_SECONDS, ReplayGuard, ackKeyOf, verifyEntry, } from "./messaging/inbox-entries.js";
|
|
46
|
+
import { ackInboxSession, drainInboxSession } from "./messaging/session-drain.js";
|
|
47
|
+
import { closeSession, getSession, registerSession } from "./messaging/sessions-http.js";
|
|
48
|
+
import { resolveRunId } from "./messaging/shared.js";
|
|
49
|
+
/** How many times the drain is retried before the receive step fails. The Hub files an addressed
|
|
50
|
+
* entry synchronously at submit, so the first drain normally finds it; the retries exist for a
|
|
51
|
+
* replica that has not yet observed its own write, not as a hope-based wait. Bounded, because a
|
|
52
|
+
* diagnostic that hangs is worse than one that fails. */
|
|
53
|
+
const RECEIVE_ATTEMPTS = 5;
|
|
54
|
+
/** Pause between drain attempts. Short: this is a local round-trip, not a poll loop. */
|
|
55
|
+
const RECEIVE_INTERVAL_MS = 750;
|
|
56
|
+
/** The label the throwaway session carries, so a human who sees it in `fleet ls` (or in the web UI,
|
|
57
|
+
* mid-run) knows immediately what created it. */
|
|
58
|
+
const SESSION_LABEL = "oh-hai doctor --self-test";
|
|
59
|
+
function fail(name, detail, exitCode = ExitCode.ERROR) {
|
|
60
|
+
return { name, status: "fail", detail, exitCode };
|
|
61
|
+
}
|
|
62
|
+
function pass(name, detail) {
|
|
63
|
+
return { name, status: "pass", detail };
|
|
64
|
+
}
|
|
65
|
+
function messageOf(error) {
|
|
66
|
+
return error instanceof Error ? error.message : String(error);
|
|
67
|
+
}
|
|
68
|
+
/** A CliError already carries the §7 exit code that describes WHY this failed (auth / usage /
|
|
69
|
+
* network / …). Preserving it means `doctor --self-test` exits with the same code the equivalent
|
|
70
|
+
* standalone command would, so a supervisor branching on the code learns the same thing either way. */
|
|
71
|
+
function exitCodeOf(error) {
|
|
72
|
+
return error instanceof CliError ? error.exitCode : ExitCode.ERROR;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Run the end-to-end self-test and return its ordered steps.
|
|
76
|
+
*
|
|
77
|
+
* NEVER THROWS for a diagnosable failure: a self-test that threw would lose the steps that already
|
|
78
|
+
* passed, which are exactly what tells a human *where* the loop breaks. Every failure becomes a
|
|
79
|
+
* `fail` step carrying its exit code; the caller decides the process exit.
|
|
80
|
+
*/
|
|
81
|
+
export async function runSelfTest(ctx) {
|
|
82
|
+
const steps = [];
|
|
83
|
+
// 0. Identity. Everything downstream needs a bearer + a resolved agent id, and without one there
|
|
84
|
+
// is nothing to test — so this short-circuits rather than producing five confusing failures.
|
|
85
|
+
let token;
|
|
86
|
+
let agentId;
|
|
87
|
+
try {
|
|
88
|
+
({ token, agentId } = await resolveSubmitIdentity(ctx));
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
steps.push(fail("self-test", `cannot run — ${messageOf(error)}`, exitCodeOf(error)));
|
|
92
|
+
return steps;
|
|
93
|
+
}
|
|
94
|
+
// 1. The §8.0 gates, BOTH directions. A pre-v0.5 Hub does not refuse `to` or `?session=`; it
|
|
95
|
+
// ignores them (§10), which would mean this test "passed" against a Hub that cannot do the
|
|
96
|
+
// thing being tested — or, worse, destructively drained the whole principal mailbox. Both gates
|
|
97
|
+
// fail closed on an unreadable capability document.
|
|
98
|
+
try {
|
|
99
|
+
await assertHubSpeaksSessionDrain(ctx);
|
|
100
|
+
await assertHubSpeaksAddressing(ctx, `agent:${agentId}`);
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
steps.push(fail("self-test hub", messageOf(error), exitCodeOf(error)));
|
|
104
|
+
return steps;
|
|
105
|
+
}
|
|
106
|
+
const capability = await fetchCapability(ctx);
|
|
107
|
+
steps.push(pass("self-test hub", `MA2H ${capability?.ma2h_version ?? "0.5+"} · session-scoped drain and addressed sends are both supported`));
|
|
108
|
+
// 2. Register the throwaway session. From here on there is durable Hub state, so every exit path
|
|
109
|
+
// runs the cleanup below.
|
|
110
|
+
let session;
|
|
111
|
+
try {
|
|
112
|
+
session = await registerSession(ctx, token, { run_id: resolveRunId(), kind: "cli", label: SESSION_LABEL });
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
steps.push(fail("self-test session", `could not register a session — ${messageOf(error)}`, exitCodeOf(error)));
|
|
116
|
+
return steps;
|
|
117
|
+
}
|
|
118
|
+
steps.push(pass("self-test session", `registered ${session.id} · addressable at agent:${agentId}#${session.id}`));
|
|
119
|
+
try {
|
|
120
|
+
await runAddressedLoop(ctx, {
|
|
121
|
+
token,
|
|
122
|
+
agentId,
|
|
123
|
+
session,
|
|
124
|
+
steps,
|
|
125
|
+
// The Hub's advertised §9.7 window when it states one, so the freshness check the bridge would
|
|
126
|
+
// apply is the one applied here. Comes from the already-cached capability document — no extra
|
|
127
|
+
// round trip.
|
|
128
|
+
replayWindowSeconds: typeof capability?.replay_window_seconds === "number" && capability.replay_window_seconds > 0
|
|
129
|
+
? capability.replay_window_seconds
|
|
130
|
+
: DEFAULT_REPLAY_WINDOW_SECONDS,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
finally {
|
|
134
|
+
// ALWAYS close it — including after a failure. A leaked session holds a §16.1 live-session slot
|
|
135
|
+
// for the whole lease and, worse, would keep reading `active` to anyone inspecting the fleet
|
|
136
|
+
// while nothing is draining it: a diagnostic must not manufacture the exact false-belief it
|
|
137
|
+
// exists to detect. Reported as a step so a failed cleanup is visible rather than swallowed.
|
|
138
|
+
steps.push(await closeTestSession(ctx, token, session.id));
|
|
139
|
+
}
|
|
140
|
+
return steps;
|
|
141
|
+
}
|
|
142
|
+
/** The send → receive → ack → reachability half, with the session already registered. Appends its
|
|
143
|
+
* steps in place and returns early on the first failure (each step depends on the previous one). */
|
|
144
|
+
async function runAddressedLoop(ctx, args) {
|
|
145
|
+
const { token, agentId, session, steps, replayWindowSeconds } = args;
|
|
146
|
+
const address = `agent:${agentId}#${session.id}`;
|
|
147
|
+
// A nonce in the body, so the receive step can prove it matched THIS run's message rather than a
|
|
148
|
+
// stale entry that happened to be sitting in the mailbox.
|
|
149
|
+
const nonce = randomUUID();
|
|
150
|
+
// 3. Send.
|
|
151
|
+
let ack;
|
|
152
|
+
try {
|
|
153
|
+
ack = await submitEnvelope(ctx, token, buildNotify({
|
|
154
|
+
agent: { id: agentId, run_id: resolveRunId(), session: session.id },
|
|
155
|
+
title: "oh-hai setup self-test",
|
|
156
|
+
body: `Sent by \`oh-hai doctor --self-test\` to prove this agent's session mailbox works. nonce=${nonce}`,
|
|
157
|
+
priority: "low",
|
|
158
|
+
tags: ["self-test"],
|
|
159
|
+
to: address,
|
|
160
|
+
}), { addressedTo: address });
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
steps.push(fail("self-test send", `could not post a self-addressed message — ${messageOf(error)}`, exitCodeOf(error)));
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
// Report the ack verbatim and say what it does NOT mean. `queued` is the §14.2 accept state: the
|
|
167
|
+
// Hub has the message, nobody has seen it. The destination snapshot is the §15.1 reading at send
|
|
168
|
+
// time, and `unknown` there means the Hub has no presence signal — never that the peer is down.
|
|
169
|
+
steps.push(pass("self-test send", `posted ${ack.id} to ${address} · status ${ack.status} (accepted, NOT yet delivered) · ` +
|
|
170
|
+
`destination ${describeSnapshot(ack.destination)}`));
|
|
171
|
+
// 4. Receive — through the same §8.7.1 drain and the same §13.4 duties `oh-hai bridge` runs.
|
|
172
|
+
const policy = {
|
|
173
|
+
agentId,
|
|
174
|
+
sessionId: session.id,
|
|
175
|
+
// A `notify` never reaches the declared-policy gate (it asks nothing of the recipient), so this
|
|
176
|
+
// stays empty on purpose: the self-test must not model a permissive default it would then teach.
|
|
177
|
+
allowFrom: [],
|
|
178
|
+
replayWindowSeconds,
|
|
179
|
+
};
|
|
180
|
+
const guard = new ReplayGuard();
|
|
181
|
+
let received;
|
|
182
|
+
let drainError;
|
|
183
|
+
// Ids of entries this drain claimed that are NOT the probe. A §8.7.1 session drain returns
|
|
184
|
+
// session-addressed entries PLUS the principal's own mail, first-claim-wins — so on a busy agent
|
|
185
|
+
// this diagnostic can lease production mail it has no business consuming (see `foreignClaimStep`).
|
|
186
|
+
// Tracked so the operator is told, never silently.
|
|
187
|
+
const foreign = [];
|
|
188
|
+
for (let attempt = 0; attempt < RECEIVE_ATTEMPTS && received === undefined; attempt += 1) {
|
|
189
|
+
if (attempt > 0)
|
|
190
|
+
await ctx.runtime.sleep(RECEIVE_INTERVAL_MS);
|
|
191
|
+
let entries;
|
|
192
|
+
try {
|
|
193
|
+
entries = await drainInboxSession(ctx, token, session.id);
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
drainError = error;
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
// SCAN THE WHOLE BATCH, even after the probe is found. The lease is taken by the drain call
|
|
200
|
+
// above, on every row it returned — so an entry sitting AFTER the probe in FIFO order is already
|
|
201
|
+
// claimed and already stamped whether this loop looks at it or not. Stopping early would leave it
|
|
202
|
+
// out of the disclosure below while its sender saw a delivery stamp: the silent case this whole
|
|
203
|
+
// step exists to prevent. What stops early is the outer loop — no FURTHER drain is issued.
|
|
204
|
+
for (const entry of entries) {
|
|
205
|
+
if (!("message" in entry) || entry.message.id !== ack.id) {
|
|
206
|
+
// Somebody else's mail, already claimed. Do NOT verify it (a §13.4 failure on an unrelated
|
|
207
|
+
// entry is the real bridge's business, not this probe's, and throwing here would fail a
|
|
208
|
+
// self-test for a fault it merely walked past), and above all do NOT ack it — an unacked
|
|
209
|
+
// entry reverts to queued-visible when the lease lapses.
|
|
210
|
+
foreign.push(foreignIdOf(entry));
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
// THE ID IS NOT ENOUGH. `verifyEntry` cannot close this gap: the `v1=` MAC is keyed with the
|
|
214
|
+
// Hub's key, which this process does not hold, and `body` is optional — so a broken Hub or an
|
|
215
|
+
// on-path proxy could return a structurally valid entry carrying the right id and substituted
|
|
216
|
+
// content, and the probe would report a green round trip for a payload it never sent. The nonce
|
|
217
|
+
// is the only content binding available to a client, so it is checked, and a mismatch is a
|
|
218
|
+
// failure rather than "keep looking" — the right id with the wrong body is a contract fault.
|
|
219
|
+
if (entry.message.body === undefined || !entry.message.body.includes(nonce)) {
|
|
220
|
+
// RECORD, do not break. Same reason the success path scans on: the lease was taken by the
|
|
221
|
+
// drain call over the whole batch, so anything after this entry is already claimed and
|
|
222
|
+
// already stamped whether the loop looks at it or not. Bailing here would fail the probe AND
|
|
223
|
+
// silently swallow the disclosure for mail this run delayed — the worst of both.
|
|
224
|
+
drainError ??= new CliError("server", `the Hub returned an entry with this probe's id (${ack.id}) but without the nonce this run put in its body. ` +
|
|
225
|
+
"The content was substituted or corrupted in transit. This client cannot verify the `v1=` MAC (it is keyed " +
|
|
226
|
+
"with the Hub's key), so the nonce is the only content binding available — and it does not match.");
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
try {
|
|
230
|
+
// Throws on the §13.4 verification classes (exit 12 territory) — a self-test must surface
|
|
231
|
+
// that loudly rather than retry past it, because it means tampering or a broken Hub.
|
|
232
|
+
const verdict = verifyEntry(entry, policy, guard, Date.now());
|
|
233
|
+
if (verdict.kind === "accept")
|
|
234
|
+
received = entry;
|
|
235
|
+
}
|
|
236
|
+
catch (error) {
|
|
237
|
+
// Also record-and-continue, for the same reason: the failure is preserved (first one wins,
|
|
238
|
+
// so the report names what actually went wrong) while the rest of the already-claimed batch
|
|
239
|
+
// is still accounted for.
|
|
240
|
+
drainError ??= error;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (drainError !== undefined)
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
// Report a foreign claim BEFORE the receive verdict, so it is visible whether the probe succeeded
|
|
247
|
+
// or not — the side effect happened either way, and it is the operator's to know about.
|
|
248
|
+
const foreignStep = foreignClaimStep(foreign);
|
|
249
|
+
if (foreignStep !== undefined)
|
|
250
|
+
steps.push(foreignStep);
|
|
251
|
+
if (drainError !== undefined) {
|
|
252
|
+
steps.push(fail("self-test receive", `the session drain failed — ${messageOf(drainError)}`, exitCodeOf(drainError)));
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
if (received === undefined) {
|
|
256
|
+
steps.push(fail("self-test receive", `the message posted to ${address} did not come back on this session's mailbox after ${RECEIVE_ATTEMPTS} drains. ` +
|
|
257
|
+
"The Hub accepted it, so it is queued and un-received — which is exactly the state a running bridge would be " +
|
|
258
|
+
"failing to clear. Check `oh-hai bridge status` and the Hub before trusting this agent to be reachable."));
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
steps.push(pass("self-test receive", `drained ${ack.id} from ${session.id} and passed §13.4 (signature header shape + freshness + replay, entry shape, ` +
|
|
262
|
+
"addressee incl. the session qualifier). The `v1=` MAC is keyed with the Hub's key, which no client holds — it was " +
|
|
263
|
+
"forwarded, not verified."));
|
|
264
|
+
// 5. Ack. This is the step that turns "the Hub handed it over" into "delivered" (§8.7.2), and it
|
|
265
|
+
// is also what stops the Hub redelivering the self-test's own message to the next real bridge.
|
|
266
|
+
try {
|
|
267
|
+
const acked = await ackInboxSession(ctx, token, session.id, [ackKeyOf(received)]);
|
|
268
|
+
if (!acked.includes(ackKeyOf(received))) {
|
|
269
|
+
steps.push(fail("self-test ack", `the Hub did not confirm the ack for ${ackKeyOf(received)} — it will be redelivered.`));
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
catch (error) {
|
|
274
|
+
steps.push(fail("self-test ack", `could not ack the received entry — ${messageOf(error)}`, exitCodeOf(error)));
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
steps.push(pass("self-test ack", `acked ${ackKeyOf(received)} — the round trip is complete and the entry counts as delivered`));
|
|
278
|
+
// 6. Reachability — the Hub's own conclusion, repeated verbatim. This is the number the human sees
|
|
279
|
+
// in `oh-hai fleet ls` and in the web inbox, so quoting it (rather than re-deriving it from
|
|
280
|
+
// `last_seen`) is what keeps every surface agreeing.
|
|
281
|
+
//
|
|
282
|
+
// REQUIRED, not advisory. `--self-test` promises to report the reachability a human will see; a
|
|
283
|
+
// run that never obtained it has not kept that promise, and reporting green anyway would be the
|
|
284
|
+
// same class of overstatement the whole command exists to prevent.
|
|
285
|
+
try {
|
|
286
|
+
const after = await getSession(ctx, token, session.id);
|
|
287
|
+
// An ABSENT snapshot is a contract failure HERE, and only here, which is worth being precise
|
|
288
|
+
// about because this repo's standing rule is the opposite: an absent capability field must never
|
|
289
|
+
// be read as "disabled" (messaging/capability.ts). That rule protects a Hub that never claimed to
|
|
290
|
+
// speak v0.5. This code is past the §8.0 gate, so the Hub HAS declared >= 0.5, and §15.1/#654 put
|
|
291
|
+
// `reachability` on the session resource unconditionally. So absence is not "this Hub is older",
|
|
292
|
+
// it is "the resource is missing a field it must carry" — and passing it through as
|
|
293
|
+
// "not reported" would report green for a run that never obtained the one conclusion the step
|
|
294
|
+
// exists to report.
|
|
295
|
+
const snapshot = after.reachability;
|
|
296
|
+
const snapshotProblem = invalidSnapshot(snapshot);
|
|
297
|
+
// The `snapshot === undefined` arm is redundant with `snapshotProblem` (absence IS one of the
|
|
298
|
+
// reasons) and is present only so the compiler narrows `snapshot` for the state check below.
|
|
299
|
+
if (snapshotProblem !== undefined || snapshot === undefined) {
|
|
300
|
+
steps.push(fail("self-test reachability", `the round trip completed, but the Hub's §15.1 reachability snapshot for session ${after.id} ${snapshotProblem ?? "is absent"}. ` +
|
|
301
|
+
"The reachability a human is about to act on was never validly obtained, so this is not a pass — check the " +
|
|
302
|
+
"Hub (or anything proxying it)."));
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
// A NON-`online` conclusion FAILS — and the reason is worth stating carefully, because this is
|
|
306
|
+
// the one place where the rule "`unknown` is not `offline`" could be mistaken for a licence to
|
|
307
|
+
// pass anything. Nothing here re-interprets `unknown` as down. The point is narrower and harder
|
|
308
|
+
// to argue with: this session was used SECONDS ago — registered, drained and acked, all
|
|
309
|
+
// session-presenting calls that renew the lease and refresh presence — so a conformant Hub reading
|
|
310
|
+
// its own freshly-used session has full visibility and derives `online`. Anything else means the
|
|
311
|
+
// Hub's conclusion CONTRADICTS a round trip that demonstrably just worked, and that conclusion is
|
|
312
|
+
// exactly what `fleet ls` and the web inbox will show the human. Passing would hand them a green
|
|
313
|
+
// result that their own dashboard disagrees with — which is the disagreement this step exists to
|
|
314
|
+
// catch, not an acceptable variation of it.
|
|
315
|
+
// A STALE `online` is still a failure. The server derives `online` from `last_seen` being inside
|
|
316
|
+
// the presence freshness window (services/presence.ts), so a schema-shaped
|
|
317
|
+
// `{state:"online", last_seen:"2000-01-01T…"}` is self-contradicting: the very timestamp that is
|
|
318
|
+
// supposed to anchor the claim disproves it. Anchoring against the session's own `created_at` —
|
|
319
|
+
// both Hub-issued, so no client clock is involved and no skew can false-fail — is the tightest
|
|
320
|
+
// check available: this session was minted THIS run, and the drain and ack that followed are what
|
|
321
|
+
// refreshed presence, so `last_seen` cannot legitimately predate its creation.
|
|
322
|
+
const staleOnline = snapshot.state === "online" &&
|
|
323
|
+
typeof snapshot.last_seen === "string" &&
|
|
324
|
+
Date.parse(snapshot.last_seen) < Date.parse(after.created_at);
|
|
325
|
+
if (staleOnline) {
|
|
326
|
+
steps.push(fail("self-test reachability", `the Hub reported this session \`online\` with last_seen ${sanitizeForTerminal(snapshot.last_seen ?? "")}, which is ` +
|
|
327
|
+
`BEFORE the session itself was created (${sanitizeForTerminal(after.created_at)}). The timestamp that is meant to ` +
|
|
328
|
+
"anchor that conclusion disproves it, so the reachability shown in `oh-hai fleet ls` and the web inbox cannot be " +
|
|
329
|
+
"trusted for this agent. Check the Hub's presence derivation."));
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
if (snapshot.state !== "online") {
|
|
333
|
+
steps.push(fail("self-test reachability", `the round trip worked, but the Hub then reported this session's reachability as ` +
|
|
334
|
+
`${describeSnapshot(snapshot)}. This session was registered, drained and acked seconds ago — all ` +
|
|
335
|
+
"lease-renewing, presence-refreshing calls — so a conformant Hub reading its own freshly-used session derives " +
|
|
336
|
+
"`online`. This is NOT a claim that the agent is down: it is that the Hub's own conclusion, the one " +
|
|
337
|
+
"`oh-hai fleet ls` and the web inbox will show, contradicts a loop that just demonstrably completed. Check the " +
|
|
338
|
+
"Hub's presence derivation before trusting either surface."));
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
steps.push(pass("self-test reachability", `session ${after.state} · reachability ${describeSnapshot(snapshot)} · lease expires ${after.expires_at}. ` +
|
|
342
|
+
"This is what `oh-hai fleet ls` and the web inbox report; a bridge that stops running goes stale here within the " +
|
|
343
|
+
"Hub's freshness window."));
|
|
344
|
+
}
|
|
345
|
+
catch (error) {
|
|
346
|
+
steps.push(fail("self-test reachability", `the round trip completed, but the Hub's reachability snapshot could not be read — ${messageOf(error)}. ` +
|
|
347
|
+
"This check reports the reachability a human will act on, so an unread snapshot is not a pass.", exitCodeOf(error)));
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Close the throwaway session, turning any failure into a reported step. Never throws: it runs in a
|
|
352
|
+
* `finally`, where a throw would replace the real result with the cleanup's error.
|
|
353
|
+
*
|
|
354
|
+
* A FAILED CLOSE FAILS THE SELF-TEST. That is not pedantry about tidiness. Until its lease lapses, a
|
|
355
|
+
* leaked session keeps reading `active` — and, having just been drained and acked, briefly reads
|
|
356
|
+
* *reachable* — to `fleet ls`, the §8.1 ack of anyone addressing it, and the web inbox, while nothing
|
|
357
|
+
* is draining it. A diagnostic whose own residue manufactures the exact false "something is
|
|
358
|
+
* listening" signal it exists to detect must not then report green.
|
|
359
|
+
*/
|
|
360
|
+
async function closeTestSession(ctx, token, id) {
|
|
361
|
+
try {
|
|
362
|
+
const closed = await closeSession(ctx, token, id);
|
|
363
|
+
// A 2xx is not the same as a CLOSED session. `closeSession` validates the resource's shape, not
|
|
364
|
+
// its terminality, so a Hub that answered 200 while leaving the session `active` would let this
|
|
365
|
+
// report "no session left behind" about a session that is still live and still advertising
|
|
366
|
+
// reachability. Assert the outcome, not the status code — §16.3 terminal states are `closed` and
|
|
367
|
+
// `expired`, and either is a genuine "nothing left listening".
|
|
368
|
+
if (closed.state === "active") {
|
|
369
|
+
return fail("self-test cleanup", `the Hub accepted the close of ${id} but still reports it \`active\`. It therefore keeps reading live to ` +
|
|
370
|
+
"`oh-hai fleet ls`, to the web inbox, and to anyone addressing it, with nothing draining it — the false " +
|
|
371
|
+
`"reachable" signal this run created. Re-run \`oh-hai session close --id ${id}\` and check the Hub.`);
|
|
372
|
+
}
|
|
373
|
+
return pass("self-test cleanup", `closed ${closed.id} (${closed.state}) — no session left behind`);
|
|
374
|
+
}
|
|
375
|
+
catch (error) {
|
|
376
|
+
return fail("self-test cleanup", `the self-test session ${id} could NOT be closed — ${messageOf(error)}. Until its lease lapses it still reads ` +
|
|
377
|
+
"live to `oh-hai fleet ls`, to the web inbox, and to anyone addressing it, with nothing draining it — a false " +
|
|
378
|
+
`"reachable" signal this run created. Close it now: \`oh-hai session close --id ${id}\`.`, exitCodeOf(error));
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* The id to DISCLOSE for a foreign entry, defensively.
|
|
383
|
+
*
|
|
384
|
+
* `isInboxEntryDelivery` (the drain's shape guard) only requires the kind key to be an object and
|
|
385
|
+
* `signature` to be a string, so a malformed unrelated payload — `{ directive: {}, signature: "…" }`
|
|
386
|
+
* from a broken Hub or proxy — reaches here with no `id`, and `ackKeyOf` returns `undefined` at
|
|
387
|
+
* runtime despite its `string` type. Feeding that to `sanitizeForTerminal` (which iterates its
|
|
388
|
+
* argument) throws a TypeError out of `runSelfTest`, destroying every step already collected — the
|
|
389
|
+
* exact opposite of this module's "never throws for a diagnosable failure" promise, and over an
|
|
390
|
+
* entry the probe was only ever going to mention. So: extract it as a string or say plainly that
|
|
391
|
+
* there wasn't one. The entry is still never verified and never acked.
|
|
392
|
+
*/
|
|
393
|
+
function foreignIdOf(entry) {
|
|
394
|
+
const key = ackKeyOf(entry);
|
|
395
|
+
return typeof key === "string" && key !== "" ? key : "(an entry the Hub returned with no id)";
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Disclose any of the PRINCIPAL's own mail this probe's drain claimed. Undefined when it claimed none
|
|
399
|
+
* (the ordinary case: a fresh machine has an empty mailbox).
|
|
400
|
+
*
|
|
401
|
+
* THE SIDE EFFECT IS REAL AND CANNOT BE PREVENTED CLIENT-SIDE. A §8.7.1 session drain deliberately
|
|
402
|
+
* returns session-addressed entries PLUS the principal's own mail, first-claim-wins across every
|
|
403
|
+
* session of that agent — that is what lets several bridges share one credential. This diagnostic
|
|
404
|
+
* presents a session, so on a busy agent it can lease an entry meant for a real bridge, and the DRAIN
|
|
405
|
+
* (unlike the §8.7.2 stream claim, which is provisional by construction) is delivery evidence.
|
|
406
|
+
*
|
|
407
|
+
* What is done about it, in order of what actually helps:
|
|
408
|
+
* • Nothing foreign is ever ACKED, so every such entry reverts to queued-visible when its lease
|
|
409
|
+
* lapses. It is delayed, never consumed and never lost.
|
|
410
|
+
* • The drain stops at the probe, so no further batch is claimed.
|
|
411
|
+
* • And it is SAID — with ids — because the one outcome this whole command exists to prevent is a
|
|
412
|
+
* human believing mail was delivered when nobody saw it. A diagnostic that quietly caused that,
|
|
413
|
+
* of all things, would be indefensible.
|
|
414
|
+
*
|
|
415
|
+
* A `warn` rather than a `fail`: the reachability round trip itself is unaffected, and reporting "you
|
|
416
|
+
* are not reachable" would be the opposite lie. Preventing the claim outright needs a probe-scoped
|
|
417
|
+
* drain on the Hub (server lane, tracked separately).
|
|
418
|
+
*/
|
|
419
|
+
function foreignClaimStep(foreign) {
|
|
420
|
+
if (foreign.length === 0)
|
|
421
|
+
return undefined;
|
|
422
|
+
return {
|
|
423
|
+
name: "self-test mailbox",
|
|
424
|
+
status: "warn",
|
|
425
|
+
detail: `this probe's drain also claimed ${foreign.length} entr${foreign.length === 1 ? "y" : "ies"} already queued for this ` +
|
|
426
|
+
`agent (${foreign.map((id) => sanitizeForTerminal(id)).join(", ")}). A §8.7.1 session drain claims the principal's ` +
|
|
427
|
+
"mail first-claim-wins, so a diagnostic cannot avoid this on a busy agent. NONE of them were acked, so each returns " +
|
|
428
|
+
"to the queue when its visibility lease lapses and a real bridge receives it then — delayed, not lost. Prefer running " +
|
|
429
|
+
"`--self-test` when no bridge is mid-flight.",
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Why a §15.1 snapshot is unusable, or undefined when it is well-formed.
|
|
434
|
+
*
|
|
435
|
+
* The SESSION-READ sibling of `destinationOf`'s validation in `messaging/http.ts`, and the reasoning
|
|
436
|
+
* carries over one-for-one: the v0.5 schema pins the enum AND the state/timestamp relationship in
|
|
437
|
+
* both directions, and each direction is load-bearing. An out-of-enum `state` ("reachable") is a
|
|
438
|
+
* claim the protocol cannot express; `unknown` carrying a `last_seen` contradicts itself (a timestamp
|
|
439
|
+
* IS visibility); and `online`/`offline` WITHOUT one is an unanchored assertion — online as of when?
|
|
440
|
+
* `isSession` (sessions-http.ts) deliberately guards only the fields it acts on, so nothing upstream
|
|
441
|
+
* checks this — and rendering an invalid snapshot verbatim would let `--self-test` report a green
|
|
442
|
+
* reachability reading that means nothing, which is the whole failure this command exists to prevent.
|
|
443
|
+
*
|
|
444
|
+
* Absence is included here rather than handled separately because, past the §8.0 gate, it is the same
|
|
445
|
+
* class of answer: the Hub has declared >= 0.5, and §15.1/#654 put the snapshot on the resource
|
|
446
|
+
* unconditionally.
|
|
447
|
+
*/
|
|
448
|
+
function invalidSnapshot(snapshot) {
|
|
449
|
+
if (snapshot === undefined) {
|
|
450
|
+
return "is absent, though a Hub declaring MA2H 0.5 must carry it on the session resource";
|
|
451
|
+
}
|
|
452
|
+
const { state, last_seen: lastSeen } = snapshot;
|
|
453
|
+
if (state !== "online" && state !== "offline" && state !== "unknown") {
|
|
454
|
+
return `declares an unknown state ${JSON.stringify(sanitizeForTerminal(String(state)))} (expected online | offline | unknown)`;
|
|
455
|
+
}
|
|
456
|
+
if (state === "unknown" && lastSeen !== undefined) {
|
|
457
|
+
return "reports `unknown` while carrying a last_seen, which the v0.5 schema forbids — a timestamp IS visibility";
|
|
458
|
+
}
|
|
459
|
+
if (state !== "unknown" && !(typeof lastSeen === "string" && lastSeen.trim() !== "" && !Number.isNaN(Date.parse(lastSeen)))) {
|
|
460
|
+
return `reports \`${state}\` without the last_seen timestamp the v0.5 schema requires to anchor it`;
|
|
461
|
+
}
|
|
462
|
+
return undefined;
|
|
463
|
+
}
|
|
464
|
+
/** Render a §15 destination/reachability snapshot without ever dressing `unknown` up as liveness. */
|
|
465
|
+
function describeSnapshot(snapshot) {
|
|
466
|
+
if (snapshot === undefined)
|
|
467
|
+
return "not reported by this Hub";
|
|
468
|
+
if (snapshot.state === "unknown")
|
|
469
|
+
return "unknown (the Hub has no presence signal — this is NOT 'offline')";
|
|
470
|
+
return snapshot.last_seen !== undefined ? `${snapshot.state} (last seen ${snapshot.last_seen})` : snapshot.state;
|
|
471
|
+
}
|
|
472
|
+
//# sourceMappingURL=self-test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"self-test.js","sourceRoot":"","sources":["../../src/commands/self-test.ts"],"names":[],"mappings":"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"}
|