Skip to content

Record trouble tickets as events: event, command, projection, ingest driver - #296

Merged
JamesMoulang merged 4 commits into
mainfrom
feature/tt-events
Sep 18, 2026
Merged

JamesMoulang merged 4 commits into
mainfrom
feature/tt-events

Conversation

@JamesMoulang

Copy link
Copy Markdown
Collaborator

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-board branch and aligned to the quiz conventions.

What this adds

  • TroubleTicketCreated event storing raw sheet facts only (submitter-provided identity is unverified free-form input; member and equipment resolution happen downstream).
  • troubleTickets.record command with dedup by rowHash; the command codec is stricter than the event codec (NonEmptyString hash, t.Int member number).
  • Read-model projection: troubleTickets table with unique rowHash; every ticket starts as Todo with 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 leaving equipmentId null (the "Unassigned" bucket).
  • Candidate reader + stable row hash. The hash covers the parsed answer values rather than the cached submitted_response_json string — the JSON keys are the sync worker's question strings (including two typo-truncated ones), which would otherwise become a frozen wire format. Excludes row_index and cached_at so re-pulls and sheet edits don't re-import rows.
  • Sequential ingest driver attributed to the system actor.

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

  1. This PR — data layer
  2. Timeline backfill + runbook (next)
  3. Going-forward poller + repoint the /trouble-tickets POC page
  4. Status workflow → board UI → email notifications

🤖 Generated with Claude Code

…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>
JamesMoulang and others added 3 commits September 18, 2026 15:10
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>
@JamesMoulang
JamesMoulang merged commit 9ed1e18 into main Sep 18, 2026
12 checks passed
@github-project-automation github-project-automation Bot moved this from Options to Done in Makespace Members App Sep 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

1 participant