Skip to content

refactor(store): wave B.19 — lps + luks repos (#281)#282

Merged
PaulDotterer merged 1 commit into
mainfrom
feat/281-lps-luks-secalert-repos
May 16, 2026
Merged

refactor(store): wave B.19 — lps + luks repos (#281)#282
PaulDotterer merged 1 commit into
mainfrom
feat/281-lps-luks-secalert-repos

Conversation

@PaulDotterer

@PaulDotterer PaulDotterer commented May 15, 2026

Copy link
Copy Markdown
Contributor

Summary

Two repos for the secret-rotation domains:

`LpsRepo` (lps_passwords_projection): `ListCurrent`, `ListHistory`.

`LuksRepo` (luks_keys_projection + luks_tokens):

  • `ListCurrent` / `ListHistory`
  • `GetCurrentForAction` — gateway proxy retrieval
  • `CreateToken` / `ConsumeToken` — one-shot mint+burn
  • `GetRevocationStreamID` — inbox worker appends terminal events to the original revocation stream

Decrypted secrets cross the boundary as plain strings — treat as sensitive in callers.

Non-goals

  • SecurityAlertRepo — only callers are the projector listener (Insert / Acknowledge). Pure projector internals; stays on `Queries()` until the projector-write wave (F).
  • `UpdateLuksKeyRevocationStatus` — projector listener write.

Call sites migrated

  • `device_handler.go` — LPS + LUKS reads + CreateToken
  • `internal_handler.go` — GetCurrentForAction ×2 + ConsumeToken
  • `control/inbox_worker.go` — GetRevocationStreamID

Acceptance

  • Build / vet / staticcheck / gofmt: clean.
  • Tests pass: api (40s).

Part of #242. Closes #281.

Summary by CodeRabbit

  • Refactor
    • Switched device password and encryption-key access to a repository-layer architecture, consolidating read/write paths while preserving existing behavior.
  • New Features
    • Added backend repositories for password rotation and encrypted-key management, including one-time token minting and consumption and Postgres-backed implementations.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 15, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: bd01462c-1603-4445-8318-4fab0a5977b4

📥 Commits

Reviewing files that changed from the base of the PR and between 4ca091f and e3c4c3a.

📒 Files selected for processing (9)
  • internal/api/device_handler.go
  • internal/api/internal_handler.go
  • internal/control/inbox_worker.go
  • internal/store/lps.go
  • internal/store/luks.go
  • internal/store/postgres/lps.go
  • internal/store/postgres/luks.go
  • internal/store/postgres/wire.go
  • internal/store/repos.go
🚧 Files skipped from review as they are similar to previous changes (9)
  • internal/control/inbox_worker.go
  • internal/api/internal_handler.go
  • internal/store/repos.go
  • internal/store/postgres/wire.go
  • internal/store/lps.go
  • internal/api/device_handler.go
  • internal/store/postgres/lps.go
  • internal/store/postgres/luks.go
  • internal/store/luks.go

📝 Walkthrough

Walkthrough

The PR adds LPS and LUKS repository contracts and Postgres implementations, wires them into Repos, and migrates handlers to call the new repo APIs for listing current/history records, action-targeted LUKS retrieval, token create/consume, and revocation stream lookup.

Changes

LPS and LUKS repository migration

Layer / File(s) Summary
Store layer contracts and data models
internal/store/lps.go, internal/store/luks.go
LpsPassword struct and LpsRepo interface for password history/current retrieval; LuksKey, LuksToken structs and LuksRepo interface for key listing, action-targeted retrieval, token creation/consumption, and revocation stream lookups.
LPS PostgreSQL repository implementation
internal/store/postgres/lps.go
Lps wrapper and constructor bind sqlc queries; ListCurrent and ListHistory call the underlying queries, wrap errors, and map rows to store.LpsPassword via lpsFromRow.
LUKS PostgreSQL repository implementation
internal/store/postgres/luks.go
Luks wrapper and constructor bind sqlc queries; methods implement list, retrieval, token mint/consume, and revocation stream lookup; all translate errors and map projection/row types to store.LuksKey and store.LuksToken.
Repository wiring into Repos aggregation
internal/store/repos.go, internal/store/postgres/wire.go
Repos struct gains Lps and Luks fields; NewRepos initializes both via NewLps(q) and NewLuks(q).
API and control flow migrations
internal/api/device_handler.go, internal/api/internal_handler.go, internal/control/inbox_worker.go
Call sites switched from Queries() to Repos() APIs: Lps.ListCurrent/ListHistory, Luks.ListCurrent/ListHistory/GetCurrentForAction, Luks.CreateToken/ConsumeToken, and Luks.GetRevocationStreamID. Downstream decryption, error mapping, and response assembly are unchanged.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 I hopped through repos, tidy and bright,
LPS and LUKS now tucked in tight,
Tokens minted, keys in line,
Queries swapped for interfaces fine,
A little rabbit cheers the refactor tonight.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title clearly summarizes the main change: introducing LPS and LUKS repository abstractions as part of the refactoring wave B.19.
Linked Issues check ✅ Passed All requirements from #281 are met: LpsRepo and LuksRepo interfaces implemented with required methods, call sites migrated, and secrets returned as plain strings.
Out of Scope Changes check ✅ Passed All changes directly support the stated objectives; SecurityAlertRepo and UpdateLuksKeyRevocationStatus correctly remain on Queries() as specified.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/281-lps-luks-secalert-repos

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

@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

🧹 Nitpick comments (1)
internal/store/luks.go (1)

10-41: ⚡ Quick win

Add redaction guardrails for secret-bearing structs.

LuksKey.Passphrase and LuksToken.Token are plaintext by design, but these structs can be leaked via generic struct logging. Consider adding redacted String() implementations (and using them in logs) to reduce accidental secret exposure.

🔒 Example hardening
+func (k LuksKey) String() string {
+	return fmt.Sprintf("LuksKey{ID:%s DeviceID:%q ActionID:%q DevicePath:%q Passphrase:[REDACTED] IsCurrent:%t}",
+		k.ID, k.DeviceID, k.ActionID, k.DevicePath, k.IsCurrent)
+}
+
+func (t LuksToken) String() string {
+	return fmt.Sprintf("LuksToken{ID:%s DeviceID:%q ActionID:%q Token:[REDACTED] Used:%t ExpiresAt:%s}",
+		t.ID, t.DeviceID, t.ActionID, t.Used, t.ExpiresAt.Format(time.RFC3339))
+}
🤖 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/store/luks.go` around lines 10 - 41, LuksKey and LuksToken contain
plaintext secrets (LuksKey.Passphrase and LuksToken.Token) and can be
accidentally exposed by generic struct logging; implement redacted String()
methods for type LuksKey and LuksToken that return the struct representation
with Passphrase and Token replaced by a constant redaction marker (e.g.,
"<redacted>") and ensure any existing log sites use the String() method (or
fmt.Stringer) rather than printing the raw struct; update tests or add unit
tests asserting String() output hides secrets and preserves other fields.
🤖 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 `@internal/api/device_handler.go`:
- Line 872: The CreateLuksToken call writes token state directly without
recording an event; change the handler to append an event after (or instead of)
the direct mutation by invoking store.AppendEvent with an event type like
"luks.token.created" and include the token details (DeviceID, ActionID, Token,
MinLength, Complexity) and actor/metadata from the request/context, then persist
the mutation through the repository only via the normal event-driven path (or
ensure the repo change and AppendEvent are atomic/inside the same transaction).
Update the code around h.store.Repos().Luks.CreateToken and ensure
store.AppendEvent is called (or used as the primary mutation) so every token
minting follows the required event-append contract.

In `@internal/api/internal_handler.go`:
- Line 248: The handler currently calls h.store.Repos().Luks.ConsumeToken(...)
which mutates durable state but does not append an event; update the handler so
that after a successful call to ConsumeToken (store.ConsumeLuksTokenParams /
h.store.Repos().Luks.ConsumeToken) you also call h.store.AppendEvent(...) to
record the mutation (e.g., an event like "LuksTokenConsumed" including Token and
DeviceID and any actor/context), and ensure AppendEvent error handling is
performed and surfaced; keep the AppendEvent invocation in the same handler flow
so every mutation done by ConsumeToken has a corresponding store.AppendEvent.

---

Nitpick comments:
In `@internal/store/luks.go`:
- Around line 10-41: LuksKey and LuksToken contain plaintext secrets
(LuksKey.Passphrase and LuksToken.Token) and can be accidentally exposed by
generic struct logging; implement redacted String() methods for type LuksKey and
LuksToken that return the struct representation with Passphrase and Token
replaced by a constant redaction marker (e.g., "<redacted>") and ensure any
existing log sites use the String() method (or fmt.Stringer) rather than
printing the raw struct; update tests or add unit tests asserting String()
output hides secrets and preserves other fields.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ea91479f-ae26-43dc-9369-54221f043d9e

📥 Commits

Reviewing files that changed from the base of the PR and between 8e6bf70 and 4ca091f.

📒 Files selected for processing (9)
  • internal/api/device_handler.go
  • internal/api/internal_handler.go
  • internal/control/inbox_worker.go
  • internal/store/lps.go
  • internal/store/luks.go
  • internal/store/postgres/lps.go
  • internal/store/postgres/luks.go
  • internal/store/postgres/wire.go
  • internal/store/repos.go

MinLength: minLength,
Complexity: complexity,
})
_, err = h.store.Repos().Luks.CreateToken(ctx, store.CreateLuksTokenParams{DeviceID: req.Msg.DeviceId, ActionID: req.Msg.ActionId, Token: token, MinLength: minLength, Complexity: complexity})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Minting the LUKS token still bypasses store.AppendEvent.

CreateLuksToken is a mutation, but this path writes token state directly through the repo and returns success without recording an event. That leaves the token lifecycle outside the normal handler audit/rebuild contract.

As per coding guidelines "Every mutation must append an event via store.AppendEvent."

🤖 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/api/device_handler.go` at line 872, The CreateLuksToken call writes
token state directly without recording an event; change the handler to append an
event after (or instead of) the direct mutation by invoking store.AppendEvent
with an event type like "luks.token.created" and include the token details
(DeviceID, ActionID, Token, MinLength, Complexity) and actor/metadata from the
request/context, then persist the mutation through the repository only via the
normal event-driven path (or ensure the repo change and AppendEvent are
atomic/inside the same transaction). Update the code around
h.store.Repos().Luks.CreateToken and ensure store.AppendEvent is called (or used
as the primary mutation) so every token minting follows the required
event-append contract.

Token: req.Msg.Token,
DeviceID: req.Msg.DeviceId,
})
token, err := h.store.Repos().Luks.ConsumeToken(ctx, store.ConsumeLuksTokenParams{Token: req.Msg.Token, DeviceID: req.Msg.DeviceId})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Consuming the one-time token also bypasses store.AppendEvent.

