Fundamental Logic Hardening Audit
Scope
This audit closes the first four hardening slices before the cross-feature ownership audit:
- org-side access and permissions coherence
- portal grant-model coherence
- Stripe truth versus app truth discipline
- collaboration closure rules
1. Org Access And Permissions Coherence
Status: Pass with one explicit watchpoint.
Feature map:
- Pass: team access policy now has a first-class client visibility model.
packages/backend/convex/schema.tspackages/backend/convex/org/access.tspackages/backend/convex/org/access/membership.tsapps/app/src/app/[locale]/org/(dashboard)/settings/team/page.tsxorgUsersmemberships now carryclientAccessMode(allvsassigned), and the owner-facing team editor can set it directly.
- Pass: shared org visibility now reads as one system.
packages/backend/convex/access/org/authorization.tspackages/backend/convex/clients/scope.tsassertAuthedresolves the viewer's client access mode, andgetClientScopeapplies that mode before falling back toclientAccessAssignments.
- Pass: client-company visibility is enforced consistently across the shipped org client-work surfaces.
packages/backend/convex/org/clients.ts and packages/backend/convex/clients/accounts/*packages/backend/convex/org/projects.tspackages/backend/convex/org/time.tspackages/backend/convex/org/drive.tspackages/backend/convex/org/contracts.tspackages/backend/convex/org/clientBilling.ts- These surfaces all resolve visibility through
getClientScope,listVisibleClientAccounts, orgetVisibleClientAccountById.
- Pass: client-assignment management is no longer an owner-only exception.
packages/backend/convex/clients/scope.tscanManageClientAssignmentsis now capability-gated throughteam.manage_organization_staff.
- Pass: billing remains the only higher-risk track with extra capability checks on top of client scope.
packages/backend/convex/org/clientBilling.ts
Watchpoint:
- Deliberate watchpoint: non-billing client-work write authority is still largely role-derived through
canManageClientAccounts.packages/backend/convex/clients/scope.tspackages/backend/convex/org/clients.ts and packages/backend/convex/clients/accounts/*packages/backend/convex/org/projects.ts- We are explicitly not adding a broader client-work capability family yet. If the product needs finer splits inside clients, projects, contracts, or drive, add explicit client-work capabilities instead of layering more role-specific exceptions.
2. Portal Grant-Model Coherence
Status: Pass.
Feature map:
- Pass: active portal session resolution is selected-grant based.
packages/backend/convex/portal/shared.tspackages/backend/convex/portal/settings/operations.ts- The runtime uses
portalAccessGrantsplusselectedGrantfor company access, authorization, and selected client context.
- Pass: org-facing portal access writes now use grant language on the live public mutation surface.
packages/backend/convex/portal/access.tspackages/backend/convex/portal/access/state.tsapps/app/src/app/[locale]/_lib/convex/api/org/access.tsapps/app/src/app/[locale]/org/(dashboard)/clients/[clientId]/page.tsx- The live mutation is now
portal/access:updatePortalAccessGrant.
- Pass: org people/access flows already target company grants, not one-company portal identities.
apps/app/src/app/[locale]/org/(dashboard)/clients/[clientId]/page.tsx- portal enable/disable, role changes, and invite replacement all act on
portalAccessGrantId.
Naming-only residue:
- Local helper naming residue around
portalUseridentity helpers has been cleaned up.- Remaining carryover is limited to stable registered surface names and the persisted
portalMembershipIdsession field. - This is not a runtime model bug. Treat it as bounded compatibility carryover, not as a reason to reopen the access model.
- Remaining carryover is limited to stable registered surface names and the persisted
3. Stripe Truth Versus App Truth Discipline
Status: Pass.
Guardrails:
- Stripe is the finance truth for invoice and subscription state transitions, payment outcomes, and provider lifecycle facts.
- App-side billing records remain operational projections that attach Stripe-backed billing state to the client-account operating model.
- Quotes and contracts stay commercial context, not replacement ledger records.
- Analytics, exports, and UI copy must not invent a second finance truth from app-local fields when Stripe-backed state already exists.
Code anchors:
packages/backend/convex/org/clientBilling.tspackages/backend/convex/org/billing/actions.tspackages/backend/convex/lib/subscriptions.ts
4. Collaboration Closure Rules
Status: Pass.
Lifecycle definition:
requested: the org asked for something.submitted: the portal answered with an upload or doc response.reviewed: the org reviewed that response.acknowledged: the portal confirmed the review or next-step receipt.closed: the org marks that no further follow-up is needed.
Rules:
- review is org-owned
- acknowledgment is portal-owned
- close is org-owned and terminal
closedis intentionally lightweight: fulfilled, superseded, or intentionally ended all collapse into the same final state- do not add a second "resolved" or "canceled" branch unless the product truly needs those semantics later
Code anchors:
packages/backend/convex/lib/fileRequests.tspackages/backend/convex/org/drive.tsapps/app/src/app/[locale]/portal/(dashboard)/files/files.client.tsx
5. Cross-Feature Ownership Audit
Status: Pass with explicit residue notes.
Outcome:
- No live feature family currently re-owns another track's records.
clientAccountremains the required anchor across delivery, commercial, collaboration, access, and portal projection.- The remaining drift is naming and legacy-presentation residue, not a live ownership regression.
Feature map:
- Pass: delivery still points back to the client account first.
packages/backend/convex/schema.tspackages/backend/convex/org/projects.tspackages/backend/convex/org/time.tsprojects,projectUpdates, andtimeEntriesall requireclientAccountId, whileprojectIdremains optional on time and commercial follow-through.
- Pass: commercial remains one client-account spine instead of splitting into project-owned or Drive-owned records.
packages/backend/convex/schema.tspackages/backend/convex/org/contracts.tsapps/app/src/app/[locale]/org/(dashboard)/invoices/[invoiceRecordId]/page.tsxapps/app/src/app/[locale]/org/(dashboard)/subscriptions/[subscriptionRecordId]/page.tsxapps/app/src/app/[locale]/org/(dashboard)/quotes/[quoteRecordId]/page.tsxinvoiceRecords,quoteRecords,subscriptionRecords, andcontractsall requireclientAccountId, project linkage stays optional, contract attachments stay contract-owned, and the detail surfaces use explicit commercial provenance instead of implying project or Drive ownership.
- Pass: collaboration remains collaboration-owned.
packages/backend/convex/schema.tspackages/backend/convex/org/drive.tsapps/app/src/app/[locale]/portal/(dashboard)/projects/[projectId]/project-detail.client.tsxdriveItemsandfileRequestsrequireclientAccountIdwith optional project context, and portal project pages only project shared files and updates instead of creating alternate collaboration records.
- Pass: portal remains a projection and access layer, not an alternate business model.
packages/backend/convex/portal/profile/operations.tspackages/backend/convex/portal/access/state.tspackages/backend/convex/portal/settings/operations.ts- Portal writes create or update portal users, portal access grants, portal invites, settings, and support records only. The portal layer does not create alternate project, invoice, subscription, quote, contract, or drive ownership records.
- Pass: org and portal surfaces are linking by adjacency, not by fake ownership.
apps/app/src/app/[locale]/org/(dashboard)/projects/[projectId]/_components/commercial-context-card.tsxapps/app/src/app/[locale]/org/(dashboard)/clients/[clientId]/page.tsxapps/app/src/app/[locale]/portal/(dashboard)/projects/[projectId]/project-detail.client.tsx- Client and project detail surfaces link across tracks through explicit context and next actions instead of collapsing those tracks into one state model.
Exact residue points:
- Naming carryover: some stable registered portal surface names and the persisted
portalMembershipIdsession field still say "membership" even though the live access model is grant-based.packages/backend/convex/portal/profile.tspackages/backend/convex/schema.ts
- Naming residue: contract authoring still reuses drive-named helper symbols for title/visibility/excerpt handling.
packages/backend/convex/org/contracts.ts- This is implementation reuse, not Drive ownership, but the naming is looser than the domain boundary now allows.
- Drive ownership boundary restored: Drive now carries only Drive concerns such as item type, hierarchy, visibility, source side, storage asset, and optional project linkage.
- Commercial and request-specific meaning must come from the owning surfaces rather than Drive-level category tags.
- Existing access watchpoint still applies across tracks: non-billing client-work write authority is still largely role-derived.
packages/backend/convex/clients/scope.ts- If the product needs finer control later, add explicit client-work capabilities instead of layering more role-specific exceptions.
Hard rule after this audit:
- Any future cross-track link must remain one of these only: required client-account ownership, optional project context, workflow handoff, or portal projection.
- Do not add new links that make Drive own commercial artifacts, make projects mandatory for billing, or let portal surfaces define alternate record truth.
Next Gate
The hardening pass is now complete.
The next work can return to operational polish and reporting depth, but it should keep the ownership rules above fixed unless a later migration intentionally changes the product model.