Skip to content

Commit 9fe5fcb

Browse files
Merge pull request #53 from DataScience-EngineeringExperts/feat/h1-pilot-pack
fix(evals): harden H1 methods and add synthetic QA pack
2 parents aa40289 + 1b67e81 commit 9fe5fcb

23 files changed

Lines changed: 3174 additions & 58 deletions

‎DOCUMENTATION_INDEX.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ the canonical authority spec on top of those.
3232
| **Decision Quality Roadmap** | [`docs/plans/2026-07-17-decision-quality-roadmap.md`](docs/plans/2026-07-17-decision-quality-roadmap.md) | H0-H4 prove-it roadmap: Elite correctness, randomized ablations, source-grounded Decision Briefs, buyer pull, outcome learning, old-backlog disposition, demand gates, and kill criteria. |
3333
| **H1 Evaluation Design** | [`docs/plans/2026-07-17-h1-budget-matched-evaluation-design.md`](docs/plans/2026-07-17-h1-budget-matched-evaluation-design.md) | DSE-708 experimental boundary, six frozen conditions, replay, blinding, and failure-inclusive analysis. |
3434
| **H1 Evaluation Plan** | [`docs/plans/2026-07-17-h1-budget-matched-evaluation.md`](docs/plans/2026-07-17-h1-budget-matched-evaluation.md) | TDD delivery plan for the offline, budget-matched evaluation substrate. |
35+
| **H1 Method Hardening** | [`docs/plans/2026-07-17-h1-method-hardening-design.md`](docs/plans/2026-07-17-h1-method-hardening-design.md) | Paid-study provenance, task-clustered inference, grader controls, confirmatory refusal boundaries, and QA design. |
36+
| **H1 Synthetic QA Pack** | [`studies/elite_qa_v1/README.md`](studies/elite_qa_v1/README.md) | Balanced 24-task open-book harness fixture, QA protocol, and confirmatory preregistration template. |
3537

3638
---
3739

‎README.md‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,9 +155,13 @@ is all known providers.
155155
The source branch also exposes an H1 evidence harness. It makes no decision-quality claim and
156156
never calls providers: `run` only validates an existing replay artifact against its frozen
157157
manifest. Grader output and the restricted blind map must remain separate.
158+
The open-book 24-task synthetic QA pack and its no-promotion boundary are documented in
159+
[`studies/elite_qa_v1/README.md`](studies/elite_qa_v1/README.md). Its committed answer keys
160+
are test fixtures, not access-controlled grader material, so this pack cannot be used for a
161+
paid or confirmatory study.
158162

159163
```bash
160-
conclave eval plan tasks.json manifest.json --study-id pilot --replicates 2 --seed 19 --max-output-tokens 1200
164+
conclave eval plan tasks.json manifest.json --study-id qa --replicates 2 --seed 19 --max-output-tokens 1200
161165
conclave eval run manifest.json validated-run.json --replay-artifact recorded-run.json
162166
conclave eval blind validated-run.json grader.json restricted-map.json --seed 23
163167
conclave eval report manifest.json validated-run.json judgments.json report.json report.md --bootstrap-seed 29
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# H1 Method Hardening Design
2+
3+
**Linear:** DSE-708
4+
**Dependency:** PR #52 at `4365c28`
5+
**Gate:** Required before any paid study call
6+
7+
## Decision
8+
9+
Keep the existing H1 substrate as the execution primitive, then add a frozen study-method
10+
contract and a balanced 24-task open-book synthetic QA pack. The committed pack validates
11+
mechanics only; it cannot estimate unbiased effects or produce a product GO or KILL. A paid
12+
pilot requires separately access-controlled grader keys and a hash frozen before execution.
13+
14+
## Frozen study contract
15+
16+
Every manifest must bind the exact base commit, study phase, task-family map, two roster specifications, provider/model revisions, condition prompt and protocol versions, generation settings, public-task and private-key hashes, rubric and grader-instruction hashes, evaluator and analysis-code versions, randomization and bootstrap settings, timeout/retry policy, exclusion/deviation policy, price snapshot, approved spend ceiling, and preregistration identifier/hash.
17+
18+
Confirmatory reports fail closed if any frozen field is missing or its observed receipt differs. Exploratory manifests are explicit and cannot be promoted after results exist.
19+
20+
## Experimental unit and randomization
21+
22+
The unit of analysis is the task. Roster and replicate are repeated measurements averaged within task. Create every task x roster x condition x replicate cell before execution. Derive an independent condition permutation from the master seed plus task and roster IDs so condition order varies across blocks. All failed, timed-out, malformed, over-budget, abstained, incomplete, ungraded, and unresolved cells remain failures.
23+
24+
## Grading contract
25+
26+
Two independent graders score every successful output. Atomic records include rubric item, error category and severity, severe-error flag, holistic dimensions, reviewer seconds, confidence or abstention, rubric version/hash, grader batch/order, and condition/provider guess. Adjudication sees only genuine disagreements, cites every source record, and never overwrites raw judgments.
27+
28+
Non-success records bypass the human queue and are automatic failures. Successful grader views expose only normalized presentation text, scan it for predeclared provider/model/condition labels, and retain a separate hashed/access-controlled blind map. Paid and confirmatory scoring rejects direct planned-run targets, unhashed maps, and maps that do not cover exactly the successful output set.
29+
30+
## Analysis and gates
31+
32+
Study reporting uses failure-inclusive Wilson rates, task-clustered paired bootstrap differences with roster/replicate averaging, raw agreement, prevalence, kappa, adjudication rate, severe errors, reviewer effort, total and analysis-set cost, p95 latency, deviations, and retained leakage guesses. A paid workflow must preregister and apply its material-leakage rule before claiming method readiness.
33+
34+
Reliability is undefined—not perfect—when expected agreement is one. A future paid pilot is
35+
method-ready only with at least 95% double grading, raw agreement at least 80%, kappa at least
36+
0.60 overall and no family below 0.50, adjudication at most 20%, and no material leakage.
37+
Otherwise redesign. The committed open-book QA pack cannot satisfy or measure this gate.
38+
39+
Confirmation freezes one strongest baseline. GO requires a point gain of at least 10 percentage points with the paired 95% interval above zero, severe-error noninferiority within +2 points, readiness noninferiority within +5 points, reviewer-effort and latency gates, positive direction in every family and roster, and failure-inclusive validity. Simpler-baseline equivalence, harm, weak reliability, or post-hoc changes trigger redesign or kill per DSE-708.
40+
41+
## Synthetic QA pack
42+
43+
Use 24 synthetic, current-fact-free tasks: 12 operational-execution and 12
44+
organizational-stewardship, balanced across six subfamilies, three difficulty tiers, and
45+
`ready`/`not_ready`/`indeterminate`. Public packets and committed fixture keys are separate
46+
files, but not separate security domains. Paid-study keys must be provisioned outside the
47+
repository under access control and frozen by hash. Confirmatory tasks are independently
48+
authored, access-restricted, and screened for sentence, numeric, structural, and semantic
49+
overlap.
50+
51+
## Non-goals
52+
53+
No provider spend, held-out task publication, hosted dashboard, mutable pricing service, LLM primary grader, routing product, release, or quality claim in this increment.
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# H1 Method Hardening Implementation Plan
2+
3+
> **Linear:** DSE-708. Use strict TDD on `feat/h1-pilot-pack`; make zero live provider calls.
4+
5+
## Phase 1 — Freeze contract and blocked plan
6+
7+
1. Add failing tests in `tests/evals/test_study_design.py` for complete provenance, two rosters, task-family mapping, cost/spend and policy freezes, tamper detection, exploratory/confirmatory separation, and deterministic task x roster blocked permutations.
8+
2. Extend `src/conclave/evals/models.py` and `protocols.py`; keep old manifests readable only as synthetic exploratory artifacts.
9+
3. Re-run focused tests and commit.
10+
11+
## Phase 2 — Atomic grading and blinding
12+
13+
1. Add failing tests for typed atomic errors, severe-error accounting, reviewer seconds, confidence/abstain, grader order/guess, complete double grading, raw agreement/prevalence, undefined constant kappa, and family/roster reliability.
14+
2. Extend `scoring.py`, `blinding.py`, and models. Queue only successful outputs, normalize views, scan leakage, and hash the separate blind map.
15+
3. Re-run focused tests and commit.
16+
17+
## Phase 3 — Confirmatory estimands and refusal gates
18+
19+
1. Add tests for task-level roster averaging, task-clustered paired bootstrap, one-sided severe-error and effort/latency noninferiority boundaries, symmetric task exclusions, deviations, complete cost/latency receipts and summaries, and report refusal on freeze drift or exploratory evidence.
20+
2. Extend `scoring.py`, `reporting.py`, and CLI artifact validation. Archive every gate input and seed.
21+
3. Re-run focused tests and commit.
22+
23+
## Phase 4 — Synthetic QA pack
24+
25+
1. Add `studies/elite_qa_v1/public_tasks.json`, committed fixture `grader_keys.json`,
26+
`qa_protocol.md`, `confirmatory_preregistration.md`, and `README.md`. Classify the pack as
27+
open-book harness QA, not a paid pilot.
28+
2. Add `tests/evals/test_qa_pack.py`: exactly 24 unique tasks, 12/12 families, 4 per
29+
subfamily, balanced tier/readiness, matching nonempty fixture keys, packet citation IDs, no
30+
grader material in public records, no duplicates, and documented leakage/holdout rules.
31+
3. Generate and validate an offline manifest only; do not create model outputs.
32+
4. Re-run focused tests and commit.
33+
34+
## Phase 5 — Verify and hand off
35+
36+
Run:
37+
38+
```bash
39+
PYTHONPATH=src PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python -m pytest -p pytest_asyncio.plugin -p no:cacheprovider -q
40+
python -m ruff check .
41+
python -m ruff format --check .
42+
git diff --check
43+
gitleaks git --redact
44+
```
45+
46+
Push, open a draft PR stacked on #52, obtain independent methods/code review, and attach
47+
evidence to DSE-708. Paid pilot execution remains blocked until PR #51 is merged/pinned,
48+
Ernest approves a hard spend ceiling, and separately access-controlled grader keys are frozen
49+
by hash before execution.

‎src/conclave/eval_cli.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,15 @@ def _validate_run_summary(study_run: StudyRun) -> None:
6262
expected_counts = dict(sorted(Counter(record.outcome for record in records).items()))
6363
expected_tokens = sum(record.completion_tokens or 0 for record in records)
6464
expected_latency = sum(record.latency_ms or 0.0 for record in records)
65+
expected_cost = sum(record.cost_usd for record in records)
66+
expected_deviations = sum(len(record.deviation_codes) for record in records)
6567
if (
6668
study_run.total_planned_runs != len(records)
6769
or study_run.outcome_counts != expected_counts
6870
or study_run.total_completion_tokens != expected_tokens
6971
or abs(study_run.total_latency_ms - expected_latency) > 1e-9
72+
or abs(study_run.total_cost_usd - expected_cost) > 1e-9
73+
or study_run.total_deviation_count != expected_deviations
7074
):
7175
_abort("run artifact summary does not match its immutable records")
7276

‎src/conclave/evals/blinding.py‎

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@
33
from __future__ import annotations
44

55
import hashlib
6+
import json
67
import random
78
from collections.abc import Sequence
89

9-
from pydantic import BaseModel, ConfigDict, Field
10+
from pydantic import BaseModel, ConfigDict, Field, model_validator
1011

11-
from .models import EVAL_SCHEMA_VERSION, RunOutcome, RunRecord, SchemaVersion
12+
from .models import EVAL_SCHEMA_VERSION, RunOutcome, RunRecord, SchemaVersion, Sha256Digest
1213

1314

1415
class BlindModel(BaseModel):
@@ -43,6 +44,37 @@ class BlindMap(BlindModel):
4344
"""Separate restricted identity artifact, never included in grader output."""
4445

4546
entries: tuple[BlindMapEntry, ...]
47+
blind_map_hash: Sha256Digest | None = None
48+
49+
@model_validator(mode="after")
50+
def validate_hash(self) -> BlindMap:
51+
if self.blind_map_hash is not None and self.blind_map_hash != hash_blind_map(self.entries):
52+
raise ValueError("blind map hash does not match its entries")
53+
return self
54+
55+
56+
class GraderQueueOutput(BlindModel):
57+
"""One normalized successful output safe for paid human grading."""
58+
59+
opaque_output_id: str = Field(pattern=r"^output_[0-9a-f]{24}$")
60+
presentation: str = Field(min_length=1)
61+
62+
63+
class GraderQueue(BlindModel):
64+
"""Successful-only grader queue with no execution labels or outcomes."""
65+
66+
outputs: tuple[GraderQueueOutput, ...]
67+
68+
69+
def hash_blind_map(entries: Sequence[BlindMapEntry]) -> str:
70+
"""Hash a restricted identity map in its frozen queue order."""
71+
72+
canonical = json.dumps(
73+
[entry.model_dump(mode="json") for entry in entries],
74+
sort_keys=True,
75+
separators=(",", ":"),
76+
).encode("utf-8")
77+
return f"sha256:{hashlib.sha256(canonical).hexdigest()}"
4678

4779

4880
def _opaque_id(*, planned_run_id: str, seed: int) -> str:
@@ -76,3 +108,38 @@ def blind_run_records(
76108
BlindedOutputSet(outputs=tuple(output for output, _ in blinded)),
77109
BlindMap(entries=tuple(entry for _, entry in blinded)),
78110
)
111+
112+
113+
def build_grader_queue(
114+
records: Sequence[RunRecord], *, seed: int, forbidden_labels: Sequence[str]
115+
) -> tuple[GraderQueue, BlindMap]:
116+
"""Normalize, leak-check, and blind successful outputs for paid grading."""
117+
118+
if len({record.planned_run_id for record in records}) != len(records):
119+
raise ValueError("run records must have unique planned_run_id values")
120+
blinded = []
121+
labels = tuple(label.casefold() for label in forbidden_labels if label)
122+
for record in records:
123+
if record.outcome != "success":
124+
continue
125+
if record.output is None:
126+
raise ValueError("successful grader-queue records require output")
127+
presentation = " ".join(record.output.split())
128+
if not presentation:
129+
raise ValueError("successful grader-queue records require nonempty output")
130+
folded = presentation.casefold()
131+
if any(label in folded for label in labels):
132+
raise ValueError("grader presentation contains a forbidden label")
133+
opaque_id = _opaque_id(planned_run_id=record.planned_run_id, seed=seed)
134+
blinded.append(
135+
(
136+
GraderQueueOutput(opaque_output_id=opaque_id, presentation=presentation),
137+
BlindMapEntry(opaque_output_id=opaque_id, planned_run_id=record.planned_run_id),
138+
)
139+
)
140+
random.Random(seed).shuffle(blinded)
141+
entries = tuple(entry for _, entry in blinded)
142+
return (
143+
GraderQueue(outputs=tuple(output for output, _ in blinded)),
144+
BlindMap(entries=entries, blind_map_hash=hash_blind_map(entries)),
145+
)

0 commit comments

Comments
 (0)