feat(repository): refactor query/mutation ports with capability-safe
interfaces - refactor: split UserRepository into AuthUserRepository and AdminUserRepository capabilities - refactor: split SessionRepository into AuthSessionRepository and repository-owned CLI/admin methods - refactor: replace generic Repository Update/Delete with operation-specific params and ownership predicates - refactor: replace CredentialRepository generic CRUD with passkey-specific methods - refactor: replace FileRepository generic Create/Update/Delete with UploadedFileParams, DirectoryParams, and owned soft-delete - refactor: remove repository fields from WebApp struct; repositories are now composition-time wiring only - feat: add domain error kinds ErrParentNotFound, ErrParentNotDir, ErrDirectoryNotEmpty, ErrInvalidMove - feat: add CredentialTypeAppPasskey constant - feat: add testutil.SetUserAdmin for test fixture setup that bypasses production service ports - test: add architecture test banning GORM Save in repository package - test: add capability interface contract tests ensuring each service receives the minimal interface - test: add blockingStorage helper for concurrent promotion tests - test: add preserved DSN parameter test for sqliteImmediateDSN - docs: update architecture decisions with repository write rules and capability separation - docs: update roadmap to clarify atomic single-use refresh sessions - docs: add -race test target to development docs
This commit is contained in:
@@ -46,7 +46,7 @@
|
||||
|
||||
**Consequences**:
|
||||
- Version is build metadata from `internal/app/version.go`, not a config-file field.
|
||||
- `app.WebApp` is the place to add future services, repositories, storage, and app metadata incrementally.
|
||||
- `app.WebApp` is the place to add future runtime services, storage, and app metadata incrementally; repository implementations stay inside composition setup.
|
||||
- Request ID middleware is not part of the current foundation; add it only with a logging/tracing/error-correlation design.
|
||||
|
||||
## 2026-04-29: Auth Refinements
|
||||
@@ -123,3 +123,26 @@
|
||||
- Authenticated file operations cannot use status codes or messages to determine whether another user's resource exists.
|
||||
- A successful first delete returns `204 No Content`; repeated deletes return `404 Not Found` while remaining idempotent in effect.
|
||||
- Unauthenticated requests remain `401 Unauthorized`, non-empty directory deletion remains `409 Conflict`, and permission-denied behavior outside the file boundary remains unchanged.
|
||||
|
||||
## 2026-07-16: Command-Scoped Persistence Mutations
|
||||
|
||||
**Context**: Generic `Repository.Update(*Entity)`, full-entity GORM `Save`, and broad delete methods exposed fields and lifecycle transitions that individual services were not authorized to perform. They also allowed a stale file entity to revive a soft-deleted row, left file parent checks vulnerable to concurrent deletion, and allowed two refresh requests to consume the same session.
|
||||
|
||||
**Decisions**:
|
||||
|
||||
| Decision | Guidance |
|
||||
|----------|----------|
|
||||
| Command-scoped writes | Every mutation has a use-case-specific method and parameter type. Mutable domain entities do not cross a service-to-repository write boundary. |
|
||||
| Capability interfaces | Auth and admin services accept only the query and command capabilities they require. Wider user and session interfaces exist only for construction and repository-focused consumers. |
|
||||
| Explicit mutation predicates | Updates name concrete columns and include ownership, credential type, and/or active-state predicates appropriate to the command. A zero-row guarded mutation maps to a domain not-found result. |
|
||||
| Transactional file hierarchy | Child creation, movement, and directory deletion revalidate hierarchy state in the same transaction. PostgreSQL locks relevant rows in sorted ID order; SQLite DSNs force `_txlock=immediate`. |
|
||||
| Atomic refresh consumption | Refresh rotates through one transaction that locks, returns, and deletes the session. A concurrent replay cannot issue a second token pair. Logout remains an idempotent revoke-by-hash command. |
|
||||
| Test-only privilege setup | Tests elevate users through `internal/testutil`; no production role mutation is added merely to prepare fixtures. |
|
||||
|
||||
**Consequences**:
|
||||
|
||||
- Protected user, file, session, and credential fields are absent from ordinary mutation inputs, reducing illegal write paths at compile time.
|
||||
- Deleted file rows cannot be revived by stale metadata updates, and successful hierarchy races cannot leave active orphan children.
|
||||
- PostgreSQL relies on row-level locking while SQLite serializes these hierarchy and session write transactions from their first database operation. This favors correctness over concurrent SQLite writers.
|
||||
- Adding a future profile, password, role, restore, or other mutation requires a named command and an explicit capability rather than extending a universal update method.
|
||||
- REST routes, payloads, status codes, database schema, and dependency set remain unchanged.
|
||||
|
||||
Reference in New Issue
Block a user