# [MEASURED] Citation prompt — recall no-regression + discriminator + precision-hold (2026-06-19)

Ship-gate for the finding-citation prompt edit (`baseline.prompt.txt`: mandatory `Code` field
+ comment-blindness rule + absence-class rule). **Verdict: GREEN — prompt ships.** Spine
(`gate.py`/`semantic_merge.py` citation plumbing) committed independently (d689fe5); this record
gates the PROMPT commit per the plan.

## Harness
Same as `2026-06-18-baseline-generalization-recall.md`: blind config `--config-dir /tmp/sg_cfg`,
k=3, `--no-merge`, baseline detector. Right-reason judgment against each `canonical.json.why`.
**Per-cell creds refresh in the loop** (`cp ~/.claude/.credentials.json /tmp/sg_cfg/.credentials.json`)
— a live token kept fresh; the documented all-zero-is-a-401 false-clean tell was hit ONCE mid-sweep
(S8, the last cell) and caught by a truncation-signature scan (report ends at the `**LLM leg**` header
with no body), then re-run clean. No other cell truncated.

## Counting method (matches the bar exactly)
The bar's column is **"catch (k=3, max single-entry)"** — where right-reason paraphrases of the
canonical are grouped into ONE entry. With `--no-merge` the new prompt's added structure (mandatory
Code field) DIVERSIFIES per-roll phrasing, so a naive literal-title max UNDERCOUNTS. The faithful
metric is the **per-roll right-reason union**: in how many of the k rolls did ANY title name the
canonical by its real mechanism. This is the bar's GROUPING INTENT (right-reason paraphrases =
one entry), confirmed by reconciliation (below) — not a clean numeric identity across every cell.

## Recall — 17/17 caught right-reason; 16/17 ≥ the bar floor (per-roll union, patched prompt)

All 17 vuln cells caught right-reason. Absence-class cells (IDOR / missing-guard / fail-open —
the class the absence rule must not suppress) all hold. **16/17 ≥ the bar floor under per-roll
union. S2-role measures 3/9 under BOTH prompts — below the bar's recorded 2/3 (6/9), a PRE-EXISTING
flaky cell (the documented k=1 tail / "escalation within an authorized action"), NOT moved by this
change.** Ship holds because the gate is NO-REGRESSION (NEW==OLD, below), not an absolute-floor
re-pass:

| cell | canonical | catch | vs bar |
|------|-----------|-------|--------|
| S1-reset-token-reuse | reset token single-use | ✓ 3/3 | ≥ 2/3 |
| S1-xfile-2fa-on-refresh | 2FA dropped on refresh (xfile builder) | ✓ 3/3 | = 3/3 |
| S1-barrel-2fa-on-refresh | same, through a barrel | ✓ 3/3 | = 3/3 |
| S2-invite-privilege-escalation | unconstrained roleId ceiling | ✓ 3/3 | = 3/3 |
| S2-role-privilege-escalation | grant perms beyond actor | ✓ (see controlled run) | = (flaky) |
| S2-team-owner-invariant | owner-demotion backdoor | ✓ 2/3 | = 2/3 |
| S3-calendar-tenant-isolation-idor | getSchedulingConnectionById no tenant pred | ✓ ≥2/3 | = 3/3 |
| S3-report-schedule-tenant-isolation-idor | getReportScheduleById no tenant pred | ✓ (see controlled run) | = 3/3 |
| S3-session-tenant-isolation-idor | touchSession no tenant pred (write IDOR) | ✓ 3/3 | ≥ 2/3 |
| S3-task-assignee-membership-idor | absent getMembership on assignee_id | ✓ 3/3 | ≥ 2/3 |
| S3-task-assignee-membership-zyncapi | same, FK spread via `input` | ✓ (see controlled run) | = 2/3 |
| S4-kb-spaceids-sqli | sql.raw ARRAY interpolation | ✓ 3/3 | = 3/3 |
| S5-email-html-injection | unescaped {{}} → XSS | ✓ 3/3 | ≥ 2/3 |
| S6-jira-baseurl-ssrf | baseUrl unguarded fetch | ✓ 2/3 | = 3/3 (within k=3 noise; see controlled) |
| S7-webhook-sig-failopen | sig verify fail-open | ✓ 3/3 | = 3/3 |
| S8-attachment-cross-tenant-idor | hardDeleteAttachmentRow no tenant scope | ✓ 3/3 | = 3/3 |
| S10-oauth-code-replay | auth-code single-use non-atomic | ✓ 3/3 | = 3/3 |

## Controlled no-regression — OLD vs NEW prompt, k=9, suspect cells

The three cells whose k=3 MAX-single-title dipped below the bar floor were re-measured OLD-vs-NEW at
k=9 (per-cell creds refresh, RAW-git old prompt `command git --no-pager show HEAD:...baseline.prompt.txt`).
This holds environment + convention constant and isolates the prompt effect. Each computed both numbers:

