Skip to content

Review remediation across backend, frontend, worker, and infra - #1

Merged
RayHCai merged 4 commits into
masterfrom
review-remediation
Aug 13, 2026
Merged

RayHCai merged 4 commits into
masterfrom
review-remediation

Conversation

@RayHCai

@RayHCai RayHCai commented Aug 13, 2026

Copy link
Copy Markdown
Owner

Correctness, performance, and safety pass over the whole stack, plus the CI that would have caught most of it.

151 files, +16,654 / −8,955.

Crash fixes

Two defects that failed on every invocation:

  • recluster: cluster_selection_method was "epi". HDBSCAN only accepts "eom" or "leaf" and validates inside fit(), so the recluster job raised every run.
  • worker/api.py: a parameterised route permanently shadowed a static one, making the static endpoint unreachable.

Data-loss fixes

  • Reclustering destroyed named people. The job reassigns every non-majority face in a cluster to the modal person, then hard-deletes the losers — with no way to distinguish a human's assignment from a machine guess. Faces attached to a named person are now marked manually_assigned and excluded from reassignment.
  • Deleting a person left their album live. collections.person_id was ON DELETE SET NULL, so a deleted person's auto-generated album and its public share links stayed reachable. Now ON DELETE CASCADE.

Performance (migration 0007_review_fixes)

The only index on the timeline sort key was ascending, which Postgres can walk backwards only as DESC NULLS FIRST — so it could not satisfy ORDER BY taken_at DESC NULLS LAST, created_at DESC and every listing degraded to a seq scan plus a full sort. Added composite DESC indexes and dropped the ones they subsume.

Also added missing indexes for duplicate-name checks on upload, ordered collection reads, person media, share links by collection, and location facets.

Timezone modelling

taken_at stored the camera's local wall clock while labelling it UTC, so photos migrated between days depending on the viewer's timezone. Capture time now resolves to a true UTC instant (taken_at) plus the camera's local wall clock (taken_at_local) and offset (taken_at_offset_min); day/month buckets derive from the local value. Video rotation is now read and applied.

The migration copies existing taken_at into taken_at_local, which preserves current bucketing — the stored value already is local wall clock. A metadata backfill will recompute both properly from EXIF.

Secrets

infra/backend.hcl and infra/terraform.tfvars are now gitignored — Terraform state holds the IAM secret in plaintext. .example files added for both. Separately, the shipped .env.example documented an empty WORKER_SECRET, which disabled auth across the entire /internal surface; both secrets are now documented as required with minimum lengths.

CI

There was no CI of any kind, which is the direct cause of both crash-on-every-run defects shipping, plus a declared ruff/mypy gate that had never executed. New workflow gates all four stacks — backend, frontend, worker, infra — and every job fails the build; nothing is advisory. A secret-scan job asserts no env or state files are tracked.

To keep the worker job to a couple of minutes rather than pulling multiple gigabytes of ML wheels, heavy imports (torch, insightface) moved out of the modules under test into worker/stages.py and worker/video.py, and behind a lazy import in recluster._fit.

These are the first tests in the repo: backend crypto and constants, frontend groupByDate and query keys, worker metadata, video, recluster, and a pipeline contract test.

Frontend

Shared query-key registry, useAppMutation, and error boundaries (app/error.tsx, ErrorBoundary.tsx).

Notes for review

  • backend/package-lock.json (+5,607) is likely unwanted — this PR deletes frontend/package-lock.json in favour of yarn, and the new CI runs yarn install --frozen-lockfile against backend/yarn.lock. It looks like a stray npm install artifact and can be dropped. It was included because the request was to add all changes.
  • CI has not run on this branch before now, so its first run on this PR is the real verification of the whole set.
  • The migration is hand-written rather than generated, because the pgvector/tsvector columns are declared Unsupported(...); prisma migrate diff should report no drift.

Broad correctness, performance, and safety pass over the whole stack,
plus the CI that would have caught most of it.

Worker
- recluster: cluster_selection_method was "epi", which HDBSCAN rejects
  inside fit() — the recluster job raised on every run. Faces attached to
  a *named* person are now marked manually_assigned and excluded from
  reassignment, so reclustering no longer destroys named people.
- api: a parameterised route permanently shadowed a static one.
- Heavy ML imports (torch, insightface) moved out of the modules under
  test into worker/stages.py and worker/video.py, and behind a lazy
  import in recluster._fit, so tests and typecheck run without the
  multi-gigabyte wheels.
- Metadata: capture time is now resolved to a true UTC instant plus the
  camera's local wall clock and UTC offset, rather than storing local
  time labelled as UTC. Video rotation is read and applied.

Backend (migration 0007_review_fixes)
- Timeline had no index matching ORDER BY taken_at DESC NULLS LAST, so
  every listing degraded to a seq scan plus full sort. Added composite
  DESC indexes and dropped the ones they subsume.
- Added missing indexes for duplicate-name checks, ordered collection
  reads, person media, share links by collection, and location facets.
- taken_at_local / taken_at_offset_min / video_rotation columns so day
  and month buckets no longer shift with the viewer's timezone.
- processing_at plus a partial index for a stalled-job reaper.
- collections.person_id was ON DELETE SET NULL, which left a deleted
  person's auto-generated album and its public share links reachable.
  Now ON DELETE CASCADE.
- pgvector and tsvector columns declared as Unsupported(...) in
  schema.prisma so a future migrate cannot silently drop them.

Frontend
- Shared query-key registry, useAppMutation, and error boundaries.
- Date grouping derives buckets from the capture-local timestamp.

Infra
- backend.hcl and terraform.tfvars are now gitignored; Terraform state
  holds the IAM secret in plaintext. Added .example files for both.

CI
- New GitHub Actions workflow gating all four stacks. Every job fails
  the build; nothing is advisory. Includes a secret scan asserting no
  env or state files are tracked.
- First tests in the repo: backend crypto and constants, frontend
  groupByDate and query keys, worker metadata, video, recluster, and a
  pipeline contract test.
@vercel

vercel Bot commented Aug 13, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
photos Ready Ready Preview Aug 13, 2026 5:02am

structlog.get_logger is annotated as returning Any, so under strict mode
the type: ignore[return-value] was both unused and covering the wrong
error code (no-any-return). Replaced with an explicit cast.

Caught by the new CI worker job on its first run.
collectMediaS3Keys named the original, thumbnail, streaming and web keys,
so two classes of object were orphaned in the bucket on every delete:
referenced by nothing, and never cleaned up.

- Face crops were only ever deleted when a *person* was deleted, so
  deleting a photo stranded every crop it had generated.
- The responsive thumbnail ladder (`<uuid>@200w.webp` and friends) is
  stored at keys derived from thumbnailKey and recorded in no column —
  the client reconstructs them, so deletion has to reconstruct them too.

Over-listing is safe: S3 DeleteObjects treats a missing key as success,
so naming a variant that was never generated costs nothing.

THUMBNAIL_WIDTHS now has a third copy (worker, frontend, backend). The
backend needs its own because it is the only place that has to *name*
these objects.
@RayHCai
RayHCai merged commit b6f6d9d into master Aug 13, 2026
6 of 7 checks passed
@RayHCai
RayHCai deleted the review-remediation branch August 13, 2026 05:01

This branch was successfully deployed

1 active deployment
Preview 7ab5072b Deployed Aug 13, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant