Evidence — units (T1, T2, T4), abuse cases (T10) and migration (T12)
Work item: issue-199 · captured 2026-08-10 · no network, no credentials.
T1 / T2 / T10 — uv run --project cli python -m pytest -q cli/tests/test_graph_contribution.py
.............................................. [100%]
46 passed in 0.63sThe ten cases this work item added or relies on, by name:
tests/test_graph_contribution.py::test_the_contribution_checklist_offers_no_surface_row
tests/test_graph_contribution.py::test_the_outer_loops_checklist_still_asks_the_question
tests/test_graph_contribution.py::test_a_ticked_surface_row_in_a_contributions_reply_changes_nothing
tests/test_graph_contribution.py::test_the_arming_comment_reaches_the_goal_gate_at_spawn
tests/test_graph_contribution.py::test_a_spawn_with_no_goal_parks_the_gate_with_its_reason
tests/test_graph_contribution.py::test_a_respawn_re_evaluates_nothing
tests/test_graph_contribution.py::test_an_unauthorized_goal_is_not_read
tests/test_graph_contribution.py::test_a_self_authored_goal_is_not_read
tests/test_graph_contribution.py::test_an_outage_reads_as_no_goal_never_a_guess
tests/test_graph_contribution.py::TestContributionWalk::test_goal_then_selection_release_only_on_authorized_inputT2 — the spawn rows on their own
$ uv run --project cli python -m pytest -q cli/tests/test_graph_contribution.py -k spawn
.... [100%]
4 passed, 42 deselected in 0.14sT10 — the abuse cases
$ uv run --project cli python -m pytest -q cli/tests/test_graph_contribution.py \
-k "unauthorized or surface"
... [100%]
3 passed, 43 deselected in 0.09s- Abuse 1 (an unauthorized user answers the gate) —
test_an_unauthorized_goal_is_not_read, plustest_a_self_authored_goal_is_not_readfor the harness answering its own gate. Both pre-date this change and are re-run becauseon_spawnnow reaches the same gate: the new caller must not widen who may answer it. - Abuse 2 (an injected
outer-loop-on-pull-requestrow makes the-loop open a pull request) —test_a_ticked_surface_row_in_a_contributions_reply_changes_nothing: the pointer advances on the phase selection,state.surfacestays"", the frozen record carries"", no phase is declared away, and the confirmation says nothing about a surface. - Abuse 3 (a gate fault at spawn wedges the item) —
test_an_outage_reads_as_no_goal_never_a_guess: an integration that raises on every call leaves the gate waiting rather than guessing, andGraphLink._guardedswallows and records any fault above it.
T4 — uv run --project cli python -m pytest -q cli/tests/test_graph_drive.py
...................... [100%]
22 passed in 2.29sThree cases matter here:
tests/test_graph_drive.py::test_the_render_places_the_outer_loops_artifacts
tests/test_graph_drive.py::test_a_contribution_is_not_told_where_to_put_an_outer_loop
tests/test_graph_drive.py::test_a_spawn_never_evaluates_an_agent_start_nodeThe last is R2.4's guard: the fixture's graph starts at an agent node whose design.md is deliberately absent, so an evaluation at spawn would have blocked. It is entered, not evaluated — parked is None, no last_block, one attempt.
T12 — migration
No persisted schema changed. graph-state.json keeps every field it had, and surface keeps its type: a work item frozen by 9.6.0 with surface: "work-item" reads identically (test_state_loop_round_trips_and_predates_gracefully, and the state round-trip cases in test_graph_state.py, all part of T6). The only new value is the empty string, which every reader already treated as "unset" — Runtime._record_selected_skips writes state.surface only for a truthy value, and graphlink._surface_line("") has always rendered the default.