---
id: T-1976
name: "arc-scoped driver add/remove parity with global /bvp"
description: >
  arc-scoped driver add/remove parity with global /bvp

status: work-completed
workflow_type: build
owner: human
horizon: now
tags: [arc:value-prioritisation, bvp, watchtower, web-ui]
components: [lib/arc.sh, tests/playwright/test_arc_detail_bvp.py, 
      tests/unit/arc_remove_driver_verb.bats, web/blueprints/arcs.py, 
      web/templates/arc_detail.html]
related_tasks: [T-1958, T-1964, T-1965, T-1926, T-1929]
arc_id: value-prioritisation
# arc_id:                         # T-1849: optional — slug (e.g. "arc-grooming") OR arc-NNN (e.g. "arc-005")
#                                 # When set, must resolve to .context/arcs/<id>.yaml; PreToolUse hook
#                                 # (check-arc-id) blocks save under agent control if it doesn't resolve.
#                                 # Empty/missing → unassigned (allowed). See CLAUDE.md §Task System.
created: 2026-05-21T10:03:43Z
last_update: '2026-06-11T22:23:27Z'
date_finished: 2026-05-22T07:14:55Z
# revisit_at: YYYY-MM-DD          # T-1451: set on DEFER decisions to enable G-053 daily revisit scan
# revisit_evidence_needed:        # T-1451: one-line description of what evidence makes the revisit actionable
# ── BVP scoring fields (T-1918, arc-006). See docs/reports/T-1915-bvp-inception.md for semantics. ──
# bvp_scores:                     # confirmed per-driver scores 0-5, set by `fw bvp confirm` (T-1924).
#                                 # Sovereignty boundary — only set after human or agent confirmation.
#                                 # Shape: {D1: <int 0-5>, D2: <int 0-5>, D3: <int 0-5>, D4: <int 0-5>, [<free-driver-id>: <int>]...}
# bvp_scores_proposed:            # estimator-proposed scores (T-1922 worker). Persists when ≥2 delta
#                                 # from bvp_scores: on any driver (M3 v2-delta). Shape: list of timestamped entries.
# cost_estimate:                  # F8 composite: 0.6×blast_radius + 0.3×tier + 0.1×effort.
#                                 # Q2 fallback: T-shirt S/M/L/XL mapped to 2/4/6/8 when blast_radius is not yet computable.
cost_estimate_proposed:
  - ts: '2026-05-21T10:15:02Z'
    estimator: bvp-estimator-v1-heuristic
    cost_estimate:
      blast_radius: 3
      tier: 2
      effort: 8
    rationale: blast_radius=3 (no-signal); tier=2 (no-signal); effort=8 
      (no-signal)
    rubric_sha: e4a00f38e801
bvp_scores_proposed:
  - ts: '2026-05-21T10:15:02Z'
    estimator: bvp-estimator-v1-heuristic
    scores:
      D1: 4
      D2: 4
      D3: 0
      D4: 0
    rationale: D1=4 (body:structural-gate); D2=4 (body:fw-audit-or-doctor); D3=0
      (no-signal); D4=0 (no-signal)
    rubric_sha: e4a00f38e801
  - ts: '2026-05-28T22:54:10Z'
    estimator: bvp-estimator-v1-heuristic
    scores:
      D1: 4
      D2: 4
      D3: 0
      D4: 0
      F1: 0
      F2: 0
    rationale: D1=4 (body:structural-gate); D2=4 (body:fw-audit-or-doctor); D3=0
      (no-signal); D4=0 (no-signal); F1=0 (no-signal); F2=0 (no-signal)
    rubric_sha: e4a00f38e801
  - ts: '2026-06-11T22:23:27Z'
    estimator: bvp-estimator-v1-heuristic
    scores:
      D1: 4
      D2: 4
      D3: 0
      D4: 0
      F-RECALL: 2
      F-ORCH: 0
      F3: 1
      F1: 0
      F2: 0
    rationale: D1=4 (body:structural-gate); D2=4 (body:fw-audit-or-doctor); D3=0
      (no-signal); D4=0 (no-signal); F-RECALL=2 (body:lightly-promoted); 
      F-ORCH=0 (no-signal); F3=1 (body/components:prompt-incidental); F1=0 
      (no-signal); F2=0 (no-signal)
    rubric_sha: e4a00f38e801
---

# T-1976: arc-scoped driver add/remove parity with global /bvp

## Context

Human observation while reviewing arc-006 (value-prioritisation) on `/arcs/<id>`: arc-scoped drivers can only be **approved from estimator proposals** or **approve-none**'d — there is no UI to add a custom scoped driver or remove an already-approved one. T-1958 inception (closed) deferred this with wait condition *"first cycle of arc-scoped drivers in the wild"* — now met. Mirror the T-1964/T-1965 pattern (global `/bvp` add+remove forms, shell out to `bin/fw … --from-watchtower`) for arc scope.

Constraints (carried over from T-1958):
- `lib/arc.sh:arc_approve_driver` already accepts custom names + weight + rationale — Add reuses it.
- Remove needs a new `arc_remove_driver` verb (no current path removes from `scoped_drivers:`).
- §ACD via `--from-watchtower` exemption; CSRF + rationale ≥30 chars match global pattern.
- M2 invariants preserved: max 3 entries, weight ≤6.

## Acceptance Criteria

### Agent
- [x] `lib/arc.sh` exports `arc_remove_driver` verb — removes a named entry from `scoped_drivers:` of an arc YAML, refuses unknown names with exit 1, requires `--rationale` ≥30 chars, refuses unless `--i-am-human` or `--from-watchtower` is passed (§ACD parity with `arc_approve_driver`).
- [x] `arc_dispatch` routes `remove-driver` to `arc_remove_driver`; `arc_help` lists it under verbs.
- [x] `web/blueprints/arcs.py` adds `POST /api/arc/<arc_id>/add-driver` shelling to `bin/fw arc approve-driver <slug> "<name>" --weight N --rationale R --from-watchtower` (name regex `[A-Za-z][A-Za-z0-9_-]*`, weight 1-6, rationale ≥30 chars).
- [x] `web/blueprints/arcs.py` adds `POST /api/arc/<arc_id>/remove-driver` shelling to `bin/fw arc remove-driver <slug> "<name>" --rationale R --from-watchtower`.
- [x] `web/templates/arc_detail.html` renders an "Add custom scoped driver" form (name + weight 1-6 + rationale + CSRF) below the Proposed section, gated by `scoped_drivers|length < 3`.
- [x] `web/templates/arc_detail.html` renders a per-row Remove button on the Scoped drivers table (rationale prompt via inline form), CSRF-protected.
- [x] New bats `tests/unit/arc_remove_driver_verb.bats` pins: happy path, unknown-name refusal, missing-rationale refusal, §ACD refusal without exemption, `arc_dispatch` wiring, `arc_help` mention. Green (12/12).
- [x] Playwright pins (`tests/playwright/test_arc_detail_bvp.py`) cover live `/arcs/value-prioritisation` Add form rendering + both /add-driver and /remove-driver route-existence + validation refusals (rationale ≥30, weight 1-6, name regex). 16/16 green including 6 new T-1976 tests. (Remove-button DOM rendering is pinned-on-fixture via the bats happy-path; the live-arc remove-form is route-tested only because production has zero scoped drivers — the Human AC exercises the round-trip end-to-end.)

### Human
- [ ] [REVIEW] Form rhythm reads cleanly — Add form sits naturally between Proposed and Scoped tables, Remove button placement on each Scoped row is unambiguous (not easy to click accidentally), and rationale prompt feels consistent with the global `/bvp` pattern.
  **Steps:**
  1. Open `http://192.168.10.107:3000/arcs/value-prioritisation`
  2. Scroll to the BVP section
  3. Approve a test driver via the new Add form (name like `test-driver-xxx`, weight 3, rationale ≥30 chars)
  4. Confirm it appears in the Scoped table with a Remove button
  5. Click Remove, supply ≥30-char rationale, submit
  **Expected:** Both forms render with the same visual rhythm as `/bvp` add/remove. Rationale errors surface inline. Successful add+remove round-trip without page flicker.
  **If not:** Note layout/styling delta to `/bvp` reference page and which step felt off.

## Verification

# Bats — arc_remove_driver verb (12 tests)
bats tests/unit/arc_remove_driver_verb.bats
# Verb routing & help
out=$(bash -c 'source lib/arc.sh; arc_help' 2>&1); echo "$out" | grep -q "remove-driver"
# Render: arc detail returns 200 + Add form action present
# Use arc-grooming (0 scoped drivers) — value-prioritisation hits the M2 cap of 3, hiding the form
curl -sf "$(bin/fw watchtower url)/arcs/arc-grooming" > /tmp/.t1976-arc.html
grep -q 'action="/api/arc/arc-grooming/add-driver"' /tmp/.t1976-arc.html
# Playwright pins for arc detail BVP (includes T-1976 add/remove route + form rendering)
bin/fw test playwright tests/playwright/test_arc_detail_bvp.py
# Python syntax of touched module
python3 -c "import ast; ast.parse(open('web/blueprints/arcs.py').read())"

## RCA

<!-- Non-bug-class — symmetry / parity build. RCA not required. -->

## Evolution

### 2026-05-21 — opening
- **What changed:** T-1958 inception's "wait for first cycle in the wild" condition has been met — human raised the gap from the field while reviewing arc-006.
- **Plan impact:** Scope locked to symmetry with global add/remove (T-1964/T-1965). Explicitly NOT in scope: bulk-edit, edit-in-place (weight/rationale change), arc-scoped weight sliders. Those are follow-ups if and when needed.
- **Triggered:** This task; no sub-tasks pre-filed.

