Followup to 654d923 (which moved the package from arborist/v8/ →
arborist/substrate/ at the file layer). The CLI surface still baked
in `v8` so a new operator running `--help` would see
``arborist v8 score`` and ask the same "what's v8 vs v9.8?"
naming-confusion question that drove the package rename in the
first place. Closing the loop end-to-end.
arborist/cli.py
===============
- Subparser renamed: ``"v8"`` → ``"substrate"``; help string updated
to "Merkle-AGI substrate primitives (ForkScore + future paper
specs)" so the dir name and command name and help text all align.
- Inner subparser dest renamed: ``v8_op`` → ``substrate_op``.
- Function renamed: ``_cmd_v8_score`` → ``_cmd_substrate_score``;
docstring updated.
- All ``v8_score`` local variables renamed to ``substrate_score``.
- New comment block above the subparser block explains the rename
+ why the v-prefix was retired (substrate-paper version vs v9.8
schema version naming collision).
The old ``arborist v8 score`` is gone — no alias preserved. CI + ops
scripts must update; today's earlier commit chain has been the only
place using it and that's been refreshed in lock-step.
tests/test_v8_fork_score.py
===========================
- 4 ``parser.parse_args(["v8", "score", ...])`` calls → ``["substrate", ...]``.
- 4 test functions renamed: ``test_cli_v8_score_*`` →
``test_cli_substrate_score_*``.
- Module docstring + section comment + helper docstring updated.
Filename intentionally kept as ``test_v8_fork_score.py`` for git
history continuity; pytest discovers by ``test_*`` content, not
filename. Renaming the file would muddle ``git log --follow`` for
the test surface.
Docs refreshed
==============
- docs/v8-fork-score.md — §5 CLI block invocation.
- docs/_source/v8-fork-score.rst — :code-block:: bash invocation.
- docs/_source/bench.rst — invocation in `### v8 ForkScore` section.
- docs/tickets/ticket-000012-selection-consensus-protocol.md —
three references in §7 close-out + §7 Phase 1c proposal +
§7 future-CLI-shape note.
- docs/dav1dprometheus-update-2026-05-09.md — bench journal mention.
Doc filenames (``v8-fork-score.{md,rst}``) kept stable since they
are URL identities; the file content explains the v8→substrate
rename internally. ``index.rst`` toctree references unchanged.
Hygiene
=======
- ``.venv/bin/arborist substrate score --help`` → 0 + valid usage.
- ``.venv/bin/arborist v8 score`` → exits non-zero (subcommand
removed, surfaced cleanly in ``argparse`` error).
- ``make test`` → 1643 passed, 45 skipped.
- ``make chain-check-shards`` → 0 across all 7 shards.
- fox's parallel work in arborist/qa/{runner,verify}.py +
arborist/qa/warrant_chain.py left untouched.
247 lines
8 KiB
Markdown
247 lines
8 KiB
Markdown
# v8 ForkScore — Phase 1a reference
|
||
|
||
Reference for `arborist.substrate.fork_score` (ticket #000012 Phase 1a;
|
||
module moved from `arborist.v8.fork_score` to `arborist.substrate.fork_score`
|
||
on 2026-05-10 when the version-prefixed namespace pattern was retired —
|
||
the `v` in `v8` referred to the substrate-paper version, which collided
|
||
with the `v9.8` SQLite schema version and confused readers).
|
||
**Pure scoring function** over a (parent, child) BatteryResult pair.
|
||
No validator state machine, no consensus protocol, no acceptance
|
||
ledger — those are commissioned by the v8 paper itself, still open
|
||
under #000012.
|
||
|
||
This document is the **canonical source** for the formula, term
|
||
semantics, and verdict thresholds. Tests in
|
||
`tests/test_v8_fork_score.py` enforce them.
|
||
|
||
## 1. Formula
|
||
|
||
```text
|
||
ForkScore =
|
||
α · Δ5S
|
||
+ β · Δ5T
|
||
+ γ · Δ5F # incl. efficiency-aware bonus
|
||
+ δ · SelfModelCalibrationGain
|
||
+ ε · AuditCompleteness
|
||
+ ζ · ValidatorDiversity
|
||
- η · RegressionPenalty
|
||
- θ · CapitalCostPenalty
|
||
- ι · SecurityRiskPenalty # reserved (Phase 1a = 0)
|
||
- κ · ComplexityPenalty # reserved (Phase 1a = 0)
|
||
- λ · MemoryInvalidationPenalty
|
||
```
|
||
|
||
Sign convention: weights are non-negative; the sign of each term is
|
||
encoded in the formula. `breakdown` keys in the output reflect the
|
||
sign:
|
||
|
||
```json
|
||
{
|
||
"alpha_x_delta_5s": 0.025,
|
||
"minus_eta_x_regression_penalty": -0.0,
|
||
"minus_lambda_x_memory_invalidation": -0.5,
|
||
...
|
||
}
|
||
```
|
||
|
||
## 2. Term semantics
|
||
|
||
### 2.1 Δ5S, Δ5T, Δ5F (Δ-rate over each battery)
|
||
|
||
**Δ5S** = mean of per-sub-battery rate deltas across the 5S battery:
|
||
|
||
```
|
||
syntax parse_pass_rate
|
||
semantics equivalence_recovery_rate
|
||
syllogism step_validity_rate
|
||
synthesis derivation_pass_rate
|
||
semiotics invariance_under_swap
|
||
```
|
||
|
||
**Δ5T** = same shape, **canonical Dav1DPrometheus vocabulary** (the
|
||
legacy `transfer` sub-battery is intentionally excluded so v2
|
||
fixtures drive the score):
|
||
|
||
```
|
||
transfer-learning transfer_learning_success_rate
|
||
triangulation triangulation_agreement_rate
|
||
truthtables truth_table_coverage_rate
|
||
transitivity full_chain_pass_rate
|
||
time temporal_context_preservation_rate
|
||
```
|
||
|
||
**Δ5F** = mean of per-sub-battery rate deltas **plus** an
|
||
efficiency-aware bonus:
|
||
|
||
```
|
||
function function_pass_rate
|
||
finetuning adaptation_improvement_rate
|
||
falsification error_detection_rate
|
||
formulate structural_match_rate
|
||
feedback-loop integration_coverage_rate
|
||
```
|
||
|
||
The efficiency bonus is computed from the metrics shipped under
|
||
ticket #000025's zero-cost guards:
|
||
|
||
- `adaptation_efficiency_mean_finite` — Δ in mean efficiency,
|
||
damped by 0.1 to keep efficiency from dominating rate-style
|
||
improvements.
|
||
- `adaptation_efficiency_infinite_count` — increase = bonus capped
|
||
at `INFINITE_BONUS_CAP` (default 1.0). +∞ buckets must not pollute
|
||
the ranking.
|
||
- `adaptation_efficiency_neg_infinite_count` — increase = `NEG_INF_REGRESSION`
|
||
flag. **Hard reject** (a free regression is unsafe).
|
||
|
||
Same shape applies to `feedback_efficiency_*` keys.
|
||
|
||
### 2.2 SelfModelCalibrationGain
|
||
|
||
Distance closed between SelfModel capability claim threshold and
|
||
measured value. Phase 1a callers pass this as a scalar (default 0).
|
||
Phase 1b will compute it from `selfmodel_capability_claims` history.
|
||
|
||
### 2.3 AuditCompleteness
|
||
|
||
Fraction of state-changing ops with an `audit_event` row. Phase 1a
|
||
caller-supplied; Phase 1b reads from the chain directly. Range
|
||
`[0, 1]`; missing rows visible in the audit chain bring this below 1.
|
||
|
||
### 2.4 ValidatorDiversity
|
||
|
||
Multi-validator agreement entropy. **Zero in single-validator
|
||
mode** (Phase 1a default). v8 paper defines the multi-validator
|
||
calibration. With ζ = 0 by default, this term has no effect on
|
||
single-validator deployments.
|
||
|
||
### 2.5 RegressionPenalty
|
||
|
||
Sum over batteries of `max(0, -Δ_battery)`. A battery whose mean
|
||
delta is negative contributes its absolute value to the penalty
|
||
term (multiplied by η).
|
||
|
||
This is **softer** than the hard-regression flag (§3.2): if 4 of 5
|
||
sub-batteries improve by 10pp and 1 drops by 8pp, the mean Δ is
|
||
positive and `RegressionPenalty = 0` — but the hard flag still
|
||
fires on that one sub-battery.
|
||
|
||
### 2.6 CapitalCostPenalty
|
||
|
||
Capital cost delta from the #000020 ledger. Phase 1a accepts a
|
||
scalar `capital_delta`; positive means the child costs more. Phase
|
||
1b will compute it from `capital_ledger.summary`.
|
||
|
||
### 2.7 SecurityRiskPenalty, ComplexityPenalty
|
||
|
||
**Reserved for Phase 1b+.** Defaults `ι = κ = 0`. No security-bench
|
||
fixtures or LOC-delta surface exist in Phase 1a.
|
||
|
||
### 2.8 MemoryInvalidationPenalty
|
||
|
||
Count of `memory_records` rows the child fork would falsify. Phase
|
||
1a accepts a scalar; Phase 1b will compute it by simulating the
|
||
fork's verifier-method-root delta and counting memory_records that
|
||
cite the changed verifier.
|
||
|
||
## 3. Verdicts
|
||
|
||
### 3.1 Verdict thresholds
|
||
|
||
| Score | Hard flags | Verdict |
|
||
|---|---|---|
|
||
| ≥ `SIGNAL_FLOOR` (0.05) | none | **ACCEPT** |
|
||
| `[0, SIGNAL_FLOOR)` | none | **MARGINAL** (insufficient signal) |
|
||
| `< 0` | any | **REJECT** |
|
||
| any | hard-regression OR neg-inf efficiency | **REJECT** |
|
||
|
||
CLI exit code: `0` for ACCEPT/MARGINAL, `1` for REJECT — so CI
|
||
gates can run `arborist substrate score …` directly.
|
||
|
||
### 3.2 Hard flags
|
||
|
||
- **REGRESSION_5S/5T/5F:** any single sub-battery dropping by ≥
|
||
`HARD_REGRESSION_FLOOR` (default 5pp). Reported per sub-battery
|
||
in `flags`.
|
||
- **NEG_INF_REGRESSION:** `*_efficiency_neg_infinite_count`
|
||
increased parent → child. Free regression is unsafe regardless
|
||
of other gains.
|
||
|
||
A hard flag forces REJECT even when the net score is positive.
|
||
Operators can disable hard-flag gating in Phase 1b by overriding
|
||
the `_delta_*` helpers — but the soft `RegressionPenalty` term
|
||
stays in the formula.
|
||
|
||
## 4. Default weights
|
||
|
||
```python
|
||
WeightSet(
|
||
alpha=1.0, # Δ5S
|
||
beta=1.0, # Δ5T
|
||
gamma=1.0, # Δ5F
|
||
delta=0.5, # SelfModelCalibrationGain
|
||
epsilon=0.3, # AuditCompleteness
|
||
zeta=0.0, # ValidatorDiversity (off in single-validator)
|
||
eta=2.0, # RegressionPenalty (heavy by design)
|
||
theta=0.5, # CapitalCostPenalty
|
||
iota=0.0, # SecurityRiskPenalty (reserved)
|
||
kappa=0.0, # ComplexityPenalty (reserved)
|
||
lambda_=0.5, # MemoryInvalidationPenalty
|
||
)
|
||
```
|
||
|
||
Override via JSON file passed to `--weights`:
|
||
|
||
```json
|
||
{
|
||
"alpha": 1.5,
|
||
"eta": 5.0,
|
||
"lambda": 1.0
|
||
}
|
||
```
|
||
|
||
(The JSON key `"lambda"` round-trips into `WeightSet.lambda_`
|
||
because `lambda` is a Python reserved word.)
|
||
|
||
## 5. CLI
|
||
|
||
```bash
|
||
arborist substrate score \
|
||
--parent parent-bench.json \
|
||
--child child-bench.json \
|
||
[--weights weights.json] \
|
||
[--capital-delta N] \
|
||
[--selfmodel-calibration-gain N] \
|
||
[--audit-completeness N] \
|
||
[--memory-invalidation-count N]
|
||
```
|
||
|
||
`parent-bench.json` and `child-bench.json` are the JSON output of
|
||
`bench.batteries.runner --all` for each organism. The CLI prints
|
||
the `ScoredFork` as JSON and exits 0 on ACCEPT/MARGINAL, 1 on
|
||
REJECT.
|
||
|
||
## 6. What's NOT in Phase 1a
|
||
|
||
- Validator state machine (bonding / signing / slashing).
|
||
- Acceptance protocol (proposal / quorum / finalization).
|
||
- Challenge protocol (audit-replay disagreement).
|
||
- Fork-choice rule (which of two competing finalizations wins).
|
||
- Mesh wire format extensions for validator gossip.
|
||
- Stake mechanics + economic incentives.
|
||
- Cross-validator ZK proof exchange.
|
||
|
||
These are commissioned by the **v8 paper itself** — `docs/merkle-agi-v8-consensus.rst`, still open under ticket #000012. Phase 1a's
|
||
scoring function is the substrate the v8 paper consumes; landing
|
||
it now lets the paper cite measured values instead of stipulated
|
||
ones.
|
||
|
||
## 7. Closure of the gap fox identified
|
||
|
||
The 2026-05-08 review of `fbd99a8` flagged: *"the immediate
|
||
frontier has shifted from 'build the fitness surface' to 'define
|
||
how an organism mutation becomes canonical under that fitness
|
||
surface.'"*
|
||
|
||
Phase 1a closes the **scoring** half of that frontier. The
|
||
**canonicalization** half (validator agreement, fork choice,
|
||
slashing) remains under #000012 as the v8 paper deliverable.
|