@@ -252,18 +252,23 @@ func (s *SQSEncoder) HandleSideRecord(prefix string, key, value []byte) error {
252252}
253253
254254// Finalize flushes every queue's _queue.json and messages.jsonl. Queues
255- // with buffered messages but no meta record (orphans) emit a warning and
256- // are skipped — restoring orphan messages without a queue config would
257- // silently create a queue with default settings, which is rarely what
258- // the operator wants.
255+ // with buffered messages but no meta record (orphans) emit a warning
256+ // and have their messages dropped — restoring orphan messages without
257+ // a queue config would silently create a queue with default settings,
258+ // which is rarely what the operator wants. However, if
259+ // --include-sqs-side-records is on and this orphan queue has buffered
260+ // side records (vis/byage/dedup/group/tombstone), those are still
261+ // flushed under the encoded-prefix directory: the most common reason
262+ // for a missing meta is a DeleteQueue that left tombstones, and
263+ // dropping exactly those records is the opposite of what the operator
264+ // asked for. Codex P2 round 8.
259265func (s * SQSEncoder ) Finalize () error {
260266 var firstErr error
261267 for _ , st := range s .queues {
262268 if st .meta == nil {
263- s .emitWarn ("sqs_orphan_messages" ,
264- "encoded_queue" , st .encoded ,
265- "buffered_messages" , len (st .messages ),
266- "hint" , "no !sqs|queue|meta record matched this encoded prefix; messages dropped from the dump" )
269+ if err := s .flushOrphanQueueSideRecords (st ); err != nil && firstErr == nil {
270+ firstErr = err
271+ }
267272 continue
268273 }
269274 if err := s .flushQueue (st ); err != nil && firstErr == nil {
@@ -273,6 +278,30 @@ func (s *SQSEncoder) Finalize() error {
273278 return firstErr
274279}
275280
281+ // flushOrphanQueueSideRecords emits buffered side records for a queue
282+ // whose !sqs|queue|meta row never arrived. Without this branch,
283+ // --include-sqs-side-records silently drops the post-DeleteQueue
284+ // tombstones and dedup-window history operators most often opt in
285+ // for. The orphan dir is named by the encoded prefix because no
286+ // decoded queue name is available; restore tools can join it with
287+ // the messages-dropped warning to reconstruct context.
288+ func (s * SQSEncoder ) flushOrphanQueueSideRecords (st * sqsQueueState ) error {
289+ s .emitWarn ("sqs_orphan_messages" ,
290+ "encoded_queue" , st .encoded ,
291+ "buffered_messages" , len (st .messages ),
292+ "buffered_side_records" , len (st .internalBuf ),
293+ "hint" , "no !sqs|queue|meta record matched this encoded prefix; messages dropped from the dump" )
294+ if ! s .includeSideRecords || len (st .internalBuf ) == 0 {
295+ return nil
296+ }
297+ // Use the encoded prefix as the directory name — it's the only
298+ // stable identifier available when meta is missing. Suffix it
299+ // with `.orphan` so a restore tool cannot mistake it for a real
300+ // queue dir produced from a successful meta flush.
301+ dir := filepath .Join (s .outRoot , "sqs" , st .encoded + ".orphan" )
302+ return s .flushInternals (dir , st .internalBuf )
303+ }
304+
276305func (s * SQSEncoder ) flushQueue (st * sqsQueueState ) error {
277306 dir := filepath .Join (s .outRoot , "sqs" , EncodeSegment ([]byte (st .name )))
278307 if err := os .MkdirAll (dir , 0o755 ); err != nil { //nolint:mnd // 0755 == standard dir mode
0 commit comments