core/scripts/scaffold_phase5_domain_lanes.py
Shay ad7993e861 feat(phase5): land 5.2–5.7 — six new fluency lanes, parallel sweep
Completes the Phase 5 curriculum-era lane checklist alongside 5.1.

English-substrate domain lanes (5.4–5.7) — extend the proven
english_fluency_ood pattern with new vocabulary domains. Same
13-construction realizer, same grammatical_coverage rubric, new
triples. All four lanes land at 100% on both splits:

  5.4 elementary_mathematics_ood    117/117 public + 39/39 holdouts
      domains: arithmetic, set, geometry  |  holdout: probability
  5.5 foundational_physics_ood      117/117 + 39/39
      domains: mechanics, electricity, thermodynamics  |  holdout: optics
  5.6 foundational_biology_ood      117/117 + 39/39
      domains: cell, organism, ecosystem  |  holdout: genetics
  5.7 classical_literature_ood      117/117 + 39/39
      domains: epic, tragedy, lyric  |  holdout: comedy

New-language lanes (5.2 Hebrew, 5.3 Koine Greek) — scoped honestly to
v1 = C01 only, script + length rubric. The realizer's
tense/aspect/quantifier/negation logic in generate/templates.py is
English-only; C02-C13 in HE/GRC requires Hebrew/Greek morphology +
rhetorical templates, named explicitly in each lane's gaps.md as the
v2 unblock path. v1 measures what infrastructure exists:

  5.2 hebrew_fluency       3/3  (predicate-subject-object assembly,
                                  Hebrew script gate)
  5.3 koine_greek_fluency  3/3  (subject-object-predicate assembly,
                                  Greek script gate)

Lane scaffolds follow the established pattern: contract.md, runner.py,
__init__.py, gaps.md, public/v1/cases.jsonl, dev/cases.jsonl,
holdouts/v1/cases.jsonl (5.4–5.7 only; HE/GRC holdouts deferred to v2
when vocabulary expands).

Generators + scorers:
  scripts/generate_phase5_domain_lanes.py      — 5.4–5.7 case emit
  scripts/scaffold_phase5_domain_lanes.py      — 5.4–5.7 contracts/runners
  scripts/generate_phase5_language_lanes.py    — 5.2/5.3 case emit
  scripts/score_phase5_holdouts.py             — parallel holdouts scoring
                                                 via multiprocessing.Pool
                                                 (mirrors the parallel-eval
                                                 pattern from evals/parallel.py)

Lanes are wired into core eval --list automatically through the
framework's lane discovery; parallel sweeps via bash background jobs
(one process per lane).

Regression clean: smoke 54, runtime 19, teaching 17, packs 6,
cognition 57, algebra 132. Cognition eval 100% across all metrics.
2026-05-16 20:59:31 -07:00

168 lines
5.9 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.

"""Write contract.md, runner.py, __init__.py, and gaps.md for each
Phase 5.45.7 domain lane.
The lane scaffolds delegate scoring to the grammatical_coverage runner
(same pattern as english_fluency_ood) so the rubric stays consistent
across lanes. Vocabulary is the only thing that changes.
Run after `generate_phase5_domain_lanes.py`.
"""
from __future__ import annotations
from pathlib import Path
LANES: dict[str, dict[str, str]] = {
"elementary_mathematics_ood": {
"phase": "5.4",
"title": "Elementary mathematics fluency OOD",
"public_domains": "arithmetic, set, geometry",
"holdout_domain": "probability",
"claim": "the deterministic realizer produces grammatical English surfaces over elementary-mathematics vocabulary the seed pack does not contain",
},
"foundational_physics_ood": {
"phase": "5.5",
"title": "Foundational physics fluency OOD",
"public_domains": "mechanics, electricity, thermodynamics",
"holdout_domain": "optics",
"claim": "the deterministic realizer produces grammatical English surfaces over foundational-physics vocabulary the seed pack does not contain",
},
"foundational_biology_ood": {
"phase": "5.6",
"title": "Foundational biology fluency OOD",
"public_domains": "cell, organism, ecosystem",
"holdout_domain": "genetics",
"claim": "the deterministic realizer produces grammatical English surfaces over foundational-biology vocabulary the seed pack does not contain",
},
"classical_literature_ood": {
"phase": "5.7",
"title": "Classical literature fluency OOD",
"public_domains": "epic, tragedy, lyric",
"holdout_domain": "comedy",
"claim": "the deterministic realizer produces grammatical English surfaces over classical-literature vocabulary the seed pack does not contain",
},
}
def _contract(lane: str, spec: dict[str, str]) -> str:
return f"""# {spec['title']} eval lane (Phase {spec['phase']})
## What it measures
Whether {spec['claim']}.
The structural claim under test: fluency is mechanistic in the
realizer (templates over typed graph nodes), not lexical
(pack-bound). If the claim holds, this OOD vocabulary passes the
same syntactic gates the seed vocabulary did at v1/v2 of
`grammatical_coverage` — and the same 13 constructions covered by
the Phase 5.1 `english_fluency_ood` lane.
## Inputs
`public/v1/cases.jsonl`
: 117 cases — 13 constructions × 3 public domains ({spec['public_domains']})
× 3 lexical items per domain.
`holdouts/v1/cases.jsonl`
: 39 cases — 13 constructions × 1 held-out domain
({spec['holdout_domain']}) × 3 items.
`dev/cases.jsonl`
: 13 cases — one of each construction from the first public domain.
Each case is one PropositionGraph JSON with realised constraints,
generated by `scripts/generate_phase5_domain_lanes.py`.
## Scoring rubric
Delegated to `evals/grammatical_coverage/runner.py` so the rubric
stays consistent across all fluency-OOD lanes:
- surface satisfies `accept_surfaces` or `constraints` (must_contain,
word_order, max_words)
- surface is not in `reject_surfaces`
## Target
100% on public + holdouts. Any miss indicates a real realizer or
inflection gap and is filed in `gaps.md`.
## Provenance
Same harness as `evals/english_fluency_ood/` (Phase 5.1).
Vocabulary is the only material change between lanes.
"""
_RUNNER = '''"""Phase {phase} domain fluency OOD eval lane runner.
Verifies the deterministic realizer produces grammatical English
surfaces across the 13 grammatical-coverage constructions when the
(subject, predicate, object) vocabulary is **out of the
en_core_cognition_v1 distribution** — drawn from the {public_domains}
public domains and the held-out {holdout_domain} domain.
Scoring is delegated to the grammatical_coverage runner so the
rubric stays consistent across all fluency-OOD lanes.
Conforms to the framework interface: run_lane(cases, config=None) -> report.
"""
from __future__ import annotations
from typing import Any
from evals.grammatical_coverage.runner import LaneReport, run_lane as _run
def run_lane(cases: list[dict[str, Any]], *, config: Any = None) -> LaneReport:
return _run(cases, config=config)
'''
def _gaps(lane: str, spec: dict[str, str]) -> str:
return f"""# {spec['title']} — gaps
## v1 (current)
- Constructions covered: all 13 (C01C13) via grammatical_coverage rubric.
- Domains covered: public — {spec['public_domains']}; holdout —
{spec['holdout_domain']}.
- Predicates default to regular verbs to keep morphology gaps from
confounding the structural fluency claim.
## Known gaps for v2
The Phase 5.1 English fluency OOD lane already names three realizer
gaps that apply equally here:
G1. Irregular past tense — past_tense() handles regular -ed but not
irregular forms (e.g. "ran", "stood").
G2. Plural agreement — the realizer does not pluralise nouns or
conjugate to plural subjects.
G3. Punctuation strictness — the rubric pins comma-bounded relative
clauses exactly; small punctuation differences still fail.
These are not lane-specific. When 5.1 closes them, every domain lane
gains the same coverage for free.
## Out of scope for this lane
- Domain-specific semantic correctness (whether "ribosome assembles
protein" is *true* biology). This lane measures fluency, not
factual correctness. Truth checks live in the
`epistemic_status` surface (ADR-0021) and future curriculum
lanes.
"""
if __name__ == "__main__":
root = Path(__file__).resolve().parent.parent
for lane, spec in LANES.items():
lane_dir = root / "evals" / lane
(lane_dir / "__init__.py").write_text("")
(lane_dir / "contract.md").write_text(_contract(lane, spec))
(lane_dir / "runner.py").write_text(_RUNNER.format(**spec))
(lane_dir / "gaps.md").write_text(_gaps(lane, spec))
print(f"scaffolded: evals/{lane}/")