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
8 changes: 8 additions & 0 deletions docs/evidence/METRICS_CURRENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ Machine-readable snapshot: `outputs/metrics_snapshot.json` regenerated with `mak

## Changelog

### v0.10.23 — Phase G G8: SyntheticBoundaryAuditRecord schema
- Added `SyntheticBoundaryAuditRecord` (SBR-) schema
- 14 fields: sbr_id, pipeline_version, batch_id, audit_date, evidence_source, total_candidates_checked, total_violations, violation_rate, blocked_upgrades, max_proposed_ladder_level, policy_enforced, enforcement_outcome, summary, notes
- 16 validation rules; 3 warnings; 63 tests
- BASELINE 6044→6107
- CLI: openamp-foundry synthetic-boundary-audit-record-check
- Closes Phase G G8 — proof-ladder boundary enforcement is now an auditable evidence artifact

### v0.10.22 — Phase G G2: RecalibrationRejectionSummary schema
- Added `RecalibrationRejectionSummary` (RRS-) schema
- 13 fields: rrs_id, pipeline_version, period_start, period_end, total_checkpoints_reviewed, total_refused, total_approved, refusal_rate, top_refusal_reason, gate_status, all_refusals_have_rrf, summary, notes
Expand Down
79 changes: 79 additions & 0 deletions docs/evidence/SYNTHETIC_BOUNDARY_AUDIT_RECORD_GUIDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# SyntheticBoundaryAuditRecord (SBR-) Guide

## Purpose

`SyntheticBoundaryAuditRecord` makes each enforcement of the synthetic results
policy an auditable artifact. It documents that the boundary between
dry-lab simulation and wet-lab proof is actively enforced — synthetic evidence
cannot raise a candidate's proof-ladder level.

## Proof-Ladder Boundary

| Level | Label | Synthetic evidence? |
|-------|-------|---------------------|
| 1 | computational nomination | Yes |
| 2 | virtual-assay support | Yes |
| 3 | in-silico ensemble agreement | Yes |
| **4** | **ex-vivo preliminary** | **No — wet-lab required** |
| 5 | in-vivo preliminary | No — wet-lab required |
| 6 | clinical evidence | No — wet-lab required |

## Schema Fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `sbr_id` | str | yes | Unique identifier, must start with `SBR-` |
| `pipeline_version` | str | yes | Pipeline version during audit |
| `batch_id` | str | yes | Batch identifier audited |
| `audit_date` | str | yes | ISO date (YYYY-MM-DD) of audit |
| `evidence_source` | str | yes | Source type (controlled vocabulary) |
| `total_candidates_checked` | int | yes | Candidates checked (≥1) |
| `total_violations` | int | yes | Candidates with invalid level proposals |
| `violation_rate` | float | yes | Fraction violated (consistency check, tol 0.01) |
| `blocked_upgrades` | int | yes | Violations that were blocked (in [0, total_violations]) |
| `max_proposed_ladder_level` | int | yes | Highest proposed level in batch (1-6) |
| `policy_enforced` | bool | yes | Must be True |
| `enforcement_outcome` | str | yes | Overall outcome (controlled vocabulary) |
| `summary` | str | yes | Narrative summary (≤400 chars) |
| `notes` | str | no | Additional context (≤300 chars) |

## Controlled Vocabularies

**evidence_source (4 values):**
- `synthetic`, `simulation`, `computational`, `mixed_synthetic_lab`

**enforcement_outcome (3 values):**
- `all_passed` — no violations found
- `violations_blocked` — violations found and blocked
- `violations_flagged` — violations found and flagged for review

## Validation Rules

1. `sbr_id` starts with `SBR-`
2. `pipeline_version` non-empty
3. `batch_id` non-empty
4. `audit_date` is ISO date (YYYY-MM-DD)
5. `evidence_source` in controlled vocabulary (4 values)
6. `total_candidates_checked` ≥ 1
7. `total_violations` in [0, total_candidates_checked]
8. `violation_rate` in [0.0, 1.0]
9. `violation_rate` consistent with total_violations/total_candidates_checked (tol 0.01)
10. `blocked_upgrades` in [0, total_violations]
11. `max_proposed_ladder_level` in [1, 6]
12. Synthetic-only sources cannot propose level ≥4 without recording violations
13. `policy_enforced` must be True
14. `enforcement_outcome` in controlled vocabulary (3 values)
15. `summary` non-empty and ≤400 chars
16. `notes` ≤300 chars

## Warnings

- `violation_rate > 0.3`: high overclaim attempt rate — review batch scoring
- Violations flagged but not all blocked: verify enforcement_outcome
- `notes` empty: consider documenting enforcement context

## Integration

- Triggered after: synthetic score run on candidates
- Violations reference: `SyntheticResultPolicyCheck` (individual policy check)
- Feeds into: evidence audit trail, calibration health, external review packets
2 changes: 1 addition & 1 deletion docs/research/NEXT_100_PR_MAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ Make learning controlled rather than self-serving.
| G5 | Add batch-2 rationale report explaining exploit/explore/diversity roles. | Reviewers understand next batch. | C |
| G6 | Add calibration-overfit warning when cohort is too small. | Prevents false learning. | C |
| G7 | Add result-quality flag propagation into calibration engine. | Low-quality outcomes cannot drive updates. | C |
| G8 | Add policy that synthetic results cannot raise proof-ladder level. | Anti-overclaim. | B/C |
| G8 | Add policy that synthetic results cannot raise proof-ladder level (complete). | SBR- schema: 14 fields, 16 validation rules, synthetic-only evidence cannot propose level ≥4 without violations recorded, policy_enforced=True enforced, violation rate consistency check (tol 0.01); anti-overclaim boundary is now auditable artifact. | B/C |
| G9 | Add calibration decision review checklist. | Human review stronger. | C/D |
| G10 | Add recalibration rollback plan. | Safer updates. | C |

Expand Down
17 changes: 17 additions & 0 deletions src/openamp_foundry/cli/commands/reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -3048,3 +3048,20 @@ def _run_negative_result_dashboard_check(args):
for w in result.warnings:
print(f"WARNING: {w}")
sys.exit(1)


def _run_synthetic_boundary_audit_record_check(args):
"""CLI handler for synthetic-boundary-audit-record-check."""
import json, sys
from openamp_foundry.evidence.synthetic_boundary_audit_record import validate_dict
data = json.load(open(args.input))
issues = validate_dict(data)
errors = [i for i in issues if not i.startswith("WARNING:")]
warnings = [i for i in issues if i.startswith("WARNING:")]
for w in warnings:
print(w)
if errors:
for e in errors:
print(f"ERROR: {e}", file=sys.stderr)
sys.exit(1)
print("OK: SyntheticBoundaryAuditRecord is valid.")
5 changes: 5 additions & 0 deletions src/openamp_foundry/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
_run_pre_registration_entry_check,
_run_recalibration_decision_log_check,
_run_recalibration_rejection_summary_check,
_run_synthetic_boundary_audit_record_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 @@ -2538,6 +2539,10 @@ def build_parser() -> argparse.ArgumentParser:
p_rrs.add_argument("input", help="Path to JSON file")
p_rrs.set_defaults(func=_run_recalibration_rejection_summary_check)

p_sbr = subparsers.add_parser("synthetic-boundary-audit-record-check", help="Validate a SyntheticBoundaryAuditRecord JSON")
p_sbr.add_argument("input", help="Path to JSON file")
p_sbr.set_defaults(func=_run_synthetic_boundary_audit_record_check)

# ── Selection rationale check (Phase K K1) ───────────────────────
src2 = sub.add_parser(
"selection-rationale-check",
Expand Down
216 changes: 216 additions & 0 deletions src/openamp_foundry/evidence/synthetic_boundary_audit_record.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
"""Synthetic boundary audit record schema — Phase G G8.

Documents each enforcement of the policy that synthetic/simulation results
cannot raise a candidate's proof-ladder level.

Proof-ladder levels:
1 = computational nomination
2 = virtual-assay support
3 = in-silico ensemble agreement
4 = ex-vivo preliminary ← requires wet-lab evidence
5 = in-vivo preliminary ← requires wet-lab evidence
6 = clinical evidence ← requires wet-lab evidence

Synthetic results may maintain or, when combined with prior wet-lab evidence,
annotate a level — but they CANNOT raise it. Level 4+ is unreachable by
simulation alone.

This schema makes each enforcement event an auditable artifact.
"""

from __future__ import annotations

import re
from dataclasses import dataclass, field
from typing import List

SBR_PREFIX = "SBR-"

PROOF_LADDER_MIN = 1
PROOF_LADDER_MAX = 3 # max level reachable via synthetic-only evidence
WET_LAB_REQUIRED_LEVEL = 4 # level ≥ 4 requires wet-lab evidence

VALID_EVIDENCE_SOURCES = frozenset({
"synthetic",
"simulation",
"computational",
"mixed_synthetic_lab",
})

VALID_ENFORCEMENT_OUTCOMES = frozenset({
"all_passed",
"violations_blocked",
"violations_flagged",
})

VIOLATION_RATE_TOLERANCE = 0.01
HIGH_VIOLATION_RATE_WARNING = 0.3
SUMMARY_MAX_LENGTH = 400
NOTES_MAX_LENGTH = 300
_ISO_DATE_RE = re.compile(r"^\d{4}-\d{2}-\d{2}$")


@dataclass
class SyntheticBoundaryAuditRecord:
sbr_id: str
pipeline_version: str
batch_id: str
audit_date: str
evidence_source: str
total_candidates_checked: int
total_violations: int
violation_rate: float
blocked_upgrades: int
max_proposed_ladder_level: int
policy_enforced: bool
enforcement_outcome: str
summary: str
notes: str = ""


def validate(record: SyntheticBoundaryAuditRecord) -> List[str]:
"""Return list of error strings; empty list means valid."""
errors: List[str] = []

# Rule 1: ID prefix
if not record.sbr_id.startswith(SBR_PREFIX):
errors.append(
f"sbr_id must start with '{SBR_PREFIX}', got: {record.sbr_id!r}"
)

# Rule 2: pipeline_version non-empty
if not record.pipeline_version.strip():
errors.append("pipeline_version must not be empty")

# Rule 3: batch_id non-empty
if not record.batch_id.strip():
errors.append("batch_id must not be empty")

# Rule 4: audit_date ISO format
if not _ISO_DATE_RE.match(record.audit_date):
errors.append(
f"audit_date must be YYYY-MM-DD, got: {record.audit_date!r}"
)

# Rule 5: evidence_source vocabulary
if record.evidence_source not in VALID_EVIDENCE_SOURCES:
errors.append(
f"evidence_source must be one of {sorted(VALID_EVIDENCE_SOURCES)}, "
f"got: {record.evidence_source!r}"
)

# Rule 6: total_candidates_checked >= 1
if record.total_candidates_checked < 1:
errors.append(
f"total_candidates_checked must be >= 1, "
f"got: {record.total_candidates_checked}"
)

# Rule 7: total_violations in [0, total_candidates_checked]
if not (0 <= record.total_violations <= record.total_candidates_checked):
errors.append(
f"total_violations must be in [0, {record.total_candidates_checked}], "
f"got: {record.total_violations}"
)

# Rule 8: violation_rate in [0.0, 1.0]
if not (0.0 <= record.violation_rate <= 1.0):
errors.append(
f"violation_rate must be in [0.0, 1.0], got: {record.violation_rate}"
)

# Rule 9: violation_rate consistency (tolerance 0.01)
if record.total_candidates_checked >= 1:
computed = record.total_violations / record.total_candidates_checked
if abs(computed - record.violation_rate) > VIOLATION_RATE_TOLERANCE:
errors.append(
f"violation_rate ({record.violation_rate:.4f}) inconsistent with "
f"total_violations/total_candidates_checked "
f"({record.total_violations}/{record.total_candidates_checked} "
f"= {computed:.4f}); tolerance is {VIOLATION_RATE_TOLERANCE}"
)

# Rule 10: blocked_upgrades in [0, total_violations]
if not (0 <= record.blocked_upgrades <= record.total_violations):
errors.append(
f"blocked_upgrades must be in [0, {record.total_violations}], "
f"got: {record.blocked_upgrades}"
)

# Rule 11: max_proposed_ladder_level in [1, 6]
if not (1 <= record.max_proposed_ladder_level <= 6):
errors.append(
f"max_proposed_ladder_level must be in [1, 6], "
f"got: {record.max_proposed_ladder_level}"
)

# Rule 12: synthetic-only evidence cannot propose level >= 4
if (
record.evidence_source in ("synthetic", "simulation", "computational")
and record.max_proposed_ladder_level >= WET_LAB_REQUIRED_LEVEL
and record.total_violations == 0
):
errors.append(
f"evidence_source '{record.evidence_source}' cannot support "
f"max_proposed_ladder_level {record.max_proposed_ladder_level} "
f"(level {WET_LAB_REQUIRED_LEVEL}+ requires wet-lab evidence) "
f"without any violations recorded"
)

# Rule 13: policy_enforced must be True
if not record.policy_enforced:
errors.append(
"policy_enforced must be True — the synthetic boundary policy "
"must be actively enforced; disable this check requires human review"
)

# Rule 14: enforcement_outcome vocabulary
if record.enforcement_outcome not in VALID_ENFORCEMENT_OUTCOMES:
errors.append(
f"enforcement_outcome must be one of "
f"{sorted(VALID_ENFORCEMENT_OUTCOMES)}, "
f"got: {record.enforcement_outcome!r}"
)

# Rule 15: summary non-empty and length
if not record.summary.strip():
errors.append("summary must not be empty")
elif len(record.summary) > SUMMARY_MAX_LENGTH:
errors.append(
f"summary exceeds {SUMMARY_MAX_LENGTH} chars "
f"(got {len(record.summary)})"
)

# Rule 16: notes length
if len(record.notes) > NOTES_MAX_LENGTH:
errors.append(
f"notes exceeds {NOTES_MAX_LENGTH} chars (got {len(record.notes)})"
)

# Warnings
if record.violation_rate > HIGH_VIOLATION_RATE_WARNING:
errors.append(
f"WARNING: violation_rate ({record.violation_rate:.2f}) > "
f"{HIGH_VIOLATION_RATE_WARNING} — high overclaim attempt rate; "
"review batch selection and scoring methodology"
)
if record.total_violations > 0 and record.blocked_upgrades < record.total_violations:
errors.append(
f"WARNING: {record.total_violations - record.blocked_upgrades} violations "
"were flagged but not blocked — verify enforcement_outcome is correct"
)
if not record.notes.strip():
errors.append(
"WARNING: notes is empty — consider documenting enforcement context"
)

return errors


def validate_dict(data: dict) -> List[str]:
"""Validate a plain dict by constructing SyntheticBoundaryAuditRecord first."""
try:
record = SyntheticBoundaryAuditRecord(**data)
except TypeError as exc:
return [f"Schema construction error: {exc}"]
return validate(record)
Loading
Loading