@tokenfactory/acc-runner 0.44.0 → 0.44.1

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 (74) hide show
  1. package/README.md +23 -0
  2. package/dist/cost-pricing.d.ts +45 -0
  3. package/dist/cost-pricing.d.ts.map +1 -1
  4. package/dist/cost-pricing.js +57 -0
  5. package/dist/cost-pricing.js.map +1 -1
  6. package/dist/doctor.d.ts +4 -0
  7. package/dist/doctor.d.ts.map +1 -1
  8. package/dist/doctor.js +19 -0
  9. package/dist/doctor.js.map +1 -1
  10. package/dist/instance.d.ts +30 -0
  11. package/dist/instance.d.ts.map +1 -0
  12. package/dist/instance.js +51 -0
  13. package/dist/instance.js.map +1 -0
  14. package/dist/keychain.d.ts +4 -8
  15. package/dist/keychain.d.ts.map +1 -1
  16. package/dist/keychain.js +10 -4
  17. package/dist/keychain.js.map +1 -1
  18. package/dist/login.d.ts +4 -0
  19. package/dist/login.d.ts.map +1 -1
  20. package/dist/login.js +6 -1
  21. package/dist/login.js.map +1 -1
  22. package/dist/memory-context.d.ts +106 -0
  23. package/dist/memory-context.d.ts.map +1 -0
  24. package/dist/memory-context.js +220 -0
  25. package/dist/memory-context.js.map +1 -0
  26. package/dist/memory-curation.d.ts +172 -0
  27. package/dist/memory-curation.d.ts.map +1 -0
  28. package/dist/memory-curation.js +277 -0
  29. package/dist/memory-curation.js.map +1 -0
  30. package/dist/program.d.ts.map +1 -1
  31. package/dist/program.js +13 -0
  32. package/dist/program.js.map +1 -1
  33. package/dist/provider-auth.d.ts +19 -0
  34. package/dist/provider-auth.d.ts.map +1 -1
  35. package/dist/provider-auth.js +23 -0
  36. package/dist/provider-auth.js.map +1 -1
  37. package/dist/runtime/cache-dir.d.ts.map +1 -1
  38. package/dist/runtime/cache-dir.js +8 -3
  39. package/dist/runtime/cache-dir.js.map +1 -1
  40. package/dist/runtime/decision-provenance.d.ts +63 -0
  41. package/dist/runtime/decision-provenance.d.ts.map +1 -0
  42. package/dist/runtime/decision-provenance.js +107 -0
  43. package/dist/runtime/decision-provenance.js.map +1 -0
  44. package/dist/runtime/prompt-substitute.d.ts +100 -0
  45. package/dist/runtime/prompt-substitute.d.ts.map +1 -0
  46. package/dist/runtime/prompt-substitute.js +124 -0
  47. package/dist/runtime/prompt-substitute.js.map +1 -0
  48. package/dist/runtime/review-cost-cap.d.ts +167 -0
  49. package/dist/runtime/review-cost-cap.d.ts.map +1 -0
  50. package/dist/runtime/review-cost-cap.js +182 -0
  51. package/dist/runtime/review-cost-cap.js.map +1 -0
  52. package/dist/runtime/reviewer-abstain.d.ts +101 -0
  53. package/dist/runtime/reviewer-abstain.d.ts.map +1 -0
  54. package/dist/runtime/reviewer-abstain.js +138 -0
  55. package/dist/runtime/reviewer-abstain.js.map +1 -0
  56. package/dist/runtime/reviewer.d.ts +75 -0
  57. package/dist/runtime/reviewer.d.ts.map +1 -1
  58. package/dist/runtime/reviewer.js +205 -65
  59. package/dist/runtime/reviewer.js.map +1 -1
  60. package/dist/runtime/singleton.d.ts.map +1 -1
  61. package/dist/runtime/singleton.js +9 -1
  62. package/dist/runtime/singleton.js.map +1 -1
  63. package/dist/serving-auth/index.d.ts +79 -0
  64. package/dist/serving-auth/index.d.ts.map +1 -0
  65. package/dist/serving-auth/index.js +169 -0
  66. package/dist/serving-auth/index.js.map +1 -0
  67. package/dist/task-runner.d.ts +8 -0
  68. package/dist/task-runner.d.ts.map +1 -1
  69. package/dist/task-runner.js +37 -3
  70. package/dist/task-runner.js.map +1 -1
  71. package/dist/watch.d.ts.map +1 -1
  72. package/dist/watch.js +47 -19
  73. package/dist/watch.js.map +1 -1
  74. package/package.json +3 -2
@@ -0,0 +1,107 @@
1
+ /**
2
+ * REVIEW-DECISION-PROVENANCE (runner half; mirrors
3
+ * api/_lib/reviewer/decision-provenance.ts, which carries the full rationale).
4
+ *
5
+ * MEASURED FAULT (PR #1913): `decision = "reject"` was persisted while every
6
+ * entry in `reasons[]` concluded "Met." — a full-pass analysis — with nothing on
7
+ * the row explaining the disagreement. That verdict was not asserted by the
8
+ * reviewer; the prose fallback in `extractReviewerDecision` SYNTHESIZED it from
9
+ * the first approve/reject keyword when the model answered in markdown, and left
10
+ * no trace. The contrast case (PR #1911) was a legitimate forced reject that
11
+ * named its rationale in `reasons[]`.
12
+ *
13
+ * INVARIANT: a forced or synthesized decision carries its justification in
14
+ * `reasons[]` plus a machine-readable marker naming the override source. The
15
+ * runner is the hot-path writer (acc.submit_review), so the guard runs here,
16
+ * immediately before the row is persisted.
17
+ *
18
+ * JUDGEMENT IS NEVER ALTERED — nothing here flips approve↔reject. `reasons[]` is
19
+ * the only channel (acc.review_queue stores `reasons text[]` and submit_review
20
+ * takes no extra argument), so the marker rides there in a parseable form, the
21
+ * same way the read-coverage telemetry line does.
22
+ *
23
+ * PURE. No I/O, no clock, no client.
24
+ */
25
+ /** Stable prefix for cheap row detection. */
26
+ export const DECISION_PROVENANCE_PREFIX = "decision_provenance:";
27
+ const PROVENANCE_RE = /^decision_provenance:\s*decision_source=(\S+)\s+override_reason=([\s\S]*)$/i;
28
+ /** Rationale stamped when the prose fallback synthesizes the verdict. */
29
+ export const PROSE_KEYWORD_OVERRIDE_REASON = "the reviewer answered in prose instead of the structured verdict object, so " +
30
+ "this decision was synthesized by the parser from the first approve/reject " +
31
+ "keyword in the text rather than asserted as a verdict";
32
+ /** Rationale stamped when a reject is contradicted by its own reasons. */
33
+ export const CONTRADICTION_OVERRIDE_REASON = "the analysis recorded every acceptance criterion as met, yet the verdict is " +
34
+ "reject and the row states no override rationale — the verdict is kept as " +
35
+ "submitted and flagged here for the operator";
36
+ /** Render a provenance record as the reasons[] line that persists it. */
37
+ export function decisionProvenanceReason(p) {
38
+ const rationale = p.override_reason.replace(/\s+/g, " ").trim() || "(unstated)";
39
+ return `${DECISION_PROVENANCE_PREFIX} decision_source=${p.decision_source} override_reason=${rationale}`;
40
+ }
41
+ /** True when a single reason IS the provenance marker (never a finding). */
42
+ export function isDecisionProvenanceReason(reason) {
43
+ return typeof reason === "string" && PROVENANCE_RE.test(reason.trim());
44
+ }
45
+ /** The first provenance marker carried by a verdict's reasons, or null. */
46
+ export function parseDecisionProvenance(reasons) {
47
+ for (const raw of reasons ?? []) {
48
+ if (typeof raw !== "string")
49
+ continue;
50
+ const m = PROVENANCE_RE.exec(raw.trim());
51
+ if (m) {
52
+ return { decision_source: m[1], override_reason: m[2].trim() };
53
+ }
54
+ }
55
+ return null;
56
+ }
57
+ /** Severity tag the server-side calibrator stamps on stored findings. */
58
+ const SEVERITY_TAG_RE = /^\s*\[(?:blocking|advisory)\]\s*/i;
59
+ /** A finding that concludes the criterion it names was satisfied. */
60
+ const MET_RE = /\bmet\b\s*[.)\]]?\s*$/i;
61
+ /**
62
+ * Defect / non-satisfaction language. Present anywhere ⇒ the analysis is not a
63
+ * clean full pass and the guard stands down — deliberately biased toward NOT
64
+ * stamping, since a missed stamp is an audit gap while a wrong one would
65
+ * editorialize a real reject.
66
+ */
67
+ const DEFECT_RE = /\b(?:not\s+met|unmet|partially\s+met|fail(?:s|ed|ing|ure)?|missing|absent|violat\w*|blocking|defect|regression|broken|incorrect|unsafe|does\s+not|is\s+not|are\s+not|was\s+not|cannot|can['’]?t)\b/i;
68
+ /**
69
+ * True when a verdict's reasons read as a FULL-PASS analysis: at least one
70
+ * criterion recorded as met and no defect language anywhere. Lines that are
71
+ * neither (telemetry such as the coverage line) are tolerated.
72
+ */
73
+ export function allCriteriaMet(reasons) {
74
+ const findings = (reasons ?? [])
75
+ .filter((r) => typeof r === "string")
76
+ .filter((r) => !isDecisionProvenanceReason(r))
77
+ .map((r) => r.replace(SEVERITY_TAG_RE, "").trim())
78
+ .filter((r) => r.length > 0);
79
+ if (findings.length === 0)
80
+ return false;
81
+ if (findings.some((r) => DEFECT_RE.test(r)))
82
+ return false;
83
+ return findings.some((r) => MET_RE.test(r));
84
+ }
85
+ /**
86
+ * The submit-seam guard. A reject whose reasons record a clean full pass and
87
+ * which carries no provenance marker gets one appended, so the #1913 shape can
88
+ * never be persisted unexplained. Every other verdict is returned untouched and
89
+ * the DECISION IS NEVER CHANGED. Idempotent.
90
+ */
91
+ export function reconcileReviewDecision(verdict) {
92
+ const reasons = (verdict.reasons ?? []).filter((r) => typeof r === "string");
93
+ const existing = parseDecisionProvenance(reasons);
94
+ if (verdict.decision !== "reject" || existing !== null || !allCriteriaMet(reasons)) {
95
+ return { decision: verdict.decision, reasons, provenance: existing };
96
+ }
97
+ const provenance = {
98
+ decision_source: "reconciled_contradiction",
99
+ override_reason: CONTRADICTION_OVERRIDE_REASON,
100
+ };
101
+ return {
102
+ decision: verdict.decision,
103
+ reasons: [...reasons, decisionProvenanceReason(provenance)],
104
+ provenance,
105
+ };
106
+ }
107
+ //# sourceMappingURL=decision-provenance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decision-provenance.js","sourceRoot":"","sources":["../../src/runtime/decision-provenance.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAUH,6CAA6C;AAC7C,MAAM,CAAC,MAAM,0BAA0B,GAAG,sBAAsB,CAAC;AAEjE,MAAM,aAAa,GACjB,6EAA6E,CAAC;AAEhF,yEAAyE;AACzE,MAAM,CAAC,MAAM,6BAA6B,GACxC,8EAA8E;IAC9E,4EAA4E;IAC5E,uDAAuD,CAAC;AAE1D,0EAA0E;AAC1E,MAAM,CAAC,MAAM,6BAA6B,GACxC,8EAA8E;IAC9E,2EAA2E;IAC3E,6CAA6C,CAAC;AAEhD,yEAAyE;AACzE,MAAM,UAAU,wBAAwB,CAAC,CAAqB;IAC5D,MAAM,SAAS,GAAG,CAAC,CAAC,eAAe,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,IAAI,YAAY,CAAC;IAChF,OAAO,GAAG,0BAA0B,oBAAoB,CAAC,CAAC,eAAe,oBAAoB,SAAS,EAAE,CAAC;AAC3G,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,0BAA0B,CAAC,MAAiC;IAC1E,OAAO,OAAO,MAAM,KAAK,QAAQ,IAAI,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;AACzE,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,uBAAuB,CACrC,OAAkE;IAElE,KAAK,MAAM,GAAG,IAAI,OAAO,IAAI,EAAE,EAAE,CAAC;QAChC,IAAI,OAAO,GAAG,KAAK,QAAQ;YAAE,SAAS;QACtC,MAAM,CAAC,GAAG,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;QACzC,IAAI,CAAC,EAAE,CAAC;YACN,OAAO,EAAE,eAAe,EAAE,CAAC,CAAC,CAAC,CAAmB,EAAE,eAAe,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;QACnF,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,yEAAyE;AACzE,MAAM,eAAe,GAAG,mCAAmC,CAAC;AAC5D,qEAAqE;AACrE,MAAM,MAAM,GAAG,wBAAwB,CAAC;AACxC;;;;;GAKG;AACH,MAAM,SAAS,GACb,qMAAqM,CAAC;AAExM;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAC5B,OAAkE;IAElE,MAAM,QAAQ,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC;SAC7B,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC;SACjD,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC;SAC7C,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;SACjD,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC/B,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACxC,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IAC1D,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;AAC9C,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAmB,OAGzD;IACC,MAAM,OAAO,GAAG,CAAC,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,MAAM,CAC5C,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAC1C,CAAC;IACF,MAAM,QAAQ,GAAG,uBAAuB,CAAC,OAAO,CAAC,CAAC;IAClD,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,IAAI,QAAQ,KAAK,IAAI,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,EAAE,CAAC;QACnF,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC;IACvE,CAAC;IACD,MAAM,UAAU,GAAuB;QACrC,eAAe,EAAE,0BAA0B;QAC3C,eAAe,EAAE,6BAA6B;KAC/C,CAAC;IACF,OAAO;QACL,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,OAAO,EAAE,CAAC,GAAG,OAAO,EAAE,wBAAwB,CAAC,UAAU,CAAC,CAAC;QAC3D,UAAU;KACX,CAAC;AACJ,CAAC"}
@@ -0,0 +1,100 @@
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 declare const REPLACEMENT_EXPANSION_SEQUENCES: readonly ["$$", "$&", "$`", "$'"];
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 declare function hasReplacementExpansion(value: string | null | undefined): boolean;
58
+ /**
59
+ * Substitute the FIRST occurrence of `placeholder` with `value`, VERBATIM.
60
+ *
61
+ * The replacement is a function, so no `$`-sequence in `value` is expanded and
62
+ * no template text can bleed into it. An absent placeholder is a no-op (the
63
+ * template is returned unchanged) — same as the string form it replaces.
64
+ */
65
+ export declare function substitutePlaceholder(template: string, placeholder: string, value: string | null | undefined): string;
66
+ /**
67
+ * Apply an ORDERED list of `[placeholder, value]` pairs. Order matters only in
68
+ * that each pass sees the output of the previous one; because values are
69
+ * inserted verbatim, a value that happens to contain a later placeholder's
70
+ * literal text (`"{{pr_diff}}"` inside a PR body, say) is NOT re-substituted
71
+ * away — the later pass still matches the TEMPLATE's own placeholder first,
72
+ * since it appears earlier in the string only if the template put it there.
73
+ */
74
+ export declare function substitutePlaceholders(template: string, pairs: ReadonlyArray<readonly [string, string | null | undefined]>): string;
75
+ /** One value that did not survive substitution verbatim. */
76
+ export interface PromptRenderBleed {
77
+ /** The placeholder whose value was corrupted (e.g. `{{pr_diff}}`). */
78
+ placeholder: string;
79
+ /** Length of the value we expected to find verbatim (never the value itself). */
80
+ expected_chars: number;
81
+ }
82
+ /**
83
+ * INTEGRITY CHECK: every value substituted for a placeholder THE TEMPLATE
84
+ * ACTUALLY CONTAINS must appear VERBATIM in the rendered output. Anything else
85
+ * means the render mangled file content — the #1933 class — and the review must
86
+ * NOT proceed on that prompt (the reviewer would be judging text the repo does
87
+ * not contain).
88
+ *
89
+ * `template` is required precisely so a placeholder the template OMITS is not
90
+ * reported: a prompt that never asks for the PR body was not corrupted by the
91
+ * body's absence, it simply does not carry it. Only a value the template asked
92
+ * for and did not receive verbatim is a bleed.
93
+ *
94
+ * Pure and allocation-cheap (`includes`, no regex). Values are never echoed into
95
+ * the result, so a bleed report is safe to log.
96
+ */
97
+ export declare function findPromptRenderBleed(template: string, rendered: string, pairs: ReadonlyArray<readonly [string, string | null | undefined]>): PromptRenderBleed[];
98
+ /** Operator-facing one-liner naming the corrupted placeholders (never the values). */
99
+ export declare function describePromptRenderBleed(bleeds: PromptRenderBleed[]): string;
100
+ //# sourceMappingURL=prompt-substitute.d.ts.map
@@ -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"}
@@ -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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"}