Commit Graph
828 Commits
Author SHA1 Message Date
Cameron CordesandClaude Opus 5.5 a6d7b8f3a0 docs(e2e): note native Linux support and E2E_PORT
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 21:45:01 -04:00
Cameron CordesandClaude Opus 5.5 b7943be022 chore(e2e): run the e2e dev server on native Linux
e2e.sh and gen_fixtures.py assumed WSL driving the Windows toolchain.
They now detect WSL (a microsoft kernel with cargo.exe available) and
otherwise use native tools:

- e2e.sh: cargo/curl instead of cargo.exe/curl.exe, pgrep/pkill
  instead of tasklist/taskkill, Linux paths in the generated .env, and
  binaries without the .exe suffix.
- gen_fixtures.py: picks the first ffmpeg that has the drawtext filter
  (a minimal build earlier on PATH may not; E2E_FFMPEG overrides), finds
  a monospace font with fc-match, and only translates paths with wslpath
  for .exe tools.
- Quote USER_NAME in the generated .env: the unquoted space made the
  dotenv parser reject the line.
- Mark the scripts executable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 21:27:47 -04:00
cameron 50dd9aa016 Merge pull request 'feat(search): explicit filters on unified search; list camera bodies' (#171) from feat/search-explicit-filters into master
Reviewed-on: #171
2026-10-16 01:13:44 +00:00
Cameron CordesandClaude Opus 5.5 015ddd6c77 feat(search): explicit filters on unified search; list camera bodies
The app's new search screen keeps tags, type, date, camera and place as
chips and sends free text to the unified endpoint. For the AI to add to
those chips instead of replacing them, the endpoint now takes them:

- /photos/search/unified accepts the /photos filters explicitly (tag_ids
  with All/Any, exclude_tag_ids, media_type, dates, camera make/model,
  gps circle) plus a recursive `path`. Explicit tags are their own
  predicate on top of the model's ANY-mode tags; single-valued filters
  replace the model's guess. `interpreted` echoes only what the model
  added, so the client doesn't show the user's own chips twice.
- interpret=false skips the LLM and uses q verbatim as the CLIP term,
  which replaces the app's separate "Semantic" mode with a visual match
  inside the user's filters.
- /image/tags/all also returns `cameras` (make + model with counts) so
  the camera picker can offer e.g. "Nexus 6" directly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 21:09:06 -04:00
cameron ed52a07d9d Merge pull request 'fix(chat): budget chat history against the model's served context window' (#170) from fix/chat-context-budget into master
Reviewed-on: #170
2026-10-16 00:36:11 +00:00
Cameron CordesandClaude Opus 5.5 305eda5894 fix(chat): budget chat history against the model's served context window
Long insight/persona chats on llama-swap failed with "exceeds the
available context size" even after raising Context in the app:

- llama-server fixes its window at launch (-c) and ignores num_ctx, but
  the history budget trusted the requested num_ctx, so raising it kept
  MORE history than the server could hold. LlmClient::context_window()
  now reports the served window (llama-swap: /upstream/<model>/props,
  cached 10 min) and the budget uses min(requested, served).
- apply_context_budget could only drop tool-call pairs, so plain Q&A
  chats never shrank. It now also drops whole earlier exchanges (keeping
  user/assistant alternation) and, last, the current turn's tool results,
  never the in-progress question.
- The budget only ran once, before the tool loop. ContextFitter now
  fits every LLM call (on a copy; the stored transcript is untouched)
  and, on a context-overflow error, shrinks the budget from the server's
  reported token counts and retries once.

GET /insights/models reports each llama-swap model's context_window when
it's known without loading the model (cached, or currently running), for
the app's Context picker.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 19:21:04 -04:00
cameron 1d2848db05 Merge pull request 'fix(video): keep signed stream tokens out of logs and traces' (#169) from fix/redact-stream-token into master
Reviewed-on: #169
2026-10-14 02:35:23 +00:00
Cameron CordesandClaude Opus 5.5 c0e6a68ca4 fix(video): keep signed stream tokens out of logs and traces
The access log and the tracing root span's http.target both recorded the
full /video/original/stream?token=… URL, i.e. a credential for a
possibly sensitive file, in the journal and OTel exports. An outermost
middleware now moves the token into request extensions and rewrites the
URI to token=redacted before either logger sees it; the handler reads
the token from extensions and fails closed (401) without the middleware.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 22:32:39 -04:00
cameron 40d9dcdbb6 Merge pull request 'feat(video): short-lived signed URLs for original-quality playback' (#168) from feat/signed-original-video into master
Reviewed-on: #168
2026-10-14 01:55:34 +00:00
Cameron CordesandClaude Opus 5.5 40c33c41a6 feat(video): short-lived signed URLs for original-quality playback
POST /video/original mints a URL for one video's original bytes, signed
with a key derived from SECRET_KEY (distinct from the login-token key)
and valid for 6 hours. GET /video/original/stream?token= serves it via
NamedFile, so Range/seek works, without an Authorization header — native
players can use it directly. HLS stays the default path.

Also extracts generate_video's library fallback into resolve_request_path
and fixes the stale /video/stream entry in CLAUDE.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 20:42:28 -04:00
cameron 4745a945fe Merge pull request 'perf(video): decode HLS transcode sources on the GPU' (#167) from perf/hls-gpu-decode into master
Reviewed-on: #167
2026-10-13 16:12:57 +00:00
Cameron CordesandClaude Opus 5.5 be532725c8 perf(video): decode HLS transcode sources on the GPU
When NVENC is available the HLS transcode encoded on the GPU but still
decoded on the CPU, since the command had no -hwaccel. Rebuilding the
4K homemade videos ran three ffmpegs at 210-240% CPU each with the
machine at 0% idle, while NVDEC sat at 0%.

Pass -hwaccel cuda when transcoding with NVENC, as generate_preview_clip
already does. Frames return to system memory, so autorotate and the
scale filter work unchanged; NVDEC-unsupported codecs fall back to
software decode.

10 s of 4K H.264 (2025-USAF-MuseumRace.mp4): 27.3 s -> 6.1 s CPU,
9.6 s -> 3.6 s wall, identical output (1920x1080, 245 frames, 3 s
segments). Also checked a 90-degree-rotated phone clip (comes out
1080x1440 upright, 233/233 frames) and ProRes (software fallback,
correct output).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 12:12:08 -04:00
cameron 78e36d8a6c Merge pull request 'fix(video): stop stream-copying oversized sources into HLS' (#166) from fix/hls-copy-limits into master
Reviewed-on: #166
2026-10-13 15:09:38 +00:00
Cameron CordesandClaude Opus 5.5 84843c1c25 docs: run tests with an in-memory DATABASE_URL
The docs said tests need BASE_PATH in .env. The full suite (lib, bin and
integration tests) passes with no .env and no BASE_PATH; the only thing
it needs is DATABASE_URL, and ":memory:" works. Without it, DB-backed
tests such as reels::tests::reel_capture_preserves_owner_defaults panic
with "DATABASE_URL must be set".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 11:09:14 -04:00
Cameron CordesandClaude Opus 5.5 985f366bc2 chore(video): drop unused Ffmpeg::_generate_playlist, fix reels comment
_generate_playlist was a private, never-called predecessor of the HLS
path in video/actors.rs. Its leftover args (-hls_list_size 100, width
1080 scale, libx264 only) no longer described how playlists are built.
Removing it also drops the now-unused std::process::Output import.

The reels encoder comment said its NVENC settings matched the HLS
transcode; reels use preset p4, HLS uses p1. Only the quality target
(cq 23) is shared.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 11:07:05 -04:00
Cameron CordesandClaude Opus 5.5 0b87320ef7 fix(video): make NVENC honour forced HLS keyframes
-force_key_frames asks for a keyframe every 3 s, but h264_nvenc inserts
those as plain I-frames unless -forced-idr is set. The HLS muxer only
splits on IDR frames, so NVENC transcodes fell back to the encoder's
default 250-frame GOP: 8.34 s segments at 29.97 fps, ~10 s at 25 fps.
On prod ~480 playlists have a target duration above 4 s. Long segments
mean more data before playback starts and coarser seeks.

Verified on a 10 s 29.97 fps clip: segments went from 8.34 + 1.67 s to
3.003 x 3 + 1.001 s, matching the libx264 path, which was already
correct.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 11:03:18 -04:00
Cameron CordesandClaude Opus 5.5 83b1923e25 fix(video): keep source frame timing when transcoding to HLS
Variable-frame-rate sources (phone clips that change capture rate,
screen recordings) report r_frame_rate as their 1/90000 timebase. The
HLS transcode used the mpegts muxer's default CFR mode, so ffmpeg
duplicated frames to fill 90000 fps: on prod, three 2.6-5 s phone clips
became 3-second "segments" of up to 446 MB / 250k frames. Players
download the whole thing before starting and can't seek within it.

-fps_mode vfr passes source timestamps through. Verified on
20220208_164356_01.mp4: 233 frames over 2.578 s in, 233 frames over
2.578 s out, 5.9 MB vs 221 MB. The copy path already keeps timestamps
and is unchanged. Requires ffmpeg >= 5.1 (prod runs 8.0.1).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 10:58:13 -04:00
Cameron CordesandClaude Opus 5.5 052609d604 docs: document HLS_COPY_MAX_BITRATE
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 10:58:12 -04:00
Cameron CordesandClaude Opus 5.5 1279cb8430 fix(video): stop stream-copying oversized sources into HLS
HLS generation stream-copied any h264 source with no rotation and a
GOP within one segment, which keeps the source's resolution and
bitrate. Mirrorless footage (4K at 100-160 Mbps) came through as-is,
giving ~50 MB 3-second segments; playback start and seeks stalled on
anything slower than wired gigabit. On prod, 16 playlists run above
50 Mbps, versus ~10 Mbps typical for phone clips.

The stream probe now also reads width, height and bit_rate (stream
level, falling back to format level; all header fields, so the probe
stays header-only). Copy additionally requires the short side to be
<= 1080 and the bitrate <= HLS_COPY_MAX_BITRATE (default 20 Mbps).
Unknown size or bitrate transcodes, matching the GOP probe's fallback.

The transcode scale filter capped width at 1080, so landscape sources
came out at 1080x608. It now caps the short side instead: 3840x2160
-> 1920x1080, 2160x3840 -> 1080x1920, smaller sources unchanged
(verified with ffmpeg).

Existing oversized playlists need regenerating after deploy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 10:46:46 -04:00
cameron ff1ba1fcd6 Merge pull request 'fix(dates): read exiftool's word-form GPS hemisphere refs (#163)' (#165) from fix/exiftool-gps-hemisphere into master
Reviewed-on: #165
2026-10-13 14:32:36 +00:00
Cameron CordesandClaude Opus 5.5 5b6ffd7040 fix(dates): read exiftool's word-form GPS hemisphere refs (#163)
The exiftool branch matched GPSLatitudeRef / GPSLongitudeRef with an
exact one-letter compare ("S" / "W"), but exiftool's print conversion
(which parse_gps_dms relies on) renders them as "South" / "West". The
flags never set, so every western longitude and southern latitude
reached the camera-offset cascade positive and the GPS step looked up
the mirrored point: northeast Ohio became China, offset UTC+8. On prod,
all 280 western-hemisphere `gps`-sourced rows carry +480, putting their
date_utc 12-13 h off.

gps_ref_is matches the first letter case-insensitively, the same rule
as exif::extract_gps_coordinate (whose kamadak path was already
correct; the stored gps_latitude / gps_longitude columns are unaffected).

Tests use exiftool's real output strings ("West" / "South", verified
against exiftool 12.76 with the resolver's -j -q -G1 flags), including
a southern-hemisphere case; the new payload test fails on the old
compare. The existing test fed "W", which exiftool doesn't emit by
default, so it couldn't catch this.

Existing rows need a re-resolve after deploy; see #163 for the reset.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:32:25 +00:00
cameron 4da2d4d0a8 Merge pull request 'docs: stack architecture diagrams (Apollo / ImageApi / FVR)' (#164) from docs into master
Reviewed-on: #164
2026-10-13 14:05:28 +00:00
Cameron CordesandClaude Opus 5.5 7077aee4ca docs: stack architecture diagrams for Apollo / ImageApi / FVR
Mermaid sources for the system context, data ownership, photo ingest,
watcher tick, Apollo photo matching and photo insight chat, traced from
the current code of all three repos. Allow-lists docs/architecture/
alongside docs/adr/ and docs/agents/.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 22:55:43 -04:00
cameron d50cad7421 Merge pull request 'feat(dates): photo date filters on the true instant (#152)' (#162) from feature/true-instant-date-filters into master
Reviewed-on: #162
2026-10-11 02:04:08 +00:00
Cameron CordesandClaude Opus 5.5 19fbf43d40 feat(dates): photo date filters on the true instant (#152)
date_from / date_to on /photos, /photos/exif and unified search ran on
the legacy mixed-convention date_taken, so a photo could display inside
a range yet be filtered out of it by up to its UTC offset (or by years,
for rows the filename class re-dated).

The filter lives in the shared ExifDao::query_by_exif, so it's fixed
once there:

- query_by_exif takes Option<date_view::InstantRange> (bounds + display
  and server zones) instead of raw date_from / date_to.
- SQL pre-filter: converted rows on date_utc exactly; unconverted rows
  (drain pending, permanent floor) on date_taken widened by
  LEGACY_INSTANT_BAND_SECS (±15 h, now shared with /memories). Converted
  rows are deliberately not bounded by date_taken: a re-dated row's
  legacy value can sit years from its instant.
- Exact pass in Rust via InstantRange::contains → row_true_instant, so
  every returned row's true instant is inside the range.

Calendar days now mean the owner's days: nl_query::iso_to_unix builds
day bounds in the display zone (it used UTC midnights, which only lined
up with the legacy wall-as-UTC column), and unified search's "today" is
the owner's today rather than UTC's. The midnight-with-DST-gap fallback
moves from memories_anchor into date_view::day_start_instant.

Compatibility: clients already sending real instants (Apollo's
/photos/exif windows) get correct edges with no change. Clients sending
UTC-midnight day bounds see ranges shift by up to the UTC offset until
they send display-zone bounds.

No index on date_utc; the OR clause scans, which is milliseconds at
personal-library scale (same as /memories today).

dev/e2e check gains /photos/exif wall_clock + true_instant assertions and
range-edge checks ([t, t] in, [.., t-1] and [t+1, ..] out). Not run here:
the harness needs the WSL + Windows toolchain.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 21:52:48 -04:00
cameron 9e780774d9 Merge pull request 'feat(dates): true_instant + wall_clock on /photos/exif (#113)' (#161) from feature/exif-summary-instant-fields into master
Reviewed-on: #161
2026-10-11 01:42:46 +00:00
Cameron CordesandClaude Opus 5.5 f1a7fb4ecc feat(dates): true_instant + wall_clock on /photos/exif (#113)
/photos/exif is Apollo's photo-to-track matching feed, but unlike
/image/metadata and /photos/gps-summary it only carried the raw
new-contract triple. date_utc is NULL for unconverted rows and, by
design, forever for the drain's edited/exported floor (#120), so a
client reading date_utc alone would have to re-implement the
per-source legacy conventions to cover those rows: the re-anchoring
ADR-0001 says consumers delete, not migrate.

ExifSummary now carries:
- true_instant: date_view::row_true_instant (date_utc when converted,
  else the per-source fallback), for instant-to-instant matching.
- wall_clock: date_view::row_wall_clock, rendered verbatim.

The row → wire mapping moves to ExifSummary::from_row so the tests
cover the real projection, including an unconverted row falling back
to the same instant and wall as its converted twin. Additive: legacy
fields are unchanged, so current clients see the same values.

The date_from/date_to filter still runs on the legacy column; that's
#152, which now explicitly covers /photos/exif too.

Unblocks Apollo #37.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 21:15:41 -04:00
cameron 991622af2a Merge pull request 'feat(memories): optional reference_date to browse relative to another day' (#159) from feature/memories-reference-date into master
Reviewed-on: #159
2026-10-11 00:29:19 +00:00
Cameron CordesandClaude Opus 5.5 3563c3de8d feat(memories): optional reference_date to browse relative to another day
GET /memories accepts reference_date=YYYY-MM-DD (a day in the owner's
display zone). It re-anchors both the span match (day_in_span's "today")
and the pre-filter window, which ends at the end of that day, so the
20-year lookback is measured from it and a future date surfaces
everything taken so far. Omitted → today, unchanged. Malformed → 400.

Reels stay anchored on now (MVP): the reel selector passes None.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 22:29:33 -04:00
cameron c256829615 Merge pull request 'Feature/no original fallback' (#158) from feature/no-original-fallback into master
Reviewed-on: #158
2026-10-04 00:50:14 +00:00
Cameron CordesandClaude Opus 5.5 4f839954b0 chore(idea): mark tests/ as a test source root
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 22:01:25 -04:00
Cameron CordesandClaude Opus 5.5 38ac3656c6 fix(image): never stream originals into thumb slots; unique preview tempfiles
A thumb miss (no thumb on disk, or an `.unsupported` sentinel) fell
through to NamedFile on the original. For a 12432x3504 panorama that
decodes to 174MB, past Android's 100MB draw limit, and crashed the
FileViewer app natively on the Memories tab. Still-image thumb misses
now serve the 2048px large preview, or 404 if even that can't be made.

Large/xlarge generation wrote to a shared `<dest>.jpg.tmp`, so two
concurrent requests for the same file (common: tile remounts) raced and
the loser's rename failed with os error 2, falling back to the original.
Generation now goes through a per-call tempfile, and a failed request
still serves a preview a concurrent request produced.

Large and xlarge branches are folded into one `serve_preview` helper.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 08:53:39 -04:00
cameron 98527bb81f Merge pull request 'fix(memories): sort Month span by time within a day; test the real sorts' (#156) from fix/memories-month-sort into master
Reviewed-on: #156
2026-09-30 03:07:39 +00:00
Cameron CordesandClaude Opus 5.5 75581b3f6a test(memories): test the real Week sort; cover every span via sort_memories
test_week_span_sorting_chronological_by_day sorted its own inline copy
(day-of-month across years), so it kept passing after 7d2a314 made the
real Week sort chronological (full date, then time), and it disagreed
with production. Replace it with a test that calls sort_memories, and
add one for Day, so all three spans are pinned against the real sort.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 23:03:57 -04:00
Cameron CordesandClaude Opus 5.5 4feffb62bb fix(memories): sort Month span by time within a day
The Month sort keyed on the display day only. The sort is stable, so a
day's photos kept DB order, merged across libraries, and their times
jumped back and forth. Month now sorts like Week: full date, then
timestamp (undated items last).

The sort moves into sort_memories so it can be tested directly; the new
test covers a multi-library, out-of-order day.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 23:03:56 -04:00
cameron fd9ecbe53b Merge pull request 'feat(otel): export logs through the tracing pipeline, correlated with traces (#147)' (#155) from feature/tracing-log-export into master
Reviewed-on: #155
2026-09-30 03:03:14 +00:00
Cameron CordesandClaude Opus 5.5 3b3e6bdb39 feat(otel): export logs through the tracing pipeline, correlated with traces (#147)
Logs were a separate pipeline: env_logger in debug (never exported) and
opentelemetry-appender-log in release (exported without trace context,
so SigNoz could not link logs to traces).

- tracing-subscriber `tracing-log` feature: try_init installs LogTracer,
  so log:: records become tracing events: one console format, and
  RUST_LOG per-module directives still apply to them.
- opentelemetry-appender-tracing exports events as OTel log records when
  OTLP_OTLS_ENDPOINT is set (any build), stamped with the enclosing
  span's trace_id/span_id. A per-layer filter drops the exporter's own
  crates.
- The OTel span layer keeps only WARN/ERROR events as span events; the
  info/debug copies duplicated the correlated logs.
- Remove env_logger init in the server, init_logs, and the unused
  opentelemetry-appender-log / opentelemetry-stdout deps.
- tests/otel_log_correlation.rs pins correlation (native + bridged),
  the span-event filter, and per-module filtering of bridged records.
- Constitution 1.2.0 (Principle V logging rule), CLAUDE.md, README,
  .env.example.

Closes #147

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 22:56:10 -04:00
cameron 79b339a2a6 Merge pull request 'test(filename_audit): seed library 2 before its image_exif row (#151)' (#154) from fix/filename-audit-test-fk into master
Reviewed-on: #154
2026-09-30 01:53:20 +00:00
Cameron CordesandClaude Opus 5.5 8a5a7c19d7 test(filename_audit): seed library 2 before its image_exif row (#151)
run_audit_skips_disabled_and_offline_libraries inserted an image_exif
row for library 2, which migrations never create, tripping the
image_exif.library_id foreign key. Insert the libraries row first so
the suite is green on master again.

Also fix the cmp_owned clippy warning in the filename_audit bin.

Closes #151

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 21:31:53 -04:00
cameron 053314137c Merge pull request 'docs(agents): the wayfinder:* labels exist now' (#153) from docs/wayfinder-labels into master
Reviewed-on: #153
2026-09-30 00:48:08 +00:00
Cameron CordesandClaude Opus 5.5 39771df86b docs(agents): the wayfinder:* labels exist now
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 20:45:47 -04:00
cameron 810db329ab Merge pull request 'docs: LOCAL_BIND_URL in README (constitution IV) + tea label behavior in tracker docs' (#150) from docs/local-bind-url into master
Reviewed-on: #150
2026-09-30 00:44:41 +00:00
Cameron CordesandClaude Opus 5.5 d29d3c0e9b docs(agents): labels must exist before tea can apply them
The tracker doc said labels that don't exist are created on first use.
They aren't: `tea issues create --labels` and `issues edit --add-labels`
silently drop unknown names (the command succeeds, the issue stays
unlabelled) — observed filing #151/#152 while the repo had no labels.
Document creating them with `tea labels create` and checking the result.
The five triage labels now exist on the server; the wayfinder:* family
still has to be created before first use.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 20:43:20 -04:00
Cameron CordesandClaude Opus 5.5 af12061cc0 docs: document LOCAL_BIND_URL (constitution IV)
#145 added the optional LOCAL_BIND_URL (the secondary loopback listener,
default localhost:8088, empty = skip) but documented it only in
CLAUDE.md. Constitution IV requires every new environment variable in
the README environment section; also add it to .env.example.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 20:40:45 -04:00
cameron aeeb05b289 Merge pull request 'docs: amend constitution to v1.1.0 — Observability after the tracing migration' (#149) from docs/tracing-constitution into master
Reviewed-on: #149
2026-09-29 23:49:09 +00:00
cameron a869bf37c9 Merge branch 'master' into docs/tracing-constitution 2026-09-29 23:48:58 +00:00
Cameron CordesandClaude Opus 5.5 fbe3d06ae5 docs: amend constitution to v1.1.0 (Observability for tracing migration)
Principle V now reflects the merged tracing migration (#148):
- tracing + tracing-opentelemetry spans in every build, with OTLP export
  gated on OTLP_OTLS_ENDPOINT;
- no manual Context threading;
- every spawn / fan-out carries its parent span;
- RUST_LOG gates span export (run at info/debug).
Layer and Tech Stack wording matches.

Also corrects stale "release builds only" telemetry notes in CLAUDE.md and
.env.example, updates the CLAUDE.md dependency list, and fixes the README's
RUST_LOG default. It also documents OTLP_OTLS_ENDPOINT in the README, as
Principle IV requires.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 18:03:44 -04:00
cameron 3ee2517a9e Merge pull request 'feat(dates): serve wall_clock + true_instant to clients (#113, #120)' (#146) from feature/wall-clock-dates into master
Reviewed-on: #146
2026-09-29 22:01:23 +00:00
Cameron CordesandClaude Opus 5.5 9269b5c9fd Dates: true_instant on metadata responses for instant matching
The mobile viewer anchored its "messages around this photo" window on
the legacy date_taken (a wall clock stored as UTC for EXIF rows), while
SMS-API dates are true instants: the window was off by the photo's UTC
offset (4-5 h for photos taken at home; ImageApi #113 user story 3).
Verified SMS-API side: importers store backup epochs as UTC, and 40/40
sampled rows equal their source epochs exactly.

`/image/metadata` (+ GPS / date mutation responses) now carries
`true_instant` from `date_view::file_true_instant`: `row_true_instant`,
else the filename-class instant, else the earliest fs time. Server-side
for the same reason as `wall_clock`: the drain's permanent floor rows
have no `date_utc`. `set_capture_dates` fills both fields at all four
response sites.

dev/e2e: the manifest records each fixture's expected instant (the wall
read in its GPS zone, else America/New_York) and `e2e.sh check` asserts
it (97/97).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 17:55:29 -04:00
Cameron CordesandClaude Opus 5.5 bc41c737e5 dev/e2e: wall_clock regression check + deterministic fixture dates
`e2e.sh check` compares every indexed fixture's manifest wall clock with
the `wall_clock` reported by /image/metadata, /memories and
/photos/gps-summary; any disagreement fails.

- Fixture videos are stamped like phone containers: the America/New_York
  wall as a UTC creation_time (the label previously showed the UTC time).
- mtimes are pinned in America/New_York instead of the host's zone.
- The e2e env sets TZ=America/New_York (the #120 preflight): the Windows
  exe has no /etc/timezone, so server_zone() was otherwise None.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 17:54:47 -04:00