@jr2/orchestrator 0.2.0 → 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/package.json +3 -4
- package/src/actor.ts +12 -7
- package/src/agent.ts +2 -2
- package/src/config.ts +119 -14
- package/src/custodian.lua +312 -0
- package/src/custodian.ts +899 -0
- package/src/harness-client.ts +30 -10
- package/src/held-secrets.ts +587 -0
- package/src/http.ts +25 -19
- package/src/images.ts +11 -10
- package/src/index.ts +6 -2
- package/src/instance.ts +13 -10
- package/src/model-keys.ts +92 -0
- package/src/names.ts +30 -6
- package/src/registration.ts +11 -3
- package/src/repo-fetch.ts +1 -1
- package/src/run-host.ts +34 -21
- package/src/sandbox-kubectl.ts +82 -110
- package/src/server.ts +5 -17
- package/src/tokens.ts +40 -9
- package/src/workspace.ts +2 -2
package/src/harness-client.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// socket-free through an injected `fetch`.
|
|
11
11
|
//
|
|
12
12
|
// Channel split (ADR-0002, refined by ADR-0016): the Harness wire carries **lifecycle only** —
|
|
13
|
-
// domain events go up the
|
|
13
|
+
// domain events go up the Menu, through the pod's Custodian. The client's `send`/`wait` pair is exactly
|
|
14
14
|
// that lifecycle surface: `send` answers with a serializable Admission
|
|
15
15
|
// (`{ streamUrl, offset, submissionId }` — jr2's durable re-attach handle, stored in the host
|
|
16
16
|
// ledger), and `wait(admission)` follows the durable stream from the admission offset to the
|
|
@@ -80,6 +80,10 @@ export type HarnessClient = {
|
|
|
80
80
|
export type HarnessClientOptions = {
|
|
81
81
|
/** The Harness base URL — what a workspace publishes, or a stub's `url`. */
|
|
82
82
|
baseUrl: string;
|
|
83
|
+
/** The Harness bearer for this placement (ADR-0058; `harnessToken` in tokens.ts) — sent on
|
|
84
|
+
* every call, the long-poll included. Absent only where no placement exists: an explicit-
|
|
85
|
+
* `endpoint` run against the stub Harness, which checks nothing. */
|
|
86
|
+
token?: string;
|
|
83
87
|
/** Injectable for socket-free tests. Default: global `fetch`. */
|
|
84
88
|
fetch?: typeof fetch;
|
|
85
89
|
/** Reconnect backoff floor/ceiling (capped exponential, jittered per rung — see `jittered`;
|
|
@@ -125,6 +129,7 @@ export function createHarnessClient(options: HarnessClientOptions): HarnessClien
|
|
|
125
129
|
const backoffMaxMs = options.backoffMaxMs ?? 5_000;
|
|
126
130
|
const admitWindowMs = options.admitWindowMs ?? 90_000;
|
|
127
131
|
const log = options.log ?? ((line: string) => console.warn(line));
|
|
132
|
+
const authorization: Record<string, string> = options.token ? { authorization: `Bearer ${options.token}` } : {};
|
|
128
133
|
|
|
129
134
|
const conversationUrl = (agentName: string, instanceId: string) =>
|
|
130
135
|
new URL(`/agents/${encodeURIComponent(agentName)}/${encodeURIComponent(instanceId)}`, baseUrl).toString();
|
|
@@ -192,7 +197,7 @@ export function createHarnessClient(options: HarnessClientOptions): HarnessClien
|
|
|
192
197
|
url,
|
|
193
198
|
{
|
|
194
199
|
method: "POST",
|
|
195
|
-
headers: { "content-type": "application/json" },
|
|
200
|
+
headers: { "content-type": "application/json", ...authorization },
|
|
196
201
|
body: JSON.stringify({
|
|
197
202
|
message: sendOptions.message,
|
|
198
203
|
// The other half of the Frame (ADR-0057): where this Turn works. Omitted only when
|
|
@@ -238,13 +243,21 @@ export function createHarnessClient(options: HarnessClientOptions): HarnessClien
|
|
|
238
243
|
let events: StreamEvent[];
|
|
239
244
|
let nextOffset: string | null;
|
|
240
245
|
try {
|
|
241
|
-
const res = await fetchImpl(url.toString(), { signal });
|
|
246
|
+
const res = await fetchImpl(url.toString(), { headers: authorization, signal });
|
|
242
247
|
if (res.status === 404) {
|
|
243
248
|
throw new SettlementFault(
|
|
244
249
|
`conversation lost: the harness answered 404 for submission "${admission.submissionId}" — ` +
|
|
245
250
|
`a conversation lives as long as its Harness process (ADR-0027)`,
|
|
246
251
|
);
|
|
247
252
|
}
|
|
253
|
+
// Refused, not unreachable: the bearer is derived, so a Harness that rejects it now
|
|
254
|
+
// rejects it on every retry — reconnecting would park the Turn forever, silently.
|
|
255
|
+
if (res.status === 401 || res.status === 403) {
|
|
256
|
+
throw new SettlementFault(
|
|
257
|
+
`harness refused the stream read (${res.status}) for submission "${admission.submissionId}": ` +
|
|
258
|
+
`${await errorDetail(res)} — the bearer is not this placement's (ADR-0058)`,
|
|
259
|
+
);
|
|
260
|
+
}
|
|
248
261
|
if (!res.ok && res.status !== 204) {
|
|
249
262
|
throw new ReconnectableError(`harness stream read failed (${res.status})`);
|
|
250
263
|
}
|
|
@@ -274,6 +287,7 @@ export function createHarnessClient(options: HarnessClientOptions): HarnessClien
|
|
|
274
287
|
async abort(agentName, instanceId, opts) {
|
|
275
288
|
const res = await fetchImpl(`${conversationUrl(agentName, instanceId)}/abort`, {
|
|
276
289
|
method: "POST",
|
|
290
|
+
headers: authorization,
|
|
277
291
|
signal: opts?.signal,
|
|
278
292
|
});
|
|
279
293
|
if (!res.ok) {
|
|
@@ -334,16 +348,16 @@ export function createHarnessAgentRunClient(options: HarnessClientOptions): Agen
|
|
|
334
348
|
|
|
335
349
|
/**
|
|
336
350
|
* The run-narrative echo push (ADR-0023): `POST /echo { events }` against one Workspace Harness,
|
|
337
|
-
* bearing
|
|
338
|
-
*
|
|
351
|
+
* bearing that placement's Harness bearer (ADR-0058 — the Harness verifies it against a sha-256,
|
|
352
|
+
* never holding it at rest). The payload is the
|
|
339
353
|
* STRUCTURED feed events; the Harness renders. A non-OK answer rejects, and the CALLER treats
|
|
340
354
|
* that as log-and-continue — fire-and-forget lives in the tee (run-host.ts), not here, so a test
|
|
341
355
|
* can still assert a push failed.
|
|
342
356
|
*/
|
|
343
357
|
export function createEchoPush(options: {
|
|
344
358
|
baseUrl: string;
|
|
345
|
-
/** The
|
|
346
|
-
token
|
|
359
|
+
/** The Workspace's Harness bearer (ADR-0058). Absent only against the stub Harness. */
|
|
360
|
+
token?: string;
|
|
347
361
|
/** Injectable for socket-free tests. Default: global `fetch`. */
|
|
348
362
|
fetch?: typeof fetch;
|
|
349
363
|
}): (events: EchoEvent[]) => Promise<void> {
|
|
@@ -356,7 +370,10 @@ export function createEchoPush(options: {
|
|
|
356
370
|
// `fetch failed` in the pod log is what let that condition hide.
|
|
357
371
|
const res = await fetchImpl(url, {
|
|
358
372
|
method: "POST",
|
|
359
|
-
headers: {
|
|
373
|
+
headers: {
|
|
374
|
+
"content-type": "application/json",
|
|
375
|
+
...(options.token ? { authorization: `Bearer ${options.token}` } : {}),
|
|
376
|
+
},
|
|
360
377
|
body: JSON.stringify({ events }),
|
|
361
378
|
}).catch((err: unknown) => {
|
|
362
379
|
throw new Error(`harness echo to ${url} failed: ${transportDetail(err)}`, { cause: err });
|
|
@@ -386,7 +403,10 @@ export function createEchoPush(options: {
|
|
|
386
403
|
* still an `AgentDefinition`: the actor narrows on start and refuses an Open one there.
|
|
387
404
|
*/
|
|
388
405
|
export function agent(declaration: AgentDeclaration): AgentLogic {
|
|
389
|
-
return agentActorWith(
|
|
406
|
+
return agentActorWith(
|
|
407
|
+
(endpoint, bearer) => createHarnessAgentRunClient({ baseUrl: endpoint, ...(bearer ? { token: bearer } : {}) }),
|
|
408
|
+
declaration,
|
|
409
|
+
);
|
|
390
410
|
}
|
|
391
411
|
|
|
392
412
|
/** A stream read worth retrying (server hiccup) — internal to the reconnect loop, never thrown out. */
|
|
@@ -457,7 +477,7 @@ async function errorDetail(res: Response): Promise<string> {
|
|
|
457
477
|
|
|
458
478
|
/**
|
|
459
479
|
* The marker the `@kind` tier greps for, and therefore a CONTRACT — duplicated verbatim in
|
|
460
|
-
* `@jr2/
|
|
480
|
+
* `@jr2/harness`'s Menu (the two packages share no runtime dependency) and matched in
|
|
461
481
|
* `features/steps/kind.steps.ts`. Renaming it on one side does not break a build; it silently turns
|
|
462
482
|
* the tier's routability budget into a check that passes because it matches nothing.
|
|
463
483
|
*/
|