From 044141de48d478fbf1a985a4877742d6c1b50992 Mon Sep 17 00:00:00 2001 From: tegwick Date: Fri, 19 Jun 2026 15:58:36 +0200 Subject: [PATCH] Add STATE-WP-0061 demand-weighted suggestion backlog workplan Proposed plan (status: proposed) for a Suggestion entity with a persisted relevance/demand counter feeding a WSJF read-model projection. Authored during ops-warden WP-0012 triage; tracks gated needs as relevance-accruing suggestions rather than inert todo tasks. Co-Authored-By: Claude Opus 4.8 --- ...0061-demand-weighted-suggestion-backlog.md | 174 ++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 workplans/STATE-WP-0061-demand-weighted-suggestion-backlog.md diff --git a/workplans/STATE-WP-0061-demand-weighted-suggestion-backlog.md b/workplans/STATE-WP-0061-demand-weighted-suggestion-backlog.md new file mode 100644 index 0000000..01479fa --- /dev/null +++ b/workplans/STATE-WP-0061-demand-weighted-suggestion-backlog.md @@ -0,0 +1,174 @@ +--- +id: STATE-WP-0061 +type: workplan +title: "Demand-weighted suggestion backlog (relevance-fed WSJF)" +domain: custodian +repo: state-hub +status: proposed +owner: codex +topic_slug: custodian +created: "2026-06-18" +updated: "2026-06-18" +--- + +# STATE-WP-0061 — Demand-weighted suggestion backlog (relevance-fed WSJF) + +**Origin:** ops-warden WP-0012 triage (2026-06-18). Most WP-0012 tasks are gated +on external owners shipping paths that do not exist yet. They should not sit as +inert `todo` tasks, nor be fabricated as active entries. They should live as +**suggestions that accrue demand pressure** every time they are needed-but-unmet, +so in-demand work is promoted to real tasks first. + +## Problem + +The hub today cannot represent "a need that has been raised but not yet vetted or +scheduled, whose urgency grows with repeated demand." Concretely: + +- `NextStep` suggestions are **derived on the fly and never persisted** + (`api/schemas/state.py`), so they cannot accumulate anything. +- There is **no relevance/demand counter** on any entity. +- There is **no suggestion → vetted requirement → task** promotion pipeline. + `CapabilityRequest` is the closest analog but models cross-domain brokering, and + a repeat need spawns a *new* request rather than bumping demand on an existing one. +- The **WSJF triage is advisory** (activity-core `daily-statehub-wsjf-triage`, + CUST-WP-0044) and consumes current summary/workplan/progress state — **no + persisted demand signal feeds it**, and the hub holds no Cost-of-Delay / Job-Size + data model. + +## Approach (decided) + +- **New `Suggestion` entity** (not an extension of `CapabilityRequest`/`TechnicalDebt`). + Stages: `suggestion` → `requirement` (vetted + structured) → `promoted` (became a + Task); plus terminal `declined`. Append-only `SuggestionNote` trail (mirrors + `TDNote`) records vetting. `promoted_task_id` links the resulting `Task`. +- **Relevance is a persisted demand counter.** It increments whenever the + suggestion is *needed but not yet done* (defined in T3). `relevance` + + `last_requested_at` + a `relevance_events` count drive ranking. +- **WSJF is computed in the hub as a read-model projection** and exposed via a + ranked endpoint; the existing activity-core daily triage **consumes** it. + `wsjf = cost_of_delay / job_size`, where + `cost_of_delay = base_value + (relevance_weight × relevance)` so repeated demand + raises priority. Job size is an operator-set estimate (default medium). +- Promotion keeps the active task backlog clean: gated needs (the WP-0012 case) + live as relevance-accruing suggestions, **not** as inert `todo` tasks or + fabricated active catalog entries. + +> **Read-model boundary (ADR-001 / hub design):** suggestion writes are a new +> sanctioned write surface alongside `resolve_decision` and `get_next_steps`. +> `bump_relevance`, `promote_suggestion_to_task`, and vetting transitions are the +> only writes; ranking/WSJF are pure projections. T6 records the ADR amendment. + +## Open questions (resolve during T1/T4, do not block proposal) + +- Exact WSJF cost-of-delay decomposition (single `base_value` vs SAFe triple of + business-value / time-criticality / risk-reduction). Start with `base_value` + + relevance; leave room to split later. +- `relevance_weight` default and whether relevance should decay over time + (staleness) — model the field now, tune in T4. + +--- + +## Tasks + +### T1 — Suggestion data model + migration + +```task +id: STATE-WP-0061-T01 +status: todo +priority: high +``` + +- [ ] `api/models/suggestion.py`: `Suggestion` (id, domain_id, topic_id?, + workstream_id?, title, description, origin, stage, relevance, + relevance_events, last_requested_at, base_value, job_size, + relevance_weight, promoted_task_id) + `SuggestionNote` (append-only trail). +- [ ] `SuggestionStage` enum: `suggestion | requirement | promoted | declined`. +- [ ] Alembic migration; register model in `api/models/__init__.py`. + +### T2 — API + MCP sanctioned write layer + +```task +id: STATE-WP-0061-T02 +status: todo +priority: high +``` + +- [ ] REST + MCP: `create_suggestion`, `vet_suggestion` (→ requirement, with + structured fields + note), `decline_suggestion`, `promote_suggestion_to_task` + (creates a `Task`, sets `promoted_task_id`, stage→promoted), and `list/get`. +- [ ] `bump_relevance(id, reason)` — sanctioned write; appends a relevance event, + increments counter, sets `last_requested_at`. +- [ ] Document these as sanctioned writes (alongside `resolve_decision`). + +### T3 — Relevance emission wiring ("needed but not done") + +```task +id: STATE-WP-0061-T03 +status: todo +priority: high +``` + +- [ ] Define the demand events that bump relevance: (a) `get_next_steps` / + dependency lookup resolves to an open suggestion/requirement; (b) a + `CapabilityRequest` matches an unfulfilled suggestion; (c) an explicit agent + bump when it hits a gap (the WP-0012 routing-scenario case). +- [ ] Wire (a) and (b) in-hub; expose (c) via the MCP write from T2. +- [ ] Idempotency/debounce so a single lookup does not double-count. + +### T4 — WSJF projection + ranked endpoint + +```task +id: STATE-WP-0061-T04 +status: todo +priority: high +``` + +- [ ] Pure projection: `wsjf = (base_value + relevance_weight × relevance) / job_size`. +- [ ] `GET /suggestions?rank=wsjf` returns suggestions/requirements ordered by score + (promoted/declined excluded by default). +- [ ] Feed the activity-core daily triage: include the ranked suggestion list in + the `daily_triage` report input (coordinate with CUST-WP-0044 runner). + +### T5 — Dashboard surface + +```task +id: STATE-WP-0061-T05 +status: todo +priority: medium +``` + +- [ ] `/suggestions` page: ranked table (stage, relevance, WSJF, last requested), + with vet/promote/decline actions guarded to the sanctioned write layer. +- [ ] Link from `/wsjf-triage`; short `src/docs/suggestions.md`. + +### T6 — Tests, docs, ADR amendment + +```task +id: STATE-WP-0061-T06 +status: todo +priority: medium +``` + +- [ ] Tests: model + migration, relevance bump idempotency, WSJF ordering, + promotion creates a linked task, stage transitions reject illegal moves. +- [ ] SCOPE/INTENT note; amend the read-model ADR to list the new sanctioned writes. +- [ ] Backfill example: register the gated WP-0012 routing scenarios as suggestions. + +--- + +## Acceptance + +- A need can be recorded as a `suggestion`, vetted into a `requirement`, and + promoted into a real `Task` — with the demand trail preserved. +- Each unmet lookup increments `relevance`; higher relevance raises WSJF, and + `GET /suggestions?rank=wsjf` reflects the new order. +- The daily WSJF triage report includes the ranked suggestion backlog. +- Gated work (WP-0012-style) lives as a relevance-accruing suggestion, never as an + inert `todo` task or a fabricated active catalog entry. + +## See also + +- `STATE-WP-0053` — WSJF triage review page (consumer surface) +- `CUST-WP-0044` — activity-core daily triage runner (producer; cross-repo seam) +- `api/models/capability_request.py`, `api/models/technical_debt.py` — prior art +- ops-warden `WARDEN-WP-0012` — the gated-backlog case that motivated this