Closes the audit's HIGH §10 + MED §§17–22 isolation footguns. None of
these are user-impactful TODAY — every site is preceded by a port-
scoped read or pre-validated by ctx.portId — but each is a future-
refactor accident waiting to happen, so the SQL itself now pins the
tenant boundary:
* mergeClients gains a callerPortId option; the route caller passes
ctx.portId. removeInterestBerth now requires portId and verifies
both the interest and the berth share it before deleting the
junction row. All three callers updated.
* Six service mutations now scope the WHERE to (id, portId):
form-templates update + delete, invoices.detectOverdue per-row
update, notifications.markRead, clients.deleteRelationship.
company-memberships uses an inArray sub-select against port
companies (no port_id column on the table itself), covering
updateMembership / endMembership / setPrimary.
* Port-scoped file lookups in portal.getDocumentDownloadUrl,
reports.getDownloadUrl (file presign), berth-reservations.activate
(contractFileId attach guard), and residential.getResidentialInterestById
(residentialClient join).
Test status: 1168/1168 vitest, tsc clean.
Refs: docs/audit-comprehensive-2026-05-05.md HIGH §10 + MED §§17–22
(auditor-B3 Issues 1–5,7).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces every em-dash and en-dash with regular ASCII hyphens
across comments, JSX strings, and dev-facing logs. Mostly cosmetic
but stops the inconsistent mix that crept in over the last few
months (some files used em-dashes in comments, others didn't,
some used both).
Bundles two small dashboard-layout tweaks that touch a couple of
already-modified files:
- (dashboard)/layout.tsx main padding goes from p-6 to pt-3 px-6
pb-6 so page content sits closer to the topbar.
- Sidebar now receives the ports list it needs for the footer
port switcher.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The same `interface AuditMeta { userId; portId; ipAddress; userAgent }`
was duplicated in 26 service files. Move the canonical definition into
`@/lib/audit` next to the related types and update every service to
import it. `ServiceAuditMeta` (the alias used in invoices.ts and
expenses.ts) collapses into the same name.
Tag CRUD across clients/companies/yachts/interests/berths followed an
identical wipe-then-rewrite recipe with two latent issues: the delete
and insert weren't wrapped in a transaction (a partial failure left
the entity with zero tags) and the audit-log payload shape diverged
(`newValue: { tagIds }` for clients/yachts/companies but
`metadata: { type: 'tags_updated', tagIds }` for interests/berths).
Extract `setEntityTags` in `entity-tags.helper.ts` that performs the
delete+insert inside a single transaction, normalizes the audit payload
to `newValue: { tagIds }`, and dispatches the per-entity socket event
through a switch so `ServerToClientEvents` typing stays intact.
The five `setXTags(...)` service functions now do parent-row tenant
verification and delegate the join-table work + side effects.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds company-membership service with six operations (add, update, end,
setPrimary, listByCompany, listByClient), the corresponding Zod
validators, three socket events, and a unit-test suite covering the
portId-scoping rules, the unique_cm_exact conflict path, and the atomic
setPrimary transaction.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>