# Reviewer Report — Re-review of Builder fixes F1–F6 + llamacpp wiring + maintainer doc corrections Date: 2026-09-06 Reviewer: independent (read-only) reviewer Reviewed refs: working tree over HEAD `0856b25` (diff above) + untracked `bin/pos-ai-llamacpp` Inputs read: my previous review (`2026-09-06_pos_ai_full_review.md`), Builder fix report (`2026-09-06_review-fixes.md`), Maintainer sweep (`2026-09-06_convention-sweep.md`), AGENTS.md, `DOC/DEV.md` via lint/check scripts, `lib/ai-providers/llamacpp.sh`, `lib/common.sh`, `scripts/lint-conventions.sh`, `scripts/check-sync.sh`. ## TL;DR - **Status:** REQUEST_CHANGES (this is a re-review of in-progress work before commit — not final acceptance) - **Verdict:** F1, F3, F5, F6 are FIXED; F2 is PARTIAL (core BLOCKING unit defect fixed, but ExecStart does not quote the model path — paths with spaces still break); F4's failure handling is FIXED but a new REQUIRED honesty defect (misleading summary/`.hf-meta` after partial failure, exit 0) must be fixed. llamacpp wiring and maintainer doc corrections PASS. - **Defect counts this pass:** 2 REQUIRED, 2 SUGGESTED, 2 NOTE. No BLOCKING findings remain. - **Gates:** `bash -n`, `make gen` idempotency, `make check`, `make lint`, `systemd-analyze verify` — **UNVERIFIED** (sandbox denies bash/make; denial quoted in Step 8). Static reading of lint/check rules shows no violation in the touched files. - **Next agent:** Builder (two small, understood, in-scope fixes), then Orchestrator to run gates + live probes and re-hand for final sign-off. --- ## Step 1: Contract scope re-check Named review inputs: previous review findings F1–F6; Builder's fix report; Maintainer convention sweep; AGENTS.md/DEV.md conventions; the FULL pending diff (`git diff HEAD` = working tree over baseline `0856b25`). - Diff touches exactly: `AGENT_TODO.md`, `DOC/AGENT_Context_Project.md`, `DOC/POS.md`, `DOC/howto/ai.md`, `bin/pos`, `bin/pos-ai`, `bin/pos-ai-hf`, `bin/pos-ai-server`, `completions/pos.bash` + untracked `bin/pos-ai-llamacpp` and plan/report files (Orchestrator decision, out of review scope). No out-of-scope source changes found. - Builder scope claim ("no changes to bin/pos-ai, bin/pos-ai-llamacpp, bin/pos, README, AGENT_TODO") — **contradicted by the actual diff**: `bin/pos`, `bin/pos-ai`, `AGENT_TODO.md` ARE modified. These are the Maintainer's corrections (usage provider lists, INTERACTIVE_CMDS, AGENT_TODO ledger) rather than Builder changes, so the *combined* tree is consistent — but the Builder report's "did not touch" list is stale. NOTE (report accuracy, not code). [PASS] ## Step 2: F1 — include/exclude glob filtering (pos-ai-hf) Verified by reading `bin/pos-ai-hf` + grep: - (a) **Array shape end-to-end**: `hf_apply_patterns` (`bin/pos-ai-hf:422-447`) consumes the input via `jq -c '.[]'` (array iteration), accumulates entries, and yields `[]` on no match or `jq -c -s '.'` (array) on matches. Downstream consumers all see an array again: `file_count` via `jq 'length'` (593), `total_size` via `[.[].size // 0]` (618), parallel/sequential iterators via `jq -c '.[]'` (674/713), `.hf-meta` via `[.[] | .rfilename]` (721). The old object-stream breakage is gone. - (b) **Composition order**: gguf/filename filter runs first (`bin/pos-ai-hf:566-584`), then the pattern block `gguf/filename → include → exclude` (588-590), matching usage/POS.md wording. The `--gguf + --include/--exclude` pre-check error was removed (541-544 now only guards filename+both-patterns), so patterns compose with `--gguf`. - (c) **No-match error**: count-0 branch errors cleanly `No files match include/exclude patterns in (branch: )` (599-600); `err()` exits 1 (`lib/common.sh:24`). - (d) **Quote safety**: patterns never enter a jq program — `hf_apply_patterns` passes them as positional args into bash `case "$fname" in $include)` glob matching (429-439); no `match(`/interpolation remains (grep: no `match(`/`include_filter`/`exclude_filter` in pos-ai-hf). `hf_apply_patterns` defaults `include="${2:-}" exclude="${3:-}"` → no unbound vars under `set -u`; `INCLUDE_PATTERN`/`EXCLUDE_PATTERN` initialized at top (120-121). - (e) **Dead code**: `err_with_context`, `hf_download_file`, `run_parallel_download` — grep across `bin/` finds zero occurrences. Carried-over SUGGESTED (from S7, unchanged): `--list` mode (553-563) returns before the pattern block, so `--list --include "*.gguf"` lists everything although POS.md:108 promises "shows exactly what download would fetch". Also new behavior note: bash `case` glob is case-sensitive (old broken regex used `"i"`); docs don't promise case-insensitivity, and the gguf filter itself is case-insensitive — flagging for doc consistency only. [PASS] ## Step 3: F2 — systemd unit generation (pos-ai-server) - ExecStart is ONE line with the full resolved command: `exec_cmd` built at `bin/pos-ai-server:431-477`; unit heredoc writes `ExecStart=$exec_cmd` (495) with no `echo >>` appends anywhere (git diff confirms removal of the entire append block); unit structure `[Unit]/[Service]/[Install]` valid (488-504). - Dry-run prints the same string (`(dry-run) ExecStart: $exec_cmd`, 481). - **NOT FIXED — model path with spaces**: `exec_cmd="$llamacpp_full -m $model --port $PORT --host $HOST"` (432) concatenates the raw path, and the heredoc writes it unquoted. systemd.service(5) splits ExecStart arguments on unquoted whitespace; `resolve_model` (163-197) accepts space-containing paths (`[ -f "$explicit" ]`), so a model under a spaced dir (or `HF_DOWNLOAD_DIR` with a space) produces a unit whose args are split (`-m /home/user/My` + `Models/model.gguf`), and the server silently fails to load the model. The brief's check item "systemd quoting of model path with spaces is correct (quote the ExecStart value properly)" is **not** satisfied. → REQUIRED R1. - `systemd-analyze verify` claim: plausible from the unit text (all keys valid, ExecStart absolute path), but **UNVERIFIED** here — sandbox denies execution. Note: `systemd-analyze verify` would not catch the space issue anyway (it validates syntax/literal paths, not runtime arg semantics). [FAIL → flagged as REQUIRED R1] ## Step 4: F3 — --branch/--revision alias (pos-ai-hf) - `BRANCH` variable removed (grep: no `BRANCH` reference anywhere in pos-ai-hf); both `--branch` (127-129) and `--revision` (146-148) set the same `REVISION`, last-arg-wins by loop overwrite. - Both consumers use `REVISION`: `bin/pos-ai-hf:547` (cmd_download) and `871` (cmd_files). - usage() documents the alias (67, 77-79); `# POS_FLAGS:` line 4 lists both; `DOC/POS.md:108` documents "alias `--revision`, when both are given the later one wins". [PASS] ## Step 5: F4 — parallel download failure handling (pos-ai-hf) - Per-pid reap: `if ! wait "${job_pids[0]}"` batch reap (666-668) and drain loop `if ! wait "${job_pids[$i]}"` (677-683) — a failed job is recorded, never fatal to the batch (checked: no unguarded `wait` remains). - Failure collection + reporting after the batch (667, 679, 687-689) — same `warn` style as the sequential path. - No orphaned jobs: drain loop waits for every started pid; temp dir cleaned by `trap 'rm -rf "$temp_dir"' EXIT` (641) + explicit `rm -rf` + `trap - EXIT` (691-692). - Dead machinery removed (see Step 2e). - **NEW REQUIRED — misleading success reporting**: on partial failure the script continues and: (i) summary prints `Downloaded: ( files, )` (745) counting **attempted** files; (ii) `.hf-meta` "files" records ALL filtered files (721) even failed ones; (iii) exit status is 0. The builder disclosed this ("summary line counts attempted files — pre-existing, noted not in review scope"), but the re-review brief says "summary counts are honest (flag anything misleading)" and the parallel failure path is exactly this rewrite's scope. Consequence: a partially-failed model is marked complete in meta and `pos ai server start` can attempt incomplete weights. → REQUIRED R2. Also minor: the progress line prints `Completed: ` for failures before the batch-end warning (670/682) — fold into R2. [PASS for original F4 defect; FAIL on honesty item → REQUIRED R2] ## Step 6: F5 — version/feature validation (pos-ai-server) - `detect_llama_version` (52-59): `command -v "$bin"` guard **before** the pipeline; pipeline terminated `|| true`; empty → `unknown`. Cannot crash on missing binary. - `validate_requested_flags` (66-87) validates only `REQUESTED_FLAGS` — populated exclusively in the parse loop when the user explicitly passes the flag (288-359); config/env-derived defaults are never validated. - Error message names flag + version (84): `installed llama.cpp does not expose — remove it or upgrade llama.cpp`. - Warn-and-proceed on unreadable `--help` (72-74) is deliberate and documented in `DOC/POS.md:125` ("if `--help` cannot be read the tool warns and proceeds"). - `cmd_status` errors cleanly before the version probe (549-551) and probes the *resolved* binary (604-605). [PASS] ## Step 7: F6 — hf cache + /dev/tty deviation (pos-ai-hf) - `cmd_cache {status|clear}` real implementation (`bin/pos-ai-hf:887-964`): default `status`, bad action → usage error; indent fixed (no more 4-space top-level). - `status`: cache dir + model count + on-disk size via existing `hf_human_size` (926), empty → `Models: 0 (nothing downloaded yet)` rc 0; discovery identical to `list` via `hf_cache_models` (897-905). - `clear`: lists models, confirmation prompt, fail-closed — only `[Yy]` proceeds; EOF/invalid → `Aborted — nothing removed` rc 0 (948-953). - **/dev/tty deviation assessment — acceptable**: - Convention (AGENTS.md) covers tools that read **stdin**; `cache clear` reads `/dev/tty`, not stdin, so the `pos` logging-tee cannot hang or swallow the prompt. - Lint rule `uses_stdin` (scripts/lint-conventions.sh:59-80) explicitly skips lines containing `/dev/tty` (74) — `pos-ai-hf` is statically lint-clean and correctly NOT in INTERACTIVE_CMDS. - Precedent: `pos-ai-server` `pick_model` reads `/dev/tty` (157) and is likewise not in INTERACTIVE_CMDS — the deviation matches an established pattern. - No controlling terminal: `IFS= read -r yn 2>/dev/null > appends: FIXED. Path-with-spaces quoting: NOT FIXED → R1 | | F3 (REQUIRED — dead --branch) | **FIXED** | Single `REVISION` var, both aliases, last-wins, docs/headers updated, both consumers use REVISION | | F4 (REQUIRED — parallel failure path) | **FIXED** (primary) + **R2** | Per-pid reap, failure collection, drain, EXIT-trap cleanup, dead machinery removed — FIXED. Misleading counts/meta/exit on partial failure — new REQUIRED R2 | | F5 (REQUIRED — version/validation) | **FIXED** | Guard before pipe, unknown-safe, explicit-flags-only validation, flag+version error, documented warn-and-proceed, clean status error | | F6 (REQUIRED — cache stub) | **FIXED** | Real status/clear, sizes via hf_human_size, fail-closed confirm; /dev/tty deviation acceptable (lint-exempt, precedent, fail-closed) | | llamacpp wiring (brief) | **PASS** | Forwarder mirror, dispatch case, INTERACTIVE_CMDS, docs factual, AGENT_TODO dated | | Maintainer llamacpp doc corrections | **PASS** | 7 provider-list fixes factual vs adapter; no drift introduced | | Gates | **UNVERIFIED** | Sandbox denial; static lint/check analysis clean | ## Gate outcomes - `bash -n` all in-scope scripts — UNVERIFIED (denied). - `make gen` idempotency (x2) — UNVERIFIED (denied); filetable line counts independently match `wc -l`; GEN blocks internally consistent with headers. - `make check` / `make lint` — UNVERIFIED (denied); static reading of `scripts/lint-conventions.sh` (shebang/strict-mode/POS headers/`-h|--help` position/`uses_stdin` tty exemption/INTERACTIVE_CMDS reverse rule/`local`-at-top warning) and of `check-sync.sh` shows no violation in the touched files. - `systemd-analyze verify` — UNVERIFIED (denied); unit text (pos-ai-server:488-504) is plausible: valid keys, absolute ExecStart, `EnvironmentFile=-%h/...` accepted syntax. - Denial quote: `{"permission":"bash","pattern":"*","action":"deny"}` with an allow-list of git read commands, `head/tail/wc/sort/grep/rg` only. ## Verification verified - F1 array/composition/no-match/quote-safety/dead-code by direct code read + grep (FACT). - F2 single-line ExecStart + dry-run parity + no append writes by code read + diff (FACT); space-quoting gap by systemd.service(5) semantics + code read (FACT). - F3 aliasing by grep + code read (FACT). - F4 failure handling by code read (FACT); misleading summary/meta/exit by code read (FACT). - F5 guards and validation scope by code read (FACT). - F6 cache behavior + /dev/tty fail-closed by code read + lint rule read (FACT). - llamacpp wiring: forwarder byte-mirror (read), dispatch (read), INTERACTIVE_CMDS (read), docs vs adapter facts (read), line counts (wc). - Git baseline HEAD = `0856b25`; tree diff matches the combined Builder+Maintainer reports. ## Verification unverified - `bash -n`, `make gen` idempotency, `make check` (incl. `gen-docs --check`), `make lint`, `systemd-analyze verify` — sandbox denies execution; must be run by Orchestrator/Builder on this exact tree. - Exec bit of untracked `bin/pos-ai-llamacpp` (claimed 100755 via `stat` by Maintainer; untracked so not confirmable via git). - Live probes (a real include/exclude download, one forced-failure parallel download, a real `server start` with a spaced model path, `status` without llama-server) — empirical confirmation pending. ## Scope compliance - In-scope, correctly implemented: F1, F3, F5, F6; F4 failure handling; llamacpp forwarder + dispatch + docs + completions; maintainer doc corrections. - In-scope, defective: F2 space-quoting (R1); F4 summary/meta honesty (R2). - Out-of-scope changes: none in source; untracked plan docs/reports remain for the Orchestrator's commit decision. ## Remaining uncertainty - Gate results on the exact tree (Builder/Maintainer claims unverified here). - Whether `R1` (space quoting) and `R2` (honest failure reporting) are fixed per the recommendations — requires a Builder pass and another review round, plus one live spaced-path probe and one forced-failure probe by the Orchestrator. ## Recommended next agent **Builder** **Reason:** Two REQUIRED defects with understood, in-scope fixes: (1) quote the ExecStart tokens (`"$llamacpp_full"`/`"$model"`) so spaced paths survive systemd's argument splitting (pos-ai-server:432/495); (2) make the failure path honest — count successes in the summary, exclude failed files from `.hf-meta` (or record status), and exit non-zero when any file failed (pos-ai-hf:687-747). After fixes: re-run `bash -n`, `make gen` x2, `make check`, `make lint`; then Orchestrator runs the live probes (include/exclude download, forced-failure parallel download, `server start` with a spaced model path + `systemd-analyze verify`, `server status` without llama-server) and re-hands to Reviewer for final acceptance. S3/S4 can ride along in the same pass. ## Changes made by Reviewer none