@claudexor/orchestrator 3.5.0 → 3.6.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.
Files changed (57) hide show
  1. package/dist/account-pool.d.ts +62 -0
  2. package/dist/account-pool.d.ts.map +1 -0
  3. package/dist/account-pool.js +103 -0
  4. package/dist/account-pool.js.map +1 -0
  5. package/dist/account-resolution.d.ts +85 -0
  6. package/dist/account-resolution.d.ts.map +1 -0
  7. package/dist/account-resolution.js +320 -0
  8. package/dist/account-resolution.js.map +1 -0
  9. package/dist/attemptOutputMarkers.d.ts +30 -0
  10. package/dist/attemptOutputMarkers.d.ts.map +1 -0
  11. package/dist/attemptOutputMarkers.js +31 -0
  12. package/dist/attemptOutputMarkers.js.map +1 -0
  13. package/dist/attemptTelemetry.d.ts +5 -4
  14. package/dist/attemptTelemetry.d.ts.map +1 -1
  15. package/dist/attemptTelemetry.js +4 -2
  16. package/dist/attemptTelemetry.js.map +1 -1
  17. package/dist/credential-cooldown.d.ts +131 -0
  18. package/dist/credential-cooldown.d.ts.map +1 -0
  19. package/dist/credential-cooldown.js +169 -0
  20. package/dist/credential-cooldown.js.map +1 -0
  21. package/dist/credential-differential.d.ts +81 -0
  22. package/dist/credential-differential.d.ts.map +1 -0
  23. package/dist/credential-differential.js +110 -0
  24. package/dist/credential-differential.js.map +1 -0
  25. package/dist/credential-preflight.d.ts +36 -0
  26. package/dist/credential-preflight.d.ts.map +1 -0
  27. package/dist/credential-preflight.js +78 -0
  28. package/dist/credential-preflight.js.map +1 -0
  29. package/dist/credential-profile-rotation.d.ts +80 -54
  30. package/dist/credential-profile-rotation.d.ts.map +1 -1
  31. package/dist/credential-profile-rotation.js +191 -163
  32. package/dist/credential-profile-rotation.js.map +1 -1
  33. package/dist/credential-profiles.d.ts +3 -49
  34. package/dist/credential-profiles.d.ts.map +1 -1
  35. package/dist/credential-profiles.js +2 -46
  36. package/dist/credential-profiles.js.map +1 -1
  37. package/dist/index.d.ts +2 -1
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +2 -1
  40. package/dist/index.js.map +1 -1
  41. package/dist/laneStreamEvents.d.ts +54 -0
  42. package/dist/laneStreamEvents.d.ts.map +1 -0
  43. package/dist/laneStreamEvents.js +81 -0
  44. package/dist/laneStreamEvents.js.map +1 -0
  45. package/dist/orchestrator-credentials.d.ts +93 -0
  46. package/dist/orchestrator-credentials.d.ts.map +1 -0
  47. package/dist/orchestrator-credentials.js +156 -0
  48. package/dist/orchestrator-credentials.js.map +1 -0
  49. package/dist/orchestrator.d.ts +17 -30
  50. package/dist/orchestrator.d.ts.map +1 -1
  51. package/dist/orchestrator.js +278 -324
  52. package/dist/orchestrator.js.map +1 -1
  53. package/dist/rotation-predicate.d.ts +71 -0
  54. package/dist/rotation-predicate.d.ts.map +1 -0
  55. package/dist/rotation-predicate.js +29 -0
  56. package/dist/rotation-predicate.js.map +1 -0
  57. 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,85 @@
