# Queue as data: adversarial plan review VERDICT: revise Rocko, 2026-09-26, for Sage / #1508. Read-only investigation; this report is the only file written for this assignment. No commits, issue writes, credentials read, live launches, or source changes. This is a design review, not a claim that unimplemented queue code has failed tests. Reviewed Filbert's plan after the Q5–Q9 decisions and the final section-5 proposal/decision distinction were folded in: - `agents/filbert/work/queue-as-data-plan-2026-09-26.md`, SHA-256 `59d5a22f1f67426237761341b686c3a9dcf0dffb2d353bab8dd2f9741bcc1fdb`. - Brief `docs/plans/2026-09-13_queue-as-data.md`, SHA-256 `12d8d39f95f40e7d1e93f3a4420390152bbf52d04a6596b48d443512ce559061`. - HEAD `21e3e908b6f64d3eba202480fede05e174799e1d` plus current shared working files: AGENTS, QUEUE, scripts/mosaic, seat, control-board, ledger, launcher and Gitea helper sources. No `packages/queue` exists yet. High means a credible loss of state, authority bypass, or false acceptance; medium means a material workflow or evidence defect. Findings against decided items request reconsideration or clarification by Sage; they do not silently reverse those decisions. Q1–Q4 remain with Jason. ## Findings ### R1 — High: the two-file write has no crash/recovery contract **Where:** plan 2.A and 3.A, atomic write under a lock; test kills only before rename. **Scenario:** A reads revision 10, installs revision 11 of queue.json, then dies before QUEUE.md is rendered. JSON readers and humans now select different work. Separately, a standalone renderer can read revision 10 before B's write and replace the Markdown with revision 10 after B has installed revision 11. A timeout-based stale-lock remover can also admit B while a slow A is alive. Atomic rename prevents a torn individual file, not these outcomes. **Fix:** specify one canonical lock identity across the checkout and its compatibility symlink; acquire it before reading, not merely before writing. All mutations and standalone render use it. Generate both outputs from one validated revision; record a durable transaction/revision and define recovery for every interruption point. Readers must detect an incomplete pair rather than treating it as current. Prefer kernel-released locking or provable stale owner detection; do not steal a lock because a deadline elapsed. Bound waits. Document fsync behavior if surviving a host crash is promised. **Acceptance:** race add/add, move/note and render/write through both checkout paths; kill after each file replacement and before/after commit; inject disk errors and a dead lock holder. Recover one acknowledged operation exactly once, or refuse with a concrete recovery instruction. Never erase a completed neighbor's update. ### R2 — High: Q3's path-limited commit still captures another author's work **Where:** Q3; AGENTS invariant 8. **Scenario:** someone has an uncommitted change to QUEUE.md's hand-written header or historical footer. `git commit -- queue.json QUEUE.md` includes that entire file, not just the generated table. A concurrent ordinary commit can also capture the queue between its write and auto-commit: it does not honor the queue lock. An index-lock or hook failure leaves already-mutated queue state without the promised commit, and an unsafe rollback could erase a later writer. Git's index lock is not a lock over the whole operation. **Fix:** default auto-commit off until Q3 is settled. If authorized, require clean queue paths relative to the intended baseline, no conflicting staged versions, no merge/rebase, and the authorized branch; serialize *all* relevant commit writers through an agreed repository protocol. Freeze and validate the exact prospective file contents, check HEAD/revision before committing, and define retry/recovery without reset/checkout of others' changes. A queue lock alone cannot enforce cooperation by arbitrary host git commands. The statement that a validator means invariant 8 holds is false: schema validation is not the applicable suites. Identify the required checks and bind their receipt to the actual prospective content/code version. Absence of a shell suite wrapper does not exempt existing Node package tests. **Acceptance:** pre-dirty header, partially staged queue file, unrelated staged files, competing ordinary commit, hook failure, and changed HEAD. Verify both the committed tree and preservation of the other author's index and working changes. No push authority follows from any local commit. ### R3 — High: single-writer checks validate appearance, not provenance **Where:** 5.9, 3.A, migration and `render --check`. **Scenario:** hand-edit a row from queued to done, run render, and both schema validation and `render --check` pass. A Markdown hand edit is silently erased by the *next successful CLI mutation*, before the suite ever checks it. A merge can produce valid JSON with illegal transitions or removed rows; two clones can independently allocate the same new id. A fresh clone has no local lock installation or uncommitted audit trail and cannot reconstruct the claimed writer from a matching table. **Fix:** check for unexpected drift *before* mutating or rendering, refuse ambiguous markers and refuse to overwrite unexplained manual changes. Define a reviewed genesis/migration and validate each subsequent operation against its parent revision, including immutable IDs, deletions and authority rules. Use a committed operation history or another explicitly authorized receipt scheme, with merge reconciliation and fresh-clone validation. Keep a high water mark/tombstones for retired IDs, including removed row 7. If multiple clone writers are out of scope, refuse their integration until reconciled; a per-checkout lock does not solve that problem. Be honest about the boundary: code running as the same host user cannot prevent that user from rewriting both data and receipts. Without an enforced integration boundary, this is a cooperative writer with drift detection, not tamper-proof authority. Hooks alone would not fix fresh clones and are outside Piece A's brief anyway. **Acceptance:** valid-but-illegal hand-edited JSON with regenerated Markdown, deleted highest id, duplicate-id branch additions, conflicting merge, fresh clone, missing/duplicate render markers, and preexisting Markdown drift. ### R4 — High: identity attribution is still being used as authorization **Where:** Q5, 3.A authority tests, 5.3 and 5.8; brief iron-clad point 5. **Scenario:** a seat runs `--by jason` or sets `MOSAIC_AGENT_NAME=sage` and moves a protected row. If assign lacks the move permission check, it can instead assign another seat's row to itself and then move it. Even without spoofing, the proposed rule only restricts *clearing* required, while the brief says only Jason *moves* required rows. These are different rules. **Fix:** separate claimed attribution from verified authority. Q5 can map the new lead's queue role, but does not turn an environment variable into proof or grant managed-worker policy authority. Specify the permitted actor for every mutating verb/field, including assign, dependencies, required, gate ownership, notes and terminal rows. Either retain an explicit reviewed human gate for protected mutations or have Jason accept the limited cooperative trust model; weekly reporting is detection after the fact, not enforcement. Reconcile the exact required-row exception before building. **Acceptance:** forged identity, reassignment bypass, dependency/gate edits by an ordinary seat, edits to done/parked rows, and every operation on a required row. No roles/*.json expansion is implied by this review. ### R5 — High: Gate G can appear to pass with extra human help or no work **Where:** Gate G setup and pass evidence; ledger.mjs `messageKind` (line 78) and `readSessions` (line 92); agent-host-dev.sh context and fresh handling. **Scenario:** Jason supplies a correction through the board or a lead relays it. The ledger counts it as board/agent, leaving exactly one human message. `--fresh` creates a new conversation but preserves old session files and loads shared SOUL/USER/CONTEXT/skills; a task-specific hint there can make the demo pass without discovering the assignment. Another seat can move the row using the test seat's claimed identity. Reading the brief and moving the row alone is also not evidence of actually starting its work. **Observed read-only probe:** `messageKind` returns human for the initial instruction, board for `[dragon-lin:control-board -> dragon-lin:rocko]` followed by a human correction, and agent for a similarly prefixed Sage relay. The new `[from: sage (...) -> to: rocko (...) class=actionable]` header is classified human. Table 2 aggregates a seat's sessions over the date range, not an identified Gate G session. It is unsuitable as the sole acceptance counter in either direction. **Fix:** pin the exact fresh session id, initial context/brief/queue revisions, test interval and command output. Audit *all* input channels in that interval; distinguish the initial instruction from further human input regardless of routing. Preserve generic governance context but exclude task-specific pre-coaching. Require a concrete first authorized work action tied to the returned row and that session. Jason's observed pass/fail remains authoritative; do not infer it from the row or git author. Allow normal prerequisite reads instead of requiring the literal first filesystem read to be the brief. A commit is not necessary proof of G and must not bypass Q3 or suite requirements. **Acceptance:** negative controls with a board correction, relayed hint, resumed session and row moved by a helper must fail. Use unit tests with competing eligible rows to test selection: the single-row demo cannot. ### R6 — High: dependencies and `next` do not prevent unauthorized starts **Where:** Q8, section 1, 3.A and 5.11. **Scenario:** `next` skips a dependent row, but the seat directly runs `move ID in-progress` and bypasses the filter. Two sessions of the same seat both read `next`, then both start the same work; a state of in-progress does not distinguish resume by the owner session from a second claimant. An in-review row returned to its author may cause premature continuation; a reviewer can repeatedly receive a row it already reviewed if its receipt is not represented. **Fix:** enforce prerequisite and authority predicates at transition time under the lock, not only in selection. Define atomic claiming or an explicit single-active-session assumption with detectable conflict. Return the action role (implement, resume, review, wait) and track review round/receipt so review dispatch has a stopping condition. There are unresolved semantics: Q8's done-only dependencies cannot express the brief's row-6-*blocked* start exception. State-priority ordering differs from the existing first-eligible-row cadence. `after` alone cannot express every arbitrary owner priority, especially between active rows. Decide these once and update the brief/cadence together. Test that required work cannot be subordinated to ordinary queued work through dependencies. ### R7 — High: posting then recording cannot promise exactly one review request **Where:** 2.D, 3.D, 5.14; scripts/gitea-api.sh. **Scenario:** Gitea accepts a comment but the response is lost, or the CLI dies before storing its id. The row remains unchanged and retry posts a second request. Conversely, a posted request survives a failed local commit. A repository lock cannot roll back the remote side effect. The existing helper supplies an ordinary POST, not an exactly-once transaction. **Fix:** define a durable operation id containing row, revision, review round and candidate digest; persist intent before sending. Include the id in the request, reconcile uncertain outcomes by lookup/readback, and refuse blind retries if delivery cannot be determined. Specify outbox/recovery state and bounded lock behavior. An authorized token path also needs an expected account check; checking that the path differs from the default is not proof of the intended poster. Do not read or provision tokens before Q4. **Acceptance:** accepted POST with dropped response, crash before receipt, duplicate retry, concurrent moves, stale candidate and local commit failure. Fake success/failure alone does not test these boundaries. ### R8 — High: missing from the open list does not establish closed **Where:** Q7 and 2.E. **Scenario:** a row references a typo/nonexistent issue, a deleted issue, a PR number or an inaccessible item. It is absent from a complete open-issues response and the proposed algorithm declares it closed. Even a short page only shows that that response is short; it does not prove each referenced number is a known issue. A done row can therefore pass with no closure evidence. **Fix:** retain the additional open-list call and full-page refusal, which are useful, but represent absent as unknown unless there is positive issue existence/type/state evidence. Fetch/cache positively identified referenced issues under a revised call budget, or report incomplete evidence instead of success. Bound cache age and detect changed scope. Specify `--no-issues` as unknown queue issue checks, not zero violations. **Acceptance:** nonexistent/deleted/PR references, denied access, malformed responses, a full page and no-issues mode. This is a requested correction to decided Q7, not an instruction to silently implement another fetch policy. ### R9 — High: the liveness exemption can turn missing evidence into green **Where:** Q6, 2.E and 3.E; scan.mjs lines 272–307. **Scenario:** remove a native seat's registration and it gets the same non-violation label as a T3 seat. `loadRegistrations` returns records plus errors; it does not check liveness and loads multiple layouts. Matching a seat name alone can select a fleet/other-workspace record. A dead or reused PID is not evidence of the intended active session. Corrupt records must not become the missing-registration exemption. **Fix:** preserve Q6's prohibition on T3 probing, but distinguish explicitly unsupported runtime, missing expected registration, invalid record, stale record and live matched record. Match repository/layout/session identity. Propagate scanner errors. Show coverage/unknowns alongside violations and do not call E fully passed on zero violations when liveness was untested. Record the owner's explicit acceptance of a temporary reduced gate. **Acceptance:** missing native registration, corrupt record, duplicate seat across layouts, wrong workspace, dead PID, unknown PID and an exempt T3 seat. ### R10 — Medium: history needed for E is absent from the schema **Where:** 5.3, 5.8 and 5.12; brief E's age and same-day remediation rules. **Scenario:** `queue note` refreshes updatedAt every week, making a long-neglected required row appear young. A row is changed twice between ledger runs and updatedBy remembers only the last writer, so E cannot list every protected change as 5.8 promises. Removing a row and having no delete verb does not prove an id was never reused. A single latest timestamp also cannot prove each observed violation was remedied within the day. **Fix:** define stable createdAt/requiredSince, operation history and audit retention, with explicit time zone and first-observed violation evidence. Do not depend on optional Q3 commits for mandatory checks. If a journal is chosen, make corrections new entries and place it in a dedicated directory; do not rewrite BUILD-LOG, SESSIONS or existing append-only runtime logs. Terminal required rows should not age forever as outstanding work. ### R11 — High: some section-5 fixes change owner requirements, not details **Where:** 5.6, 5.7, 5.10, 5.13 and 5.14; brief A/C/D and iron-clad point 3. **Scenario:** the builder follows “settle in A's round” and makes brief optional, reopens parked rows, allows completion without Jason, or changes the issue/gate semantics. Those are externally visible requirements changes. The brief explicitly demands an existing brief for every row and says parked/done rows never change. A queued row can have an existing planning brief not yet accepted; a missing brief is not technically necessary to make queued reachable. **Fix:** put each unresolved behavioral change in a disposition table: original rule, proposed rule, approving authority, acceptance change. Keep already-decided changes separate. Do not conflate source review approval with owner approval of altered gates. Decide the blanket `any→blocked` versus terminal immutability conflict and block→block semantics too. The multi-row issue problem is real, but “open iff at least one row not done” is only sound if rows exhaust the issue's scope. Row 1/22 explicitly separates broader MVP acceptance from one targeted correction; row 6 has two issue phases. Model which row gates issue closure, or report mismatch for disposition without forcing automatic closure. A single reviewer field also loses row 6's independent Darkwing/Dewey reviews; use a review policy that can preserve both if that row is migrated intact. ### R12 — Medium: immutable review candidate and receipt lifecycle are missing **Where:** 5.14 and Piece D pass tests. **Scenario:** a request posts `/tmp/candidate.sha256`; the file is replaced or disappears, so a later reviewer cannot identify what was approved. This takeover already found the historical #1511/#1512 `/tmp` snapshots absent. With `issues: [int]`, “the piece's issue” also no longer selects an unambiguous posting target. Posting a request alone does not establish a full review round or preserve its verdict when the row loops through review again. **Fix:** resolve and store the candidate content digest and durable commit or artifact reference, not just a path verbatim. Name the request issue, review round, required reviewers and receipt links; preserve prior rounds. Test request→changes requested→new candidate→approval against exact pins. Keep the brief's ban on new review files, but evaluate the stronger proposed ban separately: no-files-anywhere is not a substitute for durable receipts. ### R13 — Medium: brief validation and migration are too shallow **Where:** 3.A migration/C and 5.12; current QUEUE rows and parked table. **Scenario:** a directory or symlink outside the repository “exists” as a brief, or an untracked local brief makes the author's tests pass but is absent in a fresh clone. A nonexistent section passes even though the fresh seat is instructed to open only the named brief section. Migration shortens a state cell and drops an acceptance/publication boundary that is not actually duplicated in CURRENT or an issue. A valid golden Markdown file proves none of those semantics. **Fix:** require a readable repository-contained regular brief, a defined symlink policy, and tracked prospective content for publishable rows. Either validate section anchors or disallow them for actionable rows until resolved. Produce a reviewed row-by-row migration map preserving owners, review lanes, gates, required status, dependencies, historical IDs and each unresolved boundary. Map parked entries with no briefs explicitly rather than inventing authority from their free text. Preserve history through exact durable links or a reviewed archive; do not assume it was copied elsewhere. ### R14 — Medium: package reuse and acceptance wiring need concrete decisions **Where:** 2.E, 3.B, section 1; package.json and existing module layout. **Scenario:** ledger imports `@mosaic/queue` but adding a sibling package.json does not install a workspace package. Root package.json declares no workspaces; a read-only `import.meta.resolve('@mosaic/seat')` probe returns `ERR_MODULE_NOT_FOUND` today. Moving the registration loader into seat also touches a package outside E's listed files and requires its regression tests. **Fix:** use the existing relative-module convention or explicitly plan package linking and its installation/lockfile consequences; do not leave it implicit. Reuse the loader without silently expanding ownership, and specify liveness/error handling separately (R9). Name actual package test commands and affected suites. C also needs the promised two owner-accepted briefs, not just missing-path tests. D needs a full review round and E the dated ledger receipt/remediation evidence. B's `--check` verifies inputs exist, not that a running or fresh session received the new cadence; inspect the launch context and then perform G. Clarify the brief's “A to E in order” against the proposed C-with-A and D/E parallel delivery before dispatch. ## What is fine - Repository-local `scripts/mosaic` dispatch, keeping seat argv/env behavior, and tests for that behavior are appropriate. Q1 correctly remains an owner wording choice; no global CLI or fleet edits are needed. - Queue data under docs/plans and code under packages/queue respect bootstrap-only root. An edit to the existing root AGENTS.md is permitted; a new root lock/journal/config file would not be. The plan proposes no contract, secret, run-record or managed-role changes. - Required as a flag, an explicit review-to-work edge, strict unknown-field validation, dependency cycle checks, deterministic rendering and a non-mutating render check are good building blocks. They need the transaction/history/authority rules above to deliver the claimed result. - Missing identity refusal, Q4 held for explicit credentials, no automatic push, no T3-backed product feature, and preservation of the old table log are sound boundaries. Historical log corrections must remain additions. - Combining migration with the introduction of the authoritative data file avoids a deliberate two-master interval. A reviewed migration map still matters. Reusing one validator is correct once module resolution is explicit. ## Recommended disposition Revise the write/recovery/commit protocol (R1–R3), settle protected authority and requirement changes (R4/R11), strengthen Gate G evidence (R5), and specify transition claims and review delivery recovery (R6/R7) before implementation. Correct E's positive-evidence rules and audit model (R8–R10); complete the candidate, migration and packaging details (R12–R14). These are bounded plan changes, not authorization to build a new policy service or alter live state. Validation for this review was source inspection and read-only in-process probes of the existing message classifier and module resolution. No queue implementation or fault-injection suite was run because none exists yet. This report records the session outcome in place of editing SESSIONS.md, as this assignment explicitly permits writes only to the report.