### 2026-05-21 — Latent --rationale rejection surfaced by round-trip
- **What changed:** Human round-trip on `/arcs/value-prioritisation` hit `Unexpected arg: --rationale` from `arc_approve_driver`. Root cause: pre-existing — the verb never accepted `--rationale`, but both the existing `/api/arc/<id>/approve-driver` (T-1926 Proposed-driver Approve buttons) and the new T-1976 `/api/arc/<id>/add-driver` shell to it WITH `--rationale R`. The Approve-from-Proposed path had been latent-broken since T-1926 shipped; T-1976 round-trip exposed it.
- **Plan impact:** Added `--rationale` parsing to `arc_approve_driver` + persists it on the scoped_drivers entry. Three new bats tests pin: accepts flag, persists verbatim, back-compat without flag. No existing test exercised this contract (gap that allowed T-1926's break to ship undetected). Same class as L-417 — satellite text/test references not updated when shape changes.
- **Triggered:** Bats coverage for `arc_approve_driver` + `--rationale` now in place; verb signature aligned with web layer expectation.

### 2026-05-21 — Remove-form rendering vs route-existence pin
- **What changed:** Production `value-prioritisation` arc has zero approved scoped drivers, so the Remove-button DOM branch can't render against the live page. Two paths considered: (a) seed a fixture into the live arc just for the test, (b) split the Remove pin between bats-on-fixture (verb behavior) + Playwright route-existence (server validation refusals).
- **Plan impact:** Picked (b). The bats test exercises the full happy-path with a real scoped_drivers fixture (covers YAML mutation + audit row write + name-not-found refusal). The Playwright test pins the server route accepts POSTs and validates rationale/name — proving the wiring without polluting the live arc. The Human AC end-to-end (add a driver, see Remove button, click Remove) covers the last branch interactively, which was already required for the [REVIEW] taste call.
- **Triggered:** None — split is appropriate; live-arc behavior remains driven by the human round-trip in the Human AC.

## Recommendation

**Recommendation:** GO

**Rationale:** T-1958 inception's wait condition ("first cycle of arc-scoped drivers in the wild") is met — the human raised the gap directly while reviewing arc-006 on Watchtower. Implementation mirrors the T-1964/T-1965 pattern exactly: dedicated routes shelling to `bin/fw arc <verb> --from-watchtower`, CSRF + rationale ≥30 chars matching the global `/bvp` pattern, §ACD-gated CLI verb that refuses agent-direct invocation. Zero new failure modes; the Flask blueprint stays a thin wrapper over the canonical CLI. All Agent ACs validated through bats (12/12) + Playwright (16/16, includes 6 new T-1976 tests).

**Evidence:**
- `lib/arc.sh:1230-1357` — new `arc_remove_driver` + `_arc_remove_driver_help`, §ACD-gated via the reused `_arc_approve_driver_acd_gate` helper.
- `lib/arc.sh:1077` — `arc_dispatch` wires `remove-driver) arc_remove_driver "$@"`; `arc_help` (line 968-) lists both `approve-driver` and `remove-driver` under verbs.
- `web/blueprints/arcs.py:800-887` — new `arc_add_driver` (POST `/api/arc/<id>/add-driver`) + `arc_remove_driver` (POST `/api/arc/<id>/remove-driver`); both validate name regex, weight 1-6, rationale ≥30; both call `bin/fw arc ... --from-watchtower`.
- `web/templates/arc_detail.html:178-235` — per-row Remove control (collapsible details summary so accidental click is hard) + below-Proposed Add form (gated by `scoped_drivers|length < 3`).
- `tests/unit/arc_remove_driver_verb.bats` — 12 tests, all green (happy path, audit-row capture, unknown-name refusal, missing/short rationale, unknown arc, §ACD refusal under `$CLAUDECODE=1`, `--from-watchtower` accepted under same, dispatch wiring, help mention).
- `tests/playwright/test_arc_detail_bvp.py` — 6 new T-1976 tests covering Add-form DOM rendering + add/remove route validation (rationale, weight, name regex). 16/16 green.
- Audit on save: `fw audit` structure section all PASS except a pre-existing fabric-edge-coverage WARN unrelated to this task.

## Decisions

<!-- Record decisions ONLY when choosing between alternatives.
     Skip for tasks with no meaningful choices.
     Format:
     ### [date] — [topic]
     - **Chose:** [what was decided]
     - **Why:** [rationale]
     - **Rejected:** [alternatives and why not]
-->

## Decision

<!-- Filled at completion of inception tasks via:
     fw inception decide T-XXX go|no-go|defer --rationale "..."

     For non-inception tasks this section is ignored. Kept in template
     so `fw inception decide` (lib/inception.sh) finds the anchor heading
     without auto-creating; T-1832 added auto-create as fallback for
     legacy tasks lacking this section. -->

## Updates

### 2026-05-21T10:03:43Z — task-created [task-create-agent]
- **Action:** Created task via task-create agent
- **Output:** /opt/999-Agentic-Engineering-Framework/.tasks/active/T-1976-arc-scoped-driver-addremove-parity-with-.md
- **Context:** Initial task creation

## Reviewer Verdict (v1.4)

- **Scan ID:** R-782e0d59
- **Timestamp:** 2026-05-22T07:16:17Z
- **Catalogue:** v1.3-seed
- **Overall:** CONCERN
- **Needs Human:** no
- **Findings:** 2

**Per-AC findings:**

- **AC#5 (Agent)** — `web/templates/arc_detail.html` renders an "Add custom scoped driver" form (name + weight 1-6 + rationale + CSRF) below the Proposed section, gated by `scoped_drivers|length < 3`.
  - **AC-verify-mismatch** (narrow, heuristic) — `path=web/templates/arc_detail.html in: `web/templates/arc_detail.html` renders an "Add custom scoped driver" form (name + weight 1-6 + rationale + CSRF) below the Proposed section, gated by`
- **AC#6 (Agent)** — `web/templates/arc_detail.html` renders a per-row Remove button on the Scoped drivers table (rationale prompt via inline form), CSRF-protected.
  - **AC-verify-mismatch** (narrow, heuristic) — `path=web/templates/arc_detail.html in: `web/templates/arc_detail.html` renders a per-row Remove button on the Scoped drivers table (rationale prompt via inline form), CSRF-protected.`

### 2026-05-22T07:14:55Z — status-update [task-update-agent]
- **Change:** status: started-work → work-completed