1
+ import type { AuthPreference, CredentialProfile, CredentialProfileStatus, CredentialUnusableObservation, QuotaSnapshot } from "@claudexor/schema";
2
+ import { type HeadroomBreach, type ProfilePolicy } from "./credential-profile-rotation.js";
3
+ import { type VendorQuotaObservations } from "./credential-profiles.js";
4
+ /**
5
+ * The ONE account-resolution owner for a run's harness (INV-135 unified
6
+ * account model, owner decisions D-U1/D-U6/Q1/Q3), extracted from the
7
+ * orchestrator god-file (INV-124):
8
+ *
9
+ * 1. An explicit pin is STRICT — a fresh exhausted window OR an observed live
10
+ * quota block (A4: reactive cooldown / spent window, stale-but-live
11
+ * included) is a typed `subscription_window_exhausted` refusal, never a
12
+ * silent rotation.
13
+ * 2. Below the pin, an EXPLICIT `api_key` preference is the user's paid
14
+ * election: it takes the disclosed API-key ROUTE before binding/pool
15
+ * resolution — a healthy subscription pool must never spend subscription
16
+ * quota over that explicit choice (ARCHITECTURE: explicit `api_key`
17
+ * excludes native fallback).
18
+ * 3. An unpinned thread turn stays on its durable bound account while that
19
+ * row is ready, else switches to the pool with a DISCLOSED lane switch. A
20
+ * live typed `credential_unusable` observation (A7) condemns the bound row
21
+ * at this composition point too.
22
+ * 4. Unbound runs take the best row of the quota-aware pool.
23
+ * 5. An empty/exhausted pool is the typed `credential_pool_exhausted`
24
+ * TERMINAL carrying the pool's earliest known reset (owner Q3=A: PR-A's
25
+ * semantics supersede Q2 — waiting for the reset is the default). The paid
26
+ * API-key ROUTE serves it ONLY under the EXPLICIT `api_key` preference
27
+ * (disclosed; never a row) — never silently under `auto`. Harnesses with
28
+ * no registered rows keep the legacy default-subject ladder (unmigrated
29
+ * stores).
30
+ */
31
+ 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;
32
+ export interface AccountResolutionContext {
33
+ harnessId: string;
34
+ registry: readonly CredentialProfile[];
35
+ policy: ProfilePolicy;
36
+ snapshots: readonly QuotaSnapshot[];
37
+ quota: VendorQuotaObservations;
38
+ /** Live typed `credential_unusable` observations (A7): a condemned row is
39
+ * refused at the readiness composition point, never re-discovered by
40
+ * spending an attempt. */
41
+ unusable: readonly CredentialUnusableObservation[];
42
+ probe: ((profile: CredentialProfile) => Promise<CredentialProfileStatus>) | undefined;
43
+ /** The explicit pin, already resolved/validated by the caller (null = unpinned). */
44
+ pinnedProfile: CredentialProfile | null;
45
+ /** The thread's durable per-harness binding for unpinned turns (D-U1 order 2). */
46
+ boundProfileId: string | null;
47
+ threadId: string | null;
48
+ model: string | null;
49
+ /** Default-route estimate of the legacy unprofiled ladder (no-rows harnesses). */
50
+ defaultRoute: "local_session" | "api_key" | null;
51
+ /** Whether the legacy native login is excluded (`native_credentials_enabled: false`). */
52
+ nativeCredentialsDisabled: boolean;
53
+ /** Effective auth preference (run > harness > routing config). */
54
+ authPreference: AuthPreference;
55
+ /** Record that this harness's unpinned route fell to the PAID api_key route. */
56
+ notePoolApiKeyRoute: () => void;
57
+ emit: AccountResolutionEmit;
58
+ }
59
+ /**
60
+ * A spent subscription window on an EXPLICITLY PINNED account, refused
61
+ * MACHINE-READABLY (D-U6: a pin is strict — never a silent rotation). Homed
62
+ * beside its A4 sibling `pinnedWindowBlocked` in the pinned-lane owner.
63
+ *
64
+ * The verdict a caller actually needs from this refusal is "not now, come back
65
+ * at T" — and the only honest way to deliver T is as a field. Gluing it into
66
+ * the sentence and shipping the terminal as `category: internal, code: null`
67
+ * left every consumer (a surface, a scheduler, an automating host) to regex
68
+ * the prose for a timestamp, which no contract in this repo permits. The
69
+ * message stays human-readable; the machine reads `code` and `resetsAt`, which
70
+ * the run terminal lifts onto `final/failure.yaml` verbatim.
71
+ */
72
+ export declare function subscriptionWindowExhausted(profileId: string, harnessId: string, breach: HeadroomBreach): Error;
73
+ /** Per-run record of harnesses whose unpinned route took the PAID API-key
74
+ * route under the EXPLICIT `api_key` preference (Q3=A) — the user's paid
75
+ * election over a healthy pool, or the only permitted service of an
76
+ * empty/exhausted one: the spec then carries auth_preference=api_key so the
77
+ * adapter can never spawn back into an exhausted or excluded native login.
78
+ * Keyed by the run-input object. */
79
+ export declare class PoolRouteFlags {
80
+ private readonly flags;
81
+ note(runKey: object, harnessId: string): void;
82
+ has(runKey: object, harnessId: string): boolean;
83
+ }
84
+ export declare function resolveAccountForRun(ctx: AccountResolutionContext): Promise<CredentialProfile | null>;
85
+ //# 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;AAW3B,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,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,CAyPnC"}
@@ -0,0 +1,320 @@
1
+ import { accountPoolRows, selectFromAccountPool } from "./account-pool.js";
2
+ import { credentialPoolExhausted, liveUnusableFor, profileQuotaBlock, } from "./credential-cooldown.js";
3
+ import { readyProfilesForRotation } from "./credential-differential.js";
4
+ import { preflightDefaultSubject } from "./credential-preflight.js";
5
+ import { effectiveLimitAction, limitSubjectRoute, profileHeadroomBreach, } from "./credential-profile-rotation.js";
6
+ import { selectedProfileAvailability, } from "./credential-profiles.js";
7
+ /**
8
+ * A spent subscription window on an EXPLICITLY PINNED account, refused
9
+ * MACHINE-READABLY (D-U6: a pin is strict — never a silent rotation). Homed
10
+ * beside its A4 sibling `pinnedWindowBlocked` in the pinned-lane owner.
11
+ *
12
+ * The verdict a caller actually needs from this refusal is "not now, come back
13
+ * at T" — and the only honest way to deliver T is as a field. Gluing it into
14
+ * the sentence and shipping the terminal as `category: internal, code: null`
15
+ * left every consumer (a surface, a scheduler, an automating host) to regex
16
+ * the prose for a timestamp, which no contract in this repo permits. The
17
+ * message stays human-readable; the machine reads `code` and `resetsAt`, which
18
+ * the run terminal lifts onto `final/failure.yaml` verbatim.
19
+ */
20
+ export function subscriptionWindowExhausted(profileId, harnessId, breach) {
21
+ return Object.assign(new Error(`credential profile "${profileId}" (${harnessId}) is over its headroom threshold ` +
22
+ `(${breach.constraint_id} at ${Math.round(breach.used_ratio * 100)}% >= ${Math.round(breach.threshold * 100)}%; ` +
23
+ `a pinned account never rotates${breach.resets_at ? `; resets ${breach.resets_at}` : ""})`), {
24
+ code: "subscription_window_exhausted",
25
+ // Not `internal`: nothing malfunctioned. The account cannot serve this
26
+ // run until its window reopens, which is what harness_unavailable means.
27
+ category: "harness_unavailable",
28
+ resetsAt: breach.resets_at,
29
+ });
30
+ }
31
+ /** The strict pin's refusal for an OBSERVED live block (A4): same typed code
32
+ * and category as the headroom form — a consumer asks "when can I come back",
33
+ * not which detector fired — with the block's own evidence in the prose. */
34
+ function pinnedWindowBlocked(profileId, harnessId, block) {
35
+ return Object.assign(new Error(`credential profile "${profileId}" (${harnessId}) is ${block.kind === "exhausted"
36
+ ? "over an observed vendor window"
37
+ : "in a vendor rate-limit cooldown"} (${block.constraint_id ?? "cooldown"}; a pinned account never rotates${block.resets_at ? `; resets ${block.resets_at}` : ""})`), {
38
+ code: "subscription_window_exhausted",
39
+ category: "harness_unavailable",
40
+ resetsAt: block.resets_at,
41
+ });
42
+ }
43
+ /**
44
+ * Per-candidate rejection evidence of one exhausted POOL decision, in the
45
+ * shape the typed `credential_pool_exhausted` terminal consumes: every
46
+ * subscription-kind row of the harness with its own quota evidence (fresh
47
+ * breach / observed live block / live dead-credential verdict), so the
48
+ * terminal's earliest-reset fold and its message never disagree with the
49
+ * ranking that refused the pool.
50
+ */
51
+ function poolExhaustionCandidates(ctx, ready) {
52
+ const { harnessId, registry, policy, snapshots, model } = ctx;
53
+ return registry
54
+ .filter((row) => row.harness_id === harnessId && row.credential_kind !== "api_key")
55
+ .map((row) => {
56
+ const breach = profileHeadroomBreach(snapshots, harnessId, row.profile_id, policy.headroom_threshold, model);
57
+ const block = breach
58
+ ? null
59
+ : profileQuotaBlock(snapshots, harnessId, row.profile_id, limitSubjectRoute(row), model);
60
+ const dead = liveUnusableFor(ctx.unusable, harnessId, row.profile_id, model);
61
+ // Label precedence mirrors PR-A's rotationExhaustionCandidates: a
62
+ // not-ready row is not a POOL MEMBER, so its windows never join the
63
+ // earliest-reset fold (an unready row's reset promises no reopen).
64
+ const rejected = !row.enabled
65
+ ? "disabled"
66
+ : dead
67
+ ? "credential_unusable"
68
+ : !ready.has(row.profile_id)
69
+ ? "not_ready"
70
+ : breach
71
+ ? "headroom_exceeded"
72
+ : block
73
+ ? "cooldown"
74
+ : "not_selected";
75
+ return {
76
+ profile_id: row.profile_id,
77
+ rejected,
78
+ headroom: breach,
79
+ cooldown: block,
80
+ unusable: dead ? { code: dead.code } : null,
81
+ };
82
+ });
83
+ }
84
+ /** Per-run record of harnesses whose unpinned route took the PAID API-key
85
+ * route under the EXPLICIT `api_key` preference (Q3=A) — the user's paid
86
+ * election over a healthy pool, or the only permitted service of an
87
+ * empty/exhausted one: the spec then carries auth_preference=api_key so the
88
+ * adapter can never spawn back into an exhausted or excluded native login.
89
+ * Keyed by the run-input object. */
90
+ export class PoolRouteFlags {
91
+ flags = new WeakMap();
92
+ note(runKey, harnessId) {
93
+ const set = this.flags.get(runKey) ?? new Set();
94
+ set.add(harnessId);
95
+ this.flags.set(runKey, set);
96
+ }
97
+ has(runKey, harnessId) {
98
+ return this.flags.get(runKey)?.has(harnessId) ?? false;
99
+ }
100
+ }
101
+ export async function resolveAccountForRun(ctx) {
102
+ const { harnessId, registry, policy, snapshots, quota, model, emit } = ctx;
103
+ // A7-aware readiness (ONE composition point, `readyProfilesForRotation`):
104
+ // probe wrapper + vendor overlay + admission predicate + the live
105
+ // `credential_unusable` refusal.
106
+ const readyIds = () => readyProfilesForRotation({
107
+ registry,
108
+ harnessId,
109
+ policy,
110
+ current: null,
111
+ probe: ctx.probe,
112
+ quota,
113
+ unusable: ctx.unusable,
114
+ model,
115
+ });
116
+ if (ctx.pinnedProfile) {
117
+ // D-U6: strict pin. A fresh exhausted window refuses with its reset time;
118
+ // unknown/stale quota never refuses (D3 freshness) — but an OBSERVED live
119
+ // block (A4: reactive cooldown / spent window, stale-but-live included)
120
+ // refuses too, since a cooldown instant is absolute clock truth.
121
+ const pinned = ctx.pinnedProfile;
122
+ const breach = profileHeadroomBreach(snapshots, harnessId, pinned.profile_id, policy.headroom_threshold, model);
123
+ const block = breach
124
+ ? null
125
+ : profileQuotaBlock(snapshots, harnessId, pinned.profile_id, limitSubjectRoute(pinned), model);
126
+ if (!breach && !block)
127
+ return pinned;
128
+ emit("route.profile.headroom_exceeded", {
129
+ harness_id: harnessId,
130
+ profile_id: pinned.profile_id,
131
+ action: "refuse",
132
+ constraint_id: breach?.constraint_id ?? block?.constraint_id ?? null,
133
+ used_ratio: breach?.used_ratio ?? null,
134
+ threshold: policy.headroom_threshold,
135
+ resets_at: breach?.resets_at ?? block?.resets_at ?? null,
136
+ });
137
+ throw breach
138
+ ? subscriptionWindowExhausted(pinned.profile_id, harnessId, breach)
139
+ : pinnedWindowBlocked(pinned.profile_id, harnessId, block);
140
+ }
141
+ const pool = accountPoolRows(registry, harnessId);
142
+ // The legacy ladder serves ONLY harnesses with no REGISTERED subscription
143
+ // rows (unmigrated stores). A harness whose rows are all DISABLED must fall
144
+ // through to the pool path below and refuse typed (or take the disclosed
145
+ // paid route) — the ladder would silently spawn back into the same
146
+ // account's default store the owner just toggled off.
147
+ const hasRegisteredRows = registry.some((p) => p.harness_id === harnessId && p.credential_kind !== "api_key");
148
+ if (pool.length === 0 && !hasRegisteredRows) {
149
+ if (ctx.nativeCredentialsDisabled) {
150
+ // No rows AND the native login is excluded: nothing subscription-shaped
151
+ // can serve — never a silent spawn back INTO the disabled login. The
152
+ // paid route serves ONLY the EXPLICIT `api_key` preference (Q3=A);
153
+ // `auto`/`subscription` refuse typed (the admission gate already
154
+ // dropped non-api_key requests for auto pools).
155
+ if (ctx.authPreference !== "api_key") {
156
+ emit("route.account.pool_exhausted", {
157
+ harness_id: harnessId,
158
+ reason: "the CLI login is disabled and no account is registered",
159
+ resets_at: null,
160
+ fallback: null,
161
+ });
162
+ throw Object.assign(new Error(`no routable ${harnessId} account: the CLI login is disabled ` +
163
+ `(harnesses.${harnessId}.native_credentials_enabled=false), no account is ` +
164
+ `registered, and the paid API-key route requires the explicit api_key ` +
165
+ `preference — connect an account or pin one (--profile)`), {
166
+ code: "credential_pool_exhausted",
167
+ category: "harness_unavailable",
168
+ resetsAt: null,
169
+ });
170
+ }
171
+ ctx.notePoolApiKeyRoute();
172
+ emit("route.account.pool_exhausted", {
173
+ harness_id: harnessId,
174
+ reason: "the CLI login is disabled and no account is registered",
175
+ resets_at: null,
176
+ fallback: "api_key_route",
177
+ });
178
+ return null;
179
+ }
180
+ // Legacy default-subject ladder (unmigrated stores): under an EFFECTIVE
181
+ // `rotate` (explicit, or the A6 kind-aware `auto` on a subscription
182
+ // route), a fresh default-subject headroom breach — or its observed live
183
+ // block (A4) — starts on the next eligible subscription profile instead;
184
+ // effective `fail`/`ask` change nothing.
185
+ const breach = profileHeadroomBreach(snapshots, harnessId, null, policy.headroom_threshold, model);
186
+ const readyProfileIds = effectiveLimitAction(policy, ctx.defaultRoute) === "rotate" &&
187
+ ctx.defaultRoute === "local_session" &&
188
+ (breach !== null ||
189
+ profileQuotaBlock(snapshots, harnessId, null, ctx.defaultRoute, model) !== null)
190
+ ? await readyIds()
191
+ : new Set();
192
+ return preflightDefaultSubject({
193
+ harnessId,
194
+ policy,
195
+ registry,
196
+ snapshots,
197
+ readyProfileIds,
198
+ defaultRoute: ctx.defaultRoute,
199
+ unusable: ctx.unusable,
200
+ model,
201
+ emit,
202
+ });
203
+ }
204
+ // The EXPLICIT `api_key` preference is the user's paid election (Q3=A): it
205
+ // takes the disclosed PAID route BEFORE binding/pool resolution — a healthy
206
+ // subscription pool must never spend subscription quota over that explicit
207
+ // choice (ARCHITECTURE: explicit `api_key` excludes native fallback). Only
208
+ // an explicit pin outranks it (strict pin doctrine, handled above). Mirrors
209
+ // the exhausted-pool paid branch below: noted, disclosed, never a row.
210
+ if (ctx.authPreference === "api_key") {
211
+ ctx.notePoolApiKeyRoute();
212
+ emit("route.account.pool_exhausted", {
213
+ harness_id: harnessId,
214
+ reason: "explicit api_key preference excludes the subscription pool",
215
+ resets_at: null,
216
+ fallback: "api_key_route",
217
+ });
218
+ return null;
219
+ }
220
+ const boundId = ctx.boundProfileId;
221
+ let boundSwitchReason = null;
222
+ if (boundId) {
223
+ // D-U1 order 2: the thread's bound account resolves BEFORE the pool —
224
+ // stickiness is not a pool ranking preference, so an explicit
225
+ // rotation_eligible list cannot evict a healthy binding.
226
+ const bound = pool.find((row) => row.profile_id === boundId);
227
+ // A7: a live typed `credential_unusable` observation condemns the BOUND
228
+ // row at this composition point too (this file's contract: condemned rows
229
+ // are refused at readiness composition, never re-discovered by spending
230
+ // an attempt) — the binding re-pools with the disclosed lane switch.
231
+ const dead = bound ? liveUnusableFor(ctx.unusable, harnessId, boundId, model) : null;
232
+ if (bound && dead) {
233
+ boundSwitchReason = `the bound account's credential is unusable (${dead.code})`;
234
+ }
235
+ else if (bound) {
236
+ const verdict = await selectedProfileAvailability({
237
+ registry,
238
+ profileId: boundId,
239
+ harnessId,
240
+ probe: ctx.probe,
241
+ quota,
242
+ });
243
+ const breach = profileHeadroomBreach(snapshots, harnessId, boundId, policy.headroom_threshold, model);
244
+ // A4: an observed live block on the bound row (reactive cooldown /
245
+ // spent window) unbinds it exactly like a fresh breach would.
246
+ const block = breach
247
+ ? null
248
+ : profileQuotaBlock(snapshots, harnessId, boundId, limitSubjectRoute(bound), model);
249
+ if (verdict === "available" && !breach && !block)
250
+ return bound;
251
+ boundSwitchReason =
252
+ breach || block
253
+ ? `quota window exhausted${(breach?.resets_at ?? block?.resets_at)
254
+ ? ` (resets ${breach?.resets_at ?? block?.resets_at})`
255
+ : ""}`
256
+ : (verdict ?? "not ready");
257
+ }
258
+ else {
259
+ boundSwitchReason = "the bound account was disabled or removed";
260
+ }
261
+ }
262
+ const readyProfileIds = await readyIds();
263
+ const selection = selectFromAccountPool({
264
+ registry,
265
+ harnessId,
266
+ snapshots,
267
+ readyProfileIds,
268
+ headroomThreshold: policy.headroom_threshold,
269
+ model,
270
+ });
271
+ if (selection.outcome === "selected") {
272
+ const chosen = selection.candidate.profile;
273
+ if (boundId && boundId !== chosen.profile_id) {
274
+ // Q1=A: a lane switch is DISCLOSED, never silent (the continuity layer
275
+ // additionally discloses the hydration on the turn).
276
+ emit("route.account.lane_switch", {
277
+ harness_id: harnessId,
278
+ thread_id: ctx.threadId,
279
+ from_profile_id: boundId,
280
+ to_profile_id: chosen.profile_id,
281
+ reason: boundSwitchReason,
282
+ });
283
+ }
284
+ emit("route.account.pool_selected", {
285
+ harness_id: harnessId,
286
+ profile_id: chosen.profile_id,
287
+ quota: selection.candidate.verdict.kind,
288
+ ...(selection.candidate.verdict.kind === "fresh_headroom"
289
+ ? { headroom: selection.candidate.verdict.headroom }
290
+ : {}),
291
+ });
292
+ return chosen;
293
+ }
294
+ // Pool empty/exhausted (owner Q3=A, superseding Q2=A): the PRIMARY verdict
295
+ // is the typed `credential_pool_exhausted` TERMINAL carrying the pool's
296
+ // earliest known reset — an unpinned run under `auto` (and under explicit
297
+ // `subscription`) WAITS for a window instead of silently taking the paid
298
+ // route (the same principle as PR-A's kind-aware `limit_action: auto`,
299
+ // which resolves a metered subject to `fail`: `auto` never silently spends
300
+ // money). The EXPLICIT `api_key` preference never reaches this point — its
301
+ // paid election already returned before binding/pool resolution above.
302
+ emit("route.account.pool_exhausted", {
303
+ harness_id: harnessId,
304
+ reason: selection.outcome,
305
+ resets_at: selection.resets_at,
306
+ fallback: null,
307
+ ...(boundId ? { from_profile_id: boundId } : {}),
308
+ });
309
+ throw credentialPoolExhausted({
310
+ harnessId,
311
+ // The triggering subject of a preflight pool refusal is the thread's
312
+ // bound row when one exists (its exhaustion is what forced the pool
313
+ // decision); a fresh unbound run has no subject of its own.
314
+ profileId: boundId,
315
+ reason: "profile_headroom_preflight",
316
+ candidates: poolExhaustionCandidates(ctx, readyProfileIds),
317
+ subjectLimit: null,
318
+ });
319
+ }
320
+ //# 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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attemptOutputMarkers.d.ts","sourceRoot":"","sources":["../src/attemptOutputMarkers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD,MAAM,WAAW,oBAAoB;IACnC;yEACqE;IACrE,gBAAgB,EAAE,OAAO,CAAC;IAC1B;2BACuB;IACvB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,uBAAuB,IAAI,oBAAoB,CAE9D;AAwBD,wBAAgB,2BAA2B,CAAC,CAAC,EAAE,oBAAoB,EAAE,EAAE,EAAE,YAAY,GAAG,IAAI,CAG3F"}