Skip to content

impl(aggregator/orchestrator): proposal review state handling - proposal identity, supersede/expiry races, cross-surface counts #1293

Closed

Description

@dezren39

Finding

The delegation design (pm-stack-needs-drew-actions-design-2026-10-26.md §2, §4.1, §6 step 5) introduces a state the rest of the stack must join consistently: a needsDrew item whose delegation.status is proposal-ready (or unable). The simple path — proposal published, Drew approves from /attention, pass applies it — is covered by the UI and delegation-execution slices. The seams are not:

  • The item can expire out of the section (24h unobserved rule) or be resolved by another path while a proposal is still on display; an approve click then targets a ghost.
  • Drew can approve on a stale page render while a newer run has already replaced or withdrawn the proposal.
  • A rejection reason, an inability return, and a re-delegation must compose: reject → delegate again → new proposal must not be confused with the rejected one.
  • /today and the front-door overview count needsDrew items — a proposal-ready item is still Drew's item and must count and badge as such everywhere, not just on /attention.

Proposal

A small, explicit state-handling slice:

  • Proposal identity: a proposal is identified by (item id + delegation.runId + delegation.updatedAt); approve/reject actions carry that triple in their payload, and the consumer applies ONLY if it still matches the currently published proposal — a mismatch is rejected with note "proposal superseded/withdrawn", never a silent apply.
  • Expiry/withdrawal: when the pass withdraws a proposal (item resolved elsewhere, proposal replaced), it publishes the item once with the delegation transition recorded, or expires it with the withdrawal in actions[]; the dashboard renders any pending approve/reject action on a vanished proposal as "no longer applicable" from the actions join, not as pending forever.
  • One rendering rule everywhere: proposal-ready and unable items render/count as needsDrew with their state badge on /attention, /today's banner, and the front-door overview (impl(aggregator): make / the stack front door - overview of all projects, attention, health, quotas (board moves to /board) #1134) — no surface may drop or double-count them.
  • Re-delegation after rejection: the pass must mark a materially-new proposal (new runId) and the card shows "second first pass — previous rejected: " so Drew can see what changed.

Acceptance

  • Race test (fixture consumer): approve action carrying a stale (runId, updatedAt) → rejected with the superseded note; the current proposal is untouched.
  • Expiry test: item expires while proposal-ready → pending proposal actions render "no longer applicable"; no surface counts a ghost item.
  • Cross-surface test: one proposal-ready + one unable item in a fixture section → /attention, /today banner, and overview counts agree (2 needsDrew, badges shown).
  • Reject → re-delegate → new proposal renders with the previous rejection reason visible.

Refs: #1184 (epic), #1152 (actions store), #1134 (front-door counts). Design: pm-stack-needs-drew-actions-design-2026-10-26.md §2.1, §6.

Acceptance criteria

  • A proposal carries a stable identity (item + run/proposal id) so an approve always targets the proposal Drew saw; approving a superseded, withdrawn, or expired proposal is a visible no-op with an explanation, never a silent apply.
  • Reject → re-delegate produces a new proposal that cannot be confused with the rejected one (identity + history visible), and rejection reasons / inability returns compose with re-delegation per the design (§2, §4.1, §6 step 5).
  • needsDrew counts agree across /today, the front-door overview, and /attention while proposals expire, are replaced, or are resolved by another path (cross-surface count check).
  • Race cases are covered by tests or a scripted verification: expire-while-displayed, approve-on-stale-render, resolve-by-other-path-while-proposed.

Activity

  1. added
    kind/featureNew feature or capability request
    app/pm-stackpm-stack (Pad/Grist) in developing-today/code
    on Oct 27, 2026
  2. developing-today commented on Oct 27, 2026

    @developing-today
    Contributor
  3. added
    source/humanFiled directly by a human
    priority/normalOrdinary priority (the default)
    effort/mMedium: expected under 1 day of work
    meta/enrichedIntake review done: acceptance criteria, links, and normalized labels added
    on Oct 27, 2026
  4. dezren39 commented on Oct 27, 2026

    @dezren39
    MemberAuthor

    Intake review

    Fleet monitor intake pass (protocol §5), 2026-10-27.

    Still live: verified this run — issue is OPEN, no merged PR references or closes it (0 closing-PR references), and it was filed within the last day, so nothing has had time to supersede it.

    Related: Design: pm-stack-needs-drew-actions-design-2026-10-26.md §2/§4.1/§6. Sibling slices: #1290 (item contract), #1292 (delegation execution), #1291 (/attention UI).

    Notes / risks: The seams issue of the delegation set — the happy path is covered by #1291/#1292; this one owns the races. Risk: fixing counts in one surface only; the acceptance check is deliberately cross-surface. Effort/m: state-join work across aggregator + orchestrator.

    Suggested approach: Suggested approach: proposal identity first (everything keys off it), then expiry/supersede guards at apply time, then the cross-surface count reconciliation.

    Labels normalized this pass; acceptance criteria appended to the body (original text preserved verbatim above it).

  5. dezren39 commented on Oct 27, 2026

    @dezren39
    MemberAuthor

    Agent note — pickup (needs-user action layer build). Picked up as part of the #1290–#1293 slice on top of the landed triage write path (PR #1346/#1350, main f9c9ffdb, 228 tests). Spec: pm-stack-needs-drew-actions-design-2026-10-26.md incl. both decision updates (standing auto toggle ON for decision/missing-info/approval classes, access/policy never auto; role-based naming — new identifiers use whyUser, no new Drew-named fields; legacy needsDrew array name untouched). Sequencing: contract → UI → consumer (orchestration-pass cron step) → seam hardening. Self-merge per PR when CI green; deploy + bounded live proof (first-pass only, no approval, no live auto execution) at the end.

  6. added a commit that references this issue on Oct 27, 2026
  7. dezren39 commented on Oct 27, 2026

    @dezren39
    MemberAuthor

    Agent note — complete. Proposal review state handling is built and live (PRs #1365 + #1367).

    • Identity: one live proposal per item, identified by (item id, delegation.runId, delegation.updatedAt); approve/reject payloads carry the triple and the POST boundary refuses a stale triple as "proposal superseded/withdrawn" (also enforced consumer-side in the pass body). A newer proposal supersedes — the old one never renders as a second item.
    • Expiry: proposals older than 48h render expired on the card (no approve form, fresh-draft control offered), the boundary refuses approving them, and the pass's self-check retires them on publish.
    • Reject (reason required) returns the item to mode user with the reason recorded; a re-proposal renders as "second first pass — previous rejected: " (render-tested).
    • Pending proposal actions whose proposal changed render "no longer applicable" on the card.
    • Counts: a proposal-pending or unable item counts once, as a needs-user item — verified live post-deploy: section JSON 10 needs-user items = /attention "Needs User (10)" = /today "10 items in the user queue", with the proposal item inside the 10.
    • The live proposal from the impl(orchestrator): delegation execution - first-pass proposals and auto-unless-unable with structured inability returns #1292 proof (item queue-discovery-design-pick, run orch-2026-10-27T09:05:40Z) is pending for the user with working Approve/Reject controls carrying the exact identity — left unapproved by design.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

app/pm-stackpm-stack (Pad/Grist) in developing-today/codeeffort/mMedium: expected under 1 day of workkind/featureNew feature or capability requestmeta/enrichedIntake review done: acceptance criteria, links, and normalized labels addedpriority/normalOrdinary priority (the default)source/humanFiled directly by a humanstatus:done

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions