fan-out: ForkScore CLI · authorship warrant ladder · 5F Phase 1c
Three streams in one commit since they're independent and each is small. #000012 Phase 1b — ForkScore CLI surface ======================================== `arborist v8 score` already existed; this adds `--out` for JSON- artifact emission so CI / downstream graders / mesh peers can ingest without parsing stdout. New Makefile targets: - `make bench-fork-baseline` — pins current bench-suite output as the ForkScore parent (one-shot per iteration). - `make bench-fork-score` — runs bench-suite again, scores child vs pinned parent, writes bench/results/fork_score_report.json. Exit 1 on REJECT so CI can gate. `FORK_PARENT` / `FORK_CHILD` / `FORK_REPORT` env-vars override default paths. New regression test pins the --out contract: stdout and file are byte-identical artifacts; --out auto-creates parent directories. #000026 Phase 3 — authorship warrant ladder ============================================ Sidecar classifier in `arborist/qa/warrant_authorship.py`. Six tiers strongest-to-weakest: AUTHOR_PACKAGE_METADATA → AUTHOR_REPOSITORY_OWNER → AUTHOR_PAGE_BYLINE → AUTHOR_PRIMARY_PAGE_TITLE → AUTHOR_COPYRIGHT_FOOTER → AUTHOR_SECONDARY_SOURCE. Plus NO_AUTHORSHIP_SIGNAL when the question doesn't smell like authorship (sidecar stays quiet). Detector regexes for each tier: - Tier 1: `author = "X"` simple form + TOML inline-table `authors = [{ name = "X" }]` form (PEP 621). - Tier 2: github.com / gitlab.com / codeberg.org / bitbucket.org URL pattern. - Tier 3: "By NAME" / "Author: NAME" prose + <meta name="author">. Inline-flag regex keeps the prefix case-insensitive while the capitalized-name capture stays case-sensitive. - Tier 4: cited evidence is the entity's own primary page (host tokens overlap title + answer; third-party indexers like wikipedia.org explicitly excluded). - Tier 5: `© NAME` / `Copyright YYYY NAME` (the current `virt-back` warrant). - Tier 6: fall-through when authorship-shaped question hits cited evidence with no direct markers. Sidecar discipline: never enters proof path; never raises; returns dict with `tier`, `tier_rank` (1=strongest, 99=quiet), `signals`, `candidate_names`, `note`. 20 tests cover each tier + noise filtering + sidecar contract + tier-ordering (strongest wins when multiple fire). Wiring into `arborist inspect` sidecar output + audit-line render-tail is queued as a follow-up — sidecar itself ready. #000025 5F Phase 1c — fixture catalog expansion ================================================ Synthetic side of all five 5F sub-batteries expanded 10 → 30: function — varied claim_count, pointer_set, threshold cases falsification — 13 violation tags (WARRANT_MISSING, TITLE_MISMATCH, FORMAT_COLLAPSED, NO_EVIDENCE_POINTER, BARE_NAME_CLAIM, LAZY_ANCHOR_DEMOTED, etc.) + 7 fail cases feedback-loop — 10 chain templates × 2 cycles finetuning — 20 capability transitions across all 5S/5T/5F/5R sub-batteries + canonical math/logic formulate — 12 lattice shapes × 2 (with deliberate fail cases) 150/150 fixtures pass through `bench-5f-*` runners. test_session_integration.py total updated 462 → 562. Pinned test_5f_*_runs counts updated 10 → 30 across all assertions. Tests ===== Full suite: 1388 passed, 36 skipped (was 1367; +21 — 20 warrant tests + 1 ForkScore --out test).
This commit is contained in:
parent
62d9c7a440
commit
60b5748ff9
14 changed files with 981 additions and 24 deletions
|
|
@ -227,31 +227,32 @@ def test_5f_function_runs():
|
|||
res = b_5f.run_function(F5F / "function-v1.jsonl")
|
||||
assert res.battery == "5f"
|
||||
assert res.sub_battery == "function"
|
||||
assert res.pass_count == 10
|
||||
# Phase 1c (2026-05-09) — fixture catalog expanded 10 → 30.
|
||||
assert res.pass_count == 30
|
||||
assert res.metrics["function_pass_rate"] == 1.0
|
||||
|
||||
|
||||
def test_5f_finetuning_runs():
|
||||
res = b_5f.run_finetuning(F5F / "finetuning-v1.jsonl")
|
||||
assert res.pass_count == 10
|
||||
assert res.pass_count == 30
|
||||
assert res.metrics["adaptation_improvement_rate"] == 1.0
|
||||
|
||||
|
||||
def test_5f_falsification_runs():
|
||||
res = b_5f.run_falsification(F5F / "falsification-v1.jsonl")
|
||||
assert res.pass_count == 10
|
||||
assert res.pass_count == 30
|
||||
assert res.metrics["error_detection_rate"] == 1.0
|
||||
|
||||
|
||||
def test_5f_formulate_runs():
|
||||
res = b_5f.run_formulate(F5F / "formulate-v1.jsonl")
|
||||
assert res.pass_count == 10
|
||||
assert res.pass_count == 30
|
||||
assert res.metrics["structural_match_rate"] == 1.0
|
||||
|
||||
|
||||
def test_5f_feedback_loop_runs():
|
||||
res = b_5f.run_feedback_loop(F5F / "feedback-loop-v1.jsonl")
|
||||
assert res.pass_count == 10
|
||||
assert res.pass_count == 30
|
||||
assert res.metrics["integration_coverage_rate"] == 1.0
|
||||
|
||||
|
||||
|
|
@ -430,9 +431,10 @@ def test_5f_formulate_live_path_routes_through_parse_claims():
|
|||
|
||||
def test_5f_formulate_embedded_path_still_works():
|
||||
"""Phase 1a fixtures (embedded produced_lattice) keep working
|
||||
after the Phase 1b.2 wire-up. Backward compat invariant."""
|
||||
after the Phase 1b.2 wire-up + Phase 1c expansion. Backward
|
||||
compat invariant."""
|
||||
res = b_5f.run_formulate(F5F / "formulate-v1.jsonl")
|
||||
assert res.pass_count == 10
|
||||
assert res.pass_count == 30 # Phase 1c — expanded 10 → 30
|
||||
for t in res.per_task:
|
||||
assert t.detail["source"] == "embedded"
|
||||
|
||||
|
|
@ -485,7 +487,7 @@ def test_5f_feedback_loop_live_path_writes_real_audit_events():
|
|||
|
||||
def test_5f_feedback_loop_embedded_path_still_works():
|
||||
res = b_5f.run_feedback_loop(F5F / "feedback-loop-v1.jsonl")
|
||||
assert res.pass_count == 10
|
||||
assert res.pass_count == 30 # Phase 1c
|
||||
for t in res.per_task:
|
||||
assert t.detail["source"] == "embedded"
|
||||
|
||||
|
|
@ -558,7 +560,7 @@ def test_5f_function_live_path_routes_through_parse_claims():
|
|||
|
||||
def test_5f_function_embedded_path_still_works():
|
||||
res = b_5f.run_function(F5F / "function-v1.jsonl")
|
||||
assert res.pass_count == 10
|
||||
assert res.pass_count == 30 # Phase 1c
|
||||
for t in res.per_task:
|
||||
assert t.detail["source"] == "embedded"
|
||||
|
||||
|
|
@ -582,7 +584,7 @@ def test_5f_finetuning_live_path_round_trips_selfmodel():
|
|||
|
||||
def test_5f_finetuning_embedded_path_still_works():
|
||||
res = b_5f.run_finetuning(F5F / "finetuning-v1.jsonl")
|
||||
assert res.pass_count == 10
|
||||
assert res.pass_count == 30 # Phase 1c
|
||||
for t in res.per_task:
|
||||
assert t.detail["source"] == "embedded"
|
||||
|
||||
|
|
@ -616,7 +618,7 @@ def test_5f_falsification_live_path_routes_through_verify_quotes():
|
|||
|
||||
def test_5f_falsification_embedded_path_still_works():
|
||||
res = b_5f.run_falsification(F5F / "falsification-v1.jsonl")
|
||||
assert res.pass_count == 10
|
||||
assert res.pass_count == 30 # Phase 1c
|
||||
for t in res.per_task:
|
||||
assert t.detail["source"] == "embedded"
|
||||
|
||||
|
|
|
|||
|
|
@ -239,15 +239,21 @@ def test_full_dav1dprometheus_suite_runs_end_to_end(tmp_path, capsys):
|
|||
|
||||
|
||||
def test_full_suite_total_fixture_count():
|
||||
"""Sanity check: the complete Dav1DPrometheus suite executes 462
|
||||
deterministic tasks across 21 sub-batteries (5S+5T+5F+5R)."""
|
||||
"""Sanity check: the complete Dav1DPrometheus suite executes 562
|
||||
deterministic tasks across 21 sub-batteries (5S+5T+5F+5R).
|
||||
|
||||
History:
|
||||
- Phase 1a baseline: 462 tasks.
|
||||
- Phase 1c (#000025, 2026-05-09): 5F synthetic side expanded
|
||||
10 → 30 across all 5 sub-batteries; +100 → 562.
|
||||
"""
|
||||
from bench.batteries.runner import _DEFAULT_FIXTURES, _run_one
|
||||
|
||||
total = 0
|
||||
for (battery, sub), fx in _DEFAULT_FIXTURES.items():
|
||||
result = _run_one(battery, sub, Path(fx))
|
||||
total += result.pass_count + result.fail_count
|
||||
assert total == 462
|
||||
assert total == 562
|
||||
|
||||
|
||||
def test_5s_phase1a_digests_unchanged_after_phase1b():
|
||||
|
|
|
|||
|
|
@ -449,6 +449,39 @@ def test_cli_v8_score_rejects_returns_nonzero(tmp_path, capsys):
|
|||
assert payload["verdict"] == "REJECT"
|
||||
|
||||
|
||||
def test_cli_v8_score_out_writes_json_artifact(tmp_path, capsys):
|
||||
"""#000012 Phase 1b — `--out` mirrors stdout to a file so CI /
|
||||
mesh peers / downstream graders can ingest the artifact without
|
||||
parsing pipe output."""
|
||||
from arborist.cli import build_parser
|
||||
|
||||
parent_path = tmp_path / "parent.json"
|
||||
child_path = tmp_path / "child.json"
|
||||
out_path = tmp_path / "results" / "fork_score.json" # parent dir missing on purpose
|
||||
_write_bench_result(parent_path, {
|
||||
"5s": {"syntax": {"parse_pass_rate": 0.80}},
|
||||
"5t": {}, "5f": {},
|
||||
})
|
||||
_write_bench_result(child_path, {
|
||||
"5s": {"syntax": {"parse_pass_rate": 0.85}},
|
||||
"5t": {}, "5f": {},
|
||||
})
|
||||
parser = build_parser()
|
||||
args = parser.parse_args([
|
||||
"v8", "score",
|
||||
"--parent", str(parent_path),
|
||||
"--child", str(child_path),
|
||||
"--out", str(out_path),
|
||||
])
|
||||
args.func(args)
|
||||
stdout_payload = json.loads(capsys.readouterr().out)
|
||||
assert out_path.is_file(), "--out should create parent directories"
|
||||
file_payload = json.loads(out_path.read_text(encoding="utf-8"))
|
||||
# Stdout and file are byte-identical artifacts.
|
||||
assert stdout_payload == file_payload
|
||||
assert "verdict" in file_payload
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# Determinism
|
||||
# ---------------------------------------------------------------------
|
||||
|
|
|
|||
273
tests/test_warrant_authorship.py
Normal file
273
tests/test_warrant_authorship.py
Normal file
|
|
@ -0,0 +1,273 @@
|
|||
"""Tests for the authorship warrant ladder (#000026 Phase 3)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from arborist.qa.warrant_authorship import (
|
||||
NO_AUTHORSHIP_SIGNAL,
|
||||
TIER_RANK,
|
||||
diagnose_authorship_warrant,
|
||||
)
|
||||
|
||||
|
||||
# ---------- Question gating ------------------------------------------------
|
||||
|
||||
|
||||
def test_non_authorship_question_returns_no_signal():
|
||||
"""Sidecar should stay quiet on irrelevant questions."""
|
||||
out = diagnose_authorship_warrant(
|
||||
question_text="what is the capital of france?",
|
||||
answer_text="Paris.",
|
||||
cited_evidence_spans=["© 2024 Wikipedia Foundation"],
|
||||
)
|
||||
assert out["tier"] == NO_AUTHORSHIP_SIGNAL
|
||||
assert out["tier_rank"] == 99
|
||||
|
||||
|
||||
def test_no_evidence_no_question_returns_no_signal():
|
||||
out = diagnose_authorship_warrant()
|
||||
assert out["tier"] == NO_AUTHORSHIP_SIGNAL
|
||||
|
||||
|
||||
def test_no_evidence_with_authorship_question_returns_no_signal():
|
||||
"""Authorship question but zero cited evidence — sidecar stays
|
||||
quiet rather than reporting tier-6 secondary on no evidence."""
|
||||
out = diagnose_authorship_warrant(
|
||||
question_text="who wrote virt-back?",
|
||||
answer_text="Russell Ballestrini.",
|
||||
)
|
||||
assert out["tier"] == NO_AUTHORSHIP_SIGNAL
|
||||
|
||||
|
||||
# ---------- Tier 1 — package metadata --------------------------------------
|
||||
|
||||
|
||||
def test_tier1_python_pyproject_author():
|
||||
out = diagnose_authorship_warrant(
|
||||
question_text="who wrote arborist?",
|
||||
answer_text="Russell Ballestrini wrote arborist.",
|
||||
cited_evidence_spans=[
|
||||
'authors = [\n { name = "Russell Ballestrini" },\n]'
|
||||
],
|
||||
)
|
||||
assert out["tier"] == "AUTHOR_PACKAGE_METADATA"
|
||||
assert out["tier_rank"] == 1
|
||||
assert "Russell Ballestrini" in out["candidate_names"]
|
||||
|
||||
|
||||
def test_tier1_python_setup_py_author():
|
||||
out = diagnose_authorship_warrant(
|
||||
question_text="who is the author of virt-back?",
|
||||
answer_text="Russell Ballestrini.",
|
||||
cited_evidence_spans=['author="Russell Ballestrini"'],
|
||||
)
|
||||
assert out["tier"] == "AUTHOR_PACKAGE_METADATA"
|
||||
|
||||
|
||||
def test_tier1_package_json_author():
|
||||
out = diagnose_authorship_warrant(
|
||||
question_text="who created this library?",
|
||||
answer_text="Foo Bar.",
|
||||
cited_evidence_spans=['{"name":"my-pkg","author":"Foo Bar"}'],
|
||||
)
|
||||
assert out["tier"] == "AUTHOR_PACKAGE_METADATA"
|
||||
|
||||
|
||||
# ---------- Tier 2 — repository owner --------------------------------------
|
||||
|
||||
|
||||
def test_tier2_github_uri_owner():
|
||||
out = diagnose_authorship_warrant(
|
||||
question_text="who maintains this?",
|
||||
answer_text="The repository is owned by russellballestrini.",
|
||||
cited_source_uris=["https://github.com/russellballestrini/virt-back"],
|
||||
cited_evidence_spans=["A README about the project."],
|
||||
)
|
||||
assert out["tier"] == "AUTHOR_REPOSITORY_OWNER"
|
||||
assert "russellballestrini" in out["candidate_names"]
|
||||
|
||||
|
||||
def test_tier2_gitlab_uri_owner():
|
||||
out = diagnose_authorship_warrant(
|
||||
question_text="who owns this project?",
|
||||
answer_text="The team-arborist group.",
|
||||
cited_source_uris=[
|
||||
"https://gitlab.com/team-arborist/some-project",
|
||||
],
|
||||
cited_evidence_spans=["x"],
|
||||
)
|
||||
assert out["tier"] == "AUTHOR_REPOSITORY_OWNER"
|
||||
|
||||
|
||||
# ---------- Tier 3 — page byline -------------------------------------------
|
||||
|
||||
|
||||
def test_tier3_byline_prose_by_pattern():
|
||||
out = diagnose_authorship_warrant(
|
||||
question_text="who wrote this article?",
|
||||
answer_text="Russell Ballestrini.",
|
||||
cited_evidence_spans=["By Russell Ballestrini, posted 2024-01-01."],
|
||||
)
|
||||
assert out["tier"] == "AUTHOR_PAGE_BYLINE"
|
||||
|
||||
|
||||
def test_tier3_byline_meta_html():
|
||||
out = diagnose_authorship_warrant(
|
||||
question_text="who is the author of this page?",
|
||||
answer_text="Russell Ballestrini.",
|
||||
cited_evidence_spans=[
|
||||
'<meta name="author" content="Russell Ballestrini">'
|
||||
],
|
||||
)
|
||||
assert out["tier"] == "AUTHOR_PAGE_BYLINE"
|
||||
|
||||
|
||||
# ---------- Tier 4 — primary page title ------------------------------------
|
||||
|
||||
|
||||
def test_tier4_primary_page_title_match():
|
||||
"""Site host token appears in the cited title AND the answer
|
||||
references the same entity. The cited evidence is the entity's
|
||||
own primary page."""
|
||||
out = diagnose_authorship_warrant(
|
||||
question_text="who wrote virt-back?",
|
||||
answer_text="russellballestrini wrote virt-back.",
|
||||
cited_source_uris=[
|
||||
"https://russellballestrini.net/virt-back-restoring-from-backups/",
|
||||
],
|
||||
cited_source_titles=["virt-back: restoring from backups – Russell Ballestrini"],
|
||||
cited_evidence_spans=["A blog post about virt-back."],
|
||||
)
|
||||
# Should fire at tier 4 absent stronger signals (no package
|
||||
# metadata, no repo URL, no byline, no copyright).
|
||||
assert out["tier"] == "AUTHOR_PRIMARY_PAGE_TITLE"
|
||||
|
||||
|
||||
def test_tier4_third_party_host_does_not_fire():
|
||||
"""Wikipedia is a third-party indexer; primary-page heuristic
|
||||
must not fire even if tokens overlap."""
|
||||
out = diagnose_authorship_warrant(
|
||||
question_text="who wrote virt-back?",
|
||||
answer_text="russell wrote virt-back.",
|
||||
cited_source_uris=[
|
||||
"https://en.wikipedia.org/wiki/virt-back-russell"
|
||||
],
|
||||
cited_source_titles=["virt-back russell"],
|
||||
cited_evidence_spans=["x"],
|
||||
)
|
||||
# With NO other signals firing, falls through to secondary.
|
||||
assert out["tier"] == "AUTHOR_SECONDARY_SOURCE"
|
||||
|
||||
|
||||
# ---------- Tier 5 — copyright footer --------------------------------------
|
||||
|
||||
|
||||
def test_tier5_copyright_footer_with_year():
|
||||
out = diagnose_authorship_warrant(
|
||||
question_text="who wrote virt-back?",
|
||||
answer_text="Russell Ballestrini wrote virt-back.",
|
||||
cited_evidence_spans=["© 2024 Russell Ballestrini"],
|
||||
)
|
||||
assert out["tier"] == "AUTHOR_COPYRIGHT_FOOTER"
|
||||
|
||||
|
||||
def test_tier5_copyright_footer_no_year():
|
||||
out = diagnose_authorship_warrant(
|
||||
question_text="who wrote virt-back?",
|
||||
answer_text="Russell Ballestrini.",
|
||||
cited_evidence_spans=["© Russell Ballestrini"],
|
||||
)
|
||||
assert out["tier"] == "AUTHOR_COPYRIGHT_FOOTER"
|
||||
|
||||
|
||||
def test_tier5_copyright_word_form():
|
||||
out = diagnose_authorship_warrant(
|
||||
question_text="who created this site?",
|
||||
answer_text="Russell Ballestrini.",
|
||||
cited_evidence_spans=["Copyright 2024 Russell Ballestrini. All rights reserved."],
|
||||
)
|
||||
assert out["tier"] == "AUTHOR_COPYRIGHT_FOOTER"
|
||||
|
||||
|
||||
# ---------- Tier 6 — secondary source --------------------------------------
|
||||
|
||||
|
||||
def test_tier6_third_party_evidence_no_authorship_markers():
|
||||
"""Question is authorship; cited evidence has none of the direct
|
||||
markers — falls through to secondary."""
|
||||
out = diagnose_authorship_warrant(
|
||||
question_text="who wrote virt-back?",
|
||||
answer_text="Russell Ballestrini, according to the documentation.",
|
||||
cited_evidence_spans=[
|
||||
"virt-back is a tool for backing up libvirt VMs."
|
||||
],
|
||||
)
|
||||
assert out["tier"] == "AUTHOR_SECONDARY_SOURCE"
|
||||
assert out["tier_rank"] == TIER_RANK["AUTHOR_SECONDARY_SOURCE"]
|
||||
|
||||
|
||||
# ---------- Tier ordering: strongest wins ----------------------------------
|
||||
|
||||
|
||||
def test_strongest_tier_wins_when_multiple_fire():
|
||||
"""Both copyright AND package-metadata fire — the package-metadata
|
||||
signal is strictly stronger (tier 1 < tier 5)."""
|
||||
out = diagnose_authorship_warrant(
|
||||
question_text="who wrote virt-back?",
|
||||
answer_text="Russell Ballestrini.",
|
||||
cited_evidence_spans=[
|
||||
"© Russell Ballestrini",
|
||||
'author = "Russell Ballestrini"',
|
||||
],
|
||||
)
|
||||
assert out["tier"] == "AUTHOR_PACKAGE_METADATA"
|
||||
# But signals list captures BOTH for the operator.
|
||||
tiers_seen = {s["tier"] for s in out["signals"]}
|
||||
assert "AUTHOR_PACKAGE_METADATA" in tiers_seen
|
||||
assert "AUTHOR_COPYRIGHT_FOOTER" in tiers_seen
|
||||
|
||||
|
||||
# ---------- Sidecar contract -----------------------------------------------
|
||||
|
||||
|
||||
def test_returns_dict_always():
|
||||
"""Sidecar contract: never raises, always returns a dict."""
|
||||
out = diagnose_authorship_warrant(
|
||||
question_text=None,
|
||||
answer_text="",
|
||||
cited_evidence_spans=None,
|
||||
cited_source_uris=None,
|
||||
cited_source_titles=None,
|
||||
)
|
||||
assert isinstance(out, dict)
|
||||
assert "tier" in out
|
||||
assert "tier_rank" in out
|
||||
|
||||
|
||||
def test_does_not_raise_on_garbage_input():
|
||||
out = diagnose_authorship_warrant(
|
||||
question_text="who wrote x?",
|
||||
answer_text="\x00\x01" * 100,
|
||||
cited_evidence_spans=["", None, "<<<>>>"], # type: ignore
|
||||
)
|
||||
assert out["tier"] in (
|
||||
"NO_AUTHORSHIP_SIGNAL", "AUTHOR_SECONDARY_SOURCE",
|
||||
"AUTHOR_PACKAGE_METADATA", "AUTHOR_REPOSITORY_OWNER",
|
||||
"AUTHOR_PAGE_BYLINE", "AUTHOR_PRIMARY_PAGE_TITLE",
|
||||
"AUTHOR_COPYRIGHT_FOOTER",
|
||||
)
|
||||
|
||||
|
||||
def test_noise_capitalized_words_not_classified_as_names():
|
||||
"""`Author` / `Copyright` / etc. shouldn't slip through as names."""
|
||||
out = diagnose_authorship_warrant(
|
||||
question_text="who wrote x?",
|
||||
answer_text="x",
|
||||
cited_evidence_spans=[
|
||||
"Copyright Reserved", # should NOT classify "Reserved" as name
|
||||
"Author All Rights", # garbage shape
|
||||
],
|
||||
)
|
||||
# Even if regex matches, _looks_like_noise filters; tier may fall
|
||||
# to secondary or no-signal.
|
||||
assert "Reserved" not in out.get("candidate_names", [])
|
||||
assert "All" not in out.get("candidate_names", [])
|
||||
Loading…
Add table
Add a link
Reference in a new issue