Skip to content

feat(env): cross-device env-profile exchange (ENV-SYNC-01)#131

Merged
Reederey87 merged 3 commits into
mainfrom
feat/env-sync-01
Jul 6, 2026
Merged

feat(env): cross-device env-profile exchange (ENV-SYNC-01)#131
Reederey87 merged 3 commits into
mainfrom
feat/env-sync-01

Conversation

@Reederey87

@Reederey87 Reederey87 commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Summary

Closes the last killer-loop gap in spec/00: env profiles captured on one device now hydrate on every other device. envRecipients already encrypted captured values to all approved devices' age recipients — this PR adds the missing carrier event and synced metadata.

  • New synced event env.profile.updated (single type for captured + provider-bound profiles), added to mustVerifyEvent — env payloads feed hydrated files and lifecycle-script environments, so they are trust-affecting.
  • Migration 00023: env_profiles source-event coordinates; apply is LWW by the highest (HLC, deviceID, eventID) (same ordering as reconcileSamePath), idempotent on replays; soft-skips an unknown/tombstoned project so a stale env pointer can never abort a pull batch (deliberately unlike the draft handler — that batch-abort is being filed as a follow-up finding).
  • Same-tx emission (P6-DATA-03): env capture / env bind insert the event and upsert the rows in one transaction via the new Tx.UpsertEnvProfileTx; env rotate re-captures therefore propagate automatically.
  • Blob plane: blobRefFromEvent recognizes env payloads — push/pull, content-hash verification, and materialize's EAGER-03 auto-hydrate work unchanged. Hydrate-before-blob-arrival errors now carry a run 'devstrap sync' remedy.
  • Revoke rewrap inversion (P5-SEC-04 rationale retired): env blobs rewrap like draft blobs — hub-fetch fallback, superseding env.profile.updated emitted before hub cleanup, plus a catch-all UpdateBlobRef so bindings on tombstoned entries repoint too.
  • Snapshot plane: SnapshotEnv pointer per entry, merged on import by its own coordinate — a losing entry row can still deliver a winning env pointer — and recoverFromSnapshot pulls imported env blobs alongside draft blobs. Env profiles survive event-log compaction and snapshot bootstrap.

Flips the ENV-SYNC-01 backlog row (spec/14) to shipped; spec/00/07/09/12/13/15/16 reconciled.

Tests

  • Store: TestUpsertEnvProfileTxLWWCoordsStamped, TestEnvProfilesForBlobRef, legacy-wrapper NULL-coord coverage, schema-version bumps (22→23).
  • Sync: apply create/duplicate-idempotent/soft-skip-keeps-batch/two-order LWW convergence; TestSnapshotRoundTripsEnvProfile; TestImportSnapshotEnvLWW (older pointer never regresses; newer pointer wins on a losing entry).
  • CLI: capture-emits-event, blobRefFromEvent matrix, TestRewrapEnvBlobEmitsSupersedingProfileEvent.
  • e2e: env_exchange.txtar — two homes, mutual fingerprint enrollment, capture on A → sync → hydrate on B byte-identical, no conflicts.

Validation

gofmt clean · go build (darwin+linux) · golangci-lint run 0 issues (after cache clean for the known sibling-worktree gosec ghosts) · go test -race ./... all ok · spec-drift --base origin/main passes (29 files).

Implementation: gpt-5.5 (Codex) from a line-level spec; coordinator line-by-line review applied two fixes (tombstoned-binding repoint, unreachable-branch cleanup) and added the snapshot slice. Codex dual review to follow in-thread.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Enabled cross-device environment profile sync with synced hydration via hub-encrypted blobs.
    • env capture/binding now emits environment profile updates so other devices can hydrate consistently.
    • Snapshots now carry environment profile pointers for reliable round-trips and recovery.
  • Bug Fixes
    • Hydration now clearly instructs to run devstrap sync when synced encrypted data is missing.
    • Revoke/lost now rewraps environment blobs correctly and preserves pending hub cleanup safely.
    • Sync more robustly recovers quarantined environment-profile conflicts after event application.
  • Documentation
    • Updated environment and security model docs to reflect the shipped hub sync behavior.

Captured and provider-bound env profiles now sync fleet-wide: a new
env.profile.updated event (mustVerify, enc.v2-sealed) carries the profile
metadata + age_blob ref over the existing blob plane; apply is LWW by
source-event coordinate with soft-skip on unknown/tombstoned projects so a
stale env pointer can never abort a pull batch; capture/bind emit in the
same transaction as the row upsert (P6-DATA-03).

- Migration 00023: env_profiles source-event coordinates.
- Store: Tx.UpsertEnvProfileTx (shared transactional core), source-coord
  reader, EnvProfilesForBlobRef.
- Revoke rewrap inversion (P5-SEC-04 rationale retired): env blobs rewrap
  like draft blobs — superseding env.profile.updated first, then hub
  cleanup; catch-all UpdateBlobRef keeps tombstoned-entry bindings pointed.
- Snapshot plane: SnapshotEnv pointer per entry, merged on import by its
  OWN coordinate (a losing entry row can still deliver a winning env
  pointer); recovery pulls imported env blobs alongside draft blobs — env
  profiles survive event-log compaction and snapshot bootstrap.
- e2e: env_exchange.txtar (two devices, capture on A, hydrate on B).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2b4590a8-58fc-4c42-baa6-d8d11b2441eb

📥 Commits

Reviewing files that changed from the base of the PR and between 0f587c2 and 79352ec.

📒 Files selected for processing (1)
  • spec/00_START_HERE.md
✅ Files skipped from review due to trivial changes (1)
  • spec/00_START_HERE.md

📝 Walkthrough

Walkthrough

This PR adds cross-device environment-profile synchronization: a new env.profile.updated event, transactional env-profile persistence, blob rewrap on revoke/lost, snapshot env pointers, an end-to-end device sync script, and updated specs/tests.

Changes

Env Profile Sync

Layer / File(s) Summary
env.profile.updated event contract and apply logic
internal/sync/events.go, internal/sync/apply_test.go, internal/cli/sync.go, internal/cli/sync_test.go, internal/cli/devices.go
Adds the event type and payload, applies env updates with LWW and pending-project replay, extracts env blob refs from env events, and updates replay flow handling.
Schema and env-profile persistence
internal/state/migrations/00023_env_profile_source_events.sql, internal/state/store.go, internal/state/store_test.go, internal/state/snapshot_reads.go, internal/cli/root_test.go
Adds source-event columns, shared env-profile upsert/query helpers, blob-ref lookup, trust verification updates, snapshot env read support, and schema-version test updates.
CLI env capture, bind, and hydrate
internal/cli/env.go, internal/cli/env_test.go
Persists env capture/bind as env-profile events and reports a sync-required error when hydrated blobs are missing locally.
Hub-aware env blob rewrap and recovery
internal/cli/blob_gc.go, internal/cli/blob_gc_test.go, internal/cli/snapshot_recovery.go, internal/cli/devices.go
Rewraps env blobs through the hub, emits superseding env-profile events, changes cleanup ordering, and pulls env blobs during snapshot recovery.
Snapshot env pointer wiring
internal/sync/snapshot.go, internal/sync/snapshot_build.go, internal/sync/snapshot_import.go, internal/sync/snapshot_import_test.go
Embeds env pointers in snapshots, copies them during build, and imports them with LWW semantics.
End-to-end env exchange scenario
cmd/devstrap/testdata/script/env_exchange.txtar
Adds a two-device capture/sync/hydrate scenario for env profiles.
Spec documentation updates
spec/00_START_HERE.md, spec/07_NAMESPACE_AND_SYNC_MODEL.md, spec/09_SECRETS_AND_ENVIRONMENT.md, spec/12_DATA_MODEL_SQLITE.md, spec/13_CLI_DAEMON_API.md, spec/14_MVP_ROADMAP_AND_BACKLOG.md, spec/15_SECURITY_THREAT_MODEL.md, spec/16_TEST_PLAN.md, spec/18_WORK_LOG.md
Updates shipped-status, data model, sync model, CLI behavior, threat model, test plan, roadmap, and work log docs.

Estimated code review effort: 4 (Complex) | ~75 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI as env capture/bind
  participant State as internal/state
  participant Sync as internal/sync
  participant Hub as hub

  CLI->>State: WithTx insert env profile event + UpsertEnvProfileTx
  CLI->>Hub: sync encrypted blob and event
  Hub->>Sync: deliver env.profile.updated
  Sync->>State: applyEventTx / ReplayPendingEnvProfileConflicts
  Sync->>CLI: hydrate cached blob into .env.local
Loading

Possibly related PRs

  • Reederey87/DevStrap#38: Shares the trust/signature gating path that env.profile.updated now uses during apply and replay.
  • Reederey87/DevStrap#62: Also changes internal/cli/blob_gc.go revoke rewrap behavior and related identity/keystore handling.
  • Reederey87/DevStrap#73: Extends the snapshot recovery path that this PR now uses to pull env blob refs from imported snapshots.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The summary is good, but the required Tests checklist and Safety Checklist sections are missing or not filled in per template. Add the template’s Tests checkbox items and a filled Safety Checklist section, then align the summary formatting to the repository template.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately captures the main change: cross-device env-profile exchange.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/env-sync-01

Comment @coderabbitai help to get the list of available commands.

…event rewrap ordering

Codex review on ENV-SYNC-01 surfaced two correctness gaps:

- A verified env.profile.updated whose project had not applied yet was
  silently consumed (cursor advanced, profile lost even after the project
  later replayed). Apply now tombstone-drops only on a winning delete;
  an absent path without a tombstone quarantines as a replayable
  env_pending_project conflict, and ReplayPendingEnvProfileConflicts
  (after every pull apply + after devices-approve replay) recovers it
  once the project lands.

- rewrapHubCleanup pushed the superseding event before the rewrapped
  blob was durable, so a peer could apply the event and fail to fetch
  the ciphertext it names. Blob now uploads first; a failed event push
  self-heals on the next sync. Also fixes the pre-existing draft-rewrap
  ordering.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Reederey87

Copy link
Copy Markdown
Owner Author

Dual-review record. Coordinator line-by-line review applied two fixes pre-push (tombstoned-binding catch-all UpdateBlobRef in rewrapEnvBlob; unreachable-branch cleanup in UpsertEnvProfileTx). Codex (gpt-5.5) review surfaced two P2s, both fixed in 0f587c2:

  1. Env event consumed before its project exists → apply now tombstone-drops only on a winning delete; an absent path without a tombstone quarantines as a replayable env_pending_project conflict, recovered by ReplayPendingEnvProfileConflicts after every pull apply and after devices approve replay. Pinned by TestApplyEnvProfileEventUnknownProjectQuarantinesWithoutAbort (full quarantine→recovery cycle) and ...TombstonedProjectDrops.
  2. Superseding event visible before the rewrapped blob is durablerewrapHubCleanup now uploads the blob first (also fixes the pre-existing draft-rewrap ordering); a failed event push self-heals on the next sync since superseding events are ordinary local events. Pinned by TestRewrapHubCleanupUploadsBlobBeforeEvent.

Gates re-run on the final tree: gofmt clean, darwin+linux builds, golangci-lint 0 issues, go test -race ./... all ok, spec-drift passes.

@Reederey87 Reederey87 enabled auto-merge (squash) July 5, 2026 23:50

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/cli/blob_gc.go (1)

55-68: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Don't abort the revoke rewrap pass on the first env error. rewrapBlobsOnRevoke returns immediately when any rewrapEnvBlob call fails, so a transient failure in emitSupersedingEnvProfile or UpdateBlobRef can stop the draft loop entirely and leave draft blobs unrewrapped under the revoked key. Continue through both loops, collect the first error, and return it after processing everything.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cli/blob_gc.go` around lines 55 - 68, The revoke rewrap pass in
rewrapBlobsOnRevoke currently stops at the first failure from rewrapEnvBlob,
which can leave later draft and env blobs unprocessed. Update the EnvBlobRefs
loop (and keep the draft loop behavior aligned) so failures from
emitSupersedingEnvProfile or UpdateBlobRef are recorded but do not return
immediately; continue processing all refs, track the first error encountered,
and return that error after all rewrap attempts finish.
🧹 Nitpick comments (3)
internal/cli/env.go (1)

