Skip to content

feat(reveal-sessions-expired): worker sweeper closes reveal-session windows (Slice M3)#9

Merged
haydercyber merged 1 commit into
mainfrom
feat/m3-reveal-sessions-expired-sweeper
Jun 4, 2026
Merged

feat(reveal-sessions-expired): worker sweeper closes reveal-session windows (Slice M3)#9
haydercyber merged 1 commit into
mainfrom
feat/m3-reveal-sessions-expired-sweeper

Conversation

@haydercyber
Copy link
Copy Markdown
Contributor

Closes #8. Part of secrets-bridge/api#71 (EPIC Slice M).

Summary

Periodic sweeper that turns reveal_sessions.expires_at into a hard boundary on wrap retrievability. Stamps the session row as expired (reason='ttl') via the M1 storage layer's atomic UPDATE … RETURNING, then advances every wrap_id from the swept sessions to expires_at=now so any post-expire retrieve returns ErrExpired — even if the SPA still holds the wrap_id across a tab refresh.

Sweeper surface

RevealSessionsExpired{Sessions, Wraps, Notifier, Now}.Run(ctx)

Wired into cmd/worker/main.go alongside the existing five sweepers (wraps-expired, secrets-stale, agents-stale, jobs-recovery, discover-scheduler).

Config knob

SB_WORKER_REVEAL_SESSIONS_EXPIRED_INTERVAL=5s   # default

5s is faster than the default 60s used by other sweepers because reveal sessions are user-facing — a session ending late means a wrap could still be retrievable after the SPA already nuked its plaintext display. The underlying query is a single indexed UPDATE … RETURNING so the cadence cost is negligible.

Failure semantics

Path Behavior
Sweep failure Return error; error-severity notification; no wrap mutation attempted.
Per-wrap SetExpiry failures Joined via errors.Join; warn-severity notification with sessions_swept + wraps_expired + wrap_errors counts; a single broken wrap MUST NOT drop the rest of the sweep.
Zero rows No notification (matches WrapsExpired's posture).

Closes the session-level invariant

M1 (api):   schema + atomic UPDATE … RETURNING (sweep contract)
M2 (api):   Open peeks wraps; SPA fetches plaintext via single-shot endpoint
            during a window bounded by reveal_sessions.expires_at
M3 (this):  Past expires_at? → session row flipped expired + every wrap_id
            stamped expires_at=now → any further retrieve → ErrExpired (410)
M4 (ui):    countdown + auto-hide (consumes the same expires_at)

Tests (5 unit tests, all passing under -race)

  • Happy path: 2 sessions / 3 wraps → SweepExpired called once, SetExpiry called 3× with now timestamp, info notification fires.
  • Idempotent zero-rows: empty sweep result → no SetExpiry calls, no notifications.
  • Partial wrap-expiry failure: 1 of 3 wraps fails SetExpiry → error returned, all 3 attempted (no early exit), warn notification with 'wrap-expiry errors'.
  • Sweep failure: storage error → error returned, no wrap mutation, failure notification.
  • Name() returns 'reveal-sessions-expired'.

Test plan

  • go build ./... clean
  • go vet ./... clean
  • go test -race -count=1 ./... all packages green
  • go mod tidy (no drift)
  • M4 (ui#60 — bulk reveal page) consumes the contract via expires_at countdown

Dependencies

…indows (Slice M3)

Periodic sweeper that turns reveal_sessions.expires_at into a hard
boundary on wrap retrievability. Stamps the session row as expired
(reason='ttl') via the M1 storage layer's atomic UPDATE … RETURNING,
then advances every wrap_id from the swept sessions to expires_at=now
so any post-expire retrieve returns ErrExpired — even if the SPA still
holds the wrap_id across a tab refresh.

Service surface:

  RevealSessionsExpired{Sessions, Wraps, Notifier, Now}.Run(ctx)

Wired into cmd/worker/main.go alongside the existing five sweepers
(wraps-expired, secrets-stale, agents-stale, jobs-recovery,
discover-scheduler). Defaults:

  SB_WORKER_REVEAL_SESSIONS_EXPIRED_INTERVAL=5s

5s is faster than the default 60s used by other sweepers because
reveal sessions are user-facing — a session ending late means a wrap
could still be retrievable after the SPA already nuked its plaintext
display. The query is a single indexed UPDATE … RETURNING so the
cadence cost is negligible.

Failure semantics:
  - Sweep failure → error returned, error-severity notification, no
    wrap mutation attempted.
  - Per-wrap SetExpiry failures → joined via errors.Join, warn-severity
    notification with sessions_swept + wraps_expired + wrap_errors
    counts; a single broken wrap MUST NOT drop the rest of the sweep.
  - Zero rows → no notification (matches WrapsExpired's posture).

Tests (5 unit tests under -race):
  - happy path: 2 sessions / 3 wraps → SweepExpired called once,
    SetExpiry called 3× with now timestamp, info notification fires
  - idempotent zero-rows: empty sweep result → no SetExpiry calls,
    no notifications
  - partial wrap-expiry failure: 1 of 3 wraps fails SetExpiry → error
    returned, all 3 attempted (no early exit), warn notification with
    'wrap-expiry errors'
  - sweep failure: storage error → error returned, no wrap mutation,
    failure notification
  - Name() returns 'reveal-sessions-expired'

Closes #8. Part of secrets-bridge/api#71 (EPIC: Slice M).
Depends on: M1 (secrets-bridge/api#82 — schema + RevealSessionRepository
+ SweepExpired), M2 (secrets-bridge/api#83 — service contract that
opens the sessions this sweeper closes).
@haydercyber haydercyber merged commit a98c54a into main Jun 4, 2026
4 checks passed
@haydercyber haydercyber deleted the feat/m3-reveal-sessions-expired-sweeper branch June 4, 2026 19:59
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.

Slice M3 — reveal-sessions-expired sweeper

1 participant