Tasks: a vanished checkout is an answer, not an error
The last spec artifact (bugfix → design → testing plan → tasks). Derived from the approved
design.mdandtesting-plan.md.
Seven tasks, two independent red roots. The Python side (1 → 3 → 5) and the UI side (2 → 4) do not touch each other's files and can be worked in either order; they meet at task 6. tdd.mode: standard holds throughout — every production change below is preceded by the test that motivates it, and the red run is captured as evidence before any of it is written.
Task list
[x] 1. Write the Python tests for the new behaviour, and watch them fail
- Rewrite
test_check_malformed_repo_never_reaches_the_graph(cli/tests/test_core_graphs.py:37) from "raisesValueError" to "returnsrepoResolved: False,nodes: [],currentNode: "", and constructs no runtime" — the last part by monkeypatchinggraphs._runtimeto raise if called. - Rewrite
test_graph_check_rejects_a_bad_repo_path(cli/tests/test_api_routers_integration.py:85) to expect200withrepoResolved: false, under a Gherkin docstring namingScenario: a control-plane client asks where a work item stands in a checkout that has been deleted. Add its sibling for a checkout that is still there (Scenario: a control-plane client asks about a checkout that is still there), asserting the response carries norepoResolvedkey. - Add
test_repo_resolves_agrees_with_resolve_repocovering directory, regular file and missing path — the shared predicate, on the same three casestest_resolve_repo_rejects_non_directoryalready uses. - Add the abuse-case negative test: the unknown-position body contains no substring of the supplied path and no filesystem string.
- Run them against unfixed code and commit the failing output as
evidence/red.md. - Depends on: none
- Requirements: R4.1, R4.2
- Test:
T0 — uv run pytest cli/tests/test_core_graphs.py cli/tests/test_api_routers_integration.py(red)
- Rewrite
[x] 2. Write the UI test for the dropped answer, and watch it fail
- Export
fetchGraphsfromui/src/state/useControlPlane.ts. It is module-private today and the test needs it addressable. This is a production change the design did not name — recorded here rather than done quietly; see § Deviations. - Add
ui/src/state/useControlPlane.test.ts: given a stubTheLoopApiwhosegraphCheckresolves{…, repoResolved: false},fetchGraphsreturns{outer: {}, inner: {}}; given a normal answer, the report is stored under the ref. - Run against unfixed code and append the failing output to
evidence/red.md. - Depends on: none
- Requirements: R2.1, R4.1
- Test:
T0 — cd ui && bun run test(red)
- Export
[x] 3. Answer instead of raising, in
checkalonecli/the_loop/core/graphs.py: addrepo_resolves(repo) -> booland makeresolve_repocall it, so the predicate exists once.checkreturns the unknown-position dict whenrepo_resolvesis false, before_runtime(...)— the ordering is what makes R3.2 structural rather than incidental.- Docstring records why only
checkbehaves this way, perdesign.md§ Trade-offs. - Touch no other verb:
complete,advance,forceandskipkeep theValueError. - Depends on: 1
- Requirements: R1.1, R1.2, R1.3, R2.2, R3.1, R3.2
- Test:
T1, T3, T9 — uv run pytest cli/tests/test_core_graphs.py cli/tests/test_api_routers_integration.py(green)
[x] 4. Drop the unknown answer client-side, exactly as the rejection was dropped
ui/src/api/types.ts: addrepoResolved?: booleantoGraphStatus, documented as present-and-false only.ui/src/state/useControlPlane.ts: in the worker,if (status.repoResolved === false) continue;before storing. Compare tofalseexplicitly — absent must not be read as falsy.- Leave the
catchbelow it alone; it still covers an unreachable service and an aborted poll. - Depends on: 2
- Requirements: R2.1
- Test:
T2 — cd ui && bun run test(green)
[x] 5. Say it in the contract
docs/api-specs/openapi/the-loop.v1.yaml: give thegraphCheckoperation adescriptionstating that arepowhich does not resolve is answered200withrepoResolved: false, reserving4xxfor a malformed request.- Response schemas are untouched, so the parity assertion (paths × methods × operationIds) is unaffected — confirm rather than assume.
- Depends on: 3
- Requirements: R3.3
- Test:
T4 — uv run pytest cli/tests/test_api_contract_parity.py
[x] 6. Update the docs the change makes wrong
docs/capabilities/control-plane.md: extend thegraph/checkbehaviour bullet (line ~104) — an item whosecwdno longer resolves is answered rather than refused — and add the issue-238 history row.- Check
README.mdand the documentation site for any statement this change falsifies; record the finding either way in the execution log's## Documentationsection, with the reason if nothing changed. - Depends on: 3, 4, 5
- Requirements: none directly — the ready-to-ship gate (
reference/workflow.md) - Test:
npx markdownlint-cli2@0.18.1 <changed files>
[x] 7. Run everything the way CI runs it, and commit the green evidence
uv run pre-commit run --all-files,uv run pytest, andcd ui && bun run lint && bun run test && bun run build— the same commands.github/workflows/ci.ymlruns.- Commit the output as
evidence/unit-and-integration.mdandevidence/ui-tests.md, redacted pertesting-plan.md§ Evidence plan. - Depends on: 3, 4, 5, 6
- Requirements: R4.1, R4.2
- Test: full suite (green), paired with
evidence/red.mdfrom tasks 1–2
Dependency graph (DAG)
flowchart LR
T1["1 · red: python tests"] --> T3["3 · check answers<br/>instead of raising"]
T2["2 · red: ui test"] --> T4["4 · ui drops the<br/>unknown answer"]
T3 --> T5["5 · openapi description"]
T3 --> T6["6 · capability + user docs"]
T4 --> T6
T5 --> T6
T6 --> T7["7 · full suite + green evidence"]Tasks 1 and 2 are independent roots; 3 and 4 are independent of each other. Everything converges at 6 so the docs describe the finished behaviour rather than a half of it.
Deviations from the approved design
One, recorded here because the design gate has already closed:
- Task 2 exports
fetchGraphs.design.md§ Testing strategy says the UI assertion runs againstfetchGraphs, but the function is module-private today, so the export is needed to address it. The alternative — driving it throughuseControlPlanewithrenderHook— tests the polling effect, the abort wiring and the reducer as well, which makes a failure ambiguous about which of them broke. Exporting a pure function(api, workItems, sessions, signal) => GraphReportsis the smaller instrument. Flagged on the PR at the next gate; it is an export, not a behaviour change.
Checkpoints
After task 1 and task 2 (the red roots): evidence/red.md exists and shows the new assertions failing against unfixed code. This is the checkpoint the whole plan rests on — if it is skipped, "the test was rewritten" has no counter-evidence.
After task 3 and after task 4: their suites green, execution log appended, context compacted per contextManagement.taskBoundary.
After task 7: the full suite green under CI's own commands.
Then the verification node executes testing-plan.md — including T12, the manual curl and devtools-console check that reproduces the ticket's symptom — ticking each activity and recording its command, outcome and committed evidence. Only then do the review phases run the self and critic rounds and the security review gate (security.review), recorded in the execution log, before the work item can be marked ready.
Review comments
Appended by the-loop's
record-feedbackhook when a human gate approves with comments (issue-109). Append-only and attributed.