Skip to content

refactor(store): wave B.1 — domain repo scaffolding + compliance read canary (#245)#246

Merged
PaulDotterer merged 3 commits into
mainfrom
feat/245-compliance-repo-canary
May 14, 2026
Merged

refactor(store): wave B.1 — domain repo scaffolding + compliance read canary (#245)#246
PaulDotterer merged 3 commits into
mainfrom
feat/245-compliance-repo-canary

Conversation

@PaulDotterer

@PaulDotterer PaulDotterer commented May 14, 2026

Copy link
Copy Markdown
Contributor

Summary

First sub-wave of the Wave B migration under tracker #242. Introduces the repository-pattern scaffolding (`Repos` struct, `internal/store/postgres` package, `Store.SetRepos` / `Store.Repos()` accessor) and migrates one small read-only domain — device-compliance reads — end-to-end to validate the pattern.

Stacked on #244 (Wave A). Until #244 merges, this PR's diff includes Wave A's commits; review the head commit (`refactor(store): wave B.1`) for B.1 scope. After #244 merges, I'll rebase this onto main.

What lands

  • `internal/store/compliance.go` — `ComplianceRepo` interface plus domain types `ComplianceCheckResult` / `ComplianceSummary`. Field names are normalized away from sqlc row-type identifiers so handlers depend on stable shapes, not on generated code.
  • `internal/store/repos.go` — `Repos` struct (interface-typed fields). Adding a domain = adding a field + wiring in `postgres.NewRepos`.
  • `internal/store/postgres/{compliance,wire}.go` — Postgres impl wrapping `*generated.Queries`. Translates `pgx.ErrNoRows` → `store.ErrNotFound` at the repo boundary so callers depend only on `store.IsNotFound` (per Wave A's contract).
  • `Store.SetRepos` / `Store.Repos()` — boot code wires `Repos` after `store.New`; `Repos()` panics if `SetRepos` was never called so misconfiguration fails loudly. `SetRepos` invoked at three sites: `cmd/control/main.go`, `cmd/indexer/main.go`, `internal/testutil/postgres.go`.
  • `compliance_handler.go` and `compliance_policy_handler.go` — the four device-compliance read sites now flow through `store.Repos().Compliance` instead of `Store.Queries()`.

Why a canary first

Wave B as designed in `project_storage_abstraction_plan.md` (interfaces for ~20 domains + full handler migration + retire `Store.Queries()`) is multi-PR work. This sub-PR proves the pattern is sound before bulk migrations begin. Subsequent Wave B.N sub-PRs each migrate one or two additional domains; `Store.Queries()` goes private once the last caller migrates.

Non-goals

  • Other domains. They come in B.2..B.N.
  • Transactional repos / `UnitOfWork`. Compliance reads are single-statement; defer tx wiring until a domain actually needs it.
  • Retiring `Store.Queries()`. Stays exported as the transition pathway for un-migrated domains.

Acceptance

  • Compliance handlers no longer import `*generated.Queries` for the four migrated reads.
  • `store.ComplianceRepo` translates pgx errors → `store.ErrNotFound`; callers test via `store.IsNotFound`.
  • `go vet`, `gofmt -l`, `go build`: clean.
  • Targeted handler tests (`TestCompliance*`, `TestPolicy*`, `TestSearchListener_Device*`, `TestHandleGet*`): pass.
  • Local CodeRabbit review: skipped (rate-limited after Wave A's review — CR PR review will run on push).

Test plan

  • CI green on all checks
  • CodeRabbit PR review passes
  • No behaviour regressions in compliance handler responses

Part of #242. Closes #245.

Summary by CodeRabbit

  • Bug Fixes

    • Standardized error handling for missing resources across all API endpoints to ensure consistent "not found" responses.
  • Chores

    • Refactored internal data access layer to use a centralized repository pattern, improving code maintainability and consistency.

Review Change Stack

… from non-store packages (#243)

First wave of the storage abstraction tracker (#242). Stops handler,
projector, scim, idp, and control packages from reaching for
pgx.ErrNoRows directly; centralizes not-found recognition in
store.IsNotFound so a future backend (libSQL/Turso as illustrative
example, NoSQL as architectural ceiling) registers its own
no-rows error in one place rather than in every caller.

No behaviour change. errors.Is semantics are preserved end-to-end;
the helpers_test bridge test (TestHandleGetError_NotFound) locks both
pgx.ErrNoRows and store.ErrNotFound through the recognizer.

Adds: server/internal/store/notfound.go
Touches: 34 files across api/, scim/, idp/, projectors/, control/.
Closes #243.
… canary (#245)

First sub-wave of the Wave B migration under tracker #242. Introduces
the repository-pattern scaffolding (Repos struct, internal/store/postgres
package, Store.SetRepos / Store.Repos accessor) and migrates one small
read-only domain — device-compliance reads — end-to-end to validate
the pattern.

What lands:

- internal/store/compliance.go — ComplianceRepo interface plus
  domain types ComplianceCheckResult / ComplianceSummary. Field
  names normalized away from sqlc row-type identifiers so handlers
  depend on stable shapes, not on generated code.
- internal/store/repos.go — Repos struct (interface-typed fields).
  Adding a domain = adding a field + wiring in postgres.NewRepos.
- internal/store/postgres/{compliance,wire}.go — Postgres impl
  wrapping *generated.Queries. Translates pgx.ErrNoRows ->
  store.ErrNotFound at the repo boundary so callers depend only on
  store.IsNotFound (per Wave A's contract).
- Store.SetRepos / Store.Repos() — boot code wires Repos after
  store.New; Repos() panics if SetRepos was never called so
  misconfiguration fails loudly. SetRepos used at three sites:
  cmd/control/main.go, cmd/indexer/main.go, internal/testutil/postgres.go.
- compliance_handler.go and compliance_policy_handler.go — the four
  device-compliance read sites now flow through
  store.Repos().Compliance instead of Store.Queries().

Why a canary first: Wave B as designed (interfaces for 20 domains
+ full handler migration + retire Store.Queries) is multi-PR work.
This sub-PR proves the pattern is sound before bulk migrations
begin. Subsequent Wave B.N sub-PRs each migrate one or two
additional domains.

Non-goals:

- Other domains. They come in B.2..B.N.
- Transactional repos / UnitOfWork. Compliance reads are
  single-statement; defer the tx wiring until a domain needs it.
- Retiring Store.Queries(). Stays exported as the transition
  pathway for un-migrated domains; comes private once the last
  caller migrates.

Closes #245.
…iccheck SA1019 (#245)

Wave B.1's first attempt marked Store.Queries() with the godoc
"Deprecated:" keyword. staticcheck SA1019 then flagged every
existing caller (~50 sites in scim/, search/, internal handlers,
testutil) as a CI failure, even though those callers are part of
the planned migration set, not unsanctioned uses.

Drop the magic keyword, keep the prose. The marker re-enters
naturally when Queries() goes unexported at the end of Wave B —
at that point any remaining caller IS a bug.
@coderabbitai

coderabbitai Bot commented May 14, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR centralizes "not found" error detection by introducing a store-level IsNotFound() abstraction and scaffolding domain repository interfaces. It replaces ~100 pgx-specific error checks across 40+ files with a unified helper, implements compliance as the canary repository, and wires repos during bootup.

Changes

Store abstraction and compliance repository scaffolding

Layer / File(s) Summary
Store not-found abstraction and repository scaffolding
internal/store/notfound.go, internal/store/repos.go, internal/store/store.go
store.ErrNotFound sentinel and IsNotFound() helper recognize both store and pgx errors. Repos struct unifies domain repository interfaces. Store gains thread-safe SetRepos()/Repos() accessors.
Compliance domain types and interface
internal/store/compliance.go
ComplianceCheckResult and ComplianceSummary domain types, plus ComplianceRepo interface specifying device results/summary read methods with ErrNotFound semantics.
Postgres compliance repository
internal/store/postgres/compliance.go, internal/store/postgres/wire.go
Postgres implementation wraps sqlc queries, translates pgx.ErrNoRows to store.ErrNotFound at boundary, and provides NewRepos() factory for wiring.
API handler not-found migration
internal/api/*.go (25+ files)
All API handlers replace errors.Is(err, pgx.ErrNoRows) with store.IsNotFound(err) for user/device/role/action lookups and uniqueness checks while preserving response behavior.
Compliance handler repository usage
internal/api/compliance_handler.go, internal/api/compliance_policy_handler.go
GetDeviceCompliance and GetDeviceCompliancePolicyStatus fetch via store.Repos().Compliance.DeviceResults/DeviceSummary instead of direct query methods.
SCIM and IDP handler migration
internal/scim/*.go, internal/idp/*.go (15+ files)
SCIM/IDP handlers replace pgx error checks with store.IsNotFound(err) for provider, group, user, and identity-link lookups.
Event projector migration
internal/projectors/*.go (4 files)
Assignment, device-group, and user-group projectors use store.IsNotFound(err) for stale-replay detection and cascade guards.
Inbox worker migration
internal/control/inbox_worker.go
Control inbox worker replaces pgx checks with store.IsNotFound(err) for device validation, execution/action record detection, and orphan handling.
Bootup and test wiring
cmd/control/main.go, cmd/indexer/main.go, internal/testutil/postgres.go
Server startup and test container setup wire repository implementations via st.SetRepos(postgres.NewRepos(st.Queries())).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • #243: The main changes implement store.ErrNotFound/IsNotFound and replace pgx.ErrNoRows checks across handlers, projectors, SCIM/IDP code and wiring in Postgres repos—matching the retrieved issue's goal to centralize not-found semantics and remove pgx.ErrNoRows from non-store packages.

Possibly related PRs

  • manchtools/power-manage-server#233: The main PR's SCIM group "not found" handling changes in internal/scim/groups_helpers.go (switching to store.IsNotFound(err)) are directly related to the retrieved PR's introduction of that helper file's orphan-restoration fallback logic that previously relied on pgx.ErrNoRows.
  • manchtools/power-manage-server#29: Both PRs update the API layer's "not found" detection/mapping—main PR replaces errors.Is(err, pgx.ErrNoRows) with store.IsNotFound(err) (including internal/api/helpers.go/call sites), while PR #29 centralizes not-found handling via handleGetError in the same handlers/helpers—so the changes overlap at the same code paths.
  • manchtools/power-manage-server#179: The main PR's new Postgres Compliance repo/API reads (DeviceResults/DeviceSummary) are directly dependent on the compliance projection behavior (UpsertComplianceResultProjection/DeleteComplianceResultProjection) introduced by the Go ComplianceListener in the retrieved PR.

Poem

🐰 A rabbit hops through error's maze,
No more pgx in countless ways—
IsNotFound now leads the way,
One abstraction saves the day!
Repos bloom in structured light,
Compliance reads? Now done just right! ✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR includes extensive refactoring of pgx.ErrNoRows error handling across 30+ files beyond the four compliance read sites specified in #245, converting all application-layer error checks to use store.IsNotFound(err). Reduce scope to only the four compliance read migrations per issue #245; defer the store.IsNotFound() abstraction and pgx.ErrNoRows refactoring across application code to a separate Wave A PR or clarify in issue scope.
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: introducing domain repository scaffolding and migrating compliance reads as a canary implementation for Wave B.1 storage abstraction.
Linked Issues check ✅ Passed All coding requirements from #245 are met: ComplianceRepo interface and domain types created, Postgres implementations with pgx.ErrNoRows translation provided, Store.Repos() accessor added, and four compliance read sites migrated to use store.Repos().Compliance.

✏️ 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/245-compliance-repo-canary

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

🤖 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/compliance_handler.go`:
- Around line 33-41: The repo error handling for
h.store.Repos().Compliance.DeviceResults and .DeviceSummary currently maps all
errors to ErrInternal/connect.CodeInternal; update both error branches to detect
the repository's not-found sentinel (e.g. compare err with the repo's
ErrNotFound or use errors.Is) and return apiErrorCtx(ctx, ErrNotFound,
connect.CodeNotFound, "<message>") for that case, and only fall back to
apiErrorCtx(ctx, ErrInternal, connect.CodeInternal, "<message>") for other
errors; keep the original log/description text but use connect.CodeNotFound for
missing resources and ensure you reference the same symbols apiErrorCtx,
ErrNotFound (or repo sentinel), and connect.CodeNotFound.

In `@internal/idp/linker.go`:
- Around line 142-144: The lookup fallback is returning early when err is nil
because the code calls store.IsNotFound(err) without a nil guard; update the
condition in the fallback branch around the store.IsNotFound(err) check so it
only treats non-nil errors as failures (e.g., change the guard to require err !=
nil && !store.IsNotFound(err)) so that a nil err continues to Step 2/3 after
stale-link cleanup; ensure this change is applied to the branch that currently
contains the return fmt.Errorf("lookup identity link: %w", err).
🪄 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: 4a25c8ed-bc26-443c-bd4d-326b907e6f02

📥 Commits

Reviewing files that changed from the base of the PR and between 8d12458 and 09f9ee3.

📒 Files selected for processing (45)
  • cmd/control/main.go
  • cmd/indexer/main.go
  • internal/api/action_crud.go
  • internal/api/action_dispatch.go
  • internal/api/assignment_handler.go
  • internal/api/auth_handler.go
  • internal/api/certificate_handler.go
  • internal/api/compliance_handler.go
  • internal/api/compliance_policy_handler.go
  • internal/api/device_group_handler.go
  • internal/api/device_handler.go
  • internal/api/helpers.go
  • internal/api/helpers_test.go
  • internal/api/idp_handler.go
  • internal/api/registration_handler.go
  • internal/api/role_handler.go
  • internal/api/search_listener.go
  • internal/api/search_listener_e2e_test.go
  • internal/api/sso_handler.go
  • internal/api/terminal_handler.go
  • internal/api/totp_handler.go
  • internal/api/user_group_handler.go
  • internal/api/user_handler.go
  • internal/control/inbox_worker.go
  • internal/idp/linker.go
  • internal/idp/linker_test.go
  • internal/projectors/assignment_listener.go
  • internal/projectors/assignment_test.go
  • internal/projectors/device_group_listener.go
  • internal/projectors/user_group_listener.go
  • internal/scim/auth.go
  • internal/scim/groups.go
  • internal/scim/groups_create.go
  • internal/scim/groups_helpers.go
  • internal/scim/groups_mutate.go
  • internal/scim/users.go
  • internal/scim/users_create.go
  • internal/scim/users_mutate.go
  • internal/store/compliance.go
  • internal/store/notfound.go
  • internal/store/postgres/compliance.go
  • internal/store/postgres/wire.go
  • internal/store/repos.go
  • internal/store/store.go
  • internal/testutil/postgres.go

Comment on lines +33 to 41
results, err := h.store.Repos().Compliance.DeviceResults(ctx, deviceID)
if err != nil {
return nil, apiErrorCtx(ctx, ErrInternal, connect.CodeInternal, "failed to query compliance results")
}

summary, err := h.store.Queries().GetDeviceComplianceSummary(ctx, deviceID)
summary, err := h.store.Repos().Compliance.DeviceSummary(ctx, deviceID)
if err != nil {
return nil, apiErrorCtx(ctx, ErrInternal, connect.CodeInternal, "failed to query compliance summary")
}

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 | ⚡ Quick win

Map repo not-found errors to a non-internal Connect code.

Both repo calls currently collapse all failures to connect.CodeInternal. If the compliance repo returns a not-found condition, clients get a 500 instead of a resource error.

Suggested fix
 	results, err := h.store.Repos().Compliance.DeviceResults(ctx, deviceID)
 	if err != nil {
+		if store.IsNotFound(err) {
+			return nil, apiErrorCtx(ctx, ErrDeviceNotFound, connect.CodeNotFound, "device not found")
+		}
 		return nil, apiErrorCtx(ctx, ErrInternal, connect.CodeInternal, "failed to query compliance results")
 	}

 	summary, err := h.store.Repos().Compliance.DeviceSummary(ctx, deviceID)
 	if err != nil {
+		if store.IsNotFound(err) {
+			return nil, apiErrorCtx(ctx, ErrDeviceNotFound, connect.CodeNotFound, "device not found")
+		}
 		return nil, apiErrorCtx(ctx, ErrInternal, connect.CodeInternal, "failed to query compliance summary")
 	}

As per coding guidelines: internal/api/*_handler.go: Errors must use apiErrorCtx with proper Connect error codes. Never silently ignore errors.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
results, err := h.store.Repos().Compliance.DeviceResults(ctx, deviceID)
if err != nil {
return nil, apiErrorCtx(ctx, ErrInternal, connect.CodeInternal, "failed to query compliance results")
}
summary, err := h.store.Queries().GetDeviceComplianceSummary(ctx, deviceID)
summary, err := h.store.Repos().Compliance.DeviceSummary(ctx, deviceID)
if err != nil {
return nil, apiErrorCtx(ctx, ErrInternal, connect.CodeInternal, "failed to query compliance summary")
}
results, err := h.store.Repos().Compliance.DeviceResults(ctx, deviceID)
if err != nil {
if store.IsNotFound(err) {
return nil, apiErrorCtx(ctx, ErrDeviceNotFound, connect.CodeNotFound, "device not found")
}
return nil, apiErrorCtx(ctx, ErrInternal, connect.CodeInternal, "failed to query compliance results")
}
summary, err := h.store.Repos().Compliance.DeviceSummary(ctx, deviceID)
if err != nil {
if store.IsNotFound(err) {
return nil, apiErrorCtx(ctx, ErrDeviceNotFound, connect.CodeNotFound, "device not found")
}
return nil, apiErrorCtx(ctx, ErrInternal, connect.CodeInternal, "failed to query compliance summary")
}
🤖 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/compliance_handler.go` around lines 33 - 41, The repo error
handling for h.store.Repos().Compliance.DeviceResults and .DeviceSummary
currently maps all errors to ErrInternal/connect.CodeInternal; update both error
branches to detect the repository's not-found sentinel (e.g. compare err with
the repo's ErrNotFound or use errors.Is) and return apiErrorCtx(ctx,
ErrNotFound, connect.CodeNotFound, "<message>") for that case, and only fall
back to apiErrorCtx(ctx, ErrInternal, connect.CodeInternal, "<message>") for
other errors; keep the original log/description text but use
connect.CodeNotFound for missing resources and ensure you reference the same
symbols apiErrorCtx, ErrNotFound (or repo sentinel), and connect.CodeNotFound.

Comment thread internal/idp/linker.go
Comment on lines +142 to 144
if !store.IsNotFound(err) {
return nil, fmt.Errorf("lookup identity link: %w", err)
}

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 | ⚡ Quick win

Add a nil guard before the not-found check in the lookup fallback.

At Line 142, this branch also runs when err == nil (after stale-link cleanup), which causes an unintended early return instead of continuing to Step 2/3.

Proposed fix
-	if !store.IsNotFound(err) {
+	if err != nil && !store.IsNotFound(err) {
		return nil, fmt.Errorf("lookup identity link: %w", err)
	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if !store.IsNotFound(err) {
return nil, fmt.Errorf("lookup identity link: %w", err)
}
if err != nil && !store.IsNotFound(err) {
return nil, fmt.Errorf("lookup identity link: %w", err)
}
🤖 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/idp/linker.go` around lines 142 - 144, The lookup fallback is
returning early when err is nil because the code calls store.IsNotFound(err)
without a nil guard; update the condition in the fallback branch around the
store.IsNotFound(err) check so it only treats non-nil errors as failures (e.g.,
change the guard to require err != nil && !store.IsNotFound(err)) so that a nil
err continues to Step 2/3 after stale-link cleanup; ensure this change is
applied to the branch that currently contains the return fmt.Errorf("lookup
identity link: %w", err).

@PaulDotterer PaulDotterer merged commit e4e0a64 into main May 14, 2026
5 checks passed
@PaulDotterer PaulDotterer deleted the feat/245-compliance-repo-canary branch May 14, 2026 19:04
PaulDotterer added a commit that referenced this pull request May 14, 2026
Continues the per-domain migration started by Wave B.1 (#246). Three
small handlers move from Store.Queries() to domain-specific repos
under store.Repos(); cross-domain reads (GetUserByID, GetDeviceByID)
stay on Queries until those domains migrate in later sub-waves.

Domains migrated:

- SettingsRepo (GetServer) — settings_handler.go.
- IdentityLinkRepo (Get / ListForUser / CountForUser) — identity_link_handler.go
  + populateUserIdentityLinks in user_handler.go. ListForUser returns
  the join-shape IdentityLinkWithProvider so handlers can't confuse
  it with the basic Get shape.
- LogsRepo (CreateQueryResult / GetQueryResult / ExpirePendingQueryResult) —
  logs_handler.go. First write-side migration in Wave B; the :exec
  signatures stay opaque to handlers, matching the read-side pattern.

Each new repo's Postgres impl translates pgx.ErrNoRows to
store.ErrNotFound at the boundary, matching the contract established
in Wave A (#244) and applied in Wave B.1.

Non-goals:

- User / device repos — cross-domain reads in these handlers
  remain on Store.Queries() until those domains migrate.
- Compliance-policy queries — separate sub-wave.

Closes #247.
PaulDotterer added a commit that referenced this pull request May 14, 2026
…) (#248)

Continues the per-domain migration started by Wave B.1 (#246). Three
small handlers move from Store.Queries() to domain-specific repos
under store.Repos(); cross-domain reads (GetUserByID, GetDeviceByID)
stay on Queries until those domains migrate in later sub-waves.

Domains migrated:

- SettingsRepo (GetServer) — settings_handler.go.
- IdentityLinkRepo (Get / ListForUser / CountForUser) — identity_link_handler.go
  + populateUserIdentityLinks in user_handler.go. ListForUser returns
  the join-shape IdentityLinkWithProvider so handlers can't confuse
  it with the basic Get shape.
- LogsRepo (CreateQueryResult / GetQueryResult / ExpirePendingQueryResult) —
  logs_handler.go. First write-side migration in Wave B; the :exec
  signatures stay opaque to handlers, matching the read-side pattern.

Each new repo's Postgres impl translates pgx.ErrNoRows to
store.ErrNotFound at the boundary, matching the contract established
in Wave A (#244) and applied in Wave B.1.

Non-goals:

- User / device repos — cross-domain reads in these handlers
  remain on Store.Queries() until those domains migrate.
- Compliance-policy queries — separate sub-wave.

Closes #247.
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.1: domain repo scaffolding + compliance read canary

1 participant