test(v1-gate): the attestation's date, form and writer, as red tests — a future date, a +14:00 offset and a duplicated key all read green
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
1b48124a01
commit
cd6fb4302e
1 changed files with 218 additions and 13 deletions
|
|
@ -18,6 +18,7 @@ import os
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
from datetime import datetime, timezone
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
|
@ -841,19 +842,223 @@ def test_m7_the_attestation_file_is_pinned() -> None:
|
||||||
|
|
||||||
def test_m7_the_gate_never_writes_an_attestation(tmp_path: Path) -> None:
|
def test_m7_the_gate_never_writes_an_attestation(tmp_path: Path) -> None:
|
||||||
"""The file is the operator's word. A product that can produce one has produced a witness to
|
"""The file is the operator's word. A product that can produce one has produced a witness to
|
||||||
its own run, which is the whole thing rows 1-2 cannot do: so no source file in the package
|
its own run, which is the whole thing rows 1-2 cannot do. Measured as BEHAVIOUR for the whole
|
||||||
writes ``ATTEST_FILE``, and scoring a tree leaves none behind."""
|
package (18.09, PM): every entry point runs against an unattested but otherwise consistent
|
||||||
root = _unattested(_green_rounds(tmp_path))
|
round directory, and none may leave an attestation behind. A grep for the file name reads a
|
||||||
gate.score_rounds(root, 3, _AI)
|
name, and a writer called from ``render`` or the command line slipped past it."""
|
||||||
gate.score_changes(root, 3, _AI)
|
root = _unattested(_green_rounds(tmp_path / "rounds"))
|
||||||
assert list(root.rglob(_ATTEST_FILE)) == []
|
config = json.loads(json.dumps(_CONFIG))
|
||||||
writes = [
|
config["maf_points"]["points"] = _one_point([6])["points"]
|
||||||
f"{path.name}:{i}"
|
entries: dict[str, Any] = {
|
||||||
for path in sorted((_REPO / "src" / "portfolio_optimiser").rglob("*.py"))
|
"score_rounds": lambda: gate.score_rounds(root, 3, _AI),
|
||||||
for i, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1)
|
"score_changes": lambda: gate.score_changes(root, 3, _AI),
|
||||||
if _ATTEST_FILE in line and "write_text" in line
|
"score_kept": lambda: gate.score_kept(root, 0.8, _AI),
|
||||||
]
|
"read_attestation": lambda: [gate.read_attestation(root / str(n)) for n in range(4)],
|
||||||
assert writes == []
|
"evaluate+render": lambda: gate.render(
|
||||||
|
gate.evaluate(
|
||||||
|
rounds_dir=root,
|
||||||
|
config=config,
|
||||||
|
repo_root=_REPO,
|
||||||
|
src=_synthetic_src(tmp_path / "src"),
|
||||||
|
probe_runner=_all_pass,
|
||||||
|
stress_measure=_CLEAN,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
for name, entry in entries.items():
|
||||||
|
entry()
|
||||||
|
assert list(root.rglob(_ATTEST_FILE)) == [], name
|
||||||
|
for flags in ((), ("--json",)):
|
||||||
|
proc = _cli("--rounds-dir", str(root), *flags)
|
||||||
|
assert proc.returncode == 1, proc.stderr
|
||||||
|
assert list(root.rglob(_ATTEST_FILE)) == [], ("cli", flags)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------------------------
|
||||||
|
# M-8 — what the attestation's DATE and FORM may say (PM checkpoint 18.09 of 1b48124)
|
||||||
|
# ---------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
_NOW = datetime(2026, 5, 29, 12, 0, tzinfo=timezone.utc)
|
||||||
|
|
||||||
|
|
||||||
|
def _both_rows(root: Path, now: datetime | None = None) -> list[gate.Row]:
|
||||||
|
return [gate.score_rounds(root, 3, _AI, now=now), gate.score_changes(root, 3, _AI, now=now)]
|
||||||
|
|
||||||
|
|
||||||
|
def test_m8_the_baseline_stays_green_on_the_injected_clock(tmp_path: Path) -> None:
|
||||||
|
"""Positive control for everything below: the same tree, the clock on the fixture day."""
|
||||||
|
rows = _both_rows(_green_rounds(tmp_path), _NOW)
|
||||||
|
assert [(r.k, r.status) for r in rows] == [(3, gate.GREEN)] * 2, rows
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"given",
|
||||||
|
["2026-05-30", "3000-01-01", "2026-05-29T23:00:00-05:00"],
|
||||||
|
)
|
||||||
|
def test_m8_an_attestation_dated_in_the_future_is_red(tmp_path: Path, given: str) -> None:
|
||||||
|
"""Finding 1: ``dato: 3000-01-01`` on every round read GREEN — the check only asked whether
|
||||||
|
the date was before the run. The last case is a future INSTANT written in a west-of-UTC
|
||||||
|
offset, so its wall-clock date is still today."""
|
||||||
|
root = _green_rounds(tmp_path)
|
||||||
|
_attest(root / "1", on=given)
|
||||||
|
for row in _both_rows(root, _NOW):
|
||||||
|
assert (row.k, row.status) == (2, gate.RED), (given, row.exceptions)
|
||||||
|
assert any("i framtiden" in x for x in row.exceptions), row.exceptions
|
||||||
|
|
||||||
|
|
||||||
|
def test_m8_the_clock_is_a_parameter_and_the_default_is_the_real_one(tmp_path: Path) -> None:
|
||||||
|
root = _green_rounds(tmp_path)
|
||||||
|
_attest(root / "1", on="3000-01-01")
|
||||||
|
assert gate.score_rounds(root, 3, _AI).status == gate.RED # the real clock, no injection
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"given",
|
||||||
|
["2026-05-29T00:00:00+14:00", "2026-05-28T10:00:00+00:00", "2026-05-28T20:26:00+00:00"],
|
||||||
|
)
|
||||||
|
def test_m8_a_timezone_cannot_date_an_attestation_before_the_run(
|
||||||
|
tmp_path: Path, given: str
|
||||||
|
) -> None:
|
||||||
|
"""Finding 2: ``2026-05-29T00:00:00+14:00`` is 2026-05-28T10:00Z, ten hours BEFORE the run
|
||||||
|
(20:26Z), and read GREEN because only the wall-clock date was compared. A date that carries a
|
||||||
|
time is compared as the instant it is."""
|
||||||
|
root = _green_rounds(tmp_path)
|
||||||
|
_attest(root / "1", on=given)
|
||||||
|
for row in _both_rows(root, _NOW):
|
||||||
|
assert (row.k, row.status) == (2, gate.RED), (given, row.exceptions)
|
||||||
|
assert any("før kjøringen" in x for x in row.exceptions), row.exceptions
|
||||||
|
|
||||||
|
|
||||||
|
def test_m8_a_time_without_an_offset_is_ambiguous_and_red(tmp_path: Path) -> None:
|
||||||
|
root = _green_rounds(tmp_path)
|
||||||
|
_attest(root / "1", on="2026-05-29T10:00:00")
|
||||||
|
row = gate.score_rounds(root, 3, _AI, now=_NOW)
|
||||||
|
assert (row.k, row.status) == (2, gate.RED), row.exceptions
|
||||||
|
assert any("tidssone" in x for x in row.exceptions), row.exceptions
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("given", ["2026-05-29T08:00:00+02:00", "2026-05-29T00:00:00+00:00"])
|
||||||
|
def test_m8_an_instant_after_the_run_and_before_now_is_green(tmp_path: Path, given: str) -> None:
|
||||||
|
root = _green_rounds(tmp_path)
|
||||||
|
_attest(root / "1", on=given)
|
||||||
|
for row in _both_rows(root, _NOW):
|
||||||
|
assert (row.k, row.status) == (3, gate.GREEN), (given, row.exceptions)
|
||||||
|
|
||||||
|
|
||||||
|
def test_m8_a_plain_date_keeps_the_same_day_rule(tmp_path: Path) -> None:
|
||||||
|
"""Same day as the run, and same day as ``now``, cannot be told apart from a date alone — so
|
||||||
|
it passes, and the contract text says so."""
|
||||||
|
root = _green_rounds(tmp_path)
|
||||||
|
_attest(root / "1", on="2026-05-28") # the run's own day
|
||||||
|
assert gate.score_rounds(root, 3, _AI, now=_NOW).status == gate.GREEN
|
||||||
|
_attest(root / "1", on="2026-05-29") # today
|
||||||
|
assert gate.score_rounds(root, 3, _AI, now=_NOW).status == gate.GREEN
|
||||||
|
assert "samme dag" in gate.ATTEST_RULE
|
||||||
|
|
||||||
|
|
||||||
|
_BODY = {"runde": "runde: 1", "kjøring": "kjøring: r1", "dato": "dato: 2026-05-29"}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("key", ["runde", "kjøring", "dato"])
|
||||||
|
@pytest.mark.parametrize("first_is_right", [True, False])
|
||||||
|
def test_m8_a_key_written_twice_is_red_in_either_order(
|
||||||
|
tmp_path: Path, key: str, first_is_right: bool
|
||||||
|
) -> None:
|
||||||
|
"""Finding 3: ``runde: 1`` then ``runde: 2`` was GREEN and the reverse RED — first-wins by
|
||||||
|
accident, and a last-wins mutant survived the whole suite. Two answers to one question are
|
||||||
|
not an answer, whichever comes first."""
|
||||||
|
root = _green_rounds(tmp_path)
|
||||||
|
wrong = {"runde": "runde: 2", "kjøring": "kjøring: r9", "dato": "dato: 2020-01-01"}[key]
|
||||||
|
pair = [_BODY[key], wrong] if first_is_right else [wrong, _BODY[key]]
|
||||||
|
lines = [x for k, x in _BODY.items() if k != key] + pair
|
||||||
|
_write(root / "1" / _ATTEST_FILE, "\n".join(lines) + "\n")
|
||||||
|
for row in _both_rows(root, _NOW):
|
||||||
|
assert (row.k, row.status) == (2, gate.RED), row.exceptions
|
||||||
|
assert any("to ganger" in x for x in row.exceptions), row.exceptions
|
||||||
|
|
||||||
|
|
||||||
|
def test_m8_even_the_same_value_twice_is_red(tmp_path: Path) -> None:
|
||||||
|
root = _green_rounds(tmp_path)
|
||||||
|
_write(root / "1" / _ATTEST_FILE, "\n".join([*_BODY.values(), _BODY["runde"]]) + "\n")
|
||||||
|
assert gate.score_rounds(root, 3, _AI, now=_NOW).status == gate.RED
|
||||||
|
|
||||||
|
|
||||||
|
def test_m8_a_byte_order_mark_is_tolerated(tmp_path: Path) -> None:
|
||||||
|
"""Finding 5a: a real file from an ordinary editor may start with a BOM, which made the first
|
||||||
|
key read as a different word and the round RED. The person is not wrong; the gate tolerates
|
||||||
|
it."""
|
||||||
|
root = _green_rounds(tmp_path)
|
||||||
|
text = "\n".join(_BODY.values()) + "\n"
|
||||||
|
(root / "1" / _ATTEST_FILE).write_bytes(b"\xef\xbb\xbf" + text.encode("utf-8"))
|
||||||
|
for row in _both_rows(root, _NOW):
|
||||||
|
assert (row.k, row.status) == (3, gate.GREEN), row.exceptions
|
||||||
|
|
||||||
|
|
||||||
|
def test_m8_an_attestation_that_is_a_directory_is_red_not_missing(tmp_path: Path) -> None:
|
||||||
|
"""Finding 5b: a directory named like the file is something PUT there — not the same as
|
||||||
|
nobody having confirmed yet."""
|
||||||
|
root = _green_rounds(tmp_path)
|
||||||
|
(root / "1" / _ATTEST_FILE).unlink()
|
||||||
|
(root / "1" / _ATTEST_FILE).mkdir()
|
||||||
|
for row in _both_rows(root, _NOW):
|
||||||
|
assert (row.k, row.status) == (2, gate.RED), row.exceptions
|
||||||
|
assert any("vanlig fil" in x for x in row.exceptions), row.exceptions
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("target", ["valid", "dangling"])
|
||||||
|
def test_m8_an_attestation_that_is_a_symlink_is_red(tmp_path: Path, target: str) -> None:
|
||||||
|
"""Finding 5c: F4 refuses a linked outbox, and ``is_file()`` followed the same link here. What
|
||||||
|
a forger gains is nothing (the content binds), but the asymmetry was unmeasured."""
|
||||||
|
root = _green_rounds(tmp_path / "rounds")
|
||||||
|
elsewhere = tmp_path / "elsewhere.txt"
|
||||||
|
if target == "valid":
|
||||||
|
elsewhere.write_text("\n".join(_BODY.values()) + "\n", encoding="utf-8")
|
||||||
|
(root / "1" / _ATTEST_FILE).unlink()
|
||||||
|
(root / "1" / _ATTEST_FILE).symlink_to(elsewhere)
|
||||||
|
for row in _both_rows(root, _NOW):
|
||||||
|
assert (row.k, row.status) == (2, gate.RED), (target, row.exceptions)
|
||||||
|
assert any("lenke" in x for x in row.exceptions), row.exceptions
|
||||||
|
|
||||||
|
|
||||||
|
def test_m8_a_hard_link_is_a_declared_limit_not_a_rule(tmp_path: Path) -> None:
|
||||||
|
"""Chosen 18.09: a hard link is NOT refused, and the output says so. Nothing in the tree marks
|
||||||
|
where it points, ``cp -l`` and some backup tools make them innocently, and refusing would gain
|
||||||
|
nothing — the attestation's content binds round, run and date either way."""
|
||||||
|
root = _green_rounds(tmp_path / "rounds")
|
||||||
|
outside = tmp_path / "outside.txt"
|
||||||
|
outside.write_text("\n".join(_BODY.values()) + "\n", encoding="utf-8")
|
||||||
|
(root / "1" / _ATTEST_FILE).unlink()
|
||||||
|
os.link(outside, root / "1" / _ATTEST_FILE)
|
||||||
|
assert gate.score_rounds(root, 3, _AI, now=_NOW).status == gate.GREEN
|
||||||
|
assert "hardlenke" in gate.ATTEST_RULE
|
||||||
|
|
||||||
|
|
||||||
|
def test_m8_an_attestation_that_is_not_utf8_is_red_because_it_is_unreadable(
|
||||||
|
tmp_path: Path,
|
||||||
|
) -> None:
|
||||||
|
"""Finding 6: the ``except`` arm on the read was uncovered — a whole-file garbage input goes
|
||||||
|
RED anyway through the missing keys, so a lenient decode (``errors="ignore"``) survived. Here
|
||||||
|
the three keyed lines are perfect and ONE stray byte follows: only strictness can refuse it."""
|
||||||
|
root = _green_rounds(tmp_path)
|
||||||
|
good = ("\n".join(_BODY.values()) + "\n").encode("utf-8")
|
||||||
|
(root / "1" / _ATTEST_FILE).write_bytes(good + b"merknad \xff\xfe\n")
|
||||||
|
for row in _both_rows(root, _NOW):
|
||||||
|
assert (row.k, row.status) == (2, gate.RED), row.exceptions
|
||||||
|
assert any("uleselig" in x for x in row.exceptions), row.exceptions
|
||||||
|
|
||||||
|
|
||||||
|
def test_m8_the_output_says_the_stress_row_needs_the_untracked_scratchpad() -> None:
|
||||||
|
"""Finding 7: row 7's 1 of 20 stands on ``scratchpad/`` being present. Without it the row is
|
||||||
|
NOT MEASURED, and that dependency is a fact about the checkout, not about the product."""
|
||||||
|
assert "scratchpad" in gate.STRESS_DEPENDENCY and "IKKE MÅLT" in gate.STRESS_DEPENDENCY
|
||||||
|
rows = gate.evaluate(
|
||||||
|
rounds_dir=Path("/nonexistent"),
|
||||||
|
config=_CONFIG,
|
||||||
|
repo_root=_REPO,
|
||||||
|
probe_runner=_all_pass,
|
||||||
|
stress_measure=_CLEAN,
|
||||||
|
)
|
||||||
|
assert gate.STRESS_DEPENDENCY in gate.render(rows)
|
||||||
|
|
||||||
|
|
||||||
def _outcome_obj(rows: list[dict[str, Any]], removed: dict[str, set[str]] | None = None) -> Any:
|
def _outcome_obj(rows: list[dict[str, Any]], removed: dict[str, set[str]] | None = None) -> Any:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue