@tokenfactory/acc-runner 0.44.0 → 0.44.2

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 (197) hide show
  1. package/README.md +43 -1
  2. package/dist/bin-resolve.d.ts +39 -3
  3. package/dist/bin-resolve.d.ts.map +1 -1
  4. package/dist/bin-resolve.js +176 -12
  5. package/dist/bin-resolve.js.map +1 -1
  6. package/dist/build-fingerprint.d.ts +66 -0
  7. package/dist/build-fingerprint.d.ts.map +1 -0
  8. package/dist/build-fingerprint.js +70 -0
  9. package/dist/build-fingerprint.js.map +1 -0
  10. package/dist/build-info.generated.d.ts +3 -0
  11. package/dist/build-info.generated.d.ts.map +1 -0
  12. package/dist/build-info.generated.js +3 -0
  13. package/dist/build-info.generated.js.map +1 -0
  14. package/dist/capacity/resume-controller.d.ts +5 -3
  15. package/dist/capacity/resume-controller.d.ts.map +1 -1
  16. package/dist/capacity/resume-controller.js +9 -0
  17. package/dist/capacity/resume-controller.js.map +1 -1
  18. package/dist/chat-delegation-guard.d.ts +23 -0
  19. package/dist/chat-delegation-guard.d.ts.map +1 -0
  20. package/dist/chat-delegation-guard.js +62 -0
  21. package/dist/chat-delegation-guard.js.map +1 -0
  22. package/dist/chat-lane.d.ts +17 -6
  23. package/dist/chat-lane.d.ts.map +1 -1
  24. package/dist/chat-lane.js +29 -8
  25. package/dist/chat-lane.js.map +1 -1
  26. package/dist/companion-identity.d.ts +37 -0
  27. package/dist/companion-identity.d.ts.map +1 -1
  28. package/dist/companion-identity.js +98 -29
  29. package/dist/companion-identity.js.map +1 -1
  30. package/dist/companion-run.d.ts +1 -50
  31. package/dist/companion-run.d.ts.map +1 -1
  32. package/dist/companion-run.js +48 -105
  33. package/dist/companion-run.js.map +1 -1
  34. package/dist/config.d.ts +1 -0
  35. package/dist/config.d.ts.map +1 -1
  36. package/dist/config.js +14 -3
  37. package/dist/config.js.map +1 -1
  38. package/dist/conformance.d.ts +49 -0
  39. package/dist/conformance.d.ts.map +1 -0
  40. package/dist/conformance.js +121 -0
  41. package/dist/conformance.js.map +1 -0
  42. package/dist/cost-pricing.d.ts +91 -0
  43. package/dist/cost-pricing.d.ts.map +1 -1
  44. package/dist/cost-pricing.js +115 -1
  45. package/dist/cost-pricing.js.map +1 -1
  46. package/dist/credential-identity.d.ts +70 -0
  47. package/dist/credential-identity.d.ts.map +1 -0
  48. package/dist/credential-identity.js +128 -0
  49. package/dist/credential-identity.js.map +1 -0
  50. package/dist/doctor.d.ts +37 -1
  51. package/dist/doctor.d.ts.map +1 -1
  52. package/dist/doctor.js +300 -41
  53. package/dist/doctor.js.map +1 -1
  54. package/dist/engines/chat-stream.d.ts +71 -1
  55. package/dist/engines/chat-stream.d.ts.map +1 -1
  56. package/dist/engines/chat-stream.js +126 -8
  57. package/dist/engines/chat-stream.js.map +1 -1
  58. package/dist/engines/claude-code-chat.d.ts +11 -1
  59. package/dist/engines/claude-code-chat.d.ts.map +1 -1
  60. package/dist/engines/claude-code-chat.js +69 -5
  61. package/dist/engines/claude-code-chat.js.map +1 -1
  62. package/dist/engines/claude-code.d.ts.map +1 -1
  63. package/dist/engines/claude-code.js +30 -6
  64. package/dist/engines/claude-code.js.map +1 -1
  65. package/dist/engines/codex.d.ts.map +1 -1
  66. package/dist/engines/codex.js +18 -5
  67. package/dist/engines/codex.js.map +1 -1
  68. package/dist/engines/registry.d.ts +26 -0
  69. package/dist/engines/registry.d.ts.map +1 -1
  70. package/dist/engines/registry.js +37 -0
  71. package/dist/engines/registry.js.map +1 -1
  72. package/dist/engines/types.d.ts +5 -0
  73. package/dist/engines/types.d.ts.map +1 -1
  74. package/dist/failure-classifier.d.ts +1 -1
  75. package/dist/failure-classifier.d.ts.map +1 -1
  76. package/dist/failure-classifier.js +65 -12
  77. package/dist/failure-classifier.js.map +1 -1
  78. package/dist/gh.d.ts +15 -0
  79. package/dist/gh.d.ts.map +1 -1
  80. package/dist/gh.js +18 -0
  81. package/dist/gh.js.map +1 -1
  82. package/dist/git.d.ts +6 -0
  83. package/dist/git.d.ts.map +1 -1
  84. package/dist/git.js +27 -1
  85. package/dist/git.js.map +1 -1
  86. package/dist/instance.d.ts +30 -0
  87. package/dist/instance.d.ts.map +1 -0
  88. package/dist/instance.js +51 -0
  89. package/dist/instance.js.map +1 -0
  90. package/dist/keychain.d.ts +20 -7
  91. package/dist/keychain.d.ts.map +1 -1
  92. package/dist/keychain.js +40 -4
  93. package/dist/keychain.js.map +1 -1
  94. package/dist/loc-advisory.d.ts +70 -0
  95. package/dist/loc-advisory.d.ts.map +1 -0
  96. package/dist/loc-advisory.js +196 -0
  97. package/dist/loc-advisory.js.map +1 -0
  98. package/dist/locks/claim-lock-policy.d.ts +67 -0
  99. package/dist/locks/claim-lock-policy.d.ts.map +1 -0
  100. package/dist/locks/claim-lock-policy.js +132 -0
  101. package/dist/locks/claim-lock-policy.js.map +1 -0
  102. package/dist/login.d.ts +63 -2
  103. package/dist/login.d.ts.map +1 -1
  104. package/dist/login.js +93 -8
  105. package/dist/login.js.map +1 -1
  106. package/dist/mcp-spawn.d.ts +54 -0
  107. package/dist/mcp-spawn.d.ts.map +1 -1
  108. package/dist/mcp-spawn.js +88 -4
  109. package/dist/mcp-spawn.js.map +1 -1
  110. package/dist/memory-context.d.ts +106 -0
  111. package/dist/memory-context.d.ts.map +1 -0
  112. package/dist/memory-context.js +220 -0
  113. package/dist/memory-context.js.map +1 -0
  114. package/dist/memory-curation.d.ts +172 -0
  115. package/dist/memory-curation.d.ts.map +1 -0
  116. package/dist/memory-curation.js +277 -0
  117. package/dist/memory-curation.js.map +1 -0
  118. package/dist/migration-seq-guard.d.ts +28 -1
  119. package/dist/migration-seq-guard.d.ts.map +1 -1
  120. package/dist/migration-seq-guard.js +111 -6
  121. package/dist/migration-seq-guard.js.map +1 -1
  122. package/dist/output-diagnostics.d.ts +15 -0
  123. package/dist/output-diagnostics.d.ts.map +1 -0
  124. package/dist/output-diagnostics.js +95 -0
  125. package/dist/output-diagnostics.js.map +1 -0
  126. package/dist/pr-open-guard.d.ts +67 -0
  127. package/dist/pr-open-guard.d.ts.map +1 -0
  128. package/dist/pr-open-guard.js +79 -0
  129. package/dist/pr-open-guard.js.map +1 -0
  130. package/dist/prepr-gate.d.ts +229 -1
  131. package/dist/prepr-gate.d.ts.map +1 -1
  132. package/dist/prepr-gate.js +406 -3
  133. package/dist/prepr-gate.js.map +1 -1
  134. package/dist/program.d.ts.map +1 -1
  135. package/dist/program.js +43 -5
  136. package/dist/program.js.map +1 -1
  137. package/dist/provider-auth.d.ts +27 -0
  138. package/dist/provider-auth.d.ts.map +1 -1
  139. package/dist/provider-auth.js +33 -0
  140. package/dist/provider-auth.js.map +1 -1
  141. package/dist/runtime/cache-dir.d.ts.map +1 -1
  142. package/dist/runtime/cache-dir.js +8 -3
  143. package/dist/runtime/cache-dir.js.map +1 -1
  144. package/dist/runtime/decision-provenance.d.ts +63 -0
  145. package/dist/runtime/decision-provenance.d.ts.map +1 -0
  146. package/dist/runtime/decision-provenance.js +107 -0
  147. package/dist/runtime/decision-provenance.js.map +1 -0
  148. package/dist/runtime/prompt-substitute.d.ts +100 -0
  149. package/dist/runtime/prompt-substitute.d.ts.map +1 -0
  150. package/dist/runtime/prompt-substitute.js +124 -0
  151. package/dist/runtime/prompt-substitute.js.map +1 -0
  152. package/dist/runtime/quarantine.d.ts +11 -0
  153. package/dist/runtime/quarantine.d.ts.map +1 -1
  154. package/dist/runtime/quarantine.js +16 -1
  155. package/dist/runtime/quarantine.js.map +1 -1
  156. package/dist/runtime/review-cost-cap.d.ts +167 -0
  157. package/dist/runtime/review-cost-cap.d.ts.map +1 -0
  158. package/dist/runtime/review-cost-cap.js +182 -0
  159. package/dist/runtime/review-cost-cap.js.map +1 -0
  160. package/dist/runtime/reviewer-abstain.d.ts +101 -0
  161. package/dist/runtime/reviewer-abstain.d.ts.map +1 -0
  162. package/dist/runtime/reviewer-abstain.js +138 -0
  163. package/dist/runtime/reviewer-abstain.js.map +1 -0
  164. package/dist/runtime/reviewer.d.ts +165 -2
  165. package/dist/runtime/reviewer.d.ts.map +1 -1
  166. package/dist/runtime/reviewer.js +752 -111
  167. package/dist/runtime/reviewer.js.map +1 -1
  168. package/dist/runtime/running-build.d.ts +24 -0
  169. package/dist/runtime/running-build.d.ts.map +1 -0
  170. package/dist/runtime/running-build.js +62 -0
  171. package/dist/runtime/running-build.js.map +1 -0
  172. package/dist/runtime/singleton.d.ts.map +1 -1
  173. package/dist/runtime/singleton.js +9 -1
  174. package/dist/runtime/singleton.js.map +1 -1
  175. package/dist/runtime/worktree.d.ts +45 -0
  176. package/dist/runtime/worktree.d.ts.map +1 -1
  177. package/dist/runtime/worktree.js +174 -5
  178. package/dist/runtime/worktree.js.map +1 -1
  179. package/dist/serving-auth/index.d.ts +79 -0
  180. package/dist/serving-auth/index.d.ts.map +1 -0
  181. package/dist/serving-auth/index.js +169 -0
  182. package/dist/serving-auth/index.js.map +1 -0
  183. package/dist/task-runner.d.ts +134 -5
  184. package/dist/task-runner.d.ts.map +1 -1
  185. package/dist/task-runner.js +1255 -129
  186. package/dist/task-runner.js.map +1 -1
  187. package/dist/types.d.ts +8 -0
  188. package/dist/types.d.ts.map +1 -1
  189. package/dist/watch-chat/wire.d.ts +80 -0
  190. package/dist/watch-chat/wire.d.ts.map +1 -1
  191. package/dist/watch-chat/wire.js +203 -43
  192. package/dist/watch-chat/wire.js.map +1 -1
  193. package/dist/watch.d.ts +70 -16
  194. package/dist/watch.d.ts.map +1 -1
  195. package/dist/watch.js +548 -108
  196. package/dist/watch.js.map +1 -1
  197. package/package.json +4 -3
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt-substitute.d.ts","sourceRoot":"","sources":["../../src/runtime/prompt-substitute.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH;;;;;;;;;GASG;AACH,eAAO,MAAM,+BAA+B,mCAAoC,CAAC;AAEjF;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAGjF;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC/B,MAAM,CAGR;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,aAAa,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC,GACjE,MAAM,CAMR;AAED,4DAA4D;AAC5D,MAAM,WAAW,iBAAiB;IAChC,sEAAsE;IACtE,WAAW,EAAE,MAAM,CAAC;IACpB,iFAAiF;IACjF,cAAc,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,aAAa,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC,GACjE,iBAAiB,EAAE,CAarB;AAED,sFAAsF;AACtF,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,iBAAiB,EAAE,GAAG,MAAM,CAI7E"}
@@ -0,0 +1,124 @@
1
+ /**
2
+ * S15-FU/REVIEW-1 (AC1) — literal-safe reviewer-prompt placeholder substitution.
3
+ *
4
+ * THE FAULT THIS CLOSES (the LEDGER-1 #1933 episode)
5
+ * ----------------------------------------------------------------------------
6
+ * `renderReviewerPrompt` interpolated the PR diff into the reviewer prompt with
7
+ *
8
+ * template.replace("{{pr_diff}}", diff)
9
+ *
10
+ * `String.prototype.replace` treats a STRING replacement as a pattern: `$$`,
11
+ * `$&`, `` $` ``, `$'` and `$1`…`$9` are expanded, not copied. So a diff whose
12
+ * text contains `$'` — which every one of these clean SQL literals does —
13
+ *
14
+ * select seq, slug, sha from acc.applied_migrations where seq ~ '^[0-9]+$' order by 1
15
+ * ^^ $'
16
+ *
17
+ * made `replace` splice in "the portion of the template AFTER the match": the
18
+ * reviewer's OWN instruction text, starting at
19
+ * "Now answer in this exact JSON shape, with no surrounding prose:". The
20
+ * reviewer then read that prose INSIDE the SQL literal and — correctly, given
21
+ * what it was shown — reported "prose spliced into the literal" as a corruption
22
+ * defect, at 0.75–0.85 confidence, across several independent sessions. The
23
+ * diff on disk was clean the whole time; the harness corrupted it on the way in.
24
+ *
25
+ * (Same root cause as the 3× `gatherAppliedLedger` corruption in
26
+ * `scripts/ci/migration-lint.mjs`: `$'` expansion in a string replacement, not
27
+ * context bleed.)
28
+ *
29
+ * THE RULE: NEVER pass untrusted text as `replace`'s string replacement. A
30
+ * FUNCTION replacement is copied verbatim — `$` has no meaning in its return
31
+ * value — so `substitutePlaceholder` below is the only sanctioned way to put a
32
+ * diff, PR body, title, description, or acceptance criterion into a prompt.
33
+ *
34
+ * Mirrored, byte-for-byte in behaviour, from
35
+ * `api/_lib/reviewer/prompt-substitute.ts` (the server path). The runner ships as
36
+ * a published npm package and cannot import from `api/_lib`, so the module is
37
+ * duplicated and pinned by a cross-implementation parity test.
38
+ * // keep in sync with api/_lib/reviewer/prompt-substitute.ts
39
+ */
40
+ /**
41
+ * The `$`-sequences `String.prototype.replace` expands inside a STRING
42
+ * replacement. Documented here because the whole module exists to defeat them.
43
+ *
44
+ * `$$` → a literal `$`
45
+ * `$&` → the matched substring
46
+ * ``$` `` → everything BEFORE the match
47
+ * `$'` → everything AFTER the match ← the #1933 corruption
48
+ * `$n` → capture group n
49
+ */
50
+ export const REPLACEMENT_EXPANSION_SEQUENCES = ["$$", "$&", "$`", "$'"];
51
+ /**
52
+ * True when `value` contains a sequence that `String.prototype.replace` would
53
+ * expand if it were used as a string replacement. Diagnostic only — the
54
+ * substitution below is safe either way — but it lets the render seam log that a
55
+ * value was in the dangerous class without ever emitting the value itself.
56
+ */
57
+ export function hasReplacementExpansion(value) {
58
+ const text = value ?? "";
59
+ return /\$[$&`'\d]/.test(text);
60
+ }
61
+ /**
62
+ * Substitute the FIRST occurrence of `placeholder` with `value`, VERBATIM.
63
+ *
64
+ * The replacement is a function, so no `$`-sequence in `value` is expanded and
65
+ * no template text can bleed into it. An absent placeholder is a no-op (the
66
+ * template is returned unchanged) — same as the string form it replaces.
67
+ */
68
+ export function substitutePlaceholder(template, placeholder, value) {
69
+ const text = value ?? "";
70
+ return (template ?? "").replace(placeholder, () => text);
71
+ }
72
+ /**
73
+ * Apply an ORDERED list of `[placeholder, value]` pairs. Order matters only in
74
+ * that each pass sees the output of the previous one; because values are
75
+ * inserted verbatim, a value that happens to contain a later placeholder's
76
+ * literal text (`"{{pr_diff}}"` inside a PR body, say) is NOT re-substituted
77
+ * away — the later pass still matches the TEMPLATE's own placeholder first,
78
+ * since it appears earlier in the string only if the template put it there.
79
+ */
80
+ export function substitutePlaceholders(template, pairs) {
81
+ let out = template ?? "";
82
+ for (const [placeholder, value] of pairs) {
83
+ out = substitutePlaceholder(out, placeholder, value);
84
+ }
85
+ return out;
86
+ }
87
+ /**
88
+ * INTEGRITY CHECK: every value substituted for a placeholder THE TEMPLATE
89
+ * ACTUALLY CONTAINS must appear VERBATIM in the rendered output. Anything else
90
+ * means the render mangled file content — the #1933 class — and the review must
91
+ * NOT proceed on that prompt (the reviewer would be judging text the repo does
92
+ * not contain).
93
+ *
94
+ * `template` is required precisely so a placeholder the template OMITS is not
95
+ * reported: a prompt that never asks for the PR body was not corrupted by the
96
+ * body's absence, it simply does not carry it. Only a value the template asked
97
+ * for and did not receive verbatim is a bleed.
98
+ *
99
+ * Pure and allocation-cheap (`includes`, no regex). Values are never echoed into
100
+ * the result, so a bleed report is safe to log.
101
+ */
102
+ export function findPromptRenderBleed(template, rendered, pairs) {
103
+ const out = [];
104
+ const tpl = template ?? "";
105
+ const text = rendered ?? "";
106
+ for (const [placeholder, value] of pairs) {
107
+ const expected = value ?? "";
108
+ if (expected.length === 0)
109
+ continue;
110
+ if (!tpl.includes(placeholder))
111
+ continue;
112
+ if (!text.includes(expected)) {
113
+ out.push({ placeholder, expected_chars: expected.length });
114
+ }
115
+ }
116
+ return out;
117
+ }
118
+ /** Operator-facing one-liner naming the corrupted placeholders (never the values). */
119
+ export function describePromptRenderBleed(bleeds) {
120
+ return bleeds
121
+ .map((b) => `${b.placeholder} (${b.expected_chars} chars did not survive verbatim)`)
122
+ .join("; ");
123
+ }
124
+ //# sourceMappingURL=prompt-substitute.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt-substitute.js","sourceRoot":"","sources":["../../src/runtime/prompt-substitute.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAU,CAAC;AAEjF;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAC,KAAgC;IACtE,MAAM,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;IACzB,OAAO,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACjC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CACnC,QAAgB,EAChB,WAAmB,EACnB,KAAgC;IAEhC,MAAM,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;IACzB,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;AAC3D,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,sBAAsB,CACpC,QAAgB,EAChB,KAAkE;IAElE,IAAI,GAAG,GAAG,QAAQ,IAAI,EAAE,CAAC;IACzB,KAAK,MAAM,CAAC,WAAW,EAAE,KAAK,CAAC,IAAI,KAAK,EAAE,CAAC;QACzC,GAAG,GAAG,qBAAqB,CAAC,GAAG,EAAE,WAAW,EAAE,KAAK,CAAC,CAAC;IACvD,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAUD;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,qBAAqB,CACnC,QAAgB,EAChB,QAAgB,EAChB,KAAkE;IAElE,MAAM,GAAG,GAAwB,EAAE,CAAC;IACpC,MAAM,GAAG,GAAG,QAAQ,IAAI,EAAE,CAAC;IAC3B,MAAM,IAAI,GAAG,QAAQ,IAAI,EAAE,CAAC;IAC5B,KAAK,MAAM,CAAC,WAAW,EAAE,KAAK,CAAC,IAAI,KAAK,EAAE,CAAC;QACzC,MAAM,QAAQ,GAAG,KAAK,IAAI,EAAE,CAAC;QAC7B,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS;QACpC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,WAAW,CAAC;YAAE,SAAS;QACzC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC7B,GAAG,CAAC,IAAI,CAAC,EAAE,WAAW,EAAE,cAAc,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;QAC7D,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,sFAAsF;AACtF,MAAM,UAAU,yBAAyB,CAAC,MAA2B;IACnE,OAAO,MAAM;SACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,WAAW,KAAK,CAAC,CAAC,cAAc,kCAAkC,CAAC;SACnF,IAAI,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC"}
@@ -4,6 +4,10 @@ export interface QuarantineState {
4
4
  classifiedAt: string;
5
5
  taskId: string;
6
6
  detail: string;
7
+ engineScope?: string;
8
+ engine_scope?: string;
9
+ resumeAt?: string | null;
10
+ resume_at?: string | null;
7
11
  /**
8
12
  * v0.53 T-53-4: how many consecutive instant-empty (heuristic) claude
9
13
  * exits triggered the quarantine. Cache-schema additive — older readers
@@ -15,6 +19,13 @@ export interface QuarantineState {
15
19
  */
16
20
  consecutive?: number;
17
21
  }
22
+ export interface PublishedQuarantineState {
23
+ entered_at: string;
24
+ reason: ClaudeExitClass;
25
+ engine_scope: string;
26
+ resume_at: string | null;
27
+ }
28
+ export declare function publishedQuarantineState(state: QuarantineState | null, nowMs?: number): PublishedQuarantineState | null;
18
29
  /**
19
30
  * Path to the quarantine sentinel file. Resolves through the shared
20
31
  * `cacheDir()` (honours ACC_RUNNER_CACHE_DIR), so a per-runner cache dir
@@ -1 +1 @@
1
- {"version":3,"file":"quarantine.d.ts","sourceRoot":"","sources":["../../src/runtime/quarantine.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAEhE,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,eAAe,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;;;OAQG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;GAIG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAED,0EAA0E;AAC1E,wBAAsB,aAAa,IAAI,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAQrE;AAED,oEAAoE;AACpE,wBAAsB,aAAa,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAIzE;AAED,yEAAyE;AACzE,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAMrD"}
1
+ {"version":3,"file":"quarantine.d.ts","sourceRoot":"","sources":["../../src/runtime/quarantine.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAEhE,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,eAAe,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B;;;;;;;;OAQG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,wBAAwB;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,eAAe,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,eAAe,GAAG,IAAI,EAC7B,KAAK,GAAE,MAAmB,GACzB,wBAAwB,GAAG,IAAI,CAUjC;AAED;;;;GAIG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAED,0EAA0E;AAC1E,wBAAsB,aAAa,IAAI,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAQrE;AAED,oEAAoE;AACpE,wBAAsB,aAAa,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAIzE;AAED,yEAAyE;AACzE,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAMrD"}
@@ -3,7 +3,9 @@
3
3
  * ~/.cache/acc-runner/quarantine.json by the watch loop when the failure
4
4
  * classifier flags a machine-level failure (usage_limit | auth_expired |
5
5
  * env_broken). The presence of this file blocks pump() from claiming
6
- * new tasks so no further work is burned.
6
+ * new tasks so no further work is burned. The live watcher periodically
7
+ * re-reads this sentinel, so removing it resumes that named instance without a
8
+ * process restart.
7
9
  *
8
10
  * Clearing the quarantine (after the operator resolves the underlying issue):
9
11
  * - Delete the file manually, OR
@@ -14,6 +16,19 @@
14
16
  import fs from "node:fs/promises";
15
17
  import path from "node:path";
16
18
  import { cacheDir } from "./cache-dir.js";
19
+ export function publishedQuarantineState(state, nowMs = Date.now()) {
20
+ if (!state)
21
+ return null;
22
+ const resumeAt = state.resume_at ?? state.resumeAt ?? null;
23
+ if (resumeAt !== null && Date.parse(resumeAt) <= nowMs)
24
+ return null;
25
+ return {
26
+ entered_at: state.classifiedAt,
27
+ reason: state.cause,
28
+ engine_scope: state.engine_scope ?? state.engineScope ?? "all",
29
+ resume_at: resumeAt,
30
+ };
31
+ }
17
32
  /**
18
33
  * Path to the quarantine sentinel file. Resolves through the shared
19
34
  * `cacheDir()` (honours ACC_RUNNER_CACHE_DIR), so a per-runner cache dir
@@ -1 +1 @@
1
- {"version":3,"file":"quarantine.js","sourceRoot":"","sources":["../../src/runtime/quarantine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAoB1C;;;;GAIG;AACH,MAAM,UAAU,cAAc;IAC5B,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,iBAAiB,CAAC,CAAC;AAClD,CAAC;AAED,0EAA0E;AAC1E,MAAM,CAAC,KAAK,UAAU,aAAa;IACjC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,cAAc,EAAE,EAAE,MAAM,CAAC,CAAC;QACxD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAoB,CAAC;IAC5C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QAClE,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC;AAED,oEAAoE;AACpE,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,KAAsB;IACxD,MAAM,IAAI,GAAG,cAAc,EAAE,CAAC;IAC9B,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACxD,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;AACnE,CAAC;AAED,yEAAyE;AACzE,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC;IACpC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ;YAAE,MAAM,GAAG,CAAC;IAClE,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"quarantine.js","sourceRoot":"","sources":["../../src/runtime/quarantine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AA+B1C,MAAM,UAAU,wBAAwB,CACtC,KAA6B,EAC7B,QAAgB,IAAI,CAAC,GAAG,EAAE;IAE1B,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,MAAM,QAAQ,GAAG,KAAK,CAAC,SAAS,IAAI,KAAK,CAAC,QAAQ,IAAI,IAAI,CAAC;IAC3D,IAAI,QAAQ,KAAK,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,KAAK;QAAE,OAAO,IAAI,CAAC;IACpE,OAAO;QACL,UAAU,EAAE,KAAK,CAAC,YAAY;QAC9B,MAAM,EAAE,KAAK,CAAC,KAAK;QACnB,YAAY,EAAE,KAAK,CAAC,YAAY,IAAI,KAAK,CAAC,WAAW,IAAI,KAAK;QAC9D,SAAS,EAAE,QAAQ;KACpB,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc;IAC5B,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,iBAAiB,CAAC,CAAC;AAClD,CAAC;AAED,0EAA0E;AAC1E,MAAM,CAAC,KAAK,UAAU,aAAa;IACjC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,cAAc,EAAE,EAAE,MAAM,CAAC,CAAC;QACxD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAoB,CAAC;IAC5C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QAClE,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC;AAED,oEAAoE;AACpE,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,KAAsB;IACxD,MAAM,IAAI,GAAG,cAAc,EAAE,CAAC;IAC9B,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACxD,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;AACnE,CAAC;AAED,yEAAyE;AACzE,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC;IACpC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ;YAAE,MAAM,GAAG,CAAC;IAClE,CAAC;AACH,CAAC"}
@@ -0,0 +1,167 @@
1
+ /**
2
+ * REV-COST — runner-side per-review cost cap (size-scaled).
3
+ *
4
+ * THE EPISODE (PR #1937). The runner reviewer applied the org's FLAT
5
+ * `max_cost_usd_per_review` and, the instant the spend crossed it, discarded the
6
+ * review with `cost_cap_exceeded`. #1937 is a ~1800-line PR: it cost ~$18 against
7
+ * an $8 flat cap and produced NO verdict at all — the money was spent, the
8
+ * analysis was done, and the harness threw both away, then re-charged the same
9
+ * review on the next tick. That is the measured 239-decisions-over-9-tasks loop
10
+ * AU-5 fixed on the SERVER path (api/_lib/cost-cap.ts) and left unfixed here,
11
+ * which is why a large PR could still be stranded whenever a runner picked the
12
+ * broadcast up (the normal case — the server path is the emergency fallback).
13
+ *
14
+ * This module is the runner's copy of AU-5's PURE cost-cap core:
15
+ *
16
+ * • `effectiveReviewCostCap` — the cap for THIS review, scaled by assembled
17
+ * diff size, FLOORED at the org's flat cap (so a median PR is judged against
18
+ * exactly today's number — never inflated) and CLAMPED at
19
+ * REVIEW_CAP_MAX_MULTIPLE.
20
+ * • `foldReviewCostLedger` — the chargeable-vs-infra split. Only PRODUCTIVE
21
+ * attempts (a spawn that returned a readable reviewer envelope) are charged
22
+ * to the cap; capacity/auth/tooling faults are reported but never charged.
23
+ * • `classifyReviewCost` — within / overrun / runaway. `overrun` keeps the
24
+ * verdict (the money is already spent; discarding it only strands the PR);
25
+ * `runaway` is the hard backstop that still refuses.
26
+ *
27
+ * The runner is a standalone published package and cannot import from api/_lib
28
+ * (the same convention as the duplicated PRICING / MODEL_ALIASES maps and
29
+ * review-diff-guard.ts). KEEP IN SYNC with the AU-5 block at the bottom of
30
+ * api/_lib/cost-cap.ts — the constants and all three functions are pinned
31
+ * byte-for-behaviour by tests/unit/revcost1-runner-cap-parity.test.ts, which
32
+ * fails the build the moment the two drift.
33
+ */
34
+ /**
35
+ * Assembled-diff size the flat cap is treated as calibrated for: the measured
36
+ * p50 production churn (750 changed lines ≈ 30k chars of unified diff). At or
37
+ * below this the effective cap is EXACTLY `max_cost_usd_per_review`.
38
+ * Mirrors api/_lib/cost-cap.ts. // keep in sync
39
+ */
40
+ export declare const REVIEW_CAP_REFERENCE_DIFF_CHARS = 30000;
41
+ /** Fraction of the base cap added per reference-diff-worth of EXCESS size. */
42
+ export declare const REVIEW_CAP_SIZE_SLOPE = 0.5;
43
+ /**
44
+ * Ceiling on the scaled cap as a multiple of the base cap. Deliberately tight
45
+ * (1.5× → $3.00 at a $2 base) because the measured size↔cost correlation is
46
+ * weak (Pearson r = 0.100): a bigger ramp would be unjustified by the data.
47
+ */
48
+ export declare const REVIEW_CAP_MAX_MULTIPLE = 1.5;
49
+ /**
50
+ * Multiple of the effective cap above which an already-PAID review is still
51
+ * discarded as a runaway — the hard backstop that survives this change. Between
52
+ * 1× and this, the verdict is KEPT and flagged (`cost_overrun`) instead of
53
+ * stranding the PR.
54
+ */
55
+ export declare const REVIEW_COST_OVERRUN_TOLERANCE = 3;
56
+ export interface ReviewCostCapDecision {
57
+ /** The cap to enforce for THIS review. */
58
+ cap_usd: number;
59
+ /** The org's flat `max_cost_usd_per_review` — the floor. */
60
+ base_cap_usd: number;
61
+ /** false when the base cap is 0/negative/non-finite (cap disabled). */
62
+ enabled: boolean;
63
+ /** Assembled-diff chars the scale was computed from. */
64
+ diff_chars: number;
65
+ /** cap_usd / base_cap_usd — 1 when the flat floor applied. */
66
+ scale: number;
67
+ /** True when the size ramp hit REVIEW_CAP_MAX_MULTIPLE. */
68
+ ceiling_applied: boolean;
69
+ /** Spend above which an already-paid verdict is discarded as a runaway. */
70
+ runaway_usd: number;
71
+ }
72
+ /**
73
+ * The size-scaled per-review cost cap. Monotone non-decreasing in `diffChars`,
74
+ * floored at `baseCapUsd` (so it can only ever LOOSEN relative to the flat cap —
75
+ * a small PR's effective cap is unchanged) and clamped at
76
+ * `REVIEW_CAP_MAX_MULTIPLE × baseCapUsd`. Pure.
77
+ *
78
+ * A base cap of 0 (or a non-finite/negative one) means "cap disabled" — the
79
+ * existing `max_cost_usd_per_review > 0` semantics — and is returned verbatim.
80
+ *
81
+ * Mirrors api/_lib/cost-cap.ts `effectiveReviewCostCap`. // keep in sync
82
+ */
83
+ export declare function effectiveReviewCostCap(baseCapUsd: number, diffChars: number): ReviewCostCapDecision;
84
+ /**
85
+ * How one reviewer attempt is classified for BUDGET purposes. Only `productive`
86
+ * attempts (a spawn that returned a reviewer envelope we can read a verdict out
87
+ * of) are charged to the per-review cap. Every other class is an INFRA fault: it
88
+ * is still reported in the review's total spend so the accounting stays honest,
89
+ * but it must never consume the budget set aside for reviewing this PR.
90
+ *
91
+ * Mirrors api/_lib/cost-cap.ts `ReviewAttemptClass`. // keep in sync
92
+ */
93
+ export type ReviewAttemptClass = "productive"
94
+ /** Parse-repair re-prompt — a harness cost, not review work. */
95
+ | "parse_repair"
96
+ /** 429 / overloaded / rate-limit brownout (budget-exempt). */
97
+ | "infra_capacity"
98
+ /** Hard billing/quota wall. */
99
+ | "infra_billing"
100
+ /** Account-level spend cap. */
101
+ | "infra_billing_capped"
102
+ /** Missing/invalid credential — env broken. */
103
+ | "infra_auth"
104
+ /** Verification-tooling failure (unread file / truncated diff). */
105
+ | "infra_tooling";
106
+ export interface ReviewCostAttempt {
107
+ /** 1-based attempt ordinal within this review. */
108
+ attempt: number;
109
+ model_id: string;
110
+ attempt_class: ReviewAttemptClass;
111
+ /** Spend this attempt actually incurred (0 for a throw before any tokens). */
112
+ cost_usd: number;
113
+ }
114
+ export interface ReviewCostLedger {
115
+ attempts: ReviewCostAttempt[];
116
+ /** Sum of PRODUCTIVE attempt cost — the only figure the cap is checked against. */
117
+ chargeable_usd: number;
118
+ /** Sum of every non-productive attempt cost (reported, never charged). */
119
+ infra_usd: number;
120
+ /** chargeable + infra — what the review truly cost (stored on the queue row). */
121
+ total_usd: number;
122
+ productive_attempts: number;
123
+ infra_attempts: number;
124
+ }
125
+ /**
126
+ * Fold an attempt list into the chargeable / infra split. Pure.
127
+ * Mirrors api/_lib/cost-cap.ts `foldReviewCostLedger`. // keep in sync
128
+ */
129
+ export declare function foldReviewCostLedger(attempts: ReviewCostAttempt[]): ReviewCostLedger;
130
+ /**
131
+ * Verdict on this review's chargeable spend:
132
+ * • `within` — at or under the effective cap. Nothing changes.
133
+ * • `overrun` — over the cap but under the runaway threshold. The money is
134
+ * ALREADY SPENT, so discarding the verdict buys nothing and strands the PR
135
+ * (#1937). Keep the verdict and flag it.
136
+ * • `runaway` — past the tolerance band. Refuse the verdict; something is
137
+ * pathological and the operator must see it.
138
+ *
139
+ * Mirrors api/_lib/cost-cap.ts `classifyReviewCost`. // keep in sync
140
+ */
141
+ export type ReviewCostVerdict = "within" | "overrun" | "runaway";
142
+ export declare function classifyReviewCost(chargeableUsd: number, cap: ReviewCostCapDecision): ReviewCostVerdict;
143
+ /** Mutable cost state for ONE review, shared by every pass it runs. */
144
+ export interface ReviewCostContext {
145
+ cap: ReviewCostCapDecision;
146
+ attempts: ReviewCostAttempt[];
147
+ }
148
+ /** Open a cost context for a review over `diffChars` of assembled diff. */
149
+ export declare function openReviewCostContext(baseCapUsd: number, diffChars: number): ReviewCostContext;
150
+ /** Record one attempt and return the folded ledger. */
151
+ export declare function chargeReviewAttempt(ctx: ReviewCostContext, modelId: string, attemptClass: ReviewAttemptClass, costUsd: number): ReviewCostLedger;
152
+ /** The current folded ledger without recording anything. */
153
+ export declare function reviewCostLedger(ctx: ReviewCostContext): ReviewCostLedger;
154
+ /**
155
+ * Advisory reason for an ACCEPTED overrun. Deliberately phrased to match none of
156
+ * the absence-claim patterns in review-chunking.ts (asserted by this wave's
157
+ * test), so an audit line can never be re-read as a read-coverage claim.
158
+ */
159
+ export declare function reviewCostOverrunReason(cap: ReviewCostCapDecision, ledger: ReviewCostLedger): string;
160
+ /**
161
+ * Refusal reason for a RUNAWAY — the backstop above the scaled cap. Carries the
162
+ * PRODUCTIVE attempt count, which is what distinguishes a single-pass runaway
163
+ * from a chunked review that accumulated past the tolerance over several part
164
+ * passes (the old chunked-only reason string), without needing two call sites.
165
+ */
166
+ export declare function reviewCostRunawayReason(cap: ReviewCostCapDecision, ledger: ReviewCostLedger): string;
167
+ //# sourceMappingURL=review-cost-cap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review-cost-cap.d.ts","sourceRoot":"","sources":["../../src/runtime/review-cost-cap.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,182 @@
1
+ /**
2
+ * REV-COST — runner-side per-review cost cap (size-scaled).
3
+ *
4
+ * THE EPISODE (PR #1937). The runner reviewer applied the org's FLAT
5
+ * `max_cost_usd_per_review` and, the instant the spend crossed it, discarded the
6
+ * review with `cost_cap_exceeded`. #1937 is a ~1800-line PR: it cost ~$18 against
7
+ * an $8 flat cap and produced NO verdict at all — the money was spent, the
8
+ * analysis was done, and the harness threw both away, then re-charged the same
9
+ * review on the next tick. That is the measured 239-decisions-over-9-tasks loop
10
+ * AU-5 fixed on the SERVER path (api/_lib/cost-cap.ts) and left unfixed here,
11
+ * which is why a large PR could still be stranded whenever a runner picked the
12
+ * broadcast up (the normal case — the server path is the emergency fallback).
13
+ *
14
+ * This module is the runner's copy of AU-5's PURE cost-cap core:
15
+ *
16
+ * • `effectiveReviewCostCap` — the cap for THIS review, scaled by assembled
17
+ * diff size, FLOORED at the org's flat cap (so a median PR is judged against
18
+ * exactly today's number — never inflated) and CLAMPED at
19
+ * REVIEW_CAP_MAX_MULTIPLE.
20
+ * • `foldReviewCostLedger` — the chargeable-vs-infra split. Only PRODUCTIVE
21
+ * attempts (a spawn that returned a readable reviewer envelope) are charged
22
+ * to the cap; capacity/auth/tooling faults are reported but never charged.
23
+ * • `classifyReviewCost` — within / overrun / runaway. `overrun` keeps the
24
+ * verdict (the money is already spent; discarding it only strands the PR);
25
+ * `runaway` is the hard backstop that still refuses.
26
+ *
27
+ * The runner is a standalone published package and cannot import from api/_lib
28
+ * (the same convention as the duplicated PRICING / MODEL_ALIASES maps and
29
+ * review-diff-guard.ts). KEEP IN SYNC with the AU-5 block at the bottom of
30
+ * api/_lib/cost-cap.ts — the constants and all three functions are pinned
31
+ * byte-for-behaviour by tests/unit/revcost1-runner-cap-parity.test.ts, which
32
+ * fails the build the moment the two drift.
33
+ */
34
+ /**
35
+ * Assembled-diff size the flat cap is treated as calibrated for: the measured
36
+ * p50 production churn (750 changed lines ≈ 30k chars of unified diff). At or
37
+ * below this the effective cap is EXACTLY `max_cost_usd_per_review`.
38
+ * Mirrors api/_lib/cost-cap.ts. // keep in sync
39
+ */
40
+ export const REVIEW_CAP_REFERENCE_DIFF_CHARS = 30_000;
41
+ /** Fraction of the base cap added per reference-diff-worth of EXCESS size. */
42
+ export const REVIEW_CAP_SIZE_SLOPE = 0.5;
43
+ /**
44
+ * Ceiling on the scaled cap as a multiple of the base cap. Deliberately tight
45
+ * (1.5× → $3.00 at a $2 base) because the measured size↔cost correlation is
46
+ * weak (Pearson r = 0.100): a bigger ramp would be unjustified by the data.
47
+ */
48
+ export const REVIEW_CAP_MAX_MULTIPLE = 1.5;
49
+ /**
50
+ * Multiple of the effective cap above which an already-PAID review is still
51
+ * discarded as a runaway — the hard backstop that survives this change. Between
52
+ * 1× and this, the verdict is KEPT and flagged (`cost_overrun`) instead of
53
+ * stranding the PR.
54
+ */
55
+ export const REVIEW_COST_OVERRUN_TOLERANCE = 3;
56
+ /**
57
+ * The size-scaled per-review cost cap. Monotone non-decreasing in `diffChars`,
58
+ * floored at `baseCapUsd` (so it can only ever LOOSEN relative to the flat cap —
59
+ * a small PR's effective cap is unchanged) and clamped at
60
+ * `REVIEW_CAP_MAX_MULTIPLE × baseCapUsd`. Pure.
61
+ *
62
+ * A base cap of 0 (or a non-finite/negative one) means "cap disabled" — the
63
+ * existing `max_cost_usd_per_review > 0` semantics — and is returned verbatim.
64
+ *
65
+ * Mirrors api/_lib/cost-cap.ts `effectiveReviewCostCap`. // keep in sync
66
+ */
67
+ export function effectiveReviewCostCap(baseCapUsd, diffChars) {
68
+ const base = typeof baseCapUsd === "number" && Number.isFinite(baseCapUsd) && baseCapUsd > 0
69
+ ? baseCapUsd
70
+ : 0;
71
+ const chars = typeof diffChars === "number" && Number.isFinite(diffChars) && diffChars > 0
72
+ ? diffChars
73
+ : 0;
74
+ if (base === 0) {
75
+ return {
76
+ cap_usd: 0,
77
+ base_cap_usd: 0,
78
+ enabled: false,
79
+ diff_chars: chars,
80
+ scale: 1,
81
+ ceiling_applied: false,
82
+ runaway_usd: 0,
83
+ };
84
+ }
85
+ const excess = Math.max(0, chars - REVIEW_CAP_REFERENCE_DIFF_CHARS);
86
+ const raw = 1 + (REVIEW_CAP_SIZE_SLOPE * excess) / REVIEW_CAP_REFERENCE_DIFF_CHARS;
87
+ const scale = Math.min(raw, REVIEW_CAP_MAX_MULTIPLE);
88
+ const cap = base * scale;
89
+ return {
90
+ cap_usd: cap,
91
+ base_cap_usd: base,
92
+ enabled: true,
93
+ diff_chars: chars,
94
+ scale,
95
+ ceiling_applied: raw > REVIEW_CAP_MAX_MULTIPLE,
96
+ runaway_usd: cap * REVIEW_COST_OVERRUN_TOLERANCE,
97
+ };
98
+ }
99
+ function usableUsd(value) {
100
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
101
+ }
102
+ /**
103
+ * Fold an attempt list into the chargeable / infra split. Pure.
104
+ * Mirrors api/_lib/cost-cap.ts `foldReviewCostLedger`. // keep in sync
105
+ */
106
+ export function foldReviewCostLedger(attempts) {
107
+ let chargeable = 0;
108
+ let infra = 0;
109
+ let productiveN = 0;
110
+ let infraN = 0;
111
+ for (const a of attempts) {
112
+ const cost = usableUsd(a.cost_usd);
113
+ if (a.attempt_class === "productive") {
114
+ chargeable += cost;
115
+ productiveN += 1;
116
+ }
117
+ else {
118
+ infra += cost;
119
+ infraN += 1;
120
+ }
121
+ }
122
+ return {
123
+ attempts,
124
+ chargeable_usd: chargeable,
125
+ infra_usd: infra,
126
+ total_usd: chargeable + infra,
127
+ productive_attempts: productiveN,
128
+ infra_attempts: infraN,
129
+ };
130
+ }
131
+ export function classifyReviewCost(chargeableUsd, cap) {
132
+ if (!cap.enabled)
133
+ return "within";
134
+ const spent = usableUsd(chargeableUsd);
135
+ if (spent <= cap.cap_usd)
136
+ return "within";
137
+ return spent > cap.runaway_usd ? "runaway" : "overrun";
138
+ }
139
+ /** Open a cost context for a review over `diffChars` of assembled diff. */
140
+ export function openReviewCostContext(baseCapUsd, diffChars) {
141
+ return { cap: effectiveReviewCostCap(baseCapUsd, diffChars), attempts: [] };
142
+ }
143
+ /** Record one attempt and return the folded ledger. */
144
+ export function chargeReviewAttempt(ctx, modelId, attemptClass, costUsd) {
145
+ ctx.attempts.push({
146
+ attempt: ctx.attempts.length + 1,
147
+ model_id: modelId,
148
+ attempt_class: attemptClass,
149
+ cost_usd: costUsd,
150
+ });
151
+ return foldReviewCostLedger(ctx.attempts);
152
+ }
153
+ /** The current folded ledger without recording anything. */
154
+ export function reviewCostLedger(ctx) {
155
+ return foldReviewCostLedger(ctx.attempts);
156
+ }
157
+ /**
158
+ * Advisory reason for an ACCEPTED overrun. Deliberately phrased to match none of
159
+ * the absence-claim patterns in review-chunking.ts (asserted by this wave's
160
+ * test), so an audit line can never be re-read as a read-coverage claim.
161
+ */
162
+ export function reviewCostOverrunReason(cap, ledger) {
163
+ return (`cost_overrun: review cost $${ledger.chargeable_usd.toFixed(4)} exceeded the ` +
164
+ `effective cap $${cap.cap_usd.toFixed(2)} (base $${cap.base_cap_usd.toFixed(2)}, ` +
165
+ `scale ${cap.scale.toFixed(2)}×, ${cap.diff_chars} chars of assembled changes) — ` +
166
+ `verdict KEPT (already paid) and escalated for cap ratification`);
167
+ }
168
+ /**
169
+ * Refusal reason for a RUNAWAY — the backstop above the scaled cap. Carries the
170
+ * PRODUCTIVE attempt count, which is what distinguishes a single-pass runaway
171
+ * from a chunked review that accumulated past the tolerance over several part
172
+ * passes (the old chunked-only reason string), without needing two call sites.
173
+ */
174
+ export function reviewCostRunawayReason(cap, ledger) {
175
+ const n = ledger.productive_attempts;
176
+ return (`reviewer cost $${ledger.chargeable_usd.toFixed(4)} across ${n} productive ` +
177
+ `${n === 1 ? "attempt" : "attempts"} exceeded cap $${cap.cap_usd.toFixed(2)} ` +
178
+ `(base $${cap.base_cap_usd.toFixed(2)}, scale ${cap.scale.toFixed(2)}×) by more ` +
179
+ `than the ${REVIEW_COST_OVERRUN_TOLERANCE}× runaway tolerance ` +
180
+ `($${cap.runaway_usd.toFixed(2)}) — verdict refused`);
181
+ }
182
+ //# sourceMappingURL=review-cost-cap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review-cost-cap.js","sourceRoot":"","sources":["../../src/runtime/review-cost-cap.ts"],"names":[],"mappings":"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"}