ConsumeToken burns durable state, but this handler mutation still happens entirely through the repo with no event appended. That leaves the destructive half of the token lifecycle outside the handler audit trail as well.

As per coding guidelines "Every mutation must append an event via store.AppendEvent."

🤖 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/api/internal_handler.go` at line 248, The handler currently calls
h.store.Repos().Luks.ConsumeToken(...) which mutates durable state but does not
append an event; update the handler so that after a successful call to
ConsumeToken (store.ConsumeLuksTokenParams / h.store.Repos().Luks.ConsumeToken)
you also call h.store.AppendEvent(...) to record the mutation (e.g., an event
like "LuksTokenConsumed" including Token and DeviceID and any actor/context),
and ensure AppendEvent error handling is performed and surfaced; keep the
AppendEvent invocation in the same handler flow so every mutation done by
ConsumeToken has a corresponding store.AppendEvent.

Two repos for the secret-rotation domains that survived the projector
port:

LpsRepo (lps_passwords_projection):
  ListCurrent / ListHistory

LuksRepo (luks_keys_projection + luks_tokens):
  ListCurrent / ListHistory / GetCurrentForAction / CreateToken /
  ConsumeToken / GetRevocationStreamID

Decrypted secrets (Password, Passphrase) cross the boundary as plain
strings — caller-side rule: treat as sensitive material, don't log.

GetCurrentForAction serves the gateway-proxy retrieval flow.
CreateToken/ConsumeToken implement the one-shot retrieval-token
mint+burn pattern. GetRevocationStreamID lets the inbox worker
append terminal events to the same stream as the original
revocation request so the three-phase projection stitches together.

Non-goals:
- SecurityAlertRepo — only callers are the projector listener
  (InsertSecurityAlertProjection, AcknowledgeSecurityAlertProjection).
  Pure projector internals; stays on Queries() until the
  projector-write wave (F).
- UpdateLuksKeyRevocationStatus — projector listener write. Same.

Call sites migrated:
- device_handler.go (LPS+LUKS reads + CreateToken)
- internal_handler.go (GetCurrentForAction ×2 + ConsumeToken)
- control/inbox_worker.go (GetRevocationStreamID)

Closes #281.
@PaulDotterer PaulDotterer force-pushed the feat/281-lps-luks-secalert-repos branch from 4ca091f to e3c4c3a Compare May 15, 2026 23:57
@PaulDotterer PaulDotterer merged commit 66a4d8b into main May 16, 2026
5 checks passed
@PaulDotterer PaulDotterer deleted the feat/281-lps-luks-secalert-repos branch May 16, 2026 00:13
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.

wave B.19: lps + luks repos

1 participant