Verify Photos shared timeline User isolation #707

Open
opened 2026-10-02 09:20:31 +00:00 by kayg · 26 comments
Owner

Found during the single local adversarial round for calternal at head 9968f4d3f.

The isolation probe reported:

Photos shared timeline scope: expected exactly owner's opted-in Photos Items ['3c250fef-1fb0-4787-8b02-8ec379330e30'], got ['3c250fef-1fb0-4787-8b02-8ec379330e30', '8b0f1a32-b66c-45df-b770-e49be4435cad']

The probe used throwaway Users and a local server. The extra item may be visible across Users. Please verify the Photos shared-timeline scope before treating this as a confirmed leak. This task did not change the Photos plugin.

Found during the single local adversarial round for calternal at head `9968f4d3f`. The isolation probe reported: `Photos shared timeline scope: expected exactly owner's opted-in Photos Items ['3c250fef-1fb0-4787-8b02-8ec379330e30'], got ['3c250fef-1fb0-4787-8b02-8ec379330e30', '8b0f1a32-b66c-45df-b770-e49be4435cad']` The probe used throwaway Users and a local server. The extra item may be visible across Users. Please verify the Photos shared-timeline scope before treating this as a confirmed leak. This task did not change the Photos plugin.
Author
Owner

Started #707 on branch job/isolation-707, base c4a61e8cf090170f35b1bed3350d9de20c83ecd5 (origin/dev history). Reviewing Photos read authority and the asynchronous timeline snapshot comparison. No push or deployment.

Started #707 on branch `job/isolation-707`, base `c4a61e8cf090170f35b1bed3350d9de20c83ecd5` (origin/dev history). Reviewing Photos read authority and the asynchronous timeline snapshot comparison. No push or deployment.
Author
Owner

#707 findings at 9e170fb32 plus pending Rust changes:

  • attack2.py compared an early owner timeline with a later recipient timeline. Per-User Photos refreshes run asynchronously. The offline regression shows that one owner Item followed by two owner Items produces the original equality failure while both Items remain inside the Share root. Commit 9e170fb32 checks each returned stable ID against Files owner/path provenance instead. Foreign, private, hidden, prefix-sibling, unknown and incorrect viewer-path cases fail its tests. The original extra UUID cannot be attributed without the original throwaway Index; this is not proof that the historical finding was harmless.
  • Code review found that search::metadata_matches binds current authorized roots, but photo_row_for_clip did not. Pending fix shares that SQL root predicate with CLIP hits. This prevents stale derived rows from retaining scope after library roots change or a Share is revoked.
  • A stale Stack returned 403 after Share revoke, while DESIGN §54 requires the same 404 as a missing identity. Pending fix and router regression cover that response.

Incident scope (code review, not confirmed production exposure): the CLIP query omission is present at the reported 9968f4d3f and current base c4a61e8cf. It could affect semantic Photos searches with a still-present vector/Photos projection after scope changes, if another active root lets the search proceed. Potentially visible fields are the Photos search result metadata and thumbnail URL. Original bytes use Files authorization independently. Deployment/build inventory and original UUID ownership remain unknown. No push, deploy or production access performed.

#707 findings at `9e170fb32` plus pending Rust changes: - `attack2.py` compared an early owner timeline with a later recipient timeline. Per-User Photos refreshes run asynchronously. The offline regression shows that one owner Item followed by two owner Items produces the original equality failure while both Items remain inside the Share root. Commit `9e170fb32` checks each returned stable ID against Files owner/path provenance instead. Foreign, private, hidden, prefix-sibling, unknown and incorrect viewer-path cases fail its tests. The original extra UUID cannot be attributed without the original throwaway Index; this is not proof that the historical finding was harmless. - Code review found that `search::metadata_matches` binds current authorized roots, but `photo_row_for_clip` did not. Pending fix shares that SQL root predicate with CLIP hits. This prevents stale derived rows from retaining scope after library roots change or a Share is revoked. - A stale Stack returned 403 after Share revoke, while DESIGN §54 requires the same 404 as a missing identity. Pending fix and router regression cover that response. Incident scope (code review, not confirmed production exposure): the CLIP query omission is present at the reported `9968f4d3f` and current base `c4a61e8cf`. It could affect semantic Photos searches with a still-present vector/Photos projection after scope changes, if another active root lets the search proceed. Potentially visible fields are the Photos search result metadata and thumbnail URL. Original bytes use Files authorization independently. Deployment/build inventory and original UUID ownership remain unknown. No push, deploy or production access performed.
Author
Owner

Resumed #707 at bb967f645758224e8989ed18c918084c963ea4d5 after the build-host resize. The three committed provenance fixes are preserved. Photos crate tests pass (47 passed, 3 ignored). Commit 5ea7d4602 adds missing Notes App Password fixtures; all 14 existing Admin classification tests pass without expectation changes. Continuing Photos query scoping and local authorization checks.

Resumed #707 at `bb967f645758224e8989ed18c918084c963ea4d5` after the build-host resize. The three committed provenance fixes are preserved. Photos crate tests pass (47 passed, 3 ignored). Commit `5ea7d4602` adds missing Notes App Password fixtures; all 14 existing Admin classification tests pass without expectation changes. Continuing Photos query scoping and local authorization checks.
Author
Owner

Committed Photos fixes at 8b5224c25:

  • Metadata and CLIP result lookup now share one bound owner/path predicate. Regression checks distinguish exact roots, descendants, prefix siblings, foreign owners, empty authority, and a stale recipient projection. Router checks cover timeline, buckets, days, search, Items and Stacks before and after revoke. Full-Home search resolves opted-in incoming Shares live; narrowed App Password roots do not gain them.
  • Stacks now return 404 after revoke, like a missing identity (§54).
  • Further review found that the CLIP vector store already selects per-User files and auth-validated Share capabilities (clip_store.rs::search, VectorReadCapability). The missing Photos root predicate is therefore a library-scope gap/defence-in-depth fix, not proof of a cross-User leak. A same-User Item outside the current opted-in root could pass the old final lookup while derived rows remained. Earlier incident wording about revoked Shares is conditional; upstream live authority also applies.
  • The omission exists at 9968f4d3f and base c4a61e8cf. No production build inventory or original throwaway Index is available; historical UUID ownership and actual production exposure remain unconfirmed. Search-result metadata/thumbnail references are the affected lookup surface; original bytes have independent Files authority.

Fetched origin and merged origin/dev once: already up to date. Final per-crate gates and real local authorization matrices are in progress. No push or deployment.

Committed Photos fixes at `8b5224c25`: - Metadata and CLIP result lookup now share one bound owner/path predicate. Regression checks distinguish exact roots, descendants, prefix siblings, foreign owners, empty authority, and a stale recipient projection. Router checks cover timeline, buckets, days, search, Items and Stacks before and after revoke. Full-Home search resolves opted-in incoming Shares live; narrowed App Password roots do not gain them. - Stacks now return 404 after revoke, like a missing identity (§54). - Further review found that the CLIP vector store already selects per-User files and auth-validated Share capabilities (`clip_store.rs::search`, `VectorReadCapability`). The missing Photos root predicate is therefore a library-scope gap/defence-in-depth fix, not proof of a cross-User leak. A same-User Item outside the current opted-in root could pass the old final lookup while derived rows remained. Earlier incident wording about revoked Shares is conditional; upstream live authority also applies. - The omission exists at `9968f4d3f` and base `c4a61e8cf`. No production build inventory or original throwaway Index is available; historical UUID ownership and actual production exposure remain unconfirmed. Search-result metadata/thumbnail references are the affected lookup surface; original bytes have independent Files authority. Fetched origin and merged `origin/dev` once: already up to date. Final per-crate gates and real local authorization matrices are in progress. No push or deployment.
Author
Owner

Progress at e48d99e4f: final Photos and server Clippy gates pass. Photos test output:

test result: ok. 47 passed; 0 failed; 3 ignored; 0 measured; 0 filtered out; finished in 18.13s

Server Clippy output:

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 15m 39s

The live contract now covers a recipient with their own different Photo, opted-in Share lists/details/Stacks, Calendar Items and Activity decks, Files stable ID/original/thumbnail reads, Public gallery identities and revoke, and read-only MCP access/revoke. The two-User and Admin offline guards pass (335 API operations, 945 generated tools, 39 Admin operations). The server unit gate/build and real local matrices remain in progress.

Progress at `e48d99e4f`: final Photos and server Clippy gates pass. Photos test output: ``` test result: ok. 47 passed; 0 failed; 3 ignored; 0 measured; 0 filtered out; finished in 18.13s ``` Server Clippy output: ``` Finished `dev` profile [unoptimized + debuginfo] target(s) in 15m 39s ``` The live contract now covers a recipient with their own different Photo, opted-in Share lists/details/Stacks, Calendar Items and Activity decks, Files stable ID/original/thumbnail reads, Public gallery identities and revoke, and read-only MCP access/revoke. The two-User and Admin offline guards pass (335 API operations, 945 generated tools, 39 Admin operations). The server unit gate/build and real local matrices remain in progress.
Author
Owner

Incident note update (#707):

The final CLIP-to-Photos lookup was introduced without a current Photos library-root predicate in a778dba374196528485b2abba3f623ebf230461d (2026-09-25). The omission remains at the reported 9968f4d3f and base c4a61e8cf; this branch fixes it in 8b5224c25. A User whose vector authority already permits an Item could receive its search metadata while its old Photos projection remains after a library-root change. Current code also bounds vectors with per-User files and auth-validated Share roots, so this finding does not establish access by an ungranted User. Files checks originals independently. Actual deployed build versions and the original extra Item owner remain unknown.

Read-only inspection of the retained taskday-655 worktree and job report found no original throwaway Index. Its stored job log does not contain the original extra UUID. The probe snapshot comparison is demonstrably unsound, but that alone cannot attribute the historical UUID.

Head is 87b5f20458f6b75dc4753964f726f1cdf47f664f. All code is committed; Photos gates and server Clippy pass. The server test executable is in the shared host linker, then the server/CLI build and one local matrix round remain. No push or deployment.

Incident note update (#707): The final CLIP-to-Photos lookup was introduced without a current Photos library-root predicate in `a778dba374196528485b2abba3f623ebf230461d` (2026-09-25). The omission remains at the reported `9968f4d3f` and base `c4a61e8cf`; this branch fixes it in `8b5224c25`. A User whose vector authority already permits an Item could receive its search metadata while its old Photos projection remains after a library-root change. Current code also bounds vectors with per-User files and auth-validated Share roots, so this finding does not establish access by an ungranted User. Files checks originals independently. Actual deployed build versions and the original extra Item owner remain unknown. Read-only inspection of the retained taskday-655 worktree and job report found no original throwaway Index. Its stored job log does not contain the original extra UUID. The probe snapshot comparison is demonstrably unsound, but that alone cannot attribute the historical UUID. Head is `87b5f20458f6b75dc4753964f726f1cdf47f664f`. All code is committed; Photos gates and server Clippy pass. The server test executable is in the shared host linker, then the server/CLI build and one local matrix round remain. No push or deployment.
Author
Owner

Head 87b5f20458f6b75dc4753964f726f1cdf47f664f. All required per-crate Rust gates passed: Photos 47 tests and server 107 tests; both Clippy checks and fmt succeeded. Live two-User/Admin matrices have not run yet. The subsequent standalone server executable build is still compiling on the shared host, with CLI queued behind its target lock. Server test compilation took 60m 00s; the executable build is still pending after about 20 minutes. I will keep the live checks pending in the final report if the three-hour cutoff arrives first. No new exposure claim is supported by this build delay.

Head `87b5f20458f6b75dc4753964f726f1cdf47f664f`. All required per-crate Rust gates passed: Photos 47 tests and server 107 tests; both Clippy checks and fmt succeeded. Live two-User/Admin matrices have not run yet. The subsequent standalone server executable build is still compiling on the shared host, with CLI queued behind its target lock. Server test compilation took `60m 00s`; the executable build is still pending after about 20 minutes. I will keep the live checks pending in the final report if the three-hour cutoff arrives first. No new exposure claim is supported by this build delay.
Author
Owner

Photos isolation report (#707)

Not ready for merge: live two-User and Admin verification is pending.

Branch job/isolation-707, head 70d019131e8d4b7310c8a1fd83712d40dcdf2d36. No push or deploy. The single required origin/dev merge reported Already up to date.

Built and findings

The Photos probe now checks Files owner/path provenance, rather than comparing unequal asynchronous Photos snapshots. A deterministic offline test shows that refresh can add a second valid owner Item between those snapshots. This demonstrates a faulty oracle, not the provenance of historical Item 8b0f1a32-b66c-45df-b770-e49be4435cad.

CLIP result hydration now binds the same current owner/root predicate as metadata search. Tests reject foreign owners, prefix siblings, empty roots and stale recipient rows. Full-Home Photos/API/MCP search includes live opted-in incoming Shares; narrow authority gains no roots. A revoked Stack now returns the same 404 as a missing Stack.

Added unexecuted real-server matrix cases for recipient-owned media outside the opted-in Share, Photos timeline/buckets/day/search/Item/Stack, Calendar day/range decks, Files identity/original/thumbnail, MCP Item/search and anonymous Public link gallery/download, including revoke. The Admin fixture now includes existing Notes App Password scopes without changing old test expectations.

Incident note

The unscoped final CLIP lookup was introduced by a778dba374196528485b2abba3f623ebf230461d (2026-09-25), and exists at the reported 9968f4d3f and job base c4a61e8cf. The query fix is 8b5224c25. Upstream vector reads already use per-User data and validated Share capabilities. The demonstrated gap is stale metadata outside current Photos library scope for a User who has vector authority, not a proven ungranted cross-User exposure. Files original-byte reads have separate authorization. Deployment history and the historical extra Item's owner/path cannot be established from retained local evidence; the original Index was not retained. Keep #707 open until live evidence and historical scope are reviewed.

Files

bench/photos-scope-707.py
crates/plugins/photos/src/routes.rs
crates/plugins/photos/src/search.rs
tests/adversarial/attack2.py
tests/adversarial/authz_matrix.py
tests/adversarial/photos_scope_contracts.py
tests/adversarial/test_photos_scope_contracts.py
tests/adversarial/xuser_matrix.py

Gate output (verbatim)

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

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

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

cargo test -p calternal-plugin-photos:

    Finished `test` profile [unoptimized + debuginfo] target(s) in 6m 54s
test result: ok. 47 passed; 0 failed; 3 ignored; 0 measured; 0 filtered out; finished in 18.13s
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 15m 39s

cargo test -p calternal-server:

    Finished `test` profile [unoptimized + debuginfo] target(s) in 60m 00s
test result: ok. 107 passed; 0 failed; 3 ignored; 0 measured; 0 filtered out; finished in 34.97s

Offline Python gates: 6 Photos provenance, 14 Admin classification and 7 Cross-User classification tests pass. 335 operations and 945 generated entry points classified; 39 Admin operations agree with Rust guards. No web source changed; production web build succeeded.

Performance (local)

100k-Item query-only debug profile on the shared host: serial p50/p95 0.477/5.638 ms (200 lookups); eight-reader burst p50/p95 2.210/7.404 ms (512 lookups). Whole-process CPU including fixture 10.372 s; maximum RSS 53,940 KiB. Load average 44.46/45.10/31.32. Baseline HTTP Photos timeline is 1.4/3.9 ms at 369ab6a2f9fc673e3564b94857fbecfeb04df404; it measures a different path, so no comparable regression claim. JSON evidence: artifacts/photos-bench.json. No HTTP or model-inference performance measurement.

Decisions

Use Files stable identity, owner and component-bounded path as the probe oracle. Reuse the existing query builder predicate for the final CLIP lookup. Treat an exact full-Home session as authority for already-validated opted-in incoming Shares, consistent with global Search; narrowed requests remain clipped. Use a prebuilt Rust query profile for the private hot path without downloading model weights. No new product design.

UX gaps closed

Opted-in incoming Share results appear in metadata/API/MCP search. Revoked Stack reads use missing-Item 404 semantics.

Known gaps / UX gaps left

Historical extra Item provenance and deployed build inventory are unknown. Album views are not implemented (DESIGN §33). CLIP model-inference test was not run: no model assets configured; direct SQL authorization regressions passed. No UI changes or screenshot review required. Live two-User and Admin matrices were not run. The standalone executable build was still compiling after about 36 minutes; CLI was queued on its target lock. Both unfinished builds were stopped to allow cleanup within the three-hour limit. The successful Rust gates are separate from these stopped executable builds. No real-server authorization result is claimed.

Remaining verification

Build the server and CLI. Run one real-server two-User round with MCP enabled and the Admin matrix. The added Photos lifecycle cases run before CLI-dependent checks. Establish the historical extra Item owner/path and deployment range if the original Index or equivalent provenance can be recovered. Keep #707 open.

Offline gate output (verbatim)

artifacts/gate-photos-probe.log:

......
----------------------------------------------------------------------
Ran 6 tests in 0.009s

OK

artifacts/gate-admin-contract.log:

..............
----------------------------------------------------------------------
Ran 14 tests in 1.554s

OK

artifacts/gate-xuser-contract.log:

.......
----------------------------------------------------------------------
Ran 7 tests in 0.349s

OK

artifacts/gate-xuser-offline.log:

Cross-User classification gate: 335 operations classified
Generated entry point classification: 945 tools classified

artifacts/gate-admin-offline.log:

Admin coverage: 39 reviewed operations; contract and Rust guards agree

Cleanup

cargo clean completed successfully:

     Removed 16439 files, 8.1GiB total

Removed apps/web/build and apps/web/.svelte-kit. git status --short is empty. Review artifacts remain untracked in artifacts/.

# Photos isolation report (#707) **Not ready for merge: live two-User and Admin verification is pending.** Branch `job/isolation-707`, head `70d019131e8d4b7310c8a1fd83712d40dcdf2d36`. No push or deploy. The single required origin/dev merge reported `Already up to date.` ## Built and findings The Photos probe now checks Files owner/path provenance, rather than comparing unequal asynchronous Photos snapshots. A deterministic offline test shows that refresh can add a second valid owner Item between those snapshots. This demonstrates a faulty oracle, not the provenance of historical Item `8b0f1a32-b66c-45df-b770-e49be4435cad`. CLIP result hydration now binds the same current owner/root predicate as metadata search. Tests reject foreign owners, prefix siblings, empty roots and stale recipient rows. Full-Home Photos/API/MCP search includes live opted-in incoming Shares; narrow authority gains no roots. A revoked Stack now returns the same 404 as a missing Stack. Added unexecuted real-server matrix cases for recipient-owned media outside the opted-in Share, Photos timeline/buckets/day/search/Item/Stack, Calendar day/range decks, Files identity/original/thumbnail, MCP Item/search and anonymous Public link gallery/download, including revoke. The Admin fixture now includes existing Notes App Password scopes without changing old test expectations. ## Incident note The unscoped final CLIP lookup was introduced by `a778dba374196528485b2abba3f623ebf230461d` (2026-09-25), and exists at the reported `9968f4d3f` and job base `c4a61e8cf`. The query fix is `8b5224c25`. Upstream vector reads already use per-User data and validated Share capabilities. The demonstrated gap is stale metadata outside current Photos library scope for a User who has vector authority, not a proven ungranted cross-User exposure. Files original-byte reads have separate authorization. Deployment history and the historical extra Item's owner/path cannot be established from retained local evidence; the original Index was not retained. Keep #707 open until live evidence and historical scope are reviewed. ## Files ``` bench/photos-scope-707.py crates/plugins/photos/src/routes.rs crates/plugins/photos/src/search.rs tests/adversarial/attack2.py tests/adversarial/authz_matrix.py tests/adversarial/photos_scope_contracts.py tests/adversarial/test_photos_scope_contracts.py tests/adversarial/xuser_matrix.py ``` ## Gate output (verbatim) `cargo fmt --check`: exit 0, no output. `cargo clippy -p calternal-plugin-photos --all-targets -- -D warnings`: ``` Finished `dev` profile [unoptimized + debuginfo] target(s) in 28.96s ``` `cargo test -p calternal-plugin-photos`: ``` Finished `test` profile [unoptimized + debuginfo] target(s) in 6m 54s test result: ok. 47 passed; 0 failed; 3 ignored; 0 measured; 0 filtered out; finished in 18.13s 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 15m 39s ``` `cargo test -p calternal-server`: ``` Finished `test` profile [unoptimized + debuginfo] target(s) in 60m 00s test result: ok. 107 passed; 0 failed; 3 ignored; 0 measured; 0 filtered out; finished in 34.97s ``` Offline Python gates: 6 Photos provenance, 14 Admin classification and 7 Cross-User classification tests pass. 335 operations and 945 generated entry points classified; 39 Admin operations agree with Rust guards. No web source changed; production web build succeeded. ## Performance (local) 100k-Item query-only debug profile on the shared host: serial p50/p95 0.477/5.638 ms (200 lookups); eight-reader burst p50/p95 2.210/7.404 ms (512 lookups). Whole-process CPU including fixture 10.372 s; maximum RSS 53,940 KiB. Load average 44.46/45.10/31.32. Baseline HTTP Photos timeline is 1.4/3.9 ms at `369ab6a2f9fc673e3564b94857fbecfeb04df404`; it measures a different path, so no comparable regression claim. JSON evidence: `artifacts/photos-bench.json`. No HTTP or model-inference performance measurement. ## Decisions Use Files stable identity, owner and component-bounded path as the probe oracle. Reuse the existing query builder predicate for the final CLIP lookup. Treat an exact full-Home session as authority for already-validated opted-in incoming Shares, consistent with global Search; narrowed requests remain clipped. Use a prebuilt Rust query profile for the private hot path without downloading model weights. No new product design. ## UX gaps closed Opted-in incoming Share results appear in metadata/API/MCP search. Revoked Stack reads use missing-Item 404 semantics. ## Known gaps / UX gaps left Historical extra Item provenance and deployed build inventory are unknown. Album views are not implemented (DESIGN §33). CLIP model-inference test was not run: no model assets configured; direct SQL authorization regressions passed. No UI changes or screenshot review required. Live two-User and Admin matrices were not run. The standalone executable build was still compiling after about 36 minutes; CLI was queued on its target lock. Both unfinished builds were stopped to allow cleanup within the three-hour limit. The successful Rust gates are separate from these stopped executable builds. No real-server authorization result is claimed. ## Remaining verification Build the server and CLI. Run one real-server two-User round with MCP enabled and the Admin matrix. The added Photos lifecycle cases run before CLI-dependent checks. Establish the historical extra Item owner/path and deployment range if the original Index or equivalent provenance can be recovered. Keep #707 open. ## Offline gate output (verbatim) `artifacts/gate-photos-probe.log`: ``` ...... ---------------------------------------------------------------------- Ran 6 tests in 0.009s OK ``` `artifacts/gate-admin-contract.log`: ``` .............. ---------------------------------------------------------------------- Ran 14 tests in 1.554s OK ``` `artifacts/gate-xuser-contract.log`: ``` ....... ---------------------------------------------------------------------- Ran 7 tests in 0.349s OK ``` `artifacts/gate-xuser-offline.log`: ``` Cross-User classification gate: 335 operations classified Generated entry point classification: 945 tools classified ``` `artifacts/gate-admin-offline.log`: ``` Admin coverage: 39 reviewed operations; contract and Rust guards agree ``` ## Cleanup `cargo clean` completed successfully: ``` Removed 16439 files, 8.1GiB total ``` Removed `apps/web/build` and `apps/web/.svelte-kit`. `git status --short` is empty. Review artifacts remain untracked in `artifacts/`.
Author
Owner

Round 2 started on job/isolation-707, base 70d019131e8d4b7310c8a1fd83712d40dcdf2d36. Fetched origin and merged origin/dev: Already up to date. No production access. Live identity-replay probes cannot be executed in this session; offline defensive checks and code-history review will be reported separately, with the merge gate pending.

Round 2 started on `job/isolation-707`, base `70d019131e8d4b7310c8a1fd83712d40dcdf2d36`. Fetched origin and merged `origin/dev`: `Already up to date.` No production access. Live identity-replay probes cannot be executed in this session; offline defensive checks and code-history review will be reported separately, with the merge gate pending.
Author
Owner

Photos isolation Round 2 (#707)

Not ready for merge. The required live matrices remain pending.

Branch job/isolation-707; base 70d019131e8d4b7310c8a1fd83712d40dcdf2d36; head fad457b141034cf130e7ec90996f9589964ca49a.
Fetched origin and merged origin/dev once. Output: Already up to date.
No production access, push, deploy, issue close or merge into dev.

Built and files

Commit fad457b14 prevents an empty run from counting as a successful live Photos check. tests/adversarial/photos_scope_contracts.py is an imported provenance helper. Previously, direct execution returned exit 0 without checking a response. It now returns nonzero and states that no live checks ran. Its import behavior is unchanged.

Files: tests/adversarial/photos_scope_contracts.py and tests/adversarial/test_photos_scope_contracts.py. The new subprocess test failed with AssertionError: 0 == 0 before the guard and passes after it. Existing test expectations were not changed. Re-read the touched file comments before this report.

Historical Item evidence

At 9968f4d3f, attack2.py captures an owner timeline, then creates a Share, opts the recipient into the shared Photos root, and captures the recipient timeline. It compares the two Item ID sets for equality. Per-User refreshes can add a valid owner Item between these two reads. The retained offline regression demonstrates that path. Commit 9e170fb32 replaces the snapshot comparison with Files owner/path provenance. That closes the false-positive reporting path; it does not prevent valid Items from being indexed later.

The historical report came from /api/v1/photos/timeline. The timeline handler resolves active roots, reads day buckets and tiles, and does not call search_photos or photo_row_for_clip. Therefore the CLIP fix cannot establish why the historical extra Item appeared.

Separately, a778dba374196528485b2abba3f623ebf230461d introduced the final CLIP result query without a current Photos library-root predicate. Commit 8b5224c25 adds that predicate. This closes a search-library scope gap for stale derived rows when upstream vector authority already permits the Item. Current upstream vector reads use per-User data and validated Share capabilities. This does not establish ungranted cross-User access or production exposure.

The historical extra Item's owner/path and deployed build versions remain unknown. No original Index was inspected or recovered in this round. No production inspection was attempted.

Gates (fresh output, verbatim)

cargo fmt --check: exit 0, no output.
git diff --check: exit 0, no output.
Rust Clippy/test and web gates were not rerun: this round changed only Python test infrastructure, with no Rust/web changes from the origin/dev merge. Round 1 Rust outputs are historical, not fresh Round 2 results.

Photos offline provenance:

.......
----------------------------------------------------------------------
Ran 7 tests in 0.072s

OK

Admin offline classification:

..............
----------------------------------------------------------------------
Ran 14 tests in 4.374s

OK

Cross-User offline classification:

.......
----------------------------------------------------------------------
Ran 7 tests in 0.414s

OK

Broader offline Python discovery also ran. It failed when the unrelated test_search_result_matching module imported search_chaos.py, which requires live fixture environment variables and files. No fixtures were supplied. The failure was not fixed outside this job's files. Full output is in artifacts/round2-python-gates.log; summary:

KeyError: 'ADVERSARIAL_WORK_DIR'
Ran 49 tests in 14.266s
FAILED (errors=1)

The broader run also emitted an unclosed SQLite connection ResourceWarning. Focused gate outputs above have no warnings.

Known gaps

No local server or CLIP index was built in Round 2. Neither live matrix ran, including MCP and Public link paths. Live exploitation/foreign-identity replay probes cannot be executed in this session; the work was limited to offline defensive tests and code-history review. There are no live summary lines to quote, and no green live gate is claimed. Issue #707 remains open and blocks the merge.

Decisions

Treat the Photos scope module as import-only and fail direct execution, so an empty command cannot become live evidence. No product design or dependencies changed. No performance profile was added or measured: this round changes no user-facing hot path.

UX gaps closed / left

No UI changes in Round 2. Live confirmation of Share/revoke behavior, MCP and Public links remains pending.

Cleanup

cargo clean output:

     Removed 1 file, 356B total

No web build output was generated. Removed Python bytecode generated by test discovery under crates/calternal-sync. Review artifacts remain untracked; no screenshots or other artifacts were committed.

# Photos isolation Round 2 (#707) **Not ready for merge. The required live matrices remain pending.** Branch `job/isolation-707`; base `70d019131e8d4b7310c8a1fd83712d40dcdf2d36`; head `fad457b141034cf130e7ec90996f9589964ca49a`. Fetched origin and merged `origin/dev` once. Output: `Already up to date.` No production access, push, deploy, issue close or merge into dev. ## Built and files Commit `fad457b14` prevents an empty run from counting as a successful live Photos check. `tests/adversarial/photos_scope_contracts.py` is an imported provenance helper. Previously, direct execution returned exit 0 without checking a response. It now returns nonzero and states that no live checks ran. Its import behavior is unchanged. Files: `tests/adversarial/photos_scope_contracts.py` and `tests/adversarial/test_photos_scope_contracts.py`. The new subprocess test failed with `AssertionError: 0 == 0` before the guard and passes after it. Existing test expectations were not changed. Re-read the touched file comments before this report. ## Historical Item evidence At `9968f4d3f`, `attack2.py` captures an owner timeline, then creates a Share, opts the recipient into the shared Photos root, and captures the recipient timeline. It compares the two Item ID sets for equality. Per-User refreshes can add a valid owner Item between these two reads. The retained offline regression demonstrates that path. Commit `9e170fb32` replaces the snapshot comparison with Files owner/path provenance. That closes the false-positive reporting path; it does not prevent valid Items from being indexed later. The historical report came from `/api/v1/photos/timeline`. The timeline handler resolves active roots, reads day buckets and tiles, and does not call `search_photos` or `photo_row_for_clip`. Therefore the CLIP fix cannot establish why the historical extra Item appeared. Separately, `a778dba374196528485b2abba3f623ebf230461d` introduced the final CLIP result query without a current Photos library-root predicate. Commit `8b5224c25` adds that predicate. This closes a search-library scope gap for stale derived rows when upstream vector authority already permits the Item. Current upstream vector reads use per-User data and validated Share capabilities. This does not establish ungranted cross-User access or production exposure. **The historical extra Item's owner/path and deployed build versions remain unknown.** No original Index was inspected or recovered in this round. No production inspection was attempted. ## Gates (fresh output, verbatim) `cargo fmt --check`: exit 0, no output. `git diff --check`: exit 0, no output. Rust Clippy/test and web gates were not rerun: this round changed only Python test infrastructure, with no Rust/web changes from the origin/dev merge. Round 1 Rust outputs are historical, not fresh Round 2 results. Photos offline provenance: ``` ....... ---------------------------------------------------------------------- Ran 7 tests in 0.072s OK ``` Admin offline classification: ``` .............. ---------------------------------------------------------------------- Ran 14 tests in 4.374s OK ``` Cross-User offline classification: ``` ....... ---------------------------------------------------------------------- Ran 7 tests in 0.414s OK ``` Broader offline Python discovery also ran. It failed when the unrelated `test_search_result_matching` module imported `search_chaos.py`, which requires live fixture environment variables and files. No fixtures were supplied. The failure was not fixed outside this job's files. Full output is in `artifacts/round2-python-gates.log`; summary: ``` KeyError: 'ADVERSARIAL_WORK_DIR' Ran 49 tests in 14.266s FAILED (errors=1) ``` The broader run also emitted an unclosed SQLite connection ResourceWarning. Focused gate outputs above have no warnings. ## Known gaps No local server or CLIP index was built in Round 2. Neither live matrix ran, including MCP and Public link paths. Live exploitation/foreign-identity replay probes cannot be executed in this session; the work was limited to offline defensive tests and code-history review. There are no live summary lines to quote, and no green live gate is claimed. Issue #707 remains open and blocks the merge. ## Decisions Treat the Photos scope module as import-only and fail direct execution, so an empty command cannot become live evidence. No product design or dependencies changed. No performance profile was added or measured: this round changes no user-facing hot path. ## UX gaps closed / left No UI changes in Round 2. Live confirmation of Share/revoke behavior, MCP and Public links remains pending. ## Cleanup `cargo clean` output: ``` Removed 1 file, 356B total ``` No web build output was generated. Removed Python bytecode generated by test discovery under `crates/calternal-sync`. Review artifacts remain untracked; no screenshots or other artifacts were committed.
Author
Owner

Starting Round 3 on branch job/isolation-707, based on c4a61e8cf090170f35b1bed3350d9de20c83ecd5 (origin/dev). I read artifacts/round2-final-report.md; the live matrices were not run because ADVERSARIAL_WORK_DIR was missing. I will merge origin/dev once, then run the three requested matrices against a fresh local server and report their exact summaries.

Starting Round 3 on branch `job/isolation-707`, based on `c4a61e8cf090170f35b1bed3350d9de20c83ecd5` (`origin/dev`). I read `artifacts/round2-final-report.md`; the live matrices were not run because `ADVERSARIAL_WORK_DIR` was missing. I will merge `origin/dev` once, then run the three requested matrices against a fresh local server and report their exact summaries.
Author
Owner

The local media sandbox preparation stopped before server startup: tests/adversarial/test-media-sandbox.sh reported too many local threads for the bounded media sandbox test: 6552. prepare-media-runtime.sh caps the outer process limit at 4096. I will not weaken or bypass that sandbox bound; the API and Photos metadata scope checks continue, and I will report whether CLIP vectors finish under this host limit.

The local media sandbox preparation stopped before server startup: `tests/adversarial/test-media-sandbox.sh` reported `too many local threads for the bounded media sandbox test: 6552`. `prepare-media-runtime.sh` caps the outer process limit at 4096. I will not weaken or bypass that sandbox bound; the API and Photos metadata scope checks continue, and I will report whether CLIP vectors finish under this host limit.
Author
Owner

origin/dev advanced during this run with 440e19dce, which raises the local-only media sandbox setup ceiling while preserving the decoder's 64-process limit. I merged that commit into job/isolation-707 before the live run. I am rerunning the bounded media sandbox preparation and will check CLIP indexing on the same throwaway server.

`origin/dev` advanced during this run with `440e19dce`, which raises the local-only media sandbox setup ceiling while preserving the decoder's 64-process limit. I merged that commit into `job/isolation-707` before the live run. I am rerunning the bounded media sandbox preparation and will check CLIP indexing on the same throwaway server.
Author
Owner

The merged local-only ceiling fix passed tests/adversarial/test-media-sandbox.sh on this host (exit 0). It prepared local-nproc-limit=7352 and verified a real bounded WebP thumbnail; the sandbox still caps the decoder at 64 processes. The server build is resumed.

The merged local-only ceiling fix passed `tests/adversarial/test-media-sandbox.sh` on this host (exit 0). It prepared `local-nproc-limit=7352` and verified a real bounded WebP thumbnail; the sandbox still caps the decoder at 64 processes. The server build is resumed.
Author
Owner

Independent read-only review started on job/rev2-isolation-707. Base: 440e19dce2. Target: 0bb018a98. I will check the code and contracts without builds, tests, servers or changes in the author worktree.

Independent read-only review started on job/rev2-isolation-707. Base: 440e19dce23040ac8ebaae88f0469b6535b1afcb. Target: 0bb018a98. I will check the code and contracts without builds, tests, servers or changes in the author worktree.
Author
Owner

Independent read-only review complete. Target: 0bb018a98fa5be97b3fcf9e2a5d62bc1a51b9abf. Review head: 6c8bcf26a9b9e809d0689dbe97e5f94e4b052194 on job/rev2-isolation-707.

  • P1 — #896: crates/plugins/photos/src/routes.rs:1062. When all authoritative Files members disappear, the stale stack query has no rows. The authorization loop is skipped and lines 1082–1087 return 200 with group metadata after revoke. Fix: validate the current display identity and authorized root, and return 404 for a stack with no current authorized members. Add a stale-projection regression with removed Files rows and a revoked Share. DESIGN §54 requires the missing-ID response.
  • P2 — #895: crates/plugins/photos/src/search.rs:622, crates/plugins/photos/src/routes.rs:518. Incoming Shares enter metadata roots, but vector authority still comes from the original recipient-only request roots. Shared semantic-only queries cannot reach owner vectors. Fix: create vector authority from live request-authorized Photos roots through the existing capability boundary. Test semantic-only search, revoke and narrow roots. DESIGN §§28 and 54 require shared Photos search.

Both gaps existed before this branch. The new checks do not cover them. Existing issue searches found no specific open issue for either fix; I filed #895 and #896.

Built: review documents only, review-isolation-707.md and audit-findings.md, in two atomic commits. No product code changed. The author worktree was read only. I did not use the author's report.

The Files provenance replacement is sound for ownership checks and retains the known shared fixture assertion. It changes an existing test expectation and needs the orchestrator's review. The current-root CLIP row predicate correctly binds owner, path, item identity and hash. The positive shared search tests use metadata; they do not prove semantic candidate selection. The stack comment promises the revoke guarantee without covering the empty-member case.

Verification: no builds, tests, servers or browsers ran, as required by the LIGHT brief. Rust/web gate output: none. git diff --check exited 0 with no output. One fetch and merge of origin/dev returned verbatim:

Already up to date.

Known gaps: #895 and #896; runtime and performance results remain unverified. DESIGN §58 is absent from the target and review base. Decisions: no product decisions; I used the available DESIGN §§6, 28 and 48 for the performance read. UX gaps closed: none. UX gaps left: shared semantic search (#895).

For the merge round, after the fixes:

cargo fmt --check
cargo clippy -p calternal-plugin-photos --all-targets -- -D warnings
cargo test -p calternal-plugin-photos
python3 -m unittest discover -s tests/adversarial -p test_photos_scope_contracts.py
XUSER_MATRIX_WITH_MCP=1 ADVERSARIAL_DERIVED_ONLY=1 python3 tests/adversarial/xuser_matrix.py

Use the required Cargo environment and normal adversarial setup/server. Prove semantic-only shared results before revoke, no results after revoke, and 404 for empty stale stacks. The committed review gives the complete evidence and limits.

Independent read-only review complete. Target: `0bb018a98fa5be97b3fcf9e2a5d62bc1a51b9abf`. Review head: `6c8bcf26a9b9e809d0689dbe97e5f94e4b052194` on `job/rev2-isolation-707`. - **P1 — #896:** `crates/plugins/photos/src/routes.rs:1062`. When all authoritative Files members disappear, the stale stack query has no rows. The authorization loop is skipped and lines 1082–1087 return 200 with group metadata after revoke. Fix: validate the current display identity and authorized root, and return 404 for a stack with no current authorized members. Add a stale-projection regression with removed Files rows and a revoked Share. DESIGN §54 requires the missing-ID response. - **P2 — #895:** `crates/plugins/photos/src/search.rs:622`, `crates/plugins/photos/src/routes.rs:518`. Incoming Shares enter metadata roots, but vector authority still comes from the original recipient-only request roots. Shared semantic-only queries cannot reach owner vectors. Fix: create vector authority from live request-authorized Photos roots through the existing capability boundary. Test semantic-only search, revoke and narrow roots. DESIGN §§28 and 54 require shared Photos search. Both gaps existed before this branch. The new checks do not cover them. Existing issue searches found no specific open issue for either fix; I filed [#895](https://git.kayg.org/kayg/calternal/issues/895) and [#896](https://git.kayg.org/kayg/calternal/issues/896). Built: review documents only, `review-isolation-707.md` and `audit-findings.md`, in two atomic commits. No product code changed. The author worktree was read only. I did not use the author's report. The Files provenance replacement is sound for ownership checks and retains the known shared fixture assertion. It changes an existing test expectation and needs the orchestrator's review. The current-root CLIP row predicate correctly binds owner, path, item identity and hash. The positive shared search tests use metadata; they do not prove semantic candidate selection. The stack comment promises the revoke guarantee without covering the empty-member case. Verification: no builds, tests, servers or browsers ran, as required by the LIGHT brief. Rust/web gate output: none. `git diff --check` exited 0 with no output. One fetch and merge of `origin/dev` returned verbatim: ```text Already up to date. ``` Known gaps: #895 and #896; runtime and performance results remain unverified. DESIGN §58 is absent from the target and review base. Decisions: no product decisions; I used the available DESIGN §§6, 28 and 48 for the performance read. UX gaps closed: none. UX gaps left: shared semantic search (#895). For the merge round, after the fixes: ```sh cargo fmt --check cargo clippy -p calternal-plugin-photos --all-targets -- -D warnings cargo test -p calternal-plugin-photos python3 -m unittest discover -s tests/adversarial -p test_photos_scope_contracts.py XUSER_MATRIX_WITH_MCP=1 ADVERSARIAL_DERIVED_ONLY=1 python3 tests/adversarial/xuser_matrix.py ``` Use the required Cargo environment and normal adversarial setup/server. Prove semantic-only shared results before revoke, no results after revoke, and 404 for empty stale stacks. The committed review gives the complete evidence and limits.
Author
Owner

#707 Round 3 report — INCOMPLETE

Branch and files

  • Branch: job/isolation-707
  • Head: 0bb018a98fa5be97b3fcf9e2a5d62bc1a51b9abf
  • I merged origin/dev commit 440e19dce before the live run. It changes deploy/media-sandbox and tests/adversarial/prepare-media-runtime.sh to allow bounded local sandbox setup on a busy host. No issue-specific source change was made.
  • Those are the only tracked files in the merge commit.

Completed checks

The merged sandbox change fixed the local host limit. The bounded media test passed and generated a real WebP thumbnail. The inner decoder limit remains 64 processes.

TMPDIR="$PWD/target/tmp" tests/adversarial/test-media-sandbox.sh "$PWD/target/e2e-media-runtime"
(no stdout)
exit_code=0

The offline Photos scope tests and classifications had passed before the live run:

Ran 7 tests in 1.742s

OK
Cross-User classification gate: 335 operations classified
Generated entry point classification: 945 tools classified
Admin coverage: 39 reviewed operations; contract and Rust guards agree

Live checks not run

The local server build did not finish before the four-hour cap. I stopped it while it compiled mail/MCP dependencies. No server binary was produced, so I did not start the server, create accounts, upload the 720 JPEGs, or run xuser_matrix.py, authz_matrix.py, or the live Photos scope helper. There are no live summary lines to quote, and no live access-control result is claimed.

The build command was:

cargo build -p calternal-server -p calternal-cli -p calternal-sync

Its last output before the stop was:

Compiling arraydeque v0.5.1
Compiling ammonia v4.2.0
Compiling async-imap v0.11.3 (/home/kayg/Developer/calternal-wt/isolation-707/crates/plugins/mail/vendor/async-imap)
exit_code=130 (stopped at the four-hour cap)

No Rust crate changed, so I did not run cargo fmt --check, clippy, or crate tests. cargo clean completed:

Removed 7177 files, 3.5GiB total

I removed the temporary test accounts/corpus files, media runtime, and web build output. The worktree has no untracked files.

Historical extra Item

The old tests/adversarial/attack2.py Photos check could report a false extra Item. It saved Alice's timeline IDs, then created a Share, opted the recipient into that Photos root, and compared the recipient's later timeline with Alice's earlier snapshot. An asynchronous Alice Photos refresh could add a valid Alice Item after that snapshot. The old equality check could label it cross-User even though Files Index provenance showed the same owner. This is the past probe path that could produce the report. The UUID's actual owner and path remain unknown without its historical Index.

Decisions and gaps

  • I prepared a deterministic corpus of 240 Pillow JPEGs per User, dated 2026-09-01 through 2026-09-24, in separate Alice, Bob, and Admin Photos folders. The four-hour stop occurred before upload.
  • origin/dev later advanced to c4faf184d after my final fetch/merge. Its diff changes Photos routes/search and adversarial matrix files; this branch is now one commit behind it. I did not merge again after the time cap.
  • UX gaps closed/left: not applicable; no UI changed.
  • Required next step: rerun the local server corpus and live matrices on a fresh target after syncing the newer origin/dev commit.
#707 Round 3 report — INCOMPLETE ## Branch and files - Branch: `job/isolation-707` - Head: `0bb018a98fa5be97b3fcf9e2a5d62bc1a51b9abf` - I merged `origin/dev` commit `440e19dce` before the live run. It changes `deploy/media-sandbox` and `tests/adversarial/prepare-media-runtime.sh` to allow bounded local sandbox setup on a busy host. No issue-specific source change was made. - Those are the only tracked files in the merge commit. ## Completed checks The merged sandbox change fixed the local host limit. The bounded media test passed and generated a real WebP thumbnail. The inner decoder limit remains 64 processes. ```text TMPDIR="$PWD/target/tmp" tests/adversarial/test-media-sandbox.sh "$PWD/target/e2e-media-runtime" (no stdout) exit_code=0 ``` The offline Photos scope tests and classifications had passed before the live run: ```text Ran 7 tests in 1.742s OK Cross-User classification gate: 335 operations classified Generated entry point classification: 945 tools classified Admin coverage: 39 reviewed operations; contract and Rust guards agree ``` ## Live checks not run The local server build did not finish before the four-hour cap. I stopped it while it compiled mail/MCP dependencies. No server binary was produced, so I did not start the server, create accounts, upload the 720 JPEGs, or run `xuser_matrix.py`, `authz_matrix.py`, or the live Photos scope helper. There are no live summary lines to quote, and no live access-control result is claimed. The build command was: ```text cargo build -p calternal-server -p calternal-cli -p calternal-sync ``` Its last output before the stop was: ```text Compiling arraydeque v0.5.1 Compiling ammonia v4.2.0 Compiling async-imap v0.11.3 (/home/kayg/Developer/calternal-wt/isolation-707/crates/plugins/mail/vendor/async-imap) exit_code=130 (stopped at the four-hour cap) ``` No Rust crate changed, so I did not run `cargo fmt --check`, clippy, or crate tests. `cargo clean` completed: ```text Removed 7177 files, 3.5GiB total ``` I removed the temporary test accounts/corpus files, media runtime, and web build output. The worktree has no untracked files. ## Historical extra Item The old `tests/adversarial/attack2.py` Photos check could report a false extra Item. It saved Alice's timeline IDs, then created a Share, opted the recipient into that Photos root, and compared the recipient's later timeline with Alice's earlier snapshot. An asynchronous Alice Photos refresh could add a valid Alice Item after that snapshot. The old equality check could label it cross-User even though Files Index provenance showed the same owner. This is the past probe path that could produce the report. The UUID's actual owner and path remain unknown without its historical Index. ## Decisions and gaps - I prepared a deterministic corpus of 240 Pillow JPEGs per User, dated 2026-09-01 through 2026-09-24, in separate Alice, Bob, and Admin Photos folders. The four-hour stop occurred before upload. - `origin/dev` later advanced to `c4faf184d` after my final fetch/merge. Its diff changes Photos routes/search and adversarial matrix files; this branch is now one commit behind it. I did not merge again after the time cap. - UX gaps closed/left: not applicable; no UI changed. - Required next step: rerun the local server corpus and live matrices on a fresh target after syncing the newer `origin/dev` commit.
Author
Owner

Starting review fixes #896 and #895 on job/isolation-707, base 0bb018a98fa5be97b3fcf9e2a5d62bc1a51b9abf. I will add focused two-User Share/revoke regression tests, run Photos and server crate gates, and leave the live matrices to the merge round under the current verification policy. No dependency versions change.

Starting review fixes #896 and #895 on `job/isolation-707`, base `0bb018a98fa5be97b3fcf9e2a5d62bc1a51b9abf`. I will add focused two-User Share/revoke regression tests, run Photos and server crate gates, and leave the live matrices to the merge round under the current verification policy. No dependency versions change.
Author
Owner

Confirmed both independent findings in source. #896: /stacks/{group_id} returned the stored group when the Files member join was empty; the root check ran only inside the member loop. The fix checks the display item identity/path/hash in Files, checks its live root, and requires a current member. The added two-User regression keeps the Photos group after Share revoke and removes the Files member.

#895: /photos/search resolved incoming Shares for metadata but passed the original Home-only request context to VectorReadCapability. The planned fix passes those already validated and request-clipped library roots to the existing capability API. A deterministic inference backend will exercise actual owner-vector candidate selection, narrow roots, private candidates, and revoke. The small public embedding addition supplies an inference backend without changing normal model startup or read authority. No dependency versions or product decisions change.

Confirmed both independent findings in source. #896: `/stacks/{group_id}` returned the stored group when the Files member join was empty; the root check ran only inside the member loop. The fix checks the display item identity/path/hash in Files, checks its live root, and requires a current member. The added two-User regression keeps the Photos group after Share revoke and removes the Files member. #895: `/photos/search` resolved incoming Shares for metadata but passed the original Home-only request context to `VectorReadCapability`. The planned fix passes those already validated and request-clipped library roots to the existing capability API. A deterministic inference backend will exercise actual owner-vector candidate selection, narrow roots, private candidates, and revoke. The small public embedding addition supplies an inference backend without changing normal model startup or read authority. No dependency versions or product decisions change.
Author
Owner

Saved #896 as atomic commit fb4565a39 (fix(photos): hide stale stacks without current Files identity). The route requires the display Files identity/path/hash, a live library root, and at least one current member. The regression preserves existing Share/revoke checks, then restores the Share, removes the authoritative Files member, revokes again, and requires 404 with the Photos group still present.

Validation is pending. The required job target was empty. One Cargo process is still compiling dependencies for the focused regression, with the old stack implementation selected temporarily to prove the regression fails. No gate pass or live-server result is claimed. The committed implementation is the fix. No test expectation, dependency version, UI or migration changed.

Saved #896 as atomic commit `fb4565a39` (`fix(photos): hide stale stacks without current Files identity`). The route requires the display Files identity/path/hash, a live library root, and at least one current member. The regression preserves existing Share/revoke checks, then restores the Share, removes the authoritative Files member, revokes again, and requires 404 with the Photos group still present. Validation is pending. The required job target was empty. One Cargo process is still compiling dependencies for the focused regression, with the old stack implementation selected temporarily to prove the regression fails. No gate pass or live-server result is claimed. The committed implementation is the fix. No test expectation, dependency version, UI or migration changed.
Author
Owner

The first focused build stopped before the regression ran. Compiler-cache output was:

error: error writing dependencies to `/home/kayg/Developer/calternal-wt/authfix/target/tmp/sccache6NbVtR/deps.d`: No such file or directory (os error 2)

error: could not compile `bitflags` (lib) due to 1 previous error
warning: build failed, waiting for other jobs to finish...

The job exported its own TMPDIR, but the shared sccache process retained another worktree temporary directory. I will disable RUSTC_WRAPPER for this job only and rerun the focused regression. I did not restart the shared cache or change other jobs. The fixed stack commit remains fb4565a39; no Rust test or gate pass is claimed yet.

The first focused build stopped before the regression ran. Compiler-cache output was: ```text error: error writing dependencies to `/home/kayg/Developer/calternal-wt/authfix/target/tmp/sccache6NbVtR/deps.d`: No such file or directory (os error 2) error: could not compile `bitflags` (lib) due to 1 previous error warning: build failed, waiting for other jobs to finish... ``` The job exported its own `TMPDIR`, but the shared `sccache` process retained another worktree temporary directory. I will disable `RUSTC_WRAPPER` for this job only and rerun the focused regression. I did not restart the shared cache or change other jobs. The fixed stack commit remains `fb4565a39`; no Rust test or gate pass is claimed yet.
Author
Owner

Both fixes are now in the working tree. The focused build now checks the fixed code for both findings. The cache failure prevented an old-code runtime result.

For #895, the route supplies the live, request-clipped Photos roots to the existing VectorReadCapability::from_request_context API. Narrow sessions receive no additional roots. The route test first proves that the scene query returns no metadata-only hit, then supplies deterministic inference to the real private vector stores and requires only Alice's shared item. It also checks an authorized narrow Share subtree, excluded sibling/private roots, Bob's narrow Home, and revoke with Files and Photos rows retained. Existing fixture hashes and expectations remain unchanged. The small public PhotoClipIndexer::start_with_backend hook reuses store initialization and preserves normal model startup. No network model download is required by the new test.

Both fixes are now in the working tree. The focused build now checks the fixed code for both findings. The cache failure prevented an old-code runtime result. For #895, the route supplies the live, request-clipped Photos roots to the existing `VectorReadCapability::from_request_context` API. Narrow sessions receive no additional roots. The route test first proves that the scene query returns no metadata-only hit, then supplies deterministic inference to the real private vector stores and requires only Alice's shared item. It also checks an authorized narrow Share subtree, excluded sibling/private roots, Bob's narrow Home, and revoke with Files and Photos rows retained. Existing fixture hashes and expectations remain unchanged. The small public `PhotoClipIndexer::start_with_backend` hook reuses store initialization and preserves normal model startup. No network model download is required by the new test.
Author
Owner

Focused two-User route regression passed. Final gates after the one-time origin/dev merge at 3eda12a7b29ca139f719dba7db48c4140e888686: formatting, Photos clippy/test, and embedding clippy/test have passed. Server clippy/test are still running sequentially. The new route cases cover semantic-only candidate selection, narrow roots, revoke with retained Files/Photos rows, and an empty stale stack after Files removal then revoke. No live matrix result is claimed. Final output will be reported verbatim after the remaining gates.

Focused two-User route regression passed. Final gates after the one-time `origin/dev` merge at `3eda12a7b29ca139f719dba7db48c4140e888686`: formatting, Photos clippy/test, and embedding clippy/test have passed. Server clippy/test are still running sequentially. The new route cases cover semantic-only candidate selection, narrow roots, revoke with retained Files/Photos rows, and an empty stale stack after Files removal then revoke. No live matrix result is claimed. Final output will be reported verbatim after the remaining gates.
Author
Owner

The server clippy gate stopped at the existing RustEmbed declaration because cleanup from the previous round removed the web bundle:

error: #[derive(RustEmbed)] folder '/home/kayg/Developer/calternal-wt/isolation-707/crates/calternal-server/../../apps/web/build/' does not exist. cwd: '/home/kayg/Developer/calternal-wt/isolation-707'

All other server errors are missing Frontend::get methods caused by that failed derive. I will build the real production web bundle with bun run build, then resume only server clippy/test. Formatting and Photos/embedding clippy/test already passed. No UI source or expectation change is needed.

The server clippy gate stopped at the existing `RustEmbed` declaration because cleanup from the previous round removed the web bundle: ```text error: #[derive(RustEmbed)] folder '/home/kayg/Developer/calternal-wt/isolation-707/crates/calternal-server/../../apps/web/build/' does not exist. cwd: '/home/kayg/Developer/calternal-wt/isolation-707' ``` All other server errors are missing `Frontend::get` methods caused by that failed derive. I will build the real production web bundle with `bun run build`, then resume only server clippy/test. Formatting and Photos/embedding clippy/test already passed. No UI source or expectation change is needed.
Author
Owner

Review fixes for #707

Built #896 and #895 on job/isolation-707, from base 0bb018a98fa5be97b3fcf9e2a5d62bc1a51b9abf.

What changed

  • A stack must have a current display Files identity, path and hash, a current authorized library root, and at least one current member. A retained Photos group cannot return metadata after its Files members disappear or its Share is revoked (#896).
  • Photos search derives vector authority from the same live, request-clipped library roots as metadata. Normal full-Home requests can search owner vectors in opted-in incoming Shares. Narrow requests receive no additional roots (#895).
  • The two-User route regression proves that the scene query has no metadata match, then uses deterministic inference with the real private vector stores. It checks a shared semantic hit, authorized and excluded narrow roots, private candidates, revoke with Files and Photos rows retained, and Files removal followed by revoke with the Photos group retained.
  • PhotoClipIndexer::start_with_backend is a small public test and backend hook. It shares store initialization with normal startup. It does not change the normal model worker or vector authority.

Files changed in this round

  • crates/plugins/photos/src/routes.rs
  • crates/calternal-embed/src/clip_store.rs

Commits

  • fb4565a39: hide stale stacks without current Files identity.
  • 0aa160c65: derive shared vector reads from live library roots, with regression coverage.
  • 3eda12a7b29ca139f719dba7db48c4140e888686: required one-time merge of origin/dev. Only docs/DESIGN.md changed in that merge.

Decisions

No new product decision. Reuse the existing library-root resolver and server-owned vector capability API. Supply deterministic inference for route tests through the existing backend interface (DESIGN §36). Disable RUSTC_WRAPPER for this job only because the shared cache used another job's deleted temporary directory. No dependency version, migration, or existing test expectation changed.

UX gaps closed

Shared Photos appear for semantic-only queries. Narrow Photos search requests remain narrow. A stale stack uses the missing-item response after revoke, including when its entire current Files member join is empty.

UX gaps left and known gaps

No UI code changed. The new tests check authorization and vector candidate selection, not real CLIP model accuracy. Live HTTP/MCP matrices are deferred to the merge round under the current verification policy. The first old-code build stopped in the shared compiler cache before the test ran; no old-code runtime result is claimed. No performance measurement ran because this is not a performance issue.

For the merge round

Run these with the combined branch's local server and the normal throwaway User, token, data directory and OpenAPI fixtures. Do not print token files.

python3 -m unittest discover -s tests/adversarial -p test_photos_scope_contracts.py
XUSER_MATRIX_WITH_MCP=1 ADVERSARIAL_DERIVED_ONLY=1 python3 tests/adversarial/xuser_matrix.py
python3 tests/adversarial/authz_matrix.py

The first command checks Photos provenance helper contracts. The XUser command runs the live Photos and derived-data Share/revoke checks through HTTP and MCP. It must show only the granted owner and subtree before revoke, then no result after revoke. The authz matrix must prove the route policies for every identity and deny ungranted cross-User reads. No live matrix result is claimed by this job.

Validation and cleanup

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

The relevant output below is copied verbatim. All Cargo commands use CARGO_PROFILE_DEV_DEBUG=line-tables-only, CARGO_INCREMENTAL=0, CARGO_BUILD_JOBS=4, this worktree's target/tmp, and the preset job target directory. Clippy uses --all-targets -- -D warnings. Tests use -- --test-threads=4.

cargo clippy -p calternal-plugin-photos:

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

cargo test -p calternal-plugin-photos:

    Finished `test` profile [unoptimized + debuginfo] target(s) in 12m 28s
test result: ok. 47 passed; 0 failed; 3 ignored; 0 measured; 0 filtered out; finished in 54.26s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

cargo clippy -p calternal-embed:

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 6m 09s

cargo test -p calternal-embed:

    Finished `test` profile [unoptimized + debuginfo] target(s) in 5m 17s
test result: ok. 31 passed; 0 failed; 4 ignored; 0 measured; 0 filtered out; finished in 24.53s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

cargo clippy -p calternal-server:

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

cargo test -p calternal-server:

    Finished `test` profile [unoptimized + debuginfo] target(s) in 38m 42s
test result: ok. 107 passed; 0 failed; 3 ignored; 0 measured; 0 filtered out; finished in 34.98s

bun run build supplied the server's required embedded frontend assets and exited 0:

> Using @sveltejs/adapter-static
  ✔ done

The initial server clippy attempt stopped because apps/web/build/ did not exist. The production web build fixed that prerequisite, then server clippy passed. No source change was needed.

All requested gates passed. Web build output (apps/web/build and apps/web/.svelte-kit) was removed. cargo clean is running on the preset job target directory; it has not finished yet. The worktree is clean.

# Review fixes for #707 Built #896 and #895 on `job/isolation-707`, from base `0bb018a98fa5be97b3fcf9e2a5d62bc1a51b9abf`. ## What changed - A stack must have a current display Files identity, path and hash, a current authorized library root, and at least one current member. A retained Photos group cannot return metadata after its Files members disappear or its Share is revoked (#896). - Photos search derives vector authority from the same live, request-clipped library roots as metadata. Normal full-Home requests can search owner vectors in opted-in incoming Shares. Narrow requests receive no additional roots (#895). - The two-User route regression proves that the scene query has no metadata match, then uses deterministic inference with the real private vector stores. It checks a shared semantic hit, authorized and excluded narrow roots, private candidates, revoke with Files and Photos rows retained, and Files removal followed by revoke with the Photos group retained. - `PhotoClipIndexer::start_with_backend` is a small public test and backend hook. It shares store initialization with normal startup. It does not change the normal model worker or vector authority. ## Files changed in this round - `crates/plugins/photos/src/routes.rs` - `crates/calternal-embed/src/clip_store.rs` ## Commits - `fb4565a39`: hide stale stacks without current Files identity. - `0aa160c65`: derive shared vector reads from live library roots, with regression coverage. - `3eda12a7b29ca139f719dba7db48c4140e888686`: required one-time merge of `origin/dev`. Only `docs/DESIGN.md` changed in that merge. ## Decisions No new product decision. Reuse the existing library-root resolver and server-owned vector capability API. Supply deterministic inference for route tests through the existing backend interface (DESIGN §36). Disable `RUSTC_WRAPPER` for this job only because the shared cache used another job's deleted temporary directory. No dependency version, migration, or existing test expectation changed. ## UX gaps closed Shared Photos appear for semantic-only queries. Narrow Photos search requests remain narrow. A stale stack uses the missing-item response after revoke, including when its entire current Files member join is empty. ## UX gaps left and known gaps No UI code changed. The new tests check authorization and vector candidate selection, not real CLIP model accuracy. Live HTTP/MCP matrices are deferred to the merge round under the current verification policy. The first old-code build stopped in the shared compiler cache before the test ran; no old-code runtime result is claimed. No performance measurement ran because this is not a performance issue. ## For the merge round Run these with the combined branch's local server and the normal throwaway User, token, data directory and OpenAPI fixtures. Do not print token files. ```sh python3 -m unittest discover -s tests/adversarial -p test_photos_scope_contracts.py XUSER_MATRIX_WITH_MCP=1 ADVERSARIAL_DERIVED_ONLY=1 python3 tests/adversarial/xuser_matrix.py python3 tests/adversarial/authz_matrix.py ``` The first command checks Photos provenance helper contracts. The XUser command runs the live Photos and derived-data Share/revoke checks through HTTP and MCP. It must show only the granted owner and subtree before revoke, then no result after revoke. The authz matrix must prove the route policies for every identity and deny ungranted cross-User reads. No live matrix result is claimed by this job. ## Validation and cleanup `cargo fmt --check`: exit 0, no output. The relevant output below is copied verbatim. All Cargo commands use `CARGO_PROFILE_DEV_DEBUG=line-tables-only`, `CARGO_INCREMENTAL=0`, `CARGO_BUILD_JOBS=4`, this worktree's `target/tmp`, and the preset job target directory. Clippy uses `--all-targets -- -D warnings`. Tests use `-- --test-threads=4`. `cargo clippy -p calternal-plugin-photos`: ```text Finished `dev` profile [unoptimized + debuginfo] target(s) in 15m 52s ``` `cargo test -p calternal-plugin-photos`: ```text Finished `test` profile [unoptimized + debuginfo] target(s) in 12m 28s test result: ok. 47 passed; 0 failed; 3 ignored; 0 measured; 0 filtered out; finished in 54.26s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s ``` `cargo clippy -p calternal-embed`: ```text Finished `dev` profile [unoptimized + debuginfo] target(s) in 6m 09s ``` `cargo test -p calternal-embed`: ```text Finished `test` profile [unoptimized + debuginfo] target(s) in 5m 17s test result: ok. 31 passed; 0 failed; 4 ignored; 0 measured; 0 filtered out; finished in 24.53s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s ``` `cargo clippy -p calternal-server`: ```text Finished `dev` profile [unoptimized + debuginfo] target(s) in 7m 06s ``` `cargo test -p calternal-server`: ```text Finished `test` profile [unoptimized + debuginfo] target(s) in 38m 42s test result: ok. 107 passed; 0 failed; 3 ignored; 0 measured; 0 filtered out; finished in 34.98s ``` `bun run build` supplied the server's required embedded frontend assets and exited 0: ```text > Using @sveltejs/adapter-static ✔ done ``` The initial server clippy attempt stopped because `apps/web/build/` did not exist. The production web build fixed that prerequisite, then server clippy passed. No source change was needed. All requested gates passed. Web build output (`apps/web/build` and `apps/web/.svelte-kit`) was removed. `cargo clean` is running on the preset job target directory; it has not finished yet. The worktree is clean.
Author
Owner

Cleanup completed after the final report was posted. Head remains 3eda12a7b29ca139f719dba7db48c4140e888686. Worktree is clean. Web build output was removed. cargo clean exited 0:

     Removed 17788 files, 8.8GiB total
Cleanup completed after the final report was posted. Head remains `3eda12a7b29ca139f719dba7db48c4140e888686`. Worktree is clean. Web build output was removed. `cargo clean` exited 0: ```text Removed 17788 files, 8.8GiB total ```
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#707
No description provided.