Files

1 line
5.5 KiB
JSON

{"id":28422,"html_url":"https://git.mosaicstack.dev/mosaicstack/stack/issues/1609#issuecomment-28422","pull_request_url":"","issue_url":"https://git.mosaicstack.dev/mosaicstack/stack/issues/1609","user":{"id":103,"login":"filbert","login_name":"","source_id":0,"full_name":"Mosaic fleet seat filbert","email":"[email protected]","avatar_url":"https://git.mosaicstack.dev/avatars/833ed4bade42095803be133d8ab240ee","html_url":"https://git.mosaicstack.dev/filbert","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2026-09-04T20:14:33Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"filbert"},"original_author":"","original_author_id":0,"body":"## Filbert review: row 115 round 1 (#1609): changes\n\nCandidate manifest sha256 `f37bc933bbfd2e5f10fd5dd500f60a37e16de324c4e63395bb8d62cc3b5068ab` (5 files), as named in comment 28420. I rebuilt it in my own worktree at BASE 94ece491f with `build.patch` from packet b3fca1f1a. All 5 hashes match, `git diff --check` is clean, no manifest path has drifted from 94ece491f to HEAD, and the patch's added lines have no dashes, bidi or control characters.\n\n### The brief, item by item\n\n1. **The message.** It is inserted in the `decision.resolve/seen/withdraw` case, right after the `decision_events` row and the human answer event, so it is in the same transaction. `to_role` is `raised_by_role`, the class is `DECISION`, `decision` is the id, and `in_reply_to` and `corrects` are null. The sender is `human`/`human-cli` for a human close, and the closing role and run otherwise. The body has the fixed forms, with the option text and an optional `Note:` line. It is routed through `#holder` and `#delivery` the same way `message.send` routes.\n2. **No message.** This covers seen, a withdraw by the raising run (`s.run !== d.raised_by_run`), and supersede and within-role, which write their `decision_events` rows inside `decision.raise` and never reach this case.\n3. **`request`.** See C1.\n4. **Runner.** It is unchanged, and the new `runner.test.mjs` test shows a human resolve becoming a turn that cites the decision and the request.\n5. **README.** It has the rows and the new section. I agree with scope note 1 on `single-use.test.mjs`.\n\n### The close paths Darkwing asked about\n\n`decision_events` has three writers in `broker.mjs`: the supersede withdraw and the within-role resolve inside `decision.raise`, and this case. The broker never writes `expired`. In this case, `#closed` refuses `decision-closed` before any write, and `request()` runs each call in one `BEGIN IMMEDIATE` transaction. A second close therefore refuses, and a refused close rolls back its message. Only the raising run can withdraw as an agent, so an agent withdraw never sends. A resolve comes from the human, or from the arbiter role, which `decision.raise` never sets to the raiser's own role. I found no path that sends twice or skips the message.\n\n### Change requested\n\n- **C1. The fallback's op filter is untested, and dropping it gives the wrong `request`.** In `#request`, the fallback for a human DECISION message picks the decision's first `human.input` answer event, filtered to `op IN ('resolved','withdrawn')`. A human `decision.seen` on a human-routed decision also writes an answer event with the decision as its subject, with `op: 'seen'`. I removed ` AND json_extract(body,'$.op') IN ('resolved','withdrawn')` (mutant f4), and `decision-close`, `single-use` and `runner` all still passed, 41/41. No test has the human run seen and then close the same decision. In the no-message test, the human seens `toHuman` but never closes it.\n\n The mutant is not equivalent. I added a throwaway probe: the coder raises `deploy`, the human seens it, then resolves it (or withdraws it), and the coder receives the message. With the filter, `request` is the resolved (or withdrawn) event. Under f4 it is the seen event in both cases, which breaks brief item 3 (\"the id of that close's `human.input` answer event\"). The probe file was deleted afterwards and the manifest hashes rechecked.\n\n Fix: add a test where the human runs `decision.seen` and then resolves (or withdraws) a human-routed decision, and assert that the message's `request` equals the resolve's answer event, not the seen one. The test's `answer()` helper doesn't filter on op, so it would need to select `op='resolved'`. The broker needs no change.\n\n### Mutants, rerun by me (`decision-close`, `single-use` and harness `runner` together)\n\n| Mutant | Result |\n|---|---|\n| m1, the packet's (no insert) | killed, 8 fail |\n| m2, the packet's (to `route_to`) | killed, 7 fail |\n| m3, the packet's (send on the raiser's withdraw) | killed, 1 fail |\n| f4: op filter dropped from the fallback | **survived**, 41/41 pass |\n| f5: no fallback (`own` only) | killed, 5 fail |\n| f6: route always unheld | killed, 1 fail |\n| f7: `Note:` line dropped | killed, 2 fail |\n\nI restored the source after each one and rechecked all 5 hashes.\n\n### What I ran (single files, no slot)\n\nbus: `decision-close` 7/7, `single-use` 12/12, `decisions` 7/7, `broker` 16/16, `messages` 4/4, `review` 8/8, `tester` 11/11. harness: `runner` 22/22. I also ran the mutants and the probe above. I didn't rerun the full gate and relied on `gate-run.log` for it (bus 155, harness 81, `GATE_PASSED`). The worktree is removed and no process is left in it.\n\nVerdict: **changes** (C1, one test).\n","assets":[],"created_at":"2026-10-14T16:51:22Z","updated_at":"2026-10-14T16:51:22Z"}