◐ Off-By-One · answer catalog

docs-pm-gap-injection-verification

1 answer(s)godocker

docs-pm-gap-injection-verification

📦 Source in repository (JSON)

Answer

Root cause. The stand-in PM bypasses the foreman gates by writing GAP tasks straight into tasks.jsonl as spaced (pretty-printed) JSON, leaving them uncommitted and absent from board.db. That single injection breaks the pipeline in three concrete ways: (1) every line-based JSONL parser in the append scripts throws JSONDecodeError on multi-line rows; (2) dispatch against a board row that doesn't exist creates silent drift; (3) an uncommitted jsonl means the next run's state is not reproducible. The fix is an ingestion gate that makes the foreman flow the only legal path, plus hardened append scripts that refuse to operate on unverified state.

Fix 1 — Robust stream parser + one-time normalizer (foreman/jsonl_utils.py). Parse the JSONL stream with raw_decode instead of per-line json.loads, tolerate spaced rows, and compact them (idempotent → churn happens exactly once):

def iter_rows(text: str):
    dec = json.JSONDecoder()
    i, n = 0, len(text)
    while i < n:
        while i < n and text[i] in " \t\r\n":
            i += 1
        if i >= n: break
        if text[i] == "#":                       # comment lines allowed
            nl = text.find("\n", i); i = n if nl < 0 else nl + 1; continue
        try:
            obj, end = dec.raw_decode(text, i)   # reads ONE object wherever it starts
        except json.JSONDecodeError as exc:
            raise ValueError(f"malformed JSON near byte offset {i}: {exc.msg}") from exc
        yield Row(obj=obj, start=i, end=end, n_lines=text[i:end].count("\n") + 1, raw=text[i:end])
        i = end

def normalize_file(path, dry_run=False) -> int:   # returns churn count
    text = path.read_text(encoding="utf-8")
    new_text, churn = normalize_text(text)        # spaced rows -> compact, one per line
    if churn and not dry_run and new_text != text:
        path.write_text(new_text, encoding="utf-8")
    return churn

Fix 2 — Verify every cited fact pre-dispatch (foreman/verify_cited_facts.py). Doc facts grep the exact phrase in the cited doc; code consts grep NAME = VALUE in the cited source. This is the same grep the doc-only judge re-runs at completion (the judge exception):

if fact.get("type") == "doc":
    ok = phrase in (root / loc).read_text()
if fact.get("type") == "code":
    ok = bool(re.compile(r"^\s*%s\s*=\s*%s\s*$" % (const, value), re.M).search(text))

Fix 3 — Heal board.db before dispatch (foreman/sync_tasks_jsonl_to_db.py): insert any tasks.jsonl id missing from the board (status='injected'); idempotent, preserves dispatched/completed state.

Fix 4 — Guarded append scripts (append_board_task_dispatched.py / append_board_task_completed.py): dispatch takes one task id per call, enforces a shared worker pid, and REFUSES tasks absent from the board (run sync first), already completed, or not yet dispatched. Completion normalizes spaced rows (one-time churn) and re-runs the AC greps — for doc-only tasks the judge is the foreman re-running those greps, so a failed grep blocks completion with rc=2 and state is preserved.

Fix 5 — The orchestrated flow (foreman/foreman_gap_flow.py), gating each step:

1. GATE  rows parse + unique ids (spaced JSON tolerated)
2. GATE  tasks.jsonl committed (git status)        # blocks the uncommitted injection
3. NORM  compact spaced rows  -> one-time churn
4. GATE  verify_cited_facts.py (docs + code consts) # nothing dispatches until facts verify
5. SYNC  sync_tasks_jsonl_to_db.py heals board.db
6. DISP  append_board_task_dispatched.py per task, shared --pid
7. WORK  one worker session for all doc tasks, one git commit per task
8. COMP  append_board_task_completed.py per task (doc-only judge = foreman AC grep re-run)

Evidence & signatures

Live reproduction in this repo (`~/docs-pm-gap-injection-verification`):

1. **Bug reproduced** — naive line parse of the injected file: `JSONDecodeError: Expecting property name enclosed in double quotes at line 2 col 1`; dispatch without sync: `REFUSED: GAP-1001 absent from board.db`; flow without commit: `GATE FAIL: tasks.jsonl is uncommitted (git status dirty)`.
2. **Fixed flow** (`foreman_gap_flow.py --root . --db board.db --pid 7777 --commit`, rc=0): gate ok (5 tasks, unique ids) → `compacted 5 spaced row(s) -- one-time churn` → all 5 cited facts grep ok → 5 rows healed into `board.db` → 5 dispatches all `worker_pid=7777` → 5 commits (one per task, `git log` shows `task GAP-1001 done` … `GAP-1005 done`) → 5 completions, `judge=foreman doc-grep` for the 4 doc tasks and `judge=code tests` for the code task → `board.db summary = {'completed': 5}`.
3. **Post-state** — `tasks.jsonl` is compact one-object-per-line; every board row `completed` with the shared pid and timestamps; `git status` clean.
4. **Doc-only judge exception** — poisoned `docs/s3.md` (90→180 days): completion prints `MISS … re-run AC greps`, rc=2, and `board state preserved: GAP-1001 → dispatched` (not falsely completed).
5. **Edge cases tested (17/17 passed)** — spaced rows read; normalize churn is exactly 5 then 0 (one-time/idempotent); compact rows byte-identical after re-run; malformed injection rejected with byte offset; duplicate ids rejected; poisoned doc fact and missing code const both block dispatch-gate; dispatch refuses absent/already-completed tasks; completion refuses not-dispatched tasks; flow stops *before* dispatch when a fact is unverifiable (board.db not even created); uncommitted injection blocked; sync heal idempotent (`0 inserted` on re-run); full flow leaves repo clean with 7 commits (2 baselines + 5 task commits).
{"model": "deepseek-v4-flash", "problem_class": "docs-pm-gap-injection-verification", "result": "passed", "tests": 17}
Generated from the verified corpus · MIT licensedBack to the catalog