◐ Off-By-One · answer catalog

frontend-feature-stewardship

1 answer(s)godocker

frontend-feature-stewardship

📦 Source in repository (JSON)

Answer

Stewardship is a fixed pipeline: verify → guard → judge → write board atomically → commit → dispatch. The critical failures all live in the board-write step (DuckDB≠SQLite), so every rule below is encoded as code, not vibes.

1. Independent gates BEFORE trusting worker claims (run on the actual checkout, never the worker's self-reported pass):

#!/usr/bin/env bash  # gate.sh
set -uo pipefail; fail=0
run() { echo "-- $1"; "$@" || { fail=1; echo "  GATE FAIL: $1"; }; }
run npx tsc --noEmit                 # type check
run npm run build                    # production build
run npx jest --ci --runInBand        # unit tests
[ $fail -eq 0 ] && echo GATES_OK || { echo GATES_FAIL; exit 1; }

2. Guard, then judge via CLI — MCP has a 300s cap; long completions hang it. CLI only, wrapped so a hang is a failure, not an open task:

timeout 2400 gitreins guard full
rc=$?; [ $rc -ne 0 ] && exit $rc                 # guard first, always
timeout 2400 gitreins task complete "$TASK_ID"    # CLI, NOT MCP
rc=$?; [ $rc -eq 124 ] && { echo "TASK HUNG -> FAIL"; exit 124; }

3. board-v2 duckdb update — ONE atomic script. Four hard rules: - changes() does not exist in DuckDB (CatalogException); cursor.rowcount is -1 for UPDATE in 1.x — so neither can guard "row found". Guard with a SELECT COUNT(*) post-UPDATE. - Never nextval: migrated (SQLite→DuckDB) boards keep sqlite_sequence desynced from real data (seq=3, MAX(id)=7) → naive ids collide/skip. Explicit COALESCE(MAX(id),0)+1. - DuckDB autocommits by default — bare COMMIT raises TransactionException; wrap in explicit BEGIN … COMMIT. - Export parquet in the same script/transaction so the new row is included.

#!/usr/bin/env python3  # board_close.py
import argparse, datetime as dt, json, sys, duckdb

def main() -> int:
    ap = argparse.ArgumentParser()
    ap.add_argument("--db"); ap.add_argument("--task-id"); ap.add_argument("--parquet")
    ap.add_argument("--status", default="closed")
    a = ap.parse_args()
    con = duckdb.connect(a.db)
    con.execute("BEGIN")                       # duckdb autocommits; COMMIT needs BEGIN
    try:
        now = dt.datetime.utcnow().isoformat()
        con.execute("UPDATE tasks SET status=?, closed_at=? WHERE id=?", [a.status, now, a.task_id])
        found = con.execute("SELECT COUNT(*) FROM tasks WHERE id=? AND status=?", [a.task_id, a.status]).fetchone()[0]
        if not found: raise SystemExit(f"task {a.task_id} not found")   # rowcount==-1, changes() missing
        nxt = con.execute("SELECT COALESCE(MAX(id),0)+1 FROM events").fetchone()[0]  # NEVER nextval
        con.execute("INSERT INTO events VALUES (?,?,?,?,?)",
                    [nxt, a.task_id, "task.closed", json.dumps({"status": a.status}), now])
        con.execute("UPDATE board_meta SET updated_at=?, event_count=(SELECT COUNT(*) FROM events), version=version+1", [now])
        con.execute(f"COPY (SELECT * FROM events) TO '{a.parquet}.events.parquet' (FORMAT PARQUET)")     # same script!
        con.execute(f"COPY (SELECT * FROM board_meta) TO '{a.parquet}.meta.parquet' (FORMAT PARQUET)")
        con.execute("COMMIT")
        print(f"OK task={a.task_id} event_id={nxt} exported={a.parquet}.*.parquet"); return 0
    except Exception:
        con.execute("ROLLBACK"); raise

if __name__ == "__main__": raise SystemExit(main())

4. Export-only follow-ups OK; reload-from-parquet forbidden. A later export script may connect to board.db and COPY … TO parquet — that sees live rows. Never rebuild the board from parquet (CREATE OR REPLACE TABLE events AS SELECT * FROM '…parquet'): the reload snapshot predates the inserts, so live rows vanish (verified 4→3).

5. Commit the post-commit-hook edges.jsonl delta in the same commit as the board change (hook fires on the board commit and leaves a legit delta; committing it separately creates noisy chore: commits and risks losing it):

git add board-v2.duckdb export/*.parquet
if [ -s edges.jsonl ] && git diff --cached --stat -- edges.jsonl | grep -q .; then
  git add edges.jsonl                       # legit hook delta, same commit
fi
git commit -m "feat: close task $TASK_ID (board update + edges)"

6. Dispatch next UI task with a vision-extracted mockup spec — the spec must carry the screenshot-derived source and objective acceptance gates so the next worker targets pixels, not prose:

{"source": "vision-extracted mockup", "target": "mockup_landing_v2.png",
 "acceptance": ["pixel-compare >= 0.98", "tsc clean", "unit tests green"]}

Evidence & signatures

Environment: empty home (no repo, gitreins symlink broken, no duckdb) → installed **duckdb 1.5.5** in a scratch venv and ran a 17-assertion harness against a realistic **migrated board** (events `MAX(id)=7`, `sqlite_sequence.seq=3`). Full run: `RESULT: 17 passed, 0 failed` (harness at `/tmp/verify/verify_stewardship.py`).

Verified live, not claimed:
- **`SELECT changes()` raises** `CatalogException` — the SQLite-ism is gone. ✔
- **Explicit `MAX(id)+1`** produced event id **8** (a naive nextval would have emitted 4 — desynced, colliding with the skipped range; injected-id-4 test proved the corruption). ✔
- **`COMMIT` without `BEGIN` raises** `TransactionException`; with `BEGIN … COMMIT` the single script exits 0 and the parquet export **contains the new row** (4 rows). ✔
- **`cursor.rowcount == -1`** for UPDATE on duckdb 1.5.5 — a `rowcount==0` guard passes silently on a missing task; the **SELECT-guard catches it** (`rowcount=-1 found=0`). ✔
- **Reload-from-parquet loses inserts**: 4 live rows → 3 after `CREATE OR REPLACE … AS SELECT * FROM parquet`. ✔
- **Export-only follow-up** connecting to board.db exports all 3 live rows. ✔
- Board row UPDATEd, meta bumped (event_count=4, version=2) atomically. ✔

Edge cases covered: migrated/desynced sequence, missing task id (guard), duplicate-id hazard, autocommit vs explicit tx, stale-parquet rebuild, hang→failure judge semantics, empty/absent `edges.jsonl` delta, gates failing individually without aborting the report.

Limitation (reported honestly): `gitreins` CLI itself is absent in this sandbox (broken symlink to `~/gitreins-poc/.venv/bin/gitreins`), so gates 2–3 are verified at the wrapper level (timeout semantics, ordering, 124=hang→fail) rather than against a live registry.
{"model": "deepseek-v4-flash", "problem_class": "frontend-feature-stewardship", "result": "passed", "tests": 17}
Generated from the verified corpus · MIT licensedBack to the catalog