feat: import row validation + KB/process per-item ACLs

Continuing the audit backlog and finishing the permissions UI.

Import validation (recurring audit finding):
- Added importMembersSchema / importPaymentsCsvSchema. importMembers
  and importPaymentsCsv now validate per row (a single bad row no
  longer rejects the whole file), lowercase emails, normalize dates
  through flexibleDateTime(), enforce tier/platform enums and
  positive finite amounts, and return a `skipped` count instead of
  silently dropping invalid rows.

Permissions UI completion:
- OpsAclEditor is now wired into the KB/process viewer modal
  (admin/board), with entity type derived from the `process` tag.
  Per-item Ops access control now covers secrets + KB + processes,
  matching "all ops facilities stuff".

Left as-is with rationale in HANDOFF: /ops + /import denial paths
(unreachable behind the layout gate or already a clear non-leaking
denial); members-row make-area-lead shortcut (Customize -> Area
leads fully covers assignment); types/database.ts + docs regen
(pure hygiene, zero runtime/security impact, still tracked).

271/271 tests pass. Build clean. No migration this pass.
This commit is contained in:
Moheeb Zara
2026-05-15 18:33:53 -07:00
parent f5d670477b
commit 86d44f6efc
6 changed files with 111 additions and 54 deletions
+2
View File
@@ -19,6 +19,8 @@ All notable changes to this project are documented here. Format follows [Keep a
- `/settings` is gated server-side to admins before any space/integration data is fetched.
### Fixed
- Bulk import (`importMembers`, `importPaymentsCsv`) now validates every row with Zod: lowercased emails, `flexibleDateTime` date normalization, enum-checked platform/tier, positive finite amounts. Invalid rows are skipped and counted (returned as `skipped`) instead of silently dropped; one bad row no longer rejects the whole file.
- Per-item Ops access control now also covers knowledge base and process entries (wired into the viewer modal), not just secrets.
- `revealSecret` no longer silently returns the stale plaintext column when a row is marked encrypted but has no ciphertext; it surfaces the corruption instead.
- `upsertAreaLead` rejects a null/empty area code (Postgres treats NULLs as distinct in unique constraints, which had allowed duplicate area-lead rows).