Files

6.6 KiB

Full-sync shared contracts

This package establishes the boundary for later queue accounting, archive, replay, and HTTP work. It does not change running service behavior. Production code in this package does not import the queue package; compatibility tests compare its models and keys with the existing queue implementation.

Compatibility and counters

  • RunMeta retains all existing JSON fields and adds optional fields. Existing zset.RunMeta, zset.Task, and zset.FailureRecord remain unchanged; adapters are a later integration step. The new RunStatus is a named string type.
  • Validate accepts missing extension fields on old records. ValidateAccounting is opt-in for runs with known extended counters and enforces enqueuedCount = successCount + failedCount + activeCount + nextRoundCount. inflightCount is a subset of activeCount.
  • Source cleanup changes pendingFailureCount, never historical failedCount.
  • Round statistics have no task/card/channel dimensions. ErrorCounts normalizes both JSON input and output to eight canonical categories, merging unknown names into other and rejecting negative/overflowing totals.
  • Existing failure payloads retain their free-form classification and message. The HTTP Failure projection normalizes categories and bounds messages to 2,048 UTF-8 bytes. Historical blocked tasks may have round zero.

Status domains

Domain Wire values
Run producing, running, completed, completed_with_failures
Sync type normal, replay
Archive disabled, pending, processing, succeeded, failed
Source replay disposition none, pending, processing, completed, needs_attention
Replay action accepted, processing, cleanup_pending, receipt_pending, completed, needs_attention
Error category timeout, rate_limited, authentication, validation, upstream, transport, internal, other

The source field is replayDisposition, distinct from the replay action's status. Lease tokens, record versions, action revision, idempotency key, and request fingerprint are internal and excluded from HTTP JSON.

Interfaces and persistence obligations

HTTP consumers depend on RunReader, FailurePager, ReplayRequester, and ActionReader. Separate live/archive readers support fallback only after a live ErrNotFound; storage failures must remain visible. An archived summary cannot provide per-card details: use ErrFailureDetailsUnavailable when live details are gone. Repositories and HTTP both apply the default page limit 100 and maximum 500. Cursors remain opaque and are bound to the original query. An empty scan page with a next cursor does not signal completion.

Archive discovery, scheduling, snapshot loading, and result registration use separate narrow interfaces. Registration is conditional on an opaque work version. Archives contain run and round aggregates only. Archive receipts use a schema version, storage-owned path, lowercase SHA-256 checksum of stored bytes, and publication time. Archive schema version starts at 1.

Replay acceptance atomically reserves (sourceRunId, idempotencyKey) with a SHA-256 fingerprint of the source and exact requestedBy body value. Repeating the request returns its original action/target; changing the request returns ErrConflict. The action fixes its target ID before target creation/enqueue.

Replay work discovery, lease management, progress updates, source pagination, target creation/enqueue, and source cleanup are separate interfaces. Progress updates are fenced and use a revision check. Target enqueue distinguishes durably present duplicates from rejected writes; a zero insert count by itself does not authorize source cleanup. Cleanup checks exact source record versions and target acceptance evidence. Implementations must persist enough evidence to recover partial batches; a scan cursor by itself is insufficient. Source pagination must remain correct while already-enqueued entries are deleted.

ErrNotFound, ErrConflict, ErrInvalidArgument, ErrFailureDetailsUnavailable, ErrLeaseLost, and ErrBusy support errors.Is through wrapping. HTTP error envelopes and status mappings remain the HTTP implementation's responsibility.

Redis naming

All names are prefixed with the configured namespace (flow: by default; sim-status: for status sync). Existing run/round/task-state/failure names are preserved. New keys include:

Suffix after namespace Scope
{runId}:round:N:stats Run slot
{runId}:archive Run slot
runs:all Global index
archive:pending Global index
{replay}:action:actionId:meta Replay slot
{replay}:action:actionId:lease Replay slot
{replay}:idempotency:SHA256(sourceRunId,key) Replay slot; tuple encoded as a JSON array before hashing
{replay}:pending Replay slot

Replay action creation, idempotency, and discovery can be atomic within their shared slot. Source/target runs and global indexes can occupy different slots; their coordination requires recoverable registrations/receipts rather than a single cross-slot Lua transaction. New identifier validation rejects hash-tag or path delimiters without rewriting legacy JSON on decode.

HTTP surface

Method Route Response
GET /internal/v1/full-sync-runs?syncKind=&limit= RunList merged from active Redis Runs and run-<runId>.json archives
GET /internal/v1/full-sync-runs/{runId} RunView with source, run, optional rounds
GET /internal/v1/full-sync-runs/{runId}/failures?cursor=&limit= FailurePage with items, nextCursor
POST /internal/v1/full-sync-runs/{runId}/replay HTTP 202 ReplayAccepted
GET /internal/v1/full-sync-actions/{actionId} ReplayAction

POST requires Idempotency-Key and JSON {"requestedBy":"..."}. Authentication, HTTP handlers, storage, replay execution, archive files, and service wiring are intentionally left to subsequent implementations.

Verification

gofmt -l internal/fullsync/contract
go test ./internal/fullsync/contract
go test ./internal/queue/zset ./internal/consumer/zset ./internal/producer/simstatus ./internal/producer/monthlyusage ./internal/service/simstatus ./internal/service/monthlyusage

Tests cover legacy schema and JSON compatibility, existing key compatibility, cluster key boundaries, bounded categories/messages, overflow, run accounting, pagination limits, status domains, replay identity, internal field exclusion, and receipt/progress validation. Regression tests use the repository's isolated Redis helper and require redis-server or REDIS_SERVER_BINARY.