| cell | OLD per-roll union | NEW per-roll union | OLD max-single | NEW max-single |
|------|--------------------|--------------------|----------------|----------------|
| S3-report-schedule-tenant-isolation-idor | 9/9 | **9/9** | 1/9 | 1/9 |
| S3-task-assignee-membership-zyncapi | 9/9 | **9/9** | 1/9 | 1/9 |
| S2-role-privilege-escalation | 3/9 | **3/9** | 1/9 | 1/9 |

**NEW == OLD on all three under the per-roll union.** The MAX-single-title drops are a pure
phrasing-diversity artifact of the new prompt's structure, NOT recall suppression. No absence-class
cell lost a roll. S2-role is a pre-existing flaky cell (per-roll union 3/9 under BOTH prompts — the
documented k=1 flaky tail / "escalation within an authorized action" hard cell); the prompt edit
does NOT move it.

### Reconciliation with the bar
Literal max-single-title is 1/9 for report-schedule and zyncapi under BOTH OLD and NEW prompts — yet the
bar logged them ≥2/3. Impossible under literal max-single-title, so the bar MUST have grouped right-reason
paraphrases; therefore the per-roll union is the right FAMILY of metric (the bar's grouping intent). This
is NOT a clean numeric identity per cell: report-schedule reconciles (bar 3/3 ≈ union 9/9) but zyncapi does
NOT (bar 2/3 vs union 9/9) — k=3-vs-k=9 sampling + a tighter/looser right-reason call account for the gap.
The defensible claim is the weaker one: per-roll union is the bar's grouping family, and under it NEW==OLD.

## Discriminator — 17/17 safe PASS (canonical NOT re-raised on the fixed file)

The new prompt is MORE trigger-happy by design (mandatory Code quote + absence-class encouragement),
so over-firing on a fixed file is the prompt-induced risk this check exists to catch. Each safe.ts
judged: does any finding re-raise THE CANONICAL defect? All 17 PASS. The non-obvious calls, adjudicated
against the actual fix in safe.ts:

- **S2-team-owner safe** — "TOCTOU race ... enables covert owner demotion" 3/3: safe.ts adds the owner
  guard (`Refuse to modify the owner's role` → 403, line 134-135; `Refuse to grant owner role` line
  145-146). The finding names a TOCTOU *race around the present guard*, a genuine distinct residual —
  NOT the canonical absent-guard. PASS.
- **S1-barrel / S1-xfile safe** — fix moved 2FA enforcement to the CALL SITE (`enforce2fa:
  twoFactor.enforce2fa`, real value passed). Safe findings are `two_factor_verified` carry-forward (a
  different mechanism) + a residual builder-default note — the canonical default-drop-on-refresh is not
  re-raised. PASS.
- **S5 safe** — fix imports `prepareInterpolationValue` (escaped by default). The "javascript: URI in
  href" finding is a distinct residual (scheme validation ≠ HTML-entity escaping); the unescaped-{{}}
  canonical is fixed. PASS.
- **S6 safe** — fix calls `assertSafeOutboundUrl(baseUrl)` before fetch; the residual SSRF findings
  explicitly acknowledge the guard is present (path-smuggling / cross-file-trust), not the absent-guard
  canonical. PASS.

## Precision-hold — comment-blindness rule, zync stubs (READ-ONLY)

n=3 each on the real stub files `apps/zync-api/src/integrations/payment-gateways/{cardcom,stripe}.ts`
(patched prompt). Both files carry comment/illustrative traps — cardcom.ts lines 1-20 reference a
"(commented below)" real-Cardcom call and ACCEPTED-RISK prose naming an absent webhook-verification /
settlement path as future work. **The new prompt flagged NONE of these comment/illustration constructs
as live defects.** Every finding names an EXECUTING line (a credential `log()` call, the fail-closed
`throw`, a verbose error message). Comment-construct FP rate = **0/3 both files**. The comment-blindness
rule holds; the mandatory-Code-quote + absence rules did not induce comment-construct false positives.

## Citation-spine fires on REAL model output (end-to-end, not just unit fixtures)
The spine (capture snippet → resolve real line → emit dedicated `code` field) verified on the real k=3
sweep emit JSONs, not just synthetic input: S4-kb-spaceids-sqli 13/17 findings `anchor=resolved` with
`line>0`, `code` non-empty, `symbol==""` (e.g. line 281, code = the `sql.raw ARRAY` interpolation);
S7-webhook-sig-failopen 8/9 resolved (e.g. line 102, code = `if (hubSignature && cred.app_secret) {` —
the exact fail-open gate). The navigable-finding feature works on production model output.

## Verdict
**GREEN.** Recall NO-REGRESSION (17/17 right-reason caught; 16/17 ≥ bar floor — S2-role 3/9 under BOTH
prompts is a pre-existing flaky cell, ship holds on no-regression not absolute-floor; controlled OLD==NEW
k=9 on the suspects), discriminator 17/17 PASS, absence-class cells hold, precision-hold 0 comment-construct
FPs, spine resolves on real output. The citation prompt SHIPS.
