ISOLATION: physically separate per-User derived stores (vectors) + full cross-user inventory #435

Closed
opened 2026-09-29 12:45:20 +00:00 by kayg · 42 comments
Owner

Owner decision (2026-09-29)

"Q-index2 yes please! Why was that not the design? Please look for other cross-user things that should not be there at all."
Rule (DESIGN §48 Q-index, restated): each User's derived data is physically separate: separate files or a separate database per User, not rows keyed by owner in a shared table. A missing WHERE owner = ? must be unable to leak anything. Why this was not already done: job per-user-index (#400) was allowed to keep text and CLIP vectors as owner-keyed rows in shared SQLite tables. That was the orchestrator's brief being too permissive, not the owner's design.

1. Split vectors per User

After #400 lands on dev (merge it first if needed; ask via the issue if it is not on dev when you start), move the text-embedding and CLIP-embedding stores (crates/calternal-embed/src/{store.rs,clip_store.rs}) and every other derived table #400's audit (docs/audits/per-user-index-400.md) lists as "User-keyed shared", into per-User database files under the per-User state root, next to the per-User search index.

  • Open them lazily; bound open handles with a named config value and an LRU.
  • Migrate once, idempotent and restartable: write the new per-User files, verify the counts, and only then drop the shared rows. A crash mid-migration loses nothing, and queries keep working throughout: serve from the old store until a User's new file is verified.
  • The API must make a cross-user query impossible to express: the store handle is obtained from an authenticated User context, and there are no functions that take an owner ID parameter for reads.

2. Full cross-user inventory (the owner's second ask)

Audit every place that holds User-derived data or state outside the User's Home, and classify each:

  • (A) Global by necessity (the auth users table, sessions, instance config, the plugin registry).
  • (B) Per-User by design and physically separate (OK).
  • (C) Shared across Users with owner keys, which must move to (B) unless there is a documented reason.
  • (D) Shared caches or dedup that could leak by existence, timing or size: content-addressed stores, thumbnail caches keyed by hash, upload staging, the collab (Yjs) document cache, SSE/change feeds, job queues and their payloads, notifications, activity/analytics, Mail index and attachments, the tag index, DAV sync tokens and move hints, the WebDAV lock table, trash metadata, rate-limit buckets, logs containing User content, temp directories, and model or tool caches (only instance-shared, read-only artifacts are OK).
    For each (C) and (D) entry: the file or table, what is stored, the risk (read, write, inference, noisy neighbour), and the fix. Fix every (C) item in this job, or split it into follow-up issues if it is large, one issue per root cause, each self-contained. Put the inventory in docs/audits/cross-user-inventory.md and keep it current.

3. Proof

  • Extend the #331 two-User matrix (tests/adversarial/xuser_matrix.py) so it covers every (B) store after the split: seed User A, query as B through every surface (Search, similar photos, "related", embeddings-backed features, MCP and WebMCP search, CLI search). Expect zero results and no timing or size oracle.
  • A negative-control test that deliberately removes an owner filter in a test build and shows that nothing leaks, because the stores are separate.
  • The migration test: crash at each step, then resume.
    Gates per crate as in the preamble, for every crate touched, plus the adversarial round. Security-critical: every code path gets a doc comment explaining the isolation invariant.
## Owner decision (2026-09-29) "Q-index2 yes please! Why was that not the design? Please look for other cross-user things that should not be there at all." **Rule (DESIGN §48 Q-index, restated):** each User's derived data is **physically separate**: separate files or a separate database per User, not rows keyed by owner in a shared table. A missing `WHERE owner = ?` must be *unable* to leak anything. Why this was not already done: job per-user-index (#400) was allowed to keep text and CLIP vectors as owner-keyed rows in shared SQLite tables. That was the orchestrator's brief being too permissive, not the owner's design. ## 1. Split vectors per User After #400 lands on dev (merge it first if needed; ask via the issue if it is not on dev when you start), move the text-embedding and CLIP-embedding stores (`crates/calternal-embed/src/{store.rs,clip_store.rs}`) and every other derived table #400's audit (`docs/audits/per-user-index-400.md`) lists as "User-keyed shared", into per-User database files under the per-User state root, next to the per-User search index. - Open them lazily; bound open handles with a named config value and an LRU. - Migrate once, idempotent and restartable: write the new per-User files, verify the counts, and only then drop the shared rows. A crash mid-migration loses nothing, and queries keep working throughout: serve from the old store until a User's new file is verified. - The API must make a cross-user query impossible to express: the store handle is obtained from an authenticated User context, and there are no functions that take an owner ID parameter for reads. ## 2. Full cross-user inventory (the owner's second ask) Audit **every** place that holds User-derived data or state outside the User's Home, and classify each: - **(A) Global by necessity** (the auth users table, sessions, instance config, the plugin registry). - **(B) Per-User by design and physically separate** (OK). - **(C) Shared across Users with owner keys**, which must move to (B) unless there is a documented reason. - **(D) Shared caches or dedup** that could leak by existence, timing or size: content-addressed stores, thumbnail caches keyed by hash, upload staging, the collab (Yjs) document cache, SSE/change feeds, job queues and their payloads, notifications, activity/analytics, Mail index and attachments, the tag index, DAV sync tokens and move hints, the WebDAV lock table, trash metadata, rate-limit buckets, logs containing User content, temp directories, and model or tool caches (only instance-shared, read-only artifacts are OK). For each (C) and (D) entry: the file or table, what is stored, the risk (read, write, inference, noisy neighbour), and the fix. Fix every (C) item in this job, or split it into follow-up issues if it is large, one issue per root cause, each self-contained. Put the inventory in `docs/audits/cross-user-inventory.md` and keep it current. ## 3. Proof - Extend the #331 two-User matrix (`tests/adversarial/xuser_matrix.py`) so it covers every (B) store after the split: seed User A, query as B through every surface (Search, similar photos, "related", embeddings-backed features, MCP and WebMCP search, CLI search). Expect zero results and no timing or size oracle. - A negative-control test that deliberately removes an owner filter in a test build and shows that nothing leaks, because the stores are separate. - The migration test: crash at each step, then resume. Gates per crate as in the preamble, for every crate touched, plus the adversarial round. Security-critical: every code path gets a doc comment explaining the isolation invariant.
Author
Owner

Started #435 on branch job/iso-435 at a9b840199d. Base dev merge-base: 191b179baa. This branch already contains #400. I am auditing the shared derived stores and implementing physical per-User separation. I will merge dev once before final gates.

Started #435 on branch job/iso-435 at a9b840199dac7ee5b1065bbc5f3696ab675b761a. Base dev merge-base: 191b179baac3ef4f5bebfe07ce91c4b7a887ace2. This branch already contains #400. I am auditing the shared derived stores and implementing physical per-User separation. I will merge dev once before final gates.
Author
Owner

Finding: docs/audits/per-user-index-400.md lists seven owner-keyed shared Derived data stores. The broader source inventory found additional User-keyed projections in Files, Notes, Mail, Calendar, Search, Video and Ask. I recorded the files, tables, risks and required boundaries in docs/audits/cross-user-inventory.md. I opened self-contained migration issues #437–#447 for the non-vector root causes. The vector split remains in #435; I am implementing the CLIP store first and will report any remaining gap explicitly.

Finding: docs/audits/per-user-index-400.md lists seven owner-keyed shared Derived data stores. The broader source inventory found additional User-keyed projections in Files, Notes, Mail, Calendar, Search, Video and Ask. I recorded the files, tables, risks and required boundaries in docs/audits/cross-user-inventory.md. I opened self-contained migration issues #437–#447 for the non-vector root causes. The vector split remains in #435; I am implementing the CLIP store first and will report any remaining gap explicitly.
Author
Owner

Additional inventory finding: the shared Blob store, thumbnail cache, upload staging, Yjs/DAV state, Files feeds and shared Job dispatcher have existence, timing or resource-contention risks even where current reads are filtered. The inventory records the exact current location, risk and required fix. I opened #449–#454 for these root causes. The Blob store remains Instance-wide under DESIGN §5 but needs a timing and quota isolation test; the other durable User state needs per-User boundaries.

Additional inventory finding: the shared Blob store, thumbnail cache, upload staging, Yjs/DAV state, Files feeds and shared Job dispatcher have existence, timing or resource-contention risks even where current reads are filtered. The inventory records the exact current location, risk and required fix. I opened #449–#454 for these root causes. The Blob store remains Instance-wide under DESIGN §5 but needs a timing and quota isolation test; the other durable User state needs per-User boundaries.
Author
Owner

Finding: account deletion calls Root::purge_user_search_index before the queued semantic notify_removed runs. A lazy per-User vector opener would recreate the deleted User's .system/index/users/<id> directory when that queued command executes. I changed deletion to purge legacy semantic and CLIP rows directly after the filesystem purge, and the semantic worker treats a whole-Home removal as cleanup only. This needs the embedding and server gates before it is considered verified.

Finding: account deletion calls Root::purge_user_search_index before the queued semantic `notify_removed` runs. A lazy per-User vector opener would recreate the deleted User's `.system/index/users/<id>` directory when that queued command executes. I changed deletion to purge legacy semantic and CLIP rows directly after the filesystem purge, and the semantic worker treats a whole-Home removal as cleanup only. This needs the embedding and server gates before it is considered verified.
Author
Owner

Finding: splitting vector files by source User prevents a missing SQL owner predicate from mixing Users, but a Share recipient can still open the source User's file for one allowed Share root. First-open migration and candidate bucket work can expose the source User's unrelated Index size through timing. I opened #455 for recipient-owned vector projections and added this risk to the inventory. The current path authorization remains required; I will not claim the timing-oracle proof is complete in #435.

Finding: splitting vector files by source User prevents a missing SQL owner predicate from mixing Users, but a Share recipient can still open the source User's file for one allowed Share root. First-open migration and candidate bucket work can expose the source User's unrelated Index size through timing. I opened #455 for recipient-owned vector projections and added this risk to the inventory. The current path authorization remains required; I will not claim the timing-oracle proof is complete in #435.
Author
Owner

Evidence: cargo test -p calternal-embed passed: 25 passed, 0 failed, 4 ignored. New tests cover CLIP and text vector migration restart states, unfiltered queries over one User's private file, damaged owner-keyed candidate rows, deleted User cleanup and the LRU handle cap. cargo clippy -p calternal-embed --all-targets -- -D warnings is running now. I am checking the remaining migration lifecycle and live surface probes before a final claim.

Evidence: `cargo test -p calternal-embed` passed: 25 passed, 0 failed, 4 ignored. New tests cover CLIP and text vector migration restart states, unfiltered queries over one User's private file, damaged owner-keyed candidate rows, deleted User cleanup and the LRU handle cap. `cargo clippy -p calternal-embed --all-targets -- -D warnings` is running now. I am checking the remaining migration lifecycle and live surface probes before a final claim.
Author
Owner

Finding: the new lazy vector opener currently holds the LRU lock while it copies one User and deletes their old rows. The copy is restartable and count-checked, but a large first query can wait, and another User can wait behind that lock. This does not meet the issue's old-store serving rule. I opened #456 with the required per-User single-flight migration and old-to-new switch test. I am continuing work on this boundary; it is not a completed proof yet.

Finding: the new lazy vector opener currently holds the LRU lock while it copies one User and deletes their old rows. The copy is restartable and count-checked, but a large first query can wait, and another User can wait behind that lock. This does not meet the issue's old-store serving rule. I opened #456 with the required per-User single-flight migration and old-to-new switch test. I am continuing work on this boundary; it is not a completed proof yet.
Author
Owner

Committed the vector split at 2e20c95e56. The CLIP migration test and the full embedding suite passed before the final orphan-vector case; the targeted semantic migration test also passed after that case. The calternal-embed clippy gate is running. The server deletion call site and final merge/gates remain uncommitted or pending.

Committed the vector split at 2e20c95e5610b20d6719bf9642d56cffc64cde21. The CLIP migration test and the full embedding suite passed before the final orphan-vector case; the targeted semantic migration test also passed after that case. The `calternal-embed` clippy gate is running. The server deletion call site and final merge/gates remain uncommitted or pending.
Author
Owner

Merged dev once at 2f996336 without conflicts, then added a one-round MCP+two-User matrix option (593cfdd4). Vector tests show private-file negative controls and restartable migration, and the server deletion path now purges legacy rows without reopening a deleted User's private Index. The remaining gates and real-server campaign are in progress. Two limits are tracked separately: Share recipient vector timing (#455) and read availability while a lazy migration holds the vector cache lock (#456).

Merged dev once at 2f996336 without conflicts, then added a one-round MCP+two-User matrix option (593cfdd4). Vector tests show private-file negative controls and restartable migration, and the server deletion path now purges legacy rows without reopening a deleted User's private Index. The remaining gates and real-server campaign are in progress. Two limits are tracked separately: Share recipient vector timing (#455) and read availability while a lazy migration holds the vector cache lock (#456).
Author
Owner

Code review found one additional #435 API gap: although the text and CLIP files are private, their internal read methods still accept a plain owner ID. #458 is the self-contained follow-up to replace that parameter with a capability minted from authenticated User context. I added this limit to the cross-User inventory in b734e19a. The private-file negative-control tests still demonstrate that removing an SQL owner predicate in one User file does not reveal another User's rows.

Code review found one additional #435 API gap: although the text and CLIP files are private, their internal read methods still accept a plain owner ID. #458 is the self-contained follow-up to replace that parameter with a capability minted from authenticated User context. I added this limit to the cross-User inventory in b734e19a. The private-file negative-control tests still demonstrate that removing an SQL owner predicate in one User file does not reveal another User's rows.
Author
Owner

The first post-merge server clippy run found E0433 at wire.rs:4348: the account-deletion cleanup called calternal_embed but the server manifest did not declare that direct dependency. Added the local AGPL crate to crates/calternal-server/Cargo.toml and Cargo.lock in 78a3c150. The rerun passed: Finished dev profile [unoptimized + debuginfo] target(s) in 27.74s. Server tests and the live-server build are in progress.

The first post-merge server clippy run found E0433 at `wire.rs:4348`: the account-deletion cleanup called `calternal_embed` but the server manifest did not declare that direct dependency. Added the local AGPL crate to `crates/calternal-server/Cargo.toml` and Cargo.lock in 78a3c150. The rerun passed: `Finished dev profile [unoptimized + debuginfo] target(s) in 27.74s`. Server tests and the live-server build are in progress.
Author
Owner

For the iso-435 job (its branch includes #400): merging job/per-user-index with dev (which now has #403 tags, and #403 also changed crates/calternal-search/src/indexer.rs) makes two calternal-search tests fail deterministically, alone as well: reindex_keeps_the_previous_index_searchable_until_the_new_scan_finishes (tests/indexer.rs:363) and shared_filter_uses_only_roots_outside_the_current_user_home (tests/indexer.rs:690, left 0 right 1). #400 is therefore not merged into dev yet. Resolve this when you merge dev before your final gates, so the combined #400 + #435 lands as one merge with both tests green.

For the iso-435 job (its branch includes #400): merging job/per-user-index with dev (which now has #403 tags, and #403 also changed crates/calternal-search/src/indexer.rs) makes two calternal-search tests fail **deterministically**, alone as well: `reindex_keeps_the_previous_index_searchable_until_the_new_scan_finishes` (tests/indexer.rs:363) and `shared_filter_uses_only_roots_outside_the_current_user_home` (tests/indexer.rs:690, left 0 right 1). #400 is therefore **not** merged into dev yet. Resolve this when you merge dev before your final gates, so the combined #400 + #435 lands as one merge with both tests green.
Author
Owner

The first real-server campaign reached fixture creation, then stopped on two probe setup defects: the inherited authorization policy map lacked Apps and Mail routes, and combined matrix mode had not requested MCP credentials. Commits 64c9f061 and c16370f1 classify those routes and provision the scoped credentials. Offline preflight now classifies all 284 OpenAPI operations and identity slots. I am rerunning the same bounded campaign to get the actual cross-User result.

The first real-server campaign reached fixture creation, then stopped on two probe setup defects: the inherited authorization policy map lacked Apps and Mail routes, and combined matrix mode had not requested MCP credentials. Commits 64c9f061 and c16370f1 classify those routes and provision the scoped credentials. Offline preflight now classifies all 284 OpenAPI operations and identity slots. I am rerunning the same bounded campaign to get the actual cross-User result.
Author
Owner

One time-boxed live-server campaign completed after the harness setup repair. The Derived Search/Photos checks in verify_derived_isolation() returned without error, and MCP reported:
MCP Inspector listed 8 tools and called search, create_log, and open.
MCP read scope denied create_log; API-only scope received HTTP 403.
Cross-User Note read was denied; oversized request received HTTP 413.
Malformed request returned HTTP 415; 48 parallel calls had no HTTP 5xx.

The overall runner exited 1 because the inherited generic matrix flags D's HTTP 200 read of A's Files Item. D has a live Share of this exact file by the fixture's own design. It reported 284 operations classified; 124 operations replayed; 436 A-ID vs missing-ID comparisons across B, C, D and anonymous and three failures, all for D GET /api/v1/files/items/{id}. The existing assertion remains unchanged per the owner rule. #461 asks for the permission/expectation decision. No 5xx or crash appeared in this round.

One time-boxed live-server campaign completed after the harness setup repair. The Derived Search/Photos checks in `verify_derived_isolation()` returned without error, and MCP reported: `MCP Inspector listed 8 tools and called search, create_log, and open.` `MCP read scope denied create_log; API-only scope received HTTP 403.` `Cross-User Note read was denied; oversized request received HTTP 413.` `Malformed request returned HTTP 415; 48 parallel calls had no HTTP 5xx.` The overall runner exited 1 because the inherited generic matrix flags D's HTTP 200 read of A's Files Item. D has a live Share of this exact file by the fixture's own design. It reported `284 operations classified; 124 operations replayed; 436 A-ID vs missing-ID comparisons across B, C, D and anonymous` and three failures, all for D GET `/api/v1/files/items/{id}`. The existing assertion remains unchanged per the owner rule. #461 asks for the permission/expectation decision. No 5xx or crash appeared in this round.
Author
Owner

Final report for #435

Branch: job/iso-435. Head: c16370f1641257af27efdfd1c657473cf6360c50. I merged dev once at 2f996336. I did not push, deploy, or merge into dev.

Built

  • Text and CLIP vectors now open in separate SQLite files for each User under .system/index/users/<User ID>/vectors/. The Root opens this directory through a validated handle. The Indexers use an LRU and eight held-file permits, with at most two SQLite connections per private file.
  • Lazy migration copies one User's rows, checks row counts, commits a ready marker, and then removes old shared rows in restartable pages. Tests cover interrupted copy and cleanup. A raw SQL query without an owner predicate in User A's private file returns no User B rows.
  • Account deletion removes remaining shared text and CLIP rows without opening a new private file for the deleted User.
  • docs/audits/cross-user-inventory.md classifies Instance state, private User state, owner-keyed shared tables, and shared caches or queues. The large remaining C and D roots have self-contained follow-up issues #437–#458.
  • The real-server two-User matrix checks private Search and Photos response content and byte size. It also calls CLI Search with B and C Installation tokens. The MCP probe searches for another User's Note. The combined harness now provisions MCP credentials and classifies all 284 current OpenAPI operations.

Changed files: Cargo.lock; crates/calternal-fs/src/{root,lib}.rs; crates/calternal-embed/src/{user_store,store,clip_store,lib}.rs; crates/calternal-server/{Cargo.toml,src/wire.rs}; docs/audits/{cross-user-inventory,per-user-index-400}.md; tests/adversarial/{authz_matrix,xuser_matrix,mcp_probe}.py, run.sh, and setup.mjs.

Gate output

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

cargo clippy -p calternal-fs --all-targets -- -D warnings:

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.35s

cargo test -p calternal-fs:

test result: ok. 39 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 10.73s
test result: ok. 42 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 3.20s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

cargo clippy -p calternal-embed --all-targets -- -D warnings:

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 28s

cargo test -p calternal-embed:

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

cargo clippy -p calternal-server --all-targets -- -D warnings:

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 27.74s

cargo test -p calternal-server:

test result: ok. 82 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 14.23s

bun run --cwd apps/web build passed on the production app. cargo clean output:

     Removed 17373 files, 10.0GiB total

I removed apps/web/build and apps/web/.svelte-kit/output. git diff --check passed. The worktree is clean.

Real-server adversarial result

The campaign ran on the production server with the two-User matrix and MCP probe. The Derived Search and Photos checks returned without error. The MCP result was:

MCP Inspector listed 8 tools and called search, create_log, and open.
MCP read scope denied create_log; API-only scope received HTTP 403.
Cross-User Note read was denied; oversized request received HTTP 413.
Malformed request returned HTTP 415; 48 parallel calls had no HTTP 5xx.

The overall runner exited 1. Its three findings were all D's read of A's Files Item through D's live Share:

!! D GET /api/v1/files/items/{id}: response contains A-owned identity/data (xusermatrix-d3929ece5ccf/private-d3929ece5ccf.txt)
!! D GET /api/v1/files/items/{id}: exists/missing response profiles differ: (200, 402, '402', 'application/json') vs (404, 57, '57', 'application/json')
!! D GET /api/v1/files/items/{id}: A-owned object read returned 200
two-User OpenAPI matrix found 3 failures

The fixture grants D this Share and expects D to find it in Search. I left the existing matrix expectation unchanged under the owner rule. #461 asks for an owner decision on that permission and assertion. This round found no HTTP 5xx or crash.

Known gaps

  • #456: a lazy migration holds the shared LRU lock. Queries wait during the copy instead of reading the old store until the new file is verified. This can cause cross-User latency under a large migration. The eight-file limit is a named constant, not a runtime configuration value.
  • #458: text and CLIP read methods still accept a plain owner ID. The server passes the authenticated ID, but the API does not make a forged cross-User read handle impossible to express.
  • #455: a Share recipient can search the source User's vector file. Root filtering protects current results, but source Index size and first-open time remain an inference risk. Recipient-owned vectors are still required.
  • The old shared files retain rows for Users who have not opened their lazy private file. The inventory and #456 track completion of that transition. Other C and D stores remain in the follow-up issues listed in the inventory.
  • The real probe did not directly invoke browser WebMCP. It also cannot invoke a similar-photo or related-item route because those routes are not in the current OpenAPI contract. Response size is checked for private Search and Photos, but zero timing leakage is not proved.

These gaps mean #435 does not yet prove every requested isolation and availability invariant. I did not change any existing test expectation to hide the live matrix failure.

Decisions where DESIGN was silent

I chose fixed semantic.sqlite and photos.sqlite names below each User's vector directory, an eight-file LRU, and two SQLite connections per private file. I kept the existing model files Instance-shared and read-only. These choices bound open handles without making request input select a path.

## Final report for #435 Branch: `job/iso-435`. Head: `c16370f1641257af27efdfd1c657473cf6360c50`. I merged `dev` once at `2f996336`. I did not push, deploy, or merge into `dev`. ### Built - Text and CLIP vectors now open in separate SQLite files for each User under `.system/index/users/<User ID>/vectors/`. The Root opens this directory through a validated handle. The Indexers use an LRU and eight held-file permits, with at most two SQLite connections per private file. - Lazy migration copies one User's rows, checks row counts, commits a ready marker, and then removes old shared rows in restartable pages. Tests cover interrupted copy and cleanup. A raw SQL query without an owner predicate in User A's private file returns no User B rows. - Account deletion removes remaining shared text and CLIP rows without opening a new private file for the deleted User. - `docs/audits/cross-user-inventory.md` classifies Instance state, private User state, owner-keyed shared tables, and shared caches or queues. The large remaining C and D roots have self-contained follow-up issues #437–#458. - The real-server two-User matrix checks private Search and Photos response content and byte size. It also calls CLI Search with B and C Installation tokens. The MCP probe searches for another User's Note. The combined harness now provisions MCP credentials and classifies all 284 current OpenAPI operations. Changed files: `Cargo.lock`; `crates/calternal-fs/src/{root,lib}.rs`; `crates/calternal-embed/src/{user_store,store,clip_store,lib}.rs`; `crates/calternal-server/{Cargo.toml,src/wire.rs}`; `docs/audits/{cross-user-inventory,per-user-index-400}.md`; `tests/adversarial/{authz_matrix,xuser_matrix,mcp_probe}.py`, `run.sh`, and `setup.mjs`. ### Gate output `cargo fmt --all -- --check`: exit 0, no output. `cargo clippy -p calternal-fs --all-targets -- -D warnings`: ``` Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.35s ``` `cargo test -p calternal-fs`: ``` test result: ok. 39 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 10.73s test result: ok. 42 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 3.20s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s ``` `cargo clippy -p calternal-embed --all-targets -- -D warnings`: ``` Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 28s ``` `cargo test -p calternal-embed`: ``` test result: ok. 25 passed; 0 failed; 4 ignored; 0 measured; 0 filtered out; finished in 3.73s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s ``` `cargo clippy -p calternal-server --all-targets -- -D warnings`: ``` Finished `dev` profile [unoptimized + debuginfo] target(s) in 27.74s ``` `cargo test -p calternal-server`: ``` test result: ok. 82 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 14.23s ``` `bun run --cwd apps/web build` passed on the production app. `cargo clean` output: ``` Removed 17373 files, 10.0GiB total ``` I removed `apps/web/build` and `apps/web/.svelte-kit/output`. `git diff --check` passed. The worktree is clean. ### Real-server adversarial result The campaign ran on the production server with the two-User matrix and MCP probe. The Derived Search and Photos checks returned without error. The MCP result was: ``` MCP Inspector listed 8 tools and called search, create_log, and open. MCP read scope denied create_log; API-only scope received HTTP 403. Cross-User Note read was denied; oversized request received HTTP 413. Malformed request returned HTTP 415; 48 parallel calls had no HTTP 5xx. ``` The overall runner exited 1. Its three findings were all D's read of A's Files Item through D's live Share: ``` !! D GET /api/v1/files/items/{id}: response contains A-owned identity/data (xusermatrix-d3929ece5ccf/private-d3929ece5ccf.txt) !! D GET /api/v1/files/items/{id}: exists/missing response profiles differ: (200, 402, '402', 'application/json') vs (404, 57, '57', 'application/json') !! D GET /api/v1/files/items/{id}: A-owned object read returned 200 two-User OpenAPI matrix found 3 failures ``` The fixture grants D this Share and expects D to find it in Search. I left the existing matrix expectation unchanged under the owner rule. #461 asks for an owner decision on that permission and assertion. This round found no HTTP 5xx or crash. ### Known gaps - #456: a lazy migration holds the shared LRU lock. Queries wait during the copy instead of reading the old store until the new file is verified. This can cause cross-User latency under a large migration. The eight-file limit is a named constant, not a runtime configuration value. - #458: text and CLIP read methods still accept a plain owner ID. The server passes the authenticated ID, but the API does not make a forged cross-User read handle impossible to express. - #455: a Share recipient can search the source User's vector file. Root filtering protects current results, but source Index size and first-open time remain an inference risk. Recipient-owned vectors are still required. - The old shared files retain rows for Users who have not opened their lazy private file. The inventory and #456 track completion of that transition. Other C and D stores remain in the follow-up issues listed in the inventory. - The real probe did not directly invoke browser WebMCP. It also cannot invoke a similar-photo or related-item route because those routes are not in the current OpenAPI contract. Response size is checked for private Search and Photos, but zero timing leakage is not proved. These gaps mean #435 does not yet prove every requested isolation and availability invariant. I did not change any existing test expectation to hide the live matrix failure. ### Decisions where DESIGN was silent I chose fixed `semantic.sqlite` and `photos.sqlite` names below each User's vector directory, an eight-file LRU, and two SQLite connections per private file. I kept the existing model files Instance-shared and read-only. These choices bound open handles without making request input select a path.
Author
Owner

Round 2 resumed on job/iso-435 at c16370f1; merged dev (current merge commit in branch history). Findings: vector read methods accepted Option<&str> User IDs and opened each owner named by request roots; lazy migration ran synchronously inside the read pool lookup. The Search shared-filter fixture had a synthetic root but no live Share projection, so the recipient private Index had no shared document. The #331 two-User matrix treated D's live Share Item as private despite D's grant (#461).

I am changing reads to a typed capability from authenticated Search/Plugin request contexts, making migration reads continue on the legacy source until the verified private file is published, and aligning the Search fixture and #461 matrix with live Share grants. The Share vector projection remains under implementation; no final gate claim yet.

Round 2 resumed on `job/iso-435` at c16370f1; merged `dev` (current merge commit in branch history). Findings: vector read methods accepted `Option<&str>` User IDs and opened each owner named by request roots; lazy migration ran synchronously inside the read pool lookup. The Search shared-filter fixture had a synthetic root but no live Share projection, so the recipient private Index had no shared document. The #331 two-User matrix treated D's live Share Item as private despite D's grant (#461). I am changing reads to a typed capability from authenticated Search/Plugin request contexts, making migration reads continue on the legacy source until the verified private file is published, and aligning the Search fixture and #461 matrix with live Share grants. The Share vector projection remains under implementation; no final gate claim yet.
Author
Owner

#455 finding and implementation evidence: both Share vector readers now take an authenticated Search/Plugin request capability rather than an owner ID. For a Share root users/bob/Photos/shared, the CLIP SQL query uses INDEXED BY photo_clip_documents_path with exact and bounded path-range predicates. EXPLAIN QUERY PLAN reports SEARCH photo_clip_documents USING INDEX photo_clip_documents_path (path=?) and SEARCH photo_clip_documents USING INDEX photo_clip_documents_path (path>? AND path<?); users/bob/Photos/sharedX/photo.jpg is excluded. The semantic query uses the same indexed path boundary and reads only rows in the Share subtree before scoring. A cold Share query starts owner preparation in the background instead of waiting for the owner's unshared rows. Unit regressions for both vector kinds are added; the Rust build is still running on the shared host.

#455 finding and implementation evidence: both Share vector readers now take an authenticated Search/Plugin request capability rather than an owner ID. For a Share root `users/bob/Photos/shared`, the CLIP SQL query uses `INDEXED BY photo_clip_documents_path` with exact and bounded path-range predicates. `EXPLAIN QUERY PLAN` reports `SEARCH photo_clip_documents USING INDEX photo_clip_documents_path (path=?)` and `SEARCH photo_clip_documents USING INDEX photo_clip_documents_path (path>? AND path<?)`; `users/bob/Photos/sharedX/photo.jpg` is excluded. The semantic query uses the same indexed path boundary and reads only rows in the Share subtree before scoring. A cold Share query starts owner preparation in the background instead of waiting for the owner's unshared rows. Unit regressions for both vector kinds are added; the Rust build is still running on the shared host.
Author
Owner

Migration-number check before final gates: git fetch origin completed on this branch. origin/dev:crates/plugins/files/migrations ends at 0015_user_scoped_cursors.sql; this branch's 0016_share_search_invalidations.sql remains the next free Files number. No renumber is needed at this check.

Migration-number check before final gates: `git fetch origin` completed on this branch. `origin/dev:crates/plugins/files/migrations` ends at `0015_user_scoped_cursors.sql`; this branch's `0016_share_search_invalidations.sql` remains the next free Files number. No renumber is needed at this check.
Author
Owner

Combined-merge Search result: the old shared_filter_uses_only_roots_outside_the_current_user_home fixture supplied a foreign root but had no live Share row, so the recipient's private Index could not contain the file. The fixture now creates the Share projection and rebuilds Alice's private generation. Full cargo test -p calternal-search exited 0. Relevant output:

test reindex_keeps_the_previous_index_searchable_until_the_new_scan_finishes ... ok
test shared_filter_uses_only_roots_outside_the_current_user_home ... ok
test result: ok. 21 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 9.60s
Combined-merge Search result: the old `shared_filter_uses_only_roots_outside_the_current_user_home` fixture supplied a foreign root but had no live Share row, so the recipient's private Index could not contain the file. The fixture now creates the Share projection and rebuilds Alice's private generation. Full `cargo test -p calternal-search` exited 0. Relevant output: ``` test reindex_keeps_the_previous_index_searchable_until_the_new_scan_finishes ... ok test shared_filter_uses_only_roots_outside_the_current_user_home ... ok test result: ok. 21 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 9.60s ```
Author
Owner

Round 2 finding: the vector negative control passed. cargo test -p calternal-embed exits 0 and includes test user_store::tests::unfiltered_private_query_cannot_cross_users ... ok. That test deliberately runs a private vector query without an owner predicate and confirms another User's marker is absent because their vectors are in a separate SQLite file. cargo test -p calternal-search also exits 0 after the dev merge, including the live Share filter regression and staged-reindex test.

Round 2 finding: the vector negative control passed. `cargo test -p calternal-embed` exits 0 and includes `test user_store::tests::unfiltered_private_query_cannot_cross_users ... ok`. That test deliberately runs a private vector query without an owner predicate and confirms another User's marker is absent because their vectors are in a separate SQLite file. `cargo test -p calternal-search` also exits 0 after the dev merge, including the live Share filter regression and staged-reindex test.
Author
Owner

Round 2 gate progress at head af2349b3: cargo fmt --check exited 0; cargo clippy -p calternal-embed --all-targets -- -D warnings exited 0 (Finished dev profile ... in 32m 45s); cargo test -p calternal-embed and full cargo test -p calternal-search exited 0. The production web build exited 0; bun run --cwd apps/web check reported svelte-check found 0 errors and 0 warnings; bun run --cwd apps/web test reported Test Files 126 passed (126) and Tests 809 passed (809). The single real-server isolation run is now compiling server, CLI and sync binaries before it starts the #461, matrix and MCP probes. No runtime finding yet.

Round 2 gate progress at head af2349b3: `cargo fmt --check` exited 0; `cargo clippy -p calternal-embed --all-targets -- -D warnings` exited 0 (`Finished dev profile ... in 32m 45s`); `cargo test -p calternal-embed` and full `cargo test -p calternal-search` exited 0. The production web build exited 0; `bun run --cwd apps/web check` reported `svelte-check found 0 errors and 0 warnings`; `bun run --cwd apps/web test` reported `Test Files 126 passed (126)` and `Tests 809 passed (809)`. The single real-server isolation run is now compiling server, CLI and sync binaries before it starts the #461, matrix and MCP probes. No runtime finding yet.
Author
Owner

Round 3 start. Branch: job/iso-435; base: 43c1377c8fd3b1b495841446989a64156f8ae1e4; current head: af2349b3786fcf27b70de01e92f4b6f9932183d9. I am continuing the remaining Share-recipient capability and timing review, the full calternal-search test, the two-User matrix with its negative control, then I will fetch and merge origin/dev once, check migration numbers, and run the required crate gates.

Round 3 start. Branch: `job/iso-435`; base: `43c1377c8fd3b1b495841446989a64156f8ae1e4`; current head: `af2349b3786fcf27b70de01e92f4b6f9932183d9`. I am continuing the remaining Share-recipient capability and timing review, the full `calternal-search` test, the two-User matrix with its negative control, then I will fetch and merge `origin/dev` once, check migration numbers, and run the required crate gates.
Author
Owner

Finding: UserVectorPools::get_for_read(VectorReadSource::Share) currently calls schedule_migration(owner_id). That task calls get(), which copies and verifies every source-User vector row, including private rows, while a recipient request is active. The CLIP and semantic Share SQL is already constrained by a path-indexed exact root/range before vector scoring, but this background copy can create a timing and resource-contention signal based on unrelated source data. I am removing Share-triggered migration. Share reads will use the scoped legacy query while old rows remain; after cleanup has removed those rows, they reopen the verified private file.

Finding: `UserVectorPools::get_for_read(VectorReadSource::Share)` currently calls `schedule_migration(owner_id)`. That task calls `get()`, which copies and verifies every source-User vector row, including private rows, while a recipient request is active. The CLIP and semantic Share SQL is already constrained by a path-indexed exact root/range before vector scoring, but this background copy can create a timing and resource-contention signal based on unrelated source data. I am removing Share-triggered migration. Share reads will use the scoped legacy query while old rows remain; after cleanup has removed those rows, they reopen the verified private file.
Author
Owner

Verified cargo test -p calternal-search on the combined #400 + #435 branch; it exited 0. The required tests passed:

test reindex_keeps_the_previous_index_searchable_until_the_new_scan_finishes ... ok
test shared_filter_uses_only_roots_outside_the_current_user_home ... ok
test result: ok. 21 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 7.09s

The full Search crate run also passed all other test targets. Current head: b9fa428d. I am fetching and merging origin/dev once before the final crate gates and live two-User matrix.

Verified `cargo test -p calternal-search` on the combined #400 + #435 branch; it exited 0. The required tests passed: ```text test reindex_keeps_the_previous_index_searchable_until_the_new_scan_finishes ... ok test shared_filter_uses_only_roots_outside_the_current_user_home ... ok test result: ok. 21 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 7.09s ``` The full Search crate run also passed all other test targets. Current head: `b9fa428d`. I am fetching and merging `origin/dev` once before the final crate gates and live two-User matrix.
Author
Owner

Round 3 resumed on job/iso-435 at 0158d1af4106f717d3cf27740c9b618e798163cc (merge base with origin/dev: 918b4764aded5aa385ef784acb8887cf4361bf36). The vector isolation, migration read, and Share status-probe commits from round 2 are present. Continuing with Share recipient scoping, the full search test, origin/dev migration audit, and per-crate gates.

Round 3 resumed on `job/iso-435` at `0158d1af4106f717d3cf27740c9b618e798163cc` (merge base with `origin/dev`: `918b4764aded5aa385ef784acb8887cf4361bf36`). The vector isolation, migration read, and Share status-probe commits from round 2 are present. Continuing with Share recipient scoping, the full search test, origin/dev migration audit, and per-crate gates.
Author
Owner

Finding: docs/audits/cross-user-inventory.md still said vector reads accepted a plain owner ID and that the capability boundary was unfinished. The current Search and Photos APIs take VectorReadCapability; Share roots become typed exact-root capabilities. Both vector Share query functions use their path index directly and skip LSH, while share_read_does_not_start_source_user_migration confirms a Share read does not copy the source's private vectors. I corrected the inventory and recorded the scoped-read choice plus the SQLite B-tree timing limit.

Finding: `docs/audits/cross-user-inventory.md` still said vector reads accepted a plain owner ID and that the capability boundary was unfinished. The current Search and Photos APIs take `VectorReadCapability`; Share roots become typed exact-root capabilities. Both vector Share query functions use their path index directly and skip LSH, while `share_read_does_not_start_source_user_migration` confirms a Share read does not copy the source's private vectors. I corrected the inventory and recorded the scoped-read choice plus the SQLite B-tree timing limit.
Author
Owner

Round 3 resumes on branch job/iso-435 at ad316050703c3201fc709e8945463dd58d05fac9. The worktree is clean; prior vector capability and Share status probe commits are present. I am checking the Share timing boundary and will run the combined Search tests, origin/dev merge check, per-crate gates, and full live matrix.

Round 3 resumes on branch `job/iso-435` at `ad316050703c3201fc709e8945463dd58d05fac9`. The worktree is clean; prior vector capability and Share status probe commits are present. I am checking the Share timing boundary and will run the combined Search tests, origin/dev merge check, per-crate gates, and full live matrix.
Author
Owner

Finding after origin/dev merge: cargo test -p calternal-search stopped in the library suite because staged_publication_waits_for_search_readers_before_swapping_directories did not observe the queued writer within its 5 s guard. The host was compiling several unrelated Rust jobs at the same time. No expected behavior was changed; I will do one serial full-suite retry so the shared-filter test also runs.

Finding after origin/dev merge: `cargo test -p calternal-search` stopped in the library suite because `staged_publication_waits_for_search_readers_before_swapping_directories` did not observe the queued writer within its 5 s guard. The host was compiling several unrelated Rust jobs at the same time. No expected behavior was changed; I will do one serial full-suite retry so the shared-filter test also runs.
Author
Owner

Search suite update: the serial full run passes shared_filter_uses_only_roots_outside_the_current_user_home. It fails search_drops_a_hit_whose_file_is_gone_and_queues_its_removal: after the test deletes an indexed file, searches during the gap, recreates the same path, and calls notify_changed, the recreated hit is not indexed. I am tracing the queued Delete/Add ordering before the final gates.

Search suite update: the serial full run passes `shared_filter_uses_only_roots_outside_the_current_user_home`. It fails `search_drops_a_hit_whose_file_is_gone_and_queues_its_removal`: after the test deletes an indexed file, searches during the gap, recreates the same path, and calls `notify_changed`, the recreated hit is not indexed. I am tracing the queued Delete/Add ordering before the final gates.
Author
Owner

Resuming round 3 on branch job/iso-435 at cc98d5cc86. The worktree is clean. This head includes an origin/dev merge with parent ad31605070; I will continue from the recorded round-2 commits and finish the recipient-capability audit, full calternal-search test, origin/dev sync check, and requested gates.

Resuming round 3 on branch job/iso-435 at cc98d5cc86ee7c9fb6f861f3a286be8cee5b7498. The worktree is clean. This head includes an origin/dev merge with parent ad316050703c3201fc709e8945463dd58d05fac9; I will continue from the recorded round-2 commits and finish the recipient-capability audit, full calternal-search test, origin/dev sync check, and requested gates.
Author
Owner

Merge finding: origin/dev's newer matrix assigned D the Admin token, while the live Share fixture targets user_d. That would remove the #461 recipient read proof. The merged matrix keeps D on bearer_d and adds a separate admin identity/peer, so both Share access and the #472 Admin checks run. Validation: XUSER_CLASSIFY_ONLY=1 python3 tests/adversarial/xuser_matrix.py → Cross-User classification gate: 308 operations classified.

Merge finding: origin/dev's newer matrix assigned D the Admin token, while the live Share fixture targets user_d. That would remove the #461 recipient read proof. The merged matrix keeps D on bearer_d and adds a separate admin identity/peer, so both Share access and the #472 Admin checks run. Validation: XUSER_CLASSIFY_ONLY=1 python3 tests/adversarial/xuser_matrix.py → Cross-User classification gate: 308 operations classified.
Author
Owner

#455 finding: a cold Share read checked legacy_has_user_rows(owner) across the source User's whole vector index. That selected the legacy or private read path based on unrelated, unshared vectors and could expose their existence through latency. I am replacing that owner-wide probe with a fixed per-User migration marker check; before cleanup, the Share stays on the scoped legacy query and does not start a copy.

#455 finding: a cold Share read checked legacy_has_user_rows(owner) across the source User's whole vector index. That selected the legacy or private read path based on unrelated, unshared vectors and could expose their existence through latency. I am replacing that owner-wide probe with a fixed per-User migration marker check; before cleanup, the Share stays on the scoped legacy query and does not start a copy.
Author
Owner

#455 fix committed as 868c47ffe. Share source selection no longer asks whether any vector exists under the source User's Home. It checks only the per-User legacy-cleanup marker with a non-waiting read; before cleanup, the exact Share-root query stays on the legacy store. The in-memory ready marker remains after cleanup, and a recreated pool restores readiness from disk. Evidence: and clippy exited 0 ().

#455 fix committed as 868c47ffe. Share source selection no longer asks whether any vector exists under the source User's Home. It checks only the per-User legacy-cleanup marker with a non-waiting read; before cleanup, the exact Share-root query stays on the legacy store. The in-memory ready marker remains after cleanup, and a recreated pool restores readiness from disk. Evidence: and clippy exited 0 ().
Author
Owner

#455 fix committed as 868c47ffe. Share source selection no longer asks whether any vector exists under the source User's Home. It checks only the per-User legacy-cleanup marker with a non-waiting read; before cleanup, the exact Share-root query stays on the legacy store. The in-memory ready marker remains after cleanup, and a recreated pool restores readiness from disk.

Evidence:

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

test result: ok. 31 passed; 0 failed; 4 ignored; 0 measured; 0 filtered out; finished in 5.68s
#455 fix committed as 868c47ffe. Share source selection no longer asks whether any vector exists under the source User's Home. It checks only the per-User legacy-cleanup marker with a non-waiting read; before cleanup, the exact Share-root query stays on the legacy store. The in-memory ready marker remains after cleanup, and a recreated pool restores readiness from disk. Evidence: ```text Finished `dev` profile [unoptimized + debuginfo] target(s) in 3m 25s test result: ok. 31 passed; 0 failed; 4 ignored; 0 measured; 0 filtered out; finished in 5.68s ```
Author
Owner

cargo test -p calternal-server compiled and ran. Result: 84 passed, 2 ignored, 1 failed. The only failure is wire::tests::live_apps_run_in_separate_processes, whose nested full_app_setup_session_config_and_backup timed out while waiting for the asynchronous User deletion to finish (crates/calternal-server/src/wire.rs:7350-7359, 5-second timeout). The outer process spent 36.35 seconds before failing. Other server tests passed, including the Search provider scoping and filtering tests. I kept the existing timeout assertion unchanged; this is recorded as an unresolved test-gate gap.

`cargo test -p calternal-server` compiled and ran. Result: 84 passed, 2 ignored, 1 failed. The only failure is `wire::tests::live_apps_run_in_separate_processes`, whose nested `full_app_setup_session_config_and_backup` timed out while waiting for the asynchronous User deletion to finish (`crates/calternal-server/src/wire.rs:7350-7359`, 5-second timeout). The outer process spent 36.35 seconds before failing. Other server tests passed, including the Search provider scoping and filtering tests. I kept the existing timeout assertion unchanged; this is recorded as an unresolved test-gate gap.
Author
Owner

The live Share isolation probe found that GET /api/v1/files/download returns 403 for unshared sibling paths, prefix-sibling paths, and a revoked Share; the probe requires 404 for all three to hide whether the owner item exists. This contradicts #435's 404 non-disclosure contract. The same phase found its Photos test was not seeded: the owner timeline returned 200 with no Items, then the shared timeline also stayed empty. The server remained alive and the probe classified zero SLOW findings. I am fixing the read authorization response and adding a real Photos fixture to the probe before validation.

The live Share isolation probe found that `GET /api/v1/files/download` returns 403 for unshared sibling paths, prefix-sibling paths, and a revoked Share; the probe requires 404 for all three to hide whether the owner item exists. This contradicts #435's 404 non-disclosure contract. The same phase found its Photos test was not seeded: the owner timeline returned 200 with no Items, then the shared timeline also stayed empty. The server remained alive and the probe classified zero SLOW findings. I am fixing the read authorization response and adding a real Photos fixture to the probe before validation.
Author
Owner

Files package validation on the final test tree reported 128 passed, 1 ignored, and 1 failed. The existing 1,000-write atomic-write/reconcile stress test hit its fixed 300-second deadline. After the test aborted its writer, atomic write 933 reported entry not found. The same stress test passed in an earlier Files run that completed in 274.24 seconds. I did not change its timeout or assertions; this run is recorded as a load-sensitive gate failure.

Files package validation on the final test tree reported 128 passed, 1 ignored, and 1 failed. The existing 1,000-write atomic-write/reconcile stress test hit its fixed 300-second deadline. After the test aborted its writer, atomic write 933 reported `entry not found`. The same stress test passed in an earlier Files run that completed in 274.24 seconds. I did not change its timeout or assertions; this run is recorded as a load-sensitive gate failure.
Author
Owner

iso-435 / #435 — final report

Branch: job/iso-435
Head: 5713474fe5319f677b11f3cd99b63455188849d8
One-time merged base: dfb5964a2fcf13dc8b9a50a319eee09bc386f322

Built

  • Search and CLIP reads use authenticated Home and Share capabilities. Share reads are restricted to the exact indexed subtree. Private per-User vector files are selected by a durable ready marker; migration and missing-marker reads stay on the exact-root-scoped legacy source, without probing whether unshared vectors exist.
  • Ordinary unshared, stale, or revoked Files reads return the same 404 as a missing Item through single download, copy, and ZIP download. Hidden-path and write denials remain 403. The OpenAPI contract was regenerated.
  • The real-server isolation probe now seeds a real JPEG. The MCP probe tracks the merged Mail Reader tool and reports the actual tool count.

Files

  • Cargo.lock, contracts/openapi.json
  • crates/calternal-embed/{Cargo.toml,src/{clip_store.rs,lib.rs,store.rs,user_store.rs}}
  • crates/calternal-fs/src/{lib.rs,path.rs,root.rs,user_homes.rs}
  • crates/calternal-search/{Cargo.toml,src/{index.rs,indexer.rs,plugin.rs,query.rs},tests/{indexer.rs,relevance.rs}}
  • crates/calternal-server/{Cargo.toml,src/{main.rs,wire.rs}}
  • crates/plugins/files/{migrations/0016_share_search_invalidations.sql,src/{archive.rs,lib.rs,shares.rs}}
  • crates/plugins/photos/src/{routes.rs,search.rs}
  • docs/audits/{cross-user-inventory.md,per-user-index-400.md}
  • tests/adversarial/{attack2.py,authz_matrix.py,mcp_probe.py,run.sh,setup.mjs,xuser_matrix.py}

Gate output

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

cargo clippy -p calternal-search --all-targets -- -D warnings:

Finished `dev` profile [unoptimized + debuginfo] target(s) in 19m 49s

cargo test -p calternal-search: passed. shared_filter_uses_only_roots_outside_the_current_user_home and reindex_keeps_the_previous_index_searchable_until_the_new_scan_finishes both passed on the first run. Package suites totaled 70 passed, 4 ignored.

cargo test -p calternal-embed: 31 passed, 0 failed, 4 ignored. The negative control unfiltered_private_query_cannot_cross_users passed: an unfiltered SQL query against Alice's private file did not return Bob's vector.

cargo test -p calternal-fs: 40 unit tests and 42 integration tests passed.

cargo clippy -p calternal-plugin-files --all-targets -- -D warnings:

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

The final Files package run had one timeout in its existing atomic-write/reconcile stress test:

test result: FAILED. 128 passed; 1 failed; 1 ignored; 0 measured; 0 filtered out; finished in 340.54s
error: test failed, to rerun pass `-p calternal-plugin-files --lib`

The failure was internal_temp_paths_never_enter_index_during_atomic_write_reconcile_storm: it reached its fixed 300-second deadline. After the test aborted its writer, write 933 reported entry not found. The same stress test passed in an earlier package run that completed in 274.24s. No expectation or timeout was changed.

cargo clippy -p calternal-server --all-targets -- -D warnings:

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

cargo test -p calternal-server:

test result: ok. 85 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 42.17s

cargo test -p calternal-plugin-photos:

test result: ok. 44 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 19.55s

Photos Clippy passed. cargo clean:

Removed 22938 files, 17.8GiB total

Live matrix

The local production server campaign exited 0. Share isolation reported ROUND 2 FINDINGS 0; the two SLOW fixture requests were 6.0s and 5.2s. The four-identity matrix replayed 154 operations with 693 A-ID/missing-ID comparisons across B, C, D, and anonymous; it found 0 ownership-denial failures. The revoked Share control returned identical HTTP 404 profiles with a 10.3 ms median delta. MCP denied the cross-User Note read, returned 403 for API-only writes, returned 413 for oversized input, and had no HTTP 5xx in 48 parallel calls.

The run's MCP success line was hard-coded to say “8 tools”; its expected-set comparison included all 9 tools, including calternal_mail_reader, and passed. The output now derives the count dynamically. The correction is diagnostic-only and was checked with python3 -m py_compile; the live campaign was not rerun after that message-only change.

Known gaps and decisions

  • The Files stress test timeout above remains open as a load-sensitive gate failure. The direct Share regression assertions, including copy and ZIP, passed.
  • origin/dev advanced to 558457cf32e1d429da3834a05ff2720d11284302 after the one-time merge at dfb5964. I did not fetch or merge again, per the one-merge rule. The current origin/dev Files migrations still end at 0015_user_scoped_cursors.sql; 0016 remains next.
  • No product decision outside #435 and DESIGN §§26/48 was needed. The Photos test fixture uses the existing JPEG and a fixed capture timestamp only to keep the adversarial test date stable.
# iso-435 / #435 — final report Branch: `job/iso-435` Head: `5713474fe5319f677b11f3cd99b63455188849d8` One-time merged base: `dfb5964a2fcf13dc8b9a50a319eee09bc386f322` ## Built - Search and CLIP reads use authenticated Home and Share capabilities. Share reads are restricted to the exact indexed subtree. Private per-User vector files are selected by a durable ready marker; migration and missing-marker reads stay on the exact-root-scoped legacy source, without probing whether unshared vectors exist. - Ordinary unshared, stale, or revoked Files reads return the same 404 as a missing Item through single download, copy, and ZIP download. Hidden-path and write denials remain 403. The OpenAPI contract was regenerated. - The real-server isolation probe now seeds a real JPEG. The MCP probe tracks the merged Mail Reader tool and reports the actual tool count. ## Files - `Cargo.lock`, `contracts/openapi.json` - `crates/calternal-embed/{Cargo.toml,src/{clip_store.rs,lib.rs,store.rs,user_store.rs}}` - `crates/calternal-fs/src/{lib.rs,path.rs,root.rs,user_homes.rs}` - `crates/calternal-search/{Cargo.toml,src/{index.rs,indexer.rs,plugin.rs,query.rs},tests/{indexer.rs,relevance.rs}}` - `crates/calternal-server/{Cargo.toml,src/{main.rs,wire.rs}}` - `crates/plugins/files/{migrations/0016_share_search_invalidations.sql,src/{archive.rs,lib.rs,shares.rs}}` - `crates/plugins/photos/src/{routes.rs,search.rs}` - `docs/audits/{cross-user-inventory.md,per-user-index-400.md}` - `tests/adversarial/{attack2.py,authz_matrix.py,mcp_probe.py,run.sh,setup.mjs,xuser_matrix.py}` ## Gate output `cargo fmt --all --check`: exit 0, no output. `cargo clippy -p calternal-search --all-targets -- -D warnings`: ```text Finished `dev` profile [unoptimized + debuginfo] target(s) in 19m 49s ``` `cargo test -p calternal-search`: passed. `shared_filter_uses_only_roots_outside_the_current_user_home` and `reindex_keeps_the_previous_index_searchable_until_the_new_scan_finishes` both passed on the first run. Package suites totaled 70 passed, 4 ignored. `cargo test -p calternal-embed`: 31 passed, 0 failed, 4 ignored. The negative control `unfiltered_private_query_cannot_cross_users` passed: an unfiltered SQL query against Alice's private file did not return Bob's vector. `cargo test -p calternal-fs`: 40 unit tests and 42 integration tests passed. `cargo clippy -p calternal-plugin-files --all-targets -- -D warnings`: ```text Finished `dev` profile [unoptimized + debuginfo] target(s) in 5m 29s ``` The final Files package run had one timeout in its existing atomic-write/reconcile stress test: ```text test result: FAILED. 128 passed; 1 failed; 1 ignored; 0 measured; 0 filtered out; finished in 340.54s error: test failed, to rerun pass `-p calternal-plugin-files --lib` ``` The failure was `internal_temp_paths_never_enter_index_during_atomic_write_reconcile_storm`: it reached its fixed 300-second deadline. After the test aborted its writer, write 933 reported `entry not found`. The same stress test passed in an earlier package run that completed in 274.24s. No expectation or timeout was changed. `cargo clippy -p calternal-server --all-targets -- -D warnings`: ```text Finished `dev` profile [unoptimized + debuginfo] target(s) in 7m 55s ``` `cargo test -p calternal-server`: ```text test result: ok. 85 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 42.17s ``` `cargo test -p calternal-plugin-photos`: ```text test result: ok. 44 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 19.55s ``` Photos Clippy passed. `cargo clean`: ```text Removed 22938 files, 17.8GiB total ``` ## Live matrix The local production server campaign exited 0. Share isolation reported `ROUND 2 FINDINGS 0`; the two SLOW fixture requests were 6.0s and 5.2s. The four-identity matrix replayed 154 operations with 693 A-ID/missing-ID comparisons across B, C, D, and anonymous; it found 0 ownership-denial failures. The revoked Share control returned identical HTTP 404 profiles with a 10.3 ms median delta. MCP denied the cross-User Note read, returned 403 for API-only writes, returned 413 for oversized input, and had no HTTP 5xx in 48 parallel calls. The run's MCP success line was hard-coded to say “8 tools”; its expected-set comparison included all 9 tools, including `calternal_mail_reader`, and passed. The output now derives the count dynamically. The correction is diagnostic-only and was checked with `python3 -m py_compile`; the live campaign was not rerun after that message-only change. ## Known gaps and decisions - The Files stress test timeout above remains open as a load-sensitive gate failure. The direct Share regression assertions, including copy and ZIP, passed. - `origin/dev` advanced to `558457cf32e1d429da3834a05ff2720d11284302` after the one-time merge at `dfb5964`. I did not fetch or merge again, per the one-merge rule. The current `origin/dev` Files migrations still end at `0015_user_scoped_cursors.sql`; `0016` remains next. - No product decision outside #435 and DESIGN §§26/48 was needed. The Photos test fixture uses the existing JPEG and a fixed capture timestamp only to keep the adversarial test date stable.
Author
Owner

Search clippy passed. The full Search unit run hit the existing five-second publication wait deadline:

---- indexer::tests::staged_publication_waits_for_search_readers_before_swapping_directories stdout ----
staged publication did not wait for the active Search reader

test result: FAILED. 35 passed; 1 failed; 1 ignored; 0 measured; 0 filtered out; finished in 34.47s

The required isolated rerun passed without changing the test or its deadline:

running 1 test
test indexer::tests::staged_publication_waits_for_search_readers_before_swapping_directories ... ok

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

The test waits for a staged rebuild to reach the write lock while a reader holds the old generation. It did not reach that point within five seconds in the combined run on this shared host. Both logs are retained in the merge report. No expectation was changed.

Search clippy passed. The full Search unit run hit the existing five-second publication wait deadline: ``` ---- indexer::tests::staged_publication_waits_for_search_readers_before_swapping_directories stdout ---- staged publication did not wait for the active Search reader test result: FAILED. 35 passed; 1 failed; 1 ignored; 0 measured; 0 filtered out; finished in 34.47s ``` The required isolated rerun passed without changing the test or its deadline: ``` running 1 test test indexer::tests::staged_publication_waits_for_search_readers_before_swapping_directories ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 36 filtered out; finished in 3.50s ``` The test waits for a staged rebuild to reach the write lock while a reader holds the old generation. It did not reach that point within five seconds in the combined run on this shared host. Both logs are retained in the merge report. No expectation was changed.
Author
Owner

Merge round 3 integration report

State: incomplete; do not fast-forward this snapshot to dev yet. The four-hour job limit ended while CLI Clippy was compiling. No completed final-tree gate failed. No source branch was dropped because the completed gates showed no failing branch.

  • Branch: job/merge-round-3
  • Head: 3e5056d485e9021d2d1f708613e783b0b901b447
  • Included in order: job/multiget-500, job/dav-delete-471, job/iso-435, job/admin-deny-483, job/attach-427, job/hidden-420, job/files-sel-keys, job/small-bugs-3, job/sweep-478.
  • Additional commits: 92f5803f3, 3ea69e317, 26b986bc4, 0948cffa1, 99c088193, df6b07a5a, 3e5056d48.

Completed gate output (verbatim excerpts)

cargo fmt --check exited 0 with no output.

  • DAV clippy: Finished \dev` profile [unoptimized + debuginfo] target(s) in 56.92s`
  • DAV tests:
    test result: ok. 40 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.37s
    test result: ok. 36 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.09s
    test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.31s
  • Notes Core clippy: Finished \dev` profile [unoptimized + debuginfo] target(s) in 14.97s`
  • Notes Core tests:
    test result: ok. 512 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.21s
    test result: ok. 19 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 8.21s
    test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.06s
    test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.35s
    test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
  • Notes plugin clippy: Finished \dev` profile [unoptimized + debuginfo] target(s) in 2m 55s`
  • Notes plugin tests: test result: ok. 127 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 151.26s
  • Files clippy (after comment fix): Finished \dev` profile [unoptimized + debuginfo] target(s) in 48.77s`
  • Files tests: test result: ok. 144 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 178.62s
    The dev-version migration test passed: test tests::dev_files_schema_upgrades_through_share_log_and_sidecar_migrations ... ok
  • Calendar clippy: Finished \dev` profile [unoptimized + debuginfo] target(s) in 1m 53s`
  • Calendar tests:
    test result: ok. 52 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 7.01s
    test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.12s
    test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.22s
  • Photos clippy: Finished \dev` profile [unoptimized + debuginfo] target(s) in 1m 13s`
  • Photos tests: test result: ok. 45 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 11.10s
  • Search clippy: Finished \dev` profile [unoptimized + debuginfo] target(s) in 55.79s`
  • Search tests:
    test result: ok. 36 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 46.71s
    test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.38s
    test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.09s
    test result: ok. 21 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 10.44s
    test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.05s
    test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s
    test result: ok. 1 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 5.66s
    test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
  • Embed clippy: Finished \dev` profile [unoptimized + debuginfo] target(s) in 27.32s`
  • Embed tests: test result: ok. 31 passed; 0 failed; 4 ignored; 0 measured; 0 filtered out; finished in 1.93s
  • Filesystem clippy: Finished \dev` profile [unoptimized + debuginfo] target(s) in 10.78s`
  • Filesystem tests:
    test result: ok. 50 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 9.04s
    test result: ok. 42 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 3.56s
  • Server clippy: Finished \dev` profile [unoptimized + debuginfo] target(s) in 1m 48s`
  • Server tests: test result: ok. 85 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 19.67s

Other completed checks:

  • Parity matrix: 190 web API actions, 122 shortcuts, 2 static commands, 136 menu actions, 31 settings groups, 172 actions with adapter gaps
  • Cross-User classification gate: 311 operations classified; its test suite printed Ran 5 tests in 0.246s and OK.
  • Admin coverage: 39 reviewed operations; contract and Rust guards agree; its test suite printed Ran 14 tests in 2.404s and OK.
  • Migration audit: ai: 4 migrations, no duplicate numbers; analytics: 2 migrations, no duplicate numbers; calendar: 3 migrations, no duplicate numbers; files: 18 migrations, no duplicate numbers; mail: 8 migrations, no duplicate numbers; notes: 19 migrations, no duplicate numbers; notifications: 4 migrations, no duplicate numbers; photos: 6 migrations, no duplicate numbers; video: 1 migrations, no duplicate numbers.

Remaining work

  • CLI Clippy was interrupted at the four-hour limit while checking dependencies; CLI tests and both Auth gates did not run.
  • The generated contract check, web bun run check, bun run test, and bun run build are pending.
  • The live two-User matrix, authz matrix, DAV round (including Apple’s 100-href and DELETE re-parent replays), sidecar probe, and attachment e2e are pending.
  • Production-browser screenshots for each affected screen at 390/820/1440 px in light/dark mode are pending. No visual review artifacts were produced.
  • The new benchmark profile was added, but its local run and comparison with docs/perf/baseline.json are pending.
  • cargo clean is running but has not returned yet; apps/web/build was removed.

Decisions

  • Files migration IDs follow merge order after dev’s 0015: 0016 share_search_invalidations, 0017 log_attachment_trash, 0018 sidecar_pairs. The populated dev-schema upgrade test passed.
  • Hidden-file Settings copy leads with the User-visible result and uses “Photo edit files (.xmp, .aae)”.
  • The parity exception snapshot was regenerated and reviewed for the newly merged API and Files UI actions.

The branch contains the merged code and commits, but the listed pending gates mean this is not a green merge candidate yet.

## Merge round 3 integration report **State: incomplete; do not fast-forward this snapshot to `dev` yet.** The four-hour job limit ended while CLI Clippy was compiling. No completed final-tree gate failed. No source branch was dropped because the completed gates showed no failing branch. - Branch: `job/merge-round-3` - Head: `3e5056d485e9021d2d1f708613e783b0b901b447` - Included in order: `job/multiget-500`, `job/dav-delete-471`, `job/iso-435`, `job/admin-deny-483`, `job/attach-427`, `job/hidden-420`, `job/files-sel-keys`, `job/small-bugs-3`, `job/sweep-478`. - Additional commits: `92f5803f3`, `3ea69e317`, `26b986bc4`, `0948cffa1`, `99c088193`, `df6b07a5a`, `3e5056d48`. ### Completed gate output (verbatim excerpts) `cargo fmt --check` exited 0 with no output. - DAV clippy: `Finished \`dev\` profile [unoptimized + debuginfo] target(s) in 56.92s` - DAV tests: `test result: ok. 40 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.37s` `test result: ok. 36 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.09s` `test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.31s` - Notes Core clippy: `Finished \`dev\` profile [unoptimized + debuginfo] target(s) in 14.97s` - Notes Core tests: `test result: ok. 512 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.21s` `test result: ok. 19 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 8.21s` `test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.06s` `test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.35s` `test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s` - Notes plugin clippy: `Finished \`dev\` profile [unoptimized + debuginfo] target(s) in 2m 55s` - Notes plugin tests: `test result: ok. 127 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 151.26s` - Files clippy (after comment fix): `Finished \`dev\` profile [unoptimized + debuginfo] target(s) in 48.77s` - Files tests: `test result: ok. 144 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 178.62s` The dev-version migration test passed: `test tests::dev_files_schema_upgrades_through_share_log_and_sidecar_migrations ... ok` - Calendar clippy: `Finished \`dev\` profile [unoptimized + debuginfo] target(s) in 1m 53s` - Calendar tests: `test result: ok. 52 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 7.01s` `test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.12s` `test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.22s` - Photos clippy: `Finished \`dev\` profile [unoptimized + debuginfo] target(s) in 1m 13s` - Photos tests: `test result: ok. 45 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 11.10s` - Search clippy: `Finished \`dev\` profile [unoptimized + debuginfo] target(s) in 55.79s` - Search tests: `test result: ok. 36 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 46.71s` `test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.38s` `test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.09s` `test result: ok. 21 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 10.44s` `test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.05s` `test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s` `test result: ok. 1 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 5.66s` `test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s` - Embed clippy: `Finished \`dev\` profile [unoptimized + debuginfo] target(s) in 27.32s` - Embed tests: `test result: ok. 31 passed; 0 failed; 4 ignored; 0 measured; 0 filtered out; finished in 1.93s` - Filesystem clippy: `Finished \`dev\` profile [unoptimized + debuginfo] target(s) in 10.78s` - Filesystem tests: `test result: ok. 50 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 9.04s` `test result: ok. 42 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 3.56s` - Server clippy: `Finished \`dev\` profile [unoptimized + debuginfo] target(s) in 1m 48s` - Server tests: `test result: ok. 85 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 19.67s` Other completed checks: - `Parity matrix: 190 web API actions, 122 shortcuts, 2 static commands, 136 menu actions, 31 settings groups, 172 actions with adapter gaps` - `Cross-User classification gate: 311 operations classified`; its test suite printed `Ran 5 tests in 0.246s` and `OK`. - `Admin coverage: 39 reviewed operations; contract and Rust guards agree`; its test suite printed `Ran 14 tests in 2.404s` and `OK`. - Migration audit: `ai: 4 migrations, no duplicate numbers`; `analytics: 2 migrations, no duplicate numbers`; `calendar: 3 migrations, no duplicate numbers`; `files: 18 migrations, no duplicate numbers`; `mail: 8 migrations, no duplicate numbers`; `notes: 19 migrations, no duplicate numbers`; `notifications: 4 migrations, no duplicate numbers`; `photos: 6 migrations, no duplicate numbers`; `video: 1 migrations, no duplicate numbers`. ### Remaining work - CLI Clippy was interrupted at the four-hour limit while checking dependencies; CLI tests and both Auth gates did not run. - The generated contract check, web `bun run check`, `bun run test`, and `bun run build` are pending. - The live two-User matrix, authz matrix, DAV round (including Apple’s 100-href and DELETE re-parent replays), sidecar probe, and attachment e2e are pending. - Production-browser screenshots for each affected screen at 390/820/1440 px in light/dark mode are pending. No visual review artifacts were produced. - The new benchmark profile was added, but its local run and comparison with `docs/perf/baseline.json` are pending. - `cargo clean` is running but has not returned yet; `apps/web/build` was removed. ### Decisions - Files migration IDs follow merge order after dev’s 0015: 0016 `share_search_invalidations`, 0017 `log_attachment_trash`, 0018 `sidecar_pairs`. The populated dev-schema upgrade test passed. - Hidden-file Settings copy leads with the User-visible result and uses “Photo edit files (.xmp, .aae)”. - The parity exception snapshot was regenerated and reviewed for the newly merged API and Files UI actions. The branch contains the merged code and commits, but the listed pending gates mean this is not a green merge candidate yet.
Author
Owner

Merge round 3 report addendum

Branch job/merge-round-3, HEAD 3e5056d485e9021d2d1f708613e783b0b901b447.

  • Cleanup completed after the main report: Removed 25367 files, 17.5GiB total.
  • apps/web/build removal check: web build output removed: True.
  • git diff --check exited 0 with no output; the worktree has no modified or untracked files.
  • The 4-hour cutoff also prevented a full re-read of documentation comments across all 234 changed files. This remains an audit gap alongside the pending gates listed in the main report.

The branch is still not a green merge candidate.

## Merge round 3 report addendum Branch `job/merge-round-3`, HEAD `3e5056d485e9021d2d1f708613e783b0b901b447`. - Cleanup completed after the main report: `Removed 25367 files, 17.5GiB total`. - `apps/web/build` removal check: `web build output removed: True`. - `git diff --check` exited 0 with no output; the worktree has no modified or untracked files. - The 4-hour cutoff also prevented a full re-read of documentation comments across all 234 changed files. This remains an audit gap alongside the pending gates listed in the main report. The branch is still not a green merge candidate.
Author
Owner

Merged in merge round 3 and deployed to calternal.cloud in cc25c441b (healthy).

Merged in merge round 3 and deployed to calternal.cloud in cc25c441b (healthy).
kayg closed this issue 2026-09-30 23:22:37 +00:00
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#435
No description provided.