Ensure a root cannot be clobbered #3

Closed
opened 2026-05-26 20:26:08 +00:00 by erik · 6 comments
Owner

A single bucket can serve multiple roots, which should be fine as long as the init command fails if there is already an initialized root on the bucket.

If this functionality already exists, then make sure it's documented in the user ./doc/ somewhere.

A single bucket can serve multiple roots, which should be fine as long as the init command fails if there is already an initialized root on the bucket. If this functionality already exists, then make sure it's documented in the user ./doc/ somewhere.
Member

Brief — Developer

Phase: simple. Run fjx dev prompt simple for the cycle protocol.

<!-- pm:brief:dev --> # Brief — Developer **Phase:** simple. Run `fjx dev prompt simple` for the cycle protocol.
Member

Ledger for: agent-pm

Status: closed @ 2026-06-04T23:38:38.842Z
Briefs:

Activity

  1. i3/closed : applied

(PM manages this ledger; agent section unused)

<!-- agent-pm:ledger --> ## Ledger for: agent-pm Status: closed @ 2026-06-04T23:38:38.842Z Briefs: - [dev](#issuecomment-2324) - [qa](#issuecomment-2498) ### Activity 1. i3/closed : applied --- _(PM manages this ledger; agent section unused)_
Member

Ledger for: agent-dev

Status: review @ 2026-06-03T16:33:37.406Z
Refs: #13


Tasks

  • Read issue #3 and simple-phase prompt.
  • Inspect current branch diff for root clobber protection.
  • Implement unsafe-root bootstrap guard in dropbear register.
  • Validate implementation with project gates.
  • Open PR and hand off for review.

Evidence

  • Commit: 73361e9 refuse unsafe root bootstrap.
  • PR: #13.
  • Validation: just validate passed under Go 1.26.4 with writable external caches and test temp dirs configured for POSIX permission semantics.

Notes

  • dropbear register now refuses to initialize a root when objects already exist under the root prefix and devices/registry.json is absent.
  • Existing roots with devices/registry.json remain joinable; restore fixtures now seed that sentinel where they model initialized remote roots.
<!-- agent-dev:ledger --> ## Ledger for: agent-dev Status: review @ 2026-06-03T16:33:37.406Z Refs: #13 --- ### Tasks - [x] Read issue #3 and simple-phase prompt. - [x] Inspect current branch diff for root clobber protection. - [x] Implement unsafe-root bootstrap guard in `dropbear register`. - [x] Validate implementation with project gates. - [x] Open PR and hand off for review. ### Evidence - Commit: `73361e9 refuse unsafe root bootstrap`. - PR: #13. - Validation: `just validate` passed under Go 1.26.4 with writable external caches and test temp dirs configured for POSIX permission semantics. ### Notes - `dropbear register` now refuses to initialize a root when objects already exist under the root prefix and `devices/registry.json` is absent. - Existing roots with `devices/registry.json` remain joinable; restore fixtures now seed that sentinel where they model initialized remote roots.
Author
Owner

I fixed the issue blocking agent-dev. Try again.

I fixed the issue blocking agent-dev. Try again.
agent-dev removed their assignment 2026-06-03 15:59:31 +00:00
erik removed their assignment 2026-06-03 16:15:05 +00:00
agent-dev removed their assignment 2026-06-03 16:33:56 +00:00
Member

Brief - QA

Phase: default. Run fjx qa prompt default for the cycle protocol.

Review PR #13 for issue #3. The change should make dropbear register refuse to bootstrap a root when objects already exist under the configured root prefix but devices/registry.json is absent.

Focus on:

  • Confirming the guard runs before local root config is written.
  • Confirming initialized roots with devices/registry.json still join normally.
  • Checking restore/clean fixture changes only add the registry sentinel where they model initialized remote roots.
<!-- pm:brief:qa --> # Brief - QA **Phase:** default. Run `fjx qa prompt default` for the cycle protocol. Review PR #13 for issue #3. The change should make `dropbear register` refuse to bootstrap a root when objects already exist under the configured root prefix but `devices/registry.json` is absent. Focus on: - Confirming the guard runs before local root config is written. - Confirming initialized roots with `devices/registry.json` still join normally. - Checking restore/clean fixture changes only add the registry sentinel where they model initialized remote roots.
agent-pm removed their assignment 2026-06-03 16:35:15 +00:00
Member

Ledger for: agent-qa

Status: fail @ 2026-06-04T06:12:03.688Z


Action Summary

Run Trigger Status
#20 (run 373) pull_request failure
#19 (run 372) push failure

Both runs are on commit 73361e9 (42–44 s execution each). Both fail — this is not an infra flake (different trigger types, same outcome).

Per-step log access: fjx qa checks returns run-level metadata only; there is no fjx subcommand for step logs. WebFetch of the html_url (https://git.tfks.net/tfks/dropbear/actions/runs/20) returned no useful content — private Forgejo instance requires authenticated session. Step-level failure cannot be quoted verbatim; classification relies on local reproduction and diff analysis below.

Gap Analysis

The validate workflow runs: secrets → format → vet → lint → units → audit → build. Local execution against this branch:

Step Local result Notes
secrets pass gitleaks: no leaks
format pass gofmt -s clean
vet pass go vet ./... clean
lint panic (local env) golangci-lint compiled with go1.25, local Go runtime is go1.26.3 — version mismatch panic
units pass 750 tests, race detector, all pass
audit exit 3 (local env) govulncheck finds go1.26.3 stdlib CVEs fixed in go1.26.4; not applicable to go1.25.10
build pass binary compiles cleanly

Critical finding — Go version mismatch: The PR description states validation was run with GOTOOLCHAIN=go1.26.4. The CI workflow specifies GO_VERSION: "1.25.10", matching the go.mod (go 1.25.4 / toolchain go1.25.10). The lint step is the most likely CI failure point: golangci-lint on the runner may have a similar Go version mismatch in the opposite direction, or the runner's golangci-lint binary itself has compatibility issues with the Go version installed by setup-go.

Code Review

The diff is clean and correct for what it does:

EnsureRootNotInitialized logic (internal/deviceregistry/registry.go:271): Reads registry first; if it exists → safe, return nil. If not-found error → lists root prefix; returns ErrRootAlreadyInitialized on first non-registry object. Correct early-return pattern.

Guard placement (cmd/dropbear/register.go:113): EnsureRootNotInitialized is called after the object store is constructed but before deviceregistry.Register and before config.Init. This satisfies the brief's requirement that the guard runs before local root config is written.

clean_test.go fix: Correctly replaced runRegister(...) with config.Init(...) to bypass the new guard in tests that seed remote state before calling runRegister — the register guard would otherwise reject the pre-seeded fake state. The test intent (clean deletes root prefix) is unchanged.

Fixture updates (restore_test.go, run_test.go): seedRegistry calls added so tests that model a joinable remote root include the registry sentinel. Correct.

RootPrefix function (internal/objectstore/keys.go): Returns join(prefix, "roots", rootID) + "/". Consistent with existing key format for DeviceRegistryKey (<prefix>/roots/<rootID>/devices/registry.json). Table tests cover empty prefix and slash-wrapped prefix.

Tests that were not written (gaps):

  • No test for EnsureRootNotInitialized when store.List returns an error (non-ErrNotFound) on the listing step — the error path at line ~284 is untested.
  • No integration-level test verifying the guard runs before config.Init writes .dropbear/TestRegisterRefusesExistingRootStateWithoutRegistry does check os.Stat(.dropbear) is absent, which covers this indirectly.
  • EnsureRootNotInitialized with an empty root prefix (prefix="") is not tested, though RootPrefix is tested for that case.

Verdict

fail — CI failed on both runs (PR and push triggers); consistent failure is not an infra flake. The developer validated against Go 1.26.4 while CI requires Go 1.25.10 (per go.mod and workflow GO_VERSION). The exact failing step is not readable (see gap analysis), but the Go toolchain discrepancy is the most probable root cause.

Code logic is sound and would pass review if CI is fixed. Agent-dev should re-run just validate under Go 1.25.10 (matching CI) and fix whatever step fails there.

<!-- agent-qa:ledger --> ## Ledger for: agent-qa Status: fail @ 2026-06-04T06:12:03.688Z --- ## Action Summary | Run | Trigger | Status | |-----|---------|--------| | #20 (run 373) | pull_request | **failure** | | #19 (run 372) | push | **failure** | Both runs are on commit `73361e9` (42–44 s execution each). Both fail — this is not an infra flake (different trigger types, same outcome). Per-step log access: `fjx qa checks` returns run-level metadata only; there is no `fjx` subcommand for step logs. `WebFetch` of the `html_url` (`https://git.tfks.net/tfks/dropbear/actions/runs/20`) returned no useful content — private Forgejo instance requires authenticated session. Step-level failure cannot be quoted verbatim; classification relies on local reproduction and diff analysis below. ## Gap Analysis The validate workflow runs: secrets → format → vet → lint → units → audit → build. Local execution against this branch: | Step | Local result | Notes | |------|-------------|-------| | secrets | pass | gitleaks: no leaks | | format | pass | gofmt -s clean | | vet | pass | go vet ./... clean | | lint | **panic** (local env) | golangci-lint compiled with go1.25, local Go runtime is go1.26.3 — version mismatch panic | | units | pass | 750 tests, race detector, all pass | | audit | **exit 3** (local env) | govulncheck finds go1.26.3 stdlib CVEs fixed in go1.26.4; not applicable to go1.25.10 | | build | pass | binary compiles cleanly | **Critical finding — Go version mismatch:** The PR description states validation was run with `GOTOOLCHAIN=go1.26.4`. The CI workflow specifies `GO_VERSION: "1.25.10"`, matching the go.mod (`go 1.25.4 / toolchain go1.25.10`). The lint step is the most likely CI failure point: golangci-lint on the runner may have a similar Go version mismatch in the opposite direction, or the runner's golangci-lint binary itself has compatibility issues with the Go version installed by setup-go. ## Code Review The diff is clean and correct for what it does: **`EnsureRootNotInitialized` logic** (`internal/deviceregistry/registry.go:271`): Reads registry first; if it exists → safe, return nil. If not-found error → lists root prefix; returns `ErrRootAlreadyInitialized` on first non-registry object. Correct early-return pattern. **Guard placement** (`cmd/dropbear/register.go:113`): `EnsureRootNotInitialized` is called after the object store is constructed but **before** `deviceregistry.Register` and before `config.Init`. This satisfies the brief's requirement that the guard runs before local root config is written. **`clean_test.go` fix**: Correctly replaced `runRegister(...)` with `config.Init(...)` to bypass the new guard in tests that seed remote state before calling `runRegister` — the register guard would otherwise reject the pre-seeded fake state. The test intent (clean deletes root prefix) is unchanged. **Fixture updates** (restore_test.go, run_test.go): `seedRegistry` calls added so tests that model a joinable remote root include the registry sentinel. Correct. **`RootPrefix` function** (`internal/objectstore/keys.go`): Returns `join(prefix, "roots", rootID) + "/"`. Consistent with existing key format for `DeviceRegistryKey` (`<prefix>/roots/<rootID>/devices/registry.json`). Table tests cover empty prefix and slash-wrapped prefix. **Tests that were not written (gaps):** - No test for `EnsureRootNotInitialized` when `store.List` returns an error (non-ErrNotFound) on the listing step — the error path at line ~284 is untested. - No integration-level test verifying the guard runs before `config.Init` writes `.dropbear/` — `TestRegisterRefusesExistingRootStateWithoutRegistry` does check `os.Stat(.dropbear)` is absent, which covers this indirectly. - `EnsureRootNotInitialized` with an empty root prefix (prefix="") is not tested, though `RootPrefix` is tested for that case. ## Verdict **fail** — CI failed on both runs (PR and push triggers); consistent failure is not an infra flake. The developer validated against Go 1.26.4 while CI requires Go 1.25.10 (per go.mod and workflow `GO_VERSION`). The exact failing step is not readable (see gap analysis), but the Go toolchain discrepancy is the most probable root cause. Code logic is sound and would pass review if CI is fixed. Agent-dev should re-run `just validate` under Go 1.25.10 (matching CI) and fix whatever step fails there.
agent-qa removed their assignment 2026-06-04 06:12:27 +00:00
Sign in to join this conversation.
No description provided.