The unit-file chain climb stops at the platform's own directory
Where a database's owner is the platform's user, the climb from the file's directory had nowhere to stop short of the filesystem root, and CI (and the gate under /var/tmp) refused every unit route's named file. It now stops at the first parent that is root's or this process's user's, which is then required to be the platform's, as before. A tenant with its own user is checked exactly as it was.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2 files changed,
+12
−4
docs/superpowers/specs/DISPOSITIONS-metered-replicator.md+4 −0internal/ops/litestream_owners.go+8 −4
CI passed — run details
diff --git a/docs/superpowers/specs/DISPOSITIONS-metered-replicator.md b/docs/superpowers/specs/DISPOSITIONS-metered-replicator.md| index 07406e6f1..4a5602cbb 100644 |
| --- a/docs/superpowers/specs/DISPOSITIONS-metered-replicator.md |
| +++ b/docs/superpowers/specs/DISPOSITIONS-metered-replicator.md |
| @@ -98,3 +98,7 @@ Reports: `OPUS-REVIEW-metered-replicator-review3.md`, `CODEX-FINDINGS-metered-re |
| The fourth build is not read by a reviewer. The branch stays unwired and unattested, and item 1 of OUTSTANDING.md stays a launch blocker: nothing on any box can yet prove its replication writes. |
| + |
| +### CI on the fourth build (b53f2a93) |
| + |
| +Amadan CI failed on `b53f2a93`: `TestFleetDBOwnersNamesAnOwnerOrSaysThereIsNone` and `TestFleetDBOwnersCheckAUnitRoutesNamedFileLikeATree`, with "/ belongs to a user other than root or the platform's". The unit-file chain climb (`checkOwnedFile`, kind `tenantFile`) climbed while each parent belonged to the file's owner; where the file's owner is the platform's own user (every test, a single-user box) that climbs past the fixture to wherever the temp root's chain stops, which on the runner is `/` under a user namespace, and locally depends on `TMPDIR` (it passed under `~/.cache`, where the chain stops at `/home`, and fails under `/tmp`). The local gate on `b53f2a93` (`gate-replicator-b53f2a93.txt`, `TMPDIR` under `/var/tmp/ck`) failed the same way, so the fourth build's "ops tests pass" claim above held only for one temp root. Fixed in the next commit: the climb also stops at the first parent that is the platform's (root's or this process's user's), which is then required to be platform-owned as before. For a tenant whose user is not the platform's nothing changes: the chain still ends at the first directory that is not the tenant's, and that directory must be the platform's. The ops tests now pass under both `/tmp` and `~/.cache`. Rule kept for the morning: a package test must be run under the gate's temp root, not only the sandbox's, before its result is written down. |
diff --git a/internal/ops/litestream_owners.go b/internal/ops/litestream_owners.go| index f57e32f1d..4a987e492 100644 |
| --- a/internal/ops/litestream_owners.go |
| +++ b/internal/ops/litestream_owners.go |
| @@ -306,10 +306,14 @@ func checkOwnedFile(root, path string, kind claimKind) (fileFacts, string) { |
| case tenantFile: |
| // The root is not the file's directory but the top of the tenant's |
| // own chain: climb from the file's directory while the directory |
| - // above is the same user's and not a link. What is above that must |
| - // be the platform's, and nothing from the top of the chain down may |
| - // be a link, the top included (an operator's link is allowed for a |
| + // above is the same user's, is not the platform's (root's or this |
| + // process's user's), and is not a link. What is above that must be |
| + // the platform's, and nothing from the top of the chain down may be |
| + // a link, the top included (an operator's link is allowed for a |
| // tree claim's root, which the route names; this root is inferred). |
| + // Stopping at the platform's own directory matters where the file's |
| + // owner is the platform's user (tests, a single-user box): the |
| + // chain would otherwise climb to a directory nobody here owns. |
| owner, lerr := os.Lstat(path) |
| if lerr != nil || !owner.Mode().IsRegular() { |
| return fileFacts{}, "it is not a regular file" |
| @@ -328,7 +332,7 @@ func checkOwnedFile(root, path string, kind claimKind) (fileFacts, string) { |
| return fileFacts{}, "a directory above it cannot be looked at" |
| } |
| pf, ok := factsOf(pfi) |
| - if !ok || pfi.Mode()&os.ModeSymlink != 0 || !pfi.IsDir() || pf.UID != ownerFacts.UID { |
| + if !ok || pfi.Mode()&os.ModeSymlink != 0 || !pfi.IsDir() || pf.UID != ownerFacts.UID || pf.UID == 0 || int(pf.UID) == os.Geteuid() { |
| break |
| } |
| root = parent |