docs: correct the Proton Drive handoff after a cold read
build / windows (push) Skipped
build / other_os (push) Skipped
build / mac_amd64 (push) Skipped
build / mac_arm64 (push) Skipped
build / linux (push) Skipped
build / go1.26 (push) Skipped
build / linux_386 (push) Skipped
build / android-all (push) Skipped
build / lint (push) Skipped
Build & Push Docker Images / Build Docker Image for linux/386 (push) Skipped
Build & Push Docker Images / Build Docker Image for linux/amd64 (push) Skipped
Build & Push Docker Images / Build Docker Image for linux/arm/v6 (push) Skipped
Build & Push Docker Images / Build Docker Image for linux/arm/v7 (push) Skipped
Build & Push Docker Images / Build Docker Image for linux/arm64 (push) Skipped
Build & Push Docker Images / Merge & Push Final Docker Image (push) Waiting for needed jobs

A reader with only the repository walked the handoff and the remaining
plan tasks against the code. Mark the plan and spec lines that later
rulings superseded, add corrections for Tasks 18 to 21, fix stale TODO
and PERMISSIONS entries, ignore the skill workspace, point AGENTS.md at
the handoff and take the test account usernames out of it.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
This commit is contained in:
Lucas WintherandClaude Fable 5.2 committed 2026-11-11 21:21:43 +01:00
1 parent 901d7fd5b7
commit 85f6b2f0e2
7 files changed
+163 -30

No files matched your search

+1
View File
@@ -27,3 +27,4 @@ resource_windows_*.syso
CLAUDE.local.md
.claude/settings.local.json
.worktrees/
.superpowers/
+1
View File
@@ -141,6 +141,7 @@ This fork carries agent tooling that upstream does not. Never include `docs/spec
- Required skill: `superpowers` - check at session start, install if missing. There is no product UI here, so `impeccable` is not needed.
- Specs go in `docs/specs/NNN-slug.md` (permanent). Plans go in `docs/plans/NNN-slug.md` and are removed with `git rm` before merge. This overrides the superpowers default paths.
- Paused or in-flight work is listed under "Now" in `docs/TODO.md` with the handoff to read first.
- Non-trivial work: brainstorm → spec → plan → TDD → review. For trivial fixes say "trivial, skipping spec" and proceed.
- Log things noticed but deliberately not fixed in `docs/TODO.md`, record decisions in `docs/DECISIONS.md`, and keep `docs/ARCHITECTURE.md` in step with structural changes - in the same commit as the code.
- `docs/PERMISSIONS.md` is the source for the `.claude/`, `.codex/` and `.gemini/` permission files - change them together.
+7 -1
View File
@@ -8,7 +8,13 @@ every tool file in the same commit. Review changes to these files and to
## Verified commands
Run on 2026-11-11. The development host has **no Go toolchain**, so every `go`
Update 2026-11-11: later the same day Go 1.27.4 (`~/.local/opt/go`) and
golangci-lint 2.15.0 (`~/.local/bin`) were installed on the host at the
owner's request, and all Proton Drive work was built, tested and linted
with them directly. The table below predates that and has not been re-run
on the host (plan Task 18, step 3).
Run on 2026-11-11. The development host had **no Go toolchain** at the time, so every `go`
row was run inside the `golang:1.26` container (go1.26.11) with the repository
mounted, using this wrapper:
+5 -5
View File
@@ -84,7 +84,7 @@ Small, specific, independently actionable. Remove only when actually done.
the bridge's block reader read this format (v6 PKESK, SEIPDv2 blocks and
block signatures) and the new `toISOString` modification time.
- [ ] protondrive: when go-proton-api and Proton-API-Bridge leave `go.mod`
(Task 14), delete the old-library parts of
(plan Task 19), delete the old-library parts of
`backend/protondrive/crypto_diff_internal_test.go` and the `-update`
branch of `TestFrozenFixtures`, keeping the fixtures and their check;
`github.com/relvacode/iso8601`, made a direct requirement for that test,
@@ -145,15 +145,15 @@ Small, specific, independently actionable. Remove only when actually done.
## Known issues
- [ ] On the development host (Go 1.27.4) `RCLONE_CONFIG=/notfound go test ./...`
fails only `cmd/serve/s3`, unrelated to this work and undiagnosed (seen
2026-11-11 before the Proton Drive rewrite began). That is the baseline
for plan Task 19's "no new failures"; the container list below is not.
- [ ] `RCLONE_CONFIG=/notfound go test ./...` in the bare `golang:1.26`
container fails 7 packages (list in `docs/PERMISSIONS.md`). `fs/logger`
needs `rsync`; the mount, serve and smb failures are undiagnosed and are
probably missing container tooling (FUSE, git-annex), not code. Confirm on a
host with Go installed before treating a full-suite failure as a regression.
- [ ] protondrive: `errNotSignedIn` (session.go) tells the user to run
`rclone config reconnect remote:` with the literal name `remote:`; the
CAPTCHA error was fixed to name the real remote in `connect`, this one
was not (seen in Task 15).
- [ ] protondrive: `TestRegressionManyNewFs` can't see from the fake
server that key unlocks run one at a time; only
`TestLoadAccountSerialised` (internal, `unlockHook`) checks it.
+123 -16
View File
@@ -94,9 +94,13 @@ Not done, and easy to assume otherwise:
## 4. How to resume
Development used the `superpowers:subagent-driven-development` skill. Its
workspace is normally git-ignored at
`.superpowers/sdd/001-protondrive-tier1/`; a copy of every Markdown file
Development used the `superpowers:subagent-driven-development` skill
(obra's superpowers plugin, version 7.0.0; install with
`/plugin marketplace add obra/superpowers-marketplace` then
`/plugin install superpowers@superpowers-marketplace`). The skill is a
convenience, not a requirement: the per-task loop below can be followed by
hand. Its workspace lives at `.superpowers/sdd/001-protondrive-tier1/`,
which `.gitignore` excludes; a copy of every Markdown file
from it (ledger, per-task briefs, implementer reports, reviews, the shared
implementer/reviewer rules and the global constraints) is committed at
`docs/plans/001-protondrive-tier1-workspace/`. To resume with the skill:
@@ -110,7 +114,10 @@ git switch -c protondrive-tier1-resume # never work on master
The ledger's first line names the plan; tasks with a `Task N: complete`
line are done. Paths inside `implementer-common.md` and
`reviewer-common.md` assume the checkout is `/home/user/rclone` and the
branch `protondrive-tier1` - adjust both if they differ.
branch `protondrive-tier1` - adjust both if they differ. One rule in
`implementer-common.md` is obsolete: "the old `protondrive.go` must keep
compiling until Task 14" (Task 14 is done; there is no old implementation
left in the package).
Per task: extract the brief (`task-N-brief.md` holds the plan text for
task N; later ones are not extracted yet - copy the task's section from
@@ -145,6 +152,86 @@ most capable model (merge base `06700aaf2`), one fix wave, then
whether the server lists drafts and trashed items as children; browser
sign-in; `TestIntegration` and `test_all` against the real service.
### Notes for Tasks 18 to 21
The plan text for these tasks predates the work that changed things under
it. Known corrections, found by having a fresh reader walk the plan against
the code:
**Task 18**
- Every command needs `RCLONE_CONFIG=/notfound` except where it must read
the `TestProtonDriveFake:` definition - `test_all` gets its remote from
the init.d script's output, not from the config file, so
`RCLONE_CONFIG=/notfound go run ./fstest/test_all ...` is right. Check
that no step can reach the live `TestProtonDrive:` remote.
- `-run TestIntegration` matches both the live `TestIntegration` and
`TestIntegrationFake`. `TestIntegrationFake` skips itself when `-remote`
is given; keep that guard.
- The init.d script cannot simply copy `TestWebdavRclone`. `protonfake`
prints freshly minted credentials on stdout
(`endpoint=`, `client_uid=`, `client_access_token=`,
`client_refresh_token=`, `client_salted_key_pass=`, `type=protondrive`),
while the `run` helper in `rclone-serve.bash` redirects stdout to a log
file. The script has to start the binary, wait for the six lines, and
echo them. Use a built binary, not `go run` (killing the `go` parent
leaves the server running), and find the repository root rather than
assuming the working directory, because scripts are run from each test
package's directory. Nobody has designed this yet.
- No port has been allocated in `PORTS.md`; pick the next free one there.
- All test processes started by `test_all` would share one fake session
through those values. Refresh tokens are single-use, and a process whose
credentials come from the environment cannot see another process's
refresh (section 8, "two processes sharing tokens through environment
variables"). If anything makes a process refresh - check whether the
standalone fake ever expires an access token, and what tests that call
`Disconnect` do to the shared session - the others would be left with
dead tokens. Check this before writing the script; if it is a problem,
have `protonfake` mint a session per run or issue non-expiring tokens in
standalone mode.
- Whether the owner's Gitea has an Actions runner is unknown. Ask the
owner; the workflow cannot be verified locally either way.
- Expect real failures from `fs/operations`, `fs/sync` and `vfs`: they have
never been run against this backend.
**Task 19**
- Step 1 says to build `master` as the "old" binary. `master` now contains
the new backend. Build the plan's base commit `06700aaf2` instead (it
has the old backend and the same rclone version).
- Step 2: `TestFrozenFileFixture` is already in
`download_internal_test.go` with no old-library import.
`TestFrozenFixtures` still depends on helpers inside
`crypto_diff_internal_test.go` and on an `-update` regeneration path.
The accurate instructions are the `docs/TODO.md` entry beginning "when
go-proton-api and Proton-API-Bridge leave `go.mod`", which also records
that multi-block coverage of old-library files is lost at that point.
- `go mod tidy` is part of the approved plan for this task, so it does not
need asking again, but accept only the removal of the two old libraries
and what they alone pulled in (and `relvacode/iso8601` returning to
indirect or leaving). Anything else: stop and ask.
- "No new failures in `go test ./...`": the baseline on the host is in
`docs/TODO.md` under "Known issues" (only `cmd/serve/s3` fails).
**Task 20**
- Must also document what was decided after the plan was written: section
7 rulings 18 to 26 as user-visible behaviour, the ranged-read caveat,
the `--dump headers` exposure, and both protocol limits.
**Task 21**
- The preconditions in the plan are out of date (see the note there and
section 5). The accounts use password sign-in.
- "Interop with Proton's official CLI": the reference SDK checkout must
never be built or run (it is untrusted reference data), and no official
CLI is installed. Ask the owner whether to install a released Proton
client for this, or to limit interop to the web app as done so far.
- Golden-fixture capture stores real account material; captured nodes must
come only from the throwaway test accounts and be reviewed for anything
identifying before they are committed.
- Do not delete `rclone-live-probe-20261111-130916` on the free account.
Later milestones, each needing its own plan: M5 (events and `ChangeNotify`),
M6 (persistent metadata cache, rclone Metadata support, small-file
single-request upload), M7 (upstream path, deferred by the owner).
@@ -157,12 +244,14 @@ Machine-local, not in the repository. A new machine needs them recreated.
golangci-lint 2.15.0 in `~/.local/bin`.
- Two Proton accounts the owner created for testing, configured as rclone
remotes in `~/.config/rclone/rclone.conf` (mode 600, passwords obscured):
- `TestProtonDrive:` - free account, username
`stereotypicalcat-rclone-testacc1`, password in
- `TestProtonDrive:` - free account; password file
`~/proton_rclone_test_acc_password`.
- `TestProtonDrivePaid:` - paid account, username
`stereotypicalcat-rclone-testacc2`, password in
- `TestProtonDrivePaid:` - paid account; password file
`~/proton_paid_acc_password`. **Never signed in to yet.**
The usernames are in that rclone config (`rclone config show`), not in
this repository; on a new machine ask the owner for the accounts. Both
remotes are set up for password sign-in, which is what every live probe
used; browser sign-in has not been exercised against the real service.
Never print, paste, log or commit these passwords or any token, and never
put them in a subagent's prompt.
- **Every `go test` must set `RCLONE_CONFIG=/notfound`**, e.g.
@@ -201,15 +290,29 @@ Not yet proven: the paid account; browser (session-fork) sign-in against
the real service; 2FA; the official CLI or desktop clients reading rclone's
files; `fstests`/`test_all` against the real service; whether the real
server lists drafts or trashed items as children (affects `Rmdir`); the
items marked "confirm on a real account" in `docs/TODO.md`; anything
changed after `c00162ed2` (signature refusal, manifest-first reads, trust
inheritance, cache insert-in-place, decompression limits).
items marked "confirm on a real account" in `docs/TODO.md`.
The security changes made after `c00162ed2` were re-checked live on the
free account without `ignore_signature_errors`: at `e7e43095d` (root of
trust, subtree distrust, manifest-first reads: listing, a verified full
read of the web-app file, an upload, a `Range: bytes=0-` read, purge) and
at `9c90998f7`, the last code commit (sync up, `check` with 0 differences,
a 4-stream multi-thread download byte-identical, verified read of the
web-app file, server-side move, purge). That drive's volume was created by
this client, so the root-of-trust rule is still unchecked on a drive made
by Proton's own apps. The hostile cases themselves can only be exercised
against the fake.
## 7. Rulings made during execution
Decisions the controller made where the plan or spec was silent or wrong.
Each is in the ledger with its reason and cost; the owner has not been
asked to confirm them individually.
Where this list and the plan or spec disagree, this list is what the code
does. Most are in the ledger as `Ruling:` lines with their reason and what
it costs if wrong; 16, 17 and 24 to 28 came out of review fix rounds and
their reasoning is in `task-14-review.md`, `task-16-review.md`,
`task-16-rereview.md`, `task-16-rereview2.md` and `task-17-review.md` in
the workspace copy, and in `docs/DECISIONS.md`. The owner was told of them
in summary but has not confirmed them one by one.
1. No separate worktree: work happened on branch `protondrive-tier1` in
the main checkout.
@@ -293,9 +396,13 @@ server itself supplies.
Every review's minor findings were deferred to the final whole-branch
review. They are listed per task in the ledger (`progress.md`, lines
beginning `Task N: minor (deferred)` and "Deferred minors"), and the
per-task review files in the workspace copy give file and line. Items that
need a decision or a live check are in `docs/TODO.md`. The ones most worth
beginning `Task N: minor (deferred)` and "Deferred minors"). Only Tasks
14, 16 and 17 have review files in the workspace copy with file and line;
for Tasks 1 to 13 and 15 the reviewer's text was not saved and the ledger's
one-line entry (plus the implementer's report for that task) is all there
is, so expect to re-locate those by reading the code. The whole-branch
review will find most of them again. Items that need a decision or a live
check are in `docs/TODO.md`. The ones most worth
a new agent's attention:
- Ranged reads do not verify the signed manifest. Planned fix: a bounded
+19 -4
View File
@@ -17,7 +17,8 @@ Proton server, ending with the old bridge libraries removed.
**Architecture:** New unexported files (`session.go`, `account.go`,
`crypto.go`, `nodes.go`, `cache.go`, `upload.go`, `download.go`) grow beside
the existing `protondrive.go` and are exercised only by tests until Task 14,
which rewrites `protondrive.go` on top of them and drops the old libraries.
which rewrites `protondrive.go` on top of them. (The old libraries leave
`go.mod` in Task 19, not Task 14.)
A fork-only package `backend/protondrive/protontest` is the fake server; it
must never import `backend/protondrive`.
@@ -1079,7 +1080,9 @@ Decisions:
- **`Config` state machine** (model: `backend/internxt/internxt.go:139-209`):
state `""` asks "Sign in with a web browser?" (default true) with the
text `rclone is a third-party application not officially supported by
Proton.`; `"browser"` calls `forkStart`, shows the URL, then
Proton.` (as built: default true only when `username` and `password`
are not both set, and a remote with a saved session is first asked
whether to sign in again, default no - handoff rulings 14 and 17); `"browser"` calls `forkStart`, shows the URL, then
`forkPoll`; `"password"` runs `loginSRP`, asking for a 2FA code through
`fs.ConfigInput` only when needed. Both end by saving the four
`client_*` keys.
@@ -1110,11 +1113,14 @@ Decisions:
- `Object.SetModTime` returns `fs.ErrorCantSetModTime`. `Precision` is
`time.Second`. `Hashes` is `hash.Set(hash.SHA1)`.
- `Rmdir` lists the directory first and returns `fs.ErrorDirectoryNotEmpty`
if it has children; `Purge` of the root returns the existing
if it has children (as built: it asks the server, uncached, whether the
folder has any child at all - handoff ruling 16); `Purge` of the root returns the existing
`errCanNotPurgeRootDirectory`.
- `DirMove` to itself or an existing destination returns
`fs.ErrorDirExists`.
- `Shutdown` calls `client.close()` once (guard with `sync.Once`).
(Superseded: `Shutdown` only drops the node cache and leaves the `Fs`
usable - handoff ruling 15.)
- `go.mod`: `go get github.com/ProtonMail/gopenpgp/v3 github.com/ProtonMail/go-srp`
as direct requirements, remove the bridge imports, `go mod tidy`.
**The differential tests still import the old libraries**, so they remain
@@ -1224,7 +1230,8 @@ Decisions:
`sync.Sync` cold costs at most `2*D + ceil(N/100) + 5` requests for
D = 6, N = 200, and uploads/downloads 0 blocks.
- `TestBudgetNewObject`: `NewObject` three levels deep, cold: at most 2
requests per level + 1.
requests per level + 1. (As built: 8, four folder levels at two
requests each - handoff ruling 20.)
- `TestBudgetNoLegacyRoutes`: after the whole `fstests` run, assert in a
`TestMain` hook that no counted route matches the deprecated patterns.
Record the measured values in a comment next to each bound.
@@ -1253,6 +1260,10 @@ Model the init.d script on `fstest/testserver/init.d/TestWebdavRclone`
allocated in `PORTS.md`, echo its `key=value` lines and
`_connect=127.0.0.1:PORT`.
> Read "Notes for Tasks 18 to 21" in the handoff before starting: this
> text leaves out how the script obtains `protonfake`'s output and manages
> its process, and the commands below need `RCLONE_CONFIG=/notfound`.
- [ ] **Step 1:** Add the script and config entry. Run
`go run ./fstest/test_all -backends protondrive -remotes TestProtonDriveFake: -maxtries 1 -n 4`
and fix backend failures in `fs/operations`, `fs/sync`, `vfs` until the
@@ -1282,6 +1293,10 @@ allocated in `PORTS.md`, echo its `key=value` lines and
`download_internal_test.go` (they need no old-library import).
- Modify: `go.mod`, `go.sum`
> Read "Notes for Tasks 18 to 21" in the handoff before starting: `master`
> is no longer the old backend, `TestFrozenFileFixture` has already moved,
> and `docs/TODO.md` has the accurate list of what to keep and delete.
- [ ] **Step 1:** Record the current binary size:
`git stash -u` is not allowed - instead build `master` in a worktree:
`git worktree add ../rclone-master master && (cd ../rclone-master && go build -o /tmp/rclone-old .)`,
+7 -4
View File
@@ -186,7 +186,10 @@ rclone core -> Fs/Object (protondrive.go)
### 6.1 Authentication and sessions
- **Browser sign-in is the default for new remotes.** `rclone config` runs
- **Browser sign-in is the default for new remotes.** (As built: only when
`username` and `password` are not both set, so scripted setup does not
wait on a browser; and `rclone config` on a remote with a saved session
asks before signing in again.) `rclone config` runs
the session-fork flow used by Proton's official CLI: request a fork, show
the user a URL to open on any device, poll, decrypt the returned key
password. Client ID `external-drive`. Password, 2FA and CAPTCHA are handled
@@ -328,7 +331,7 @@ AES-GCM blocks; names, passphrases, hash keys and attributes without AEAD).
| Operation | Behaviour |
|---|---|
| Mkdir | Create folder; modification time set at creation |
| Rmdir | Trash an empty folder; `fs.ErrorDirNotFound` when missing |
| Rmdir | Trash an empty folder; `fs.ErrorDirNotFound` when missing. Emptiness is asked of the server, uncached, counting every child including ones rclone does not list |
| Remove, Purge | Move to trash, as today (Proton has no direct permanent delete) |
| CleanUp | Empty trash |
| Move, DirMove | Server-side move and rename with re-encryption to the new parent |
@@ -338,8 +341,8 @@ AES-GCM blocks; names, passphrases, hash keys and attributes without AEAD).
| Hashes | SHA1 (claimed digest from extended attributes) |
| Precision | 1 second, unchanged **(call)** |
| Metadata (later milestone) | Read `mtime`, `btime`, `content-type`; write `mtime` and `content-type` at upload |
| Shutdown | Stop the event poller, flush the persistent cache |
| Disconnect | Log out and clear saved session keys |
| Shutdown | Stop the event poller, flush the persistent cache. Never clears keys: the `Fs` stays usable, because rclone's fs cache shuts down entries that rc jobs may still hold |
| Disconnect | Log out and clear saved session keys; later use fails with a "not signed in" error naming the remote |
Feature score target: Hash, Modtime, Stream upload, Copy/Move, DirMove = 5/7
at the first upstream milestone; Metadata makes 6/7.