docs(localdb-phase2): pre-answer Task 0's STOP condition — it does not fire

Phase 2's recon carries a STOP condition: a BLOB payload column in the legacy
alarm store-and-forward table could not be registered for replication and would
have to become base64 TEXT, sized against the chunk guidance. Resolved ahead of
execution so the answer survives into the Phase 2 session:

- No BLOB. All 8 columns are TEXT/INTEGER; the payload is PayloadJson TEXT NOT
  NULL. No base64 conversion, no chunk sizing needed.
- The executed DDL (SqliteStoreAndForwardSink.cs:657-667) matches the class
  doc-comment at :17-26 exactly — checked because a doc comment is not evidence.
- Payload is bounded by shape: a serialized AlarmHistorianEvent is 10 scalar
  fields, no collections or nesting, so realistic worst case is low single-digit
  KB against a 171 KB guidance.
- The PK IS autoincrement, confirming the plan's prediction that the migrator
  must mint deterministic mig-{node}-{legacyId} ids and the new table needs a
  TEXT GUID PK.
- Also captured for reuse: the drain's covering index (DeadLettered, RowId), and
  the exact legacy column list the migrator's pragma_table_info intersection
  check compares against.

Claude-Session: https://claude.ai/code/session_01GASWkNEi68FSCtvr6rLoEW
This commit is contained in:
Joseph Doherty
2026-07-21 03:46:56 -04:00
parent d218282cd6
commit f2049a31d0
@@ -56,6 +56,29 @@ auth already in place, no in-memory SQLite in tests, cp-triplet-only rig inspect
(**STOP condition:** a BLOB payload column cannot be registered — it must become base64 TEXT in
the new schema, and the recon must size the largest realistic payload against the 171 KB-ish
chunk guidance; alarm events are small JSON, so expect this to be fine, but verify).
> **PRE-ANSWERED 2026-07-21 (verified against `master` `d218282c`) — the STOP condition does NOT
> fire.** Re-verify cheaply during recon, but do not expect a surprise:
>
> - **Executed DDL:** `SqliteStoreAndForwardSink.cs:657-667`. It matches the class doc-comment at
> `:17-26` exactly — no drift between the documented and executed schema.
> - **No BLOB.** All 8 columns are TEXT/INTEGER; the payload column is **`PayloadJson TEXT NOT
> NULL`**. So no base64 conversion is needed and the chunk-size sizing is moot.
> - **Payload is small and bounded by shape.** `PayloadJson` is a serialized
> `AlarmHistorianEvent` — 10 scalar fields (`AlarmId`, `EquipmentPath`, `AlarmName`,
> `AlarmTypeName`, `Severity`, `EventKind`, `Message`, `User`, `Comment?`, `TimestampUtc`).
> No collections, no nesting. Realistic worst case is low single-digit KB (operator `Comment`
> is the only unbounded-ish field), far under the 171 KB chunk guidance.
> - **PK IS autoincrement** — `RowId INTEGER PRIMARY KEY AUTOINCREMENT`. This confirms the
> plan's own prediction: LocalDb cannot replicate an autoincrement key, so the migrator
> **must** mint deterministic `mig-{node}-{legacyId}` ids, and the new table needs a TEXT
> GUID PK as already specified in the Architecture note.
> - **One index to carry across:** `IX_Queue_Drain ON Queue (DeadLettered, RowId)` (`:667`) —
> the drain's covering index. The `alarm_sf_events` equivalent wants the same shape over
> (dead-lettered flag, insertion order) so the drain query stays index-covered.
> - **Legacy column list for the migrator's `pragma_table_info` intersection check:**
> `RowId, AlarmId, EnqueuedUtc, PayloadJson, AttemptCount, LastAttemptUtc, LastError,
> DeadLettered`.
2. The public seam: the interface the drain worker and producers use (e.g. `IAlarmHistorianSink` /
enqueue+dequeue+markDelivered+deadLetter methods), so the rewire can keep it byte-compatible.
3. Semantics to preserve: `Capacity` (1,000,000) enforcement, `MaxAttempts` (10), dead-letter