79-107: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consolidate duplicated payload/params construction.

captureEnvProfile (Lines 82-99) and newEnvBindCommand (Lines 383-398) both build an almost-identical dssync.EnvProfilePayload and state.EnvProfileParams pair from the same fields (Name/Profile, Provider, Mode, BlobRef/VarNames or Refs). The same pattern is repeated a third time in emitSupersedingEnvProfile (internal/cli/blob_gc.go, Lines 234-260). Any future field addition/rename risks the wire payload and persisted params drifting out of sync since they're maintained by hand in three call sites.

Consider a small helper (e.g. newEnvProfileEventAndParams(...) (dssync.EnvProfilePayload, state.EnvProfileParams)) that derives both from one input, and have all three sites call it.

Also applies to: 380-406

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cli/env.go` around lines 79 - 107, `captureEnvProfile`,
`newEnvBindCommand`, and `emitSupersedingEnvProfile` duplicate construction of
matching `dssync.EnvProfilePayload` and `state.EnvProfileParams`, which can
drift over time. Refactor the shared field mapping into a small helper such as
`newEnvProfileEventAndParams(...)` that derives both objects from one input
source, and update each call site to use it so payload and persisted params stay
in sync.
internal/sync/snapshot_import_test.go (1)

317-445: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Solid LWW coverage; consider adding a provider-profile (Refs) snapshot case.

Both new tests only exercise the devstrap_encrypted shape (VarNames/BlobRef). A round-trip/LWW test using the provider-ref shape (Refs map, runtime_only mode) would close the coverage gap for importEnvTx/UpsertEnvProfileTx's other branch.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/sync/snapshot_import_test.go` around lines 317 - 445, Add a snapshot
round-trip/LWW test for the provider-ref env profile shape in addition to the
existing devstrap_encrypted coverage. Extend the snapshot import tests around
TestSnapshotRoundTripsEnvProfile and TestImportSnapshotEnvLWW to build an Env
profile using Refs with runtime_only mode, then verify BuildSnapshot and
ImportSnapshot preserve it and that the env pointer LWW behavior still works
through importEnvTx and UpsertEnvProfileTx. Ensure the new case asserts the refs
survive import/export and idempotent re-import.
internal/state/store.go (1)

1787-1816: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Inconsistent "unsynced" definition vs. snapshotEnvForProject.

EnvProfileSourceCoords treats a profile as having no source coordinates only when all three of source_event_hlc/device_id/id are NULL/empty (AND):

if !nHLC.Valid && (!nDeviceID.Valid || nDeviceID.String == "") && (!nEventID.Valid || nEventID.String == "") {
	return 0, "", "", false, nil
}

internal/state/snapshot_reads.go's snapshotEnvForProject treats it as unsynced when any one of the three is missing/empty (OR):

if !nHLC.Valid || !nDev.Valid || nDev.String == "" || !nEvt.Valid || nEvt.String == "" {
	return nil, nil
}

UpsertEnvProfileTx stamps all three columns together only when event.HLC != 0 || event.DeviceID != "" || event.ID != "" is true — so a partially-populated Event (e.g. HLC=0 but DeviceID/ID set) would produce non-NULL-but-empty/zero values in one or two columns. In that scenario, EnvProfileSourceCoords would report ok=true (using the zero/empty value in the LWW compare), while snapshotEnvForProject would treat the same row as never-synced and drop the env pointer from snapshots. Since both are meant to express the identical "has real source-event coordinates" concept for the same LWW/snapshot exchange, this divergence is a latent correctness risk if a caller ever passes a partially-stamped Event (current callers pass either a fully-stamped event or Event{}, so it isn't triggered today, but it's a fragile invariant).

Consider extracting a single shared predicate (e.g. hasSourceCoords(hlc sql.NullInt64, dev, evt sql.NullString) bool) and using it in both places.

♻️ Suggested consolidation
+func hasSourceCoords(hlc sql.NullInt64, dev, evt sql.NullString) bool {
+	return hlc.Valid && dev.Valid && dev.String != "" && evt.Valid && evt.String != ""
+}
+
 func (tx *Tx) EnvProfileSourceCoords(ctx context.Context, namespaceID string) (hlc int64, deviceID, eventID string, ok bool, err error) {
 	...
-	if !nHLC.Valid && (!nDeviceID.Valid || nDeviceID.String == "") && (!nEventID.Valid || nEventID.String == "") {
+	if !hasSourceCoords(nHLC, nDeviceID, nEventID) {
 		return 0, "", "", false, nil
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/state/store.go` around lines 1787 - 1816, `EnvProfileSourceCoords`
and `snapshotEnvForProject` use different rules for deciding whether an env
profile has valid source coordinates, which can make the same row be treated as
synced in one path and unsynced in the other. Align the logic in
`EnvProfileSourceCoords`, `snapshotEnvForProject`, and the `UpsertEnvProfileTx`
stamping flow by introducing one shared helper/predicate for “has real
source-event coordinates” and using it everywhere. Make the helper reflect the
intended invariant for `source_event_hlc`, `source_event_device_id`, and
`source_event_id`, so both read and snapshot paths make the same decision.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@spec/00_START_HERE.md`:
- Line 159: The shipped list in the START_HERE overview is overstating the
feature set by including production remote device registration. Update the
bullet to match the later env/security docs by removing remote registration and
keeping only the fingerprint-confirmation/manual-approval path, while preserving
references to the local trust plane, synced encrypted env/draft bundle exchange,
and device-revoke rewrap if they are still shipped.

In `@spec/16_TEST_PLAN.md`:
- Around line 279-287: Narrow the “sync” test-plan bullet in Env profile sync so
it only claims the behavior already covered by the implementation and work log.
Update the wording in the relevant test-plan entry to keep the unknown-project
path limited to the soft-skip behavior, and remove any implication that
batch-abort handling is included unless you split it into a separate bullet tied
to the sync flow. Refer to the existing env.profile.updated and unknown-project
cases when rewriting the scope.

---

Outside diff comments:
In `@internal/cli/blob_gc.go`:
- Around line 55-68: The revoke rewrap pass in rewrapBlobsOnRevoke currently
stops at the first failure from rewrapEnvBlob, which can leave later draft and
env blobs unprocessed. Update the EnvBlobRefs loop (and keep the draft loop
behavior aligned) so failures from emitSupersedingEnvProfile or UpdateBlobRef
are recorded but do not return immediately; continue processing all refs, track
the first error encountered, and return that error after all rewrap attempts
finish.

---

Nitpick comments:
In `@internal/cli/env.go`:
- Around line 79-107: `captureEnvProfile`, `newEnvBindCommand`, and
`emitSupersedingEnvProfile` duplicate construction of matching
`dssync.EnvProfilePayload` and `state.EnvProfileParams`, which can drift over
time. Refactor the shared field mapping into a small helper such as
`newEnvProfileEventAndParams(...)` that derives both objects from one input
source, and update each call site to use it so payload and persisted params stay
in sync.

In `@internal/state/store.go`:
- Around line 1787-1816: `EnvProfileSourceCoords` and `snapshotEnvForProject`
use different rules for deciding whether an env profile has valid source
coordinates, which can make the same row be treated as synced in one path and
unsynced in the other. Align the logic in `EnvProfileSourceCoords`,
`snapshotEnvForProject`, and the `UpsertEnvProfileTx` stamping flow by
introducing one shared helper/predicate for “has real source-event coordinates”
and using it everywhere. Make the helper reflect the intended invariant for
`source_event_hlc`, `source_event_device_id`, and `source_event_id`, so both
read and snapshot paths make the same decision.

In `@internal/sync/snapshot_import_test.go`:
- Around line 317-445: Add a snapshot round-trip/LWW test for the provider-ref
env profile shape in addition to the existing devstrap_encrypted coverage.
Extend the snapshot import tests around TestSnapshotRoundTripsEnvProfile and
TestImportSnapshotEnvLWW to build an Env profile using Refs with runtime_only
mode, then verify BuildSnapshot and ImportSnapshot preserve it and that the env
pointer LWW behavior still works through importEnvTx and UpsertEnvProfileTx.
Ensure the new case asserts the refs survive import/export and idempotent
re-import.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7d627da1-2fff-45f2-9e0a-43bb500f0445

📥 Commits

Reviewing files that changed from the base of the PR and between b07e791 and 2cf64c6.

📒 Files selected for processing (29)
  • cmd/devstrap/testdata/script/env_exchange.txtar
  • internal/cli/blob_gc.go
  • internal/cli/blob_gc_test.go
  • internal/cli/devices.go
  • internal/cli/env.go
  • internal/cli/env_test.go
  • internal/cli/root_test.go
  • internal/cli/snapshot_recovery.go
  • internal/cli/sync.go
  • internal/cli/sync_test.go
  • internal/state/migrations/00023_env_profile_source_events.sql
  • internal/state/snapshot_reads.go
  • internal/state/store.go
  • internal/state/store_test.go
  • internal/sync/apply_test.go
  • internal/sync/events.go
  • internal/sync/snapshot.go
  • internal/sync/snapshot_build.go
  • internal/sync/snapshot_import.go
  • internal/sync/snapshot_import_test.go
  • spec/00_START_HERE.md
  • spec/07_NAMESPACE_AND_SYNC_MODEL.md
  • spec/09_SECRETS_AND_ENVIRONMENT.md
  • spec/12_DATA_MODEL_SQLITE.md
  • spec/13_CLI_DAEMON_API.md
  • spec/14_MVP_ROADMAP_AND_BACKLOG.md
  • spec/15_SECURITY_THREAT_MODEL.md
  • spec/16_TEST_PLAN.md
  • spec/18_WORK_LOG.md

Comment thread spec/00_START_HERE.md Outdated
Comment thread spec/16_TEST_PLAN.md

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
internal/cli/blob_gc_test.go (1)

118-137: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the cleanup step on the success path.
This event-bearing case already checks PutBlob -> Push; adding hub.Deletes == 1 here would also lock in the final cleanup order for this scenario.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cli/blob_gc_test.go` around lines 118 - 137, The event-bearing
cleanup test only asserts that rewrapHubCleanup calls PutBlob before Push and
keeps old ciphertext on push failure, but it does not verify the success-path
cleanup. Update TestRewrapHubCleanupUploadsBlobBeforeEvent to also assert that
the successful run on faultHub performs exactly one DeleteBlob after the push,
using the existing rewrapHubCleanup and faultHub symbols to lock in the final
cleanup order.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@internal/cli/blob_gc_test.go`:
- Around line 118-137: The event-bearing cleanup test only asserts that
rewrapHubCleanup calls PutBlob before Push and keeps old ciphertext on push
failure, but it does not verify the success-path cleanup. Update
TestRewrapHubCleanupUploadsBlobBeforeEvent to also assert that the successful
run on faultHub performs exactly one DeleteBlob after the push, using the
existing rewrapHubCleanup and faultHub symbols to lock in the final cleanup
order.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c374ce9b-89e5-45b1-9ed1-c07f5fbf6b00

📥 Commits

Reviewing files that changed from the base of the PR and between 2cf64c6 and 0f587c2.

📒 Files selected for processing (9)
  • internal/cli/blob_gc.go
  • internal/cli/blob_gc_test.go
  • internal/cli/devices.go
  • internal/cli/sync.go
  • internal/sync/apply_test.go
  • internal/sync/events.go
  • spec/09_SECRETS_AND_ENVIRONMENT.md
  • spec/16_TEST_PLAN.md
  • spec/18_WORK_LOG.md
✅ Files skipped from review due to trivial changes (2)
  • spec/16_TEST_PLAN.md
  • spec/18_WORK_LOG.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • internal/cli/blob_gc.go
  • spec/09_SECRETS_AND_ENVIRONMENT.md

…cal (CodeRabbit)

The bullet lists what is NOT implemented; out-of-band fingerprint
confirmation shipped with P4-SEC-04 and belongs in the shipped list.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant