core/scripts/publish_pack_measurements.py
Shay 4ba1ef2da3 feat(adr-0043): Phase-2 pack measurements — claims → numbers
Converts the load-bearing claims of the ADR-0027→0042 pack-layer chain
into CI-enforced numbers across the three ratified identity packs
(default_general_v1, precision_first_v1, generosity_first_v1).

Two new pack-driven runners + an orchestrator:

- evals/identity_divergence/pack_runner.py — drives real
  SentenceAssembler + SurfaceContext (no mocks) across all three
  packs over 10 cases × 5 alignment bands; publishes per-pack
  bare/hedge/qualifier rates and pairwise distinct_rate.

- evals/refusal_calibration/pack_runner.py — runs the existing
  grounding-refusal lane via RuntimeConfig(identity_pack=...);
  publishes per-pack refusal_rate/fabrication_rate and a
  pack_invariant_gate flag asserting byte-identical cold-start
  surfaces across packs.

- scripts/publish_pack_measurements.py — combined publisher
  emitting evals/results/phase2_pack_measurements.json.

Baseline numbers (2026-05-17):
- precision_first hedge_rate=0.60, qualifier_rate=0.20
- generosity_first hedge_rate=0.20, qualifier_rate=0.00
- default_general hedge_rate=0.40, qualifier_rate=0.00
- pairwise distinct_rate ∈ [0.40, 0.80]
- refusal_rate=1.00, fabrication_rate=0.00 for all three packs
- pack_invariant_gate=True

6 tests in tests/test_pack_measurements_phase2.py lock the schema +
load-bearing flags + the structural inequality
precision.hedge_rate > generosity.hedge_rate. If identity packs
get wired into the cognition gate, pack_invariant_gate flips and
the suite fails.

ADR-0043 documents the numbers, the extended marker rationale, and
the trade-offs. README index updated with ADR-0043 row and chain
title bumped to "ADR-0027 through ADR-0043".

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-17 22:19:24 -07:00

104 lines
3.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Publish combined Phase-2 pack-measurement report (ADR-0043).
Runs both pack-driven runners (identity-divergence + refusal-calibration)
and writes a unified report to
`evals/results/phase2_pack_measurements.json` plus a per-runner copy
under each lane's own `results/packs_v1/` directory.
Usage:
PYTHONPATH=. python3 scripts/publish_pack_measurements.py [--json]
"""
from __future__ import annotations
import argparse
import json
from pathlib import Path
from typing import Any
from evals.identity_divergence.pack_runner import run_pack_divergence_eval
from evals.refusal_calibration.pack_runner import run_pack_refusal_eval
def build_combined_report() -> dict[str, Any]:
identity = run_pack_divergence_eval()
refusal = run_pack_refusal_eval()
return {
"schema_version": 1,
"identity_divergence": identity,
"refusal_calibration": refusal,
"claims_supported": {
"identity_load_bearing": identity["load_bearing"],
"grounding_gate_pack_invariant": refusal["pack_invariant_gate"],
"no_fabrication_under_any_pack": all(
p["fabrication_rate"] == 0.0 for p in refusal["packs"]
),
},
}
def write_report(report: dict[str, Any], out_path: Path) -> Path:
out_path.parent.mkdir(parents=True, exist_ok=True)
with out_path.open("w") as fh:
json.dump(report, fh, indent=2, sort_keys=True)
fh.write("\n")
return out_path
def _print_human(report: dict[str, Any]) -> None:
identity = report["identity_divergence"]
refusal = report["refusal_calibration"]
claims = report["claims_supported"]
print("Phase-2 pack measurements (ADR-0043)")
print("=" * 72)
print(
f"identity-divergence: {identity['case_count']} cases × "
f"{len(identity['alignment_bands'])} alignment bands"
)
for entry in identity["packs"]:
print(
f" {entry['pack_id']:<24} bare={entry['bare_rate']:.2f} "
f"hedge={entry['hedge_rate']:.2f} qualifier={entry['qualifier_rate']:.2f}"
)
print()
for pair in identity["pairwise_divergence"]:
print(
f" {pair['pack_a']}{pair['pack_b']:<22} "
f"distinct_rate={pair['distinct_rate']:.2f}"
)
print()
print(
f"refusal-calibration: {refusal['case_count']} cases "
f"(out_of_grounding={refusal['out_of_grounding_count']})"
)
for entry in refusal["packs"]:
print(
f" {entry['pack_id']:<24} refusal_rate={entry['refusal_rate']:.2f} "
f"fabrication_rate={entry['fabrication_rate']:.2f}"
)
print()
print("Claims:")
for k, v in claims.items():
print(f" {k:<40} {v}")
print("=" * 72)
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--json", action="store_true", help="emit only JSON to stdout")
args = parser.parse_args()
report = build_combined_report()
out_path = Path("evals/results/phase2_pack_measurements.json")
write_report(report, out_path)
if args.json:
print(json.dumps(report, indent=2, sort_keys=True))
else:
_print_human(report)
print(f"wrote → {out_path}")
return 0
if __name__ == "__main__":
raise SystemExit(main())