Skip to content

fix(approval): distinguish durable decisions from executor application #2353

Description

@DeliciousBuding

Summary

Complete the user-facing task approval loop across Desktop delivery and direct Edge execution. A human decision must not be reported as applied merely because Hub stored it or queued a WebSocket frame.

Verified source defects

  • RunEventService.DecideTaskApproval persists run.agent.permission_decided before DeliverToDesktopDevice. A delivery error leaves the projection decided; repeating the same decision is rejected as not pending.
  • The same endpoint rejects tasks without target_id. Unbound direct-HTTP tasks have an actual edge_device_id/run binding but no target_id, so their runtime approval requests cannot be decided remotely.
  • Desktop only remembers successful permission controls in memory; a lost Edge HTTP response is not retry-safe because the existing one-shot receiver responds not-found after consumption.

Scope / Interfaces

  • Persist immutable task/run/request decision intent with a unique identity and explicit pending/applied state. Separate that durable intent from run.agent.permission_decided runtime evidence. Identical retries reuse intent; conflicting choice/reason/target/run cannot overwrite it.
  • Reuse existing Desktop agent.control and device queue. New task controls carry control_id, hubTaskId and run/request identity; Desktop confirms application through a task-scoped Hub receipt only after an explicit Edge receipt. Existing Team controls keep their compatibility path.
  • Direct control is permitted only for the configured registered original device with matching health identity and explicit receiver receipt support. Keep Hub tokens out of renderer/agent context. Reuse bounded HTTP/egress and least-agency capability conventions.
  • Edge accepts an optional controlId/hubTaskId on POST /v1/permissions/decide. Enforce existing run-owner gates and exact task binding before any effect; repeated identical modern controls return their application receipt without consuming the broker twice, while conflicts fail closed. No-controlId callers retain existing one-shot behavior. A cold/evicted unprovable result is not invented as success and never recreates a permission request.
  • UI distinguishes the recorded human choice from Edge application; pending/failed delivery remains visible and can resend only the same immutable decision.

Invariants

Only the recorded task owner can choose; only the bound device can acknowledge. Queue acceptance is not application. Missing control service is not success. Terminal/mismatched tasks cannot gain fresh approval authority. An offline/device/HTTP failure does not discard the original choice or strand the UI in a fake-applied state. No automatic client retry loop or new general workflow framework.

Files / Ownership

  • Main: hub-server/internal/model, repository, service/agent, service/agentcontrol, related handler/router/composition, migration, API owner docs and integration tests.
  • Edge worker: only edge-server/internal/api permission-control receiver/helpers/tests and strictly necessary Handler fields/capability metadata. No Hub or TS writes.
  • Frontend worker: app/shared Hub/types/task-approval presentation and app/desktop control bridge + focused browser/unit coverage. Main will define the wire fields before dispatch.

Acceptance

  • Failed delivery leaves durable pending intent; same-choice retry after service recreation can reach the original device and conflicting retry cannot change authority.
  • A real PostgreSQL test proves one immutable decision winner under concurrent allow/deny and correct owner/device/run authorization.
  • Direct task approval with no target_id reaches the real Edge broker through fixture HTTP and unblocks the original waiter once; wrong identity and unsupported Edge issue no decision POST.
  • Desktop application failure does not acknowledge, successful/replayed Edge application does, and UI pending/applied states are behaviorally tested in light/dark at 1440x810.
  • Applicable Go/frontend/L1/L2/API checks and seven required CI checks pass; one-topic squash PR and affected documentation/cleanup closeout.

Negative Constraints / Dependencies

Build on #2352, without claiming real-login/model E2E, production deployment or process recovery. No workspace/permission bypass, credentials in Git/logs, durable automatic execution restart, root-rule rewrite, or unrelated storage cleanup. Task cancellation has a separate confirmed delivery gap and is not silently claimed fixed by this approval slice.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions