Summary
#958 established that a withheld source is dropped and the watermark still advances, so the un-shipped rows stay in the local cache while the sink believes it has moved past them. That issue explicitly defers recovery ("separate ops task, not this issue"). This issue is that deferred half: there is no supported way to perform it.
Recovering a drop-but-advance gap requires hand-editing a private state file that the running daemon overwrites roughly once a minute. There is no verb for it, no --since, no dry-run, and no way to see whether the edit survived.
What's missing
hyp sink has exactly one subcommand:
Subcommands:
maintain Run export maintenance (snapshot expiration; data-file compaction with --compact)
Same in deployed 1.22.0 and current master 1.24.0. Nothing resends, rewinds, or resets a watermark. --since exists only on hyp backfill, which imports client history into the cache — the opposite direction.
So the only route is editing:
<stateDir>/plugins/@hypaware/central/sink-instances/central/watermarks/ai_gateway_messages/source=claude.json
{ "v":1, "continuation":{"v":1,"seq":"674822"}, "exportedRowCount":255086, "updatedAt":"..." }
Why the hand-edit races
forwardPartition re-reads the watermark from disk every tick (central/src/sink.js:218), which is good — an edit needs no restart. But the same tick rewrites it at the end (sink.js:346). Measured on the Neutral container:
04:03:28 seq 675930
04:04:37 seq 675948 → 69s apart
That is the entire window. Edit outside it and the next tick silently re-advances past the rewind; nothing reports that the edit was discarded, because from the sink's perspective nothing unusual happened.
Avoiding the race by stopping the daemon is not free: the daemon owns the capture gateway (127.0.0.1:18521), which on this box had live ESTABLISHED connections from running agents. Stopping it to fix an export bookkeeping value interrupts unrelated in-flight capture.
Concrete stakes
On Neutral today: 488,016 rows cached, exportedRowCount frozen at 255,086 since 08-12, ~236,650 rows recoverable. The correct resume point (last seq at/before the last-landed row 08-12T00:50:47Z) is 355254, verified against the complete 488,088-row table and cross-checked across four independent compaction generations, which agree exactly (next seq after the boundary: 355257).
Deleting the watermark instead is not a workaround: with no since, readRowsSince full-scans and includeLegacy defaults true, re-sending all 255k already-delivered rows on chunk boundaries that won't match the idempotency ledger (#958 makes the same point).
Fix directions
- A first-class verb, e.g.
hyp sink rewind <instance> --dataset <d> --partition <p> --to-seq <n> (or --since <timestamp>, resolving the seq internally, since operators reason in time, not seq).
- Make it safe against the tick: take the sink instance's lock, or apply the rewind through the driver rather than the file.
--dry-run reporting how many rows the rewind would re-send.
- Failing a verb: make the watermark file's hand-edit path documented and safe — detect an externally-modified watermark (compare against last-written value) and honor it rather than clobbering it.
- Related: an operator needs to know the seq for a wall-clock instant, but
_hyp_ingest_seq is in INTERNAL_FIELDS and stripped from both readRows and the query layer (SELECT _hyp_ingest_seq → "Column not found"), so deriving it today means reading the parquet files directly.
🤖 Generated with Claude Code
Summary
#958 established that a withheld source is dropped and the watermark still advances, so the un-shipped rows stay in the local cache while the sink believes it has moved past them. That issue explicitly defers recovery ("separate ops task, not this issue"). This issue is that deferred half: there is no supported way to perform it.
Recovering a drop-but-advance gap requires hand-editing a private state file that the running daemon overwrites roughly once a minute. There is no verb for it, no
--since, no dry-run, and no way to see whether the edit survived.What's missing
hyp sinkhas exactly one subcommand:Same in deployed 1.22.0 and current master 1.24.0. Nothing resends, rewinds, or resets a watermark.
--sinceexists only onhyp backfill, which imports client history into the cache — the opposite direction.So the only route is editing:
Why the hand-edit races
forwardPartitionre-reads the watermark from disk every tick (central/src/sink.js:218), which is good — an edit needs no restart. But the same tick rewrites it at the end (sink.js:346). Measured on the Neutral container:That is the entire window. Edit outside it and the next tick silently re-advances past the rewind; nothing reports that the edit was discarded, because from the sink's perspective nothing unusual happened.
Avoiding the race by stopping the daemon is not free: the daemon owns the capture gateway (127.0.0.1:18521), which on this box had live
ESTABLISHEDconnections from running agents. Stopping it to fix an export bookkeeping value interrupts unrelated in-flight capture.Concrete stakes
On Neutral today: 488,016 rows cached,
exportedRowCountfrozen at 255,086 since 08-12, ~236,650 rows recoverable. The correct resume point (last seq at/before the last-landed row 08-12T00:50:47Z) is 355254, verified against the complete 488,088-row table and cross-checked across four independent compaction generations, which agree exactly (next seq after the boundary: 355257).Deleting the watermark instead is not a workaround: with no
since,readRowsSincefull-scans andincludeLegacydefaults true, re-sending all 255k already-delivered rows on chunk boundaries that won't match the idempotency ledger (#958 makes the same point).Fix directions
hyp sink rewind <instance> --dataset <d> --partition <p> --to-seq <n>(or--since <timestamp>, resolving the seq internally, since operators reason in time, not seq).--dry-runreporting how many rows the rewind would re-send._hyp_ingest_seqis inINTERNAL_FIELDSand stripped from bothreadRowsand the query layer (SELECT _hyp_ingest_seq→ "Column not found"), so deriving it today means reading the parquet files directly.🤖 Generated with Claude Code