# Rewind contract guardrail inventory

This inventory records the current rewind boundaries that must stay explicit
while the implementation is refactored. It is intentionally durable: do not use
PR, task, branch, or agent names as normative context.

## Locked characterization

- Rewind is the only server-side operation that means “return this conversation
  to before a target message”. Message-side apply is not a physical transcript
  delete: suffix rows in `scope_messages` remain as `rewound` lifecycle
  tombstones with scrubbed content and metadata.
- `PrepareRewindForUser` / `ValidateRewindPlan` build a suffix from non-rewound
  `scope_messages`, reject suffix drift before apply, and keep reply-thread
  cascade validation scoped to active child `scopes` topology.
- `rewindplan.SuffixBoundary` is the typed suffix lifecycle contract: one
  primary `scope_id` boundary plus explicit cascade reply-thread boundaries.
  Parent/broadcast scopes are never tombstone boundaries unless represented as
  boundaries.
- `ApplyRewind` delegates suffix fact mutation to `message.RewindScopeSuffixTx`
  after agent recovery succeeds.
- `RewindScopeSuffixTx` tombstones suffix message rows and scrubs message-bound
  durable projections: `scope_share_cards`, `scope_message_reactions`,
  `scope_message_pins`, `scope_message_top_notices`,
  `scope_message_search_docs`, `scope_message_sources`, attachments,
  durable `im_stream_events`, durable `im_conversation_index`, and consumed
  message recovery points.
- Rewind read-visibility is transcript-last: share/reaction/pin/top/search/source
  projections are removed before `scope_messages` is updated to the rewound
  tombstone. The server-side projection/read-surface contract names the
  projection-backed surfaces separately from transcript-authority read surfaces:
  read/history, attachment read-check, root/source context, search, receive,
  delivery diagnostics, diagnostics preview, message sync, and durable stream
  replay must treat `messageRecall.status=rewound` as authoritative invisibility.
- Rewind read-surface regression covers search, attachment read-check and
  attachment row removal, source-context projection removal, delivery diagnostics
  and diagnostics preview, share-card action state, receive, message-sync payload
  disappearance, durable stream/conversation projection scrub, and post-rewind
  physical seq high-water behavior for new live messages.
- Rewind suppresses only unfinished `scope_fanout_outbox` rows (`pending` and
  `leased`) and suppresses matching `scope_activity_events`; already delivered
  outbox rows remain historical delivery facts and must not resurrect rewound
  messages in read surfaces.
- Rewind recomputes visible `scopes` summary fields and clamps
  `scope_memberships.last_read_seq`, `last_read_global_seq`, and
  `owner_context_dismissed_through_global_seq` to the post-rewind visible tail;
  physical `latest_seq` / `latest_global_seq` remain high-water values to avoid
  seq reuse.
- Rewind clamps `scope_sync_dirty` and `member_scope_sync_dirty`
  `latest_message_seq` to the post-rewind visible tail while preserving
  non-message activity dirty frontiers. Message sync is not a tombstone page;
  rewound suffix visibility is removed by lifecycle metadata, projection scrub,
  dirty frontier clamps, and direct invalidation.
- `rewindplan.OperationStatus` owns the durable operation lifecycle
  (`preparing`, `completed`, `failed`, `repair_required`). Completion must be
  written via the typed `OperationStatusCompleted` path, not an ad-hoc string
  literal in apply code.
- The API emits direct `conversation:rewound` invalidation after DB apply; it
  does not create a message, reaction activity, runtime event, or message
  fanout outbox event for the rewind itself.
- Browser recovery for `conversation:rewound` is generation-bound invalidation,
  not a frontend suffix patch: advance the local message-sync cursor generation,
  invalidate any outstanding websocket resume generation, clear in-memory and
  persisted opaque message-sync cursors, invalidate/refetch conversation and
  summary caches, then bootstrap a fresh-generation message sync.
- Stale HTTP `syncGap` / `syncMessages` responses and websocket
  `sync:resume:response` payloads must return before they persist cursors,
  merge payload messages, or emit `hasMore` follow-up resumes; stale responses
  queue a fresh-generation sync instead of reviving the old cursor frontier.

## Current debt to keep visible

- Rewind plan scope roles are explicit in code: `ScopeID`/`RewindScopeID` is the
  primary storage/tombstone scope, `OperationScopeID` serializes the durable
  operation even though the DB column remains `thread_id`, `BroadcastScopeID`
  owns coarse realtime invalidation, `ReplyThreadScopeID` owns reply-thread
  state recompute, and cascade reply-thread `ScopeID` owns child tombstone.
  Plan structs no longer carry alias fields such as `ChannelID`,
  `BroadcastChannelID`, `ReplyThreadID`, `ThreadChannelID`, or `ThreadID`; any
  DB/wire compatibility names must be translated before constructing the typed
  rewind plan. The guardrail rejects reintroducing those aliases or direct
  business-logic reads instead of typed scope accessors.
- Rewind realtime is a coarse direct invalidation payload. The projection/read-surface
  contract names the projection-backed surfaces separately from
  transcript-authority surfaces for invalidation and refetch, but it is not a typed per-client payload;
  it does not expose a typed per-surface projection-impact payload, and message sync
  must not grow a tombstone-page contract as a hidden substitute.
- Runtime recovery capability lives in the core capability map plus daemon
  restore implementations; runtime IDs and recovery strategies are separate
  constants in `internal/core/rewindcaps`. New runtimes must update capability
  tests and must not mark themselves supported until a restore strategy can
  actually recover live or detached sessions and LAN E2E verifies the behavior.
  Supported runtime implementations are currently `codex`, `claude`, and
  `opencode`; `builtin` follows OpenCode capability. LAN E2E verifies the behavior
  for Codex, Claude, OpenCode, and builtin before release checks exist;
  recovery strategy names such as `claude_session_resume_at` and
  `opencode_session_revert` are not runtime IDs.
- Completed delivery facts may reference rewound message ids after the message
  row is tombstoned. Diagnostics and read paths must treat the rewound lifecycle
  metadata as authoritative and must not surface historical rows as live content;
  diagnostics queries start from readable `scope_messages` and must not join
  historical outbox/activity/runtime/recovery evidence to reconstruct a message;
  suppressed or delivered outbox/runtime evidence is never sufficient to
  resurrect a rewound suffix in diagnostics.

## Refresh procedure

1. Run `guardrails/message/rewind.sh --check-only` after changing rewind
   planning, apply, API, read-surface, realtime, or related specs.
2. Run the focused Go tests named by the guardrail output when changing code.
3. Update this inventory when a debt item is resolved or a new durable boundary
   is introduced.
