Commit graph

2 commits

Author SHA1 Message Date
3ea27aa471
#000047 — close: delta_aggregator knob on ForkScore (Option D)
The #000025 §10.14 calibration showed _delta_5{s,t,f} mean over a
battery's 5 subs, so a single-sub gain weighs 1/5 of face value (the
5× dilution). #000047 ships the knob to pick the aggregation, default
unchanged.

WeightSet.delta_aggregator ∈ {"mean","max","sum"} (default "mean") —
a categorical field, validated in __post_init__ against
DELTA_AGGREGATORS; from_dict takes it as a string. Default unchanged →
ScoredFork output byte-identical → no fork_score.ESTIMATOR_VERSION
bump.

fork_score._aggregate(deltas, how): mean = arithmetic mean, max =
max(0.0, max_i Δ_i), sum = Σ Δ_i; empty → 0.0. _delta_5s/_delta_5t/
_delta_5f take an aggregator arg (default "mean"); the 5F efficiency
bonus is added after the aggregated base (aggregator-independent).
fork_score passes weights.delta_aggregator. The per-sub
HARD_REGRESSION_FLOOR flags are computed before aggregation, so a
single-sub regression still forces REJECT under max/sum. The chosen
aggregator is recorded in ScoredFork.weights["delta_aggregator"] (via
WeightSet.as_dict()); fork_score_branches traceability stays via the
opaque weights_id — no schema migration.

bench/scripts/fivef_threshold_calibration.py gained §5 — runs the
#000046 below-ceiling pack (5f/falsification at 0.333) and shows the
verdict / γ·Δ5f under each aggregator; bench/results/5f-threshold-
calibration-2026-05-11.md §5 is the captured record. Default stays
"mean" — the conservative, noise-robust, regression-symmetric choice
matching docs/bench-maxing.md's per-rate floor framing; v8 picks
max/sum per-deployment.

Tests: 8 new in tests/test_fork_score.py + 1 anchor in
tests/test_fivef_threshold_calibration.py; tests/test_weights.py
as_dict field-set test updated to include delta_aggregator;
test_fork_score.py AUTOCOUNT tags (#000012 §286, warrant-substrate-
cookbook.md ×2) bumped 23 → 31.

#000047 closed; #000012 §8 §3 + TICKETS.md row updated.
Full suite: 2330 passed, 28 skipped.
2026-05-11 08:27:38 -04:00
3ef8975623
tests/weights: 16 tests for WeightSet defaults + from_dict adapter
arborist/substrate/weights.py was zero-coverage. 73 LOC of
dataclass + adapter. Tests pin:

  - documented defaults from the module docstring (each weight
    has a comment block explaining its design intent; tests
    pin the values so a PR that flips alpha=1.0 → 0.5 fires the
    test and forces an explicit docstring update)
  - documented invariants: alpha=beta=gamma (3 batteries equal),
    eta > alpha (regression heavier than improvement),
    reserved-zero defaults (zeta/iota/kappa)
  - as_dict() returns all 11 fields; lambda_ key (not "lambda" —
    keyword)
  - from_dict() greek-letter keys, "lambda" → "lambda_" translation
    (JSON/YAML friendly), missing-keys-default fall-through,
    extra-keys silent drop, str → float coercion, int → float
    coercion
  - frozen-dataclass invariant (mutation raises FrozenInstanceError)
  - dataclass equality

Full suite: 1881 passed, 54 skipped. tests/ count growing
roughly 1655 → 1881 (+226) across today's autonomous quality
session.
2026-05-10 12:41:49 -04:00