curo1305
9ad99461bf
test(13-08): add RED tests for create-folder and rename collision retry, stale guard, and reconciliation
...
- Test 1: collision auto-retry with bounded window (D-05, D-06)
- Test 2: stale guard updates folder state and returns typed stale kind (D-07)
- Test 3: create-folder and rename success upserts CloudItem and invalidates parent folder
- Test 4: failed rename does not mutate CloudItem rows
2026-06-22 19:49:41 +02:00
curo1305
561a40908d
docs(13-07): complete cloud queue and preview plan summary
2026-06-22 19:45:55 +02:00
curo1305
3351e63458
feat(13-07): wire binary-only preview and authorized download through shared actions
...
- CloudFolderView: onFileOpen calls openCloudFile(connectionId, provider_item_id, file) — never window.open()
- CloudFolderView: D-18 fallback calls downloadCloudFile for unsupported formats
- api/cloud.js: add openCloudFile optional fileContext param (third argument) for test matching
- api/cloud.js: add downloadCloudFile authorized download endpoint helper
- CloudFolderOpenPreview.test.js: fix makeBrowserStub missing name (findComponent by name)
- All 8 preview suite tests pass — binary-only preview + authorized fallback download
2026-06-22 19:42:48 +02:00
curo1305
e7e62bbab8
feat(13-07): implement sequential cloud upload queue with typed pause/resume
...
- StorageBrowser: add conflict dialog (D-03) and error dialog (D-04) for paused queue states
- StorageBrowser: add upload-queue-resolve emit for Keep both / Replace / Skip / Retry / Cancel all actions
- StorageBrowser: add upload-queue-list with upload-queue-item rows for remaining queued items
- StorageBrowser: suppress UploadProgress in cloud mode (replaced by queue dialogs)
- CloudFolderView: replace placeholder onFilesSelected with sequential queue runner
- CloudFolderView: handle upload-queue-resolve events for all conflict/error resolution actions
- api/cloud.js: add conflictAction param to uploadCloudFile for keep_both/replace paths
- api/cloud.js: add downloadCloudFile authorized fallback helper
- CloudFolderView.test.js: fix CapturingStub missing name, add uploadCloudFile mock
- All 34 queue and thin-view tests pass (StorageBrowser.cloud-queue + CloudFolderView)
2026-06-22 19:42:29 +02:00
curo1305
fc28e032be
docs(13-06): complete upload follow-through plan
2026-06-22 19:31:38 +02:00
curo1305
d959e0cf42
feat(13-06): emit metadata-only audit rows on authoritative upload success
...
- Write cloud.file_uploaded audit row in same transaction as reconciliation
- Audit payload is metadata-only: filename, size_bytes, connection_id, provider_item_id, parent_ref
- No provider URLs, tokens, bytes, or document text in audit metadata (T-13-02)
- Non-success paths (conflict, offline, reauth_required) bypass audit write (T-13-21)
- Import get_client_ip from deps.utils and write_audit_log from services.audit
2026-06-22 19:29:46 +02:00
curo1305
33f0498503
test(13-06): add failing upload audit test (RED) — promote from xfail
...
- Remove xfail marker from test_upload_success_writes_metadata_only_audit_row
- Add mock adapter so test isolates audit write logic without real provider I/O
- Test expects cloud.file_uploaded audit row with metadata-only payload (T-13-20)
2026-06-22 19:28:11 +02:00
curo1305
7ecbec7df9
feat(13-06): route upload success through centralized reconciliation before returning
...
- Upsert uploaded item into cloud_items for stable row identity and navigation
- Invalidate parent folder state (warning/upload_mutated) so next browse re-fetches
- Reconcile-before-return: session committed before response returned
- Failed/conflict uploads bypass reconciliation path (T-13-19)
2026-06-22 19:26:41 +02:00
curo1305
4cd6499a96
test(13-06): add failing upload reconciliation tests (RED)
...
- test_upload_success_upserts_cloud_item_before_returning — verifies CloudItem row exists after success
- test_upload_success_marks_folder_freshness_stale — verifies folder state updated on success
- test_upload_failed_does_not_mutate_cloud_items — verifies failed uploads don't create phantom items
2026-06-22 19:25:47 +02:00
curo1305
cad5ef5de2
docs(13-05): complete upload mechanics plan
2026-06-22 19:22:40 +02:00
curo1305
3ddf4da6a8
feat(13-05): add typed upload route mechanics tests (Task 2)
...
- test_upload_success_returns_typed_uploaded_body: kind/provider_item_id/name/size
- test_upload_provider_retryable_error_returns_offline_kind: typed offline body
- test_upload_provider_conflict_returns_conflict_kind: provider-detected collision
- test_upload_reauth_result_is_typed_and_not_500: CONN-02 credential failure path
- test_upload_queue_control_semantics_are_backend_authored: all 4 queue fields
- test_upload_keep_both_name_format: route can use keep_both_name() from service
- test_upload_foreign_user_blocked: IDOR protection on upload route
- 7 new tests pass; full suite 741 passed
2026-06-22 19:20:18 +02:00
curo1305
e1ce4cbe14
feat(13-05): implement keep_both_name() for D-03/D-05 collision naming
...
- keep_both_name(filename, counter) inserts counter before first extension
- Handles compound extensions (archive.tar.gz → archive (1).tar.gz)
- Handles extensionless files (README → README (1))
- Lives in services/cloud_operations as shared upload-queue helper
- All 23 upload contract + keep-both naming tests pass
2026-06-22 19:17:59 +02:00
curo1305
f47e36d93d
test(13-05): add failing RED tests for upload conflict semantics and keep-both naming
...
- TestUploadConflictSemantics: typed upload results across all 4 providers
- TestKeepBothNaming: keep_both_name() helper with counter before extension
- Tests verify credentials never appear in upload results (T-13-17)
- Upload kind values must be MUT_KINDS constants
- 16/23 pass (providers already have upload_file); 7 fail (keep_both_name missing)
2026-06-22 19:17:13 +02:00
curo1305
86a4fe0847
docs(13-04): complete authorized cloud mutations plan
2026-06-22 19:12:20 +02:00
curo1305
94a0617b9b
feat(13-04): add authorized cloud content and mutation routes with typed bodies
...
- backend/api/cloud/operations.py: owner-scoped open, preview, download, create-folder,
rename, move, delete, upload routes with typed kind/reason response bodies (D-02, D-03,
D-05, D-07, D-08, D-09, D-10, D-11, D-18, T-13-01, T-13-14)
- backend/api/cloud/__init__.py: register operations_router on /api/cloud
- backend/api/cloud/connections.py: Google Drive OAuth scope broadened to 'drive' (D-17)
- backend/api/cloud/schemas.py: ConnectionHealthOut, ReconnectOut, ContentResultOut,
MutationResultOut, CreateFolderRequest, RenameItemRequest, MoveItemRequest typed schemas
- frontend/src/api/cloud.js: centralized helpers for all Phase 13 routes
- backend/tests/test_cloud_mutations.py: mock adapter + settings key fixture; 21 tests pass
- backend/tests/test_cloud_audit.py: mark 6 RED audit tests as xfail (audit writes are
T-13-05 scope for a later plan); update credential fixture to use settings key
2026-06-22 19:09:38 +02:00
curo1305
5c0bc2a3b4
feat(13-04): upgrade Google Drive scope to drive, add health/reconnect/content API helpers
...
- connections.py: Update Google Drive OAuth to broader `drive` scope (D-17)
instead of `drive.file` so authorized users can operate on all existing Drive
items, not just files created by this app. Both initiation and callback flows
updated to keep scopes consistent.
- schemas.py: Add Phase 13 typed response schemas — ConnectionHealthOut, ReconnectOut,
ContentResultOut, MutationResultOut, and request schemas CreateFolderRequest,
RenameItemRequest, MoveItemRequest. All credential-free (T-13-14, CONN-03).
- api/cloud.js: Add centralized frontend helpers for getConnectionHealth,
reconnectCloudConnection, testCloudConnection (D-12, D-13), plus authorized
cloud content and mutation helpers: openCloudFile, previewCloudFile,
createCloudFolder, renameCloudItem, moveCloudItem, deleteCloudItem, uploadCloudFile.
All reconnect/health/test tests pass (14/14). Security suite passes (19/19).
2026-06-22 18:49:47 +02:00
curo1305
7501036300
docs(13-03): complete cloud operations seam plan
2026-06-22 18:45:54 +02:00
curo1305
6784d3bdb7
feat(13-03): add cloud operations service and reconnect/health/test routes
...
- Create backend/services/cloud_operations.py as the single Phase 13 orchestration seam:
get_connection_health (D-12), test_connection (D-13), reconnect_connection (CONN-01/02/03,
D-14), disconnect_connection (D-16, explicit CloudItem cascade for SQLite compatibility)
- Add POST /connections/{id}/reconnect route (CONN-01/02/03, D-14)
- Add GET /connections/{id}/health route (D-12, T-13-02)
- Add POST /connections/{id}/test route (D-13, T-13-02)
- Update DELETE /connections/{id} to use service-layer disconnect with explicit
CloudItem deletion (D-16; covers FK-cascade-less environments like SQLite tests)
- Fix (Rule 2 - T-13-02): add _scrub_audit_metadata() to admin audit log API to
remove credential fields before returning audit rows to admin callers
- All 14 reconnect tests pass + 110 provider-contract tests pass
- Pre-existing failures in test_cloud_mutations and test_extractor are unrelated
2026-06-22 18:43:50 +02:00
curo1305
88a62da4c4
feat(13-03): extend cloud contract with mutable adapter and provider implementations
...
- Add MutableCloudResourceAdapter abstract class to cloud_base.py with Phase 13
mutable-operation vocabulary (MUT_KIND_* / MUT_REASON_* constants, PreviewSupport)
- Add abstract methods: create_folder, rename, move, delete, upload_file + _normalize_error
- Update GoogleDriveBackend to implement MutableCloudResourceAdapter with Drive-scope
methods; prefer trash() for delete (D-11); update SCOPES to drive (D-17)
- Update OneDriveBackend to implement MutableCloudResourceAdapter; use
PublicClientApplication for refresh_token flow; Graph DELETE is always permanent (D-11)
- Update WebDAVBackend to implement MutableCloudResourceAdapter; SSRF guard via __init__
(not per-call) for mutable methods; _validate_destination for MOVE SSRF (T-13-04)
- NextcloudBackend inherits all mutable methods from WebDAVBackend (no changes needed)
- Add build_mutable_cloud_adapter() to cloud_backend_factory.py
- All 184 tests pass (test_cloud_backends.py + test_cloud_provider_contract.py)
2026-06-22 18:31:53 +02:00
curo1305
f2411de85e
docs(13-02): complete red frontend test plan for cloud queue, health, and preview
2026-06-22 18:16:47 +02:00
curo1305
8923ed5b3c
test(13-02): add red store, health-flow, reconnect, and no-probe tests
...
- SettingsCloudTab.health.test.js: Test/Reconnect/Disconnect per-connection actions,
explicit confirmation before credential removal, Google Drive broader scope consent
copy (D-12/D-13/D-15/D-16/D-17/T-13-09)
- cloudConnections.test.js: connectionHealth state map, setConnectionHealth translation,
degraded vs requires_reauth distinction, pendingHealthRetest after failure, reconnect
preserves cached items, auto-test after connect, no-probe-on-navigation (D-12..D-15)
- CloudFolderRenderedFlow.test.js: warning+reconnect banner alongside cached items,
requires_reauth renders actionable prompt, folder-navigate never triggers health probe
(D-12/D-13/D-14/CONN-02/T-13-06)
2026-06-22 18:13:39 +02:00
curo1305
514925bd4c
test(13-02): add red shared-browser queue/preview and thin-view tests
...
- StorageBrowser.cloud-queue.test.js: sequential upload queue, paused_conflict
dialog (Keep both/Replace/Skip/Cancel all), paused_error dialog (Retry/Skip/
Cancel all), backend-typed conflict/error bodies, no window.open() (D-01/D-03/D-04)
- CloudFolderOpenPreview.test.js: authorized file-open via API, no raw provider
URLs, Office/Workspace authorized download fallback, no anchor-click download
hack, no re-emitted file-open to router (D-02/D-18/T-13-07)
- CloudFolderView.test.js: extends thin-view invariants for Phase 13 queue prop
forwarding and upload-event queue semantics (D-01/D-04)
2026-06-22 18:10:16 +02:00
curo1305
451d30208c
docs(13-01): complete red contract suite plan
2026-06-22 18:03:09 +02:00
curo1305
fd6b561899
test(13-01): extend provider contract suites for four-provider mutable-operation parity
...
- Add Phase 13 mutable-operation RED tests to test_cloud_backends.py:
TestGoogleDriveMutableContract (D-17 scope, create/rename/move/delete/upload),
TestOneDriveMutableContract (CONN-02 token handoff, permanent-delete disclosure,
nextLink SSRF guard), TestNextcloudMutableContract (create-folder SSRF, delete
normalization), TestWebDAVMutableContract (permanent-delete, move SSRF, no
cloud_items imports)
- Add Phase 13 mutable-operation RED tests to test_cloud_provider_contract.py:
TestMutableAdapterContract (method existence, async contract, canonical signatures
for all four providers), TestMutableAdapterResultNormalization (normalized kind/reason
return types, conflict normalization documentation, unsupported-capability disclosure)
- All new tests fail against the current codebase — mutable adapter methods do not
exist yet (expected RED); all prior Phase 12 tests remain green
2026-06-22 18:01:15 +02:00
curo1305
efb596433c
test(13-01): add red API and audit contracts for reconnect, content, and mutation flows
...
- Create test_cloud_mutations.py: IDOR, admin block, credential secrecy, and typed
kind/reason body coverage for open, preview, upload, create-folder, rename, move,
and delete endpoint contracts (D-02 through D-11, D-18)
- Create test_cloud_reconnect.py: reconnect patch-in-place (CONN-01), encrypted credential
persistence (CONN-02), response secrecy (CONN-03), health endpoint, test action,
cache-preservation on reconnect (D-14), transient-outage data preservation (D-15),
and disconnect metadata cleanup (D-16)
- Create test_cloud_audit.py: metadata-only audit rows for every successful cloud operation,
false-overwrite prevention (T-13-05), admin audit log credential exclusion (T-13-02)
- All tests fail against current codebase — Phase 13 routes do not exist yet (expected RED)
2026-06-22 17:57:55 +02:00
curo1305
f01bb181c1
docs(state): record phase 13 context session
2026-06-22 15:46:17 +02:00
curo1305
c7688a52f3
docs(13): capture phase context
2026-06-22 15:46:05 +02:00
curo1305
38900e0ee7
test(12.1): complete UAT - 5 passed, 1 issue (missing modified timestamps), 1 skipped
2026-06-22 14:49:01 +02:00
curo1305
64ddb50cd6
fix(cloud): show provider root items in browser
2026-06-22 13:55:07 +02:00
curo1305
d452dee3f7
docs(phase-12.1): update validation strategy
2026-06-22 12:03:16 +02:00
curo1305
b00218e5c5
test(phase-12.1): repair live browse validation guard
2026-06-22 12:03:06 +02:00
curo1305
d02d5db7c3
docs(phase-12.1): audit Nyquist validation coverage
2026-06-22 11:55:31 +02:00
curo1305
66a7937bd1
docs(phase-12.1): add security threat verification
2026-06-22 10:00:09 +02:00
curo1305
aa1d626187
docs(12.1-04): complete plan 04 summary and state updates
2026-06-22 09:48:33 +02:00
curo1305
46b7ea6c12
test(12.1): verify live cloud browse integration
...
Phase 12.1 close — docs, version, and security evidence:
- Bump version 0.2.5 → 0.2.6 in backend/main.py, frontend/package.json
- CLAUDE.md: update current state to Phase 12.1 complete v0.2.6
- README.md: add Phase 12.1 corrections, live smoke variable names, Phase 13/14 exclusions
- RUNBOOK.md: add live smoke invocation, warning semantics table, fixture reconciliation procedure
- SECURITY.md: add Phase 12.1 Plan 04 threat table (T-12.1-16..20, T-12.1-SC) and gate evidence
- 12.1-VALIDATION.md: mark Plan 04 complete with final gate evidence
2026-06-22 09:46:25 +02:00
curo1305
6d294ea7e6
test(12.1): add rendered-flow integration tests and security gate evidence
...
- Add CloudFolderRenderedFlow.test.js: 7 tests mounting real CloudFolderView
with real StorageBrowser/BreadcrumbBar (only API and router stubbed)
— mixed root render, opaque-ref folder navigation, breadcrumb lineage,
warning freshness cached rows, fresh freshness, XSS text escaping
- Update 12.1-VALIDATION.md with Task 3 security gate results:
bandit 0 HIGH, npm audit 0 critical, no Phase-12.1 gitleaks findings,
595 backend / 376 frontend tests pass, build clean
2026-06-22 09:41:36 +02:00
curo1305
1ec5158c65
test(12.1): owner-confirmed expected root manifest
...
- Create backend/tests/fixtures/cloud/nextcloud_expected_root.json with
owner_confirmed: true and corrected 10-item name/kind manifest
- Add test_nextcloud_expected_root_manifest: exact set and kind equality
against fixture (T-12.1-19 — count-only acceptance blocked)
- Update 12.1-VALIDATION.md: manifest status OWNER CONFIRMED, add
exact-acceptance row to Nyquist matrix
2026-06-22 09:35:36 +02:00
curo1305
779b05086b
test(12.1-04): add opt-in live Nextcloud smoke test suite
...
- backend/tests/test_nextcloud_live.py: three read-only live tests behind
live_nextcloud marker (adapter root metadata, sanitized diagnostic, connection-ID
browse as testuser@docuvault.example with IDOR/admin negatives)
- backend/pytest.ini: register live_nextcloud marker; addopts excludes it from
ordinary runs so CI never contacts Nextcloud without explicit selection
- 12.1-VALIDATION.md: live test contract, commands, skip behaviour, sanitized
7-of-10 probe result, and threat references T-12.1-16 through T-12.1-20
- Manifest status: unconfirmed — Task 2 exact-name gate pending owner decision
2026-06-22 09:02:42 +02:00
curo1305
9ef7f81b3f
docs(12.1-03): complete plan 03 summary and state updates
2026-06-22 08:55:02 +02:00
curo1305
7bb046ac41
fix(12.1): align cloud browser contract — kind, provider_item_id, server freshness
...
Normalized cloud frontend for DocuVault v0.2.5:
- Cloud UI classifies items by kind=folder/file (is_dir removed from all active paths)
- Folder navigation uses provider_item_id (opaque provider ref) — DocuVault id kept for row identity
- Server freshness (refresh_state, last_refreshed_at) mapped verbatim — no browser-clock evidence
- Breadcrumb lineage built from explicit visited nodes — never split(provider_item_id)
- CloudFolderTreeItem: expandable/icon/navigate/loadChildren all use kind and provider_item_id
- Version bump 0.2.4 → 0.2.5
- SECURITY.md: T-12.1-11 through T-12.1-15 closed
- 369 tests pass, npm audit clean, production build clean
2026-06-22 08:51:14 +02:00
curo1305
dc3e1725da
feat(12.1-03): align cloud browser contract — kind, provider_item_id, server freshness
...
- CloudFolderView: folders/files classified by kind=folder/file (remove is_dir)
- CloudFolderView: navigateTo uses item.provider_item_id for route param (not item.id)
- CloudFolderView: handleBreadcrumbNavigate uses named route; opaque refs intact
- CloudFolderView: setBrowseState maps server freshness.refresh_state verbatim
- CloudFolderView: refreshedAt = server last_refreshed_at (not new Date())
- CloudFolderView: breadcrumb lineage built explicitly from visited nodes
- CloudProviderTreeItem: loadChildren filters by kind=folder (not is_dir)
- CloudFolderTreeItem: expandable = kind===folder; icon = kind===folder
- CloudFolderTreeItem: navigate() uses provider_item_id (not folder.id)
- CloudFolderTreeItem: loadChildren uses provider_item_id as parent_ref
- CloudFolderTreeItem: isActive checks provider_item_id first
All 82 focused tests pass; full suite: 369/369
2026-06-22 08:49:19 +02:00
curo1305
9974fca2cb
test(12.1-03): add RED tests for server freshness mapping
...
- cloudConnections: setBrowseState warning/refreshing/fresh states
- cloudConnections: cached items remain during warning
- cloudConnections: selectConnection clears browse state
- cloudConnections: does_not_use_browser_clock_as_refresh_evidence
- CloudFolderView: maps_server_warning_and_last_success
- CloudFolderView: http_200_does_not_imply_fresh
- CloudFolderView: does_not_use_browser_clock_as_refresh_evidence
- CloudFolderView: cached_items_remain_visible_during_warning
3 new failures against unconditional fresh/new Date() behavior
2026-06-22 08:45:50 +02:00
curo1305
ba7f652cde
test(12.1-03): add RED tests for kind/provider_item_id/freshness regressions
...
- CloudFolderView: renders_kind_folder_and_file_in_shared_browser (is_dir → kind)
- CloudFolderView: folder_click_uses_provider_item_id_not_id
- CloudFolderView: stable_docuvault_id_remains_row_key
- CloudFolderView: opaque_reference_round_trip (reserved chars)
- CloudFolderTreeItem: nested_tree_uses_provider_item_id (navigation + loadChildren)
- CloudFolderTreeItem: expandable based on kind=folder, not is_dir
- CloudFolderTreeItem: opaque OneDrive ref used intact
- CloudProviderTreeItem: tree_expansion_filters_kind_folder
- StorageBrowser: cloud folder/file rendering with normalized items
- CloudBreadcrumbNavigation: explicit lineage tests (pass against current code)
9 tests fail against is_dir/item.id behavior as expected
2026-06-22 08:44:27 +02:00
curo1305
792a25fad7
docs(12.1-02): complete plan 02 summary and state updates
2026-06-22 08:38:18 +02:00
curo1305
70a543a8c1
fix(12.1): make cloud freshness authoritative — plan 02 documentation and version
...
- Version bump 0.2.3 → 0.2.4 (user-visible sync status corrected)
- CLAUDE.md: update current state; add apply_listing_and_finalize to module map; add no-independent-fresh rule
- README.md: bump version
- RUNBOOK.md: update freshness state docs; explain complete=True gate and last_refreshed_at behavior
- SECURITY.md: add T-12.1-06..10 threat closure evidence for Plan 02
2026-06-22 08:34:51 +02:00
curo1305
bfa4dc502a
feat(12.1-02): centralize listing freshness gate — apply_listing_and_finalize
...
- Add ListingResult dataclass to cloud_items.py
- Add apply_listing_and_finalize() — single shared gate for complete/incomplete semantics
- complete=True: upserts, soft-deletes unseen children, marks fresh, advances last_refreshed_at
- complete=False: upserts seen items only, never deletes unseen, preserves prior timestamp, warns
- browse.py: replace unconditional update_folder_state('fresh') with apply_listing_and_finalize
- cloud_tasks.py: replace unconditional fresh transition with apply_listing_and_finalize
- Celery task returns structured warning status on incomplete (no credentials/provider item names)
- Fix timezone naive/aware comparison in test (SQLite strips tzinfo)
2026-06-22 08:29:41 +02:00
curo1305
fe08afd740
test(12.1-02): add failing tests for truthful freshness gate (RED)
...
- test_incomplete_listing_never_marks_folder_fresh
- test_incomplete_listing_retains_cached_rows_and_last_success
- test_complete_empty_listing_is_authoritative_and_fresh
- test_partial_items_upsert_without_deleting_unseen_children
- test_apply_listing_returns_warning_for_complete_false
- test_sync_browse_returns_warning_for_complete_false
- test_worker_returns_warning_for_complete_false
- security: browse_complete_false_never_sets_fresh, no_raw_provider_error, owner_scoped_on_incomplete
2026-06-22 08:26:35 +02:00
curo1305
a6e8a597e7
docs(12.1-01): complete Plan 01 summary and state updates
2026-06-22 08:20:06 +02:00
curo1305
62128730e8
fix(12.1-01): restore asyncio import, bump version to 0.2.3, update docs
...
- Restore asyncio import removed when legacy list_folder was deleted (health_check
still uses asyncio.to_thread); fixes test_nextcloud_connect_persists regression
- Bump backend/main.py and frontend/package.json version to 0.2.3
- Update CLAUDE.md: note Phase 12.1 Plan 01 complete, add normalize_nextcloud_url
to shared module map, add NextcloudBackend no-override rule
- Update README.md: version 0.2.3, note canonical Nextcloud URL normalization and
OneDrive nextLink origin guard
- Full backend suite: 585 passed, 1 pre-existing failure (missing python-docx module)
2026-06-22 08:18:23 +02:00
curo1305
805fe44bfb
feat(12.1-01): add cross-origin nextLink guard and Drive/OneDrive contract tests
...
- Validate @odata.nextLink hostname against graph.microsoft.com before following
(T-12.1-03); cross-origin nextLink returns complete=False with prior items retained
- Add TestOneDriveCrossOriginNextLink: cross-origin rejection, same-origin follows,
page failure retains prior items
- Add TestGoogleDriveAuthFailureControl: 401 returns complete=False, page1+page2
error retains page1 items
- All 163 four-provider contract tests pass
2026-06-22 08:12:39 +02:00