@mnemom/mnemom 0.17.37 → 0.17.38-next.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/dist/commands/agent-run.d.ts +11 -0
- package/dist/commands/agent-run.js +37 -0
- package/dist/commands/agent-share-render.d.ts +5 -5
- package/dist/commands/agent-share-render.js +9 -7
- package/dist/commands/agent-share-test-issuer.d.ts +1 -1
- package/dist/commands/agent-share-test-issuer.js +1 -1
- package/dist/commands/agent.d.ts +8 -1
- package/dist/commands/agent.js +14 -3
- package/dist/commands/scan.d.ts +15 -2
- package/dist/commands/scan.js +48 -3
- package/dist/index.js +1 -1
- package/dist/lib/commit-report.d.ts +7 -13
- package/dist/lib/commit-report.js +12 -5
- package/dist/lib/conduct-receipt.d.ts +8 -3
- package/dist/lib/conduct-receipt.js +29 -7
- package/dist/lib/experimental.d.ts +1 -1
- package/dist/lib/experimental.js +1 -12
- package/dist/lib/runner/executor-client.d.ts +5 -0
- package/dist/lib/runner/executor-client.js +8 -0
- package/dist/lib/runner/job.d.ts +37 -0
- package/dist/lib/runner/job.js +74 -1
- package/dist/lib/runner/phases.d.ts +7 -2
- package/dist/lib/runner/phases.js +7 -2
- package/dist/lib/runner/proxy.d.ts +26 -2
- package/dist/lib/runner/proxy.js +33 -1
- package/dist/lib/runner/record-commit.d.ts +16 -0
- package/dist/lib/runner/record-commit.js +7 -0
- package/dist/lib/scan/spend/anthropic-usage.d.ts +20 -1
- package/dist/lib/scan/spend/anthropic-usage.js +32 -7
- package/dist/lib/scan/spend/anthropic-wif.d.ts +42 -0
- package/dist/lib/scan/spend/anthropic-wif.js +153 -0
- package/package.json +2 -5
- package/node_modules/@mnemom/aip-verifier/LICENSE +0 -190
- package/node_modules/@mnemom/aip-verifier/dist/claim-lint-CVITIRcU.d.cts +0 -478
- package/node_modules/@mnemom/aip-verifier/dist/claim-lint-CVITIRcU.d.ts +0 -478
- package/node_modules/@mnemom/aip-verifier/dist/index.cjs +0 -971
- package/node_modules/@mnemom/aip-verifier/dist/index.cjs.map +0 -1
- package/node_modules/@mnemom/aip-verifier/dist/index.d.cts +0 -330
- package/node_modules/@mnemom/aip-verifier/dist/index.d.ts +0 -330
- package/node_modules/@mnemom/aip-verifier/dist/index.js +0 -933
- package/node_modules/@mnemom/aip-verifier/dist/index.js.map +0 -1
- package/node_modules/@mnemom/aip-verifier/dist/render-lines.cjs +0 -879
- package/node_modules/@mnemom/aip-verifier/dist/render-lines.cjs.map +0 -1
- package/node_modules/@mnemom/aip-verifier/dist/render-lines.d.cts +0 -96
- package/node_modules/@mnemom/aip-verifier/dist/render-lines.d.ts +0 -96
- package/node_modules/@mnemom/aip-verifier/dist/render-lines.js +0 -875
- package/node_modules/@mnemom/aip-verifier/dist/render-lines.js.map +0 -1
- package/node_modules/@mnemom/aip-verifier/package.json +0 -86
- package/node_modules/@noble/ed25519/LICENSE +0 -21
- package/node_modules/@noble/ed25519/README.md +0 -371
- package/node_modules/@noble/ed25519/index.d.ts +0 -391
- package/node_modules/@noble/ed25519/index.js +0 -933
- package/node_modules/@noble/ed25519/index.ts +0 -1134
- package/node_modules/@noble/ed25519/package.json +0 -54
- package/node_modules/@noble/hashes/LICENSE +0 -21
- package/node_modules/@noble/hashes/README.md +0 -478
- package/node_modules/@noble/hashes/_blake.d.ts +0 -20
- package/node_modules/@noble/hashes/_blake.js +0 -59
- package/node_modules/@noble/hashes/_md.d.ts +0 -95
- package/node_modules/@noble/hashes/_md.js +0 -209
- package/node_modules/@noble/hashes/_u64.d.ts +0 -36
- package/node_modules/@noble/hashes/_u64.js +0 -77
- package/node_modules/@noble/hashes/argon2.d.ts +0 -148
- package/node_modules/@noble/hashes/argon2.js +0 -577
- package/node_modules/@noble/hashes/blake1.d.ts +0 -162
- package/node_modules/@noble/hashes/blake1.js +0 -630
- package/node_modules/@noble/hashes/blake2.d.ts +0 -203
- package/node_modules/@noble/hashes/blake2.js +0 -573
- package/node_modules/@noble/hashes/blake3.d.ts +0 -68
- package/node_modules/@noble/hashes/blake3.js +0 -310
- package/node_modules/@noble/hashes/eskdf.d.ts +0 -91
- package/node_modules/@noble/hashes/eskdf.js +0 -225
- package/node_modules/@noble/hashes/hkdf.d.ts +0 -70
- package/node_modules/@noble/hashes/hkdf.js +0 -152
- package/node_modules/@noble/hashes/hmac.d.ts +0 -50
- package/node_modules/@noble/hashes/hmac.js +0 -93
- package/node_modules/@noble/hashes/index.d.ts +0 -1
- package/node_modules/@noble/hashes/index.js +0 -34
- package/node_modules/@noble/hashes/legacy.d.ts +0 -107
- package/node_modules/@noble/hashes/legacy.js +0 -348
- package/node_modules/@noble/hashes/package.json +0 -91
- package/node_modules/@noble/hashes/pbkdf2.d.ts +0 -66
- package/node_modules/@noble/hashes/pbkdf2.js +0 -176
- package/node_modules/@noble/hashes/scrypt.d.ts +0 -98
- package/node_modules/@noble/hashes/scrypt.js +0 -304
- package/node_modules/@noble/hashes/sha2.d.ts +0 -176
- package/node_modules/@noble/hashes/sha2.js +0 -446
- package/node_modules/@noble/hashes/sha3-addons.d.ts +0 -533
- package/node_modules/@noble/hashes/sha3-addons.js +0 -973
- package/node_modules/@noble/hashes/sha3.d.ts +0 -208
- package/node_modules/@noble/hashes/sha3.js +0 -475
- package/node_modules/@noble/hashes/src/_blake.ts +0 -67
- package/node_modules/@noble/hashes/src/_md.ts +0 -227
- package/node_modules/@noble/hashes/src/_u64.ts +0 -111
- package/node_modules/@noble/hashes/src/argon2.ts +0 -693
- package/node_modules/@noble/hashes/src/blake1.ts +0 -677
- package/node_modules/@noble/hashes/src/blake2.ts +0 -648
- package/node_modules/@noble/hashes/src/blake3.ts +0 -344
- package/node_modules/@noble/hashes/src/eskdf.ts +0 -265
- package/node_modules/@noble/hashes/src/hkdf.ts +0 -161
- package/node_modules/@noble/hashes/src/hmac.ts +0 -130
- package/node_modules/@noble/hashes/src/index.ts +0 -33
- package/node_modules/@noble/hashes/src/legacy.ts +0 -361
- package/node_modules/@noble/hashes/src/pbkdf2.ts +0 -219
- package/node_modules/@noble/hashes/src/scrypt.ts +0 -375
- package/node_modules/@noble/hashes/src/sha2.ts +0 -486
- package/node_modules/@noble/hashes/src/sha3-addons.ts +0 -1157
- package/node_modules/@noble/hashes/src/sha3.ts +0 -525
- package/node_modules/@noble/hashes/src/utils.ts +0 -978
- package/node_modules/@noble/hashes/src/webcrypto.ts +0 -283
- package/node_modules/@noble/hashes/utils.d.ts +0 -560
- package/node_modules/@noble/hashes/utils.js +0 -687
- package/node_modules/@noble/hashes/webcrypto.d.ts +0 -121
- package/node_modules/@noble/hashes/webcrypto.js +0 -228
package/dist/lib/runner/job.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { type HttpResult, type RunnerApi } from "./executor-client.js";
|
|
|
15
15
|
import { type OutcomeFields, type RefusedReason } from "./outcome.js";
|
|
16
16
|
import { type PhaseInvocation } from "./phases.js";
|
|
17
17
|
import { type ModelProxy, type ProxyState } from "./proxy.js";
|
|
18
|
+
import type { RecordCommitBody } from "./record-commit.js";
|
|
18
19
|
import { type RunnerFailMode } from "./settings.js";
|
|
19
20
|
/** A claimed job's working copy. */
|
|
20
21
|
export interface Workspace {
|
|
@@ -45,6 +46,13 @@ export interface JobDeps {
|
|
|
45
46
|
openPr(repo: string, ws: Workspace, title: string, body: string): Promise<string>;
|
|
46
47
|
/** Commits on the job branch that the base does not have; 0 means there is nothing to open. */
|
|
47
48
|
commitsAhead(ws: Workspace): Promise<number>;
|
|
49
|
+
/** The working copy's HEAD sha before a phase runs; null only for a clone with no commit. Throws otherwise. */
|
|
50
|
+
headSha(ws: Workspace): Promise<string | null>;
|
|
51
|
+
/**
|
|
52
|
+
* RecordCommit/v1 bodies for the commits made since `since` (null: since the base), oldest
|
|
53
|
+
* first, under `lineageKey` and the job's repo. A commit that cannot be read is left out.
|
|
54
|
+
*/
|
|
55
|
+
commitBodies(ws: Workspace, since: string | null, lineageKey: string, repo: string): Promise<RecordCommitBody[]>;
|
|
48
56
|
enableAutoMerge(prUrl: string): Promise<void>;
|
|
49
57
|
cleanup(ws: Workspace): Promise<void>;
|
|
50
58
|
}
|
|
@@ -69,6 +77,35 @@ export declare function authRefusal(r: HttpResult): RefusedReason | null;
|
|
|
69
77
|
* outcome. Throws only on a runner bug.
|
|
70
78
|
*/
|
|
71
79
|
export declare function runJob(sessionId: string, opts: JobOptions, deps: JobDeps): Promise<JobResult>;
|
|
80
|
+
/** What the job reported to Record's commit index; logged once, never part of the outcome. */
|
|
81
|
+
export interface CommitReport {
|
|
82
|
+
reported: number;
|
|
83
|
+
/** Refused by the api (4xx other than 404) or unreachable: left out of the index. */
|
|
84
|
+
dropped: number;
|
|
85
|
+
/** Made in a phase whose model calls left no lineage key, so there was nothing to file under. */
|
|
86
|
+
unlinked: number;
|
|
87
|
+
/** Phases whose starting HEAD could not be read: their commits are never guessed at. */
|
|
88
|
+
skipped: number;
|
|
89
|
+
/** The index answered 404 (Record dark for the org): nothing more is sent for this job. */
|
|
90
|
+
dark: boolean;
|
|
91
|
+
}
|
|
92
|
+
/** Waits before each retry of a 429, 5xx or unreachable report; then the commit is dropped. */
|
|
93
|
+
export declare const COMMIT_RETRY_MS: readonly [1000, 3000];
|
|
94
|
+
/**
|
|
95
|
+
* Report the commits one phase made (ruling 34.05 (a)): each under the lineage key the gateway
|
|
96
|
+
* answered for that phase's conversation, with the lease key, from this process. Never throws and
|
|
97
|
+
* never changes the outcome; the lease key is sent only as the request credential, never logged.
|
|
98
|
+
*/
|
|
99
|
+
export declare function reportPhaseCommits(deps: JobDeps, report: CommitReport, p: {
|
|
100
|
+
ws: Workspace;
|
|
101
|
+
/** The HEAD before the phase; null: a clone with no commit; undefined: it could not be read. */
|
|
102
|
+
before: string | null | undefined;
|
|
103
|
+
conversationId: string;
|
|
104
|
+
proxy: Pick<ModelProxy, "lineageFor">;
|
|
105
|
+
orgId: string;
|
|
106
|
+
repo: string;
|
|
107
|
+
leaseKey: string;
|
|
108
|
+
}): Promise<void>;
|
|
72
109
|
/**
|
|
73
110
|
* Report a failed job after claim (34.12 (6), (10)); a refusal releases the lease instead. The
|
|
74
111
|
* fail reason vocabulary is the Durable lead's to rule (A3 accepts only transcript_lost today); a
|
package/dist/lib/runner/job.js
CHANGED
|
@@ -20,7 +20,7 @@ import { errorCode, errorDetails } from "./executor-client.js";
|
|
|
20
20
|
import { doneOutcome, failedOutcome, phaseReason, refusedOutcome, } from "./outcome.js";
|
|
21
21
|
import { planPhase } from "./phases.js";
|
|
22
22
|
import { canonNodeKey, prBody } from "./pr-body.js";
|
|
23
|
-
import { requestSummary } from "./proxy.js";
|
|
23
|
+
import { phaseConversationId, requestSummary } from "./proxy.js";
|
|
24
24
|
import { resolveMergeStrategy } from "./settings.js";
|
|
25
25
|
const TOLERATED = RUNNER_OPEN_MODE_TOLERATED;
|
|
26
26
|
const OPEN_MODE_RETRIES = 3;
|
|
@@ -189,6 +189,13 @@ export async function runJob(sessionId, opts, deps) {
|
|
|
189
189
|
let leaseLost = false;
|
|
190
190
|
let ws = null;
|
|
191
191
|
let proxy = null;
|
|
192
|
+
const commits = {
|
|
193
|
+
reported: 0,
|
|
194
|
+
dropped: 0,
|
|
195
|
+
unlinked: 0,
|
|
196
|
+
skipped: 0,
|
|
197
|
+
dark: false,
|
|
198
|
+
};
|
|
192
199
|
let heartbeat = null;
|
|
193
200
|
const phaseAbort = new AbortController();
|
|
194
201
|
const onStop = () => phaseAbort.abort();
|
|
@@ -303,10 +310,17 @@ export async function runJob(sessionId, opts, deps) {
|
|
|
303
310
|
contractValue: header.value,
|
|
304
311
|
leaseKey: claim.leaseKey,
|
|
305
312
|
agentId: job.agent_id,
|
|
313
|
+
conversationId: phaseConversationId(sessionId, claim.epoch, phases[0].order),
|
|
306
314
|
};
|
|
307
315
|
proxy = await deps.startProxy(state);
|
|
308
316
|
let opensPr = false;
|
|
309
317
|
for (const phase of phases) {
|
|
318
|
+
state.conversationId = phaseConversationId(sessionId, claim.epoch, phase.order);
|
|
319
|
+
// undefined: the HEAD could not be read, so this phase's commits cannot be told apart.
|
|
320
|
+
const before = await deps.headSha(ws).catch((e) => {
|
|
321
|
+
deps.log(`reading HEAD before phase ${phase.order} failed: ${e.message}`);
|
|
322
|
+
return undefined;
|
|
323
|
+
});
|
|
310
324
|
const planned = planPhase(phase);
|
|
311
325
|
if (!planned.ok) {
|
|
312
326
|
deps.log(`phase ${phase.order} ${phase.name}: ${planned.detail}`);
|
|
@@ -354,6 +368,15 @@ export async function runJob(sessionId, opts, deps) {
|
|
|
354
368
|
}
|
|
355
369
|
break;
|
|
356
370
|
}
|
|
371
|
+
await reportPhaseCommits(deps, commits, {
|
|
372
|
+
ws,
|
|
373
|
+
before,
|
|
374
|
+
conversationId: state.conversationId,
|
|
375
|
+
proxy,
|
|
376
|
+
orgId: bundle.org_id,
|
|
377
|
+
repo: job.repo,
|
|
378
|
+
leaseKey: claim.leaseKey,
|
|
379
|
+
});
|
|
357
380
|
}
|
|
358
381
|
if (opensPr) {
|
|
359
382
|
try {
|
|
@@ -390,12 +413,62 @@ export async function runJob(sessionId, opts, deps) {
|
|
|
390
413
|
await proxy.close();
|
|
391
414
|
// One line per job that reached its phases: what every model request carried (W3.11).
|
|
392
415
|
deps.log(`model requests: ${JSON.stringify(requestSummary(proxy.forwarded))}`);
|
|
416
|
+
deps.log(`commits reported: ${JSON.stringify(commits)}`);
|
|
393
417
|
}
|
|
394
418
|
// A workspace left behind never changes the outcome; it is logged for the operator.
|
|
395
419
|
if (ws)
|
|
396
420
|
await deps.cleanup(ws).catch((e) => deps.log(`cleanup failed: ${e.message}`));
|
|
397
421
|
}
|
|
398
422
|
}
|
|
423
|
+
/** Waits before each retry of a 429, 5xx or unreachable report; then the commit is dropped. */
|
|
424
|
+
export const COMMIT_RETRY_MS = [1_000, 3_000];
|
|
425
|
+
/**
|
|
426
|
+
* Report the commits one phase made (ruling 34.05 (a)): each under the lineage key the gateway
|
|
427
|
+
* answered for that phase's conversation, with the lease key, from this process. Never throws and
|
|
428
|
+
* never changes the outcome; the lease key is sent only as the request credential, never logged.
|
|
429
|
+
*/
|
|
430
|
+
export async function reportPhaseCommits(deps, report, p) {
|
|
431
|
+
if (report.dark)
|
|
432
|
+
return;
|
|
433
|
+
if (p.before === undefined) {
|
|
434
|
+
report.skipped += 1;
|
|
435
|
+
deps.log(`${p.conversationId}: commits not reported, the phase's starting HEAD is unknown`);
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
try {
|
|
439
|
+
const lineageKey = p.proxy.lineageFor(p.conversationId);
|
|
440
|
+
if (lineageKey === null) {
|
|
441
|
+
const n = (await deps.commitBodies(p.ws, p.before, "-", p.repo)).length;
|
|
442
|
+
report.unlinked += n;
|
|
443
|
+
if (n > 0)
|
|
444
|
+
deps.log(`${n} commit(s) in ${p.conversationId} have no lineage key: not reported`);
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
for (const body of await deps.commitBodies(p.ws, p.before, lineageKey, p.repo)) {
|
|
448
|
+
let res = await deps.api.recordCommit(p.orgId, body, p.leaseKey);
|
|
449
|
+
for (const wait of COMMIT_RETRY_MS) {
|
|
450
|
+
if (!(res.status === 0 || res.status === 429 || res.status >= 500))
|
|
451
|
+
break;
|
|
452
|
+
await deps.sleep(wait);
|
|
453
|
+
res = await deps.api.recordCommit(p.orgId, body, p.leaseKey);
|
|
454
|
+
}
|
|
455
|
+
if (res.status >= 200 && res.status < 300)
|
|
456
|
+
report.reported += 1;
|
|
457
|
+
else if (res.status === 404) {
|
|
458
|
+
report.dark = true;
|
|
459
|
+
deps.log("the commit index answered 404 (Record is off for this org): commits not reported");
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
else {
|
|
463
|
+
report.dropped += 1;
|
|
464
|
+
deps.log(`commit ${body.sha.slice(0, 12)} not reported: ${res.code ?? res.networkError ?? res.status}`);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
catch (e) {
|
|
469
|
+
deps.log(`commit report failed: ${e.message}`);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
399
472
|
async function fetchBundle(deps, sessionId, claim) {
|
|
400
473
|
const [jwksRes, res] = await Promise.all([
|
|
401
474
|
deps.api.jwks(),
|
|
@@ -15,8 +15,13 @@ export declare const CAPABILITY_TOOLS: Readonly<Record<string, {
|
|
|
15
15
|
tools: string[];
|
|
16
16
|
allowed: string[];
|
|
17
17
|
}>>;
|
|
18
|
-
/**
|
|
19
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Env names a phase may inherit: a working PATH, the shell and locale. Nothing else. Claude Code's
|
|
20
|
+
* Bash tool needs SHELL to name a POSIX shell; without it, on an image with no bash on PATH (Alpine),
|
|
21
|
+
* every Bash call fails and a phase can write files but never commit them (door2-path image run
|
|
22
|
+
* 41940973594 ended pr_no_changes).
|
|
23
|
+
*/
|
|
24
|
+
export declare const PHASE_ENV_PASSTHROUGH: readonly ["PATH", "SHELL", "LANG", "TZ"];
|
|
20
25
|
/** Not a secret: lets claude start; the proxy drops it before the gateway (ruling: option (i)). */
|
|
21
26
|
export declare const PHASE_MODEL_KEY_PLACEHOLDER = "mnemom-runner-placeholder";
|
|
22
27
|
export type ToolGrant = {
|
|
@@ -21,8 +21,13 @@ export const CAPABILITY_TOOLS = Object.freeze({
|
|
|
21
21
|
// The runner opens the PR itself once every phase has passed; the model gets no tool for it.
|
|
22
22
|
"pr.open": { tools: [], allowed: [] },
|
|
23
23
|
});
|
|
24
|
-
/**
|
|
25
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Env names a phase may inherit: a working PATH, the shell and locale. Nothing else. Claude Code's
|
|
26
|
+
* Bash tool needs SHELL to name a POSIX shell; without it, on an image with no bash on PATH (Alpine),
|
|
27
|
+
* every Bash call fails and a phase can write files but never commit them (door2-path image run
|
|
28
|
+
* 41940973594 ended pr_no_changes).
|
|
29
|
+
*/
|
|
30
|
+
export const PHASE_ENV_PASSTHROUGH = ["PATH", "SHELL", "LANG", "TZ"];
|
|
26
31
|
/** Not a secret: lets claude start; the proxy drops it before the gateway (ruling: option (i)). */
|
|
27
32
|
export const PHASE_MODEL_KEY_PLACEHOLDER = "mnemom-runner-placeholder";
|
|
28
33
|
export function toolsFor(capabilityIds) {
|
|
@@ -7,24 +7,39 @@
|
|
|
7
7
|
* Anthropic key in x-api-key (ruling 34.30.1; the phase only ever holds a placeholder), the job read's agent id in
|
|
8
8
|
* x-mnemom-agent-id (the gateway checks it against the lease's session), and the job's
|
|
9
9
|
* sealed contract goes in `x-mnemom-contract` (exactly one, the sealed version). The phase never
|
|
10
|
-
* sees the lease key.
|
|
10
|
+
* sees the lease key. Each phase gets its own `x-mnemom-conversation-id` (from the session, lease
|
|
11
|
+
* epoch and phase order), so the gateway keys one lineage per phase instead of a body-hash one per
|
|
12
|
+
* request, and the `x-mnemom-lineage-key` it answers is kept for the phase's commit report. A 409 `contract_mismatch` from the gateway (34.11) is reported to the job,
|
|
11
13
|
* which refetches and continues only on a higher sealed version, else fails closed.
|
|
12
14
|
*/
|
|
13
15
|
import { request as httpRequest, type IncomingMessage, type ServerResponse } from "node:http";
|
|
14
16
|
export declare const LEASE_KEY_HEADER = "x-mnemom-api-key";
|
|
15
17
|
export declare const AGENT_ID_HEADER = "x-mnemom-agent-id";
|
|
18
|
+
export declare const CONVERSATION_ID_HEADER = "x-mnemom-conversation-id";
|
|
19
|
+
export declare const LINEAGE_KEY_HEADER = "x-mnemom-lineage-key";
|
|
20
|
+
/**
|
|
21
|
+
* One phase's conversation id: the session, the lease epoch and the phase order, so a retried
|
|
22
|
+
* claim or another phase never shares a lineage. Within the gateway's 1-256 printable, no-`#` id.
|
|
23
|
+
*/
|
|
24
|
+
export declare function phaseConversationId(sessionId: string, epoch: number, order: number): string;
|
|
16
25
|
export interface ProxyState {
|
|
17
26
|
/** The sealed header value to send; swapped in place after an accepted amend. */
|
|
18
27
|
contractValue: string;
|
|
19
28
|
leaseKey: string;
|
|
20
29
|
/** The session's agent, from the job read; always sent, a phase's own value never is. */
|
|
21
30
|
agentId: string;
|
|
31
|
+
/** The running phase's conversation id; the job sets it before each phase. */
|
|
32
|
+
conversationId: string;
|
|
22
33
|
}
|
|
23
34
|
export interface ForwardedRequest {
|
|
24
35
|
method: string;
|
|
25
36
|
path: string;
|
|
26
37
|
contract: string;
|
|
27
38
|
status: number;
|
|
39
|
+
/** The conversation id the request carried. */
|
|
40
|
+
conversation: string;
|
|
41
|
+
/** The gateway's `x-mnemom-lineage-key` answer, when it sent a well-formed one. */
|
|
42
|
+
lineage: string | null;
|
|
28
43
|
}
|
|
29
44
|
export interface ModelProxy {
|
|
30
45
|
url: string;
|
|
@@ -34,17 +49,26 @@ export interface ModelProxy {
|
|
|
34
49
|
contractMismatch: {
|
|
35
50
|
expected_version: number | null;
|
|
36
51
|
} | null;
|
|
52
|
+
/** The lineage key the gateway last answered for a conversation id, or null when none came. */
|
|
53
|
+
lineageFor(conversationId: string): string | null;
|
|
37
54
|
close(): Promise<void>;
|
|
38
55
|
}
|
|
39
56
|
/** Outbound headers for one request: the phase's own, minus anything the proxy decides. */
|
|
40
57
|
export declare function outboundHeaders(incoming: IncomingMessage["headers"], state: ProxyState, modelKey: string): Record<string, string | string[] | undefined>;
|
|
41
|
-
/**
|
|
58
|
+
/**
|
|
59
|
+
* What the job's model requests carried: each contract by its sha256, in first-seen order, and the
|
|
60
|
+
* lineage key the gateway answered for each conversation (one per phase), in first-seen order.
|
|
61
|
+
*/
|
|
42
62
|
export interface RequestSummary {
|
|
43
63
|
count: number;
|
|
44
64
|
contracts: {
|
|
45
65
|
contract_sha256: string;
|
|
46
66
|
requests: number;
|
|
47
67
|
}[];
|
|
68
|
+
lineages: {
|
|
69
|
+
conversation_id: string;
|
|
70
|
+
lineage_key: string | null;
|
|
71
|
+
}[];
|
|
48
72
|
}
|
|
49
73
|
/**
|
|
50
74
|
* One job's forwarded requests, summarised for the operator's log and for the door2-path header
|
package/dist/lib/runner/proxy.js
CHANGED
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
* Anthropic key in x-api-key (ruling 34.30.1; the phase only ever holds a placeholder), the job read's agent id in
|
|
8
8
|
* x-mnemom-agent-id (the gateway checks it against the lease's session), and the job's
|
|
9
9
|
* sealed contract goes in `x-mnemom-contract` (exactly one, the sealed version). The phase never
|
|
10
|
-
* sees the lease key.
|
|
10
|
+
* sees the lease key. Each phase gets its own `x-mnemom-conversation-id` (from the session, lease
|
|
11
|
+
* epoch and phase order), so the gateway keys one lineage per phase instead of a body-hash one per
|
|
12
|
+
* request, and the `x-mnemom-lineage-key` it answers is kept for the phase's commit report. A 409 `contract_mismatch` from the gateway (34.11) is reported to the job,
|
|
11
13
|
* which refetches and continues only on a higher sealed version, else fails closed.
|
|
12
14
|
*/
|
|
13
15
|
import { createServer, request as httpRequest, } from "node:http";
|
|
@@ -16,6 +18,17 @@ import { createHash } from "node:crypto";
|
|
|
16
18
|
import { applySealedContract, CONTRACT_HEADER } from "./contract-header.js";
|
|
17
19
|
export const LEASE_KEY_HEADER = "x-mnemom-api-key";
|
|
18
20
|
export const AGENT_ID_HEADER = "x-mnemom-agent-id";
|
|
21
|
+
export const CONVERSATION_ID_HEADER = "x-mnemom-conversation-id";
|
|
22
|
+
export const LINEAGE_KEY_HEADER = "x-mnemom-lineage-key";
|
|
23
|
+
/** The gateway's lineage key as echoed: short, printable, no spaces. Anything else is not kept. */
|
|
24
|
+
const LINEAGE_KEY_RE = /^[\x21-\x7e]{1,256}$/;
|
|
25
|
+
/**
|
|
26
|
+
* One phase's conversation id: the session, the lease epoch and the phase order, so a retried
|
|
27
|
+
* claim or another phase never shares a lineage. Within the gateway's 1-256 printable, no-`#` id.
|
|
28
|
+
*/
|
|
29
|
+
export function phaseConversationId(sessionId, epoch, order) {
|
|
30
|
+
return `durable-${sessionId}-e${epoch}-p${order}`;
|
|
31
|
+
}
|
|
19
32
|
/** Hop-by-hop headers (RFC 9110 7.6.1) and the ones the proxy owns. */
|
|
20
33
|
const DROP = new Set([
|
|
21
34
|
"connection",
|
|
@@ -43,8 +56,16 @@ export function outboundHeaders(incoming, state, modelKey) {
|
|
|
43
56
|
kept[LEASE_KEY_HEADER] = state.leaseKey;
|
|
44
57
|
kept["x-api-key"] = modelKey;
|
|
45
58
|
kept[AGENT_ID_HEADER] = state.agentId;
|
|
59
|
+
kept[CONVERSATION_ID_HEADER] = state.conversationId;
|
|
46
60
|
return applySealedContract(kept, state.contractValue);
|
|
47
61
|
}
|
|
62
|
+
/** The last well-formed lineage key answered for each conversation, in first-seen order. */
|
|
63
|
+
function lineagesOf(forwarded) {
|
|
64
|
+
const m = new Map();
|
|
65
|
+
for (const f of forwarded)
|
|
66
|
+
m.set(f.conversation, f.lineage ?? m.get(f.conversation) ?? null);
|
|
67
|
+
return m;
|
|
68
|
+
}
|
|
48
69
|
/**
|
|
49
70
|
* One job's forwarded requests, summarised for the operator's log and for the door2-path header
|
|
50
71
|
* check (W3.11): the sha256 of each header value's decoded bytes is the contract it sealed to.
|
|
@@ -58,6 +79,10 @@ export function requestSummary(forwarded) {
|
|
|
58
79
|
return {
|
|
59
80
|
count: forwarded.length,
|
|
60
81
|
contracts: [...by].map(([contract_sha256, requests]) => ({ contract_sha256, requests })),
|
|
82
|
+
lineages: [...lineagesOf(forwarded)].map(([conversation_id, lineage_key]) => ({
|
|
83
|
+
conversation_id,
|
|
84
|
+
lineage_key,
|
|
85
|
+
})),
|
|
61
86
|
};
|
|
62
87
|
}
|
|
63
88
|
/** The largest 409 body the proxy reads for a contract mismatch. */
|
|
@@ -102,11 +127,13 @@ export async function startModelProxy(gatewayUrl, state, modelKey) {
|
|
|
102
127
|
url: "",
|
|
103
128
|
forwarded,
|
|
104
129
|
contractMismatch: null,
|
|
130
|
+
lineageFor: (id) => lineagesOf(forwarded).get(id) ?? null,
|
|
105
131
|
close: async () => { },
|
|
106
132
|
};
|
|
107
133
|
const server = createServer((req, res) => {
|
|
108
134
|
const headers = outboundHeaders(req.headers, state, modelKey);
|
|
109
135
|
const sent = headers[CONTRACT_HEADER];
|
|
136
|
+
const conversation = state.conversationId;
|
|
110
137
|
const out = send({
|
|
111
138
|
protocol: upstream.protocol,
|
|
112
139
|
hostname: upstream.hostname,
|
|
@@ -116,11 +143,14 @@ export async function startModelProxy(gatewayUrl, state, modelKey) {
|
|
|
116
143
|
headers,
|
|
117
144
|
}, (up) => {
|
|
118
145
|
const status = up.statusCode ?? 502;
|
|
146
|
+
const echoed = up.headers[LINEAGE_KEY_HEADER];
|
|
119
147
|
forwarded.push({
|
|
120
148
|
method: req.method ?? "GET",
|
|
121
149
|
path: req.url ?? "/",
|
|
122
150
|
contract: sent,
|
|
123
151
|
status,
|
|
152
|
+
conversation,
|
|
153
|
+
lineage: typeof echoed === "string" && LINEAGE_KEY_RE.test(echoed) ? echoed : null,
|
|
124
154
|
});
|
|
125
155
|
if (status === 409) {
|
|
126
156
|
// Buffered to read the mismatch, up to a cap; a larger body is relayed unread.
|
|
@@ -162,6 +192,8 @@ export async function startModelProxy(gatewayUrl, state, modelKey) {
|
|
|
162
192
|
path: req.url ?? "/",
|
|
163
193
|
contract: sent,
|
|
164
194
|
status: 502,
|
|
195
|
+
conversation,
|
|
196
|
+
lineage: null,
|
|
165
197
|
});
|
|
166
198
|
upstreamFailed(res, e);
|
|
167
199
|
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The RecordCommit/v1 body Record's commit index takes (`POST /v1/orgs/{org}/record/commits`).
|
|
3
|
+
* It lives here so the runner can type its commit report without loading the CLI's commit reporter
|
|
4
|
+
* (which spawns git and reads auth); lib/commit-report.ts re-exports both names.
|
|
5
|
+
*/
|
|
6
|
+
/** The commit index body's schema tag (shared with the fleet worker's reporter). */
|
|
7
|
+
export declare const COMMIT_SCHEMA = "RecordCommit/v1";
|
|
8
|
+
/** RecordCommit/v1: strings only, every field required; the route rejects an unknown field. */
|
|
9
|
+
export interface RecordCommitBody {
|
|
10
|
+
schema: typeof COMMIT_SCHEMA;
|
|
11
|
+
lineage_key: string;
|
|
12
|
+
repo: string;
|
|
13
|
+
branch: string;
|
|
14
|
+
sha: string;
|
|
15
|
+
patch_id: string;
|
|
16
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The RecordCommit/v1 body Record's commit index takes (`POST /v1/orgs/{org}/record/commits`).
|
|
3
|
+
* It lives here so the runner can type its commit report without loading the CLI's commit reporter
|
|
4
|
+
* (which spawns git and reads auth); lib/commit-report.ts re-exports both names.
|
|
5
|
+
*/
|
|
6
|
+
/** The commit index body's schema tag (shared with the fleet worker's reporter). */
|
|
7
|
+
export const COMMIT_SCHEMA = "RecordCommit/v1";
|
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
export declare const ANTHROPIC_ADMIN_ORIGIN = "https://api.anthropic.com";
|
|
2
2
|
export declare const ADMIN_KEY_ENV = "ANTHROPIC_ADMIN_KEY";
|
|
3
|
+
/** The only paths a usage credential is ever sent to (design T18: fenced to report endpoints). */
|
|
4
|
+
export declare const REPORT_PATHS: readonly string[];
|
|
5
|
+
/** The fence, as one exported check so it is directly testable. Throws AnthropicUsageError for any other path. */
|
|
6
|
+
export declare function assertReportPath(path: string): void;
|
|
7
|
+
/** The exact auth header to send; same shape as mnemom-api src/org-scan/usage/credential-source.ts. */
|
|
8
|
+
export type AnthropicAuthHeader = {
|
|
9
|
+
header: "x-api-key";
|
|
10
|
+
value: string;
|
|
11
|
+
} | {
|
|
12
|
+
header: "authorization";
|
|
13
|
+
value: string;
|
|
14
|
+
};
|
|
3
15
|
export declare const DEFAULT_MAX_PAGES = 20;
|
|
4
16
|
/** One shared deadline for a request including its retries (not N x timeout). */
|
|
5
17
|
export declare const DEFAULT_TIMEOUT_MS = 15000;
|
|
6
18
|
export declare const DEFAULT_MAX_RETRIES = 2;
|
|
7
19
|
/** Shape check only; the key is never echoed, even on a mismatch. */
|
|
8
20
|
export declare const isAdminKeyShape: (k: unknown) => k is string;
|
|
9
|
-
export type UsageFailure = "key_invalid" | "key_refused" | "rate_limited" | "timeout" | "network" | "upstream" | "malformed" | "too_large";
|
|
21
|
+
export type UsageFailure = "key_invalid" | "key_refused" | "token_refused" | "rate_limited" | "timeout" | "network" | "upstream" | "malformed" | "too_large";
|
|
10
22
|
/** Fixed sentence for a failure reason. */
|
|
11
23
|
export declare const usageFailureText: (r: UsageFailure) => string;
|
|
12
24
|
/** Carries only a reason tag. Its message is the fixed sentence: nothing upstream can reach it. */
|
|
@@ -55,3 +67,10 @@ export declare function fetchMeasuredUsage(key: string | undefined, period: {
|
|
|
55
67
|
start: string;
|
|
56
68
|
end: string;
|
|
57
69
|
}, deps?: AnthropicUsageDeps): Promise<MeasuredUsage>;
|
|
70
|
+
/** A WIF access token as the bearer for the two report reads. Printable ASCII only, else key_invalid. */
|
|
71
|
+
export declare function bearerAuth(token: string): AnthropicAuthHeader;
|
|
72
|
+
/** The two reads with an already-resolved credential (pasted key or WIF bearer). Throws AnthropicUsageError only. */
|
|
73
|
+
export declare function fetchMeasuredUsageWithAuth(auth: AnthropicAuthHeader, period: {
|
|
74
|
+
start: string;
|
|
75
|
+
end: string;
|
|
76
|
+
}, deps?: AnthropicUsageDeps): Promise<MeasuredUsage>;
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
// MNE-9298 Tier 1: the CTO's own Anthropic Admin key, used LOCALLY, to read measured usage. Port of
|
|
2
2
|
// mnemom-api src/org-scan/usage/usage-client.ts (two read-only GETs: usage_report/messages grouped by
|
|
3
|
-
// context_window, and cost_report) with the server-only parts (egress allowlist
|
|
3
|
+
// context_window, and cost_report) with the server-only parts (egress allowlist) dropped.
|
|
4
|
+
//
|
|
5
|
+
// MNE-9706: the reads take either credential, as the server's credential-source.ts does: the pasted Admin
|
|
6
|
+
// key (x-api-key) or a short-lived WIF access token (Authorization: Bearer, see anthropic-wif.ts). Either
|
|
7
|
+
// one is sent ONLY to the two report paths below; walk() refuses any other path (the fence, design T18:
|
|
8
|
+
// Anthropic's WIF scope for these reports is org:admin, so the fence has to live here).
|
|
4
9
|
//
|
|
5
10
|
// The key is held in memory for the length of the two reads. It is never stored, logged, written to disk,
|
|
6
11
|
// echoed, put in argv, a URL or an error message, and is sent only to api.anthropic.com (a constant, not
|
|
@@ -11,6 +16,13 @@ export const ADMIN_KEY_ENV = "ANTHROPIC_ADMIN_KEY";
|
|
|
11
16
|
const ANTHROPIC_VERSION = "2023-06-01";
|
|
12
17
|
const USAGE_PATH = "/v1/organizations/usage_report/messages";
|
|
13
18
|
const COST_PATH = "/v1/organizations/cost_report";
|
|
19
|
+
/** The only paths a usage credential is ever sent to (design T18: fenced to report endpoints). */
|
|
20
|
+
export const REPORT_PATHS = [USAGE_PATH, COST_PATH];
|
|
21
|
+
/** The fence, as one exported check so it is directly testable. Throws AnthropicUsageError for any other path. */
|
|
22
|
+
export function assertReportPath(path) {
|
|
23
|
+
if (!REPORT_PATHS.includes(path))
|
|
24
|
+
throw new AnthropicUsageError("malformed");
|
|
25
|
+
}
|
|
14
26
|
export const DEFAULT_MAX_PAGES = 20;
|
|
15
27
|
/** One shared deadline for a request including its retries (not N x timeout). */
|
|
16
28
|
export const DEFAULT_TIMEOUT_MS = 15_000;
|
|
@@ -22,6 +34,7 @@ export const isAdminKeyShape = (k) => typeof k === "string" && ADMIN_KEY_RE.test
|
|
|
22
34
|
const FAILURE_TEXT = {
|
|
23
35
|
key_invalid: "The Anthropic admin key is not an Admin API key (it should start with sk-ant-admin).",
|
|
24
36
|
key_refused: "Anthropic refused the admin key (401/403).",
|
|
37
|
+
token_refused: "Anthropic refused the workload identity token for the usage reports (401/403). The federation rule needs scope org:admin.",
|
|
25
38
|
rate_limited: "Anthropic rate limit reached.",
|
|
26
39
|
timeout: "Anthropic did not answer in time.",
|
|
27
40
|
network: "Anthropic was unreachable.",
|
|
@@ -96,14 +109,14 @@ function retryWait(res, attempt) {
|
|
|
96
109
|
return Number.isFinite(ra) && ra > 0 ? ra * 1000 : backoff(attempt);
|
|
97
110
|
}
|
|
98
111
|
class Reader {
|
|
99
|
-
|
|
112
|
+
auth;
|
|
100
113
|
fetchImpl;
|
|
101
114
|
sleep;
|
|
102
115
|
maxPages;
|
|
103
116
|
timeoutMs;
|
|
104
117
|
maxRetries;
|
|
105
|
-
constructor(
|
|
106
|
-
this.
|
|
118
|
+
constructor(auth, deps) {
|
|
119
|
+
this.auth = auth;
|
|
107
120
|
this.fetchImpl = deps.fetchImpl ?? ((...a) => fetch(...a));
|
|
108
121
|
this.sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
109
122
|
this.maxPages = deps.maxPages ?? DEFAULT_MAX_PAGES;
|
|
@@ -111,6 +124,8 @@ class Reader {
|
|
|
111
124
|
this.maxRetries = deps.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
112
125
|
}
|
|
113
126
|
async walk(path, base, parse, callMaxPages) {
|
|
127
|
+
// The fence: the credential goes to the two report paths and nowhere else.
|
|
128
|
+
assertReportPath(path);
|
|
114
129
|
const ceiling = typeof callMaxPages === "number" && Number.isFinite(callMaxPages) && callMaxPages >= 1
|
|
115
130
|
? Math.min(this.maxPages, Math.floor(callMaxPages))
|
|
116
131
|
: this.maxPages;
|
|
@@ -154,7 +169,7 @@ class Reader {
|
|
|
154
169
|
res = await this.fetchImpl(url, {
|
|
155
170
|
method: "GET",
|
|
156
171
|
headers: {
|
|
157
|
-
|
|
172
|
+
[this.auth.header]: this.auth.value,
|
|
158
173
|
"anthropic-version": ANTHROPIC_VERSION,
|
|
159
174
|
accept: "application/json",
|
|
160
175
|
},
|
|
@@ -185,7 +200,7 @@ class Reader {
|
|
|
185
200
|
}
|
|
186
201
|
await res.body?.cancel().catch(() => { });
|
|
187
202
|
if (res.status === 401 || res.status === 403)
|
|
188
|
-
throw new AnthropicUsageError("key_refused");
|
|
203
|
+
throw new AnthropicUsageError(this.auth.header === "x-api-key" ? "key_refused" : "token_refused");
|
|
189
204
|
const retryable = res.status === 429 || res.status >= 500;
|
|
190
205
|
const wait = retryable ? retryWait(res, attempt + 1) : 0;
|
|
191
206
|
if (retryable &&
|
|
@@ -209,11 +224,21 @@ export async function fetchMeasuredUsage(key, period, deps = {}) {
|
|
|
209
224
|
const k = key?.trim();
|
|
210
225
|
if (!k || !isAdminKeyShape(k))
|
|
211
226
|
throw new AnthropicUsageError("key_invalid");
|
|
227
|
+
return fetchMeasuredUsageWithAuth({ header: "x-api-key", value: k }, period, deps);
|
|
228
|
+
}
|
|
229
|
+
/** A WIF access token as the bearer for the two report reads. Printable ASCII only, else key_invalid. */
|
|
230
|
+
export function bearerAuth(token) {
|
|
231
|
+
if (!/^[\x21-\x7e]{8,4096}$/.test(token))
|
|
232
|
+
throw new AnthropicUsageError("key_invalid");
|
|
233
|
+
return { header: "authorization", value: `Bearer ${token}` };
|
|
234
|
+
}
|
|
235
|
+
/** The two reads with an already-resolved credential (pasted key or WIF bearer). Throws AnthropicUsageError only. */
|
|
236
|
+
export async function fetchMeasuredUsageWithAuth(auth, period, deps = {}) {
|
|
212
237
|
if (!validDay(period.start) || !validDay(period.end))
|
|
213
238
|
throw new AnthropicUsageError("malformed");
|
|
214
239
|
const startingAt = alignStartingAt(`${period.start}T00:00:00Z`);
|
|
215
240
|
const endingAt = new Date(Date.parse(`${period.end}T00:00:00Z`) + DAY_MS).toISOString();
|
|
216
|
-
const reader = new Reader(
|
|
241
|
+
const reader = new Reader(auth, deps);
|
|
217
242
|
const up = new URLSearchParams({
|
|
218
243
|
starting_at: startingAt,
|
|
219
244
|
ending_at: endingAt,
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export declare const WIF_EXCHANGE_URL = "https://api.anthropic.com/v1/oauth/token";
|
|
2
|
+
export declare const WIF_ENV: {
|
|
3
|
+
readonly rule: "ANTHROPIC_FEDERATION_RULE_ID";
|
|
4
|
+
readonly org: "ANTHROPIC_ORGANIZATION_ID";
|
|
5
|
+
readonly serviceAccount: "ANTHROPIC_SERVICE_ACCOUNT_ID";
|
|
6
|
+
readonly workspace: "ANTHROPIC_WORKSPACE_ID";
|
|
7
|
+
};
|
|
8
|
+
/** Same ceiling the server held a WIF token to (credential-source.ts MAX_WIF_TOKEN_LIFETIME_MS). */
|
|
9
|
+
export declare const MAX_WIF_TOKEN_LIFETIME_MS = 300000;
|
|
10
|
+
export interface WifConfig {
|
|
11
|
+
federationRuleId: string;
|
|
12
|
+
organizationId: string;
|
|
13
|
+
serviceAccountId: string;
|
|
14
|
+
workspaceId?: string;
|
|
15
|
+
}
|
|
16
|
+
export type WifFailure = "config_invalid" | "assertion_unavailable" | "assertion_refused" | "exchange_refused" | "exchange_failed";
|
|
17
|
+
export declare const wifFailureText: (r: WifFailure) => string;
|
|
18
|
+
export declare const WIF_FALLBACK_NOTE = "Falling back to the pasted Anthropic Admin key.";
|
|
19
|
+
/** Carries only a reason tag; its message is the fixed sentence. */
|
|
20
|
+
export declare class WifError extends Error {
|
|
21
|
+
readonly reason: WifFailure;
|
|
22
|
+
constructor(reason: WifFailure);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Reads the WIF ids from env. None of the three required ids set: null (WIF is not set up, no message).
|
|
26
|
+
* Some set, or any malformed: WifError("config_invalid"), so a half-done setup is said out loud.
|
|
27
|
+
*/
|
|
28
|
+
export declare function readWifConfig(env: NodeJS.ProcessEnv): WifConfig | null;
|
|
29
|
+
export interface WifDeps {
|
|
30
|
+
/** Step 1 seam: returns the assertion for the org. Defaults to the mnemom-api route with the CLI's auth. */
|
|
31
|
+
mintAssertion?: (orgId: string) => Promise<unknown>;
|
|
32
|
+
/** Step 2 seam: the fetch used ONLY for the Anthropic exchange. */
|
|
33
|
+
fetchImpl?: typeof fetch;
|
|
34
|
+
now?: () => number;
|
|
35
|
+
}
|
|
36
|
+
export interface WifAccessToken {
|
|
37
|
+
accessToken: string;
|
|
38
|
+
/** Epoch ms, clamped to MAX_WIF_TOKEN_LIFETIME_MS from receipt. */
|
|
39
|
+
expiresAt: number;
|
|
40
|
+
}
|
|
41
|
+
/** Steps 1 and 2. Throws WifError only. */
|
|
42
|
+
export declare function obtainWifAccessToken(orgId: string, cfg: WifConfig, deps?: WifDeps): Promise<WifAccessToken>;
|