Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .github/workflows/paired-feedback.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,27 @@ jobs:
timeout-minutes: 5
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
persist-credentials: false
- uses: actions/setup-python@v6
with:
python-version: '3.12'
- name: Verify contracts, temporal bridge and demo endpoints
working-directory: benchmarks/paired-feedback-v0.1
run: python verify.py --out results
- name: Reproduce pinned presentation-lineage audit
working-directory: benchmarks/paired-feedback-v0.1
run: |
git show 0d83d3e767756e40b5fceab4fe207a524c152ede:benchmarks/paired-feedback-v0.1/paired.py > "$RUNNER_TEMP/paired_before.py"
python lineage_probe.py --baseline "$RUNNER_TEMP/paired_before.py" --out results
python prepare_abc.py --out results/abc-smoke
- name: Verify optimized-Python validation guards
working-directory: benchmarks/paired-feedback-v0.1
run: |
python -O verify.py --out results/optimized
python -O lineage_probe.py --baseline "$RUNNER_TEMP/paired_before.py" --out results/optimized
python -O prepare_abc.py --out results/optimized/abc-smoke
- name: Upload explicit research evidence
if: always()
uses: actions/upload-artifact@v4
Expand Down
46 changes: 46 additions & 0 deletions benchmarks/paired-feedback-v0.1/LINEAGE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Presentation-lineage audit and the A/B/C gate


The continuation separates `answer_id` (every issued rendering/review marker) from
`fact_revision` (changed context or an accepted evaluated proof). Presentation-only
changes and pending-review markers retain the factual revision. A delayed evidence
or outcome event may refer to an earlier answer in that revision, but its receipt
must still match that exact earlier answer and all existing scope/time bindings.
A new evaluated proof is a barrier even when its verdict is unchanged; returning
to an earlier context name does not revive older receipts.

Current-answer outcome attribution is unchanged. `related_outcome_observations`
shows earlier-answer records in the same factual revision with the original
`answer_id`; `related_outcome_basis=RECORDED_FOR_EARLIER_ANSWER` does NOT mark the
current answer successful. Different factual revisions remain separate. Initial
state and persisted-history migration are not supported; regenerate with
`initial_state`. The host remains the trusted receipt and state boundary.

Reproduce the before/after audit from a checkout containing the pinned parent:

```sh
cd benchmarks/paired-feedback-v0.1
python verify.py --out results
git show 0d83d3e767756e40b5fceab4fe207a524c152ede:benchmarks/paired-feedback-v0.1/paired.py > /tmp/paired_before.py
python lineage_probe.py --baseline /tmp/paired_before.py --out results
python prepare_abc.py --out results/abc-smoke
```

Open `results/lineage-audit.html`. The audit requires the exact baseline SHA-256.
Observed: all 52 unchanged original tests plus 20 new test methods passed; the
12 original temporal cases and eight old demo endpoints still pass. The new
21 presentation/receipt-binding perturbations passed 6/21 in the pinned baseline
and 21/21 in the candidate. These are overlapping variants of one fixture, NOT
independent tasks or LLM accuracy rates. Short-from-start and later-shortened
answers are separately exercised.

The exported A/B/C packet contains **18 planned two-turn development episodes**
(6 open task families x 3 arms, one repeat), NOT executed model responses.
A is full R5, B is brief R5, C is brief R5+P. All receive identical substantive
feedback and may ask useful questions. Freeze one model, parameters and total
episode budget before execution; keep input, output and any reported reasoning
usage, latency, errors and abandoned runs. Exclude `operator-key.json` and
`assessor-oracle.json` from model inputs. Deliver turn 1 only after turn 0.
This is a matched-feedback smoke test, not a human interaction experiment;
response style can reveal the arm even when labels are withheld. No isolated
model execution adapter or scorer has been run or claimed here.
9 changes: 9 additions & 0 deletions benchmarks/paired-feedback-v0.1/REVIEW_FOLLOWUP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Validation follow-up for the factual-lineage candidate

Review follow-up: eight additional validation tests bring the current total to
80 methods (zero skipped, zero expected failures). All pass with and without
Python `-O`; runtime validation in the bridge/report/probe/exporter is now
unconditional. Checkout credential persistence is disabled, output HTML suffixes
are checked before writing, pass counts exclude skipped/expected-failure tests,
and fingerprint paths use POSIX separators. Automated code review is not an
independent scientific replication; no efficacy claim has changed.
2 changes: 2 additions & 0 deletions benchmarks/paired-feedback-v0.1/build_demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ def examples():


def build(out: Path):
if out.suffix.lower() != '.html':
raise ValueError('--out must use an .html suffix')
payload = examples()
encoded = json.dumps(payload, ensure_ascii=False).replace('<', '\\u003c')
out.parent.mkdir(parents=True, exist_ok=True)
Expand Down
15 changes: 10 additions & 5 deletions benchmarks/paired-feedback-v0.1/integration_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,16 @@ def check() -> dict:
s = replay(initial, [e], as_of=cp['known_at'], receipts={'evaluated': receipt})
actual = card(s)['proof']['verdict']
mapped_expected = {'CONTESTED': 'CONFLICTS'}.get(expected, expected)
assert actual == mapped_expected, (name, actual, mapped_expected)
assert card(s)['proof']['support'] == evaluated['support_ids']
assert card(s)['proof']['counterevidence'] == evaluated['refute_ids']
assert s['history'][0]['proof']['verdict'] == 'UNKNOWN'
assert not card(s)['external_action_authorized']
if not (actual == mapped_expected):
raise AssertionError((name, actual, mapped_expected))
if not (card(s)['proof']['support'] == evaluated['support_ids']):
raise AssertionError('integration_check.py:52: validation failed')
if not (card(s)['proof']['counterevidence'] == evaluated['refute_ids']):
raise AssertionError('integration_check.py:53: validation failed')
if not (s['history'][0]['proof']['verdict'] == 'UNKNOWN'):
raise AssertionError('integration_check.py:54: validation failed')
if not (not card(s)['external_action_authorized']):
raise AssertionError('integration_check.py:55: validation failed')
rows.append({'case_id': name, 'expected': mapped_expected, 'actual': actual,
'receipt_created': True, 'match': True})
return {'schema': 'resonance.r5p.temporal-bridge.v1', 'cases_reused': len(rows),
Expand Down
128 changes: 128 additions & 0 deletions benchmarks/paired-feedback-v0.1/lineage_probe.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
"""Paired presentation-path audit. Deterministic fixtures, NOT an LLM A/B/C trial.

Reproduce with the pinned pre-fix module supplied via --baseline. No API/network.
"""
from __future__ import annotations
import argparse
import hashlib
import html
import importlib.util
import json
from datetime import datetime, timedelta, timezone
from pathlib import Path
import paired
from test_paired import SCOPE, PROOF, T0, event, receipt

BASELINE_SHA256 = '0517e3c573772a999b149d8f1b6c3ee96b40b7cd3d628cb067a0d49a331027c6'
BASELINE_COMMIT = '0d83d3e767756e40b5fceab4fe207a524c152ede'


def stamp(i):
return (datetime(2026, 9, 5, 9, tzinfo=timezone.utc) + timedelta(minutes=i)).isoformat().replace('+00:00', 'Z')


def initial(module, compact=False):
s = module.initial_state(episode_id='demo', goal_id='check-target', recipient_id='demo-user', target=SCOPE,
evaluated_proof=PROOF, known_at=T0, query_time=T0)
if compact:
s['history'][0]['detail'] = 'compact'
return s


def path(module, changes, bound_index=1, initially_compact=False):
s = initial(module, initially_compact)
es = []
detail = s['history'][0]['detail']
for i in range(1, changes + 1):
detail = 'compact' if detail == 'full' else 'full'
es.append(event('PREFERENCE_FEEDBACK', {'detail': detail}, id=f'e{i}', sequence=i,
answer_id=f'a{i}', event_at=stamp(i), known_at=stamp(i)))
i = changes + 1
es.append(event('EVIDENCE_CHALLENGE', {'receipt_id': 'r'}, id=f'e{i}', sequence=i,
answer_id=f'a{bound_index}', event_at=stamp(i), known_at=stamp(i)))
r = receipt(answer_id=f'a{bound_index}', known_at=stamp(i))
out = module.replay(s, es, as_of=stamp(i), receipts={'r': r})
return {'card': module.card(out), 'audit': out['audit'], 'history': out['history'],
'initial': s, 'events': es, 'receipts': {'r': r}}


def make_report(baseline):
rows = []
for name, n, brief in [('Full from start', 0, False), ('Compact from start', 0, True), ('Compact after issued answer', 1, False)]:
old, new = path(baseline, n, initially_compact=brief), path(paired, n, initially_compact=brief)
if not (new['card']['proof']['verdict'] == 'REFUTED'):
raise AssertionError('lineage_probe.py:53: validation failed')
if not (not new['card']['external_action_authorized']):
raise AssertionError('lineage_probe.py:54: validation failed')
rows.append({'path': name, 'expected': 'REFUTED', 'baseline': old['card']['proof']['verdict'],
'candidate': new['card']['proof']['verdict'], 'trace_before': old, 'trace_after': new})
matrix = []
for n in range(6):
for bound in range(1, n + 2):
old, new = path(baseline, n, bound), path(paired, n, bound)
matrix.append({'presentation_changes': n, 'receipt_answer': f'a{bound}',
'baseline_correct': old['card']['proof']['verdict'] == 'REFUTED',
'candidate_correct': new['card']['proof']['verdict'] == 'REFUTED'})
if not (len(matrix) == 21 and all(x['candidate_correct'] for x in matrix)):
raise AssertionError('lineage_probe.py:64: validation failed')
outcome = {}
es = [event('RESULT_REPORTED', {'result': 'fixture completed'}),
event('PREFERENCE_FEEDBACK', {'detail': 'compact'}, id='e2', sequence=2, event_at=stamp(2), known_at=stamp(2))]
for label, mod in [('baseline', baseline), ('candidate', paired)]:
state = mod.replay(initial(mod), es, as_of=stamp(2))
c = mod.card(state)
outcome[label] = {'current_answer_id': c['answer_id'], 'current_outcome_basis': c['outcome_basis'],
'earlier_answer_records_visible': c.get('related_outcome_observations', []),
'historically_stored': state['outcomes']}
if not (outcome['candidate']['current_outcome_basis'] == 'NOT_OBSERVED'):
raise AssertionError('lineage_probe.py:74: validation failed')
if not (outcome['candidate']['earlier_answer_records_visible'][0]['answer_id'] == 'a1'):
raise AssertionError('lineage_probe.py:75: validation failed')
if not (outcome['candidate']['historically_stored'] == outcome['baseline']['historically_stored']):
raise AssertionError('lineage_probe.py:76: validation failed')
return {'schema': 'resonance.presentation-lineage.probe.v1', 'synthetic': True,
'baseline_commit': BASELINE_COMMIT, 'baseline_sha256': BASELINE_SHA256,
'candidate_sha256': hashlib.sha256(Path(paired.__file__).read_bytes()).hexdigest(),
'rows': rows, 'matrix': matrix,
'matrix_baseline_correct': sum(x['baseline_correct'] for x in matrix),
'matrix_candidate_correct': sum(x['candidate_correct'] for x in matrix),
'outcome_visibility': outcome, 'llm_runs': 0, 'human_participants': 0,
'tokens_measured': False,
'boundary': 'Three paths and 21 overlapping perturbations of one developer-authored fixture. Not a population error rate, token saving, or a comparison of models/protocol efficacy.'}


def render(report):
rows = ''.join('<tr><td>'+html.escape(r['path'])+'</td><td>'+r['baseline']+'</td><td>'+r['candidate']+'</td><td>'+r['expected']+'</td></tr>' for r in report['rows'])
raw = json.dumps(report, indent=2)
return '''<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Shorter must not mean harder to correct — RESONANCE</title>
<style>body{margin:0;background:#f4f6fa;color:#182236;font:17px/1.6 system-ui,sans-serif}main{max-width:1050px;margin:auto;padding:36px 22px}h1{font-size:clamp(32px,5vw,58px);line-height:1.1;letter-spacing:-.035em}h2{font-size:24px}section{background:white;border:1px solid #cdd6e1;border-radius:14px;padding:24px;margin:22px 0}.label{font-size:12px;letter-spacing:.12em;font-weight:700}.limit{padding:16px;border-left:4px solid #6250b4;background:#ece9f8}.table{overflow-x:auto}table{width:100%;border-collapse:collapse;font-size:15px}td,th{text-align:left;padding:13px;border-bottom:1px solid #dbe1e8}th{background:#edf1f7}pre{white-space:pre-wrap;overflow-wrap:anywhere;font-size:12px;max-height:550px;overflow:auto}code{overflow-wrap:anywhere}summary,button{cursor:pointer}button{font:inherit;padding:10px 14px;border-radius:8px;border:1px solid #adb9cc;background:white}.flow{font-size:22px}footer{font-size:14px;color:#546276}</style>
<main><div class="label">RESONANCE / R5 + P / DEVELOPMENT EVIDENCE</div><h1>Shorter must not mean<br>harder to correct.</h1><p>A presentation change must not orphan a verified correction. A factual change must still block stale evidence.</p>
<div class="limit"><strong>Observed in deterministic code, not in a language-model trial.</strong> One synthetic fixture; no human participants; no measured token or accuracy benefit.</div>
<section><h2>The failure</h2><p class="flow">Answer a1 → “Make it shorter” → Answer a2 → Verified correction addressed to a1</p><p>The original reducer treats the correction as stale merely because the answer ID changed. The candidate separates <strong>answer_id</strong> from <strong>fact_revision</strong>. Exact receipt bindings are retained.</p></section>
<section><h2>Same evidence. Three presentation paths.</h2><div class="table"><table><thead><tr><th>Presentation path</th><th>Before</th><th>Candidate</th><th>Expected</th></tr></thead><tbody>'''+rows+'''</tbody></table></div><p>Compact from the start works in both versions. The failure is tied to the presentation transition, not to the length of the answer. These are state-machine paths, not experimental R5/R5+P model arms.</p></section>
<section><h2>Do not erase — or misattribute — the outcome</h2><p>A report associated with a1 remains associated with a1. The current a2 outcome is still <code>NOT_OBSERVED</code>; the earlier record is shown separately as <code>RECORDED_FOR_EARLIER_ANSWER</code>. Visibility is not evidence that the reformulated answer caused success.</p></section>
<section><h2>Systematic presentation perturbations</h2><p><strong>Before: '''+str(report['matrix_baseline_correct'])+'''/21. Candidate: '''+str(report['matrix_candidate_correct'])+'''/21.</strong> These 21 overlapping variations change the number of presentation revisions and the exact answer receiving the receipt. They are not 21 independent tasks.</p><p>Context changes and accepted factual revisions are barriers even when a version name or verdict later returns to its previous value.</p></section>
<section><details><summary>Inspect source fingerprints and complete before/after traces</summary><pre id="raw">'''+html.escape(raw)+'''</pre></details><button id="save">Export audit JSON</button></section>
<footer>Baseline commit: <code>'''+BASELINE_COMMIT+'''</code><br>Read-only local test boundary. No release decision, source authentication or external action authority. Candidate pending review; not scientific novelty or product efficacy evidence.</footer></main>
<script>document.getElementById('save').onclick=()=>{const b=new Blob([document.getElementById('raw').textContent],{type:'application/json'}),u=URL.createObjectURL(b),a=document.createElement('a');a.href=u;a.download='presentation-lineage-audit.json';a.click();setTimeout(()=>URL.revokeObjectURL(u),1000)};</script></html>'''


def main():
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument('--baseline', type=Path, required=True)
ap.add_argument('--out', type=Path, default=Path('results'))
args = ap.parse_args()
if hashlib.sha256(args.baseline.read_bytes()).hexdigest() != BASELINE_SHA256:
raise SystemExit('Baseline fingerprint mismatch; refusing an unpinned comparison.')
spec = importlib.util.spec_from_file_location('paired_pinned_baseline', args.baseline)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
report = make_report(mod)
args.out.mkdir(parents=True, exist_ok=True)
(args.out / 'lineage-audit.json').write_text(json.dumps(report, indent=2)+'\n', encoding='utf-8')
(args.out / 'lineage-audit.html').write_text(render(report), encoding='utf-8')
print(json.dumps({k: v for k,v in report.items() if k not in {'rows','matrix','outcome_visibility'}}, indent=2))

if __name__ == '__main__':
main()
Loading
Loading