Archived — see roadmap.md for live planning¶
This file is preserved as a historical audit trail of what was deferred at the close of Phase 9. The live forward-planning source of truth has moved to
docs/roadmap.md(version × theme organized; evolves continuously).Every forward-looking item below (P9B-1 through P9B-9) has been re-homed in the roadmap with its original ID preserved. Use this file only to understand the Phase 9 decision context. For "what's planned next", read
roadmap.md.
anvil — Post-v1.9.0 Backlog (v2.x roadmap + carry-forward tech debt)¶
This file is the forward-looking companion to
tech-debt-backlog.md. Where tech-debt-backlog.md
catalogues items by origin PR and tracks their close-out across phases,
this file catalogues items by future release intent — what v2.0 / v2.1 /
v2.x will plausibly carry.
Phase 9 (v1.9.0) closed the audit-honesty deferrals from Phase 8 and the Phase 7 LLM-augmentation cleanup. Eight items below were considered for inclusion in Phase 9 but consciously deferred — either the implementation risk was higher than the plan window allowed, the design needed a separate spec doc, or the work depended on infrastructure that does not exist yet.
Status legend: OPEN = unscheduled; TARGETED-VN.M = scheduled for that release; SPEC-FIRST = needs a design doc before implementation.
v2.0 — Additional sync providers¶
The SyncProvider Protocol shipped in v1.8.0 was deliberately
registry-driven so contributors can add providers without engine changes.
v1.8.0 / v1.9.0 ship github_issues only. The next batch:
P9B-1 · LinearIssuesProvider · linear_issues¶
Status: OPEN. Target: v2.0.
GraphQL-only API; httpx client with respx mocking. Status mapping needs a per-team workflow inspection step (Linear lets teams customise their workflow states). Step-by-step contributor guide in docs/sync-providers.md § "Step-by-step: add Linear support" — uncommented placeholder for the work.
Acceptance:
bin/src/anvil/sync/providers/linear.pywith full Protocol surface.bin/src/anvil/sync/clients/linear_api.py— GraphQL transport.tests/test_linear_provider.pywith respx mocks; full lifecycle test.- Live-Linear nightly workflow under
.github/workflows/anvil-live-linear.ymlgated onLINEAR_API_KEYsecret. anvil sync linear_issues --healthworks.
P9B-2 · MondayBoardsProvider · monday_boards¶
Status: OPEN. Target: v2.0.
Monday has people-columns and per-board custom columns; the provider_metadata dict will carry the bulk of the shape. Auth via Monday API key.
Acceptance: same shape as P9B-1 but using REST+JSON (Monday's GraphQL is opt-in per workspace).
P9B-3 · JiraIssuesProvider · jira_issues¶
Status: OPEN. Target: v2.1.
Jira's workflow/status taxonomy is per-project; the provider needs a one-time discovery call to map anvil's 11 TaskStatus values to the project's actual statuses. Auth via PAT + email pair.
Acceptance: same shape as P9B-1; one-time --discover-statuses flag that writes the discovered mapping into .anvil/config.yaml under sync.providers.jira_issues.status_map.
P9B-4 · GitHubProjectsProvider · github_projects¶
Status: OPEN. Target: v2.1.
Sibling to github_issues but for Projects v2 (the newer board surface). Shares the gh-CLI / httpx transport from github_issues but addresses a different remote object kind. Probably co-locates in sync/providers/github_projects.py.
v2.0 — Sync infrastructure upgrades¶
P9B-5 · Webhook-based sync (vs polling)¶
Status: SPEC-FIRST. Target: v2.0.
--watch polls every N seconds. For providers that publish webhooks (GitHub, Linear, Monday, Jira), the engine should accept push-based sync via a long-running listener. Webhook secret in .anvil/config.yaml; HMAC verification on every payload.
Needs a design doc first: the engine's current "one fetch round-trip per task per pass" assumption does not hold under webhooks (events arrive out of order, may duplicate, may race with manual sync calls). The reconciliation engine has to become idempotent over arbitrary event ordering rather than just over sequential polling iterations.
Spec scope:
- Webhook listener as a separate
anvil webhook-listen --provider X --port 8080subcommand (decouples lifetime from CLI sync calls). - Event de-duplication via
(provider_id, external_id, last_modified)tuple — first event wins, later same-tuple events are ignored. - Out-of-order delivery — the listener queues events and processes them in
last_modifiedorder with a configurable max-delay. - HMAC verification per provider (GitHub uses
X-Hub-Signature-256; Linear usesLinear-Signature; etc.). - Failure mode when the listener crashes: the polling fallback must continue to work and reconcile catch-up state from the JSONL audit log.
P9B-6 · Immediate-apply *_applied resolution variants¶
Status: TARGETED-V2.0. Target: v2.0.
Phase 9 T5 (agent-welder-honesty-status.md § 2 — "Immediate-apply variants for local_wins / remote_wins: deferred") explicitly deferred wiring remote_wins_applied / local_wins_applied per the TODOs at cli/sync.py:1054 and :1068. The conflict-safety design (re-fetch on a moving target, retry/back-off contract) needs to be specified before the implementation can land.
Acceptance:
remote_wins_applied: when the conflict-resolution chose remote, immediately call_apply_remote_to_localinline inside the pull loop. Emitsync.pull.completedwithresolution="remote_wins_applied".local_wins_applied: when the conflict-resolution chose local, immediately callprovider.push_task(...)inside the pull loop. Emitsync.pull.completedwithresolution="local_wins_applied". Define the retry/back-off contract for the race where a parallel remote edit lands between thelocal_winsdecision and thepush_taskcall.- Updated
docs/github-sync.md§ "Audit honesty" — the*_appliedtokens join the controlled vocabulary alongside the*_deferredones. tests/test_cli_sync.py— 4+ new tests covering both immediate-apply paths plus the race-edit fallback.
MPRD-RG1 · PRD→release-group sync (ensure_release_group Protocol + GitHub milestone client)¶
Status: OPEN. Target: v2.0.
Deferred out of the multi-PRD release (Phase 7 of docs/specs/2026-06-22-multi-prd-revisable.md; tracked as T029 in docs/backlog/multi-prd-revisable.prd.md). That release shipped the release/sync data plumbing only — SyncMapping.prd_id + entity_kind: Literal['task','prd'], per-PRD --prd push scoping, and prd_id on reconciliation discrepancies. The network-touching milestone wiring was explicitly not built so no existing provider had to change.
Acceptance (none of this lands in the multi-PRD release; existing providers unchanged):
- A provider-neutral
ensure_release_group(release_tag, target_version, prd_summary, mapping) -> ExternalRefProtocol method (covers GitHub Milestone / Linear Cycle / Jira FixVersion), optional and capability-gated via asupports_milestones/supports_release_groupsflag so existing providers stay unchanged. - GitHub client
create_milestone/list_milestonescalls plus issue→milestone assignment for the issues a PRD owns. - A
sync.milestone.ensured(release-group) audit event and prd-kindSyncMappingrow persistence (entity_kind='prd', carryingprd_id, nulltask_id) recording the 1:1 PRD↔release/milestone mapping. - A
missing_milestone_mappingreconciliation discrepancy kind for a PRD that has atarget_tagbut no persisted milestone mapping (sibling tomissing_sync_mapping;drift_sync_statealready special-cases prd-kind rows so a milestone produces no task-shaped drift). - The milestone-ensure step is wired into
anvil sync … --prd <id> --pushonly for providers that advertise the capability.
Re-homed in the live roadmap as [MPRD-RG1] (docs/roadmap.md § v2.0 → Theme: Sync infrastructure).
v2.x — Carry-forward from tech-debt-backlog.md¶
The items below remain open in tech-debt-backlog.md after Phase 9
closed P9-1..P9-8. They are not scheduled for any specific Phase 9
follow-up but track them here so the next planning pass picks them up.
Cleanup (CL-N items still open)¶
| ID | Source | Description |
|---|---|---|
| CL-1 | PR #41 Critic-2 | check-claim.sh does not call the Phase 4 CLI subcommand; per-file warning logic in CLI is dead from the hook's perspective. |
| CL-2 | PR #41 Critic-1 | --commands / --files-changed comma-split mangles commands with embedded commas. Switch to repeatable flag. |
| CL-3 | PR #41 Critic-3 | _reap_stale_claims bare except Exception: pass swallows SchemaMismatch. |
| CL-4 | PR #41 Critic-3 | ConflictGroup records never persisted; conflict_groups table always empty. |
| CL-5 | PR #41 Critic-3 | conflicts CLI command referenced in docstring but not implemented. Depends on CL-4. |
| CL-8 | PR #41 Critic-1 | Double-submit with different evidence_id inserts duplicate row. |
| CL-10 | PR #41 Critic-1 | capture-evidence.sh + gates.py pattern sets are not aligned; agent running go test gets no capture but the gate passes. |
| CL-11 | PR #41 Critic-3 | template.py:374 calls datetime.now() directly — bypasses Clock abstraction. |
| CL-12 | PR #41 Critic-3 | score_all() / infer_dependencies() / infer_conflict_groups() are in __all__ but have no external callers — misleading public surface. |
| CL-13 | PR #41 Critic-2 | next_event_id returns hardcoded "E000001" when conn is None instead of raising. |
Test Quality (TQ-N items still open)¶
| ID | Source | Description |
|---|---|---|
| TQ-1 | PR #41 Critic-4 | _sqlite_dump docstring claims user_version filtering but doesn't filter. Either implement or delete the claim. |
| TQ-2 | PR #41 Critic-4 | test_replay_includes_claim_stale skips prd.reviewed between parsed and approved. |
| TQ-3 | PR #41 Critic-4 | Two unittest.mock.patch usages on SqliteBackend violate the no-mocking rule. Replace with real failure injection or delete unreachable branches. |
| TQ-4 | PR #41 Critic-4 | test_init_creates_state_directory first runner.invoke runs without chdir(tmp_path) — pollutes real cwd. |
| TQ-6 | PR #41 Critic-4 | _do_init_and_plan doesn't assert exit codes — sub-commands can silently fail. |
| TQ-7 | PR #41 Critic-4 | Phase 3 CLI tests assert on output strings, not SQLite state — "feature not created" would pass "feature" in result.output.lower(). |
| TQ-8 | PR #41 Critic-4 | tests/test_sqlite.py is 3,924 lines — split per phase. |
Performance / Scale (PS-N items still open)¶
| ID | Source | Description |
|---|---|---|
| PS-1 | PR #41 Critic-2 | _check_group_conflicts has N+1 query — for each active claim, calls backend.get_task() inside a loop. Pre-fetch via list_tasks() + local map. |
v2.x — New work surfaces¶
P9B-7 · anvil snapshot subcommand¶
Status: OPEN. Target: v2.1.
Phase 5 (v1.4.0) removed the pre-created .anvil/snapshots/ directory because nothing wrote to it. The intent was always to ship a sqlite3 .backup wrapper as anvil snapshot that writes a timestamped .db into that directory and prunes by retention policy. The directory will be created on first invocation.
Acceptance:
anvil snapshot [--retention 30d|count:N]writes.anvil/snapshots/YYYY-MM-DDTHH-MM-SSZ.db.anvil snapshot --listshows existing snapshots with size + age.anvil snapshot --restore <name>restores a snapshot atomically (writes to a temp file, swaps via rename).- Documented in
docs/specs/2026-05-24-anvil-v0.md§ Snapshots.
P9B-8 · MCP sync tools surface¶
Status: OPEN. Target: v2.1.
The MCP server (Phase 6) exposes 13 read/mutate tools but does NOT expose sync_* tools. Agents that want sync today have to shell out via Bash. A sync_run / sync_health / sync_status tool surface would close that gap — useful for agents running in sandboxes without shell access.
Acceptance:
- 4 new MCP tools:
sync_run(provider, *, direction='both', task_id=None),sync_health(provider),sync_status(),sync_reconcile(*, fix=False). - Tool errors map cleanly to
ToolError(message)with the same exception classes the CLI handles. - Documented in
docs/mcp.md§ Sync tools. - Tests in
tests/test_mcp.py.
P9B-9 · Provider config schemas in config.yaml¶
Status: SPEC-FIRST. Target: v2.0 (alongside P9B-1 Linear).
The current sync.providers config key is a flat list. As soon as providers need per-provider config (Linear team ID, Monday board ID, Jira project key + workflow map), the flat list becomes a nested map. The transition needs a design doc — does the new map shape coexist with the flat list, or replace it?
Probable shape:
sync:
providers:
github_issues: {} # no per-provider config
linear_issues:
team_id: ENG
jira_issues:
project_key: ENG
status_map: # filled by --discover-statuses
ready: "Open"
in_progress: "In Progress"
done: "Done"
Migration path from the flat list form (a list of strings is shorthand for "the listed providers with empty config") keeps v1.9.0 configs valid.
Closed in Phase 9 (cross-reference)¶
See tech-debt-backlog.md § "Phase 8 / Phase 9
closures (sync + LLM cleanups)" for the full list of P9-1..P9-8 entries
with implementation details and test counts.
| ID | Title | Closed in |
|---|---|---|
| P9-1 | Audit-event honesty — sync.pull.completed emitted on deferred branches |
Phase 9 T5 |
| P9-2 | local_moved-only path set sync_state="in_sync" instead of local_ahead |
Phase 9 T5 |
| P9-3 | SyncAuditPayload single all-optional model accepted nonsense |
Phase 9 T3 |
| P9-4 | RecordedLLMProvider.record_key ignored max_tokens / temperature |
Phase 9 T6 |
| P9-5 | Brainstorm-flow bridge used fuzzy detection | Phase 9 T6 |
| P9-6 | expand --use-llm had no --format prd UX |
Phase 9 T6 |
| P9-7 | Multi-provider config — no way to opt out of every sync provider | Phase 9 T5 |
| P9-8 | Two new plugin-owned doc agents — marketplace-scribe, docs-scribe |
Phase 9 T4 |