@claudexor/orchestrator 1.0.1 → 2.1.3

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 (87) hide show
  1. package/dist/attemptTelemetry.d.ts +47 -2
  2. package/dist/attemptTelemetry.d.ts.map +1 -1
  3. package/dist/attemptTelemetry.js +100 -2
  4. package/dist/attemptTelemetry.js.map +1 -1
  5. package/dist/attemptUsage.d.ts +19 -0
  6. package/dist/attemptUsage.d.ts.map +1 -0
  7. package/dist/attemptUsage.js +29 -0
  8. package/dist/attemptUsage.js.map +1 -0
  9. package/dist/candidateEvidence.d.ts +22 -0
  10. package/dist/candidateEvidence.d.ts.map +1 -0
  11. package/dist/candidateEvidence.js +42 -0
  12. package/dist/candidateEvidence.js.map +1 -0
  13. package/dist/candidateOutputs.d.ts +43 -0
  14. package/dist/candidateOutputs.d.ts.map +1 -0
  15. package/dist/candidateOutputs.js +146 -0
  16. package/dist/candidateOutputs.js.map +1 -0
  17. package/dist/contract-gates.d.ts +21 -0
  18. package/dist/contract-gates.d.ts.map +1 -0
  19. package/dist/contract-gates.js +93 -0
  20. package/dist/contract-gates.js.map +1 -0
  21. package/dist/credential-profiles.d.ts +162 -0
  22. package/dist/credential-profiles.d.ts.map +1 -0
  23. package/dist/credential-profiles.js +357 -0
  24. package/dist/credential-profiles.js.map +1 -0
  25. package/dist/diffReview.d.ts +12 -1
  26. package/dist/diffReview.d.ts.map +1 -1
  27. package/dist/diffReview.js +117 -18
  28. package/dist/diffReview.js.map +1 -1
  29. package/dist/index.d.ts +1 -0
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +1 -0
  32. package/dist/index.js.map +1 -1
  33. package/dist/modelGovernance.d.ts +6 -1
  34. package/dist/modelGovernance.d.ts.map +1 -1
  35. package/dist/modelGovernance.js +8 -2
  36. package/dist/modelGovernance.js.map +1 -1
  37. package/dist/orchestrateExecutor.d.ts +43 -0
  38. package/dist/orchestrateExecutor.d.ts.map +1 -0
  39. package/dist/orchestrateExecutor.js +207 -0
  40. package/dist/orchestrateExecutor.js.map +1 -0
  41. package/dist/orchestratePlanner.d.ts.map +1 -1
  42. package/dist/orchestratePlanner.js.map +1 -1
  43. package/dist/orchestrator.d.ts +97 -85
  44. package/dist/orchestrator.d.ts.map +1 -1
  45. package/dist/orchestrator.js +1056 -1090
  46. package/dist/orchestrator.js.map +1 -1
  47. package/dist/outcomeReducer.d.ts +25 -0
  48. package/dist/outcomeReducer.d.ts.map +1 -0
  49. package/dist/outcomeReducer.js +121 -0
  50. package/dist/outcomeReducer.js.map +1 -0
  51. package/dist/policyFindings.d.ts +18 -0
  52. package/dist/policyFindings.d.ts.map +1 -0
  53. package/dist/policyFindings.js +166 -0
  54. package/dist/policyFindings.js.map +1 -0
  55. package/dist/requestRequirements.d.ts +38 -0
  56. package/dist/requestRequirements.d.ts.map +1 -0
  57. package/dist/requestRequirements.js +127 -0
  58. package/dist/requestRequirements.js.map +1 -0
  59. package/dist/reviewerPanel.d.ts +15 -1
  60. package/dist/reviewerPanel.d.ts.map +1 -1
  61. package/dist/reviewerPanel.js +85 -2
  62. package/dist/reviewerPanel.js.map +1 -1
  63. package/dist/routeContext.d.ts +37 -0
  64. package/dist/routeContext.d.ts.map +1 -0
  65. package/dist/routeContext.js +38 -0
  66. package/dist/routeContext.js.map +1 -0
  67. package/dist/runSupport.d.ts +37 -21
  68. package/dist/runSupport.d.ts.map +1 -1
  69. package/dist/runSupport.js +102 -78
  70. package/dist/runSupport.js.map +1 -1
  71. package/dist/runTelemetryWriter.d.ts +20 -0
  72. package/dist/runTelemetryWriter.d.ts.map +1 -0
  73. package/dist/runTelemetryWriter.js +68 -0
  74. package/dist/runTelemetryWriter.js.map +1 -0
  75. package/dist/runTerminals.d.ts +7 -1
  76. package/dist/runTerminals.d.ts.map +1 -1
  77. package/dist/runTerminals.js +42 -4
  78. package/dist/runTerminals.js.map +1 -1
  79. package/dist/structuredOutput.d.ts +33 -0
  80. package/dist/structuredOutput.d.ts.map +1 -0
  81. package/dist/structuredOutput.js +106 -0
  82. package/dist/structuredOutput.js.map +1 -0
  83. package/package.json +19 -18
  84. package/dist/finalVerifier.d.ts +0 -25
  85. package/dist/finalVerifier.d.ts.map +0 -1
  86. package/dist/finalVerifier.js +0 -110
  87. package/dist/finalVerifier.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"candidateOutputs.js","sourceRoot":"","sources":["../src/candidateOutputs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import type { AccessProfile, GateResult, ProjectConfig, TaskContract, TestCommandGrant, TestCommandInvocation } from "@claudexor/schema";
2
+ import type { GateSpec } from "@claudexor/review";
3
+ type GateCommands = TaskContract["tests"]["commands"];
4
+ /** Merge the three command authorities once and attach external grants only
5
+ * to versioned project commands. Spec/operator commands are explicit input. */
6
+ export declare function resolveContractGates(input: {
7
+ repoRoot: string;
8
+ effectiveAccess: AccessProfile;
9
+ config: ProjectConfig;
10
+ trustGrants: TestCommandGrant[];
11
+ specCommands: TestCommandInvocation[];
12
+ operatorCommands: TestCommandInvocation[];
13
+ projectCommands: TestCommandInvocation[];
14
+ }): {
15
+ commands: GateCommands;
16
+ autoProtectedPaths: string[];
17
+ };
18
+ export declare function gateSpecsFromContract(contract: TaskContract): GateSpec[];
19
+ export declare function renderTestsEvidence(contract: TaskContract, gates?: GateResult[]): string;
20
+ export {};
21
+ //# sourceMappingURL=contract-gates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract-gates.d.ts","sourceRoot":"","sources":["../src/contract-gates.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,UAAU,EACV,aAAa,EACb,YAAY,EACZ,gBAAgB,EAChB,qBAAqB,EACtB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAElD,KAAK,YAAY,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC,UAAU,CAAC,CAAC;AAEtD;+EAC+E;AAC/E,wBAAgB,oBAAoB,CAAC,KAAK,EAAE;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,aAAa,CAAC;IAC/B,MAAM,EAAE,aAAa,CAAC;IACtB,WAAW,EAAE,gBAAgB,EAAE,CAAC;IAChC,YAAY,EAAE,qBAAqB,EAAE,CAAC;IACtC,gBAAgB,EAAE,qBAAqB,EAAE,CAAC;IAC1C,eAAe,EAAE,qBAAqB,EAAE,CAAC;CAC1C,GAAG;IAAE,QAAQ,EAAE,YAAY,CAAC;IAAC,kBAAkB,EAAE,MAAM,EAAE,CAAA;CAAE,CA+C3D;AAED,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,YAAY,GAAG,QAAQ,EAAE,CAaxE;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,YAAY,EAAE,KAAK,CAAC,EAAE,UAAU,EAAE,GAAG,MAAM,CAiCxF"}
@@ -0,0 +1,93 @@
1
+ import { canonicalProjectRoot, containsSecretLikeToken, hashJson, sha256 } from "@claudexor/util";
2
+ import { gateProtectedPaths } from "./runSupport.js";
3
+ /** Merge the three command authorities once and attach external grants only
4
+ * to versioned project commands. Spec/operator commands are explicit input. */
5
+ export function resolveContractGates(input) {
6
+ const projectDigest = sha256(canonicalProjectRoot(input.repoRoot));
7
+ const configDigest = hashJson(input.config);
8
+ const seen = new Set();
9
+ const sourced = [
10
+ ...input.specCommands.map((command) => ({ command, source: "spec" })),
11
+ ...input.operatorCommands.map((command) => ({ command, source: "operator" })),
12
+ ...input.projectCommands.map((command) => ({ command, source: "project" })),
13
+ ];
14
+ const commands = sourced
15
+ .filter(({ command }) => {
16
+ const digest = hashJson(command);
17
+ if (seen.has(digest))
18
+ return false;
19
+ seen.add(digest);
20
+ return true;
21
+ })
22
+ .map(({ command, source }, index) => {
23
+ if (containsSecretLikeToken(JSON.stringify(command))) {
24
+ throw new Error(`gate command ${index + 1} contains secret-like token; refusing to persist artifact`);
25
+ }
26
+ const commandDigest = hashJson(command);
27
+ const trustGrant = source === "project"
28
+ ? (input.trustGrants.find((candidate) => candidate.projectDigest === projectDigest &&
29
+ candidate.configDigest === configDigest &&
30
+ candidate.commandDigest === commandDigest &&
31
+ candidate.accessProfile === input.effectiveAccess) ?? null)
32
+ : null;
33
+ return {
34
+ id: `gate-${index + 1}`,
35
+ ...command,
36
+ required: true,
37
+ trust_required: source === "project",
38
+ trust_grant: trustGrant,
39
+ };
40
+ });
41
+ return {
42
+ commands,
43
+ autoProtectedPaths: [
44
+ ...new Set(gateProtectedPaths(commands.flatMap(({ program, args }) => [program, ...args]))),
45
+ ],
46
+ };
47
+ }
48
+ export function gateSpecsFromContract(contract) {
49
+ return contract.tests.commands.map((command) => ({
50
+ id: command.id,
51
+ program: command.program,
52
+ args: command.args,
53
+ cwd: command.cwd,
54
+ envAllowlist: command.envAllowlist,
55
+ trustRequired: command.trust_required,
56
+ trustGrant: command.trust_grant,
57
+ projectRoot: contract.repo.root,
58
+ accessProfile: contract.access.effective_profile,
59
+ required: command.required,
60
+ }));
61
+ }
62
+ export function renderTestsEvidence(contract, gates) {
63
+ const specs = gateSpecsFromContract(contract);
64
+ if (gates === undefined || gates.length === 0) {
65
+ if (specs.length === 0)
66
+ return "(no test commands configured)";
67
+ const heading = gates
68
+ ? "Configured test commands did not produce gate results before this review:"
69
+ : "Configured test commands (not run yet):";
70
+ return [
71
+ heading,
72
+ ...specs.map((spec) => `- ${spec.id}${spec.required === false ? " (optional)" : ""}: ${JSON.stringify([spec.program, ...spec.args])}`),
73
+ ].join("\n");
74
+ }
75
+ const required = gates.filter((gate) => gate.required);
76
+ const lines = [
77
+ `Gate results: required ${required.filter((gate) => gate.status === "passed").length}/${required.length} passed; total ${gates.length}.`,
78
+ ];
79
+ const appendTail = (label, text) => {
80
+ if (!text)
81
+ return;
82
+ lines.push(` ${label}: |`, ...text.split(/\r?\n/).map((line) => ` ${line}`));
83
+ };
84
+ for (const gate of gates) {
85
+ lines.push(`- ${gate.id}${gate.required === false ? " (optional)" : ""}: ${gate.status}; exit=${gate.exit_code ?? "null"}; duration_ms=${gate.duration_ms}`, ` command: ${gate.command}`);
86
+ if (gate.output_truncated)
87
+ lines.push(" output_truncated: true");
88
+ appendTail("stdout_tail", gate.stdout_tail);
89
+ appendTail("stderr_tail", gate.stderr_tail);
90
+ }
91
+ return lines.join("\n");
92
+ }
93
+ //# sourceMappingURL=contract-gates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract-gates.js","sourceRoot":"","sources":["../src/contract-gates.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,162 @@
1
+ import type { CredentialProfile, HarnessEvent, HarnessRunSpec, QuotaSnapshot } from "@claudexor/schema";
2
+ /**
3
+ * The ONE resolve owner for credential profiles (INV-135): explicit id →
4
+ * durable registry entry for exactly this harness. Unknown, disabled, or
5
+ * harness-mismatched ids throw a typed refusal — an explicit profile must
6
+ * never silently become the default credential ladder.
7
+ */
8
+ export declare function resolveCredentialProfile(registry: readonly CredentialProfile[], wanted: string, harnessId: string): CredentialProfile;
9
+ export declare function selectedProfileAvailability(input: {
10
+ registry: readonly CredentialProfile[];
11
+ profileId?: string | null;
12
+ harnessId: string;
13
+ probe?: (profile: CredentialProfile) => Promise<{
14
+ availability: string;
15
+ verification: string;
16
+ detail?: string | null;
17
+ }>;
18
+ }): Promise<string | null>;
19
+ export interface ProfilePolicy {
20
+ limit_action: "fail" | "ask" | "rotate";
21
+ rotation_eligible: string[];
22
+ headroom_threshold: number;
23
+ }
24
+ /** Typed quota evidence for a profile over its headroom bound (provenance). */
25
+ export interface HeadroomBreach {
26
+ constraint_id: string;
27
+ used_ratio: number;
28
+ threshold: number;
29
+ resets_at: string | null;
30
+ }
31
+ /**
32
+ * Proactive headroom check (W5.4 `profile_headroom_preflight`): the SELECTED
33
+ * profile's freshest snapshot windows against the policy threshold. Unknown
34
+ * usage is NOT a breach — rotation never triggers on missing data.
35
+ */
36
+ export declare function profileHeadroomBreach(snapshots: readonly QuotaSnapshot[], harnessId: string, profileId: string | null, threshold: number): HeadroomBreach | null;
37
+ /**
38
+ * The next rotation target after `currentProfileId` (W5.4): policy order wins
39
+ * (`rotation_eligible`), else every enabled profile of the harness in registry
40
+ * order. The current profile and disabled/unknown ids never come back; a
41
+ * profile already over the headroom bound is skipped (rotating INTO a spent
42
+ * subscription is not a failover). Cross-subscription rotation of one vendor
43
+ * is allowed by owner decision — but rotation NEVER crosses credential kinds
44
+ * (release wave round-16 BLOCK): a subscription→API-key swap silently changes
45
+ * the payment model mid-attempt, and the attempt's first-wins auth-route
46
+ * receipt (decided once before spawn) would misvalue metered usage as
47
+ * subscription entitlement, bypassing a finite cash cap.
48
+ */
49
+ export declare function nextEligibleProfile(registry: readonly CredentialProfile[], harnessId: string, policy: ProfilePolicy, current: Pick<CredentialProfile, "profile_id" | "credential_kind"> | null, snapshots: readonly QuotaSnapshot[], excluded?: ReadonlySet<string>): CredentialProfile | null;
50
+ /**
51
+ * `rotation_retry_eligible` (sol #30): a failover retry is allowed ONLY when
52
+ * the attempt saw a TYPED vendor limit AND produced no deliverable and no
53
+ * workspace mutation — a partially-acted attempt never silently reruns.
54
+ */
55
+ export declare function rotationRetryEligible(input: {
56
+ sawTypedLimit: boolean;
57
+ deliverableEmpty: boolean;
58
+ }): boolean;
59
+ type EmitFn = (type: "route.profile.headroom_exceeded" | "route.profile.rotated" | "route.profile.rotation_exhausted", payload: Record<string, unknown>) => void;
60
+ /**
61
+ * `profile_headroom_preflight` (W5.4): BEFORE spawn, the selected profile's
62
+ * freshest quota windows are checked against the policy threshold. A breach
63
+ * is always a typed event; `rotate` swaps to the next eligible profile with
64
+ * provenance, `ask`/`fail` proceed on the selected profile — the runtime
65
+ * `vendor_limit_rejected` evidence stays the terminating truth.
66
+ */
67
+ export declare function preflightCredentialProfile(args: {
68
+ profile: CredentialProfile;
69
+ harnessId: string;
70
+ policy: ProfilePolicy;
71
+ registry: readonly CredentialProfile[];
72
+ snapshots: readonly QuotaSnapshot[];
73
+ emit: EmitFn;
74
+ }): CredentialProfile;
75
+ /**
76
+ * Default-subject start selection (INV-135, owner scope "auto-balance"): when
77
+ * NO profile is pinned, the policy is `rotate`, and the DEFAULT store's own
78
+ * fresh quota window is at/over the headroom bound, pick the first eligible
79
+ * SUBSCRIPTION profile instead of spawning into a near-certain vendor limit.
80
+ * Strictly opt-in: `fail`/`ask` keep today's default-user behavior untouched
81
+ * (the profile engine's fail action governs PINNED profiles only — throwing
82
+ * here would brick every default user at 90% quota). Unknown or stale usage
83
+ * never rotates.
84
+ */
85
+ export declare function preflightDefaultSubject(args: {
86
+ harnessId: string;
87
+ policy: ProfilePolicy;
88
+ registry: readonly CredentialProfile[];
89
+ snapshots: readonly QuotaSnapshot[];
90
+ emit: EmitFn;
91
+ }): CredentialProfile | null;
92
+ /**
93
+ * Reactive failover plan (`vendor_limit_rejected`, W5.4): rotation fires ONLY
94
+ * on the typed predicate under a `rotate` policy, marks the current profile
95
+ * tried (at most once per attempt each), and returns the next target with
96
+ * provenance already emitted — or null when the attempt must fail as-is.
97
+ * `currentProfile: null` is the DEFAULT subject: allowed ONLY when the caller
98
+ * proves the attempt's pre-spawn route was vendor_native (a metered default
99
+ * hitting a limit is a budget fact, not a subscription to fail over from).
100
+ */
101
+ export declare function planReactiveRotation(args: {
102
+ currentProfile: CredentialProfile | null;
103
+ defaultRouteWasVendorNative?: boolean;
104
+ harnessId: string;
105
+ attemptId: string;
106
+ policy: ProfilePolicy;
107
+ registry: readonly CredentialProfile[];
108
+ snapshots: readonly QuotaSnapshot[];
109
+ triedProfiles: Set<string>;
110
+ sawTypedLimit: boolean;
111
+ deliverableEmpty: boolean;
112
+ lastLimit: {
113
+ retryDelayMs: number | null;
114
+ resetsAt: string | null;
115
+ } | null;
116
+ emit: EmitFn;
117
+ }): CredentialProfile | null;
118
+ /**
119
+ * INV-135 at the engine boundary: a cached vendor session resumes ONLY under
120
+ * exactly the profile it was recorded with (null-default equality included) —
121
+ * regardless of what the caller's map claims. Preflight rotation changing the
122
+ * profile therefore starts fresh.
123
+ */
124
+ export declare function resumeSessionForProfile(cached: {
125
+ sessionId: string;
126
+ profileId: string | null;
127
+ } | undefined, profile: CredentialProfile | null): string | null;
128
+ /**
129
+ * Record a harness-emitted native session id for future thread resume; the
130
+ * observer never fails the run. profileId is the adapter's per-event stamp —
131
+ * the EFFECTIVE profile (rotation makes it differ from the requested id).
132
+ */
133
+ export declare function observeNativeSessionEvent(input: {
134
+ onSessionObserved?: (harnessId: string, nativeSessionId: string, observedModel?: string | null, profileId?: string | null) => void;
135
+ } | undefined, harnessId: string, ev: HarnessEvent): void;
136
+ /**
137
+ * Shared reactive-failover step for BOTH lanes: given the attempt's typed
138
+ * evidence, plan a rotation and rebuild the spec on a NEW vendor session
139
+ * under the next profile — or return null when the attempt must fail as-is.
140
+ */
141
+ export declare function rotateSpecOnTypedLimit(args: {
142
+ spec: HarnessRunSpec;
143
+ harnessId: string;
144
+ attemptId: string;
145
+ policy: ProfilePolicy;
146
+ registry: readonly CredentialProfile[];
147
+ snapshots: readonly QuotaSnapshot[];
148
+ triedProfiles: Set<string>;
149
+ sawTypedLimit: boolean;
150
+ deliverableEmpty: boolean;
151
+ lastLimit: {
152
+ retryDelayMs: number | null;
153
+ resetsAt: string | null;
154
+ } | null;
155
+ emit: EmitFn;
156
+ newSessionId: () => string;
157
+ /** Pre-spawn route estimate for a profile-less spec: default-subject
158
+ * rotation is allowed ONLY off a vendor_native attempt. */
159
+ defaultRouteWasVendorNative?: boolean;
160
+ }): HarnessRunSpec | null;
161
+ export {};
162
+ //# sourceMappingURL=credential-profiles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credential-profiles.d.ts","sourceRoot":"","sources":["../src/credential-profiles.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,357 @@
1
+ import { HarnessRunSpec as HarnessRunSpecSchema } from "@claudexor/schema";
2
+ /**
3
+ * The ONE resolve owner for credential profiles (INV-135): explicit id →
4
+ * durable registry entry for exactly this harness. Unknown, disabled, or
5
+ * harness-mismatched ids throw a typed refusal — an explicit profile must
6
+ * never silently become the default credential ladder.
7
+ */
8
+ export function resolveCredentialProfile(registry, wanted, harnessId) {
9
+ const match = registry.find((p) => p.profile_id === wanted && p.harness_id === harnessId);
10
+ if (!match) {
11
+ throw new Error(`credential profile "${wanted}" is not registered for harness "${harnessId}"`);
12
+ }
13
+ if (!match.enabled) {
14
+ throw new Error(`credential profile "${wanted}" (${harnessId}) is disabled`);
15
+ }
16
+ return match;
17
+ }
18
+ export async function selectedProfileAvailability(input) {
19
+ if (!input.profileId)
20
+ return null;
21
+ let profile;
22
+ try {
23
+ profile = resolveCredentialProfile(input.registry, input.profileId, input.harnessId);
24
+ }
25
+ catch (error) {
26
+ return error instanceof Error ? error.message : String(error);
27
+ }
28
+ if (!input.probe)
29
+ return `harness "${input.harnessId}" has no profile probe`;
30
+ const result = await input.probe(profile);
31
+ const verificationAdmits = result.verification === "passed" ||
32
+ (profile.credential_kind === "api_key" && result.verification === "not_run");
33
+ return result.availability === "available" && verificationAdmits
34
+ ? "available"
35
+ : (result.detail ?? `${result.availability}/${result.verification}`);
36
+ }
37
+ /**
38
+ * Proactive headroom check (W5.4 `profile_headroom_preflight`): the SELECTED
39
+ * profile's freshest snapshot windows against the policy threshold. Unknown
40
+ * usage is NOT a breach — rotation never triggers on missing data.
41
+ */
42
+ export function profileHeadroomBreach(snapshots, harnessId, profileId, threshold) {
43
+ for (const snapshot of snapshots) {
44
+ // FRESH evidence only (release wave tier1 #3): a stale or unknown reading
45
+ // must never breach headroom, rotate a profile away, or fail selection.
46
+ if (snapshot.freshness !== "fresh")
47
+ continue;
48
+ if (snapshot.subject.harness !== harnessId)
49
+ continue;
50
+ if ((snapshot.subject.subject_id ?? null) !== profileId)
51
+ continue;
52
+ for (const constraint of snapshot.constraints) {
53
+ if (constraint.used_ratio !== null && constraint.used_ratio >= threshold) {
54
+ return {
55
+ constraint_id: constraint.id,
56
+ used_ratio: constraint.used_ratio,
57
+ threshold,
58
+ resets_at: constraint.resets_at,
59
+ };
60
+ }
61
+ }
62
+ }
63
+ return null;
64
+ }
65
+ /**
66
+ * The next rotation target after `currentProfileId` (W5.4): policy order wins
67
+ * (`rotation_eligible`), else every enabled profile of the harness in registry
68
+ * order. The current profile and disabled/unknown ids never come back; a
69
+ * profile already over the headroom bound is skipped (rotating INTO a spent
70
+ * subscription is not a failover). Cross-subscription rotation of one vendor
71
+ * is allowed by owner decision — but rotation NEVER crosses credential kinds
72
+ * (release wave round-16 BLOCK): a subscription→API-key swap silently changes
73
+ * the payment model mid-attempt, and the attempt's first-wins auth-route
74
+ * receipt (decided once before spawn) would misvalue metered usage as
75
+ * subscription entitlement, bypassing a finite cash cap.
76
+ */
77
+ export function nextEligibleProfile(registry, harnessId, policy, current, snapshots, excluded = new Set()) {
78
+ const pool = registry.filter((p) => p.harness_id === harnessId && p.enabled);
79
+ const ordered = policy.rotation_eligible.length > 0
80
+ ? policy.rotation_eligible
81
+ .map((id) => pool.find((p) => p.profile_id === id))
82
+ .filter((p) => p !== undefined)
83
+ : pool;
84
+ for (const candidate of ordered) {
85
+ if (candidate.profile_id === current?.profile_id)
86
+ continue;
87
+ if (excluded.has(candidate.profile_id))
88
+ continue;
89
+ // Fail-CLOSED kind guard (round-17 hardening of the round-16 BLOCK): the
90
+ // kind comes from the TYPED current profile in hand — never re-found in
91
+ // a freshly reloaded pool, where a mid-flight disable/remove of the
92
+ // current profile would have silently dropped the cross-kind prohibition.
93
+ if (current !== null && candidate.credential_kind !== current.credential_kind)
94
+ continue;
95
+ // `current === null` is the DEFAULT subject (the vendor-native store): the
96
+ // same round-16 BLOCK applies — rotating the subscription default INTO an
97
+ // api_key profile would silently change the payment model mid-attempt.
98
+ if (current === null && candidate.credential_kind === "api_key")
99
+ continue;
100
+ if (profileHeadroomBreach(snapshots, harnessId, candidate.profile_id, policy.headroom_threshold))
101
+ continue;
102
+ return candidate;
103
+ }
104
+ return null;
105
+ }
106
+ /**
107
+ * `rotation_retry_eligible` (sol #30): a failover retry is allowed ONLY when
108
+ * the attempt saw a TYPED vendor limit AND produced no deliverable and no
109
+ * workspace mutation — a partially-acted attempt never silently reruns.
110
+ */
111
+ export function rotationRetryEligible(input) {
112
+ return input.sawTypedLimit && input.deliverableEmpty;
113
+ }
114
+ function emitRotationExhausted(args) {
115
+ const excluded = args.excluded ?? new Set();
116
+ const candidates = args.registry
117
+ .filter((profile) => profile.harness_id === args.harnessId && profile.enabled)
118
+ .map((profile) => {
119
+ const breach = profileHeadroomBreach(args.snapshots, args.harnessId, profile.profile_id, args.policy.headroom_threshold);
120
+ const rejected = profile.profile_id === args.current?.profile_id
121
+ ? "current"
122
+ : args.policy.rotation_eligible.length > 0 &&
123
+ !args.policy.rotation_eligible.includes(profile.profile_id)
124
+ ? "not_in_rotation_policy"
125
+ : excluded.has(profile.profile_id)
126
+ ? "already_tried"
127
+ : args.current && profile.credential_kind !== args.current.credential_kind
128
+ ? "credential_kind_mismatch"
129
+ : args.current === null && profile.credential_kind === "api_key"
130
+ ? "credential_kind_mismatch"
131
+ : breach
132
+ ? "headroom_exceeded"
133
+ : "not_selected";
134
+ return {
135
+ profile_id: profile.profile_id,
136
+ credential_kind: profile.credential_kind,
137
+ rejected,
138
+ headroom: breach,
139
+ };
140
+ });
141
+ args.emit("route.profile.rotation_exhausted", {
142
+ harness_id: args.harnessId,
143
+ attempt_id: args.attemptId,
144
+ from_profile_id: args.current?.profile_id ?? null,
145
+ reason: args.reason,
146
+ threshold: args.policy.headroom_threshold,
147
+ candidates,
148
+ });
149
+ }
150
+ /**
151
+ * `profile_headroom_preflight` (W5.4): BEFORE spawn, the selected profile's
152
+ * freshest quota windows are checked against the policy threshold. A breach
153
+ * is always a typed event; `rotate` swaps to the next eligible profile with
154
+ * provenance, `ask`/`fail` proceed on the selected profile — the runtime
155
+ * `vendor_limit_rejected` evidence stays the terminating truth.
156
+ */
157
+ export function preflightCredentialProfile(args) {
158
+ const { profile, harnessId, policy, registry, snapshots, emit } = args;
159
+ const breach = profileHeadroomBreach(snapshots, harnessId, profile.profile_id, policy.headroom_threshold);
160
+ if (!breach)
161
+ return profile;
162
+ emit("route.profile.headroom_exceeded", {
163
+ harness_id: harnessId,
164
+ profile_id: profile.profile_id,
165
+ action: policy.limit_action,
166
+ constraint_id: breach.constraint_id,
167
+ used_ratio: breach.used_ratio,
168
+ threshold: breach.threshold,
169
+ resets_at: breach.resets_at,
170
+ });
171
+ if (policy.limit_action === "fail") {
172
+ // The documented FAIL action fails (release wave tier1 #4): a FRESH breach
173
+ // under fail refuses before spawn with the evidence, instead of silently
174
+ // proceeding into a vendor rejection.
175
+ throw new Error(`credential profile "${profile.profile_id}" (${harnessId}) is over its headroom threshold ` +
176
+ `(${breach.constraint_id} at ${Math.round(breach.used_ratio * 100)}% >= ${Math.round(breach.threshold * 100)}%; ` +
177
+ `limit_action=fail${breach.resets_at ? `; resets ${breach.resets_at}` : ""})`);
178
+ }
179
+ if (policy.limit_action !== "rotate")
180
+ return profile;
181
+ const next = nextEligibleProfile(registry, harnessId, policy, profile, snapshots);
182
+ if (!next) {
183
+ emitRotationExhausted({
184
+ current: profile,
185
+ harnessId,
186
+ policy,
187
+ registry,
188
+ snapshots,
189
+ reason: "profile_headroom_preflight",
190
+ emit,
191
+ });
192
+ return profile;
193
+ }
194
+ emit("route.profile.rotated", {
195
+ harness_id: harnessId,
196
+ from_profile_id: profile.profile_id,
197
+ to_profile_id: next.profile_id,
198
+ reason: "profile_headroom_preflight",
199
+ constraint_id: breach.constraint_id,
200
+ used_ratio: breach.used_ratio,
201
+ resets_at: breach.resets_at,
202
+ });
203
+ return next;
204
+ }
205
+ /**
206
+ * Default-subject start selection (INV-135, owner scope "auto-balance"): when
207
+ * NO profile is pinned, the policy is `rotate`, and the DEFAULT store's own
208
+ * fresh quota window is at/over the headroom bound, pick the first eligible
209
+ * SUBSCRIPTION profile instead of spawning into a near-certain vendor limit.
210
+ * Strictly opt-in: `fail`/`ask` keep today's default-user behavior untouched
211
+ * (the profile engine's fail action governs PINNED profiles only — throwing
212
+ * here would brick every default user at 90% quota). Unknown or stale usage
213
+ * never rotates.
214
+ */
215
+ export function preflightDefaultSubject(args) {
216
+ const { harnessId, policy, registry, snapshots, emit } = args;
217
+ if (policy.limit_action !== "rotate")
218
+ return null;
219
+ const breach = profileHeadroomBreach(snapshots, harnessId, null, policy.headroom_threshold);
220
+ if (!breach)
221
+ return null;
222
+ emit("route.profile.headroom_exceeded", {
223
+ harness_id: harnessId,
224
+ profile_id: null,
225
+ action: policy.limit_action,
226
+ constraint_id: breach.constraint_id,
227
+ used_ratio: breach.used_ratio,
228
+ threshold: breach.threshold,
229
+ resets_at: breach.resets_at,
230
+ });
231
+ const next = nextEligibleProfile(registry, harnessId, policy, null, snapshots);
232
+ if (!next) {
233
+ emitRotationExhausted({
234
+ current: null,
235
+ harnessId,
236
+ policy,
237
+ registry,
238
+ snapshots,
239
+ reason: "profile_headroom_preflight",
240
+ emit,
241
+ });
242
+ return null;
243
+ }
244
+ emit("route.profile.rotated", {
245
+ harness_id: harnessId,
246
+ from_profile_id: null,
247
+ to_profile_id: next.profile_id,
248
+ reason: "profile_headroom_preflight",
249
+ constraint_id: breach.constraint_id,
250
+ used_ratio: breach.used_ratio,
251
+ resets_at: breach.resets_at,
252
+ });
253
+ return next;
254
+ }
255
+ /**
256
+ * Reactive failover plan (`vendor_limit_rejected`, W5.4): rotation fires ONLY
257
+ * on the typed predicate under a `rotate` policy, marks the current profile
258
+ * tried (at most once per attempt each), and returns the next target with
259
+ * provenance already emitted — or null when the attempt must fail as-is.
260
+ * `currentProfile: null` is the DEFAULT subject: allowed ONLY when the caller
261
+ * proves the attempt's pre-spawn route was vendor_native (a metered default
262
+ * hitting a limit is a budget fact, not a subscription to fail over from).
263
+ */
264
+ export function planReactiveRotation(args) {
265
+ if (!rotationRetryEligible(args))
266
+ return null;
267
+ if (args.policy.limit_action !== "rotate")
268
+ return null;
269
+ if (args.currentProfile === null && args.defaultRouteWasVendorNative !== true)
270
+ return null;
271
+ if (args.currentProfile)
272
+ args.triedProfiles.add(args.currentProfile.profile_id);
273
+ const next = nextEligibleProfile(args.registry, args.harnessId, args.policy, args.currentProfile, args.snapshots, args.triedProfiles);
274
+ if (!next) {
275
+ emitRotationExhausted({
276
+ current: args.currentProfile,
277
+ harnessId: args.harnessId,
278
+ policy: args.policy,
279
+ registry: args.registry,
280
+ snapshots: args.snapshots,
281
+ excluded: args.triedProfiles,
282
+ attemptId: args.attemptId,
283
+ reason: "vendor_limit_rejected",
284
+ emit: args.emit,
285
+ });
286
+ return null;
287
+ }
288
+ args.emit("route.profile.rotated", {
289
+ harness_id: args.harnessId,
290
+ attempt_id: args.attemptId,
291
+ from_profile_id: args.currentProfile?.profile_id ?? null,
292
+ to_profile_id: next.profile_id,
293
+ reason: "vendor_limit_rejected",
294
+ retry_delay_ms: args.lastLimit?.retryDelayMs ?? null,
295
+ resets_at: args.lastLimit?.resetsAt ?? null,
296
+ });
297
+ return next;
298
+ }
299
+ /**
300
+ * INV-135 at the engine boundary: a cached vendor session resumes ONLY under
301
+ * exactly the profile it was recorded with (null-default equality included) —
302
+ * regardless of what the caller's map claims. Preflight rotation changing the
303
+ * profile therefore starts fresh.
304
+ */
305
+ export function resumeSessionForProfile(cached, profile) {
306
+ if (!cached)
307
+ return null;
308
+ return (cached.profileId ?? null) === (profile?.profile_id ?? null) ? cached.sessionId : null;
309
+ }
310
+ /**
311
+ * Record a harness-emitted native session id for future thread resume; the
312
+ * observer never fails the run. profileId is the adapter's per-event stamp —
313
+ * the EFFECTIVE profile (rotation makes it differ from the requested id).
314
+ */
315
+ export function observeNativeSessionEvent(input, harnessId, ev) {
316
+ if (!input?.onSessionObserved || ev.type !== "started")
317
+ return;
318
+ const nid = ev.payload?.["native_session_id"];
319
+ if (typeof nid === "string" && nid.length > 0) {
320
+ try {
321
+ input.onSessionObserved(harnessId, nid, ev.observed_model ?? null, ev.credential_profile_id ?? null);
322
+ }
323
+ catch {
324
+ /* observer errors must never fail the run */
325
+ }
326
+ }
327
+ }
328
+ /**
329
+ * Shared reactive-failover step for BOTH lanes: given the attempt's typed
330
+ * evidence, plan a rotation and rebuild the spec on a NEW vendor session
331
+ * under the next profile — or return null when the attempt must fail as-is.
332
+ */
333
+ export function rotateSpecOnTypedLimit(args) {
334
+ const rotation = planReactiveRotation({
335
+ currentProfile: args.spec.credential_profile ?? null,
336
+ defaultRouteWasVendorNative: args.defaultRouteWasVendorNative,
337
+ harnessId: args.harnessId,
338
+ attemptId: args.attemptId,
339
+ policy: args.policy,
340
+ registry: args.registry,
341
+ snapshots: args.snapshots,
342
+ triedProfiles: args.triedProfiles,
343
+ sawTypedLimit: args.sawTypedLimit,
344
+ deliverableEmpty: args.deliverableEmpty,
345
+ lastLimit: args.lastLimit,
346
+ emit: args.emit,
347
+ });
348
+ if (!rotation)
349
+ return null;
350
+ return HarnessRunSpecSchema.parse({
351
+ ...args.spec,
352
+ session_id: args.newSessionId(),
353
+ credential_profile: rotation,
354
+ resume_session_id: null,
355
+ });
356
+ }
357
+ //# sourceMappingURL=credential-profiles.js.map