@claudexor/orchestrator 3.5.0 → 3.8.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/account-pool.d.ts +62 -0
- package/dist/account-pool.d.ts.map +1 -0
- package/dist/account-pool.js +103 -0
- package/dist/account-pool.js.map +1 -0
- package/dist/account-resolution.d.ts +88 -0
- package/dist/account-resolution.d.ts.map +1 -0
- package/dist/account-resolution.js +327 -0
- package/dist/account-resolution.js.map +1 -0
- package/dist/attemptOutputMarkers.d.ts +30 -0
- package/dist/attemptOutputMarkers.d.ts.map +1 -0
- package/dist/attemptOutputMarkers.js +31 -0
- package/dist/attemptOutputMarkers.js.map +1 -0
- package/dist/attemptTelemetry.d.ts +5 -4
- package/dist/attemptTelemetry.d.ts.map +1 -1
- package/dist/attemptTelemetry.js +4 -2
- package/dist/attemptTelemetry.js.map +1 -1
- package/dist/credential-cooldown.d.ts +131 -0
- package/dist/credential-cooldown.d.ts.map +1 -0
- package/dist/credential-cooldown.js +169 -0
- package/dist/credential-cooldown.js.map +1 -0
- package/dist/credential-differential.d.ts +81 -0
- package/dist/credential-differential.d.ts.map +1 -0
- package/dist/credential-differential.js +110 -0
- package/dist/credential-differential.js.map +1 -0
- package/dist/credential-preflight.d.ts +36 -0
- package/dist/credential-preflight.d.ts.map +1 -0
- package/dist/credential-preflight.js +78 -0
- package/dist/credential-preflight.js.map +1 -0
- package/dist/credential-profile-rotation.d.ts +80 -54
- package/dist/credential-profile-rotation.d.ts.map +1 -1
- package/dist/credential-profile-rotation.js +191 -163
- package/dist/credential-profile-rotation.js.map +1 -1
- package/dist/credential-profiles.d.ts +3 -49
- package/dist/credential-profiles.d.ts.map +1 -1
- package/dist/credential-profiles.js +2 -46
- package/dist/credential-profiles.js.map +1 -1
- package/dist/deepScanReducer.d.ts.map +1 -1
- package/dist/deepScanReducer.js +2 -1
- package/dist/deepScanReducer.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/laneStreamEvents.d.ts +54 -0
- package/dist/laneStreamEvents.d.ts.map +1 -0
- package/dist/laneStreamEvents.js +81 -0
- package/dist/laneStreamEvents.js.map +1 -0
- package/dist/modelGovernance.d.ts +35 -4
- package/dist/modelGovernance.d.ts.map +1 -1
- package/dist/modelGovernance.js +71 -27
- package/dist/modelGovernance.js.map +1 -1
- package/dist/orchestrator-credentials.d.ts +93 -0
- package/dist/orchestrator-credentials.d.ts.map +1 -0
- package/dist/orchestrator-credentials.js +162 -0
- package/dist/orchestrator-credentials.js.map +1 -0
- package/dist/orchestrator.d.ts +17 -30
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +293 -344
- package/dist/orchestrator.js.map +1 -1
- package/dist/plannerAttempt.d.ts.map +1 -1
- package/dist/plannerAttempt.js +2 -1
- package/dist/plannerAttempt.js.map +1 -1
- package/dist/rotation-predicate.d.ts +71 -0
- package/dist/rotation-predicate.d.ts.map +1 -0
- package/dist/rotation-predicate.js +29 -0
- package/dist/rotation-predicate.js.map +1 -0
- package/dist/routeContext.d.ts +4 -1
- package/dist/routeContext.d.ts.map +1 -1
- package/dist/routeContext.js +8 -3
- package/dist/routeContext.js.map +1 -1
- package/package.json +17 -17
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { CredentialProfile, QuotaSnapshot } from "@claudexor/schema";
|
|
2
|
+
/**
|
|
3
|
+
* Quota-aware ACCOUNT POOL of the unified account model (INV-135 rewrite,
|
|
4
|
+
* owner decisions D-U1 + K.5): with no explicit pin and no thread binding, an
|
|
5
|
+
* unpinned run routes to the best enabled+ready account row of its harness.
|
|
6
|
+
*
|
|
7
|
+
* Ranking (D3 freshness preserved — stale quota never authorizes routing):
|
|
8
|
+
* 1. rows with FRESH model-applicable quota evidence and headroom below the
|
|
9
|
+
* exhaustion bound, by known headroom DESCENDING;
|
|
10
|
+
* 2. rows with unknown/stale quota (eligible, but never ranked as known
|
|
11
|
+
* headroom);
|
|
12
|
+
* 3. exhausted rows (fresh evidence at/over the policy threshold) — never
|
|
13
|
+
* selected; they only count toward "the pool is exhausted".
|
|
14
|
+
* Ties break deterministically by profile id (ascending).
|
|
15
|
+
*
|
|
16
|
+
* The pool contains SUBSCRIPTION-kind rows only: an api_key row is a paid
|
|
17
|
+
* route and is never silently selected (INV-061); it remains an explicit pin
|
|
18
|
+
* target. Pool exhaustion is the typed `credential_pool_exhausted` terminal
|
|
19
|
+
* (owner Q3=A); the paid API-key ROUTE serves it only under the EXPLICIT
|
|
20
|
+
* api_key preference — never silently under auto.
|
|
21
|
+
*/
|
|
22
|
+
export type PoolQuotaVerdict = {
|
|
23
|
+
kind: "fresh_headroom";
|
|
24
|
+
headroom: number;
|
|
25
|
+
} | {
|
|
26
|
+
kind: "unknown";
|
|
27
|
+
} | {
|
|
28
|
+
kind: "exhausted";
|
|
29
|
+
resets_at: string | null;
|
|
30
|
+
};
|
|
31
|
+
export interface PoolCandidate {
|
|
32
|
+
profile: CredentialProfile;
|
|
33
|
+
verdict: PoolQuotaVerdict;
|
|
34
|
+
}
|
|
35
|
+
/** The subscription-kind enabled rows of one harness (the static pool). */
|
|
36
|
+
export declare function accountPoolRows(registry: readonly CredentialProfile[], harnessId: string): CredentialProfile[];
|
|
37
|
+
/**
|
|
38
|
+
* Rank one harness's READY pool rows per the unified-model comparator. Rows
|
|
39
|
+
* outside `readyProfileIds` are excluded entirely (not ready ≠ exhausted).
|
|
40
|
+
*/
|
|
41
|
+
export declare function rankAccountPool(args: {
|
|
42
|
+
registry: readonly CredentialProfile[];
|
|
43
|
+
harnessId: string;
|
|
44
|
+
snapshots: readonly QuotaSnapshot[];
|
|
45
|
+
readyProfileIds: ReadonlySet<string>;
|
|
46
|
+
headroomThreshold: number;
|
|
47
|
+
model?: string | null;
|
|
48
|
+
}): PoolCandidate[];
|
|
49
|
+
export type PoolSelection = {
|
|
50
|
+
outcome: "selected";
|
|
51
|
+
candidate: PoolCandidate;
|
|
52
|
+
} | {
|
|
53
|
+
/** Every ready row is exhausted, or nothing is ready at all: the run
|
|
54
|
+
* terminalizes on the typed `credential_pool_exhausted` refusal (Q3=A)
|
|
55
|
+
* unless the EXPLICIT api_key preference opts into the paid route. */
|
|
56
|
+
outcome: "exhausted" | "empty";
|
|
57
|
+
/** Earliest known window reset among exhausted rows, when any. */
|
|
58
|
+
resets_at: string | null;
|
|
59
|
+
};
|
|
60
|
+
/** Select the unpinned-run account from the pool (or report why not). */
|
|
61
|
+
export declare function selectFromAccountPool(args: Parameters<typeof rankAccountPool>[0]): PoolSelection;
|
|
62
|
+
//# sourceMappingURL=account-pool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"account-pool.d.ts","sourceRoot":"","sources":["../src/account-pool.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAK1E;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,MAAM,MAAM,gBAAgB,GACxB;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAC5C;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AAEpD,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,iBAAiB,CAAC;IAC3B,OAAO,EAAE,gBAAgB,CAAC;CAC3B;AAED,2EAA2E;AAC3E,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,SAAS,iBAAiB,EAAE,EACtC,SAAS,EAAE,MAAM,GAChB,iBAAiB,EAAE,CAKrB;AAyBD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE;IACpC,QAAQ,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,SAAS,aAAa,EAAE,CAAC;IACpC,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACrC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB,GAAG,aAAa,EAAE,CAgElB;AAED,MAAM,MAAM,aAAa,GACrB;IAAE,OAAO,EAAE,UAAU,CAAC;IAAC,SAAS,EAAE,aAAa,CAAA;CAAE,GACjD;IACE;;0EAEsE;IACtE,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC;IAC/B,kEAAkE;IAClE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B,CAAC;AAEN,yEAAyE;AACzE,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC,CAAC,CAAC,GAAG,aAAa,CAahG"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { quotaConstraintAppliesToModel } from "@claudexor/budget";
|
|
2
|
+
import { profileQuotaBlock } from "./credential-cooldown.js";
|
|
3
|
+
import { limitSubjectRoute, profileHeadroomBreach } from "./credential-profile-rotation.js";
|
|
4
|
+
/** The subscription-kind enabled rows of one harness (the static pool). */
|
|
5
|
+
export function accountPoolRows(registry, harnessId) {
|
|
6
|
+
return registry.filter((profile) => profile.harness_id === harnessId && profile.enabled && profile.credential_kind !== "api_key");
|
|
7
|
+
}
|
|
8
|
+
/** Worst-window FRESH model-applicable headroom for one subject, or null when
|
|
9
|
+
* no fresh applicable evidence exists (unknown/stale — D3: display-only). */
|
|
10
|
+
function freshModelHeadroom(snapshots, harnessId, profileId, model) {
|
|
11
|
+
let worst = null;
|
|
12
|
+
for (const snapshot of snapshots) {
|
|
13
|
+
if (snapshot.freshness !== "fresh")
|
|
14
|
+
continue;
|
|
15
|
+
if (snapshot.subject.harness !== harnessId)
|
|
16
|
+
continue;
|
|
17
|
+
if ((snapshot.subject.subject_id ?? null) !== profileId)
|
|
18
|
+
continue;
|
|
19
|
+
for (const constraint of snapshot.constraints) {
|
|
20
|
+
if (!quotaConstraintAppliesToModel(constraint, model))
|
|
21
|
+
continue;
|
|
22
|
+
if (constraint.used_ratio === null)
|
|
23
|
+
continue;
|
|
24
|
+
const headroom = 1 - constraint.used_ratio;
|
|
25
|
+
if (worst === null || headroom < worst)
|
|
26
|
+
worst = headroom;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return worst;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Rank one harness's READY pool rows per the unified-model comparator. Rows
|
|
33
|
+
* outside `readyProfileIds` are excluded entirely (not ready ≠ exhausted).
|
|
34
|
+
*/
|
|
35
|
+
export function rankAccountPool(args) {
|
|
36
|
+
const candidates = [];
|
|
37
|
+
for (const profile of accountPoolRows(args.registry, args.harnessId)) {
|
|
38
|
+
if (!args.readyProfileIds.has(profile.profile_id))
|
|
39
|
+
continue;
|
|
40
|
+
const breach = profileHeadroomBreach(args.snapshots, args.harnessId, profile.profile_id, args.headroomThreshold, args.model);
|
|
41
|
+
if (breach) {
|
|
42
|
+
candidates.push({
|
|
43
|
+
profile,
|
|
44
|
+
verdict: { kind: "exhausted", resets_at: breach.resets_at },
|
|
45
|
+
});
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
// A4 cooldown reader: a row under an OBSERVED live block (reactive
|
|
49
|
+
// vendor-limit cooldown or spent window, stale-but-live included) ranks
|
|
50
|
+
// exhausted with its earliest known release instant — selecting it would
|
|
51
|
+
// burn an attempt to rediscover the limit the registry already holds. The
|
|
52
|
+
// block is read route-scoped (the row's own credential kind), so an
|
|
53
|
+
// api-key sibling's window can never cool a subscription row.
|
|
54
|
+
const block = profileQuotaBlock(args.snapshots, args.harnessId, profile.profile_id, limitSubjectRoute(profile), args.model);
|
|
55
|
+
if (block) {
|
|
56
|
+
candidates.push({
|
|
57
|
+
profile,
|
|
58
|
+
verdict: { kind: "exhausted", resets_at: block.resets_at },
|
|
59
|
+
});
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
const headroom = freshModelHeadroom(args.snapshots, args.harnessId, profile.profile_id, args.model);
|
|
63
|
+
candidates.push({
|
|
64
|
+
profile,
|
|
65
|
+
verdict: headroom === null ? { kind: "unknown" } : { kind: "fresh_headroom", headroom },
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
const rankOf = (verdict) => verdict.kind === "fresh_headroom" ? 0 : verdict.kind === "unknown" ? 1 : 2;
|
|
69
|
+
return candidates.sort((a, b) => {
|
|
70
|
+
const byRank = rankOf(a.verdict) - rankOf(b.verdict);
|
|
71
|
+
if (byRank !== 0)
|
|
72
|
+
return byRank;
|
|
73
|
+
if (a.verdict.kind === "fresh_headroom" && b.verdict.kind === "fresh_headroom") {
|
|
74
|
+
const byHeadroom = b.verdict.headroom - a.verdict.headroom;
|
|
75
|
+
if (byHeadroom !== 0)
|
|
76
|
+
return byHeadroom;
|
|
77
|
+
}
|
|
78
|
+
return a.profile.profile_id < b.profile.profile_id
|
|
79
|
+
? -1
|
|
80
|
+
: a.profile.profile_id > b.profile.profile_id
|
|
81
|
+
? 1
|
|
82
|
+
: 0;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
/** Select the unpinned-run account from the pool (or report why not). */
|
|
86
|
+
export function selectFromAccountPool(args) {
|
|
87
|
+
const ranked = rankAccountPool(args);
|
|
88
|
+
const selectable = ranked.find((candidate) => candidate.verdict.kind !== "exhausted");
|
|
89
|
+
if (selectable)
|
|
90
|
+
return { outcome: "selected", candidate: selectable };
|
|
91
|
+
if (ranked.length === 0)
|
|
92
|
+
return { outcome: "empty", resets_at: null };
|
|
93
|
+
let resetsAt = null;
|
|
94
|
+
for (const candidate of ranked) {
|
|
95
|
+
if (candidate.verdict.kind !== "exhausted" || candidate.verdict.resets_at === null)
|
|
96
|
+
continue;
|
|
97
|
+
if (resetsAt === null || candidate.verdict.resets_at < resetsAt) {
|
|
98
|
+
resetsAt = candidate.verdict.resets_at;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return { outcome: "exhausted", resets_at: resetsAt };
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=account-pool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"account-pool.js","sourceRoot":"","sources":["../src/account-pool.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { AuthPreference, CredentialProfile, CredentialProfileStatus, CredentialUnusableObservation, QuotaSnapshot } from "@claudexor/schema";
|
|
2
|
+
import { type CredentialProfilePolicyState } from "@claudexor/core";
|
|
3
|
+
import { type HeadroomBreach, type ProfilePolicy } from "./credential-profile-rotation.js";
|
|
4
|
+
import { type VendorQuotaObservations } from "./credential-profiles.js";
|
|
5
|
+
/**
|
|
6
|
+
* The ONE account-resolution owner for a run's harness (INV-135 unified
|
|
7
|
+
* account model, owner decisions D-U1/D-U6/Q1/Q3), extracted from the
|
|
8
|
+
* orchestrator god-file (INV-124):
|
|
9
|
+
*
|
|
10
|
+
* 1. An explicit pin is STRICT — a fresh exhausted window OR an observed live
|
|
11
|
+
* quota block (A4: reactive cooldown / spent window, stale-but-live
|
|
12
|
+
* included) is a typed `subscription_window_exhausted` refusal, never a
|
|
13
|
+
* silent rotation.
|
|
14
|
+
* 2. Below the pin, an EXPLICIT `api_key` preference is the user's paid
|
|
15
|
+
* election: it takes the disclosed API-key ROUTE before binding/pool
|
|
16
|
+
* resolution — a healthy subscription pool must never spend subscription
|
|
17
|
+
* quota over that explicit choice (ARCHITECTURE: explicit `api_key`
|
|
18
|
+
* excludes native fallback).
|
|
19
|
+
* 3. An unpinned thread turn stays on its durable bound account while that
|
|
20
|
+
* row is ready, else switches to the pool with a DISCLOSED lane switch. A
|
|
21
|
+
* live typed `credential_unusable` observation (A7) condemns the bound row
|
|
22
|
+
* at this composition point too.
|
|
23
|
+
* 4. Unbound runs take the best row of the quota-aware pool.
|
|
24
|
+
* 5. An empty/exhausted pool is the typed `credential_pool_exhausted`
|
|
25
|
+
* TERMINAL carrying the pool's earliest known reset (owner Q3=A: PR-A's
|
|
26
|
+
* semantics supersede Q2 — waiting for the reset is the default). The paid
|
|
27
|
+
* API-key ROUTE serves it ONLY under the EXPLICIT `api_key` preference
|
|
28
|
+
* (disclosed; never a row) — never silently under `auto`. Harnesses with
|
|
29
|
+
* no registered rows keep the legacy default-subject ladder (unmigrated
|
|
30
|
+
* stores).
|
|
31
|
+
*/
|
|
32
|
+
export type AccountResolutionEmit = (type: "route.profile.headroom_exceeded" | "route.profile.rotated" | "route.profile.rotation_exhausted" | "route.profile.credential_unusable" | "route.account.pool_selected" | "route.account.lane_switch" | "route.account.pool_exhausted", payload: Record<string, unknown>) => void;
|
|
33
|
+
export interface AccountResolutionContext {
|
|
34
|
+
harnessId: string;
|
|
35
|
+
registry: readonly CredentialProfile[];
|
|
36
|
+
policy: ProfilePolicy;
|
|
37
|
+
/** Static platform cardinality, resolved without vendor discovery/probes. */
|
|
38
|
+
profileCardinality?: CredentialProfilePolicyState;
|
|
39
|
+
snapshots: readonly QuotaSnapshot[];
|
|
40
|
+
quota: VendorQuotaObservations;
|
|
41
|
+
/** Live typed `credential_unusable` observations (A7): a condemned row is
|
|
42
|
+
* refused at the readiness composition point, never re-discovered by
|
|
43
|
+
* spending an attempt. */
|
|
44
|
+
unusable: readonly CredentialUnusableObservation[];
|
|
45
|
+
probe: ((profile: CredentialProfile) => Promise<CredentialProfileStatus>) | undefined;
|
|
46
|
+
/** The explicit pin, already resolved/validated by the caller (null = unpinned). */
|
|
47
|
+
pinnedProfile: CredentialProfile | null;
|
|
48
|
+
/** The thread's durable per-harness binding for unpinned turns (D-U1 order 2). */
|
|
49
|
+
boundProfileId: string | null;
|
|
50
|
+
threadId: string | null;
|
|
51
|
+
model: string | null;
|
|
52
|
+
/** Default-route estimate of the legacy unprofiled ladder (no-rows harnesses). */
|
|
53
|
+
defaultRoute: "local_session" | "api_key" | null;
|
|
54
|
+
/** Whether the legacy native login is excluded (`native_credentials_enabled: false`). */
|
|
55
|
+
nativeCredentialsDisabled: boolean;
|
|
56
|
+
/** Effective auth preference (run > harness > routing config). */
|
|
57
|
+
authPreference: AuthPreference;
|
|
58
|
+
/** Record that this harness's unpinned route fell to the PAID api_key route. */
|
|
59
|
+
notePoolApiKeyRoute: () => void;
|
|
60
|
+
emit: AccountResolutionEmit;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* A spent subscription window on an EXPLICITLY PINNED account, refused
|
|
64
|
+
* MACHINE-READABLY (D-U6: a pin is strict — never a silent rotation). Homed
|
|
65
|
+
* beside its A4 sibling `pinnedWindowBlocked` in the pinned-lane owner.
|
|
66
|
+
*
|
|
67
|
+
* The verdict a caller actually needs from this refusal is "not now, come back
|
|
68
|
+
* at T" — and the only honest way to deliver T is as a field. Gluing it into
|
|
69
|
+
* the sentence and shipping the terminal as `category: internal, code: null`
|
|
70
|
+
* left every consumer (a surface, a scheduler, an automating host) to regex
|
|
71
|
+
* the prose for a timestamp, which no contract in this repo permits. The
|
|
72
|
+
* message stays human-readable; the machine reads `code` and `resetsAt`, which
|
|
73
|
+
* the run terminal lifts onto `final/failure.yaml` verbatim.
|
|
74
|
+
*/
|
|
75
|
+
export declare function subscriptionWindowExhausted(profileId: string, harnessId: string, breach: HeadroomBreach): Error;
|
|
76
|
+
/** Per-run record of harnesses whose unpinned route took the PAID API-key
|
|
77
|
+
* route under the EXPLICIT `api_key` preference (Q3=A) — the user's paid
|
|
78
|
+
* election over a healthy pool, or the only permitted service of an
|
|
79
|
+
* empty/exhausted one: the spec then carries auth_preference=api_key so the
|
|
80
|
+
* adapter can never spawn back into an exhausted or excluded native login.
|
|
81
|
+
* Keyed by the run-input object. */
|
|
82
|
+
export declare class PoolRouteFlags {
|
|
83
|
+
private readonly flags;
|
|
84
|
+
note(runKey: object, harnessId: string): void;
|
|
85
|
+
has(runKey: object, harnessId: string): boolean;
|
|
86
|
+
}
|
|
87
|
+
export declare function resolveAccountForRun(ctx: AccountResolutionContext): Promise<CredentialProfile | null>;
|
|
88
|
+
//# sourceMappingURL=account-resolution.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"account-resolution.d.ts","sourceRoot":"","sources":["../src/account-resolution.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,iBAAiB,EACjB,uBAAuB,EACvB,6BAA6B,EAC7B,aAAa,EACd,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAkC,KAAK,4BAA4B,EAAE,MAAM,iBAAiB,CAAC;AAUpG,OAAO,EAIL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAEL,KAAK,uBAAuB,EAC7B,MAAM,0BAA0B,CAAC;AAElC;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,MAAM,MAAM,qBAAqB,GAAG,CAClC,IAAI,EACA,iCAAiC,GACjC,uBAAuB,GACvB,kCAAkC,GAClC,mCAAmC,GACnC,6BAA6B,GAC7B,2BAA2B,GAC3B,8BAA8B,EAClC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC7B,IAAI,CAAC;AAEV,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACvC,MAAM,EAAE,aAAa,CAAC;IACtB,6EAA6E;IAC7E,kBAAkB,CAAC,EAAE,4BAA4B,CAAC;IAClD,SAAS,EAAE,SAAS,aAAa,EAAE,CAAC;IACpC,KAAK,EAAE,uBAAuB,CAAC;IAC/B;;8BAE0B;IAC1B,QAAQ,EAAE,SAAS,6BAA6B,EAAE,CAAC;IACnD,KAAK,EAAE,CAAC,CAAC,OAAO,EAAE,iBAAiB,KAAK,OAAO,CAAC,uBAAuB,CAAC,CAAC,GAAG,SAAS,CAAC;IACtF,oFAAoF;IACpF,aAAa,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACxC,kFAAkF;IAClF,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,kFAAkF;IAClF,YAAY,EAAE,eAAe,GAAG,SAAS,GAAG,IAAI,CAAC;IACjD,yFAAyF;IACzF,yBAAyB,EAAE,OAAO,CAAC;IACnC,kEAAkE;IAClE,cAAc,EAAE,cAAc,CAAC;IAC/B,gFAAgF;IAChF,mBAAmB,EAAE,MAAM,IAAI,CAAC;IAChC,IAAI,EAAE,qBAAqB,CAAC;CAC7B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,2BAA2B,CACzC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,cAAc,GACrB,KAAK,CAeP;AAwED;;;;;oCAKoC;AACpC,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAsC;IAC5D,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAI5C;IACD,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAE9C;CACF;AAED,wBAAsB,oBAAoB,CACxC,GAAG,EAAE,wBAAwB,GAC5B,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CA+PnC"}
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import { accountPoolRows, selectFromAccountPool } from "./account-pool.js";
|
|
2
|
+
import { credentialProfilePolicyProblem } from "@claudexor/core";
|
|
3
|
+
import { credentialPoolExhausted, liveUnusableFor, profileQuotaBlock, } from "./credential-cooldown.js";
|
|
4
|
+
import { readyProfilesForRotation } from "./credential-differential.js";
|
|
5
|
+
import { preflightDefaultSubject } from "./credential-preflight.js";
|
|
6
|
+
import { effectiveLimitAction, limitSubjectRoute, profileHeadroomBreach, } from "./credential-profile-rotation.js";
|
|
7
|
+
import { selectedProfileAvailability, } from "./credential-profiles.js";
|
|
8
|
+
/**
|
|
9
|
+
* A spent subscription window on an EXPLICITLY PINNED account, refused
|
|
10
|
+
* MACHINE-READABLY (D-U6: a pin is strict — never a silent rotation). Homed
|
|
11
|
+
* beside its A4 sibling `pinnedWindowBlocked` in the pinned-lane owner.
|
|
12
|
+
*
|
|
13
|
+
* The verdict a caller actually needs from this refusal is "not now, come back
|
|
14
|
+
* at T" — and the only honest way to deliver T is as a field. Gluing it into
|
|
15
|
+
* the sentence and shipping the terminal as `category: internal, code: null`
|
|
16
|
+
* left every consumer (a surface, a scheduler, an automating host) to regex
|
|
17
|
+
* the prose for a timestamp, which no contract in this repo permits. The
|
|
18
|
+
* message stays human-readable; the machine reads `code` and `resetsAt`, which
|
|
19
|
+
* the run terminal lifts onto `final/failure.yaml` verbatim.
|
|
20
|
+
*/
|
|
21
|
+
export function subscriptionWindowExhausted(profileId, harnessId, breach) {
|
|
22
|
+
return Object.assign(new Error(`credential profile "${profileId}" (${harnessId}) is over its headroom threshold ` +
|
|
23
|
+
`(${breach.constraint_id} at ${Math.round(breach.used_ratio * 100)}% >= ${Math.round(breach.threshold * 100)}%; ` +
|
|
24
|
+
`a pinned account never rotates${breach.resets_at ? `; resets ${breach.resets_at}` : ""})`), {
|
|
25
|
+
code: "subscription_window_exhausted",
|
|
26
|
+
// Not `internal`: nothing malfunctioned. The account cannot serve this
|
|
27
|
+
// run until its window reopens, which is what harness_unavailable means.
|
|
28
|
+
category: "harness_unavailable",
|
|
29
|
+
resetsAt: breach.resets_at,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/** The strict pin's refusal for an OBSERVED live block (A4): same typed code
|
|
33
|
+
* and category as the headroom form — a consumer asks "when can I come back",
|
|
34
|
+
* not which detector fired — with the block's own evidence in the prose. */
|
|
35
|
+
function pinnedWindowBlocked(profileId, harnessId, block) {
|
|
36
|
+
return Object.assign(new Error(`credential profile "${profileId}" (${harnessId}) is ${block.kind === "exhausted"
|
|
37
|
+
? "over an observed vendor window"
|
|
38
|
+
: "in a vendor rate-limit cooldown"} (${block.constraint_id ?? "cooldown"}; a pinned account never rotates${block.resets_at ? `; resets ${block.resets_at}` : ""})`), {
|
|
39
|
+
code: "subscription_window_exhausted",
|
|
40
|
+
category: "harness_unavailable",
|
|
41
|
+
resetsAt: block.resets_at,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Per-candidate rejection evidence of one exhausted POOL decision, in the
|
|
46
|
+
* shape the typed `credential_pool_exhausted` terminal consumes: every
|
|
47
|
+
* subscription-kind row of the harness with its own quota evidence (fresh
|
|
48
|
+
* breach / observed live block / live dead-credential verdict), so the
|
|
49
|
+
* terminal's earliest-reset fold and its message never disagree with the
|
|
50
|
+
* ranking that refused the pool.
|
|
51
|
+
*/
|
|
52
|
+
function poolExhaustionCandidates(ctx, ready) {
|
|
53
|
+
const { harnessId, registry, policy, snapshots, model } = ctx;
|
|
54
|
+
return registry
|
|
55
|
+
.filter((row) => row.harness_id === harnessId && row.credential_kind !== "api_key")
|
|
56
|
+
.map((row) => {
|
|
57
|
+
const breach = profileHeadroomBreach(snapshots, harnessId, row.profile_id, policy.headroom_threshold, model);
|
|
58
|
+
const block = breach
|
|
59
|
+
? null
|
|
60
|
+
: profileQuotaBlock(snapshots, harnessId, row.profile_id, limitSubjectRoute(row), model);
|
|
61
|
+
const dead = liveUnusableFor(ctx.unusable, harnessId, row.profile_id, model);
|
|
62
|
+
// Label precedence mirrors PR-A's rotationExhaustionCandidates: a
|
|
63
|
+
// not-ready row is not a POOL MEMBER, so its windows never join the
|
|
64
|
+
// earliest-reset fold (an unready row's reset promises no reopen).
|
|
65
|
+
const rejected = !row.enabled
|
|
66
|
+
? "disabled"
|
|
67
|
+
: dead
|
|
68
|
+
? "credential_unusable"
|
|
69
|
+
: !ready.has(row.profile_id)
|
|
70
|
+
? "not_ready"
|
|
71
|
+
: breach
|
|
72
|
+
? "headroom_exceeded"
|
|
73
|
+
: block
|
|
74
|
+
? "cooldown"
|
|
75
|
+
: "not_selected";
|
|
76
|
+
return {
|
|
77
|
+
profile_id: row.profile_id,
|
|
78
|
+
rejected,
|
|
79
|
+
headroom: breach,
|
|
80
|
+
cooldown: block,
|
|
81
|
+
unusable: dead ? { code: dead.code } : null,
|
|
82
|
+
};
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
/** Per-run record of harnesses whose unpinned route took the PAID API-key
|
|
86
|
+
* route under the EXPLICIT `api_key` preference (Q3=A) — the user's paid
|
|
87
|
+
* election over a healthy pool, or the only permitted service of an
|
|
88
|
+
* empty/exhausted one: the spec then carries auth_preference=api_key so the
|
|
89
|
+
* adapter can never spawn back into an exhausted or excluded native login.
|
|
90
|
+
* Keyed by the run-input object. */
|
|
91
|
+
export class PoolRouteFlags {
|
|
92
|
+
flags = new WeakMap();
|
|
93
|
+
note(runKey, harnessId) {
|
|
94
|
+
const set = this.flags.get(runKey) ?? new Set();
|
|
95
|
+
set.add(harnessId);
|
|
96
|
+
this.flags.set(runKey, set);
|
|
97
|
+
}
|
|
98
|
+
has(runKey, harnessId) {
|
|
99
|
+
return this.flags.get(runKey)?.has(harnessId) ?? false;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
export async function resolveAccountForRun(ctx) {
|
|
103
|
+
const { harnessId, registry, policy, snapshots, quota, model, emit } = ctx;
|
|
104
|
+
// D13: persisted over-limit rows are not candidates to rank or probe. Fail
|
|
105
|
+
// before the explicit pin, durable binding, paid preference, or pool can
|
|
106
|
+
// choose one identity and make an OS-user-scoped credential look isolated.
|
|
107
|
+
if (ctx.profileCardinality?.ambiguous) {
|
|
108
|
+
throw credentialProfilePolicyProblem(ctx.profileCardinality, "credential_profile_ambiguous");
|
|
109
|
+
}
|
|
110
|
+
// A7-aware readiness (ONE composition point, `readyProfilesForRotation`):
|
|
111
|
+
// probe wrapper + vendor overlay + admission predicate + the live
|
|
112
|
+
// `credential_unusable` refusal.
|
|
113
|
+
const readyIds = () => readyProfilesForRotation({
|
|
114
|
+
registry,
|
|
115
|
+
harnessId,
|
|
116
|
+
policy,
|
|
117
|
+
current: null,
|
|
118
|
+
probe: ctx.probe,
|
|
119
|
+
quota,
|
|
120
|
+
unusable: ctx.unusable,
|
|
121
|
+
model,
|
|
122
|
+
});
|
|
123
|
+
if (ctx.pinnedProfile) {
|
|
124
|
+
// D-U6: strict pin. A fresh exhausted window refuses with its reset time;
|
|
125
|
+
// unknown/stale quota never refuses (D3 freshness) — but an OBSERVED live
|
|
126
|
+
// block (A4: reactive cooldown / spent window, stale-but-live included)
|
|
127
|
+
// refuses too, since a cooldown instant is absolute clock truth.
|
|
128
|
+
const pinned = ctx.pinnedProfile;
|
|
129
|
+
const breach = profileHeadroomBreach(snapshots, harnessId, pinned.profile_id, policy.headroom_threshold, model);
|
|
130
|
+
const block = breach
|
|
131
|
+
? null
|
|
132
|
+
: profileQuotaBlock(snapshots, harnessId, pinned.profile_id, limitSubjectRoute(pinned), model);
|
|
133
|
+
if (!breach && !block)
|
|
134
|
+
return pinned;
|
|
135
|
+
emit("route.profile.headroom_exceeded", {
|
|
136
|
+
harness_id: harnessId,
|
|
137
|
+
profile_id: pinned.profile_id,
|
|
138
|
+
action: "refuse",
|
|
139
|
+
constraint_id: breach?.constraint_id ?? block?.constraint_id ?? null,
|
|
140
|
+
used_ratio: breach?.used_ratio ?? null,
|
|
141
|
+
threshold: policy.headroom_threshold,
|
|
142
|
+
resets_at: breach?.resets_at ?? block?.resets_at ?? null,
|
|
143
|
+
});
|
|
144
|
+
throw breach
|
|
145
|
+
? subscriptionWindowExhausted(pinned.profile_id, harnessId, breach)
|
|
146
|
+
: pinnedWindowBlocked(pinned.profile_id, harnessId, block);
|
|
147
|
+
}
|
|
148
|
+
const pool = accountPoolRows(registry, harnessId);
|
|
149
|
+
// The legacy ladder serves ONLY harnesses with no REGISTERED subscription
|
|
150
|
+
// rows (unmigrated stores). A harness whose rows are all DISABLED must fall
|
|
151
|
+
// through to the pool path below and refuse typed (or take the disclosed
|
|
152
|
+
// paid route) — the ladder would silently spawn back into the same
|
|
153
|
+
// account's default store the owner just toggled off.
|
|
154
|
+
const hasRegisteredRows = registry.some((p) => p.harness_id === harnessId && p.credential_kind !== "api_key");
|
|
155
|
+
if (pool.length === 0 && !hasRegisteredRows) {
|
|
156
|
+
if (ctx.nativeCredentialsDisabled) {
|
|
157
|
+
// No rows AND the native login is excluded: nothing subscription-shaped
|
|
158
|
+
// can serve — never a silent spawn back INTO the disabled login. The
|
|
159
|
+
// paid route serves ONLY the EXPLICIT `api_key` preference (Q3=A);
|
|
160
|
+
// `auto`/`subscription` refuse typed (the admission gate already
|
|
161
|
+
// dropped non-api_key requests for auto pools).
|
|
162
|
+
if (ctx.authPreference !== "api_key") {
|
|
163
|
+
emit("route.account.pool_exhausted", {
|
|
164
|
+
harness_id: harnessId,
|
|
165
|
+
reason: "the CLI login is disabled and no account is registered",
|
|
166
|
+
resets_at: null,
|
|
167
|
+
fallback: null,
|
|
168
|
+
});
|
|
169
|
+
throw Object.assign(new Error(`no routable ${harnessId} account: the CLI login is disabled ` +
|
|
170
|
+
`(harnesses.${harnessId}.native_credentials_enabled=false), no account is ` +
|
|
171
|
+
`registered, and the paid API-key route requires the explicit api_key ` +
|
|
172
|
+
`preference — connect an account or pin one (--profile)`), {
|
|
173
|
+
code: "credential_pool_exhausted",
|
|
174
|
+
category: "harness_unavailable",
|
|
175
|
+
resetsAt: null,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
ctx.notePoolApiKeyRoute();
|
|
179
|
+
emit("route.account.pool_exhausted", {
|
|
180
|
+
harness_id: harnessId,
|
|
181
|
+
reason: "the CLI login is disabled and no account is registered",
|
|
182
|
+
resets_at: null,
|
|
183
|
+
fallback: "api_key_route",
|
|
184
|
+
});
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
// Legacy default-subject ladder (unmigrated stores): under an EFFECTIVE
|
|
188
|
+
// `rotate` (explicit, or the A6 kind-aware `auto` on a subscription
|
|
189
|
+
// route), a fresh default-subject headroom breach — or its observed live
|
|
190
|
+
// block (A4) — starts on the next eligible subscription profile instead;
|
|
191
|
+
// effective `fail`/`ask` change nothing.
|
|
192
|
+
const breach = profileHeadroomBreach(snapshots, harnessId, null, policy.headroom_threshold, model);
|
|
193
|
+
const readyProfileIds = effectiveLimitAction(policy, ctx.defaultRoute) === "rotate" &&
|
|
194
|
+
ctx.defaultRoute === "local_session" &&
|
|
195
|
+
(breach !== null ||
|
|
196
|
+
profileQuotaBlock(snapshots, harnessId, null, ctx.defaultRoute, model) !== null)
|
|
197
|
+
? await readyIds()
|
|
198
|
+
: new Set();
|
|
199
|
+
return preflightDefaultSubject({
|
|
200
|
+
harnessId,
|
|
201
|
+
policy,
|
|
202
|
+
registry,
|
|
203
|
+
snapshots,
|
|
204
|
+
readyProfileIds,
|
|
205
|
+
defaultRoute: ctx.defaultRoute,
|
|
206
|
+
unusable: ctx.unusable,
|
|
207
|
+
model,
|
|
208
|
+
emit,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
// The EXPLICIT `api_key` preference is the user's paid election (Q3=A): it
|
|
212
|
+
// takes the disclosed PAID route BEFORE binding/pool resolution — a healthy
|
|
213
|
+
// subscription pool must never spend subscription quota over that explicit
|
|
214
|
+
// choice (ARCHITECTURE: explicit `api_key` excludes native fallback). Only
|
|
215
|
+
// an explicit pin outranks it (strict pin doctrine, handled above). Mirrors
|
|
216
|
+
// the exhausted-pool paid branch below: noted, disclosed, never a row.
|
|
217
|
+
if (ctx.authPreference === "api_key") {
|
|
218
|
+
ctx.notePoolApiKeyRoute();
|
|
219
|
+
emit("route.account.pool_exhausted", {
|
|
220
|
+
harness_id: harnessId,
|
|
221
|
+
reason: "explicit api_key preference excludes the subscription pool",
|
|
222
|
+
resets_at: null,
|
|
223
|
+
fallback: "api_key_route",
|
|
224
|
+
});
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
const boundId = ctx.boundProfileId;
|
|
228
|
+
let boundSwitchReason = null;
|
|
229
|
+
if (boundId) {
|
|
230
|
+
// D-U1 order 2: the thread's bound account resolves BEFORE the pool —
|
|
231
|
+
// stickiness is not a pool ranking preference, so an explicit
|
|
232
|
+
// rotation_eligible list cannot evict a healthy binding.
|
|
233
|
+
const bound = pool.find((row) => row.profile_id === boundId);
|
|
234
|
+
// A7: a live typed `credential_unusable` observation condemns the BOUND
|
|
235
|
+
// row at this composition point too (this file's contract: condemned rows
|
|
236
|
+
// are refused at readiness composition, never re-discovered by spending
|
|
237
|
+
// an attempt) — the binding re-pools with the disclosed lane switch.
|
|
238
|
+
const dead = bound ? liveUnusableFor(ctx.unusable, harnessId, boundId, model) : null;
|
|
239
|
+
if (bound && dead) {
|
|
240
|
+
boundSwitchReason = `the bound account's credential is unusable (${dead.code})`;
|
|
241
|
+
}
|
|
242
|
+
else if (bound) {
|
|
243
|
+
const verdict = await selectedProfileAvailability({
|
|
244
|
+
registry,
|
|
245
|
+
profileId: boundId,
|
|
246
|
+
harnessId,
|
|
247
|
+
probe: ctx.probe,
|
|
248
|
+
quota,
|
|
249
|
+
});
|
|
250
|
+
const breach = profileHeadroomBreach(snapshots, harnessId, boundId, policy.headroom_threshold, model);
|
|
251
|
+
// A4: an observed live block on the bound row (reactive cooldown /
|
|
252
|
+
// spent window) unbinds it exactly like a fresh breach would.
|
|
253
|
+
const block = breach
|
|
254
|
+
? null
|
|
255
|
+
: profileQuotaBlock(snapshots, harnessId, boundId, limitSubjectRoute(bound), model);
|
|
256
|
+
if (verdict === "available" && !breach && !block)
|
|
257
|
+
return bound;
|
|
258
|
+
boundSwitchReason =
|
|
259
|
+
breach || block
|
|
260
|
+
? `quota window exhausted${(breach?.resets_at ?? block?.resets_at)
|
|
261
|
+
? ` (resets ${breach?.resets_at ?? block?.resets_at})`
|
|
262
|
+
: ""}`
|
|
263
|
+
: (verdict ?? "not ready");
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
boundSwitchReason = "the bound account was disabled or removed";
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
const readyProfileIds = await readyIds();
|
|
270
|
+
const selection = selectFromAccountPool({
|
|
271
|
+
registry,
|
|
272
|
+
harnessId,
|
|
273
|
+
snapshots,
|
|
274
|
+
readyProfileIds,
|
|
275
|
+
headroomThreshold: policy.headroom_threshold,
|
|
276
|
+
model,
|
|
277
|
+
});
|
|
278
|
+
if (selection.outcome === "selected") {
|
|
279
|
+
const chosen = selection.candidate.profile;
|
|
280
|
+
if (boundId && boundId !== chosen.profile_id) {
|
|
281
|
+
// Q1=A: a lane switch is DISCLOSED, never silent (the continuity layer
|
|
282
|
+
// additionally discloses the hydration on the turn).
|
|
283
|
+
emit("route.account.lane_switch", {
|
|
284
|
+
harness_id: harnessId,
|
|
285
|
+
thread_id: ctx.threadId,
|
|
286
|
+
from_profile_id: boundId,
|
|
287
|
+
to_profile_id: chosen.profile_id,
|
|
288
|
+
reason: boundSwitchReason,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
emit("route.account.pool_selected", {
|
|
292
|
+
harness_id: harnessId,
|
|
293
|
+
profile_id: chosen.profile_id,
|
|
294
|
+
quota: selection.candidate.verdict.kind,
|
|
295
|
+
...(selection.candidate.verdict.kind === "fresh_headroom"
|
|
296
|
+
? { headroom: selection.candidate.verdict.headroom }
|
|
297
|
+
: {}),
|
|
298
|
+
});
|
|
299
|
+
return chosen;
|
|
300
|
+
}
|
|
301
|
+
// Pool empty/exhausted (owner Q3=A, superseding Q2=A): the PRIMARY verdict
|
|
302
|
+
// is the typed `credential_pool_exhausted` TERMINAL carrying the pool's
|
|
303
|
+
// earliest known reset — an unpinned run under `auto` (and under explicit
|
|
304
|
+
// `subscription`) WAITS for a window instead of silently taking the paid
|
|
305
|
+
// route (the same principle as PR-A's kind-aware `limit_action: auto`,
|
|
306
|
+
// which resolves a metered subject to `fail`: `auto` never silently spends
|
|
307
|
+
// money). The EXPLICIT `api_key` preference never reaches this point — its
|
|
308
|
+
// paid election already returned before binding/pool resolution above.
|
|
309
|
+
emit("route.account.pool_exhausted", {
|
|
310
|
+
harness_id: harnessId,
|
|
311
|
+
reason: selection.outcome,
|
|
312
|
+
resets_at: selection.resets_at,
|
|
313
|
+
fallback: null,
|
|
314
|
+
...(boundId ? { from_profile_id: boundId } : {}),
|
|
315
|
+
});
|
|
316
|
+
throw credentialPoolExhausted({
|
|
317
|
+
harnessId,
|
|
318
|
+
// The triggering subject of a preflight pool refusal is the thread's
|
|
319
|
+
// bound row when one exists (its exhaustion is what forced the pool
|
|
320
|
+
// decision); a fresh unbound run has no subject of its own.
|
|
321
|
+
profileId: boundId,
|
|
322
|
+
reason: "profile_headroom_preflight",
|
|
323
|
+
candidates: poolExhaustionCandidates(ctx, readyProfileIds),
|
|
324
|
+
subjectLimit: null,
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
//# sourceMappingURL=account-resolution.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"account-resolution.js","sourceRoot":"","sources":["../src/account-resolution.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A2: typed per-attempt output markers for the STRUCTURAL rotation predicate.
|
|
3
|
+
*
|
|
4
|
+
* `sawAgentProgress` proves the agent demonstrably started working under the
|
|
5
|
+
* current credential — after that, a credential rotation would silently replay
|
|
6
|
+
* work (and any external side effects a tool call already performed), so the
|
|
7
|
+
* structural branch must never fire. The policy is deliberately NARROWER than
|
|
8
|
+
* the inactivity watchdog's `countsAsAgentProgress`: `message` and `error`
|
|
9
|
+
* events do NOT count, because a vendor's limit/failure prose arriving as a
|
|
10
|
+
* message must never block the structural branch (the exact incident class
|
|
11
|
+
* this predicate exists to close), and `status` events are lifecycle/failure
|
|
12
|
+
* disclosures, never model work.
|
|
13
|
+
*
|
|
14
|
+
* `fileChanges` counts typed `file_change` events WITHOUT requiring a tool ref
|
|
15
|
+
* (adapters legitimately emit tool-less file_change frames), so the mutation
|
|
16
|
+
* guard sees every disclosed write even when the workspace diff is empty or —
|
|
17
|
+
* in the read-only lane — no workspace exists at all.
|
|
18
|
+
*/
|
|
19
|
+
import type { HarnessEvent } from "@claudexor/schema";
|
|
20
|
+
export interface AttemptOutputMarkers {
|
|
21
|
+
/** The agent demonstrably produced work under this credential (thinking /
|
|
22
|
+
* tool activity / file or patch output / a completed compaction). */
|
|
23
|
+
sawAgentProgress: boolean;
|
|
24
|
+
/** Count of typed `file_change` events observed this attempt (tool-less
|
|
25
|
+
* frames included). */
|
|
26
|
+
fileChanges: number;
|
|
27
|
+
}
|
|
28
|
+
export declare function newAttemptOutputMarkers(): AttemptOutputMarkers;
|
|
29
|
+
export declare function observeAttemptOutputMarkers(m: AttemptOutputMarkers, ev: HarnessEvent): void;
|
|
30
|
+
//# sourceMappingURL=attemptOutputMarkers.d.ts.map
|