Skip to content

feat(runtime): add typed execution outcomes and safe retry lifecycle - #13

Merged
itsmeares merged 6 commits into
mainfrom
feature/typed-outcomes-safe-retry-lifecycle
Jul 12, 2026
Merged

feat(runtime): add typed execution outcomes and safe retry lifecycle#13
itsmeares merged 6 commits into
mainfrom
feature/typed-outcomes-safe-retry-lifecycle

Conversation

@itsmeares

@itsmeares itsmeares commented Jul 12, 2026

Copy link
Copy Markdown
Owner

Summary

  • add stable typed executor outcomes for success, already-satisfied targets, retryable and permanent failures, rate limits, authentication loss, stop, and cancellation
  • separate provider-owned ProviderResult data from runner-owned normalized ActionResult identity, status, route, and attempts
  • add bounded automatic retry with one-attempt default and explicit stop-after-final-failure policy
  • halt safely on rate limits, authentication loss, and positive retry-after without sleeping or changing untouched pending actions

Outcome mapping

  • succeeded and already_satisfied -> done
  • retryable_failure -> failed when exhausted or halted for retry-after
  • permanent_failure, rate_limited, and authentication_required -> failed
  • stopped -> stopped
  • cancelled -> cancelled

Already-satisfied remains distinct in runtime results and events. Retryability is derived only from retryable_failure; ambiguous and invalid results fail closed as permanent failures.

Runtime lifecycle

  • attempts start at 1 and never exceed MaxAttemptsPerAction
  • default policy performs one attempt and continues after ordinary final failures
  • only retryable failures without positive retry-after can retry automatically
  • no timers, jitter, queues, background workers, countdowns, or hidden waits
  • provider halts retain current action metadata and leave later actions pending
  • explicit stop and context cancellation preserve existing propagation behavior
  • pre-cancelled contexts never invoke the executor

Events and audit

Action and lifecycle events retain execution mode and executor ID with outcome, attempt, derived retryability, optional retry-after milliseconds, optional safe provider code, and typed halt reason. Empty optional fields are omitted. Messages, targets, raw errors, raw responses, credentials, authorization data, and private content are not written to execution audit fields.

Standalone retry edits return PlanActionEdit; they are not represented as provider execution events.

Simulation and TUI

  • Instagram and connected Reddit simulations still return succeeded, done, attempt 1, and No-op apply completed.
  • Reddit readiness and unsupported-route blockers remain fail-closed
  • apply results keep ordinary success compact
  • failures and halts show concise outcome, relevant attempt count, retry-after, and reconnect guidance
  • provider codes remain diagnostic and are not primary UI copy
  • Instagram assisted manual cleanup remains separate from simulation

Compatibility

  • cleanup-plan JSON remains format version 1
  • no persisted plan fields or domain action statuses were added
  • existing execution event type strings remain unchanged
  • no real Instagram or Reddit mutation path was added
  • no OAuth scopes, credential lifecycle, network calls, release versions, tags, or GitHub Releases changed

Tests

Deterministic scripted executors cover first-attempt success, already satisfied, retry-success, retry exhaustion, permanent failure, rate limits, authentication loss, retry-after halt, stop, cancellation, invalid outcomes, unsafe metadata, and unexpected executor errors. Regression tests cover route identity, audit safety, simulations, plan JSON isolation, event names, policy continuation, and concise TUI output.

Deferred

  • durable execution journal and restart/resume: PR 14
  • idempotency keys and remote-state reconciliation: later runtime work
  • real platform execution and credential changes: out of scope

Validation

  • changed-file gofmt verification
  • go test -count=1 ./...
  • go vet ./...
  • go build ./cmd/vanish
  • scripts/check-safety.sh
  • git diff --check origin/main...HEAD
  • final diff scan for waits, unbounded retries, mutation/network paths, schema/status drift, route identity loss, and raw audit data

Final review fixes

  1. Runner-context cancellation

    • whole execution cancels only when the Runner.Run context is done
    • child context.Canceled and context.DeadlineExceeded errors fail safely as permanent failures
    • ordinary continue/stop-after-failure policy still applies; untouched actions are not cancelled
  2. Hard retry ceiling

    • automatic attempts remain one by default
    • configured attempts clamp to a documented maximum of five
    • zero, negative, normal, boundary, excessive, and actual-call limits are covered
  3. Runtime-owned provider messages

    • provider prose was replaced by a closed structured message identifier
    • normalized results, events, audit, and TUI use runtime-owned copy only
    • unknown, credential-like, token, cookie, JSON, HTML, URL, multiline, and terminal-control values cannot be copied to user-visible output
  4. Unique cleanup action IDs

    • CleanupPlan.Validate rejects duplicate action IDs with a clear indexed error
    • preview fails before provider resolution/prerequisites and execution produces no results or executor calls
    • result association remains keyed to validated unique IDs

Validation rerun: changed-file gofmt, go test -count=1 ./..., go vet ./..., go build ./cmd/vanish, scripts/check-safety.sh, and git diff --check origin/main...HEAD all pass.

Final runtime corrections

  1. Definitive results survive cancellation races

    • a valid nil-error typed result remains authoritative for the current action
    • runner cancellation is checked after that result is recorded
    • confirmed succeeded and already_satisfied actions stay done; only untouched remaining actions become cancelled
    • executor errors plus a cancelled runner context still cancel the current action
    • child-context errors with an active runner context remain permanent failures
  2. Provider code persistence is closed

    • ProviderCode is now a runtime-owned typed identifier with an explicit allowlist
    • temporary_failure remains available as a known diagnostic category
    • arbitrary API keys, OAuth codes, opaque values, IDs, URLs, statuses, and response text normalize to no code without changing outcome
    • audit defensively rechecks code membership before persistence
  3. Authentication guidance is concise

    • authentication_required renders one reconnect instruction instead of duplicate Message and Next step rows

Validation rerun: changed-file gofmt, go test -count=1 ./..., go vet ./..., go build ./cmd/vanish, scripts/check-safety.sh, and git diff --check origin/main...HEAD all pass.

Classify cancellation only from runner context, cap tight retry loops,
keep provider text runtime-owned, and reject duplicate action IDs.
Keep nil-error typed outcomes authoritative across cancellation races
and allow only runtime-owned provider code identifiers.
@itsmeares
itsmeares marked this pull request as ready for review July 12, 2026 22:28
@itsmeares
itsmeares merged commit 1309dff into main Jul 12, 2026
1 check passed
@itsmeares
itsmeares deleted the feature/typed-outcomes-safe-retry-lifecycle branch July 12, 2026 22:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant