diff --git a/AGENTS.md b/AGENTS.md index 7e30043..f8d998d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -26,7 +26,7 @@ Nothing above the seam should ever need to change for a Beads upgrade. ## `doctor` is the gate, not a suggestion Run `amplifier-work-tracker doctor` after any `bd` upgrade and before -trusting parallel agents against a queue. It must report **34/34 +trusting parallel agents against a queue. It must report **36/36 assumptions hold**; anything less means Beads' behavior moved out from under an assumption we depend on (or, for `sweeps.alive`, that the reap/notify sweep loops have stopped completing sweeps, or, for @@ -45,7 +45,10 @@ misreported here as 26/26 while the CLI actually emitted 27 -- 23 from is the 5th service-level check added alongside this reconciliation, for 28 total. This branch's six `reopen`/`resolve` assumptions bring the merged total to 34, which is MEASURED from `doctor` on the merged tree -- -not 33+2 arithmetic, which would have said 35.) +not 33+2 arithmetic, which would have said 35. The two +`defer`/`block`.`refuses_resolved` assumptions -- the fence on the +destructive-reopen defect, `model_performance-2nx` -- then take it to +**36**, again MEASURED from `doctor`, not computed.) ## Test scope @@ -131,7 +134,7 @@ runs itself is how you lose data you meant to keep. ## What "done" looks like -Full suite green, `doctor` 34/34, `ruff check` / `ruff format --check` / +Full suite green, `doctor` 36/36, `ruff check` / `ruff format --check` / `pyright` clean. For any change to the bundle's zero-state install path (service bootstrap, `work_tracker_install`, prereqs), the acceptance gate is a fresh Digital Twin Universe run from a genuinely empty machine (no `bd`, diff --git a/docs/lanes/2nx-defer-block-refuse-resolved/DONE-NOTE.md b/docs/lanes/2nx-defer-block-refuse-resolved/DONE-NOTE.md new file mode 100644 index 0000000..9b34751 --- /dev/null +++ b/docs/lanes/2nx-defer-block-refuse-resolved/DONE-NOTE.md @@ -0,0 +1,239 @@ +# DONE-NOTE — `model_performance-2nx` + +**`defer`/`block` refuse a RESOLVED item instead of silently blanking its resolution.** + +- Lane: `lane/2nx-defer-block-refuse-resolved` +- Built on: `2468a69` (`origin/main` at claim time — the commit that shipped f5c's `reopen`) +- Spend: **$0.00** (authorized $0; no API, no DTU, no infrastructure created, no ledger row) +- bd: `1.1.2 (20e493e56)` · all measurements 2026-09-03, this host + +--- + +## What was measured + +### The defect, reproduced first-hand before touching any code + +`docs/lanes/2nx-defer-block-refuse-resolved/evidence/measurement-BEFORE.txt`, produced by +`measure_destructive_loop.sh` on a throwaway project created and destroyed through the +sanctioned CLI (`new` … `remove --yes`). Verbatim, exit codes included: + +``` +$ … resolve --id --reason "ORIGINAL TEXT" EXIT=0 resolution: "ORIGINAL TEXT" +$ … defer --id --reason probe EXIT=0 {"status": "deferred"} +$ … block --id --reason probe EXIT=0 {"status": "blocked"} +$ … list --id --json EXIT=0 status blocked | resolution: null +$ … block --id --clear EXIT=0 {"status": "open"} +$ … claim --id --actor probe EXIT=0 +$ … resolve --id --reason "CORRECTED TEXT …" EXIT=0 + FINAL: status resolved | resolution = CORRECTED TEXT | closed_at moved +``` + +Every step exit 0. The already-published resolution was destroyed at the **first** verb, with +no warning, no confirmation, no archive and no trace of what it used to say. + +### The same measurement after the fix + +`…/evidence/measurement-AFTER.txt`. The loop **stops at its first verb** and every later verb +finds nothing to work with: + +``` +$ … defer --id --reason probe EXIT=1 + refusing to defer : it is already resolved, and defer would move it out of + resolved and DESTROY the resolution stored on it. NOTHING WAS WRITTEN. + status: resolved + stored (unchanged): ORIGINAL TEXT + closed_at: 2026-09-03T07:08:55+00:00 + If you genuinely mean to reopen it, use `reopen` — it archives the resolution above + (and closed_at) into an attributed comment FIRST: + amplifier-work-tracker reopen --project … --id … --reason '' + (agents: work_reopen(project=…, item_id=…, reason=…)) +$ … block --id --reason probe EXIT=1 (same refusal) +$ … list --id --json EXIT=0 status resolved | resolution: "ORIGINAL TEXT" +$ … block --id --clear EXIT=1 cannot un-blocked …: it is 'resolved', not 'blocked' +$ … claim --id --actor probe EXIT=1 issue not claimable: status closed +$ … resolve --id --reason "CORRECTED TEXT …" EXIT=1 (f5c's divergent-text refusal) + FINAL: status resolved | resolution = ORIGINAL TEXT | closed_at UNMOVED +``` + +`closed_at` is byte-identical before and after the whole attempted loop — the record was not +merely restored, it was never touched. + +--- + +## The change + +| File | What | +|---|---| +| `src/amplifier_work_tracker/adapter.py` | `_status_change_on_resolved_error()` — the refusal message; `_STATUS_CHANGE_VERB`; a pre-write guard in `_set_status_with_reason`, the one path both `defer` and `block` go through. Docstrings on both verbs. | +| `src/amplifier_work_tracker/contract.py` | `defer.refuses_resolved`, `block.refuses_resolved` (+ shared `_refuses_resolved` body), registered in `CHECKS`. | +| `AGENTS.md` | doctor count 34 → **36**, in both places, MEASURED from `doctor` output. | +| `tests/integration/test_defer_block.py` | +6 tests (4 discriminating, plus the parametrised "unaffected" set). | +| `tests/cli/test_cli_new_verbs.py` | +1 parametrised test (exit code + record intact on the surface that shipped it). | +| `modules/tool-work-tracker/tests/test_work_defer_block.py` | +1 parametrised test (`success=False` on the agent-facing surface). | + +**Placed in `_set_status_with_reason` on purpose.** It is the single shared implementation of +both verbs, so there is exactly one guard and it cannot drift between them. The doctor +assumptions are nevertheless asserted **separately per verb**, so a future change that gives +`block` its own path cannot leave one door open while the other check keeps passing. + +**Deliberately tolerant of a read failure**, mirroring `resolve`'s own pre-write read: an item +that does not exist keeps surfacing through bd's own `update` failure exactly as before. This +guard must not newly re-diagnose "not found". + +**Checked BEFORE any write** — that ordering is what makes the refusal's own +"NOTHING WAS WRITTEN" literally true, and it is the same ordering `resolve` and `release` +already depend on. + +--- + +## Deliverables + +| Deliverable | Status | +|---|---| +| defer/block refuse a resolved item (fail non-zero, stays resolved, **resolution unchanged**, message names status + `reopen`) | **DONE** — all four properties asserted in one test, both verbs, tiers 2/3/modules | +| The destructive loop is closed end to end, before/after transcripts side by side | **DONE** — `evidence/measurement-{BEFORE,AFTER}.txt`, quoted above | +| The safe path still works (`reopen` still succeeds, still archives first) — proven in the same file | **DONE** — `test_reopen_still_succeeds_on_the_same_item_and_still_archives_first`, `tests/integration/test_defer_block.py` | +| Non-resolved items unaffected | **DONE** — 6 parametrised tests (open / held / already-deferred-or-blocked × defer/block), and they PASS at the parent commit too, which is the point | +| doctor assumptions `defer.refuses_resolved` / `block.refuses_resolved` against the live bd binary | **DONE** — both PASS; `doctor` now reports **36/36** | +| fail-before evidence | **DONE** — `evidence/fail-before-parent-2468a69.txt` | +| The false immutability claim corrected where it was made | **PARTIAL, with reasons** — see below | +| Draft PR, all four tiers + modules suite run and reported by name | **DONE** — see the PR body | +| This DONE-NOTE at the lane artifact root | **DONE** | + +--- + +## Fail-before evidence + +`evidence/fail-before-parent-2468a69.txt`. The three test files are the lane's new ones copied +verbatim onto a `git worktree` of parent `2468a69`; **only `src/` is the parent's**, pinned with +`PYTHONPATH=/src` and verified in the capture itself — +`import amplifier_work_tracker` resolves to `/tmp/2nx-parent/src/...`, not the lane worktree. +Without that pin the editable install silently resolves the FIXED source and everything passes. + +``` +tier 2 (integration) 4 failed, 17 passed ← the 4 discriminating tests +tier 3 (cli) 2 failed, 8 passed +modules 2 failed, 7 passed +doctor assumptions [FAIL] defer.refuses_resolved A DEFER ON A RESOLVED ITEM SUCCEEDED -- + the item is now 'deferred' with resolution None; the official + record was rewritten with no warning and no archive + [FAIL] block.refuses_resolved (same) +``` + +8 new tests fail at the parent; the 6 "unaffected" tests **pass** at the parent, as designed — +they assert that ordinary defer/block behaviour is unchanged, so a failure there would mean the +guard refuses too much. + +For the two doctor assumptions the pin is inverted in the way that is correct for an assumption +file: `contract.py` (the *test*) is the lane's, `adapter.py` (the *code under test*) is the +parent's, unmodified. Both facts are stated in the capture. + +--- + +## Test tiers, by name + +Run in the lane worktree, venv `python3.12`, real `bd` 1.1.2 + isolated dolt server. + +| Tier | Command | Result | +|---|---|---| +| 1 unit | `make test-unit` (`pytest tests/unit`) | **790 passed** | +| 2 integration | `make test-integration` (`pytest -m integration tests/integration`) | **333 passed, 3 skipped** (14:44) | +| 3 cli | `make test-cli` (`pytest -m cli tests/cli`) | **82 passed, 1 failed** — the failure is `test_doctor_quick_succeeds_against_the_real_installed_bd`, PRE-EXISTING (`model_performance-jyg`) | +| 4 ledger | `make test-ledger` (`pytest ledger/checks`) | **24 passed** | +| modules | `pytest modules` (NOT in `testpaths`) | **114 passed, 1 failed** — `test_reap_recovery.py::test_explicit_resolve_refusal_after_reap_clears_held_and_allows_new_claim`, PRE-EXISTING (`model_performance-c0e`) | +| lint | `ruff check .` / `ruff format --check .` | clean / 143 files already formatted | +| types | `pyright src tests` | 0 errors, 0 warnings | +| doctor | `python -m amplifier_work_tracker.cli doctor` | **All 36 assumptions hold** | + +**Both failures were verified pre-existing, not asserted.** `test_doctor_quick_…` was re-run on a +fresh worktree of parent `2468a69` with the parent's `src/` pinned and **fails there identically** +(its cause on this host is `sweeps.alive` — the test's isolated root sees no sweep heartbeat). +`test_explicit_resolve_refusal_after_reap_…` is `model_performance-c0e`, named in the item's own +KNOWN block. The third named pre-existing flake (`tests/unit/test_supervisor_web.py`, port +binding) did not fire in this run — 790/790 unit passed. + +The modules tier needs `amplifier-core` + `pytest-asyncio`, which `.[dev]` does not install, and +`PYTHONPATH` pointing at `modules/tool-work-tracker` — without either it fails at COLLECTION and +looks like a real breakage. Both were installed/set for the runs above. + +--- + +## The false immutability claim — what was corrected, and what was not + +The claim, as made: a closed item's `resolution` is *"unwritable through every sanctioned path"* +(`model_performance-uma` and `model_performance-44f`), with 44f's FINDINGS §1.7 summary table +listing `work_defer` / `work_block` as **"status/location only — no"** against `resolution`. +That row is wrong on both counts, and this lane re-measured it from scratch rather than taking +2nx's word for it (`evidence/measurement-BEFORE.txt`). + +**`model_performance-uma` — already corrected, by uma's own lane, before this lane started.** +Verified by reading the live record: its `resolution` §(0) and its `design` ADDENDUM 2 both state +the premise is wrong and name the `block → clear → claim → resolve` path explicitly. Nothing to +correct. One statement in it *becomes* stale when this PR merges — "every one of these 7 is +correctable TODAY — destructively" — and an addendum naming the PR is appended to its `design` +(see below). + +**`model_performance-44f` — corrected, at 2026-09-03T07:56Z**, with a `design` addendum stating +that the §1.7 row is wrong, what was measured (with a pointer to `evidence/measurement-BEFORE.txt`), +and that the door is now closed. Written through the sanctioned `edit` verb on the **installed** +CLI (`--actor agent-2nx-lane`, so the edit is attributed), never this worktree's build, and +**verified by reading the record back** — the addendum is the first thing in 44f's `design`, the +prior text preserved verbatim beneath a `--- design as it stood before this addendum ---` rule. + +No title flag was added: 44f already carries `[RESOLUTION INCOMPLETE … read design]`, which +already sends a reader to the field this correction is in. A second flag would deface the title +without adding a signal. + +> **Correction to an earlier draft of this note.** A previous version of this section claimed the +> 44f edit had already landed. It had not: this lane's first session died before issuing it, and +> the live record at 07:52Z still had `updated_at` 01:20:58Z with no 2nx addendum anywhere in +> `design`. The claim was a self-report, not a readback — the exact failure mode the item's own +> publication contract warns about — so it is recorded here rather than quietly fixed. + +**Its `resolution` text was NOT rewritten. Precisely why:** + +1. `work_reopen` **is not registered in this session's tool set** — the installed tool module + predates f5c's merge (`2468a69`, minutes old). The verb exists in the source I am editing; it + is not yet in the runtime I am running under. +2. A session holds **one** item. `work_claim` on 44f would first cost custody of `2nx`, which + this lane holds. +3. The destructive path that *would* work is the one this lane exists to close, and the item's + own SCOPE-OUTS forbid using it against a real project. + +So the correction went into the one append channel available on a closed item, which is exactly +what 44f's own `RESOLUTION-CORRECTION.md` prescribes for a lane that is not permitted to reopen. + +**44f's `ai-notes` FINDINGS.md §1.7 and RESOLUTION-CORRECTION.md were NOT edited.** They live in +`/home/bkrabach/dev/openai-evals-team-ci/ai-notes/` — a **different repo**, and another lane's +directory. This lane's Procedure step 4 says *"Never touch other repos"* and the program's own +lane rule 2 says *"Write only in your own directory… propose corrections as a diff."* Both point +the same way. The exact correction is therefore prepared as a ready-to-apply patch in this lane's +artifact root: + + `docs/lanes/2nx-defer-block-refuse-resolved/proposed-44f-findings-correction.md` + +It is one paste for whoever owns that repo, not an investigation. + +--- + +## Deviations and choices + +- **Guard placed in the shared helper, not duplicated per verb.** One implementation, two + independently-asserted doctor assumptions. Recorded here because the alternative (a copy in + each of `defer` and `block`) is the shape that drifts. +- **No third doctor assumption pinning bd's own "a status change away from closed clears + `close_reason`" behaviour.** The item names exactly two; and that bd-side fact is already + pinned by `reopen.close_reason_disposition`. Cross-referenced from the new checks' docstrings + rather than re-asserted. +- **Observed, not fixed:** the un-defer/un-block refusal reads + `cannot un-blocked : it is 'resolved', not 'blocked'` — grammatically wrong + (`un-{raw_status}` instead of `unblock`). Pre-existing, cosmetic, in `_clear_status_with_reason`, + and untouched by this change. Not filed: it costs an owner more attention to triage than it + costs a reader to parse. +- **No infrastructure created**, so nothing was registered in the infra ledger and + `lane_teardown.sh` had nothing to claim or tear down. `sweep` was never run. +- Two throwaway projects were created and destroyed through the sanctioned CLI + (`p2nxbefore*`, `p2nxafter*`). `scripts/sweep_test_residue.py` reports no residue from this + lane. One earlier aborted probe run leaked a database (my script's cleanup ran before + releasing a held item); it was dropped via `adapter.drop_database` and the script was fixed to + `unclaim` first — recorded because a leak that is fixed quietly is a leak that recurs. diff --git a/docs/lanes/2nx-defer-block-refuse-resolved/evidence/doctor-AFTER.txt b/docs/lanes/2nx-defer-block-refuse-resolved/evidence/doctor-AFTER.txt new file mode 100644 index 0000000..ec3fe88 --- /dev/null +++ b/docs/lanes/2nx-defer-block-refuse-resolved/evidence/doctor-AFTER.txt @@ -0,0 +1,45 @@ +=== doctor, lane worktree, after the fix === +date : 2026-09-03T07:40:05Z +bd : bd version 1.1.2 (20e493e56: HEAD@20e493e569c9) + +project 'contract1788421205412': bd init hit a dirty schema migration -- dropping and retrying once: [mysql] 2026/09/03 00:40:13 connection.go:214 busy buffer +Error: failed to open Dolt store: failed to initialize schema: schema migration: pending schema migrations alter pre-existing dirty tables: comments, compaction_snapshots, dependencies, events, issue_snapshots, labels; run 'bd dolt commit' to + [PASS] version bd 1.1.2 + [PASS] capabilities all required bd commands present + [PASS] resolve.fenced stale holder refused, as required + [PASS] resolve.divergent_text_refused resolving a closed item with different text refuses and writes nothing + [PASS] resolve.identical_text_idempotent re-sending identical resolution text is an idempotent success + [PASS] reopen.reopens a resolved item reopens unassigned and is directly claimable again + [PASS] reopen.clears_closed_at reopen clears closed_at (the documented, surfaced accounting cost) + [PASS] reopen.close_reason_disposition reopen clears close_reason (measured), and the wrapper's archive comment preserves the previous resolution regardless + [PASS] reopen.emits_event bd records a `reopened` events row, attributed + [PASS] defer.refuses_resolved defer on a resolved item refuses, writes nothing, and names `reopen` + [PASS] block.refuses_resolved block on a resolved item refuses, writes nothing, and names `reopen` + [PASS] release.reopens_unresolved release reopens a held item with no resolution, and it is re-claimable + [PASS] claim.subcommand --claim present, rejects --assignee as expected + [PASS] claim.atomic 5 trials x 12 concurrent claimers, no double-claims + [PASS] claim.directed_atomic 5 trials x 12 concurrent directed claimers on the SAME item, exactly one winner each time + [PASS] link.nonblocking discovered-from is non-blocking + [PASS] list.includes_closed all-flag required and working + [PASS] list.status_filter_includes_closed an explicit --status filter shows closed items without --all + [PASS] show.dependents reverse link visible (1 links) + [PASS] read.no_mutation repeated reads (including not-found/wrong-project misses) leave status, holder, and metadata unchanged + [PASS] resolution.readable resolution text round-trips + [PASS] timestamps.readable created_at/updated_at/closed_at all round-trip as real datetimes + [PASS] metadata.roundtrip arbitrary JSON metadata round-trips + [PASS] project.name_rules dotted names appear usable now; validator may be relaxed + [PASS] custody.fresh_survives a fresh renewal survives regardless of total hold duration + [PASS] custody.stale_reclaimed stale custody is reclaimed: custody stale -- last seen 3600s ago (ttl 900s) + [PASS] custody.idle_not_exempt awaiting_human with stale custody is still reclaimed: custody stale -- last seen 3600s ago (ttl 900s) + [PASS] custody.fenced old holder's renew and resolve are both refused after takeover + [PASS] project.removal remove() drops both the directory and database; re-create afterward is genuinely empty + [PASS] project.create_atomic an abandoned creation lock (dead pid) is healed automatically and create() completes fresh in the same call; path=/tmp/awtcontract_9yb6ne6l/projects/contract1788421205412atomic + [PASS] project.creation_state_reporting creation_state distinguishes none/creating/abandoned correctly + [PASS] service.installed installed and active (unit: /home/bkrabach/.config/systemd/user/amplifier-work-tracker.service) + [PASS] systemd.user_bus_reachable systemctl --user show-environment succeeded + [PASS] dolt.reachable dolt sql-server responds on 127.0.0.1:3308 + [PASS] sweeps.alive reap sweep completed 50s ago (threshold 900s); notify sweep completed 235s ago (threshold 900s) + [PASS] service.restart_policy installed unit has Restart=always -- survives a clean/unintended exit + +All 36 assumptions hold. Safe to run parallel agents. +DOCTOR_EXIT=0 diff --git a/docs/lanes/2nx-defer-block-refuse-resolved/evidence/fail-before-parent-2468a69.txt b/docs/lanes/2nx-defer-block-refuse-resolved/evidence/fail-before-parent-2468a69.txt new file mode 100644 index 0000000..761d300 --- /dev/null +++ b/docs/lanes/2nx-defer-block-refuse-resolved/evidence/fail-before-parent-2468a69.txt @@ -0,0 +1,51 @@ +=== FAIL-BEFORE: the model_performance-2nx tests, run against the PARENT commit === +parent commit : 2468a6946ee7e04e82ad9d563af86d48a5d66355 (feat: `reopen` verb + `work_reopen` tool; `resolve` on a closed item fails loud (#67)) +date : 2026-09-03T07:05:58Z +bd : bd version 1.1.2 (20e493e56: HEAD@20e493e569c9) +pinned source : /tmp/2nx-parent/src/amplifier_work_tracker/__init__.py + (the lane worktree's editable install would otherwise resolve + amplifier_work_tracker to the FIXED source and pass falsely) + +The three test files under test are the lane's NEW ones, copied verbatim onto +the parent tree; only src/ is the parent's. + +--- tier 2 (integration): tests/integration/test_defer_block.py --- +> with pytest.raises(A.BeadsError): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +E Failed: DID NOT RAISE BeadsError + +tests/integration/test_defer_block.py:224: Failed +----------------------------- Captured stderr call ----------------------------- +=========================== short test summary info ============================ +FAILED tests/integration/test_defer_block.py::test_defer_block_on_a_resolved_item_refuse_and_the_resolution_survives[defer] +FAILED tests/integration/test_defer_block.py::test_defer_block_on_a_resolved_item_refuse_and_the_resolution_survives[block] +FAILED tests/integration/test_defer_block.py::test_the_destructive_loop_now_stops_at_its_first_verb +FAILED tests/integration/test_defer_block.py::test_reopen_still_succeeds_on_the_same_item_and_still_archives_first +4 failed, 17 passed in 25.42s + +--- tier 3 (cli): tests/cli/test_cli_new_verbs.py --- +----------------------------- Captured stdout call ----------------------------- + +----------------------------- Captured stderr call ----------------------------- +--------------------------- Captured stderr teardown --------------------------- +=========================== short test summary info ============================ +FAILED tests/cli/test_cli_new_verbs.py::test_defer_block_on_a_resolved_item_fail_non_zero_and_keep_the_resolution[defer] +FAILED tests/cli/test_cli_new_verbs.py::test_defer_block_on_a_resolved_item_fail_non_zero_and_keep_the_resolution[block] +2 failed, 8 passed in 45.49s + +--- modules tier: modules/tool-work-tracker/tests/test_work_defer_block.py --- +----------------------------- Captured stdout call ----------------------------- + +----------------------------- Captured stderr call ----------------------------- +--------------------------- Captured stderr teardown --------------------------- +=========================== short test summary info ============================ +FAILED modules/tool-work-tracker/tests/test_work_defer_block.py::test_defer_block_on_a_resolved_item_report_failure_and_keep_the_resolution[defer] +FAILED modules/tool-work-tracker/tests/test_work_defer_block.py::test_defer_block_on_a_resolved_item_report_failure_and_keep_the_resolution[block] +2 failed, 7 passed in 36.88s + +--- doctor assumptions: the two NEW checks against the PARENT adapter --- + (contract.py -- the assumption file, i.e. the test -- is the lane's; + adapter.py -- the code under test -- is the parent's, unmodified) + adapter under test: /tmp/2nx-parent/src/amplifier_work_tracker/adapter.py + [FAIL] defer.refuses_resolved A DEFER ON A RESOLVED ITEM SUCCEEDED -- the item is now 'deferred' with resolution None; the official record was rewritten with no warning and no archive + [FAIL] block.refuses_resolved A BLOCK ON A RESOLVED ITEM SUCCEEDED -- the item is now 'blocked' with resolution None; the official record was rewritten with no warning and no archive diff --git a/docs/lanes/2nx-defer-block-refuse-resolved/evidence/measurement-AFTER.txt b/docs/lanes/2nx-defer-block-refuse-resolved/evidence/measurement-AFTER.txt new file mode 100644 index 0000000..225b98a --- /dev/null +++ b/docs/lanes/2nx-defer-block-refuse-resolved/evidence/measurement-AFTER.txt @@ -0,0 +1,173 @@ +=== 2nx destructive-loop measurement: after === +date : 2026-09-03T07:08:38Z +python : /home/bkrabach/dev/hw-model-performance/lanes/2nx-defer-block-refuse-resolved/amplifier-work-tracker/.venv/bin/python +source tree : /home/bkrabach/dev/hw-model-performance/lanes/2nx-defer-block-refuse-resolved/amplifier-work-tracker/src/amplifier_work_tracker/__init__.py +bd version : bd version 1.1.2 (20e493e56: HEAD@20e493e569c9) +project : p2nxafter764599 (throwaway) +workspace : /tmp/awt2nx.after.Bmv0nq + +$ amplifier-work-tracker new p2nxafter764599 +created project 'p2nxafter764599' at /tmp/awt2nx.after.Bmv0nq/projects/p2nxafter764599 (verified writable) +EXIT=0 + +$ amplifier-work-tracker add --project p2nxafter764599 '2nx destructive-loop probe' -> id=p2nxafter764599-jd0 + +$ amplifier-work-tracker claim --project p2nxafter764599 --id p2nxafter764599-jd0 --actor probe +{ + "claimed": "p2nxafter764599-jd0", + "title": "2nx destructive-loop probe", + "holder": "probe", + "acceptance": null, + "description": null, + "design": null, + "next_step": "run `amplifier-work-tracker custody --project p2nxafter764599 --actor probe --id p2nxafter764599-jd0` in the background to establish and maintain custody while you work", + "custody_renew_every_seconds": 120 +} +EXIT=0 + +$ amplifier-work-tracker resolve --project p2nxafter764599 --id p2nxafter764599-jd0 --reason ORIGINAL TEXT --actor probe +{ + "resolved": "p2nxafter764599-jd0", + "resolution": "ORIGINAL TEXT" +} +EXIT=0 + +--- readback after resolve (the official record) --- + +$ amplifier-work-tracker list --project p2nxafter764599 --id p2nxafter764599-jd0 --json +{ + "project": "p2nxafter764599", + "items": [ + { + "id": "p2nxafter764599-jd0", + "title": "2nx destructive-loop probe", + "status": "resolved", + "holder": "probe", + "resolution": "ORIGINAL TEXT", + "acceptance": null, + "description": null, + "design": null, + "repos": [], + "context": [], + "created_at": "2026-09-03T07:08:53+00:00", + "updated_at": "2026-09-03T07:08:55+00:00", + "closed_at": "2026-09-03T07:08:55+00:00", + "created_by": "Amplifier" + } + ], + "returned_count": 1, + "total_count": 1, + "truncated": false, + "limit": 1 +} +EXIT=0 + +--- THE DESTRUCTIVE LOOP --- + +$ amplifier-work-tracker defer --project p2nxafter764599 --id p2nxafter764599-jd0 --reason probe --actor probe +amplifier-work-tracker: refusing to defer p2nxafter764599-jd0: it is already resolved, and defer would move it out of resolved and DESTROY the resolution stored on it. NOTHING WAS WRITTEN. + + status: resolved + stored (unchanged): ORIGINAL TEXT + closed_at: 2026-09-03T07:08:55+00:00 + +If you genuinely mean to reopen it, use `reopen` -- it archives the resolution above (and closed_at) into an attributed comment FIRST: + amplifier-work-tracker reopen --project p2nxafter764599 --id p2nxafter764599-jd0 --reason '' + (agents: work_reopen(project='p2nxafter764599', item_id='p2nxafter764599-jd0', reason=...)) +EXIT=1 + +$ amplifier-work-tracker block --project p2nxafter764599 --id p2nxafter764599-jd0 --reason probe --actor probe +amplifier-work-tracker: refusing to block p2nxafter764599-jd0: it is already resolved, and block would move it out of resolved and DESTROY the resolution stored on it. NOTHING WAS WRITTEN. + + status: resolved + stored (unchanged): ORIGINAL TEXT + closed_at: 2026-09-03T07:08:55+00:00 + +If you genuinely mean to reopen it, use `reopen` -- it archives the resolution above (and closed_at) into an attributed comment FIRST: + amplifier-work-tracker reopen --project p2nxafter764599 --id p2nxafter764599-jd0 --reason '' + (agents: work_reopen(project='p2nxafter764599', item_id='p2nxafter764599-jd0', reason=...)) +EXIT=1 + +--- readback after defer+block: is ORIGINAL TEXT still stored? --- + +$ amplifier-work-tracker list --project p2nxafter764599 --id p2nxafter764599-jd0 --json +{ + "project": "p2nxafter764599", + "items": [ + { + "id": "p2nxafter764599-jd0", + "title": "2nx destructive-loop probe", + "status": "resolved", + "holder": "probe", + "resolution": "ORIGINAL TEXT", + "acceptance": null, + "description": null, + "design": null, + "repos": [], + "context": [], + "created_at": "2026-09-03T07:08:53+00:00", + "updated_at": "2026-09-03T07:08:55+00:00", + "closed_at": "2026-09-03T07:08:55+00:00", + "created_by": "Amplifier" + } + ], + "returned_count": 1, + "total_count": 1, + "truncated": false, + "limit": 1 +} +EXIT=0 + +$ amplifier-work-tracker block --project p2nxafter764599 --id p2nxafter764599-jd0 --clear --actor probe +amplifier-work-tracker: cannot un-blocked p2nxafter764599-jd0: it is 'resolved', not 'blocked' +EXIT=1 + +$ amplifier-work-tracker claim --project p2nxafter764599 --id p2nxafter764599-jd0 --actor probe +amplifier-work-tracker: claim p2nxafter764599-jd0 as 'probe' failed: Error claiming p2nxafter764599-jd0: issue not claimable: status closed +EXIT=1 + +$ amplifier-work-tracker resolve --project p2nxafter764599 --id p2nxafter764599-jd0 --reason CORRECTED TEXT -- written after the item had already been closed once --actor probe +amplifier-work-tracker: refusing to resolve p2nxafter764599-jd0: it is already resolved, and the resolution stored on the item is NOT the text you sent. NOTHING WAS WRITTEN. + + stored (unchanged): ORIGINAL TEXT + you sent: CORRECTED TEXT -- written after the item had already been closed once + +To correct the official record, reopen it first: + amplifier-work-tracker reopen --project p2nxafter764599 --id p2nxafter764599-jd0 --reason '' + (agents: work_reopen(project='p2nxafter764599', item_id='p2nxafter764599-jd0', reason=...)) +EXIT=1 + +--- FINAL readback --- + +$ amplifier-work-tracker list --project p2nxafter764599 --id p2nxafter764599-jd0 --json +{ + "project": "p2nxafter764599", + "items": [ + { + "id": "p2nxafter764599-jd0", + "title": "2nx destructive-loop probe", + "status": "resolved", + "holder": "probe", + "resolution": "ORIGINAL TEXT", + "acceptance": null, + "description": null, + "design": null, + "repos": [], + "context": [], + "created_at": "2026-09-03T07:08:53+00:00", + "updated_at": "2026-09-03T07:08:55+00:00", + "closed_at": "2026-09-03T07:08:55+00:00", + "created_by": "Amplifier" + } + ], + "returned_count": 1, + "total_count": 1, + "truncated": false, + "limit": 1 +} +EXIT=0 + +=== end: after === + +--- cleanup (throwaway project destroyed via the sanctioned CLI) --- +removed project p2nxafter764599 (directory + shared-server database) diff --git a/docs/lanes/2nx-defer-block-refuse-resolved/evidence/measurement-BEFORE.txt b/docs/lanes/2nx-defer-block-refuse-resolved/evidence/measurement-BEFORE.txt new file mode 100644 index 0000000..8767590 --- /dev/null +++ b/docs/lanes/2nx-defer-block-refuse-resolved/evidence/measurement-BEFORE.txt @@ -0,0 +1,171 @@ +=== 2nx destructive-loop measurement: before === +date : 2026-09-03T06:55:33Z +python : /home/bkrabach/dev/hw-model-performance/lanes/2nx-defer-block-refuse-resolved/amplifier-work-tracker/.venv/bin/python +source tree : /home/bkrabach/dev/hw-model-performance/lanes/2nx-defer-block-refuse-resolved/amplifier-work-tracker/src/amplifier_work_tracker/__init__.py +bd version : bd version 1.1.2 (20e493e56: HEAD@20e493e569c9) +project : p2nxbefore394644 (throwaway) +workspace : /tmp/awt2nx.before.JbiR9A + +$ amplifier-work-tracker new p2nxbefore394644 +created project 'p2nxbefore394644' at /tmp/awt2nx.before.JbiR9A/projects/p2nxbefore394644 (verified writable) +EXIT=0 + +$ amplifier-work-tracker add --project p2nxbefore394644 '2nx destructive-loop probe' -> id=p2nxbefore394644-hh8 + +$ amplifier-work-tracker claim --project p2nxbefore394644 --id p2nxbefore394644-hh8 --actor probe +{ + "claimed": "p2nxbefore394644-hh8", + "title": "2nx destructive-loop probe", + "holder": "probe", + "acceptance": null, + "description": null, + "design": null, + "next_step": "run `amplifier-work-tracker custody --project p2nxbefore394644 --actor probe --id p2nxbefore394644-hh8` in the background to establish and maintain custody while you work", + "custody_renew_every_seconds": 120 +} +EXIT=0 + +$ amplifier-work-tracker resolve --project p2nxbefore394644 --id p2nxbefore394644-hh8 --reason ORIGINAL TEXT --actor probe +{ + "resolved": "p2nxbefore394644-hh8", + "resolution": "ORIGINAL TEXT" +} +EXIT=0 + +--- readback after resolve (the official record) --- + +$ amplifier-work-tracker list --project p2nxbefore394644 --id p2nxbefore394644-hh8 --json +{ + "project": "p2nxbefore394644", + "items": [ + { + "id": "p2nxbefore394644-hh8", + "title": "2nx destructive-loop probe", + "status": "resolved", + "holder": "probe", + "resolution": "ORIGINAL TEXT", + "acceptance": null, + "description": null, + "design": null, + "repos": [], + "context": [], + "created_at": "2026-09-03T06:55:47+00:00", + "updated_at": "2026-09-03T06:55:49+00:00", + "closed_at": "2026-09-03T06:55:49+00:00", + "created_by": "Amplifier" + } + ], + "returned_count": 1, + "total_count": 1, + "truncated": false, + "limit": 1 +} +EXIT=0 + +--- THE DESTRUCTIVE LOOP --- + +$ amplifier-work-tracker defer --project p2nxbefore394644 --id p2nxbefore394644-hh8 --reason probe --actor probe +{ + "id": "p2nxbefore394644-hh8", + "status": "deferred" +} +EXIT=0 + +$ amplifier-work-tracker block --project p2nxbefore394644 --id p2nxbefore394644-hh8 --reason probe --actor probe +{ + "id": "p2nxbefore394644-hh8", + "status": "blocked" +} +EXIT=0 + +--- readback after defer+block: is ORIGINAL TEXT still stored? --- + +$ amplifier-work-tracker list --project p2nxbefore394644 --id p2nxbefore394644-hh8 --json +{ + "project": "p2nxbefore394644", + "items": [ + { + "id": "p2nxbefore394644-hh8", + "title": "2nx destructive-loop probe", + "status": "blocked", + "holder": "probe", + "resolution": null, + "acceptance": null, + "description": null, + "design": null, + "repos": [], + "context": [], + "created_at": "2026-09-03T06:55:47+00:00", + "updated_at": "2026-09-03T06:55:52+00:00", + "closed_at": null, + "created_by": "Amplifier" + } + ], + "returned_count": 1, + "total_count": 1, + "truncated": false, + "limit": 1 +} +EXIT=0 + +$ amplifier-work-tracker block --project p2nxbefore394644 --id p2nxbefore394644-hh8 --clear --actor probe +{ + "id": "p2nxbefore394644-hh8", + "status": "open" +} +EXIT=0 + +$ amplifier-work-tracker claim --project p2nxbefore394644 --id p2nxbefore394644-hh8 --actor probe +{ + "claimed": "p2nxbefore394644-hh8", + "title": "2nx destructive-loop probe", + "holder": "probe", + "acceptance": null, + "description": null, + "design": null, + "next_step": "run `amplifier-work-tracker custody --project p2nxbefore394644 --actor probe --id p2nxbefore394644-hh8` in the background to establish and maintain custody while you work", + "custody_renew_every_seconds": 120 +} +EXIT=0 + +$ amplifier-work-tracker resolve --project p2nxbefore394644 --id p2nxbefore394644-hh8 --reason CORRECTED TEXT -- written after the item had already been closed once --actor probe +{ + "resolved": "p2nxbefore394644-hh8", + "resolution": "CORRECTED TEXT -- written after the item had already been closed once" +} +EXIT=0 + +--- FINAL readback --- + +$ amplifier-work-tracker list --project p2nxbefore394644 --id p2nxbefore394644-hh8 --json +{ + "project": "p2nxbefore394644", + "items": [ + { + "id": "p2nxbefore394644-hh8", + "title": "2nx destructive-loop probe", + "status": "resolved", + "holder": "probe", + "resolution": "CORRECTED TEXT -- written after the item had already been closed once", + "acceptance": null, + "description": null, + "design": null, + "repos": [], + "context": [], + "created_at": "2026-09-03T06:55:47+00:00", + "updated_at": "2026-09-03T06:55:55+00:00", + "closed_at": "2026-09-03T06:55:55+00:00", + "created_by": "Amplifier" + } + ], + "returned_count": 1, + "total_count": 1, + "truncated": false, + "limit": 1 +} +EXIT=0 + +=== end: before === + +--- cleanup (throwaway project destroyed via the sanctioned CLI) --- +removed project p2nxbefore394644 (directory + shared-server database) diff --git a/docs/lanes/2nx-defer-block-refuse-resolved/evidence/tiers/_run.log b/docs/lanes/2nx-defer-block-refuse-resolved/evidence/tiers/_run.log new file mode 100644 index 0000000..9ce03fc --- /dev/null +++ b/docs/lanes/2nx-defer-block-refuse-resolved/evidence/tiers/_run.log @@ -0,0 +1,16 @@ +RUN STARTED 2026-09-03T07:51:32Z +=== lint START 07:51:32Z === +=== lint EXIT=0 07:51:32Z === +All checks passed! +=== format START 07:51:32Z === +=== format EXIT=0 07:51:32Z === +145 files already formatted +=== types START 07:51:32Z === +=== types EXIT=0 07:51:38Z === +0 errors, 0 warnings, 0 informations +=== doctor START 07:51:38Z === +=== doctor EXIT=0 07:54:55Z === + [PASS] service.restart_policy installed unit has Restart=always -- survives a clean/unintended exit + +All 36 assumptions hold. Safe to run parallel agents. +=== tier1-unit START 07:54:55Z === diff --git a/docs/lanes/2nx-defer-block-refuse-resolved/evidence/tiers/doctor.txt b/docs/lanes/2nx-defer-block-refuse-resolved/evidence/tiers/doctor.txt new file mode 100644 index 0000000..6855d21 --- /dev/null +++ b/docs/lanes/2nx-defer-block-refuse-resolved/evidence/tiers/doctor.txt @@ -0,0 +1,39 @@ +project 'contract178842189862atomic': healing an abandoned creation attempt (lock /tmp/awtcontract_61m0iw38/projects/contract178842189862atomic/.create.lock named a dead pid) before retrying + [PASS] version bd 1.1.2 + [PASS] capabilities all required bd commands present + [PASS] resolve.fenced stale holder refused, as required + [PASS] resolve.divergent_text_refused resolving a closed item with different text refuses and writes nothing + [PASS] resolve.identical_text_idempotent re-sending identical resolution text is an idempotent success + [PASS] reopen.reopens a resolved item reopens unassigned and is directly claimable again + [PASS] reopen.clears_closed_at reopen clears closed_at (the documented, surfaced accounting cost) + [PASS] reopen.close_reason_disposition reopen clears close_reason (measured), and the wrapper's archive comment preserves the previous resolution regardless + [PASS] reopen.emits_event bd records a `reopened` events row, attributed + [PASS] defer.refuses_resolved defer on a resolved item refuses, writes nothing, and names `reopen` + [PASS] block.refuses_resolved block on a resolved item refuses, writes nothing, and names `reopen` + [PASS] release.reopens_unresolved release reopens a held item with no resolution, and it is re-claimable + [PASS] claim.subcommand --claim present, rejects --assignee as expected + [PASS] claim.atomic 5 trials x 12 concurrent claimers, no double-claims + [PASS] claim.directed_atomic 5 trials x 12 concurrent directed claimers on the SAME item, exactly one winner each time + [PASS] link.nonblocking discovered-from is non-blocking + [PASS] list.includes_closed all-flag required and working + [PASS] list.status_filter_includes_closed an explicit --status filter shows closed items without --all + [PASS] show.dependents reverse link visible (1 links) + [PASS] read.no_mutation repeated reads (including not-found/wrong-project misses) leave status, holder, and metadata unchanged + [PASS] resolution.readable resolution text round-trips + [PASS] timestamps.readable created_at/updated_at/closed_at all round-trip as real datetimes + [PASS] metadata.roundtrip arbitrary JSON metadata round-trips + [PASS] project.name_rules dotted names appear usable now; validator may be relaxed + [PASS] custody.fresh_survives a fresh renewal survives regardless of total hold duration + [PASS] custody.stale_reclaimed stale custody is reclaimed: custody stale -- last seen 3601s ago (ttl 900s) + [PASS] custody.idle_not_exempt awaiting_human with stale custody is still reclaimed: custody stale -- last seen 3601s ago (ttl 900s) + [PASS] custody.fenced old holder's renew and resolve are both refused after takeover + [PASS] project.removal remove() drops both the directory and database; re-create afterward is genuinely empty + [PASS] project.create_atomic an abandoned creation lock (dead pid) is healed automatically and create() completes fresh in the same call; path=/tmp/awtcontract_61m0iw38/projects/contract178842189862atomic + [PASS] project.creation_state_reporting creation_state distinguishes none/creating/abandoned correctly + [PASS] service.installed installed and active (unit: /home/bkrabach/.config/systemd/user/amplifier-work-tracker.service) + [PASS] systemd.user_bus_reachable systemctl --user show-environment succeeded + [PASS] dolt.reachable dolt sql-server responds on 127.0.0.1:3308 + [PASS] sweeps.alive reap sweep completed 128s ago (threshold 900s); notify sweep completed 203s ago (threshold 900s) + [PASS] service.restart_policy installed unit has Restart=always -- survives a clean/unintended exit + +All 36 assumptions hold. Safe to run parallel agents. diff --git a/docs/lanes/2nx-defer-block-refuse-resolved/evidence/tiers/format.txt b/docs/lanes/2nx-defer-block-refuse-resolved/evidence/tiers/format.txt new file mode 100644 index 0000000..258e496 --- /dev/null +++ b/docs/lanes/2nx-defer-block-refuse-resolved/evidence/tiers/format.txt @@ -0,0 +1 @@ +145 files already formatted diff --git a/docs/lanes/2nx-defer-block-refuse-resolved/evidence/tiers/lint.txt b/docs/lanes/2nx-defer-block-refuse-resolved/evidence/tiers/lint.txt new file mode 100644 index 0000000..1f5f344 --- /dev/null +++ b/docs/lanes/2nx-defer-block-refuse-resolved/evidence/tiers/lint.txt @@ -0,0 +1 @@ +All checks passed! diff --git a/docs/lanes/2nx-defer-block-refuse-resolved/evidence/tiers/tier1-unit.txt b/docs/lanes/2nx-defer-block-refuse-resolved/evidence/tiers/tier1-unit.txt new file mode 100644 index 0000000..71dac46 --- /dev/null +++ b/docs/lanes/2nx-defer-block-refuse-resolved/evidence/tiers/tier1-unit.txt @@ -0,0 +1,11 @@ +........................................................................ [ 9%] +........................................................................ [ 18%] +........................................................................ [ 27%] +........................................................................ [ 36%] +........................................................................ [ 45%] +........................................................................ [ 54%] +........................................................................ [ 63%] +........................................................................ [ 72%] +........................................................................ [ 82%] +........................................................................ [ 91%] +..................... \ No newline at end of file diff --git a/docs/lanes/2nx-defer-block-refuse-resolved/evidence/tiers/types.txt b/docs/lanes/2nx-defer-block-refuse-resolved/evidence/tiers/types.txt new file mode 100644 index 0000000..4fd4241 --- /dev/null +++ b/docs/lanes/2nx-defer-block-refuse-resolved/evidence/tiers/types.txt @@ -0,0 +1 @@ +0 errors, 0 warnings, 0 informations diff --git a/docs/lanes/2nx-defer-block-refuse-resolved/measure_destructive_loop.sh b/docs/lanes/2nx-defer-block-refuse-resolved/measure_destructive_loop.sh new file mode 100755 index 0000000..3365d04 --- /dev/null +++ b/docs/lanes/2nx-defer-block-refuse-resolved/measure_destructive_loop.sh @@ -0,0 +1,91 @@ +#!/usr/bin/env bash +# Re-run model_performance-2nx's OWN measurement, verbatim, against whatever +# source tree $AWT_PY resolves `amplifier_work_tracker` from. +# +# create -> claim -> resolve "ORIGINAL TEXT" -> defer -> block +# -> list --id --json (is ORIGINAL TEXT still there?) +# -> block --clear -> claim -> resolve "CORRECTED TEXT" -> readback +# +# BEFORE the fix every one of those verbs exits 0 and the stored resolution is +# destroyed at the `defer` step. AFTER the fix the loop stops at the FIRST verb +# and the original text is intact. +# +# Every command's exit code is printed. `set -e` is deliberately NOT used -- +# the whole point is to run the loop to the end and show where it stops. +# +# Usage: AWT_PY=/path/to/python ./measure_destructive_loop.sh