This is a demonstration deliverable — a fictional project showing what a faithful execution of
project-interview-docsproduces. It models the discipline, not just the shape: metrics carry evidence labels, contribution uses grounded verbs, a drift note is resolved, and one evidence gap is left as a gap rather than fabricated. Project: a service that reconciles a payment provider's webhook stream against an internal ledger. Candidate role used in this example: implemented the reconciliation core; integrated with others on the webhook receiver.
A subscription company's internal ledger drifted out of sync with its payment provider: webhooks arrived out of order, were redelivered, and occasionally lost, so finance could not trust the ledger's view of who had paid. The reconciliation service replays the provider's event stream, matches each event to the ledger, and emits a daily exception list of unmatched or duplicated transactions for a human operator to resolve. Built in Go on PostgreSQL, it runs as a scheduled worker and exposes a read-only exception API. The candidate implemented the matching engine and integrated with others on the webhook ingestion path.
(~95 words — note: over the 30-second band; trim the next two for the spoken version.)
Provider API ──▶ Webhook receiver (team-owned) ──▶ event_store (PostgreSQL)
│
reconciliation worker ──────┘ (scheduled, idempotent)
│
ledger (PostgreSQL) ◀────── match & compare
│
exception_store ───────────┘ ──▶ exception API (read-only) ──▶ operator UI
Responsibilities, not names:
- Webhook receiver (team-owned, not the candidate's): accepts provider events, writes
them to
event_storewith the provider's event id and timestamp. Out-of-order and redelivered events are accepted as-is; ordering is the reconciler's problem. - Reconciliation worker (candidate's core): for each event not yet reconciled, loads the
corresponding ledger record, classifies the pair as
matched,unmatched, orduplicated, and writes the result toexception_store. - Ledger: the source of truth for what the system believes the customer was charged. Append-only; corrections are new rows, never updates.
- Exception API: read-only view of
exception_store; the operator UI fetches the daily exception list and a human resolves each row.
State locations: event_store and ledger are durable (PostgreSQL); exception_store is
durable but derived — it can be rebuilt from the other two. No caching layer; the workload is
batch, not low-latency.
scheduled trigger → load unreconciled events → for each: load ledger record →
classify (matched / unmatched / duplicated) → write exception or mark reconciled →
observable outcome: daily exception list shrinks as the operator resolves rows
One event, end to end:
- Trigger. The worker runs on a 5-minute schedule (verified:
workercron entry + the schedule config). - Load unreconciled. It selects events from
event_storewith no matchingreconciliationrow, ordered by provider timestamp. - Load ledger record. For each event, it fetches the ledger row keyed by the provider's transaction id.
- Classify.
matched: ledger amount and status agree with the event.unmatched: the event has no ledger row (we were charged but recorded nothing).duplicated: the event was already reconciled (provider redelivery).
- Write.
matchedmarks the event reconciled;unmatchedandduplicatedwrite a row toexception_storewith the classification and both timestamps. - Outcome. The operator UI shows the new exceptions; a human decides whether to post a ledger correction or mark the exception resolved.
Branches that change the outcome: only the three classifications above. The worker does
not retry provider calls (it reads only event_store, not the provider) and does not
auto-correct the ledger — both are deliberate, addressed under core decisions.
Problem. Webhooks are redelivered; running the same event twice must not create a second exception or double-count.
Decision. The reconciliation row is keyed by the provider's event id. Re-processing an
event that already has a reconciliation row short-circuits to duplicated.
Mechanism. A UNIQUE constraint on (provider_event_id) plus an
INSERT ... ON CONFLICT DO NOTHING. The worker treats a conflict as "already reconciled."
Tradeoff. Rejected alternative: dedupe in application memory. Delta: in-memory dedupe
survives only one process lifetime; a restart re-processes the redelivery. The DB constraint
survives restarts and concurrent workers. Failure mode: if two workers race on the same event,
exactly one wins the insert; the other sees duplicated — correct, not a bug.
Validation. Integration test: replay a redelivered event 100× and assert exactly one reconciliation row.
Problem. The reconciler could fetch the "truth" live from the provider, or could post corrections to the ledger directly. Both are tempting and both are dangerous.
Decision. The worker reads only event_store (written by the receiver) and writes only
to exception_store. It neither calls the provider nor mutates the ledger.
Mechanism. The worker's database role has SELECT on event_store/ledger and
INSERT on exception_store only — enforced at the DB grant level, not just in code.
Tradeoff. Rejected alternative: live-fetch the provider on unmatched to "self-heal."
Delta: live-fetch hides receiver gaps and creates a runtime dependency on provider uptime;
the chosen design surfaces gaps as exceptions for a human, making loss visible. Failure mode:
if the provider is down, reconciliation still completes from event_store.
Validation. The DB grant is asserted by a test that attempts a forbidden write and expects a permission error.
Problem. Mutating a ledger row to "fix" a reconciliation error destroys the audit trail of what was believed when.
Decision. The ledger is append-only. A correction is a new row referencing the original; the current view is the latest row per transaction.
Mechanism. Ledger rows are immutable; view_ledger_current is a ROW_NUMBER() OVER (PARTITION BY transaction_id ORDER BY created_at DESC) view.
Tradeoff. Rejected alternative: in-place update. Delta: in-place is simpler to query but loses history; append-only costs a slightly more complex read but preserves an audit trail — a requirement for finance. Failure mode: a bug that appends a spurious correction is visible (excess rows) rather than silent (overwritten value).
Validation. Property test: the count of ledger rows for a transaction is monotonically non-decreasing across all operations.
-
Out-of-order events produced false
unmatchedexceptions. Symptom: high exception volume where the ledger row arrived seconds after the event. Diagnosis: the worker compared the event to the ledger at the moment of processing, with no grace window. Options: (a) a grace period before classifyingunmatched, (b) re-run reconciliation on the next cycle, (c) block until ledger catches up. Final choice: (b) re-run on next cycle —unmatchedis provisional until two consecutive cycles confirm it. Cost: exceptions appear one cycle later than strictly necessary. Verification: the false-positive rate dropped to near-zero in the integration replay suite. -
A provider schema change silently broke parsing. Symptom: events stopped matching;
unmatchedspiked. Diagnosis: the provider added a nullable field; the parser treatednullas a missing transaction id. Options: (a) pin to a provider API version, (b) make the parser fail loud on unknown shapes. Final choice: both — pin the version header and add a strict parser that rejects unknown fields. Cost: legitimate new fields now require a deploy. Verification: the parser's strict mode is covered by a contract test against a captured provider schema sample. -
Concurrent workers double-processing events. Symptom: intermittent
duplicatedrows during scale testing. Diagnosis: two workers picked up the same event. Options: (a) a distributed lock, (b) theUNIQUEconstraint already chosen in Decision A. Final choice: rely on the constraint — no lock needed. Cost: one worker does wasted work. Verification: a concurrency test with N workers produces exactly one reconciliation row per event.
We had a trust problem: our ledger disagreed with the payment provider because webhooks arrive out of order and sometimes get redelivered. I built a reconciliation service that replays the event stream, matches each event to the ledger, and flags unmatched or duplicated transactions for a human to resolve. It's idempotent, so redelivery is safe, and it never auto-corrects the ledger — it surfaces the gaps instead of hiding them.
(~70 words.)
The system has three durable stores and one worker. The webhook receiver — owned by the
team, not me — writes every provider event to event_store exactly as it arrives, including
redeliveries and out-of-order events. Ordering is deliberately the reconciler's problem. I
implemented the reconciliation worker: on a five-minute schedule it loads unreconciled events,
fetches the matching ledger row by transaction id, and classifies the pair as matched,
unmatched, or duplicated. Matched events are marked reconciled; the other two write a row to
exception_store. The operator UI reads exception_store read-only and a human resolves each
row by posting a ledger correction or marking it resolved.
Two decisions matter most. First, reconciliation is idempotent on the provider's event id — a
unique constraint plus ON CONFLICT DO NOTHING means a redelivered event is processed exactly
once. Second, the worker has no grants to call the provider or to mutate the ledger; it only
reads event_store and writes exception_store. That means a provider outage can't block
reconciliation, and the system surfaces receiver gaps instead of silently self-healing them.
The ledger is append-only — corrections are new rows — so the audit trail of what was believed
when is never destroyed.
One limitation: unmatched is provisional until two consecutive cycles confirm it, so an
exception can appear one cycle late. That's the cost of not blocking until the ledger catches
up.
(~250 words — under the 400–450 band; expand with one classification example if asked for the full three minutes.)
The full flow is in sections 3–5 above. Spoken, walk the interviewer through one event from
schedule trigger through classification, then open each of the three core decisions (idempotent
reconciliation, no provider calls / no ledger mutations, append-only ledger), then the
out-of-order and schema-change challenges. Land on the limitation: provisional unmatched and
the one-cycle latency it costs.
| Claim | Strongest evidence | Evidence type | Confidence | Treatment |
|---|---|---|---|---|
| 5-minute worker schedule | worker cron entry + schedule config |
active config | high | State directly. |
| Idempotency via unique constraint | migration DDL + ON CONFLICT in code + integration test |
implementation + test | high | State directly. |
| Worker DB grants are restricted | DB role grants + forbidden-write test | config + test | high | State directly. |
| Append-only ledger | migration DDL + view definition + property test | implementation + test | high | State directly. |
| Provider API version pinned | version header in code + contract test | implementation + test | high | State directly. |
| Exception volume dropped to "near-zero" | integration replay suite | test (not production metric) | medium | Qualify: "in the replay suite," not "in production." |
| Provider redelivery rate / production QPS | not available | — | low — gap | Do not state. If asked, say "not established by the available materials." |
| Candidate led the project | evidence does not support "led" | — | low — gap | Use "implemented the core / integrated with others"; do not claim "led." |
| Claim | Sources | Status | Treatment |
|---|---|---|---|
Does the worker fetch the provider live on unmatched? |
README says "self-heals by fetching the provider"; active code + DB grants show no provider calls and no ledger writes. | resolved — README describes a planned feature, code is current. | State the current behavior (no live fetch). Label the README claim as planned, not historical-presented-as-current. |
The candidate implemented the reconciliation engine and its tests, and integrated with others on the webhook receiver. The webhook receiver and operator UI were team-owned. The candidate did not design the overall service topology. Use these verbs; do not generalize to "led" or "owned."