Skip to content
Merged
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
3 changes: 2 additions & 1 deletion docs/evidence/METRICS_CURRENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ Machine-readable snapshot: `outputs/metrics_snapshot.json` regenerated with `mak
> **Purpose:** One authoritative table of current pipeline metrics. If any doc disagrees
> with this file, this file wins. Updated whenever benchmark/benchmark config changes.
>
> **Last updated:** 2026-07-10 (Phase P P5 — calibration cycle summary — v0.10.6)
> **Last updated:** 2026-07-10 (Phase Q Q1 — pilot evidence package — v0.10.7)
> **New in v0.10.7 (Phase Q Q1):** PilotEvidencePackageEntry — external export bundle (14 fields, CCS+BSP+PSC+PRE+BCM refs, completeness+safety enforcement, 63 tests, BASELINE 4973→5036).
> **New in v0.10.5 (Phase P P2):** RecalibrationRefusalEntry schema (10 fields: rrf_id, pipeline_version, trigger_id, recalibration_refused, refusal_reason, minimum_batches_required, batches_evaluated, refusal_notes, reviewer, dry_lab_only). recalibration_refused must be True — this schema only records valid refusals. 7 validation rules (RRF- prefix, CPS-/DRM- trigger prefix, recalibration_refused=True enforced, reason in 5 valid values, minimum_batches>=1, batches_evaluated>=0, notes<=400 chars). 2 warnings (insufficient_data but meets minimum; reviewer_override without documentation). Complements CalibrationImprovementRecord (O3). Added validate_recalibration_refusal() and validate_recalibration_refusal_dict(). Added 63 tests. CLI: openamp-foundry recalibration-refusal-check with --entry-json and --format text|json. Corrected inflated BASELINE from 4889 to 4784 (actual count 4721 + 63 = 4784).
> **New in v0.10.4 (Phase P P1):** BatchSelectionProposalEntry schema (12 fields: bsp_id, pipeline_version, gate_id, gate_passed, candidate_ids, selection_strategy, exploitation_fraction, exploration_fraction, max_brier_score_allowed, proposal_notes, reviewer, dry_lab_only). gate_passed must be True — a batch can only be proposed when the calibration readiness gate passes. 9 validation rules (BSP- prefix, CRG- prefix, gate_passed=True enforced, candidate_ids >=1, strategy in 5 valid values, fraction bounds, fraction sum=1.0 to 0.001 tolerance, Brier bounds [0.0,1.0], notes <=400 chars). 3 warnings (pure exploitation >=0.99, single candidate, marginal Brier >=0.25). Added validate_batch_selection_proposal() and validate_batch_selection_proposal_dict(). Added 68 tests. CLI: openamp-foundry batch-selection-proposal-check with --entry-json and --format text|json. Closes the calibration-to-selection loop from Phase O.
> **New in v0.10.3 (Phase O O5):** CalibrationReadinessEntry schema (11 fields, 8 validation rules, 2 warnings). Added validate_calibration_readiness() and validate_calibration_readiness_dict(). Added 68 tests. CLI: openamp-foundry calibration-readiness-check. Phase O (Calibration Quality Assurance) COMPLETE.
Expand Down
73 changes: 73 additions & 0 deletions docs/evidence/PILOT_EVIDENCE_PACKAGE_GUIDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Pilot Evidence Package Guide

## Purpose

A Pilot Evidence Package (PEP) is the external export artifact for a pilot candidate
batch. It bundles references to all required evidence artifacts and validates that
the package is complete and cleared before it can be shared with an external lab
partner.

## Required evidence chain

A complete PEP must reference:

| Artifact | Prefix | Phase | What it documents |
|---|---|---|---|
| CalibrationCycleSummary | CCS- | P5 | Full calibration feedback loop |
| BatchSelectionProposal | BSP- | P1 | Why these candidates were selected |
| PilotBatchSafetyClearance | PSC- | P4 | Safety review and clearance |
| PreRegistrationEntry | PRE- | N1 | Pre-committed experiment plan |
| BaselineComparisonManifest | BCM- | N3 | Evidence pipeline beats baselines |

## Completeness constraint

`is_complete=True` requires `missing_artifacts=[]`.
`is_complete=False` requires `missing_artifacts` to be non-empty.

Incomplete packages can be saved as work-in-progress but cannot be released
for external review.

## Safety constraint

`cleared_for_synthesis=True` is required. The referenced PSC must have cleared
the batch. A package without safety clearance cannot be marked as ready for
external sharing.

## Schema fields

| Field | Type | Description |
|---|---|---|
| `pep_id` | str | Must start with `PEP-` |
| `pipeline_version` | str | Pipeline version at package creation |
| `ccs_id` | str | Must start with `CCS-` |
| `bsp_id` | str | Must start with `BSP-` |
| `psc_id` | str | Must start with `PSC-` |
| `pre_registration_id` | str | Must start with `PRE-` |
| `baseline_comparison_id` | str | Must start with `BCM-` |
| `candidate_count` | int | >= 1 |
| `cleared_for_synthesis` | bool | Must be True |
| `is_complete` | bool | True iff missing_artifacts is empty |
| `missing_artifacts` | List[str] | Empty if complete |
| `package_notes` | str | Max 400 chars |
| `reviewer` | str | Who prepared and approved the package |
| `dry_lab_only` | bool | True if no real experimental data in this package |

## Honest boundaries

- A PEP is a reference index, not a data container. The artifacts it references
must be independently validated.
- `cleared_for_synthesis=True` reflects the PSC gate — it is a computational
safety check, not wet-lab confirmation.
- `is_complete=True` means all required artifact IDs are present. It does not
validate the quality of those artifacts.
- `dry_lab_only=True` packages must clearly communicate to lab partners that
no experimental validation has been performed — candidates are computational
nominations only.

## Warnings

The validator emits warnings (not errors) for:

- `dry_lab_only=True` with `is_complete=True` — lab partners must be informed
this is a computational screening package.
- `candidate_count=1` — minimal diversity; consider larger batch.
11 changes: 11 additions & 0 deletions docs/research/NEXT_100_PR_MAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,17 @@ preventing the pipeline from selecting candidates under poor calibration.
| P4 | Add pilot batch safety clearance schema (complete). | Safety gate before wet-lab synthesis: all 4 screens required, high-risk batches cannot be cleared. | B/D |
| P5 | Add calibration cycle summary schema (complete). | Index record for one complete BSP→PSC→BOS→CPS→CBA→CRG cycle; crg_id_previous must differ from crg_id_next. | B/D |

## Phase Q — External Pilot Package Readiness

Package and validate the evidence chain for external lab partners. A complete
pilot package references pre-registration, baseline comparisons, calibration
cycles, safety clearance, and candidate batch proposal — in one verifiable
bundle.

| PR | Task | Why it matters | Review class |
|---:|---|---|---|
| Q1 | Add pilot evidence package schema (complete). | External export artifact: bundles CCS+BSP+PSC+PRE+BCM references, enforces completeness and safety clearance before external sharing. | B/D |

## Prioritization rule

When choosing between tasks, prefer the one that:
Expand Down
37 changes: 34 additions & 3 deletions src/openamp_foundry/cli/commands/reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -3717,9 +3717,40 @@ def _run_calibration_cycle_summary_check(args) -> int:
print(json.dumps(dataclasses.asdict(result), indent=2))
else:
status = "PASS" if result.passed else "FAIL"
print(f"[{status}] Calibration Cycle Summary: {result.ccs_id}")
print(f" Outcome: {result.cycle_outcome}")
print(f" CRG: {result.crg_id_previous} → {result.crg_id_next}")
complete = "COMPLETE" if result.is_complete else f"INCOMPLETE ({result.missing_artifact_count} missing)"
print(f"[{status}] Calibration Cycle Summary: {result.ccs_id} [{complete}]")
print(f" BSP: {result.bsp_id}, candidates: {result.candidate_count}")
print(f" Cycle outcome: {result.cycle_outcome}")
for err in result.errors:
print(f" ERROR: {err}")
for warn in result.warnings:
print(f" WARN: {warn}")
return 0 if result.passed else 1


def _run_pilot_evidence_package_check(args) -> int:
import json
from openamp_foundry.evidence.pilot_evidence_package import (
validate_pilot_evidence_package_dict,
)
if args.entry_json:
try:
data = json.loads(args.entry_json)
except json.JSONDecodeError as exc:
print(f"Error: invalid JSON: {exc}", file=__import__("sys").stderr)
return 1
else:
data = json.load(__import__("sys").stdin)
result = validate_pilot_evidence_package_dict(data)
if args.format == "json":
import dataclasses
print(json.dumps(dataclasses.asdict(result), indent=2))
else:
status = "PASS" if result.passed else "FAIL"
complete = "COMPLETE" if result.is_complete else f"INCOMPLETE ({result.missing_artifact_count} missing)"
print(f"[{status}] Pilot Evidence Package: {result.pep_id} [{complete}]")
print(f" CCS: {result.ccs_id}, candidates: {result.candidate_count}")
print(f" Cleared: {result.cleared_for_synthesis}")
for err in result.errors:
print(f" ERROR: {err}")
for warn in result.warnings:
Expand Down
12 changes: 12 additions & 0 deletions src/openamp_foundry/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
_run_batch_outcome_summary_check,
_run_pilot_batch_safety_clearance_check,
_run_calibration_cycle_summary_check,
_run_pilot_evidence_package_check,
)
from openamp_foundry.cli.commands.gates import _run_gate_check, _run_release_gate_check
from openamp_foundry.cli.commands.reports import _run_contribution_check
Expand Down Expand Up @@ -2401,6 +2402,14 @@ def build_parser() -> argparse.ArgumentParser:
p_ccs.add_argument("--format", choices=["text", "json"], default="text")
p_ccs.set_defaults(func=_run_calibration_cycle_summary_check)

p_pep = sub.add_parser(
"pilot-evidence-package-check",
help="Validate pilot evidence package entry",
)
p_pep.add_argument("--entry-json", default=None)
p_pep.add_argument("--format", choices=["text", "json"], default="text")
p_pep.set_defaults(func=_run_pilot_evidence_package_check)

# ── Selection rationale check (Phase K K1) ───────────────────────
src2 = sub.add_parser(
"selection-rationale-check",
Expand Down Expand Up @@ -2791,6 +2800,9 @@ def main(argv: list[str] | None = None) -> int:
if args.command == "calibration-cycle-summary-check":
return _run_calibration_cycle_summary_check(args)

if args.command == "pilot-evidence-package-check":
return _run_pilot_evidence_package_check(args)

parser.error("unknown command")
return 2

Expand Down
214 changes: 214 additions & 0 deletions src/openamp_foundry/evidence/pilot_evidence_package.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
"""Pilot evidence package schema — Phase Q Q1.

The external export artifact for a pilot package. References all required
evidence artifacts and validates package completeness before external sharing.

A complete package (is_complete=True) means every required artifact ID is
present and the safety clearance is confirmed. Incomplete packages can be
saved but not released for external review.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import List

PEP_PREFIX = "PEP-"
CCS_PREFIX = "CCS-"
BSP_PREFIX = "BSP-"
PSC_PREFIX = "PSC-"
PRE_PREFIX = "PRE-"
BCM_PREFIX = "BCM-"
PACKAGE_NOTES_MAX_LENGTH = 400
MIN_CANDIDATES = 1


@dataclass
class PilotEvidencePackageEntry:
"""Evidence package for external pilot sharing."""

pep_id: str
pipeline_version: str
ccs_id: str
bsp_id: str
psc_id: str
pre_registration_id: str
baseline_comparison_id: str
candidate_count: int
cleared_for_synthesis: bool
is_complete: bool
missing_artifacts: List[str]
package_notes: str
reviewer: str
dry_lab_only: bool = True


@dataclass
class PilotEvidencePackageResult:
pep_id: str
pipeline_version: str
ccs_id: str
bsp_id: str
psc_id: str
candidate_count: int
cleared_for_synthesis: bool
is_complete: bool
missing_artifact_count: int
passed: bool
errors: List[str]
warnings: List[str]
dry_lab_only: bool = True


def validate_pilot_evidence_package(
entry: PilotEvidencePackageEntry,
) -> PilotEvidencePackageResult:
errors: List[str] = []
warnings: List[str] = []

if not entry.pep_id.startswith(PEP_PREFIX):
errors.append(
f"pep_id must start with '{PEP_PREFIX}', got '{entry.pep_id}'"
)

if not entry.ccs_id.startswith(CCS_PREFIX):
errors.append(
f"ccs_id must start with '{CCS_PREFIX}', got '{entry.ccs_id}'"
)

if not entry.bsp_id.startswith(BSP_PREFIX):
errors.append(
f"bsp_id must start with '{BSP_PREFIX}', got '{entry.bsp_id}'"
)

if not entry.psc_id.startswith(PSC_PREFIX):
errors.append(
f"psc_id must start with '{PSC_PREFIX}', got '{entry.psc_id}'"
)

if not entry.pre_registration_id.startswith(PRE_PREFIX):
errors.append(
f"pre_registration_id must start with '{PRE_PREFIX}', "
f"got '{entry.pre_registration_id}'"
)

if not entry.baseline_comparison_id.startswith(BCM_PREFIX):
errors.append(
f"baseline_comparison_id must start with '{BCM_PREFIX}', "
f"got '{entry.baseline_comparison_id}'"
)

if entry.candidate_count < MIN_CANDIDATES:
errors.append(
f"candidate_count must be >= {MIN_CANDIDATES}, "
f"got {entry.candidate_count}"
)

if not entry.cleared_for_synthesis:
errors.append(
"cleared_for_synthesis must be True — a pilot package requires a "
"confirmed safety clearance (PSC) before external sharing"
)

# is_complete consistency
has_missing = len(entry.missing_artifacts) > 0
if entry.is_complete and has_missing:
errors.append(
"is_complete cannot be True when missing_artifacts is non-empty "
f"(missing: {entry.missing_artifacts})"
)
if not entry.is_complete and not has_missing:
errors.append(
"is_complete must be True when missing_artifacts is empty"
)

if len(entry.package_notes) > PACKAGE_NOTES_MAX_LENGTH:
errors.append(
f"package_notes exceeds {PACKAGE_NOTES_MAX_LENGTH} characters "
f"(got {len(entry.package_notes)})"
)

# Warnings
if entry.dry_lab_only and entry.is_complete and not errors:
warnings.append(
"dry_lab_only=True: this package is based on computational screening "
"only — external lab partners must be informed that no real experimental "
"validation has been performed"
)

if entry.candidate_count == 1 and not errors:
warnings.append(
"candidate_count is 1 — a single-candidate pilot provides minimal "
"diversity; consider whether a larger batch better serves the "
"experiment design"
)

return PilotEvidencePackageResult(
pep_id=entry.pep_id,
pipeline_version=entry.pipeline_version,
ccs_id=entry.ccs_id,
bsp_id=entry.bsp_id,
psc_id=entry.psc_id,
candidate_count=entry.candidate_count,
cleared_for_synthesis=entry.cleared_for_synthesis,
is_complete=entry.is_complete,
missing_artifact_count=len(entry.missing_artifacts),
passed=len(errors) == 0,
errors=errors,
warnings=warnings,
dry_lab_only=entry.dry_lab_only,
)


def validate_pilot_evidence_package_dict(d: dict) -> PilotEvidencePackageResult:
missing = []
for k in (
"pep_id",
"pipeline_version",
"ccs_id",
"bsp_id",
"psc_id",
"pre_registration_id",
"baseline_comparison_id",
"candidate_count",
"cleared_for_synthesis",
"is_complete",
"missing_artifacts",
"package_notes",
"reviewer",
):
if k not in d:
missing.append(k)
if missing:
return PilotEvidencePackageResult(
pep_id=d.get("pep_id", ""),
pipeline_version=d.get("pipeline_version", ""),
ccs_id=d.get("ccs_id", ""),
bsp_id=d.get("bsp_id", ""),
psc_id=d.get("psc_id", ""),
candidate_count=d.get("candidate_count", 0),
cleared_for_synthesis=d.get("cleared_for_synthesis", False),
is_complete=d.get("is_complete", False),
missing_artifact_count=0,
passed=False,
errors=[f"missing required fields: {missing}"],
warnings=[],
dry_lab_only=d.get("dry_lab_only", True),
)
entry = PilotEvidencePackageEntry(
pep_id=d["pep_id"],
pipeline_version=d["pipeline_version"],
ccs_id=d["ccs_id"],
bsp_id=d["bsp_id"],
psc_id=d["psc_id"],
pre_registration_id=d["pre_registration_id"],
baseline_comparison_id=d["baseline_comparison_id"],
candidate_count=int(d["candidate_count"]),
cleared_for_synthesis=bool(d["cleared_for_synthesis"]),
is_complete=bool(d["is_complete"]),
missing_artifacts=list(d["missing_artifacts"]),
package_notes=d["package_notes"],
reviewer=d["reviewer"],
dry_lab_only=bool(d.get("dry_lab_only", True)),
)
return validate_pilot_evidence_package(entry)
Loading
Loading