Files without an extension are shown as Documents: detect type from content #851

Open
opened 2026-10-02 13:47:26 +00:00 by kayg · 35 comments
Owner

Owner report (2026-10-02)

A phone camera app that the owner prefers writes files with no extension when its naming scheme is changed. 19 photos uploaded over WebDAV into Photos/16pm/Recents/ (IMG_20261002_135643, ...) show in Files as Kind: Document with a generic file icon. "We also gotta handle these files."

Evidence (first 12 bytes of each file on calternal.cloud, read-only): all 19 are HEIC: 00 00 00 28|18 66 74 79 70 68 65 69 63 = ....ftypheic.

Owner principle

Fix the code to handle the User's files as they are. Never rename or rewrite the User's files to add an extension.

Cause

Type detection is by extension in several places: crates/plugins/files/src/{index,lib,thumbnails,user_bytes,public}.rs, crates/plugins/photos/src/index.rs, crates/calternal-search/src/{indexer,semantic}.rs, crates/calternal-tags/src/source.rs, crates/calternal-embed/src/store.rs, crates/calternal-server/src/main.rs, plus the web client's kind/icon mapping.

Fix: one shared content-type primitive (reuse rule)

  1. One function in a shared crate: detect(path, first_bytes) -> ContentType (MIME + kind). When the extension is missing, unknown or contradicts the content, the magic bytes decide: ISO-BMFF brands (heic, heix, mif1, avif, qt , isom, mp41/42, M4A ), JPEG, PNG, GIF, WebP, TIFF/DNG and the common RAW containers, PDF, ZIP-based office files, MP3, WAV, FLAC, Ogg, plain UTF-8 text. Use a maintained, AGPL-compatible crate if one fits (check the licence), otherwise a small table with tests.
  2. Detect once at write/index time from bytes the indexer already reads (no extra disk read on the interactive path, DESIGN §58), store the result in the Files index, and use the stored value everywhere: Files Kind and icon, Photos indexing and thumbnails, Live Photo pairing (an extensionless HEIC and MOV with the same stem pair by content type and the pairing identifier in their metadata), search/OCR/embeddings, the Content-Type of downloads, WebDAV getcontenttype and public links.
  3. Re-detect the already-indexed files once in a background pass (projection rebuild, no file writes) so the owner's 19 photos appear in Photos without re-upload.
  4. Security: a sniffed type never makes the browser run content. Keep X-Content-Type-Options: nosniff, and never serve HTML/SVG/JS from a sniff as an active type on the app origin.

Tests

  • Fixtures without extensions for each listed format (generated, not the owner's photos), plus a misleading extension (photo.txt that is a JPEG).
  • e2e like a User: upload extensionless HEIC + MOV over WebDAV; Files shows Kind "Image"/"Video" with thumbnails; Photos shows the photo on its capture date as one Live Photo; download has the right Content-Type; the file name on disk is unchanged.
  • Bench: index time for 20k files does not regress.
## Owner report (2026-10-02) A phone camera app that the owner prefers writes files with **no extension** when its naming scheme is changed. 19 photos uploaded over WebDAV into `Photos/16pm/Recents/` (`IMG_20261002_135643`, ...) show in Files as **Kind: Document** with a generic file icon. "We also gotta handle these files." Evidence (first 12 bytes of each file on calternal.cloud, read-only): all 19 are HEIC: `00 00 00 28|18 66 74 79 70 68 65 69 63` = `....ftypheic`. ## Owner principle Fix the code to handle the User's files as they are. Never rename or rewrite the User's files to add an extension. ## Cause Type detection is by extension in several places: `crates/plugins/files/src/{index,lib,thumbnails,user_bytes,public}.rs`, `crates/plugins/photos/src/index.rs`, `crates/calternal-search/src/{indexer,semantic}.rs`, `crates/calternal-tags/src/source.rs`, `crates/calternal-embed/src/store.rs`, `crates/calternal-server/src/main.rs`, plus the web client's kind/icon mapping. ## Fix: one shared content-type primitive (reuse rule) 1. One function in a shared crate: `detect(path, first_bytes) -> ContentType` (MIME + kind). When the extension is missing, unknown or contradicts the content, the magic bytes decide: ISO-BMFF brands (`heic`, `heix`, `mif1`, `avif`, `qt `, `isom`, `mp41/42`, `M4A `), JPEG, PNG, GIF, WebP, TIFF/DNG and the common RAW containers, PDF, ZIP-based office files, MP3, WAV, FLAC, Ogg, plain UTF-8 text. Use a maintained, AGPL-compatible crate if one fits (check the licence), otherwise a small table with tests. 2. Detect once at write/index time from bytes the indexer already reads (no extra disk read on the interactive path, DESIGN §58), store the result in the Files index, and use the stored value everywhere: Files Kind and icon, Photos indexing and thumbnails, Live Photo pairing (an extensionless HEIC and MOV with the same stem pair by content type and the pairing identifier in their metadata), search/OCR/embeddings, the `Content-Type` of downloads, WebDAV `getcontenttype` and public links. 3. Re-detect the already-indexed files once in a background pass (projection rebuild, no file writes) so the owner's 19 photos appear in Photos without re-upload. 4. Security: a sniffed type never makes the browser run content. Keep `X-Content-Type-Options: nosniff`, and never serve HTML/SVG/JS from a sniff as an active type on the app origin. ## Tests - Fixtures without extensions for each listed format (generated, not the owner's photos), plus a misleading extension (`photo.txt` that is a JPEG). - e2e like a User: upload extensionless HEIC + MOV over WebDAV; Files shows Kind "Image"/"Video" with thumbnails; Photos shows the photo on its capture date as one Live Photo; download has the right `Content-Type`; the file name on disk is unchanged. - Bench: index time for 20k files does not regress.
Author
Owner

Owner direction (2026-10-02): content decides the type for every file, not only files without an extension. Rule: (1) if the bytes carry a known signature (images, video, audio, PDF, archives, office/ZIP containers inspected one level for their inner type, fonts, executables), that type wins over the extension; (2) only for plain text, where the bytes cannot tell formats apart (Markdown, txt, CSV, JSON, ICS, VCF, source code), the extension chooses the text format, after a cheap structural check where one exists (ICS starts with BEGIN:VCALENDAR, VCF with BEGIN:VCARD, JSON parses); (3) a mismatch (for example photo.txt that is a JPEG, or notes.md that is a PNG) uses the content type, and the file name stays unchanged. This also stops a disguised file from being treated as something else. Add tests for each rule.

Owner direction (2026-10-02): content decides the type for **every** file, not only files without an extension. Rule: (1) if the bytes carry a known signature (images, video, audio, PDF, archives, office/ZIP containers inspected one level for their inner type, fonts, executables), that type wins over the extension; (2) only for plain text, where the bytes cannot tell formats apart (Markdown, txt, CSV, JSON, ICS, VCF, source code), the extension chooses the text format, after a cheap structural check where one exists (ICS starts with BEGIN:VCALENDAR, VCF with BEGIN:VCARD, JSON parses); (3) a mismatch (for example `photo.txt` that is a JPEG, or `notes.md` that is a PNG) uses the content type, and the file name stays unchanged. This also stops a disguised file from being treated as something else. Add tests for each rule.
Author
Owner

Started investigation on job/noext-851, based on c4a61e8cf090170f35b1bed3350d9de20c83ecd5 (origin/dev). I am tracing the existing content-type path and reuse points before implementing the shared detector.

Started investigation on `job/noext-851`, based on `c4a61e8cf090170f35b1bed3350d9de20c83ecd5` (`origin/dev`). I am tracing the existing content-type path and reuse points before implementing the shared detector.
Author
Owner

Finding: the checked-in docs/DESIGN.md ends at §57, so the issue’s §58 reference is not present in this branch. I am using the existing decisions in §2 (files are authoritative; the Index is rebuildable) and §4 (derived data is rebuildable) for the projection-only re-detect pass. I will not write to or rename any Home file.

Finding: the checked-in `docs/DESIGN.md` ends at §57, so the issue’s §58 reference is not present in this branch. I am using the existing decisions in §2 (files are authoritative; the Index is rebuildable) and §4 (derived data is rebuildable) for the projection-only re-detect pass. I will not write to or rename any Home file.
Author
Owner

Finding: prepare_index_write() computes the Files MIME value with mime_guess::from_path(name). An unchanged fingerprint returns the previous row without hashing or updating MIME, so changing the live indexer alone would leave old extension-based MIME values in place. The implementation needs to use the existing hash read for changed files and run one guarded, projection-only pass over legacy rows. User file bytes, paths, and names remain unchanged.

Finding: `prepare_index_write()` computes the Files MIME value with `mime_guess::from_path(name)`. An unchanged fingerprint returns the previous row without hashing or updating MIME, so changing the live indexer alone would leave old extension-based MIME values in place. The implementation needs to use the existing hash read for changed files and run one guarded, projection-only pass over legacy rows. User file bytes, paths, and names remain unchanged.
Author
Owner

Finding: The Files Index is not the current source for every consumer. Photos filters by path suffix, Tags re-guesses photo/video MIME, Embed uses its own text suffix table, Search derives kinds from the name, and the Files UI prefers the suffix in its visible Kind label. The implementation will remove those conflicting guesses and use the Files Index MIME value so downstream projections agree with the shared byte detector.

Finding: The Files Index is not the current source for every consumer. Photos filters by path suffix, Tags re-guesses photo/video MIME, Embed uses its own text suffix table, Search derives kinds from the name, and the Files UI prefers the suffix in its visible Kind label. The implementation will remove those conflicting guesses and use the Files Index MIME value so downstream projections agree with the shared byte detector.
Author
Owner

Finding: The pushed audiophotos-720 branch adds sniff_media_mime() after the Files Index hash step. That opens the same User file again and reads a bounded prefix on the indexing path. #851 requires one detect(path, first_bytes) call using bytes already read by the Indexer, so I will fold the media classification into the shared detector and remove that second open.

Finding: The pushed `audiophotos-720` branch adds `sniff_media_mime()` after the Files Index hash step. That opens the same User file again and reads a bounded prefix on the indexing path. #851 requires one `detect(path, first_bytes)` call using bytes already read by the Indexer, so I will fold the media classification into the shared detector and remove that second open.
Author
Owner

SLOW-only environment note: The new Files regression test has not reached test execution. The first Cargo run waited on the shared sccache; a direct-rustc rerun spent over 30 minutes compiling cold dependencies and was stopped at the one-round timebox. At that point the host had more than 20 concurrent Cargo jobs. The test remains in the tree; I will run the Files crate gate once after the implementation and report the result.

SLOW-only environment note: The new Files regression test has not reached test execution. The first Cargo run waited on the shared sccache; a direct-rustc rerun spent over 30 minutes compiling cold dependencies and was stopped at the one-round timebox. At that point the host had more than 20 concurrent Cargo jobs. The test remains in the tree; I will run the Files crate gate once after the implementation and report the result.
Author
Owner

Test findings with direct reproductions:

  • The new migration 0021 made the Files migration test's expected maximum version 20 stale; it reported actual 21. I updated the expectation to 21 and documented why.
  • The extensionless HEIC integration test failed with NotFound because its fixture wrote into users/u/Photos before creating that directory. I added fixture setup; the test still checks both MIME and byte preservation.
  • The existing inline SVG response test failed with text/plain because the shared detector erased SVG's indexed MIME before Files could apply its existing image-only response rule. The detector now preserves actual SVG MIME; Files still sends it only for image loads and DAV keeps active MIME responses inert.
  • The sidecar move/copy/trash/restore test overflows its default 2 MiB harness thread stack but passes with RUST_MIN_STACK=2621440; I am checking the normal full-crate run after the current fixes.
Test findings with direct reproductions: - The new migration 0021 made the Files migration test's expected maximum version 20 stale; it reported actual 21. I updated the expectation to 21 and documented why. - The extensionless HEIC integration test failed with `NotFound` because its fixture wrote into `users/u/Photos` before creating that directory. I added fixture setup; the test still checks both MIME and byte preservation. - The existing inline SVG response test failed with `text/plain` because the shared detector erased SVG's indexed MIME before Files could apply its existing image-only response rule. The detector now preserves actual SVG MIME; Files still sends it only for image loads and DAV keeps active MIME responses inert. - The sidecar move/copy/trash/restore test overflows its default 2 MiB harness thread stack but passes with `RUST_MIN_STACK=2621440`; I am checking the normal full-crate run after the current fixes.
Author
Owner

Evidence: cargo clippy -p calternal-media --all-targets -- -D warnings flagged an unused ContentKind import in the new benchmark example. I removed the import; the rerun passed.

Evidence: `cargo clippy -p calternal-media --all-targets -- -D warnings` flagged an unused `ContentKind` import in the new benchmark example. I removed the import; the rerun passed.
Author
Owner

Status: I stopped at the shared ~4-hour job cap after the DAV gates. This issue is not complete and must not merge yet.

Head: 3a5eb4599928018c3b59118b97d20e2a81e12627 on job/noext-851. I merged origin/dev, origin/job/mediafix and origin/job/audiophotos-720; origin/job/voicefiles-620 was already an ancestor.

Committed slice: feat(fs): capture prefixes for content detection (#851) adds bounded prefix retention to the existing write stream. The remaining #851 detector, index migrations, consumers, API contract, UI, tests and benchmark are still in the worktree and are not committed.

Gate output:

cargo fmt --check: no output; exit 0
Checking calternal-media v0.0.1 (/home/kayg/Developer/calternal-wt/noext-851/crates/calternal-media)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.28s
test result: ok. 13 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Checking calternal-fs v0.1.0 (/home/kayg/Developer/calternal-wt/noext-851/crates/calternal-fs)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 42.31s
test result: ok. 70 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 24.90s
test result: ok. 42 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 17.83s
Checking calternal-dav v0.0.1 (/home/kayg/Developer/calternal-wt/noext-851/crates/calternal-dav)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 34s
test result: ok. 51 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.17s
test result: ok. 37 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.04s
svelte-check found 0 errors and 0 warnings

Remaining: run per-crate gates for Files, Photos, Search, Embed, Tags and Server; rerun bun run test after the merges; production build and Mac-emulated screenshots at 390/820/1440 in light/dark; attach evidence; generated API check; run and record the 20k average/worst detector profile; one local WebDAV adversarial round; review comments in touched files; then cargo clean and remove web build output. No benchmark measurement, production screenshots, adversarial pass or clean has been completed.

Decision so far: extend the existing calternal-media crate for the single shared content detector. Signature bytes take precedence; the central fallback uses mime_guess 2.0.5 (MIT, verified with cargo info), and detected active content is made inert.

Status: I stopped at the shared ~4-hour job cap after the DAV gates. This issue is not complete and must not merge yet. Head: `3a5eb4599928018c3b59118b97d20e2a81e12627` on `job/noext-851`. I merged `origin/dev`, `origin/job/mediafix` and `origin/job/audiophotos-720`; `origin/job/voicefiles-620` was already an ancestor. Committed slice: `feat(fs): capture prefixes for content detection (#851)` adds bounded prefix retention to the existing write stream. The remaining #851 detector, index migrations, consumers, API contract, UI, tests and benchmark are still in the worktree and are not committed. Gate output: ```text cargo fmt --check: no output; exit 0 Checking calternal-media v0.0.1 (/home/kayg/Developer/calternal-wt/noext-851/crates/calternal-media) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.28s test result: ok. 13 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Checking calternal-fs v0.1.0 (/home/kayg/Developer/calternal-wt/noext-851/crates/calternal-fs) Finished `dev` profile [unoptimized + debuginfo] target(s) in 42.31s test result: ok. 70 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 24.90s test result: ok. 42 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 17.83s Checking calternal-dav v0.0.1 (/home/kayg/Developer/calternal-wt/noext-851/crates/calternal-dav) Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 34s test result: ok. 51 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.17s test result: ok. 37 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.04s svelte-check found 0 errors and 0 warnings ``` Remaining: run per-crate gates for Files, Photos, Search, Embed, Tags and Server; rerun `bun run test` after the merges; production build and Mac-emulated screenshots at 390/820/1440 in light/dark; attach evidence; generated API check; run and record the 20k average/worst detector profile; one local WebDAV adversarial round; review comments in touched files; then `cargo clean` and remove web build output. No benchmark measurement, production screenshots, adversarial pass or clean has been completed. Decision so far: extend the existing `calternal-media` crate for the single shared content detector. Signature bytes take precedence; the central fallback uses `mime_guess 2.0.5` (MIT, verified with `cargo info`), and detected active content is made inert.
Author
Owner

Status: implementation complete on job/noext-851, ready for the merge round

Head: aa280c2e1e65c0bd4f40e4896682dbd1fe89d885. Nothing is pushed or deployed.

The unverified WIP checkpoint (51c5df730) is gone. The branch history is now:

  • 3a5eb4599 feat(fs): capture prefixes for content detection
  • bb4f97143 merge of the latest origin/job/audiophotos-720 (046dc6591). For calternal-media I kept dev's finite ISO grammar (#816).
  • d6aa8855e feat(media): one shared content-type detector
  • 0b085f0b2 fix(fs): 64 KiB detection prefix and a MIME-led text-card rule
  • 2501c5268 feat(files): store MIME from content, re-detect old rows
  • ba93df24c feat(dav): serve the indexed MIME in PROPFIND and GET
  • b757641f9 refactor: Photos, Tags, Search and Embed read the indexed MIME
  • 6a0413ceb feat(web): file kinds and icons follow the indexed MIME
  • d314676a1 test(e2e): extensionless Live Photo and DAV MIME probes
  • aa280c2e1 bench: detector profile

What the code does now

One detector: calternal_media::detect(path, prefix) and detect_with_reader. The per-crate extension tables are removed from Files, Photos, Tags, Search, Embed and the web client. Audiophotos' MediaKind/classify_mime and the sniff_media_mime suffix table are replaced by kind_for_mime and the detector. Photos admits a file only when kind_for_mime says Image or Video.

Owner rules:

  1. A known signature wins over the name: ISO-BMFF brands (AVIF is checked before generic mif1), JPEG, PNG, GIF, WebP, JXL, BMP, TIFF/DNG and RAW, PDF, ZIP (Office/OpenDocument/EPUB, one level deep), OLE, audio/video containers, archives, fonts, executables, wasm and SQLite.
  2. Only plain text lets the extension choose the format. A structural check runs where one exists: ICS must start with BEGIN:VCALENDAR, VCF with BEGIN:VCARD, JSON with { or [. Text bytes under a binary name (photo.jpg that holds text) become text/plain. Binary bytes never get a text type from the name. Inside the TIFF and OLE families the suffix may choose the subtype.
  3. File names are never changed.

Audio-only check (#720): for an ISO movie whose moov box is after the prefix, a lazy reader reads only that box. The Files hash path seeks the same open file. Write paths open the file only for ISO movies. The 64 KiB prefix replaces 1 MiB.

Security: HTML, XML and script are stored as text/plain. is_active_mime now also covers text/jscript and wasm. DAV GET makes active types inert. SVG keeps its MIME only for the Files image-load rule. A baseline comparison found that the dev build served active.html over WebDAV GET as text/html; this branch sends text/plain.

Projection-only backfill: files_index_mime_rebuild.detector_version is compared with DETECTOR_VERSION. One background pass after reconcile re-reads a 64 KiB prefix of each indexed file. The UPDATE changes only files_index.mime and is guarded by the fingerprint. It never writes, moves or renames a User file. It runs before the files_reconciled event, so Photos and Search rebuild from the new MIME. A later detector change only bumps the constant. The owner's 19 ftypheic files will become image/heic on the first start after deploy, with no re-upload.

Text cards: the detector also stores inert text/plain for code and HTML. So ThumbnailKind::for_indexed_file gives a text card for text/plain only when the name is plain text (no suffix, .txt, .log, ...). The web thumbnailKind follows the same rule.

DAV PROPFIND: the xmltree DOM rewrite is replaced by one linear scan. The DOM would cost several times the body size on a 50k-entry listing. Only each response's own D:href selects its MIME, so a principal href inside a property cannot redirect it.

Tests added or changed

  • media: extensionless HEIC/JPEG/PNG/PDF/WebP/TIFF/audio, misleading names (photo.txt JPEG, notes.md PNG), text formats by extension with ICS/VCF/JSON checks, binary under a text name, AVIF vs mif1, archives/fonts/executables/OLE, ODT mimetype, RAW subtype by suffix inside TIFF only, lazy moov reader, prefix bound.
  • Files: generated extensionless HEIC, MOV (ftyp qt + video handler), JPEG, PDF, the misleading photo.txt, and md/csv/README text. Each checks the indexed MIME and that the bytes are unchanged. The backfill test simulates a legacy row and checks that the MIME is updated, the version is recorded and the bytes are unchanged.
  • DAV: href-scoped PROPFIND rewrite.
  • Photos: RAW/HEIC admitted by MIME, not by name.
  • Tags/Search: fixtures carry files_index.mime. The Search and Embed MIME lookups skip NULL-MIME rows instead of failing.
  • Changed tests that encoded suffix gating now encode the content rule (thumbnail kinds, the user_bytes active-type test). The user_bytes test now checks served_type directly on every active MIME.
  • Fixed a stale audiophotos assertion. full_reindex_removes_audio_only_containers_from_photos_projection counted photos_days rows, but two items on one day give one row. It now checks SUM(item_count) == 2. I reproduced the failure on plain origin/job/audiophotos-720 before the change.
  • Fixed e2e/photos.mjs: it addressed /dav/files/owner/, but the URL needs the User ID.

Gate output (verbatim summary lines)

cargo fmt --check: fmt ok
cargo clippy --all-targets -p calternal-media -p calternal-fs -p calternal-plugin-files -p calternal-plugin-photos -p calternal-search -p calternal-embed -p calternal-tags -p calternal-dav -p calternal-plugin-calendar -p calternal-server -- -D warnings
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 3m 26s
calternal-media    test result: ok. 24 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
calternal-fs       test result: ok. 70 passed; 0 failed; ... / ok. 42 passed; 0 failed
calternal-dav      test result: ok. 52 passed; 0 failed; ... / ok. 37 passed; 0 failed
calternal-tags     test result: ok. 12 passed; 0 failed; 0 ignored
calternal-embed    test result: ok. 36 passed; 0 failed; 4 ignored
calternal-search   test result: ok. 43 passed; 0 failed; 1 ignored (+ 1, 1, 21, 5, 2, 1, 4 passed in integration targets; 0 failed)
calternal-plugin-photos   test result: ok. 48 passed; 0 failed; 3 ignored
calternal-plugin-files    test result: ok. 159 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 95.17s
calternal-plugin-calendar test result: ok. 87 passed; 0 failed; 1 ignored (+ 1, 3 passed)
bun run check: COMPLETED 1990 FILES 0 ERRORS 0 WARNINGS 0 FILES_WITH_PROBLEMS
bun run test:  Test Files  154 passed (154) / Tests  1075 passed (1075)

Real-server checks (local debug build of this branch)

  • I ran the e2e flow uploadExtensionlessLivePhoto alone. Result: PASS extensionless WebDAV HEIC and MOV retain bytes, MIME, thumbnails, Search and Live Photo identity. In detail:
    • WebDAV PUT of NOEXT_LIVE_851 (HEIC) and the MOV.
    • PROPFIND returns image/heic and video/quicktime, and GET sends the same Content-Type.
    • Files stat and download return image/heic, and the bytes and the extensionless name on disk are unchanged.
    • Photos shows one Live Photo tile (kind: live, member_count: 2), and the viewer offers "Play the Live Photo".
    • Search finds the file with mime: image/heic.
  • Thumbnails: I had to skip the thumbnail wait on this host. Even a control control.jpg fails here with decoder-failed-v1, so the local media sandbox does not work on this host. The thumbnail step still needs a check on o2.
  • I also had to run the e2e with two local patches that I did not commit: a fallback for window.__userStorageTest in e2e/harness.mjs, and the flow run alone. Without them, the full photos.mjs stops before the #851 step. The first stop is the missing window.__userStorageTest hook in harness.mjs. The second is the "May 2024" heading. Neither is in code that #851 changes.
  • Adversarial round (tests/adversarial/webdav.py against this branch): the new MIME probes pass (extensionless HEIC, renamed JPEG, inert active.html).
    • The same run fails some LOCK, Trash and COPY-race checks. The same checks also fail on another dev-based build (maillayouts), so this branch did not cause them.
    • That baseline also fails all three MIME probes; this branch fixes them. No 5xx, crash or accepted hostile input came from the MIME work.
  • Detector profile (busy shared host, informational only): average p50 0.32 µs and p95 0.52 µs per file over 3×20k files. Worst case (64 KiB prefixes, 4 workers): p50 5.05 µs, p95 9.76 µs, 0.05 s wall time for 20k files.

For the deploy

  • Migration 0021 adds files_index_mime_rebuild. The first start after deploy runs the backfill in the background after reconcile: one 64 KiB read per indexed file.
  • On o2, check that Photos/16pm/Recents/IMG_20261002_* shows Kind "HEIC image" with thumbnails and appears in Photos.
  • Not done: production-build screenshots at 390/820/1440 in light and dark. The UI change only touches Kind labels and icons.
## Status: implementation complete on `job/noext-851`, ready for the merge round Head: `aa280c2e1e65c0bd4f40e4896682dbd1fe89d885`. Nothing is pushed or deployed. The unverified WIP checkpoint (`51c5df730`) is gone. The branch history is now: - `3a5eb4599` feat(fs): capture prefixes for content detection - `bb4f97143` merge of the latest `origin/job/audiophotos-720` (`046dc6591`). For `calternal-media` I kept dev's finite ISO grammar (#816). - `d6aa8855e` feat(media): one shared content-type detector - `0b085f0b2` fix(fs): 64 KiB detection prefix and a MIME-led text-card rule - `2501c5268` feat(files): store MIME from content, re-detect old rows - `ba93df24c` feat(dav): serve the indexed MIME in PROPFIND and GET - `b757641f9` refactor: Photos, Tags, Search and Embed read the indexed MIME - `6a0413ceb` feat(web): file kinds and icons follow the indexed MIME - `d314676a1` test(e2e): extensionless Live Photo and DAV MIME probes - `aa280c2e1` bench: detector profile ### What the code does now **One detector:** `calternal_media::detect(path, prefix)` and `detect_with_reader`. The per-crate extension tables are removed from Files, Photos, Tags, Search, Embed and the web client. Audiophotos' `MediaKind`/`classify_mime` and the `sniff_media_mime` suffix table are replaced by `kind_for_mime` and the detector. Photos admits a file only when `kind_for_mime` says Image or Video. **Owner rules:** 1. A known signature wins over the name: ISO-BMFF brands (AVIF is checked before generic `mif1`), JPEG, PNG, GIF, WebP, JXL, BMP, TIFF/DNG and RAW, PDF, ZIP (Office/OpenDocument/EPUB, one level deep), OLE, audio/video containers, archives, fonts, executables, wasm and SQLite. 2. Only plain text lets the extension choose the format. A structural check runs where one exists: ICS must start with `BEGIN:VCALENDAR`, VCF with `BEGIN:VCARD`, JSON with `{` or `[`. Text bytes under a binary name (`photo.jpg` that holds text) become `text/plain`. Binary bytes never get a text type from the name. Inside the TIFF and OLE families the suffix may choose the subtype. 3. File names are never changed. **Audio-only check (#720):** for an ISO movie whose `moov` box is after the prefix, a lazy reader reads only that box. The Files hash path seeks the same open file. Write paths open the file only for ISO movies. The 64 KiB prefix replaces 1 MiB. **Security:** HTML, XML and script are stored as `text/plain`. `is_active_mime` now also covers `text/jscript` and wasm. DAV GET makes active types inert. SVG keeps its MIME only for the Files image-load rule. A baseline comparison found that the dev build served `active.html` over WebDAV GET as `text/html`; this branch sends `text/plain`. **Projection-only backfill:** `files_index_mime_rebuild.detector_version` is compared with `DETECTOR_VERSION`. One background pass after reconcile re-reads a 64 KiB prefix of each indexed file. The `UPDATE` changes only `files_index.mime` and is guarded by the fingerprint. It never writes, moves or renames a User file. It runs before the `files_reconciled` event, so Photos and Search rebuild from the new MIME. A later detector change only bumps the constant. The owner's 19 `ftypheic` files will become `image/heic` on the first start after deploy, with no re-upload. **Text cards:** the detector also stores inert `text/plain` for code and HTML. So `ThumbnailKind::for_indexed_file` gives a text card for `text/plain` only when the name is plain text (no suffix, `.txt`, `.log`, ...). The web `thumbnailKind` follows the same rule. **DAV PROPFIND:** the xmltree DOM rewrite is replaced by one linear scan. The DOM would cost several times the body size on a 50k-entry listing. Only each response's own `D:href` selects its MIME, so a principal href inside a property cannot redirect it. ### Tests added or changed - media: extensionless HEIC/JPEG/PNG/PDF/WebP/TIFF/audio, misleading names (`photo.txt` JPEG, `notes.md` PNG), text formats by extension with ICS/VCF/JSON checks, binary under a text name, AVIF vs `mif1`, archives/fonts/executables/OLE, ODT `mimetype`, RAW subtype by suffix inside TIFF only, lazy `moov` reader, prefix bound. - Files: generated extensionless HEIC, MOV (`ftyp qt` + video handler), JPEG, PDF, the misleading `photo.txt`, and md/csv/README text. Each checks the indexed MIME and that the bytes are unchanged. The backfill test simulates a legacy row and checks that the MIME is updated, the version is recorded and the bytes are unchanged. - DAV: href-scoped PROPFIND rewrite. - Photos: RAW/HEIC admitted by MIME, not by name. - Tags/Search: fixtures carry `files_index.mime`. The Search and Embed MIME lookups skip NULL-MIME rows instead of failing. - Changed tests that encoded suffix gating now encode the content rule (thumbnail kinds, the `user_bytes` active-type test). The `user_bytes` test now checks `served_type` directly on every active MIME. - Fixed a stale audiophotos assertion. `full_reindex_removes_audio_only_containers_from_photos_projection` counted `photos_days` rows, but two items on one day give one row. It now checks `SUM(item_count) == 2`. I reproduced the failure on plain `origin/job/audiophotos-720` before the change. - Fixed `e2e/photos.mjs`: it addressed `/dav/files/owner/`, but the URL needs the User ID. ### Gate output (verbatim summary lines) ```text cargo fmt --check: fmt ok cargo clippy --all-targets -p calternal-media -p calternal-fs -p calternal-plugin-files -p calternal-plugin-photos -p calternal-search -p calternal-embed -p calternal-tags -p calternal-dav -p calternal-plugin-calendar -p calternal-server -- -D warnings Finished `dev` profile [unoptimized + debuginfo] target(s) in 3m 26s calternal-media test result: ok. 24 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s calternal-fs test result: ok. 70 passed; 0 failed; ... / ok. 42 passed; 0 failed calternal-dav test result: ok. 52 passed; 0 failed; ... / ok. 37 passed; 0 failed calternal-tags test result: ok. 12 passed; 0 failed; 0 ignored calternal-embed test result: ok. 36 passed; 0 failed; 4 ignored calternal-search test result: ok. 43 passed; 0 failed; 1 ignored (+ 1, 1, 21, 5, 2, 1, 4 passed in integration targets; 0 failed) calternal-plugin-photos test result: ok. 48 passed; 0 failed; 3 ignored calternal-plugin-files test result: ok. 159 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 95.17s calternal-plugin-calendar test result: ok. 87 passed; 0 failed; 1 ignored (+ 1, 3 passed) bun run check: COMPLETED 1990 FILES 0 ERRORS 0 WARNINGS 0 FILES_WITH_PROBLEMS bun run test: Test Files 154 passed (154) / Tests 1075 passed (1075) ``` ### Real-server checks (local debug build of this branch) - I ran the e2e flow `uploadExtensionlessLivePhoto` alone. Result: `PASS extensionless WebDAV HEIC and MOV retain bytes, MIME, thumbnails, Search and Live Photo identity`. In detail: - WebDAV PUT of `NOEXT_LIVE_851` (HEIC) and the MOV. - PROPFIND returns `image/heic` and `video/quicktime`, and GET sends the same Content-Type. - Files stat and download return `image/heic`, and the bytes and the extensionless name on disk are unchanged. - Photos shows one Live Photo tile (`kind: live`, `member_count: 2`), and the viewer offers "Play the Live Photo". - Search finds the file with `mime: image/heic`. - Thumbnails: I had to skip the thumbnail wait on this host. Even a control `control.jpg` fails here with `decoder-failed-v1`, so the local media sandbox does not work on this host. The thumbnail step still needs a check on o2. - I also had to run the e2e with two local patches that I did not commit: a fallback for `window.__userStorageTest` in `e2e/harness.mjs`, and the flow run alone. Without them, the full `photos.mjs` stops before the #851 step. The first stop is the missing `window.__userStorageTest` hook in `harness.mjs`. The second is the "May 2024" heading. Neither is in code that #851 changes. - Adversarial round (`tests/adversarial/webdav.py` against this branch): the new MIME probes pass (extensionless HEIC, renamed JPEG, inert `active.html`). - The same run fails some LOCK, Trash and COPY-race checks. The same checks also fail on another dev-based build (`maillayouts`), so this branch did not cause them. - That baseline also fails all three MIME probes; this branch fixes them. No 5xx, crash or accepted hostile input came from the MIME work. - Detector profile (busy shared host, informational only): average p50 0.32 µs and p95 0.52 µs per file over 3×20k files. Worst case (64 KiB prefixes, 4 workers): p50 5.05 µs, p95 9.76 µs, 0.05 s wall time for 20k files. ### For the deploy - Migration 0021 adds `files_index_mime_rebuild`. The first start after deploy runs the backfill in the background after reconcile: one 64 KiB read per indexed file. - On o2, check that `Photos/16pm/Recents/IMG_20261002_*` shows Kind "HEIC image" with thumbnails and appears in Photos. - Not done: production-build screenshots at 390/820/1440 in light and dark. The UI change only touches Kind labels and icons.
Author
Owner

Starting the #851 send-back fixes on branch job/noext-851. Base SHA: c4faf184df. I am tracing the PROPFIND response path, Files type backfill, and concurrent movie-header reads before editing.

Starting the #851 send-back fixes on branch job/noext-851. Base SHA: c4faf184df726a9375ae0c13bdfb6018ac2cf57e. I am tracing the PROPFIND response path, Files type backfill, and concurrent movie-header reads before editing.
Author
Owner

Resuming the #851 send-back work after the build VM reboot. Worktree is clean at aa280c2e1e; the original job base is c4faf184df. I am addressing the three review requirements: streaming PROPFIND rewrite without the 64 MiB response cap, background resumable MIME backfill after the reconcile event, and bounded concurrent ISO moov probes.

Resuming the #851 send-back work after the build VM reboot. Worktree is clean at aa280c2e1e65c0bd4f40e4896682dbd1fe89d885; the original job base is c4faf184df726a9375ae0c13bdfb6018ac2cf57e. I am addressing the three review requirements: streaming PROPFIND rewrite without the 64 MiB response cap, background resumable MIME backfill after the reconcile event, and bounded concurrent ISO moov probes.
Author
Owner

Finding: the prior PROPFIND rewrite copied the full 207 body with a 64 MiB limit and returned 507 above it. The rewrite now streams with a bounded tag carry, an 8 KiB href cap and output chunks capped at 16 KiB. The calternal-dav regression test consumes a 120,000-entry response larger than 64 MiB and verifies all MIME replacements arrive without a size error. Gates: cargo clippy -p calternal-dav --all-targets -- -D warnings passed; cargo test -p calternal-dav passed (54 unit, 37 integration).

Finding: the prior PROPFIND rewrite copied the full 207 body with a 64 MiB limit and returned 507 above it. The rewrite now streams with a bounded tag carry, an 8 KiB href cap and output chunks capped at 16 KiB. The calternal-dav regression test consumes a 120,000-entry response larger than 64 MiB and verifies all MIME replacements arrive without a size error. Gates: cargo clippy -p calternal-dav --all-targets -- -D warnings passed; cargo test -p calternal-dav passed (54 unit, 37 integration).
Author
Owner

Send-back fixes are committed on job/noext-851:

  • 9e1812e58 streams PROPFIND MIME rewriting in bounded chunks and covers a generated 120,000-entry, >64 MiB response. DAV clippy/tests passed earlier in this turn.
  • d0f9e9e62 moves MIME re-detection after the Files completion event, persists page cursors with MIME updates, and shares a single process-wide semaphore for lazy movie-header probes. Files clippy passed; both new backfill/probe regressions passed in the Files test run.

The Files suite also surfaced two existing-gate issues. The schema-upgrade test expected migration 21; this change adds migration 22, so the assertion is updated to 22. The 1,000-write reconcile storm timed out at its 5-minute limit under host load (load average reached about 50); write 492 returned entry not found. I did not change that test or calternal-fs behavior. I will include this as a merge-round finding unless the required post-merge run clarifies it.

Send-back fixes are committed on `job/noext-851`: - `9e1812e58` streams PROPFIND MIME rewriting in bounded chunks and covers a generated 120,000-entry, >64 MiB response. DAV clippy/tests passed earlier in this turn. - `d0f9e9e62` moves MIME re-detection after the Files completion event, persists page cursors with MIME updates, and shares a single process-wide semaphore for lazy movie-header probes. Files clippy passed; both new backfill/probe regressions passed in the Files test run. The Files suite also surfaced two existing-gate issues. The schema-upgrade test expected migration 21; this change adds migration 22, so the assertion is updated to 22. The 1,000-write reconcile storm timed out at its 5-minute limit under host load (load average reached about 50); write 492 returned `entry not found`. I did not change that test or `calternal-fs` behavior. I will include this as a merge-round finding unless the required post-merge run clarifies it.
Author
Owner

Post-merge Files gates: cargo fmt --check passed; cargo clippy -p calternal-plugin-files --all-targets -- -D warnings passed. The Files crate test suite now passes the migration-22 assertion and both new MIME/semaphore regressions, but the existing atomic-write/reconcile storm failed again: Root::write returned entry not found at write 627, followed by the test's five-minute timeout (161 passed, 1 failed, 1 ignored). I filed the repeated filesystem finding as #1001 with both run results. I did not change the existing storm test or calternal-fs behavior.

Post-merge Files gates: `cargo fmt --check` passed; `cargo clippy -p calternal-plugin-files --all-targets -- -D warnings` passed. The Files crate test suite now passes the migration-22 assertion and both new MIME/semaphore regressions, but the existing atomic-write/reconcile storm failed again: `Root::write` returned `entry not found` at write 627, followed by the test's five-minute timeout (161 passed, 1 failed, 1 ignored). I filed the repeated filesystem finding as #1001 with both run results. I did not change the existing storm test or calternal-fs behavior.
Author
Owner

Finished send-back round

Head: eda4311351e9c79c339da3d9418ed5a57d8c7f67 on job/noext-851. No push, deploy or issue close.

Built

  • crates/calternal-dav/src/files.rs: PROPFIND MIME rewriting now streams with bounded carry and 16 KiB output chunks. It has no 64 MiB body cap. The 120,000-entry response test exceeds 64 MiB and checks all replacements.
  • crates/plugins/files/src/index.rs, lib.rs, agent_undo.rs, public.rs, uploads.rs, migration 0022_index_content_type_progress.sql: Files emits its scan completion event before starting MIME re-detection. The background pass saves its (owner, path) cursor and page MIME writes in one transaction, so restart resumes after the last committed page. Changed rows produce a later Files event. One process-wide semaphore bounds lazy 16 MiB movie-header probes across all FilesState values.
  • crates/calternal-server/src/wire.rs: updated the startup comment to describe the immediate scan event and possible later MIME event.
  • docs/DESIGN.md: resolved the origin/dev conflict by retaining both branch additions (§58 and §§60–61).

Commits: 9e1812e58, d0f9e9e62, and merge eda431135.

Gates (verbatim output summaries)

cargo fmt --check
(no output; exit 0)

cargo clippy -p calternal-plugin-files --all-targets -- -D warnings
    Checking calternal-plugin-files v0.0.1 (/home/kayg/Developer/calternal-wt/noext-851/crates/plugins/files)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 28.25s

cargo test -p calternal-plugin-files -- --test-threads=3
    test result: FAILED. 161 passed; 1 failed; 1 ignored; 0 measured; 0 filtered out; finished in 449.42s

cargo clippy -p calternal-server --all-targets -- -D warnings
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 14m 13s

cargo test -p calternal-server -- --test-threads=3
    test result: FAILED. 160 passed; 1 failed; 5 ignored; 0 measured; 0 filtered out; finished in 94.28s
    nested full_app_setup_session_config_and_backup: test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 34.21s

cargo clippy -p calternal-dav --all-targets -- -D warnings
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 5m 02s

cargo test -p calternal-dav
    test result: ok. 54 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 5.70s
    test result: ok. 37 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.19s
    test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

cargo clean
    Removed 21472 files, 15.1GiB total

The Files migration-22 test and both new backfill/semaphore regression tests passed. The Files suite failure was the existing internal_temp_paths_never_enter_index_during_atomic_write_reconcile_storm: it timed out at five minutes and Root::write returned entry not found at write 627. The earlier run failed the same test at write 492. I filed both results as #1001 and did not change the test or calternal-fs behavior. The server suite's only failure was its spawned live-app setup/backup test timing out after 34.21 seconds; this was a SLOW-only host finding.

UX gaps and known gaps

No UI changed in this send-back. The earlier #851 UI changes still need the required production-build screenshots: 390/820/1440 px, light/dark, with macOS platform rendering. The full adversarial matrix remains for the merge round under the verification policy.

The existing #851 detector profile was recorded earlier on a busy shared host: average 3×20k scan p50 0.32 µs / p95 0.52 µs per file; worst 20k 64 KiB prefixes with four workers p50 5.05 µs / p95 9.76 µs and 0.05 s wall time. I did not rerun it here. It measures detector CPU, not the complete paged Files backfill or PROPFIND memory/latency; the merge round should record those paths if the owner wants them compared with the baseline.

Decisions not specified in DESIGN

  • One process-wide movie-probe permit, because one probe can allocate up to 16 MiB.
  • 128 Files rows per backfill page, with page data and cursor committed together.
  • 16 KiB PROPFIND output chunks and an 8 KiB href capture bound.
  • One MIME rebuild at a time per FilesState; emit the later Files event only when the pass changes MIME rows.

Migration check after fetching origin/dev: its Files migrations ended at 0018; this branch already had 0019–0021, so migration 0022 was the next free Files migration.

## Finished send-back round Head: `eda4311351e9c79c339da3d9418ed5a57d8c7f67` on `job/noext-851`. No push, deploy or issue close. ### Built - `crates/calternal-dav/src/files.rs`: PROPFIND MIME rewriting now streams with bounded carry and 16 KiB output chunks. It has no 64 MiB body cap. The 120,000-entry response test exceeds 64 MiB and checks all replacements. - `crates/plugins/files/src/index.rs`, `lib.rs`, `agent_undo.rs`, `public.rs`, `uploads.rs`, migration `0022_index_content_type_progress.sql`: Files emits its scan completion event before starting MIME re-detection. The background pass saves its `(owner, path)` cursor and page MIME writes in one transaction, so restart resumes after the last committed page. Changed rows produce a later Files event. One process-wide semaphore bounds lazy 16 MiB movie-header probes across all FilesState values. - `crates/calternal-server/src/wire.rs`: updated the startup comment to describe the immediate scan event and possible later MIME event. - `docs/DESIGN.md`: resolved the `origin/dev` conflict by retaining both branch additions (§58 and §§60–61). Commits: `9e1812e58`, `d0f9e9e62`, and merge `eda431135`. ### Gates (verbatim output summaries) ```text cargo fmt --check (no output; exit 0) cargo clippy -p calternal-plugin-files --all-targets -- -D warnings Checking calternal-plugin-files v0.0.1 (/home/kayg/Developer/calternal-wt/noext-851/crates/plugins/files) Finished `dev` profile [unoptimized + debuginfo] target(s) in 28.25s cargo test -p calternal-plugin-files -- --test-threads=3 test result: FAILED. 161 passed; 1 failed; 1 ignored; 0 measured; 0 filtered out; finished in 449.42s cargo clippy -p calternal-server --all-targets -- -D warnings Finished `dev` profile [unoptimized + debuginfo] target(s) in 14m 13s cargo test -p calternal-server -- --test-threads=3 test result: FAILED. 160 passed; 1 failed; 5 ignored; 0 measured; 0 filtered out; finished in 94.28s nested full_app_setup_session_config_and_backup: test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 34.21s cargo clippy -p calternal-dav --all-targets -- -D warnings Finished `dev` profile [unoptimized + debuginfo] target(s) in 5m 02s cargo test -p calternal-dav test result: ok. 54 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 5.70s test result: ok. 37 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.19s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s cargo clean Removed 21472 files, 15.1GiB total ``` The Files migration-22 test and both new backfill/semaphore regression tests passed. The Files suite failure was the existing `internal_temp_paths_never_enter_index_during_atomic_write_reconcile_storm`: it timed out at five minutes and `Root::write` returned `entry not found` at write 627. The earlier run failed the same test at write 492. I filed both results as #1001 and did not change the test or `calternal-fs` behavior. The server suite's only failure was its spawned live-app setup/backup test timing out after 34.21 seconds; this was a SLOW-only host finding. ### UX gaps and known gaps No UI changed in this send-back. The earlier #851 UI changes still need the required production-build screenshots: 390/820/1440 px, light/dark, with macOS platform rendering. The full adversarial matrix remains for the merge round under the verification policy. The existing #851 detector profile was recorded earlier on a busy shared host: average 3×20k scan p50 0.32 µs / p95 0.52 µs per file; worst 20k 64 KiB prefixes with four workers p50 5.05 µs / p95 9.76 µs and 0.05 s wall time. I did not rerun it here. It measures detector CPU, not the complete paged Files backfill or PROPFIND memory/latency; the merge round should record those paths if the owner wants them compared with the baseline. ### Decisions not specified in DESIGN - One process-wide movie-probe permit, because one probe can allocate up to 16 MiB. - 128 Files rows per backfill page, with page data and cursor committed together. - 16 KiB PROPFIND output chunks and an 8 KiB href capture bound. - One MIME rebuild at a time per FilesState; emit the later Files event only when the pass changes MIME rows. Migration check after fetching `origin/dev`: its Files migrations ended at 0018; this branch already had 0019–0021, so migration 0022 was the next free Files migration.
Author
Owner

Owner report 2026-10-05: Photos still misses the owner's photos. Production check (read-only): 19 files in Photos/16pm/Recents have no extension and are image/heic (PhotoSync uploads); Photos indexes 3 items. This branch (noext-851) is approved in round 7c, which has not shipped yet. After the 7c deploy, verify that these 19 appear in Photos.

Owner report 2026-10-05: Photos still misses the owner's photos. Production check (read-only): 19 files in Photos/16pm/Recents have no extension and are image/heic (PhotoSync uploads); Photos indexes 3 items. This branch (noext-851) is approved in round 7c, which has not shipped yet. After the 7c deploy, verify that these 19 appear in Photos.
Author
Owner

Starting noext2-851 integration repair on job/noext-851, base/head eda431135. Fetched and merging origin/dev once. Resolving production changes with #851 detection, then checking migration receipts, streaming PROPFIND, startup event ordering, bounded movie probes and real extensionless media on the local server. No pushes or deploys.

Starting noext2-851 integration repair on `job/noext-851`, base/head `eda431135`. Fetched and merging `origin/dev` once. Resolving production changes with #851 detection, then checking migration receipts, streaming PROPFIND, startup event ordering, bounded movie probes and real extensionless media on the local server. No pushes or deploys.
Author
Owner

Integration findings: origin/dev now contains Files migration 25 (Canvas upload validation). #851's legacy 21/22 MIME migrations would collide with deployed history; they now use 26/27, preserving existing SQL receipts. Search's MIME addition remains 5, after production Search 4. Added deterministic tests that hold the movie-probe lane while checking startup completion, and run 16 concurrent upload detection calls. Added a local-server restart regression for 19 real HEIC fixtures in Photos/16pm/Recents plus extensionless JPEG, PNG and MOV. Production web build passes. Rust gates are compiling from the job's empty target directory; web check currently finds changed exact syntax pins in the performance debt ledger, being reviewed without increasing its limits.

Integration findings: `origin/dev` now contains Files migration 25 (Canvas upload validation). #851's legacy 21/22 MIME migrations would collide with deployed history; they now use 26/27, preserving existing SQL receipts. Search's MIME addition remains 5, after production Search 4. Added deterministic tests that hold the movie-probe lane while checking startup completion, and run 16 concurrent upload detection calls. Added a local-server restart regression for 19 real HEIC fixtures in `Photos/16pm/Recents` plus extensionless JPEG, PNG and MOV. Production web build passes. Rust gates are compiling from the job's empty target directory; web check currently finds changed exact syntax pins in the performance debt ledger, being reviewed without increasing its limits.
Author
Owner

Checkpoint: 16f20b17b merges production df92d4da1 into job/noext-851. Files clippy passed before the checkpoint:

    Checking calternal-plugin-files v0.0.1 (/home/kayg/Developer/calternal-wt/noext-851/crates/plugins/files)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 46.00s

Focused web tests passed:

 Test Files  3 passed (3)
      Tests  32 passed (32)

The full requested crate tests are next; the first Files test build is compiling its dependencies. bun run check currently fails on the new performance-contract ledger: changed exact syntax scopes and additional detector/backfill sites need coverage. The ledger and its limits remain unchanged. Svelte diagnostics found integration imports/client declarations that have been repaired and are being checked again. The server-restart regression and macOS screenshot matrix run after the server is built. READY FOR MERGE is not yet established.

Checkpoint: `16f20b17b` merges production `df92d4da1` into `job/noext-851`. Files clippy passed before the checkpoint: ``` Checking calternal-plugin-files v0.0.1 (/home/kayg/Developer/calternal-wt/noext-851/crates/plugins/files) Finished `dev` profile [unoptimized + debuginfo] target(s) in 46.00s ``` Focused web tests passed: ``` Test Files 3 passed (3) Tests 32 passed (32) ``` The full requested crate tests are next; the first Files test build is compiling its dependencies. `bun run check` currently fails on the new performance-contract ledger: changed exact syntax scopes and additional detector/backfill sites need coverage. The ledger and its limits remain unchanged. Svelte diagnostics found integration imports/client declarations that have been repaired and are being checked again. The server-restart regression and macOS screenshot matrix run after the server is built. READY FOR MERGE is not yet established.
Author
Owner

Post-checkpoint verification: Svelte diagnostics now finish with svelte-check found 0 errors and 4 warnings in 3 files. Formatting passed with no output. The web production build and focused 32 Vitest checks passed. Remaining Rust gates are sequential, with four compiler jobs and four test threads; no workspace-wide build/test commands are used. The restart test now checks the actual Files Inspector Kind labels and records the recovered 19-photo Home at 390/820/1440 in both themes, with macOS platform hints. These runtime checks are pending the server build, not claimed as passed.

Post-checkpoint verification: Svelte diagnostics now finish with `svelte-check found 0 errors and 4 warnings in 3 files`. Formatting passed with no output. The web production build and focused 32 Vitest checks passed. Remaining Rust gates are sequential, with four compiler jobs and four test threads; no workspace-wide build/test commands are used. The restart test now checks the actual Files Inspector Kind labels and records the recovered 19-photo Home at 390/820/1440 in both themes, with macOS platform hints. These runtime checks are pending the server build, not claimed as passed.
Author
Owner

Integration finding: the inherited notes_used_in route had no entry in the production action policy. python3 scripts/action_registry.py --check stopped with ValueError: notes_used_in: missing action policy; the server policy lookup would also reject that route during contract construction. Added a read-only data policy with the same replay and continuation rules as the existing linked-Notes read, and added its OpenAPI summary/description. This is a small contract repair in Notes, without changing handler behavior. The production 7c migration fixture now pins SQL from df92d4da129bfead879d417aef6e0514fc56ee96, with Files 26/27 and Search 5 as the only additions.

Integration finding: the inherited `notes_used_in` route had no entry in the production action policy. `python3 scripts/action_registry.py --check` stopped with `ValueError: notes_used_in: missing action policy`; the server policy lookup would also reject that route during contract construction. Added a read-only data policy with the same replay and continuation rules as the existing linked-Notes read, and added its OpenAPI summary/description. This is a small contract repair in Notes, without changing handler behavior. The production 7c migration fixture now pins SQL from `df92d4da129bfead879d417aef6e0514fc56ee96`, with Files 26/27 and Search 5 as the only additions.
Author
Owner

Checkpoint d2b675dd2: fixed the Search integration regression found by the unchanged SearchResultRow.svelte.test.ts assertion. The production shared ItemCard had replaced the Files MIME glyph with its generic voice glyph. Added an optional glyph snippet to ItemCard and supplied the existing shared Files glyph in Search; default ItemCard behavior is unchanged. This small public UI addition preserves the production layout.

Focused verification (verbatim):

 Test Files  1 passed (1)
      Tests  4 passed (4)

Files crate suite (verbatim):

test result: FAILED. 256 passed; 1 failed; 4 ignored; 0 measured; 0 filtered out; finished in 445.73s

The failure was public_password_attempts_are_reserved_before_parallel_checks: [401, 401, 429, 429, 401, 429, 429, 429], expected five 401s, observed three. The unchanged test passes in isolation (verbatim):

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 260 filtered out; finished in 8.91s

Its shared two-worker password budget returns 429 after five seconds of waiting; this result is consistent with shared-host contention. No assertion or worker limit was changed. Both new #851 startup-event and concurrent-movie-upload tests passed in the full run.

Checkpoint `d2b675dd2`: fixed the Search integration regression found by the unchanged `SearchResultRow.svelte.test.ts` assertion. The production shared ItemCard had replaced the Files MIME glyph with its generic voice glyph. Added an optional glyph snippet to ItemCard and supplied the existing shared Files glyph in Search; default ItemCard behavior is unchanged. This small public UI addition preserves the production layout. Focused verification (verbatim): ``` Test Files 1 passed (1) Tests 4 passed (4) ``` Files crate suite (verbatim): ``` test result: FAILED. 256 passed; 1 failed; 4 ignored; 0 measured; 0 filtered out; finished in 445.73s ``` The failure was `public_password_attempts_are_reserved_before_parallel_checks`: `[401, 401, 429, 429, 401, 429, 429, 429]`, expected five 401s, observed three. The unchanged test passes in isolation (verbatim): ``` test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 260 filtered out; finished in 8.91s ``` Its shared two-worker password budget returns 429 after five seconds of waiting; this result is consistent with shared-host contention. No assertion or worker limit was changed. Both new #851 startup-event and concurrent-movie-upload tests passed in the full run.
Author
Owner

Filed the final web-gate failure as #1106, with the exact output and initial scope comparison. The accepted performance registry, exception ledger and ratchet remain unchanged. Separate Svelte verification reports svelte-check found 0 errors and 4 warnings in 3 files.

Verification scope: the #851 large-body test feeds 120,000 entries and more than 64 MiB through the streaming rewrite. The Files DAV provider still has production's DAV_MAX_LIST_ENTRIES = 50_000 (origin/dev:crates/plugins/files/src/dav.rs:43); this job will not claim a real 120,000-file folder request succeeds. The regression under review was the rewrite's second full-body allocation and 64 MiB cap. That cap is removed by bounded output chunks.

The new restart scenario now checks all 22 new extensionless HEIC/JPEG/PNG/MOV uploads in Photos before restart, resets only the test Index MIME projection, removes the test thumbnail cache, and requires fresh thumbnails after startup backfill. Source files and item IDs remain unchanged.

Filed the final web-gate failure as [#1106](https://git.kayg.org/kayg/calternal/issues/1106), with the exact output and initial scope comparison. The accepted performance registry, exception ledger and ratchet remain unchanged. Separate Svelte verification reports `svelte-check found 0 errors and 4 warnings in 3 files`. Verification scope: the #851 large-body test feeds 120,000 entries and more than 64 MiB through the streaming rewrite. The Files DAV provider still has production's `DAV_MAX_LIST_ENTRIES = 50_000` (`origin/dev:crates/plugins/files/src/dav.rs:43`); this job will not claim a real 120,000-file folder request succeeds. The regression under review was the rewrite's second full-body allocation and 64 MiB cap. That cap is removed by bounded output chunks. The new restart scenario now checks all 22 new extensionless HEIC/JPEG/PNG/MOV uploads in Photos before restart, resets only the test Index MIME projection, removes the test thumbnail cache, and requires fresh thumbnails after startup backfill. Source files and item IDs remain unchanged.
Author
Owner

Starting integration/repair pass on branch job/noext-851 at 24a1d6b42ada467149f372edacf6df894107dd5e; base merge point is df92d4da129bfead879d417aef6e0514fc56ee96 (origin/dev, round 7c). The tree already has uncommitted updates in crates/plugins/files/src/lib.rs and apps/web/e2e/photos.mjs; I am preserving and reviewing them while verifying the three returned findings and the owner's extensionless-media case.

Starting integration/repair pass on branch `job/noext-851` at `24a1d6b42ada467149f372edacf6df894107dd5e`; base merge point is `df92d4da129bfead879d417aef6e0514fc56ee96` (`origin/dev`, round 7c). The tree already has uncommitted updates in `crates/plugins/files/src/lib.rs` and `apps/web/e2e/photos.mjs`; I am preserving and reviewing them while verifying the three returned findings and the owner's extensionless-media case.
Author
Owner

Finding during integration review: the content-type screenshot helper navigated to Photos/, but the startup/backfill fixtures live in Photos/16pm/Recents. The helper then searched for the nested file at the root, so screenshot verification could not cover the owner's folder. I am changing it to use the fixture's Home-relative file path; this does not change any User files.

Finding during integration review: the content-type screenshot helper navigated to `Photos/`, but the startup/backfill fixtures live in `Photos/16pm/Recents`. The helper then searched for the nested file at the root, so screenshot verification could not cover the owner's folder. I am changing it to use the fixture's Home-relative file path; this does not change any User files.
Author
Owner

Web gate finding after required origin/dev integration: scripts/perf-lint --check fails because the branch's current source and performance exception ledger diverge beyond the two touched fingerprints. The deterministic parser reports 810 changed exception hashes, 203 entries whose scopes no longer exist, and 302 live findings without ledger entries (22,076 findings vs 21,977 entries). The largest groups are crates/plugins/files/src/index.rs (231 hash changes, 87 missing, 150 unledgered) and crates/calternal-fs/src/thumbnails.rs (128 unledgered, 80 missing). I have not rewritten the ledger wholesale because these entries pin changed call sites and new findings; blanket fingerprint updates would hide scope changes. I will complete the issue-specific runtime/e2e and crate gates, then report web bun run check as blocked unless the source contracts can be reconciled safely within this job.

Web gate finding after required origin/dev integration: `scripts/perf-lint --check` fails because the branch's current source and performance exception ledger diverge beyond the two touched fingerprints. The deterministic parser reports 810 changed exception hashes, 203 entries whose scopes no longer exist, and 302 live findings without ledger entries (22,076 findings vs 21,977 entries). The largest groups are `crates/plugins/files/src/index.rs` (231 hash changes, 87 missing, 150 unledgered) and `crates/calternal-fs/src/thumbnails.rs` (128 unledgered, 80 missing). I have not rewritten the ledger wholesale because these entries pin changed call sites and new findings; blanket fingerprint updates would hide scope changes. I will complete the issue-specific runtime/e2e and crate gates, then report web `bun run check` as blocked unless the source contracts can be reconciled safely within this job.
Author
Owner

Photos startup E2E found a thumbnail race on the legacy MIME rebuild. The test uploads extensionless HEIC/JPEG/PNG/MOV files and queues thumbnails, then resets the indexed MIME to application/octet-stream and restarts the real server. A queued files.thumbnail job can run before its backfill page, mark valid HEIC/JPEG/PNG hashes as terminally failed, and complete. The MIME later repairs, but the marker then suppresses thumbnail generation. Evidence in the preserved Home: the 22 backfilled Files rows had their expected MIME, 22 Photos rows existed, MOV had both WebP sizes, while the HEIC/JPEG/PNG hashes had *.failed-v2 markers and no thumbnails; the jobs had state=completed, attempts=1, and no last_error. I am fixing the worker to defer failure for recognized content under generic MIME and clearing any old failure marker as the backfill changes renderer selection.

Photos startup E2E found a thumbnail race on the legacy MIME rebuild. The test uploads extensionless HEIC/JPEG/PNG/MOV files and queues thumbnails, then resets the indexed MIME to `application/octet-stream` and restarts the real server. A queued `files.thumbnail` job can run before its backfill page, mark valid HEIC/JPEG/PNG hashes as terminally failed, and complete. The MIME later repairs, but the marker then suppresses thumbnail generation. Evidence in the preserved Home: the 22 backfilled Files rows had their expected MIME, 22 Photos rows existed, MOV had both WebP sizes, while the HEIC/JPEG/PNG hashes had `*.failed-v2` markers and no thumbnails; the jobs had `state=completed`, `attempts=1`, and no `last_error`. I am fixing the worker to defer failure for recognized content under generic MIME and clearing any old failure marker as the backfill changes renderer selection.
Author
Owner

Gate note: cargo test -p calternal-plugin-files ran 262 tests and had one unrelated timing failure: public_password_rejection_does_not_wait_for_data_mutation_lock hit its existing 2-second wall timeout under the shared-host load (the suite took 195.98s). I did not change its expectation. Running that one test alone passed (1 passed; 0 failed; 261 filtered out; finished in 2.61s). The #851 MIME-backfill marker regression and recognized-signature worker regression both passed in the suite.

Gate note: `cargo test -p calternal-plugin-files` ran 262 tests and had one unrelated timing failure: `public_password_rejection_does_not_wait_for_data_mutation_lock` hit its existing 2-second wall timeout under the shared-host load (the suite took 195.98s). I did not change its expectation. Running that one test alone passed (`1 passed; 0 failed; 261 filtered out; finished in 2.61s`). The #851 MIME-backfill marker regression and recognized-signature worker regression both passed in the suite.
Author
Owner

#851 integration closeout

Head: 587cfb8e90c4b2360d3013b9fe5aaf1ba41be936.

Repair and evidence

  • PROPFIND MIME rewriting streams the 207 response. The test files::tests::propfind_stream_accepts_a_120k_entry_listing_over_64_mib passed (120k entries, response larger than 64 MiB).
  • Startup sends the Files reconciliation completion event before the resumable MIME backfill. startup_completion_precedes_blocked_mime_backfill passed.
  • Lazy movie-header parsing uses one process-wide semaphore slot across FilesState instances. concurrent_upload_movie_detection_uses_bounded_lane passed; uploads cannot create unbounded 16 MiB moov buffers.
  • A queued thumbnail job seeing a generic legacy MIME now defers its terminal marker for recognized media signatures. MIME repair clears a marker made under the old generic renderer. The added race regression and scoped filesystem marker test passed.
  • The production-schema upgrade test covers Files migrations 26 and 27 after production 7c and passed.

The owner-case Photos E2E passed its startup assertions with 19 real extensionless HEIC files plus extensionless JPEG, PNG, and MOV fixtures. It checked newly uploaded files, MIME repair without changing bytes or IDs, Photos membership and thumbnails, visible Files Kind labels, and inspector labels. A later run failed while creating a JPEG thumbnail after media-sandbox logs reported NamespaceResources and RuntimeMissing; this matches the shared-host sandbox resource pressure seen during the other timed tests. It did not expose a MIME mismatch. The full bun run check is also red on inherited perf-lint ledger drift in PublicLinkPage.svelte; that finding was reported earlier on this issue.

Screenshot evidence

These production-build screenshots use macOS platform emulation. Files and Photos cover 390, 820, and 1440 px in both themes; empty Photos covers the same matrix.

Gates

cargo fmt --check exited 0 with no output.

Passing gate output:

calternal-plugin-photos clippy:     Finished `dev` profile [unoptimized + debuginfo] target(s) in 15m 48s
calternal-plugin-photos tests:      test result: ok. 54 passed; 0 failed; 4 ignored; 0 measured; 0 filtered out; finished in 8.17s
calternal-fs clippy:                Finished `dev` profile [unoptimized + debuginfo] target(s) in 4.18s
calternal-fs tests:                 test result: ok. 94 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 13.77s
calternal-plugin-files clippy:      Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 00s
calternal-server clippy:            Finished `dev` profile [unoptimized + debuginfo] target(s) in 2m 41s
calternal-dav tests:                test result: ok. 61 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 10.66s
test result: ok. 38 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.08s

The Files suite had one shared-host timeout; that test passed alone:

thread 'tests::public_password_rejection_does_not_wait_for_data_mutation_lock' panicked at crates/plugins/files/src/lib.rs:13688:29:
called `Result::unwrap()` on an `Err` value: Elapsed(())
test result: FAILED. 257 passed; 1 failed; 4 ignored; 0 measured; 0 filtered out; finished in 195.98s
test tests::public_password_rejection_does_not_wait_for_data_mutation_lock ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 261 filtered out; finished in 2.61s

The server suite had one process-isolated startup timeout under host load:

HTTP startup waited for an upgrade backfill: Elapsed(())
test result: FAILED. 255 passed; 1 failed; 10 ignored; 0 measured; 0 filtered out; finished in 105.32s
error: test failed, to rerun pass `-p calternal-server --bin calternal-server`

bun run check stops in the inherited perf-lint ledger check:

perf-lint: parse Rust product sources
perf-lint: parse browser product sources
perf-lint: validate coverage and architecture
perf-lint: INVALID: ('render.bound', 'apps/web/src/lib/files/PublicLinkPage.svelte', 'apps/web/src/lib/files/PublicLinkPage.svelte#each:09a378ac039c9ff8:1'): unused or changed exception

The Photos E2E's last retry ended with:

ERROR calternal_plugin::media_sandbox: native media command failed; check runtime and service limits for launcher failures cause=NamespaceResources
ERROR calternal_plugin::media_sandbox: native media command failed; check runtime and service limits for launcher failures cause=RuntimeMissing
error: timed out: JPEG_851 has a real thumbnail

The content-type profile ran locally once (no #851 profile baseline exists in docs/perf/baseline.json; host load average was 42.93): average 20k-file scans, p50 2.320 µs / p95 3.883 µs, 0.284 s wall / 0.209 CPU seconds / 17.9 MiB peak RSS; four-worker 64 KiB prefix burst, p50 30.747 µs / p95 61.831 µs, 0.707 s wall / 0.807 CPU seconds / 18.3 MiB peak RSS. The recorded profile is local and remains under ignored artifacts/.

UX gaps

Closed: the test now checks the visible Files Kind cell and inspector labels while real thumbnail previews are present; screenshots cover both themes, all three required widths and macOS shortcut rendering.

Left: the latest thumbnail E2E retry could not complete under media-sandbox resource pressure; the clean full web gate remains blocked by inherited perf-lint drift.

Decision

Use a process-wide one-slot movie-header semaphore. Files routes, Photos and startup create separate FilesState values, so a state-local semaphore would not cap total concurrent 16 MiB probes.

READY FOR MERGE: no. The owner flow passed once, but the latest E2E retry and required web/server gates did not finish cleanly under this host's resource load and inherited perf-lint drift.

## #851 integration closeout Head: `587cfb8e90c4b2360d3013b9fe5aaf1ba41be936`. ### Repair and evidence - PROPFIND MIME rewriting streams the 207 response. The test `files::tests::propfind_stream_accepts_a_120k_entry_listing_over_64_mib` passed (120k entries, response larger than 64 MiB). - Startup sends the Files reconciliation completion event before the resumable MIME backfill. `startup_completion_precedes_blocked_mime_backfill` passed. - Lazy movie-header parsing uses one process-wide semaphore slot across FilesState instances. `concurrent_upload_movie_detection_uses_bounded_lane` passed; uploads cannot create unbounded 16 MiB `moov` buffers. - A queued thumbnail job seeing a generic legacy MIME now defers its terminal marker for recognized media signatures. MIME repair clears a marker made under the old generic renderer. The added race regression and scoped filesystem marker test passed. - The production-schema upgrade test covers Files migrations 26 and 27 after production 7c and passed. The owner-case Photos E2E passed its startup assertions with 19 real extensionless HEIC files plus extensionless JPEG, PNG, and MOV fixtures. It checked newly uploaded files, MIME repair without changing bytes or IDs, Photos membership and thumbnails, visible Files Kind labels, and inspector labels. A later run failed while creating a JPEG thumbnail after media-sandbox logs reported `NamespaceResources` and `RuntimeMissing`; this matches the shared-host sandbox resource pressure seen during the other timed tests. It did not expose a MIME mismatch. The full `bun run check` is also red on inherited perf-lint ledger drift in `PublicLinkPage.svelte`; that finding was reported earlier on this issue. ### Screenshot evidence These production-build screenshots use macOS platform emulation. Files and Photos cover 390, 820, and 1440 px in both themes; empty Photos covers the same matrix. - Files 390: [light](https://git.kayg.org/attachments/2df7e230-7c13-4fe6-afcf-1c1cb62f9d35), [dark](https://git.kayg.org/attachments/8076f835-b9ae-4932-b5c5-5390bcfc6cf5); 820: [light](https://git.kayg.org/attachments/191a2441-1ec2-4be9-b4f0-8bd065b49a0d), [dark](https://git.kayg.org/attachments/bcb80751-8ae6-48db-996a-d82fcf75c168); 1440: [light](https://git.kayg.org/attachments/f5fbc398-f669-43f4-9c58-a785c2a203be), [dark](https://git.kayg.org/attachments/e7439c1e-8022-4a4f-a352-5fd379456ef7). - Photos 390: [light](https://git.kayg.org/attachments/b8e6bdd7-7b16-4796-86c0-a31c6028eec2), [dark](https://git.kayg.org/attachments/36be896f-4bb1-4880-88be-759440646955); 820: [light](https://git.kayg.org/attachments/501d865f-4d18-4e6b-9f1d-7f7df879b5c0), [dark](https://git.kayg.org/attachments/1759ebd6-5026-484a-92f8-f75d0bd25eeb); 1440: [light](https://git.kayg.org/attachments/5feba081-3c19-4af6-8847-8bda86828639), [dark](https://git.kayg.org/attachments/b041293c-d919-4395-bae4-a024419f4838). - Empty Photos 390: [light](https://git.kayg.org/attachments/95739137-6d79-4b02-88cd-b5a32388d80c), [dark](https://git.kayg.org/attachments/105b912f-7a8d-4896-92cf-dcc7a03eb8d1); 820: [light](https://git.kayg.org/attachments/6045a4e5-f48c-4176-b438-627f3faefb8a), [dark](https://git.kayg.org/attachments/0afb74cc-454a-4de9-9f36-7fa3c4213352); 1440: [light](https://git.kayg.org/attachments/8ba4f2a6-2f79-4554-8150-0cf57ce3fd17), [dark](https://git.kayg.org/attachments/da6d6b66-f5f6-4f36-b09f-584916cac0fb). ### Gates `cargo fmt --check` exited 0 with no output. Passing gate output: ```text calternal-plugin-photos clippy: Finished `dev` profile [unoptimized + debuginfo] target(s) in 15m 48s calternal-plugin-photos tests: test result: ok. 54 passed; 0 failed; 4 ignored; 0 measured; 0 filtered out; finished in 8.17s calternal-fs clippy: Finished `dev` profile [unoptimized + debuginfo] target(s) in 4.18s calternal-fs tests: test result: ok. 94 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 13.77s calternal-plugin-files clippy: Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 00s calternal-server clippy: Finished `dev` profile [unoptimized + debuginfo] target(s) in 2m 41s calternal-dav tests: test result: ok. 61 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 10.66s test result: ok. 38 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.08s ``` The Files suite had one shared-host timeout; that test passed alone: ```text thread 'tests::public_password_rejection_does_not_wait_for_data_mutation_lock' panicked at crates/plugins/files/src/lib.rs:13688:29: called `Result::unwrap()` on an `Err` value: Elapsed(()) test result: FAILED. 257 passed; 1 failed; 4 ignored; 0 measured; 0 filtered out; finished in 195.98s test tests::public_password_rejection_does_not_wait_for_data_mutation_lock ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 261 filtered out; finished in 2.61s ``` The server suite had one process-isolated startup timeout under host load: ```text HTTP startup waited for an upgrade backfill: Elapsed(()) test result: FAILED. 255 passed; 1 failed; 10 ignored; 0 measured; 0 filtered out; finished in 105.32s error: test failed, to rerun pass `-p calternal-server --bin calternal-server` ``` `bun run check` stops in the inherited perf-lint ledger check: ```text perf-lint: parse Rust product sources perf-lint: parse browser product sources perf-lint: validate coverage and architecture perf-lint: INVALID: ('render.bound', 'apps/web/src/lib/files/PublicLinkPage.svelte', 'apps/web/src/lib/files/PublicLinkPage.svelte#each:09a378ac039c9ff8:1'): unused or changed exception ``` The Photos E2E's last retry ended with: ```text ERROR calternal_plugin::media_sandbox: native media command failed; check runtime and service limits for launcher failures cause=NamespaceResources ERROR calternal_plugin::media_sandbox: native media command failed; check runtime and service limits for launcher failures cause=RuntimeMissing error: timed out: JPEG_851 has a real thumbnail ``` The content-type profile ran locally once (no #851 profile baseline exists in `docs/perf/baseline.json`; host load average was 42.93): average 20k-file scans, p50 2.320 µs / p95 3.883 µs, 0.284 s wall / 0.209 CPU seconds / 17.9 MiB peak RSS; four-worker 64 KiB prefix burst, p50 30.747 µs / p95 61.831 µs, 0.707 s wall / 0.807 CPU seconds / 18.3 MiB peak RSS. The recorded profile is local and remains under ignored `artifacts/`. ### UX gaps Closed: the test now checks the visible Files Kind cell and inspector labels while real thumbnail previews are present; screenshots cover both themes, all three required widths and macOS shortcut rendering. Left: the latest thumbnail E2E retry could not complete under media-sandbox resource pressure; the clean full web gate remains blocked by inherited perf-lint drift. ### Decision Use a process-wide one-slot movie-header semaphore. Files routes, Photos and startup create separate `FilesState` values, so a state-local semaphore would not cap total concurrent 16 MiB probes. READY FOR MERGE: no. The owner flow passed once, but the latest E2E retry and required web/server gates did not finish cleanly under this host's resource load and inherited perf-lint drift.
Author
Owner

Starting noext3-851 on job/noext-851, base/head 587cfb8e90c4b2360d3013b9fe5aaf1ba41be936. Fetched origin and integrating origin/dev; preserve current Search layout and MIME glyphs. Files 26/27 and Search 5 follow current dev migrations without a number collision. No pushes or deploys.

Starting noext3-851 on `job/noext-851`, base/head `587cfb8e90c4b2360d3013b9fe5aaf1ba41be936`. Fetched origin and integrating origin/dev; preserve current Search layout and MIME glyphs. Files 26/27 and Search 5 follow current dev migrations without a number collision. No pushes or deploys.
Author
Owner

Integration head 8b21cc9fc includes fetched origin/dev 04afb0043c559c59084e4ec12609bb9fe679cbf6 (newer than the brief's 0eea1138b). Search preserves both intents; focused tests: Test Files 1 passed (1) / Tests 9 passed (9). Commit f02798630 refreshes exactly 12 PublicLinkPage each-block hashes with unchanged limits/counts. The scoped comparison passes. Full web check now stops sooner on an inherited dev ledger/ratchet mismatch: 21,977 exceptions vs a 21,964 ceiling (the merged performance files otherwise equal origin/dev). Fresh source analysis also finds 190 vanished Rust scopes and 805 changed hashes in the combined implementation. No blanket adoption or raised ceiling is applied. Upgrade regression is being extended to copy a stopped fixture Home, restore Files 25/Search 4 schema shapes, upgrade once, and guard against rebuild writes on the second restart.

Integration head `8b21cc9fc` includes fetched `origin/dev` `04afb0043c559c59084e4ec12609bb9fe679cbf6` (newer than the brief's `0eea1138b`). Search preserves both intents; focused tests: `Test Files 1 passed (1)` / `Tests 9 passed (9)`. Commit `f02798630` refreshes exactly 12 PublicLinkPage each-block hashes with unchanged limits/counts. The scoped comparison passes. Full web check now stops sooner on an inherited dev ledger/ratchet mismatch: 21,977 exceptions vs a 21,964 ceiling (the merged performance files otherwise equal origin/dev). Fresh source analysis also finds 190 vanished Rust scopes and 805 changed hashes in the combined implementation. No blanket adoption or raised ceiling is applied. Upgrade regression is being extended to copy a stopped fixture Home, restore Files 25/Search 4 schema shapes, upgrade once, and guard against rebuild writes on the second restart.
Author
Owner

Checkpoint 5224929f6: copied-Home upgrade/restart regression committed. node --check apps/web/e2e/photos.mjs and independent SQLite schema rollback/reapply pass; the real server run remains pending compilation. The previously timed-out Files test passed alone, verbatim:

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 261 filtered out; finished in 4.78s

Server-only startup test is compiling; both returned crate suites are queued with four test threads. No assertions or timeouts changed. Full web check remains blocked by the confirmed origin/dev ledger=21977 versus ratchet=21964 mismatch.

Checkpoint `5224929f6`: copied-Home upgrade/restart regression committed. `node --check apps/web/e2e/photos.mjs` and independent SQLite schema rollback/reapply pass; the real server run remains pending compilation. The previously timed-out Files test passed alone, verbatim: ```text test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 261 filtered out; finished in 4.78s ``` Server-only startup test is compiling; both returned crate suites are queued with four test threads. No assertions or timeouts changed. Full web check remains blocked by the confirmed origin/dev ledger=21977 versus ratchet=21964 mismatch.
Author
Owner

#851 finish pass

Head: 0d7df1b655f4934df361860968b6a8981cc52fab on job/noext-851. The tree is clean. No push or deploy.

Integrated fetched origin/dev 04afb0043c559c59084e4ec12609bb9fe679cbf6 once. This was newer than the brief's dev revision. The shared tracking ref later advanced to f2a34dd6e; its Files migrations still end at 25 and Search at 4. Files 26/27 and Search 5 have no number collision. No renumber was needed.

Built and files

  • apps/web/src/lib/search/SearchResultRow.svelte: kept dev's row layout, accessible title fallback and shared spacing, plus #851's MIME glyphs.
  • apps/web/src/lib/search/SearchResultRow.svelte.test.ts: kept both sets of regression checks. No expectation was weakened.
  • contracts/perf/exceptions.json: refreshed exactly 12 hashes for the Public link each-block. Keys, limits, owners and counts stayed fixed. Each pin matches the current parsed block.
  • apps/web/e2e/photos.mjs: extended the owner-case regression to copy a stopped Home with Files 25/Search 4 shapes, apply the three new receipts once, finish the MIME backfill, and restart again. A trigger rejects repeated backfill progress writes. Copied Index fingerprints match the copied files while MIME stays generic. The test checks real thumbnail signatures and a decoded HEIC tile before each screenshot.

Atomic commits: 8b21cc9fc (merge), f02798630 (Public link pins), 5224929f6 (upgrade/restart regression), 0d7df1b65 (copied fingerprints and HEIC evidence).

Owner case and upgrade

Started the focused production-web-build run after uptime showed load5 19.86, within the one 40-minute wait budget. All 19 extensionless HEIC fixtures in Photos/16pm/Recents appeared in Files and Photos with fresh decoded thumbnails. JPEG, PNG and MOV fixtures also passed. Names, bytes, download MIME and item IDs stayed fixed. Files Kind cells and Inspector labels passed. The second restart retained the exact migration and rebuild receipts.

Verbatim:

PASS copied production-7c Files/Search Index upgrades once; second restart skips completed backfill
PASS startup backfill: 19 real extensionless HEIC + JPEG/PNG/MOV, Photos, thumbnails, Files kinds, stable identities and unchanged bytes
PHOTOS CONTENT TYPE REGRESSION PASSED
CSP REPORTS photos: 0 across 3 pages

The independent production_7c_upgrades_content_types_once server test also passed. It pins Files/Search production migration SQL from df92d4da129bfead879d417aef6e0514fc56ee96.

Gate output (verbatim excerpts)

cargo fmt --check: exit 0, no output.

Files clippy:

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 7m 24s

Server clippy:

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 5m 21s

Files suite, --test-threads=4:

test result: ok. 258 passed; 0 failed; 4 ignored; 0 measured; 0 filtered out; finished in 278.96s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

Files returned timeout test alone:

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 261 filtered out; finished in 4.78s

Server suite, --test-threads=4, had one SLOW timing finding. All 32 Calendar writes returned Created; its p95 assertion failed:

search_reconcile_calendar_write_profile files=20000 writes=32 p50_ms=10225 p95_ms=10601 p95_budget_ms=10000
Calendar write p95 10.601398009s exceeded 10s
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 265 filtered out; finished in 40.67s
test result: FAILED. 255 passed; 1 failed; 10 ignored; 0 measured; 0 filtered out; finished in 131.80s

The same profile passed alone, with its original budget:

search_reconcile_calendar_write_profile files=20000 writes=32 p50_ms=5394 p95_ms=5548 p95_budget_ms=10000
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 265 filtered out; finished in 81.52s

These are shared-host debug test numbers, compared with the existing 10-second test budget. They are not a perf-VM benchmark. No timing assertion changed.

The returned HTTP-startup test passed inside the full-suite process wrapper. My initial direct invocation used the default 2 MiB test stack and aborted; the module's existing wrapper sets the server worker stack budget. Two direct four-thread attempts with that budget still hit the original timeout:

HTTP startup waited for an upgrade backfill: Elapsed(())
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 265 filtered out; finished in 15.30s
HTTP startup waited for an upgrade backfill: Elapsed(())
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 265 filtered out; finished in 41.17s

The load check for the latter happened before compilation, so this does not prove a quiet-host failure. The final direct invocation used the wrapper's exact configuration (RUST_MIN_STACK=4194304, --exact --ignored --test-threads=1) and avoided recompilation. It passed:

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 265 filtered out; finished in 9.54s

Focused Search Vitest:

 Test Files  1 passed (1)
      Tests  9 passed (9)

Separate Svelte diagnostics:

svelte-check found 0 errors and 4 warnings in 3 files

Web production build:

Compressed 883 static variants; saved 20880635 bytes.

Server build:

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 3m 52s

bun run check is still red. The fetched dev snapshot itself has 21,977 ledger entries against a 21,964 ceiling. The Public link repair leaves those counts unchanged. Full output of the failure:

perf-lint: INVALID: exception ratchet: contract.blaze: 290 exceptions exceed the ratchet ceiling 289; fix the new violation instead; contract.blocked-network: 356 exceptions exceed the ratchet ceiling 355; fix the new violation instead; contract.dom-bound: 290 exceptions exceed the ratchet ceiling 289; fix the new violation instead; contract.model-bound: 290 exceptions exceed the ratchet ceiling 289; fix the new violation instead; contract.row-identity: 290 exceptions exceed the ratchet ceiling 289; fix the new violation instead; contract.snapshot: 356 exceptions exceed the ratchet ceiling 355; fix the new violation instead; coverage.profile: 356 exceptions exceed the ratchet ceiling 355; fix the new violation instead; coverage.readiness: 356 exceptions exceed the ratchet ceiling 355; fix the new violation instead; io.unresolved-call: 16191 exceptions exceed the ratchet ceiling 16190; fix the new violation instead; render.blaze-adapter: 290 exceptions exceed the ratchet ceiling 289; fix the new violation instead; render.bound: 290 exceptions exceed the ratchet ceiling 289; fix the new violation instead; render.model_byte_cap: 290 exceptions exceed the ratchet ceiling 289; fix the new violation instead; render.model_row_cap: 290 exceptions exceed the ratchet ceiling 289; fix the new violation instead; total: ledger has 21977 exceptions; ratchet total is 21964

The initial combined-source comparison also found stale Rust scopes and changed hashes beyond the Public link block. No blanket adoption or increased ceiling was applied. The performance ledger repair remains with #1106/#1058.

Cleanup:

Removed 19393 files, 15.6GiB total

Deleted the web production build output. Screenshots and logs remain under ignored artifacts/. Re-read the changed comments and exception reasons before this report.

Screenshots

All 18 screenshots are attached to this issue. They use macOS platform emulation and a real production web build. Claude remains the visual reviewer.

Photos at 1440 px: Paper White, Tokyo Night.

UX gaps closed

  • The evidence now requires decoded HEIC media rather than a PNG or a placeholder elsewhere in the grid.
  • The copied-Home fixture retains generic MIME with valid fingerprints, so a normal changed-file scan cannot mask a missing background repair.
  • The restart check proves that the completed migration/backfill receipts remain fixed.

Known gaps and decisions

  • Full bun run check remains blocked by the inherited performance ledger/ratchet mismatch and further scope drift.
  • The full server suite records a SLOW p95 failure, while the isolated profile passes. Startup attempts varied; all attempts are reported above. No expectation or timeout was weakened.
  • The copied fixture models Files 25/Search 4. Other plugins and Security state come from the current generated test Instance. No production User data was copied.
  • Thumbnails use the existing WebP service. The test verifies real raster bytes and browser decoding. No output-format change was made.
  • No new product design decision was needed. Test-only decisions: update copied filesystem tokens before startup; reject repeat backfill writes with a trigger; select HEIC evidence by content hash so virtualisation cannot hide an offscreen item.

For the merge round

  • After the ledger repair, run (cd apps/web && bun run check && bun run test --maxWorkers=2) to verify the combined web branch.
  • Run tests/adversarial/run.sh for the full real-server API, cross-User and authorization matrices, as required by the verification policy.
  • Reconcile with the then-current dev once and review these attached screenshots. The branch gates above apply to the fetched dev snapshot integrated in this job.

READY FOR MERGE: no. The owner flow and upgrade/restart checks pass; the required full web gate remains red.

# #851 finish pass Head: `0d7df1b655f4934df361860968b6a8981cc52fab` on `job/noext-851`. The tree is clean. No push or deploy. Integrated fetched `origin/dev` `04afb0043c559c59084e4ec12609bb9fe679cbf6` once. This was newer than the brief's dev revision. The shared tracking ref later advanced to `f2a34dd6e`; its Files migrations still end at 25 and Search at 4. Files 26/27 and Search 5 have no number collision. No renumber was needed. ## Built and files - `apps/web/src/lib/search/SearchResultRow.svelte`: kept dev's row layout, accessible title fallback and shared spacing, plus #851's MIME glyphs. - `apps/web/src/lib/search/SearchResultRow.svelte.test.ts`: kept both sets of regression checks. No expectation was weakened. - `contracts/perf/exceptions.json`: refreshed exactly 12 hashes for the Public link each-block. Keys, limits, owners and counts stayed fixed. Each pin matches the current parsed block. - `apps/web/e2e/photos.mjs`: extended the owner-case regression to copy a stopped Home with Files 25/Search 4 shapes, apply the three new receipts once, finish the MIME backfill, and restart again. A trigger rejects repeated backfill progress writes. Copied Index fingerprints match the copied files while MIME stays generic. The test checks real thumbnail signatures and a decoded HEIC tile before each screenshot. Atomic commits: `8b21cc9fc` (merge), `f02798630` (Public link pins), `5224929f6` (upgrade/restart regression), `0d7df1b65` (copied fingerprints and HEIC evidence). ## Owner case and upgrade Started the focused production-web-build run after `uptime` showed load5 **19.86**, within the one 40-minute wait budget. All 19 extensionless HEIC fixtures in `Photos/16pm/Recents` appeared in Files and Photos with fresh decoded thumbnails. JPEG, PNG and MOV fixtures also passed. Names, bytes, download MIME and item IDs stayed fixed. Files Kind cells and Inspector labels passed. The second restart retained the exact migration and rebuild receipts. Verbatim: ```text PASS copied production-7c Files/Search Index upgrades once; second restart skips completed backfill PASS startup backfill: 19 real extensionless HEIC + JPEG/PNG/MOV, Photos, thumbnails, Files kinds, stable identities and unchanged bytes PHOTOS CONTENT TYPE REGRESSION PASSED CSP REPORTS photos: 0 across 3 pages ``` The independent `production_7c_upgrades_content_types_once` server test also passed. It pins Files/Search production migration SQL from `df92d4da129bfead879d417aef6e0514fc56ee96`. ## Gate output (verbatim excerpts) `cargo fmt --check`: exit 0, no output. Files clippy: ```text Finished `dev` profile [unoptimized + debuginfo] target(s) in 7m 24s ``` Server clippy: ```text Finished `dev` profile [unoptimized + debuginfo] target(s) in 5m 21s ``` Files suite, `--test-threads=4`: ```text test result: ok. 258 passed; 0 failed; 4 ignored; 0 measured; 0 filtered out; finished in 278.96s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s ``` Files returned timeout test alone: ```text test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 261 filtered out; finished in 4.78s ``` Server suite, `--test-threads=4`, had one SLOW timing finding. All 32 Calendar writes returned Created; its p95 assertion failed: ```text search_reconcile_calendar_write_profile files=20000 writes=32 p50_ms=10225 p95_ms=10601 p95_budget_ms=10000 Calendar write p95 10.601398009s exceeded 10s test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 265 filtered out; finished in 40.67s test result: FAILED. 255 passed; 1 failed; 10 ignored; 0 measured; 0 filtered out; finished in 131.80s ``` The same profile passed alone, with its original budget: ```text search_reconcile_calendar_write_profile files=20000 writes=32 p50_ms=5394 p95_ms=5548 p95_budget_ms=10000 test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 265 filtered out; finished in 81.52s ``` These are shared-host debug test numbers, compared with the existing 10-second test budget. They are not a perf-VM benchmark. No timing assertion changed. The returned HTTP-startup test passed inside the full-suite process wrapper. My initial direct invocation used the default 2 MiB test stack and aborted; the module's existing wrapper sets the server worker stack budget. Two direct four-thread attempts with that budget still hit the original timeout: ```text HTTP startup waited for an upgrade backfill: Elapsed(()) test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 265 filtered out; finished in 15.30s HTTP startup waited for an upgrade backfill: Elapsed(()) test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 265 filtered out; finished in 41.17s ``` The load check for the latter happened before compilation, so this does not prove a quiet-host failure. The final direct invocation used the wrapper's exact configuration (`RUST_MIN_STACK=4194304`, `--exact --ignored --test-threads=1`) and avoided recompilation. It passed: ```text test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 265 filtered out; finished in 9.54s ``` Focused Search Vitest: ```text Test Files 1 passed (1) Tests 9 passed (9) ``` Separate Svelte diagnostics: ```text svelte-check found 0 errors and 4 warnings in 3 files ``` Web production build: ```text Compressed 883 static variants; saved 20880635 bytes. ``` Server build: ```text Finished `dev` profile [unoptimized + debuginfo] target(s) in 3m 52s ``` `bun run check` is still red. The fetched dev snapshot itself has 21,977 ledger entries against a 21,964 ceiling. The Public link repair leaves those counts unchanged. Full output of the failure: ```text perf-lint: INVALID: exception ratchet: contract.blaze: 290 exceptions exceed the ratchet ceiling 289; fix the new violation instead; contract.blocked-network: 356 exceptions exceed the ratchet ceiling 355; fix the new violation instead; contract.dom-bound: 290 exceptions exceed the ratchet ceiling 289; fix the new violation instead; contract.model-bound: 290 exceptions exceed the ratchet ceiling 289; fix the new violation instead; contract.row-identity: 290 exceptions exceed the ratchet ceiling 289; fix the new violation instead; contract.snapshot: 356 exceptions exceed the ratchet ceiling 355; fix the new violation instead; coverage.profile: 356 exceptions exceed the ratchet ceiling 355; fix the new violation instead; coverage.readiness: 356 exceptions exceed the ratchet ceiling 355; fix the new violation instead; io.unresolved-call: 16191 exceptions exceed the ratchet ceiling 16190; fix the new violation instead; render.blaze-adapter: 290 exceptions exceed the ratchet ceiling 289; fix the new violation instead; render.bound: 290 exceptions exceed the ratchet ceiling 289; fix the new violation instead; render.model_byte_cap: 290 exceptions exceed the ratchet ceiling 289; fix the new violation instead; render.model_row_cap: 290 exceptions exceed the ratchet ceiling 289; fix the new violation instead; total: ledger has 21977 exceptions; ratchet total is 21964 ``` The initial combined-source comparison also found stale Rust scopes and changed hashes beyond the Public link block. No blanket adoption or increased ceiling was applied. The performance ledger repair remains with #1106/#1058. Cleanup: ```text Removed 19393 files, 15.6GiB total ``` Deleted the web production build output. Screenshots and logs remain under ignored `artifacts/`. Re-read the changed comments and exception reasons before this report. ## Screenshots All 18 screenshots are attached to this issue. They use macOS platform emulation and a real production web build. Claude remains the visual reviewer. Photos at 1440 px: [Paper White](https://git.kayg.org/attachments/f6bf96a8-443f-4c7d-b43f-79141dfa729e), [Tokyo Night](https://git.kayg.org/attachments/e4e2908e-fa0b-498f-ba09-b5086ebd7e47). - Files 390: [Paper White](https://git.kayg.org/attachments/422fefa6-0225-45f2-9653-59c7734e39e1), [Tokyo Night](https://git.kayg.org/attachments/cf4c04d6-21cd-44d3-812f-614e47b44820). - Files 820: [Paper White](https://git.kayg.org/attachments/b1fed8a8-36fd-49e0-ad1a-e749c921f406), [Tokyo Night](https://git.kayg.org/attachments/820c78b2-a3fc-4a62-aa28-9b15daf7d4dc). - Photos 390: [Paper White](https://git.kayg.org/attachments/972eb760-9f17-4a6a-8eb3-3fa8dd75e8bd), [Tokyo Night](https://git.kayg.org/attachments/2ad4b472-c9f0-4482-9328-c56ffa6eeedc). - Photos 820: [Paper White](https://git.kayg.org/attachments/6b2ba69a-ad14-4352-afe2-5650eb0c85ec), [Tokyo Night](https://git.kayg.org/attachments/53baf415-0116-4a37-a25c-e5598868c1df). - Files 1440: [Paper White](https://git.kayg.org/attachments/72a93741-718c-4e01-b1c0-9247a253ecb0), [Tokyo Night](https://git.kayg.org/attachments/6663b62c-e5c7-4795-991e-f355add073e2). ## UX gaps closed - The evidence now requires decoded HEIC media rather than a PNG or a placeholder elsewhere in the grid. - The copied-Home fixture retains generic MIME with valid fingerprints, so a normal changed-file scan cannot mask a missing background repair. - The restart check proves that the completed migration/backfill receipts remain fixed. ## Known gaps and decisions - Full `bun run check` remains blocked by the inherited performance ledger/ratchet mismatch and further scope drift. - The full server suite records a SLOW p95 failure, while the isolated profile passes. Startup attempts varied; all attempts are reported above. No expectation or timeout was weakened. - The copied fixture models Files 25/Search 4. Other plugins and Security state come from the current generated test Instance. No production User data was copied. - Thumbnails use the existing WebP service. The test verifies real raster bytes and browser decoding. No output-format change was made. - No new product design decision was needed. Test-only decisions: update copied filesystem tokens before startup; reject repeat backfill writes with a trigger; select HEIC evidence by content hash so virtualisation cannot hide an offscreen item. ## For the merge round - After the ledger repair, run `(cd apps/web && bun run check && bun run test --maxWorkers=2)` to verify the combined web branch. - Run `tests/adversarial/run.sh` for the full real-server API, cross-User and authorization matrices, as required by the verification policy. - Reconcile with the then-current dev once and review these attached screenshots. The branch gates above apply to the fetched dev snapshot integrated in this job. READY FOR MERGE: no. The owner flow and upgrade/restart checks pass; the required full web gate remains red.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kayg/calternal#851
No description provided.