previews · sprint reports · Data Migration Engine · Sprint 2

Sprint 2 — End to end in dev

A real Lawhive Legal matter travelled all four stages into dev: dumped from v2, shaped and exported by a one-click Dagster job, imported into v3 with its files and the firm's people, and the first activation machinery landed behind it. The architecture grew a deliberate fourth stage this sprint: activation, which turns imported rows into things lawyers can log into, search, and ask about.

Draft · for review Sprint 2 · Aug 19 → Sep 2 2026 · numbers as of Sep 1 evening Engine · Daniele, Cesar, Adolfo · Lukas handed the import service over at the sprint's opening Linear · Build & Test Migration Engine [LLL V2 > V3] Notion · Data Migration Hub Design docs · firm-migrations Deck · review deck
TL;DR

Sprint 1 built the engine's parts; Sprint 2 connected them. A real v2 matter, with its 36 files and the firm's 92 staff records, was dumped from the v2 dev database, shaped into boundary packets by the new one-click firm_import_shape_and_export Dagster job, and imported into v3 on dev: database rows, member records, and every file byte copied into place. 64 PRs landed across 5 repos, up from 27 last sprint, and two milestones closed to 100%.

The fourth stage, activation, went from idea to machinery: five tech plans agreed, member-login verbs on trunk, a dedicated ingestion backfill lane proven end to end in dev, and the file-activation chain staged as approved draft PRs. Two things are deliberately not claimed: file virus-scan verdicts in dev are blocked on a bucket-policy decision (fix raised, decision Sep 2), and nothing has touched production yet, by design.

Sprint health

64PRs
Landed on trunk
34 platform-v3 · 19 data-dbt · 6 platform · 3 infra-v3 · 2 ingestion
2milestones
Closed to 100%
Platform v3 migration readiness · E2E load of a matter
9docs
Design docs published
the files-migration and activation planning arc
1real matter
End to end into dev
36 files placed, 92 staff records imported, from a real v2 dump

Why no ticket counts: this sprint's Linear board does not reflect the work. The import-service lane in platform-v3 (22 of its 34 landed PRs) ran with no tickets behind it, and the merge queue never moves tickets on its own, so several finished tickets still read In Review. Rather than quote numbers we know are wrong, this report counts landed PRs, git-verified on each trunk; getting the board back in step is a named item in Lessons learnt.

The architecture grew a fourth stage

Sprint 1's picture ended when rows landed in v3. This sprint made explicit what has to happen after that: an imported row is inert until someone can log in to see it, until its files have previews and are searchable, and until the AI has case context to answer questions about the matter. That work is now a named stage with its own tech plans, its own infrastructure, and its own controls, and it reuses the product's normal machinery (Clerk, Knock, previews, ingestion, case context) rather than reimplementing any of it.

Stage 1 · Sourcing
Source system → import bucket raw/
Whatever the source firm can give us lands raw and untouched in the regulated import bucket: v2 database dumps, and now the file binaries themselves via S3 Batch Copy with checksums.
Daniele
Stage 2 · Transformation
raw/target/ packets
dbt reshapes raw rows into the 17 canonical boundary models, and a one-click Dagster job builds and exports the packets: sorted JSONL per table, in the layout the import service reads.
Cesar · Daniele
Stage 3 · Importing
target/ → v3 rows and files
The import service validates every row, writes units with provenance, and now also places every file's bytes into the matter store, recording each copy in a placement ledger.
Adolfo · Lukas
Stage 4 · Activation — new
Imported rows → a working firm
Members get logins (Clerk) and invites (Knock); files get previews and AI search via a dedicated ingestion lane; matters get case context. Operator-driven, throttled, and torn down with the run.
Adolfo
The contract, now self-syncing The 17 boundary Zod schemas in platform-v3 remain the single source of truth. A daily cron in data-dbt now regenerates the dbt contracts from them and opens a review PR when they drift, so the sync no longer depends on anyone remembering.

Stages 1 and 2 live in the data platform (BigQuery, dbt, Dagster), stages 3 and 4 in platform-v3. The halves still only meet at the packet: this sprint's end-to-end run is the first time all four stages ran against the same real matter on a deployed environment.

The four fronts named at Sprint 1 close

Sprint 1's report ended with four fronts for this sprint. Here is how each landed, validated against trunk.

Front 1 · Heart of the sprint

First slice end to end: one v2 matter into dev

Done

The pipeline and the import service met on a real environment for the first time. A real matter from the v2 dev database was dumped raw (DM-72), shaped and exported (DM-68, closed Aug 24), and imported into v3 on dev on Sep 1: database rows, 92 staff records, and all 36 files physically copied into the matter store.

What ran, and the one open gap
  • Dump prefix firms/lawhive-legal-ltd/target/…/data in the dev import bucket, produced by the real Dagster export from the real v2 dev dump
  • Import run in the dev admin: rows and members landed, every file reached PLACED in the placement ledger
  • The gap: virus-scan verdicts. A dev bucket-policy change outside the squad left GuardDuty unable to read fresh objects, so all 36 placements hold a failed scan verdict. We diagnosed the deadlock, raised the fix (infra-v3 #573), and the policy decision lands Sep 2. The placement panel can then requeue the 36 to real verdicts.
Front 2

Pipeline: from parts to a one-click job

Done, and beyond

On Aug 19 the warehouse side was a deploy fix away from nothing on trunk. By Sep 1 it can shape a firm's entire caseload and export it unattended: 19 PRs landed in data-dbt building the canonical package, the exporter, and the Dagster job around them.

What the transformation lane can do now
  • All 17 firm-import.v1 boundary schemas implemented as canonical dbt models in firm_migration_package, fed by v2 staging models for every in-scope entity
  • firm_import_shape_and_export: one Dagster click runs the dbt build and writes the JSONL packets into firms/{firm}/target/{exportId}/data
  • Selection is firm-scoped (a whole caseload, optionally narrowed to named cases), sources are swappable behind the firm_import_base__* adapter contract, so LEAP slots in later without touching canonical models
  • Rehearsal mode: an import_ready switch demotes blocking gates to warnings and the job ends with a triage summary of failing tests
  • Every matter is classified live / recent / archive from a per-firm cutoff date, which is what the activation stage's cost and behaviour decisions key on
Front 3

Import service: shipped, then taught files and people

Done

The service Lukas handed over went from "on trunk" to "running in dev" in the sprint's first two days: CI registration, ECS deploy, configurable dump bucket, Axiom telemetry. Then the two missing halves landed as an 11-PR stack on Sep 1: file placement and member activation groundwork.

What the 11-PR stack adds
  • Files: a placement ledger records every imported file's byte destiny; a mover copies bytes into the matter store, virus-scans, and quarantines; the imports screen drives and watches it; a report and local rehearsal tooling close the loop
  • People: identity-service verbs activate an imported identity into a working login and own its whole lifecycle including teardown; an activation ledger and worker, admin members panel, and a rehearsal roster that answers the people questions before a real run
  • Dry runs now resolve references from a persisted scratch registry, so rehearsals never touch production tables
  • Deliberate rebuild: the file-placement tracer was built quick, proven live on Aug 21, then recut into the production lane that merged. Same for the activation spike PRs.
Front 4

Verification: moving, not closed

Partially done

The verification net is being built alongside the files lane rather than as a separate front. Reconciliation models (DM-54) and runtime gates (DM-57) are in progress; the blob catalogue, its matter_file join, and the unclaimed-binaries test (DM-56/82/83) are open PRs; business-readable run reports (DM-80) are next in the queue. The coverage loop over the full book has not started.

What did land on this front
  • Generated dbt contracts now carry primary-key and parent tests plus the dump ordering, so a malformed packet fails its build, not the load
  • The end-of-shape triage summary lists every non-passing dbt test at export time
  • Two ingestion-status fixes in the product so imported files read honestly: a file ingestion was never asked about now shows "Not analysed" instead of a false pending state

Where each stage stands: proven, where, and how

The squad's definition of "proven" is a real run on a real environment, not a passing unit test. Nothing has touched production yet, by design: the project's gate order is local, then dev, then prod behind sign-offs.

CapabilityLocalDevProd
Stage 1 · Sourcing
v2 database dump into raw/the real v2 dev database, dumped by Daniele and Cesar ✓ proven ✓ proven not yet
File binaries via S3 Batch Copy + SHA-256Dagster one-off job, resumable, Launchpad pre-filled; first full-book copy is next n/a ◐ landed, first run pending not yet
Stage 2 · Transformation
Shape + export: one-click Dagster jobwhole-caseload dbt build → JSONL packets in target/ ✓ proven ✓ proven not yet
Contract sync: Zod → dbt daily crondrift opens a review PR automatically ✓ live in CI, daily n/a
Stage 3 · Importing
Rows + members into v3714-matter rehearsal (Sprint 1) and this sprint's real-matter run ✓ proven ✓ proven not yet
File placement: copy, scan, quarantine, report36/36 files placed in dev; scan verdicts blocked on the bucket-policy fix (#573) ✓ proven ◐ placed, scans blocked not yet
Stage 4 · Activation
Member logins (Clerk) + invites (Knock)lifecycle verbs, ledger, worker, admin panel on trunk and deployed; first pilot activation is next ✓ proven ◐ deployed, pilot next not yet
Ingestion backfill lanededicated queues + workers so migration volume never delays live traffic ✓ proven ✓ proven not yet
File activation: previews + AI searchthe chain that wakes a run's files, newest matter first ✓ proven ○ drafts in review not yet
Case context per matterbuilt once a run's files settle ○ draft in review not yet not yet

Around the engine

Infrastructure that unblocks activation at scale

  • The ingestion backfill lane is live in dev, end to end. Migration backfill and everyday ingestion no longer share queues: a real document went lane=backfill through preparation, processing and indexing to INDEXED while every live queue stayed at zero. Misconfiguration cannot hurt the live path: the backfill queue resolves lazily and an unconfigured lane answers 422.
  • Batch-copy IAM for the byte lane (DM-85/87): read access on the v2 source buckets, the real v3 Batch role and dump-bucket ARNs, and Size in the inventory reports.
  • Signing for backfill workers: the dev backfill worker roles can use the intra-service KMS signing key, so they talk to other services exactly like live workers. The prod arm is a deliberate later step.

In review, and one incident

  • Pipeline observability (ingestion #745, in review): one Langfuse trace per file across the whole ingestion pipeline, with nested spans showing what each stage did and what it caused. Proven on three live local pipeline runs; flag-gated off by default. Migration backfill waves become individually inspectable.
  • The GuardDuty deny saga: the squad shipped the original fail-closed protection on the matters bucket (deny reads of flagged objects, #566). A later live policy change in dev unintentionally denied the scanner itself, so nothing uploaded since can earn a verdict; that is what blocked the demo's 36 scans. We diagnosed it, raised #573, and the fail-open versus fail-closed decision is set for Sep 2 with the platform team.
  • Descoped deliberately: GitHub App auth for the sync workflow (DM-65) and GuardDuty on the import bucket (DM-67) were canceled rather than left to linger.

The planning arc: nine design docs

The sprint's biggest decisions were made in writing before the code was cut, in firm-migrations. Read in order, they are the story of how files and activation went from open question to build contract:

DocWhat it settles
Dev dry-run lessons · Aug 20The import service's first deployed runs, measured: real work rate ~160 rows/s, 97% of the wall clock lost to dispatch idle, and a process-affinity bug behind 302 phantom failures. Set the instrumentation agenda.
Import instrumentation plan · Aug 21How run time per firm becomes predictable: three small PRs, four kinds of measurement run, one calibration formula.
Files from v2 to v3 · Aug 20Daniele's investigation of the byte hop: stage, scan, copy, hydrate, with the approach proven against the real v2 estate (3 TB, 4M objects).
File migration framework · Aug 24The source-blind design for how bytes behind migrated matters move into v3, with the decisions, alternatives, and the local tracer that proved it end to end.
Files migration tech plan · Aug 24The locked build contract after team alignment: destination-only scanning, import-bucket staging, data classes with firm-configurable thresholds, and the build sequence.
Firm provisioning · Aug 25A migration run never creates its firm: provision in admin first, point the run at it, delete the seed script.
Imported staff activation · Aug 26How a migrated firm's people get a login: one credential per identity, minted from the imports admin, invited through Knock, unwound by teardown.
Imported file activation · Aug 30The interfaces that wake a completed run's files: one operator action fans out to previews and ingestion, a second builds case context, all on the backfill lane.
Activation at migration scale · Aug 30The numbers companion: measured book sizes, per-document and per-matter economics of previews, AI search and case context, and which shared dependencies give way first.

Where the milestones stand

Platform v3 migration readiness
50 → 100%
E2E load of matter
17 → 100%
Import-service readiness
30 → 92%
Staging & infrastructure ready
50 → 73%
Data contracts / canonical model
33 → 68%
Files migration mapping
25 → 63%
Full load on dev · Testing framework
~0 → 13%

Milestone progress from the Linear project as of Sep 1, with Sprint 1's close as the left number. Two milestones closed to 100% this sprint. Six late-stage milestones (verification, live cutover, login migration, sign-offs, post-migration) remain at 0%; the project targets 20 October for the full Lawhive Legal load.

In flight at close

WorkWhatState
#14400–05, #14410The Stage-1 file-activation chain: wake a run's files newest matter first, fan out to previews and ingestion on the backfill lane, report progress, build case context once the run settles, drive it all from the run pageDrafts, approved
ingestion #745One Langfuse trace per file across the whole ingestion pipeline (nested per-stage spans, deterministic trace ids, flag-gated)In review
infra-v3 #573Matters-bucket scan-policy fix; the fail-open versus fail-closed decision is scheduled for Sep 2Open · decision Sep 2
DM-56 / 82 / 83 / 80The files verification wave in data-dbt: catalogue staged binaries into BigQuery, join the catalogue into matter_file so packets only reference genuinely staged files, test for unclaimed binaries, and ship a business-readable run report per packetOpen PRs
DM-54 / 57 / 81 / 89Reconciliation models, runtime gates, the full-book binary copy run, and the operator runbook for itIn progress
DM-66 / 79 / 16Built and merged (contract sync cron live; firm-provisioning write-up published); awaiting the ticket sync and the sync smoke testAwaiting sync

Lessons learnt

What went well

  • Build it rough, prove it live, then rebuild it properly. The file-placement tracer went from idea to a live dev proof in two days, was thrown away on purpose, and came back as the production lane that merged clean. The activation verbs followed the same arc. The rebuild cost less than the first review round of a speculative design would have.
  • Docs before code paid off again, at higher stakes. Five plans (files framework, tech plan, provisioning, staff activation, file activation) absorbed team alignment and a full review round before the draft PRs were cut, so the 11-PR stack's review was about code.
  • The contract earned its keep. When the stack drifted from a newly landed generator check, the merge queue rejected it at merge time rather than at load time, which is exactly the failure mode the contract was built to move earlier. The daily sync cron now makes that protection automatic.

What to improve

  • The import lane is invisible in Linear. 22 of platform-v3's 34 landed PRs run on IMPORT-N numbering with no DM tickets, and the merge queue still leaves merged DM tickets reading In Review. Agree one home for import-service work and script the close-of-sprint sync, or every report undercounts the squad.
  • Shared buckets need shared change management. Three workstreams (malware protection, backups, migration placement) touched the same bucket policy in one week, one change landed live outside review, and the squad's demo paid for it. The Sep 2 session should leave one owner and one written posture for the matters-bucket policy.
  • Dev Clerk caps member activation rehearsals at ~100 seats. Fine for the one-member pilot, not for a 92-member firm or a real book. Needs a plan (test instance, cap raise, or batched rehearsal) before activation scale tests.

Next sprint

Pending sprint planning; these are the fronts the open work already points at.

Front Work
Close the file loop Settle the matters-bucket scan posture (Sep 2), requeue the demo's 36 files to real verdicts, run the full-book binary copy (DM-81/89), and land the blob catalogue joins so packets only reference genuinely staged bytes (DM-56/82/83)
First activation in dev Land the Stage-1 drafts, wake the demo run's files into previews and search on the backfill lane, build its case context, and run the one-member login pilot
Scale toward the full load Full Lawhive Legal load on dev (the milestone sits at 13%), business-readable run reports (DM-80), and the instrumentation that predicts run time per firm
Observability & prod prep Land per-file pipeline tracing (ingestion #745) and start the prod backfill-lane checklist: workers, config syncs, and the KMS prod arm