Record trouble tickets as events: event, command, projection, ingest driver - #296
Merged
Merged
Conversation
…driver First stage of migrating trouble tickets from the Google Sheets poll into the event log, mirroring the training-quiz migration (#275). Salvaged from the stale feature/trouble-tickets-board branch and aligned to the quiz conventions: - TroubleTicketCreated event storing raw sheet facts only; member and equipment resolution happen downstream. - record command with dedup by rowHash; codec stricter than the event. - troubleTickets read-model table (unique rowHash) projecting status Todo, title from the issue text, and equipment resolved by name (null = Unassigned). - Candidate reader + stable rowHash over parsed answer *values* (not the cached JSON string, whose keys are the sync worker's question strings and would otherwise become a frozen wire format). - Sequential ingest driver attributed to the system actor. Deliberately not wired into the sync worker and not exposed over HTTP: appending claims each row's hash at recordedAt=now, which would prevent the one-time timeline backfill from weaving rows in at their historical submittedAt. The poller is wired up only after the backfill has run on prod. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This was referenced Sep 18, 2026
Review finding: after the timeline weave, historical tickets replay before the EquipmentAdded events for the machines they name, so name resolution at TroubleTicketCreated time found nothing and every backfilled ticket landed permanently in the Unassigned bucket. When equipment is added, link any still-unresolved tickets whose submitted string matches its name. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Audit finding: soft-deleting a TroubleTicketCreated event (e.g. for a data-removal request) removed its rowHash from the read model, so the ingest dedup saw the cached sheet row as new and silently re-imported the same PII as a fresh event on the next sync cycle. Deleted ticket events now record their rowHash in a suppression table that hasRowHash also consults. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
First stage of migrating trouble tickets from the Google Sheets poll into the event log, mirroring the training-quiz migration stack (#275 → #276 → #285 → #287). Salvaged from the stale
feature/trouble-tickets-boardbranch and aligned to the quiz conventions.What this adds
TroubleTicketCreatedevent storing raw sheet facts only (submitter-provided identity is unverified free-form input; member and equipment resolution happen downstream).troubleTickets.recordcommand with dedup byrowHash; the command codec is stricter than the event codec (NonEmptyStringhash,t.Intmember number).troubleTicketstable with uniquerowHash; every ticket starts asTodowith its title defaulted from the form's issue text; the raw equipment string is resolved case/whitespace-insensitively to a known equipment record, with a miss leavingequipmentIdnull (the "Unassigned" bucket).submitted_response_jsonstring — the JSON keys are the sync worker's question strings (including two typo-truncated ones), which would otherwise become a frozen wire format. Excludesrow_indexandcached_atso re-pulls and sheet edits don't re-import rows.What this deliberately does NOT do
The driver is not wired into the sync worker and not exposed over HTTP. Appending claims each row's hash at
recordedAt = now, which would permanently prevent the follow-up one-time timeline backfill from weaving historical tickets in at their true submission times (the same trap the quiz migration hit in review). The poller is wired up in a later PR, only after the backfill has run and been verified on prod.Stack
/trouble-ticketsPOC page🤖 Generated with Claude Code