Skip to content

promote: development history slice 4 - #35

Merged
BeforeLights merged 60 commits into
mainfrom
promote/dev-20260803-04
Aug 3, 2026
Merged

promote: development history slice 4#35
BeforeLights merged 60 commits into
mainfrom
promote/dev-20260803-04

Conversation

@BeforeLights

@BeforeLights BeforeLights commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Promotion slice

This PR promotes the next ordered 60 commits from dev after merged promotion PR #33. It ends at 0617995 and intentionally stays below the hard 79-commit promotion ceiling. The remaining ordered dev history will be promoted in a separate follow-up slice.

Review and verification

  • Feature PR feat: complete foundation telemetry and identity safety #34 was merged to dev without CodeRabbit; its review fixes are already included in the previous promotion history or the next slice as appropriate.
  • This is a promotion PR to main and receives exactly one full CodeRabbit review.
  • Hosted checks must pass before merge.
  • No customer data, runtime artifacts, or credentials are included.

Summary by CodeRabbit

  • New Features
    • Added cursor-based pagination for audit events and seals.
    • Added project-level usage tracking and stronger organization/workspace session scoping.
    • MFA verification now uses proof-based requests with server-controlled timestamps.
  • Bug Fixes
    • Improved tenant authorization, session expiration, token reuse handling, and cross-organization isolation.
    • Added safer cookie and CSRF validation with resource limits.
    • Standardized API error responses for unavailable, invalid, and missing resources.
  • Documentation
    • Updated operational records and execution-plan guidance.
  • Chores
    • Updated local MinIO tooling and shell-file line-ending handling.

BeforeLights and others added 24 commits August 3, 2026 13:53
…y-reconciliation

fix: reconcile identity audit and entitlement foundations
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR hardens IAM session and tenant scoping, adds audit pagination and digest checks, persists project-scoped entitlement data, standardizes HTTP errors, updates MFA proof handling, tightens CSRF and cookie limits, revises execution budgets, and records reconciliation evidence.

Changes

Platform reconciliation and hardening

Layer / File(s) Summary
Contracts and persistence foundations
packages/domain/*, services/api/openapi/v1.json, services/api/prisma/*
Session, audit, entitlement, and MFA contracts now include the new scoped fields, pagination inputs, proof fields, errors, indexes, and migrations.
IAM scope and session enforcement
services/api/src/features/iam/*, services/api/test/features/iam/*
Membership selection is deterministic and scope-aware. Sessions persist organization and workspace identifiers. Refresh, access-token, device, MFA, and identity-bootstrap operations add scope, expiry, proof, and revision checks.
Audit scoping and pagination
services/api/src/features/aud/*, services/api/test/features/aud/*, packages/domain/src/audit/v1.ts
Audit event and seal reads support bounded cursor pagination, tenant visibility filtering, structural equality, digest verification, and persistence-unavailable errors.
Entitlement scope and reservation persistence
services/api/src/features/bua/*, services/api/test/features/bua/*
Usage records preserve project scope. Visibility queries include inherited scopes. Reservation updates enforce allowed transitions, revisions, structural equality, and transactions.
HTTP error mapping and module composition
services/api/src/app.module.ts, services/api/src/platform/http/*, services/api/test/http-contract.test.ts, services/api/test/openapi.test.ts
Controllers and filters expose typed problem responses. Request context validates production CSRF origins, unsafe mutations require idempotency keys, cookie parsing has resource limits, and session persistence is composed through the Prisma adapter.
Execution and local infrastructure controls
docs/plans/*, tools/repo-cli/*, infrastructure/local/*, .gitattributes
Normal execution slices use 30–50 commits with a 79-commit ceiling. Compose operations use readiness-derived timeouts. MinIO references are updated and shell files require LF endings.
Reconciliation evidence and traceability
docs/operations/*, docs/plans/requirement-traceability.json
Promotion disposition, reconciliation evidence, release gates, and requirement paths are documented. Implemented IAM, audit, and BUA requirements move to partial status.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant AuthenticationController
  participant RequestTenantContext
  participant SessionLifecycle
  participant Database
  Client->>AuthenticationController: Send authenticated request
  AuthenticationController->>RequestTenantContext: Resolve bearer principal
  RequestTenantContext->>SessionLifecycle: Find principal by access token
  SessionLifecycle->>Database: Validate token, session, scope, and membership
  Database-->>SessionLifecycle: Return scoped principal
  SessionLifecycle-->>RequestTenantContext: Return authentication result
  RequestTenantContext-->>AuthenticationController: Return tenant context
  AuthenticationController-->>Client: Return response or mapped problem details
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies this change as promotion of development history slice 4.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch promote/dev-20260803-04

Comment @coderabbitai help to get the list of available commands.

@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: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
services/api/openapi/v1.json (2)

7009-7076: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Add a 200 response schema for the entitlement usage endpoint.

The responses object for GET /v1/entitlements/usage documents 400, 500, and 503, but it does not document a 200 success response. Add the success schema for the append-only usage ledger state so the contract is complete, consistent with /v1/audit/events and /v1/audit/seals, which both retain their 200 blocks.

🤖 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 `@services/api/openapi/v1.json` around lines 7009 - 7076, Add a 200 response
entry to the responses object for EntitlementController.usage, defining the
success schema for the append-only usage ledger state and matching the existing
200 response structure used by the audit events and audit seals endpoints.

6932-7008: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Add a 200 response schema for the entitlement snapshot endpoint.

The responses object for GET /v1/entitlements/snapshots/{snapshotId} documents 400, 404, 500, and 503, but it does not document a 200 success response. Add the success schema so API consumers and generated clients know the shape of a successful snapshot read.

🤖 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 `@services/api/openapi/v1.json` around lines 6932 - 7008, Add a 200 response to
the responses object for EntitlementController.snapshot, documenting the
successful entitlement snapshot payload with the existing snapshot schema
reference used by the API specification. Keep the current error responses
unchanged and ensure the success response describes the returned snapshot
content.
🧹 Nitpick comments (21)
docs/plans/requirement-traceability.json (1)

1043-1047: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reconsider citing the reconciliation record for requirements that stay planned.

This entry keeps status and coverage as planned, but it now lists docs/operations/identity-audit-entitlement-reconciliation-2026-08-03.md as its only releaseEvidence. That document states the corresponding behavior is absent, so the reference reports evidence that does not exist. The same pattern applies to the other entries that remain planned in this file, for example AUD-016, AUD-017, AUD-018, AUD-020, AUD-021, AUD-022, AUD-023, BUA-006, BUA-009, BUA-010, BUA-011, BUA-013, BUA-014, BUA-016 through BUA-020, IAM-010, IAM-013, IAM-015, IAM-017, and IAM-018.

Keep the enumerated gate tokens for planned requirements, and add the reconciliation document only where status is partial.

♻️ Proposed change for AUD-013
       "releaseEvidence": [
-        "docs/operations/identity-audit-entitlement-reconciliation-2026-08-03.md"
+        "requirement-linked-tests",
+        "security-and-tenant-gate",
+        "release-manager-approval"
       ],
       "status": "planned",
       "coverage": "planned",
🤖 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 `@docs/plans/requirement-traceability.json` around lines 1043 - 1047, Update
the requirement entries in requirement-traceability so every requirement that
remains status and coverage “planned” has no reconciliation document in
releaseEvidence, while preserving their enumerated gate tokens. Add
identity-audit-entitlement-reconciliation-2026-08-03.md only to entries whose
status is “partial,” including the affected AUD, BUA, and IAM requirements
listed in the review.
services/api/test/prisma-foundation.test.mjs (1)

507-508: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Resolve the migration by name, not by position.

inventory[33] couples this test to migration ordering. A future migration can shift the index and make this test inspect the wrong file. Use the migration directory name directly and assert that the name exists in inventory.

Proposed fix
-  const sessionScopeMigration = await readFile(
-    path.join(migrationsDirectory, inventory[33], 'migration.sql'),
+  const sessionMigrationId = '20260803010000_iam_session_scope_binding';
+  assert.ok(inventory.includes(sessionMigrationId));
+  const sessionScopeMigration = await readFile(
+    path.join(migrationsDirectory, sessionMigrationId, 'migration.sql'),
     'utf8',
   );
🤖 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 `@services/api/test/prisma-foundation.test.mjs` around lines 507 - 508, Update
the migration lookup in the test around sessionScopeMigration to use the
expected migration directory name directly instead of inventory[33]. First
assert that this name exists in inventory, then pass the name to path.join so
the test remains stable when migration ordering changes.
services/api/src/features/bua/adapter/in-memory-entitlement-repository.adapter.ts (1)

76-85: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Share the reservation transition rule between both adapters.

sameReservationExceptStatus is now a pass-through wrapper over sameUsageReservationExceptStatusV1. validReservationTransition encodes a domain invariant, and services/api/src/features/bua/adapter/prisma-entitlement-repository.adapter.ts defines the same rule again around lines 456 and 460. If one copy changes, then one persistence path accepts a transition that the other rejects. Move validReservationTransition into ../application/entitlement-equality.js or a sibling transitions module, import it in both adapters, and call sameUsageReservationExceptStatusV1 directly.

🤖 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
`@services/api/src/features/bua/adapter/in-memory-entitlement-repository.adapter.ts`
around lines 76 - 85, Centralize the reservation transition invariant currently
duplicated by validReservationTransition in the in-memory and Prisma adapters.
Move it to ../application/entitlement-equality.js or a sibling transitions
module, import and reuse it in both adapters, and replace the
sameReservationExceptStatus wrapper with direct calls to
sameUsageReservationExceptStatusV1.
services/api/test/features/aud/audit-page-cursor.test.ts (1)

44-51: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Cover the remaining rejection branches.

parseAuditPageCursorV1 also rejects a non-integer or negative offset and any unknown key. The current cases do not reach those branches. Add two forged cursors so the fail-closed contract stays covered.

💚 Proposed additional cases
+void test('[AUD-001] audit page cursors reject forged payloads', () => {
+  const forge = (payload: Record<string, unknown>) =>
+    Buffer.from(JSON.stringify(payload), 'utf8').toString('base64url');
+  const scope = `workspace:${organizationId}:${workspaceId}`;
+  for (const payload of [
+    { version: 1, kind: 'events', scope, offset: -1 },
+    { version: 1, kind: 'events', scope, offset: 1.5 },
+    { version: 1, kind: 'events', scope, offset: 0, extra: 'x' },
+  ]) {
+    assert.deepEqual(parseAuditPageCursorV1(forge(payload), 'events', workspaceScope), {
+      accepted: false,
+      code: 'INVALID_CURSOR',
+    });
+  }
+});
🤖 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 `@services/api/test/features/aud/audit-page-cursor.test.ts` around lines 44 -
51, Extend the cursor cases in the test for parseAuditPageCursorV1 to include
forged cursors with a non-integer or negative offset and with an unknown key.
Assert both return accepted: false with code: INVALID_CURSOR, preserving the
existing fail-closed assertions.
services/api/src/features/aud/application/audit-ledger.service.ts (1)

60-71: 🩺 Stability & Availability | 🔵 Trivial

Confirm that descendant scopes still get sealed.

seal now reads only the exact context.tenantScope chain. A seal created at workspace scope no longer covers project-scoped events inside that workspace. Each scope that receives events now needs its own seal run. Confirm that the sealing schedule enumerates every active scope, and add an alert for scopes that hold unsealed events past the retention window.

🤖 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 `@services/api/src/features/aud/application/audit-ledger.service.ts` around
lines 60 - 71, Update the sealing workflow around AuditLedgerService.seal and
its scheduler so every active descendant scope with events receives its own seal
run, rather than relying on a parent workspace seal. Ensure the schedule
enumerates all active scopes, and add an alert when any scope retains unsealed
events beyond the retention window.
services/api/src/features/aud/api/audit.controller.ts (1)

42-44: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Document the page response body.

The response body changed from an array to a page object with items and an optional nextCursor. @ApiOkResponse() carries no schema, so the generated OpenAPI document does not describe the new shape. Add a response type or an inline schema so clients can generate correct models.

Also applies to: 70-72

🤖 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 `@services/api/src/features/aud/api/audit.controller.ts` around lines 42 - 44,
Update the audit controller’s paginated endpoint around its `@ApiOkResponse`
decorator to document the page-object response shape, including the items
collection and optional nextCursor field. Use the existing response/page DTO if
available, or define an inline OpenAPI schema, so generated clients no longer
interpret the response as an undocumented body.
services/api/src/features/aud/adapter/in-memory-audit-repository.adapter.ts (2)

42-53: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the duplicated pageOffset helper. Both audit adapters define an identical pageOffset function with the same 1-to-100 limit bound, the same cursor parse call, and the same AUD_PAGE_LIMIT_INVALID and AUD_CURSOR_INVALID error strings. The shared root cause is one page-input validation rule copied into two adapters. The controller pageLimit in services/api/src/features/aud/api/audit.controller.ts repeats the same bound a third time. If the bound changes in one place only, then the adapters and the API disagree on the accepted limit.

  • services/api/src/features/aud/adapter/in-memory-audit-repository.adapter.ts#L42-L53: delete this copy and import the shared helper.
  • services/api/src/features/aud/adapter/prisma-audit-repository.adapter.ts#L305-L316: delete this copy and import the same shared helper.

Place the helper and an exported AUDIT_PAGE_LIMIT_MAX_V1 constant in services/api/src/features/aud/application/audit-page-cursor.ts, then use that constant in the controller.

🤖 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 `@services/api/src/features/aud/adapter/in-memory-audit-repository.adapter.ts`
around lines 42 - 53, Extract the duplicated pageOffset validation into
services/api/src/features/aud/application/audit-page-cursor.ts, exporting
pageOffset and AUDIT_PAGE_LIMIT_MAX_V1 while preserving the existing limit and
cursor error behavior. In
services/api/src/features/aud/adapter/in-memory-audit-repository.adapter.ts
lines 42-53 and
services/api/src/features/aud/adapter/prisma-audit-repository.adapter.ts lines
305-316, remove the local pageOffset copies and import the shared helper. Update
services/api/src/features/aud/api/audit.controller.ts to use
AUDIT_PAGE_LIMIT_MAX_V1 for pageLimit validation.

98-124: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Align page ordering with the Prisma adapter.

This adapter orders event pages by occurredAt then eventId, and seal pages by sealedAt then rootDigest. The Prisma adapter orders both by createdAt then id. Both classes implement AuditRepositoryPortV1, so callers and tests observe two different page orders for the same API. Document the ordering contract in AuditRepositoryPortV1, and make both adapters use a comparable key.

Also applies to: 165-192

🤖 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 `@services/api/src/features/aud/adapter/in-memory-audit-repository.adapter.ts`
around lines 98 - 124, Document the pagination ordering contract in
AuditRepositoryPortV1, specifying that event and seal pages are ordered by
createdAt and then id. Update listEventPage and the corresponding seal-page
method in the in-memory adapter to sort by those keys, and align the Prisma
adapter’s ordering with the same comparable fields so both implementations
return identical page order.
services/api/src/features/aud/application/audit-repository.port.ts (1)

18-27: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove unused transaction methods from the port.

listEvents and listSeals have no production callers. Remove both declarations from AuditTransactionPortV1. Update the test that calls transaction.listEvents.

🤖 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 `@services/api/src/features/aud/application/audit-repository.port.ts` around
lines 18 - 27, Remove the unused listEvents and listSeals declarations from
AuditTransactionPortV1, leaving appendEvent, listEventsForScope, and saveSeal
unchanged. Update the test that invokes transaction.listEvents to stop using
that removed transaction method while preserving the test’s intended coverage.
services/api/src/features/aud/application/audit-page-cursor.ts (1)

11-16: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract a shared scopeKey helper. The audit cursor, Prisma adapter, and domain module contain identical private derivations. Export one helper from the shared tenant-scope module and use it in all three locations.

🤖 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 `@services/api/src/features/aud/application/audit-page-cursor.ts` around lines
11 - 16, Extract the duplicated scopeKey derivation into an exported helper in
the shared tenant-scope module, preserving the organization, workspace, and
project key formats. Update the audit cursor, Prisma adapter, and domain module
to import and use this shared helper instead of their private implementations.
services/api/src/features/bua/adapter/prisma-entitlement-repository.adapter.ts (2)

545-578: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Flattening per-scope-key query results loses ordering and adds avoidable round trips.

listUsageState issues one findMany per inherited scope key (up to three for a project context) for entries and again for reservations, each ordered independently by sequence/createdAt. After Promise.all, the groups are concatenated with .flat() without a final merge-sort. For a workspace or project context, this returns entries/reservations ordered by scope-key group first (organization rows, then workspace rows, then project rows) rather than by sequence/createdAt globally. If any downstream logic (for example, computing the next sequence value or determining chronological reservation state) relies on state.entries/state.reservations being globally ordered, this breaks that assumption.

Separately, issuing up to 6 concurrent queries per call on what is likely a hot path (EntitlementAdmissionService.admit/finalize/release all call listUsageState) adds unnecessary round trips. A single query per row type with scopeKey: { in: scopeKeys } (or organizationId filter as before) would preserve a single global orderBy and cut the query count.

♻️ Proposed fix: single query per row type with `IN` filter
   public async listUsageState(context: IamTenantContextV1): Promise<UsageLedgerStateV1> {
     const scopeKeys = inheritedUsageScopeKeys(context.tenantScope);
-    const entryQueries = (scopeKeys ?? [undefined]).map((key) =>
-      this.client.usageLedgerEntryRecord.findMany({
-        where:
-          key === undefined
-            ? { organizationId: context.tenantScope.organizationId }
-            : { scopeKey: key },
-        orderBy: { sequence: 'asc' },
-      }),
-    );
-    const reservationQueries = (scopeKeys ?? [undefined]).map((key) =>
-      this.client.usageReservationRecord.findMany({
-        where:
-          key === undefined
-            ? { organizationId: context.tenantScope.organizationId }
-            : { scopeKey: key },
-        orderBy: { createdAt: 'asc' },
-      }),
-    );
-    const [entryGroups, reservationGroups] = await Promise.all([
-      Promise.all(entryQueries),
-      Promise.all(reservationQueries),
-    ]);
-    const entryRows = entryGroups.flat();
-    const reservationRows = reservationGroups.flat();
+    const [entryRows, reservationRows] = await Promise.all([
+      this.client.usageLedgerEntryRecord.findMany({
+        where:
+          scopeKeys === undefined
+            ? { organizationId: context.tenantScope.organizationId }
+            : { scopeKey: { in: scopeKeys } },
+        orderBy: { sequence: 'asc' },
+      }),
+      this.client.usageReservationRecord.findMany({
+        where:
+          scopeKeys === undefined
+            ? { organizationId: context.tenantScope.organizationId }
+            : { scopeKey: { in: scopeKeys } },
+        orderBy: { createdAt: 'asc' },
+      }),
+    ]);

Do you want me to verify how state.entries/state.reservations ordering is consumed downstream (reserveUsageV1, finalizeUsageV1, releaseUsageV1) before finalizing this fix?

🤖 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
`@services/api/src/features/bua/adapter/prisma-entitlement-repository.adapter.ts`
around lines 545 - 578, Update listUsageState to replace the per-scope
entryQueries and reservationQueries with one findMany query per record type
using an IN filter for inherited scope keys, while preserving the organizationId
fallback when no keys exist. Keep the existing global orderBy values, visibility
filtering, and frozen return structure unchanged.

446-454: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Reuse scopeKey() for inherited scope keys.

The current literals match scopeKey(), but they duplicate its format. Build TenantScopeV1 objects and pass them to scopeKey() so future format changes cannot omit inherited usage entries or reservations.

🤖 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
`@services/api/src/features/bua/adapter/prisma-entitlement-repository.adapter.ts`
around lines 446 - 454, Update inheritedUsageScopeKeys to construct organization
and workspace TenantScopeV1 objects and derive their keys through scopeKey(),
adding the project scope the same way when applicable. Remove the duplicated
string literals while preserving the existing undefined result for organization
scopes and frozen key array.
services/api/test/features/bua/prisma-entitlement-repository.test.ts (1)

350-366: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Loose assertion on query count.

assert.ok(tenantQueries.length >= 4) only checks a lower bound. Consider asserting the exact expected count (or asserting per-call-site scope keys) so a future regression that drops a scoped lookup doesn't silently pass because the count is still >= 4. This is a test-quality nit, not a functional defect.

🤖 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 `@services/api/test/features/bua/prisma-entitlement-repository.test.ts` around
lines 350 - 366, The test’s query-count assertion is too permissive and can miss
a missing tenant-scoped lookup. In the test covering Prisma entitlement identity
lookups, replace the lower-bound check on tenantQueries with an exact expected
count or explicit assertions for each relevant repository/service call, while
retaining validation that every inspected query uses organizationId.
services/api/src/features/iam/adapter/prisma-session-lifecycle.adapter.ts (1)

241-279: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider reusing revokeRefreshFamily() inside revoke().

revokeRefreshFamily() and expireSession() duplicate logic that already exists, in slightly different form, in the unchanged revoke() method further down this file (updateMany on refresh tokens, session status update, updateMany on access tokens). Extract one shared implementation to avoid divergence between the two revocation paths.

Note that revoke() currently preserves an existing revokedAt via session.revokedAt ?? now, while revokeRefreshFamily() always writes now. Preserve that behavior if you unify the two.

🤖 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 `@services/api/src/features/iam/adapter/prisma-session-lifecycle.adapter.ts`
around lines 241 - 279, Unify the duplicated revocation updates by reusing or
extracting a shared implementation between revokeRefreshFamily() and revoke().
Preserve revoke()’s existing revokedAt value with session.revokedAt ?? now,
while still revoking the refresh tokens, session, and active access tokens; keep
expireSession()’s EXPIRED behavior unchanged.
services/api/src/features/iam/adapter/prisma-iam-repository.adapter.ts (2)

101-109: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Record a signal when a persisted membership row is skipped.

membershipFromRowOrSkip discards every validation error without any trace. A corrupted iam.membership_identity row then disappears from findMembership and listMemberships results, and no log or metric shows that authority data was dropped. Fail-closed selection is correct, but the silent drop makes the corruption undetectable.

Emit a structured warning or counter with the row id before returning undefined.

🤖 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 `@services/api/src/features/iam/adapter/prisma-iam-repository.adapter.ts`
around lines 101 - 109, Update membershipFromRowOrSkip to record a structured
warning or counter containing the persisted membership row’s id when
membershipFromRow throws, then preserve the existing fail-closed behavior by
returning undefined.

173-176: 🚀 Performance & Scalability | 🔵 Trivial | ⚖️ Poor tradeoff

Bound the organization-wide membership read.

listMemberships selects every membership row for the organization, then applies visibleInScope in memory. The query has no take limit and no scope predicate. For a large organization this loads the full membership table on each request.

Push the workspace and project predicates into the where clause, or add pagination to this port.

🤖 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 `@services/api/src/features/iam/adapter/prisma-iam-repository.adapter.ts`
around lines 173 - 176, Update listMemberships to avoid loading every
organization membership: push the workspace and project scope predicates into
the membershipIdentity.findMany where clause using the available context scope
fields, or implement bounded pagination for this repository port. Preserve
visibleInScope behavior while ensuring each request has a database-level scope
filter or take limit.
services/api/src/features/iam/adapter/in-memory-iam-repository.adapter.ts (1)

18-22: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the shared membership-authority selection into one helper.

scopeSpecificity and the filter-sort-first selection now exist twice. The same code exists in services/api/src/features/iam/adapter/prisma-iam-repository.adapter.ts at lines 133-137 and 154-167. Both adapters implement the same authority contract, so a change in one place can silently diverge from the other.

Move scopeSpecificity and a selectAuthoritativeMembership(memberships, context, principalId) helper into a shared application module, then call it from both adapters.

Also applies to: 44-55

🤖 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 `@services/api/src/features/iam/adapter/in-memory-iam-repository.adapter.ts`
around lines 18 - 22, Extract scopeSpecificity and the duplicated
filter-sort-first membership selection into a shared application-level helper,
exposing selectAuthoritativeMembership(memberships, context, principalId).
Update both InMemoryIamRepositoryAdapter and PrismaIamRepositoryAdapter to call
this helper while preserving the existing authority ordering and filtering
behavior.
services/api/test/features/iam/prisma-identity-bootstrap-repository.test.ts (1)

189-224: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for two personal organizations.

This test covers one personal organization plus one non-personal organization. The adapter also has a fail-closed branch at services/api/src/features/iam/adapter/prisma-identity-bootstrap-repository.adapter.ts line 290 that throws IAM_PERSISTED_ORGANIZATION_INVALID when more than one personal candidate exists. That branch selects which tenant a user is bootstrapped into, so it should not regress silently.

Seed a second personal organization with an owner membership and assert the rejection.

🤖 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 `@services/api/test/features/iam/prisma-identity-bootstrap-repository.test.ts`
around lines 189 - 224, Extend the test around bootstrap lookup in the existing
adapter test to seed a second personal organization with its active owner
membership for the same user, then assert that findByUserId rejects with
IAM_PERSISTED_ORGANIZATION_INVALID. Preserve the current unrelated-organization
setup and successful single-personal-organization assertion in a separate
scenario if needed.
services/api/src/features/iam/adapter/prisma-identity-bootstrap-repository.adapter.ts (1)

279-288: 🚀 Performance & Scalability | 🔵 Trivial | ⚖️ Poor tradeoff

Replace the per-candidate organization query with one batched read.

The loop awaits organizationIdentity.findUnique once for every owner membership. The bootstrap lookup runs on the sign-in path, so the round-trip count grows with the number of organizations the user owns.

Add findMany to the organization delegate and select all candidate ids in one query with personal: true, then apply the deterministic ordering in memory. The test double in services/api/test/features/iam/prisma-identity-bootstrap-repository.test.ts at lines 69-76 must gain the matching findMany implementation.

🤖 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
`@services/api/src/features/iam/adapter/prisma-identity-bootstrap-repository.adapter.ts`
around lines 279 - 288, Replace the per-membership findUnique calls in the
candidate organization loop with one organizationIdentity.findMany query using
all stable candidate IDs and personal: true, then map results back to
memberships while preserving deterministic membership ordering. Update the
Prisma test double’s organization delegate with the matching findMany
implementation.
services/api/test/features/iam/session-cookies.test.ts (1)

52-83: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Derive boundary fixtures from accessible cookie limits.

The limits are private constants, so the test cannot import them. Export all four limits used by these fixtures, or expose a public limits object. Add accepted at-limit cases for the 8,192-byte header, 64 segments, 4,096-character value, and 64-character name. Keep each header field within the value limit so the header case tests only the header boundary.

🤖 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 `@services/api/test/features/iam/session-cookies.test.ts` around lines 52 - 83,
Update the cookie limit fixtures used by readCookieValueV1 and serializeCookieV1
tests to derive from exported parser limits or a public limits object instead of
hardcoded private values. Export all four relevant limits, add accepted boundary
cases for the 8,192-byte header, 64 segments, 4,096-character value, and
64-character name, and ensure every field value in the header-boundary case
remains within the value limit.
services/api/prisma/schema/iam.prisma (1)

95-107: 🗄️ Data Integrity & Integration | 🔵 Trivial

Recreate databases with existing sessions before applying this migration.

This migration adds organization_id and workspace_id as NOT NULL without backfilling. It fails on any non-empty iam.sessions table.

🤖 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 `@services/api/prisma/schema/iam.prisma` around lines 95 - 107, Update the
sessions schema migration for the organizationId and workspaceId fields to
preserve existing iam.sessions rows by backfilling valid values before enforcing
NOT NULL constraints. Ensure the migration succeeds when the table is non-empty,
rather than requiring databases to be recreated.
🤖 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 `@docs/plans/000-platform-program.md`:
- Line 46: Align the execution limits in the policy with the limits established
in the paragraph describing docs/plans/004-luna-max-execution-plan.md: update
the later 30–70 commit range and 280-file split threshold to match the 30–50
commit range and 260-file cap, unless 280 is explicitly documented as a distinct
review-stop threshold.

In `@docs/plans/003-luna-handoff-runbook.md`:
- Line 94: Align the commit-budget references throughout the runbook, especially
the stale 30–70 and 99-commit limits around the ledger and checker guidance,
with the enforced 30–50 target and 79-commit maximum stated in step 12. If those
references intentionally describe a different scope, explicitly label and define
that scope; otherwise update them to the active-slice limits.

In `@packages/domain/src/audit/v1.ts`:
- Around line 307-322: Update listEventPage and the in-memory adapter to sort
paginated events by scopeKey, then sequence, with a stable deterministic
tie-breaker such as event identity; apply the same ordering in both
implementations so clients can reconstruct chains consistently across pages.
Leave verifyAuditEventDigestV1 digest-only and unchanged.

In `@services/api/src/features/aud/api/audit.controller.ts`:
- Around line 58-65: Update the error handling in the audit controller methods
wrapping listEventPage and listSealPage so AUD_CHAIN_INVALID is rethrown or
mapped to a non-retryable audit code instead of AUDIT_UNAVAILABLE. Add the
corresponding code to AuditProblemError and handle it in describe within the
problem-details filter with a non-retryable response; preserve the existing 503
mapping for other failures.

In `@services/api/src/features/iam/adapter/prisma-iam-repository.adapter.ts`:
- Around line 191-196: Update the membership identity creation flow around the
existingRow lookup to check MembershipIdentity by id alone before the
tenant-scoped operation, throwing IAM_REVISION_CONFLICT when the ID already
belongs to another organization. Also catch Prisma P2002 from create and convert
it to IAM_REVISION_CONFLICT as a race-safe fallback, since ProblemDetailsFilter
does not map P2002.

In
`@services/api/src/features/iam/adapter/prisma-identity-bootstrap-repository.adapter.ts`:
- Around line 201-211: Update the membership mapping before validateMembershipV1
so non-null membership.startsAt or membership.expiresAt values must successfully
convert via timestamp; otherwise throw IAM_PERSISTED_MEMBERSHIP_INVALID. Do not
pass an unparseable timestamp as undefined, and preserve the existing
optional-field behavior for null or absent timestamps.

In `@services/api/src/features/iam/adapter/prisma-mfa-repository.adapter.ts`:
- Around line 53-56: Update the MFA error handling path that maps
MfaProblemError codes in ProblemDetailsFilter to include
IAM_MFA_REVISION_CONFLICT, and return HttpStatus.CONFLICT for that code instead
of HttpStatus.INTERNAL_ERROR. Ensure repository errors from the Mfa repository
updateMany flow are classified through this MFA mapping.

In `@services/api/src/features/iam/api/authentication.controller.ts`:
- Around line 168-195: In signOut, call requestContext.resolve(request) before
the try block so its original authentication error propagates unchanged. Keep
only findPrincipal, the ownership validation, and revoke inside the catch that
maps unexpected failures to SESSION_UNAVAILABLE, preserving direct propagation
of SessionProblemError.

In `@services/api/src/platform/http/problem-details.filter.ts`:
- Around line 91-98: Add catalog entries for all five device-related
api.error.device_* message keys in both supported locales, or update the mapping
around the error response construction to use existing catalog keys. Ensure
formatMessageV1 resolves every possible device error code without returning
MISSING_MESSAGE.

In `@services/api/test/prisma-foundation.test.mjs`:
- Around line 512-521: Add coverage in the existing prisma-foundation migration
tests for upgrading databases that already contain sessions, verifying the
session-scope migration handles existing rows before enforcing the new
organization_id and workspace_id requirements. Anchor the test around
sessionScopeMigration and preserve the current assertions for the ALTER TABLE,
columns, and index.

---

Outside diff comments:
In `@services/api/openapi/v1.json`:
- Around line 7009-7076: Add a 200 response entry to the responses object for
EntitlementController.usage, defining the success schema for the append-only
usage ledger state and matching the existing 200 response structure used by the
audit events and audit seals endpoints.
- Around line 6932-7008: Add a 200 response to the responses object for
EntitlementController.snapshot, documenting the successful entitlement snapshot
payload with the existing snapshot schema reference used by the API
specification. Keep the current error responses unchanged and ensure the success
response describes the returned snapshot content.

---

Nitpick comments:
In `@docs/plans/requirement-traceability.json`:
- Around line 1043-1047: Update the requirement entries in
requirement-traceability so every requirement that remains status and coverage
“planned” has no reconciliation document in releaseEvidence, while preserving
their enumerated gate tokens. Add
identity-audit-entitlement-reconciliation-2026-08-03.md only to entries whose
status is “partial,” including the affected AUD, BUA, and IAM requirements
listed in the review.

In `@services/api/prisma/schema/iam.prisma`:
- Around line 95-107: Update the sessions schema migration for the
organizationId and workspaceId fields to preserve existing iam.sessions rows by
backfilling valid values before enforcing NOT NULL constraints. Ensure the
migration succeeds when the table is non-empty, rather than requiring databases
to be recreated.

In `@services/api/src/features/aud/adapter/in-memory-audit-repository.adapter.ts`:
- Around line 42-53: Extract the duplicated pageOffset validation into
services/api/src/features/aud/application/audit-page-cursor.ts, exporting
pageOffset and AUDIT_PAGE_LIMIT_MAX_V1 while preserving the existing limit and
cursor error behavior. In
services/api/src/features/aud/adapter/in-memory-audit-repository.adapter.ts
lines 42-53 and
services/api/src/features/aud/adapter/prisma-audit-repository.adapter.ts lines
305-316, remove the local pageOffset copies and import the shared helper. Update
services/api/src/features/aud/api/audit.controller.ts to use
AUDIT_PAGE_LIMIT_MAX_V1 for pageLimit validation.
- Around line 98-124: Document the pagination ordering contract in
AuditRepositoryPortV1, specifying that event and seal pages are ordered by
createdAt and then id. Update listEventPage and the corresponding seal-page
method in the in-memory adapter to sort by those keys, and align the Prisma
adapter’s ordering with the same comparable fields so both implementations
return identical page order.

In `@services/api/src/features/aud/api/audit.controller.ts`:
- Around line 42-44: Update the audit controller’s paginated endpoint around its
`@ApiOkResponse` decorator to document the page-object response shape, including
the items collection and optional nextCursor field. Use the existing
response/page DTO if available, or define an inline OpenAPI schema, so generated
clients no longer interpret the response as an undocumented body.

In `@services/api/src/features/aud/application/audit-ledger.service.ts`:
- Around line 60-71: Update the sealing workflow around AuditLedgerService.seal
and its scheduler so every active descendant scope with events receives its own
seal run, rather than relying on a parent workspace seal. Ensure the schedule
enumerates all active scopes, and add an alert when any scope retains unsealed
events beyond the retention window.

In `@services/api/src/features/aud/application/audit-page-cursor.ts`:
- Around line 11-16: Extract the duplicated scopeKey derivation into an exported
helper in the shared tenant-scope module, preserving the organization,
workspace, and project key formats. Update the audit cursor, Prisma adapter, and
domain module to import and use this shared helper instead of their private
implementations.

In `@services/api/src/features/aud/application/audit-repository.port.ts`:
- Around line 18-27: Remove the unused listEvents and listSeals declarations
from AuditTransactionPortV1, leaving appendEvent, listEventsForScope, and
saveSeal unchanged. Update the test that invokes transaction.listEvents to stop
using that removed transaction method while preserving the test’s intended
coverage.

In
`@services/api/src/features/bua/adapter/in-memory-entitlement-repository.adapter.ts`:
- Around line 76-85: Centralize the reservation transition invariant currently
duplicated by validReservationTransition in the in-memory and Prisma adapters.
Move it to ../application/entitlement-equality.js or a sibling transitions
module, import and reuse it in both adapters, and replace the
sameReservationExceptStatus wrapper with direct calls to
sameUsageReservationExceptStatusV1.

In
`@services/api/src/features/bua/adapter/prisma-entitlement-repository.adapter.ts`:
- Around line 545-578: Update listUsageState to replace the per-scope
entryQueries and reservationQueries with one findMany query per record type
using an IN filter for inherited scope keys, while preserving the organizationId
fallback when no keys exist. Keep the existing global orderBy values, visibility
filtering, and frozen return structure unchanged.
- Around line 446-454: Update inheritedUsageScopeKeys to construct organization
and workspace TenantScopeV1 objects and derive their keys through scopeKey(),
adding the project scope the same way when applicable. Remove the duplicated
string literals while preserving the existing undefined result for organization
scopes and frozen key array.

In `@services/api/src/features/iam/adapter/in-memory-iam-repository.adapter.ts`:
- Around line 18-22: Extract scopeSpecificity and the duplicated
filter-sort-first membership selection into a shared application-level helper,
exposing selectAuthoritativeMembership(memberships, context, principalId).
Update both InMemoryIamRepositoryAdapter and PrismaIamRepositoryAdapter to call
this helper while preserving the existing authority ordering and filtering
behavior.

In `@services/api/src/features/iam/adapter/prisma-iam-repository.adapter.ts`:
- Around line 101-109: Update membershipFromRowOrSkip to record a structured
warning or counter containing the persisted membership row’s id when
membershipFromRow throws, then preserve the existing fail-closed behavior by
returning undefined.
- Around line 173-176: Update listMemberships to avoid loading every
organization membership: push the workspace and project scope predicates into
the membershipIdentity.findMany where clause using the available context scope
fields, or implement bounded pagination for this repository port. Preserve
visibleInScope behavior while ensuring each request has a database-level scope
filter or take limit.

In
`@services/api/src/features/iam/adapter/prisma-identity-bootstrap-repository.adapter.ts`:
- Around line 279-288: Replace the per-membership findUnique calls in the
candidate organization loop with one organizationIdentity.findMany query using
all stable candidate IDs and personal: true, then map results back to
memberships while preserving deterministic membership ordering. Update the
Prisma test double’s organization delegate with the matching findMany
implementation.

In `@services/api/src/features/iam/adapter/prisma-session-lifecycle.adapter.ts`:
- Around line 241-279: Unify the duplicated revocation updates by reusing or
extracting a shared implementation between revokeRefreshFamily() and revoke().
Preserve revoke()’s existing revokedAt value with session.revokedAt ?? now,
while still revoking the refresh tokens, session, and active access tokens; keep
expireSession()’s EXPIRED behavior unchanged.

In `@services/api/test/features/aud/audit-page-cursor.test.ts`:
- Around line 44-51: Extend the cursor cases in the test for
parseAuditPageCursorV1 to include forged cursors with a non-integer or negative
offset and with an unknown key. Assert both return accepted: false with code:
INVALID_CURSOR, preserving the existing fail-closed assertions.

In `@services/api/test/features/bua/prisma-entitlement-repository.test.ts`:
- Around line 350-366: The test’s query-count assertion is too permissive and
can miss a missing tenant-scoped lookup. In the test covering Prisma entitlement
identity lookups, replace the lower-bound check on tenantQueries with an exact
expected count or explicit assertions for each relevant repository/service call,
while retaining validation that every inspected query uses organizationId.

In `@services/api/test/features/iam/prisma-identity-bootstrap-repository.test.ts`:
- Around line 189-224: Extend the test around bootstrap lookup in the existing
adapter test to seed a second personal organization with its active owner
membership for the same user, then assert that findByUserId rejects with
IAM_PERSISTED_ORGANIZATION_INVALID. Preserve the current unrelated-organization
setup and successful single-personal-organization assertion in a separate
scenario if needed.

In `@services/api/test/features/iam/session-cookies.test.ts`:
- Around line 52-83: Update the cookie limit fixtures used by readCookieValueV1
and serializeCookieV1 tests to derive from exported parser limits or a public
limits object instead of hardcoded private values. Export all four relevant
limits, add accepted boundary cases for the 8,192-byte header, 64 segments,
4,096-character value, and 64-character name, and ensure every field value in
the header-boundary case remains within the value limit.

In `@services/api/test/prisma-foundation.test.mjs`:
- Around line 507-508: Update the migration lookup in the test around
sessionScopeMigration to use the expected migration directory name directly
instead of inventory[33]. First assert that this name exists in inventory, then
pass the name to path.join so the test remains stable when migration ordering
changes.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9880ed80-ef8e-4545-8823-97499ec88913

📥 Commits

Reviewing files that changed from the base of the PR and between f85eefe and 0617995.

📒 Files selected for processing (84)
  • .gitattributes
  • apps/web/test/error-privacy-query.test.tsx
  • docs/operations/coderabbit-pr-29-disposition.md
  • docs/operations/identity-audit-entitlement-reconciliation-2026-08-03.md
  • docs/plans/000-platform-program.md
  • docs/plans/003-luna-handoff-runbook.md
  • docs/plans/004-luna-max-execution-plan.md
  • docs/plans/execution-orchestration.json
  • docs/plans/requirement-traceability.json
  • infrastructure/local/.env.example
  • infrastructure/local/compose.yml
  • infrastructure/local/minio/bootstrap-buckets.sh
  • packages/domain/src/audit/v1.ts
  • packages/domain/src/identity/v1.ts
  • packages/domain/src/mfa/v1.ts
  • packages/domain/test/audit-v1.test.mjs
  • packages/domain/test/identity-v1.test.mjs
  • services/api/openapi/v1.json
  • services/api/prisma/migrations/20260803010000_iam_session_scope_binding/migration.sql
  • services/api/prisma/migrations/20260803020000_bua_project_usage_scope/migration.sql
  • services/api/prisma/schema/bua.prisma
  • services/api/prisma/schema/iam.prisma
  • services/api/src/app.module.ts
  • services/api/src/features/aud/adapter/in-memory-audit-repository.adapter.ts
  • services/api/src/features/aud/adapter/prisma-audit-repository.adapter.ts
  • services/api/src/features/aud/api/audit.controller.ts
  • services/api/src/features/aud/application/audit-equality.ts
  • services/api/src/features/aud/application/audit-ledger.service.ts
  • services/api/src/features/aud/application/audit-page-cursor.ts
  • services/api/src/features/aud/application/audit-problem.error.ts
  • services/api/src/features/aud/application/audit-repository.port.ts
  • services/api/src/features/bua/adapter/in-memory-entitlement-repository.adapter.ts
  • services/api/src/features/bua/adapter/prisma-entitlement-repository.adapter.ts
  • services/api/src/features/bua/api/entitlement.controller.ts
  • services/api/src/features/bua/application/entitlement-equality.ts
  • services/api/src/features/bua/application/entitlement-problem.error.ts
  • services/api/src/features/iam/adapter/in-memory-iam-repository.adapter.ts
  • services/api/src/features/iam/adapter/in-memory-session-lifecycle.adapter.ts
  • services/api/src/features/iam/adapter/prisma-credential-lookup.adapter.ts
  • services/api/src/features/iam/adapter/prisma-device-identity-repository.adapter.ts
  • services/api/src/features/iam/adapter/prisma-iam-repository.adapter.ts
  • services/api/src/features/iam/adapter/prisma-identity-bootstrap-repository.adapter.ts
  • services/api/src/features/iam/adapter/prisma-mfa-repository.adapter.ts
  • services/api/src/features/iam/adapter/prisma-session-lifecycle.adapter.ts
  • services/api/src/features/iam/api/authentication.controller.ts
  • services/api/src/features/iam/api/device-identity.controller.ts
  • services/api/src/features/iam/api/mfa.controller.ts
  • services/api/src/features/iam/api/mfa.dto.ts
  • services/api/src/features/iam/api/session-cookies.ts
  • services/api/src/features/iam/api/session-refresh-response.dto.ts
  • services/api/src/features/iam/application/device-identity-problem.error.ts
  • services/api/src/features/iam/application/mfa.service.ts
  • services/api/src/features/iam/iam.module.ts
  • services/api/src/platform/http/csrf-protection.ts
  • services/api/src/platform/http/problem-details.filter.ts
  • services/api/src/platform/http/request-context.ts
  • services/api/src/platform/http/session-tenant-context.adapter.ts
  • services/api/test/features/aud/audit-ledger.service.test.ts
  • services/api/test/features/aud/audit-page-cursor.test.ts
  • services/api/test/features/aud/audit-repository.test.ts
  • services/api/test/features/aud/prisma-audit-repository.test.ts
  • services/api/test/features/bua/entitlement-repository.test.ts
  • services/api/test/features/bua/prisma-entitlement-repository.test.ts
  • services/api/test/features/foundation-module-composition.test.ts
  • services/api/test/features/iam/device-identity.controller.test.ts
  • services/api/test/features/iam/mfa.service.test.ts
  • services/api/test/features/iam/prisma-credential-lookup.test.ts
  • services/api/test/features/iam/prisma-device-identity-repository.test.ts
  • services/api/test/features/iam/prisma-iam-repository.test.ts
  • services/api/test/features/iam/prisma-identity-bootstrap-repository.test.ts
  • services/api/test/features/iam/prisma-mfa-repository.test.ts
  • services/api/test/features/iam/prisma-session-lifecycle.test.ts
  • services/api/test/features/iam/scoped-repository.test.ts
  • services/api/test/features/iam/session-cookies.test.ts
  • services/api/test/features/iam/session-lifecycle.test.ts
  • services/api/test/http-contract.test.ts
  • services/api/test/openapi.test.ts
  • services/api/test/platform/http/csrf-protection.test.ts
  • services/api/test/platform/http/session-tenant-context.test.ts
  • services/api/test/prisma-foundation.test.mjs
  • tools/repo-cli/src/check-execution-orchestration.mjs
  • tools/repo-cli/src/local-services.mjs
  • tools/repo-cli/test/execution-orchestration.test.mjs
  • tools/repo-cli/test/local-infrastructure.test.mjs

Comment thread docs/plans/000-platform-program.md
10. Inspect generated/runtime debris before commit. Do not commit `.venv`, `node_modules`, Gradle state, build output, logs, caches, secrets, local databases, Terraform state, or test reports unless the repository explicitly tracks a sanitized fixture.
11. Commit one independently reversible outcome with a semantic message. Do not combine contracts, an unrelated fix, and a different feature just to increase commit count.
12. Recount the active batch against its base. Do not open a normal PR below 30 commits; target about 70, stop accepting new tasks at 90, and never exceed 99. Split before the promotion diff reaches 280 changed files; the packet target is 260.
12. Recount the active PR slice against its base. Do not open below 30 commits; target 30–50, stop accepting new tasks at 50, and split at the next completed-task boundary. An exceptional boundary must never exceed 79 commits, preserving margin below CodeRabbit's 100-commit limit. Split before the promotion diff reaches 280 changed files; the packet target is 260.

@coderabbitai coderabbitai Bot Aug 3, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n \
  -e '30–70' \
  -e 'hard maximum is 99' \
  -e 'hard cap 99' \
  -e 'preferredMaximum' \
  -e 'hardMaximum' \
  docs/plans tools/repo-cli

Repository: DatabreezeService/databreeze-platform

Length of output: 1672


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- runbook policy references ---'
sed -n '86,104p;168,184p' docs/plans/003-luna-handoff-runbook.md

printf '%s\n' '--- orchestration policy and validator ---'
sed -n '34,50p' docs/plans/execution-orchestration.json
sed -n '1,90p' tools/repo-cli/src/check-execution-orchestration.mjs

printf '%s\n' '--- related plan scope ---'
sed -n '44,60p' docs/plans/000-platform-program.md

printf '%s\n' '--- test expectations ---'
sed -n '132,154p' tools/repo-cli/test/execution-orchestration.test.mjs

Repository: DatabreezeService/databreeze-platform

Length of output: 11911


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
import json
import re

runbook = Path("docs/plans/003-luna-handoff-runbook.md").read_text()
policy = json.loads(Path("docs/plans/execution-orchestration.json").read_text())
validator = Path("tools/repo-cli/src/check-execution-orchestration.mjs").read_text()

print("runbook_commit_policy_occurrences:")
for number, line in enumerate(runbook.splitlines(), 1):
    if re.search(r'30[–-](?:50|70)|(?:hard maximum|hard cap)\s+(?:is\s+)?(?:79|99)', line):
        print(f"{number}: {line}")

print("json_commit_budget:", policy.get("commitBudget"))
print("validator_commit_budget_literals:")
for number, line in enumerate(validator.splitlines(), 1):
    if re.search(r'commitBudget|preferredMinimum|preferredMaximum|hardMaximum|79|99', line):
        print(f"{number}: {line}")
PY

Repository: DatabreezeService/databreeze-platform

Length of output: 1620


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- ledger review policy and batch budgets ---'
rg -n -C 8 \
  -e '"reviewPolicy"' \
  -e '"commitBudget"' \
  -e '"preferredMinimum"' \
  -e '"preferredMaximum"' \
  -e '"hardMaximum"' \
  docs/plans/execution-orchestration.json

printf '%s\n' '--- validator budget logic ---'
sed -n '130,180p' tools/repo-cli/src/check-execution-orchestration.mjs
sed -n '180,235p' tools/repo-cli/src/check-execution-orchestration.mjs

printf '%s\n' '--- runbook and plan headings around policy scopes ---'
rg -n '^## |^### |feature PR|promotion PR|delivery.batch|commit budget|commitBudget|30–50|30–70|79|99' \
  docs/plans/003-luna-handoff-runbook.md \
  docs/plans/004-luna-max-execution-plan.md \
  docs/plans/000-platform-program.md

Repository: DatabreezeService/databreeze-platform

Length of output: 25773


Align the runbook commit budgets.

The runbook uses 30–50 commits with a 79-commit maximum for active slices, but lines 99 and 178 use 30–70 with a 99-commit maximum. The ledger and checker enforce a 79-commit batch maximum. Define separate scopes or update the stale references.

🤖 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 `@docs/plans/003-luna-handoff-runbook.md` at line 94, Align the commit-budget
references throughout the runbook, especially the stale 30–70 and 99-commit
limits around the ledger and checker guidance, with the enforced 30–50 target
and 79-commit maximum stated in step 12. If those references intentionally
describe a different scope, explicitly label and define that scope; otherwise
update them to the active-slice limits.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

System.Collections.Hashtable.Body

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@BeforeLights, I cannot determine the intended request from System.Collections.Hashtable.Body.

Please send the message or command that you intended to provide.

You are interacting with an AI system.

Comment on lines +307 to +322
if (!verifyAuditEventDigestV1(event, digestPort).accepted) return rejected('CHAIN_INVALID');
previousDigest = event.digest;
}
}
return Object.freeze({ accepted: true, value: true });
}

/** Verify one immutable event when a bounded page does not contain the full scope chain. */
export function verifyAuditEventDigestV1(
event: AuditEventV1,
digestPort: AuditDigestPortV1,
): AuditResultV1<true> {
const { digest, ...withoutDigest } = event;
if (digestPort.digest(canonicalEvent(withoutDigest)) !== digest) return rejected('CHAIN_INVALID');
return Object.freeze({ accepted: true, value: true });
}

@coderabbitai coderabbitai Bot Aug 3, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Inspect paginated audit read paths for ordering and integrity guarantees.
rg -n -C 5 'verifyAuditEventDigestV1|listEventPage|pageOffset' services/api/src/features/aud/adapter/prisma-audit-repository.adapter.ts

Repository: DatabreezeService/databreeze-platform

Length of output: 2239


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- domain verifier and chain logic ---'
sed -n '250,330p' packages/domain/src/audit/v1.ts
printf '%s\n' '--- paginated repository implementation ---'
sed -n '420,475p' services/api/src/features/aud/adapter/prisma-audit-repository.adapter.ts
printf '%s\n' '--- audit page types and pagination call sites ---'
rg -n -C 4 'type AuditPage|interface AuditPage|AuditPageInputV1|listEventPage|sequence|previousDigest' packages services --glob '!**/node_modules/**' | head -n 300
printf '%s\n' '--- relevant audit tests ---'
rg -n -C 5 'listEventPage|AUD_CHAIN_INVALID|verifyAuditEventDigestV1|previousDigest|createdAt.*id' . --glob '*test*' --glob '*spec*' --glob '!**/node_modules/**' | head -n 300

Repository: DatabreezeService/databreeze-platform

Length of output: 39681


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- pagination and visibility helpers ---'
sed -n '250,325p' services/api/src/features/aud/adapter/prisma-audit-repository.adapter.ts
printf '%s\n' '--- repository pagination tests ---'
sed -n '120,190p' services/api/test/features/aud/prisma-audit-repository.test.ts
printf '%s\n' '--- page cursor definitions and consumers ---'
rg -n -C 8 'createAuditPageCursorV1|AuditPageCursor|nextCursor|cursor:' packages/domain services/api/src services/api/test --glob '!**/node_modules/**' | head -n 400
printf '%s\n' '--- audit repository interfaces and in-memory pagination ---'
rg -n -C 8 'AuditPageInputV1|AuditPageV1|listEventPage' packages/domain services/api/src --glob '!**/node_modules/**' | head -n 400

Repository: DatabreezeService/databreeze-platform

Length of output: 50393


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- Prisma append and full-list paths ---'
sed -n '318,440p' services/api/src/features/aud/adapter/prisma-audit-repository.adapter.ts
printf '%s\n' '--- in-memory event ordering and pagination ---'
sed -n '80,130p' services/api/src/features/aud/adapter/in-memory-audit-repository.adapter.ts
printf '%s\n' '--- audit schema and migration constraints ---'
sed -n '1,35p' services/api/prisma/schema/aud.prisma
sed -n '1,70p' services/api/prisma/migrations/20260802020000_aud_audit_ledger/migration.sql
printf '%s\n' '--- exact cursor continuation code ---'
sed -n '440,472p' services/api/src/features/aud/adapter/prisma-audit-repository.adapter.ts
sed -n '95,130p' services/api/src/features/aud/adapter/in-memory-audit-repository.adapter.ts

Repository: DatabreezeService/databreeze-platform

Length of output: 13221


🏁 Script executed:

#!/bin/bash
set -eu
python3 - <<'PY'
from datetime import datetime

events = [
    {"eventId": "event-1", "sequence": 1, "occurredAt": "2026-01-01T00:00:02Z",
     "createdAt": "2026-01-01T00:00:02Z", "id": "00000000-0000-4000-8000-000000000001"},
    {"eventId": "event-2", "sequence": 2, "occurredAt": "2026-01-01T00:00:01Z",
     "createdAt": "2026-01-01T00:00:01Z", "id": "00000000-0000-4000-8000-000000000002"},
]
db_page_order = sorted(events, key=lambda e: (e["createdAt"], e["id"]))
chain_order = sorted(events, key=lambda e: e["sequence"])
memory_order = sorted(events, key=lambda e: (e["occurredAt"], e["eventId"]))
print("db_page_order:", [e["sequence"] for e in db_page_order])
print("chain_order:", [e["sequence"] for e in chain_order])
print("memory_page_order:", [e["sequence"] for e in memory_order])
assert [e["sequence"] for e in db_page_order] != [e["sequence"] for e in chain_order]
assert [e["sequence"] == i for i, e in enumerate(db_page_order, 1)] == [True, False]
PY

Repository: DatabreezeService/databreeze-platform

Length of output: 332


🏁 Script executed:

#!/bin/bash
set -eu
python3 - <<'PY'
events = [
    {"eventId": "event-1", "sequence": 1,
     "occurredAt": "2026-01-01T00:00:01Z",
     "createdAt": "2026-01-01T00:00:02Z",
     "id": "00000000-0000-4000-8000-000000000001"},
    {"eventId": "event-2", "sequence": 2,
     "occurredAt": "2026-01-01T00:00:02Z",
     "createdAt": "2026-01-01T00:00:01Z",
     "id": "00000000-0000-4000-8000-000000000002"},
]
db_order = sorted(events, key=lambda e: (e["createdAt"], e["id"]))
memory_order = sorted(events, key=lambda e: (e["occurredAt"], e["eventId"]))
chain_order = sorted(events, key=lambda e: e["sequence"])
print("db_page_order:", [e["sequence"] for e in db_order])
print("memory_page_order:", [e["sequence"] for e in memory_order])
print("chain_order:", [e["sequence"] for e in chain_order])
assert [e["sequence"] for e in db_order] == [2, 1]
assert [e["sequence"] for e in memory_order] == [1, 2]
assert [e["sequence"] for e in chain_order] == [1, 2]
PY

Repository: DatabreezeService/databreeze-platform

Length of output: 243


Order paginated events by chain position.

listEventPage and the in-memory adapter use different orders, and neither guarantees sequence order. Clients cannot reliably reconstruct or detect chain gaps across pages.

Order both adapters by scopeKey and sequence, with a stable tie-breaker. Keep verifyAuditEventDigestV1 digest-only for bounded pages.

🤖 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 `@packages/domain/src/audit/v1.ts` around lines 307 - 322, Update listEventPage
and the in-memory adapter to sort paginated events by scopeKey, then sequence,
with a stable deterministic tie-breaker such as event identity; apply the same
ordering in both implementations so clients can reconstruct chains consistently
across pages. Leave verifyAuditEventDigestV1 digest-only and unchanged.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

System.Collections.Hashtable.Body

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@BeforeLights, I cannot determine the intended action from System.Collections.Hashtable.Body.

Please state whether you want to fix, defer, or withdraw the audit pagination finding.

You are interacting with an AI system.

Comment on lines +58 to +65
try {
return await this.repository.listEventPage(context, {
limit,
...(cursor === undefined ? {} : { cursor }),
});
} catch {
throw new AuditProblemError('AUDIT_UNAVAILABLE');
}

@coderabbitai coderabbitai Bot Aug 3, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Do not map integrity failures to a retryable 503.

The bare catch converts every error from listEventPage and listSealPage into AuditProblemError('AUDIT_UNAVAILABLE'). The problem-details filter marks that code retryable: true with status 503. listEventPage throws AUD_CHAIN_INVALID when a page item fails digest verification. A corrupt or tampered audit record then appears to clients and operators as a transient outage, and clients retry it.

Distinguish the integrity failure from the unavailability failure. Rethrow or map AUD_CHAIN_INVALID to a non-retryable code.

🛡️ Proposed change
     try {
       return await this.repository.listEventPage(context, {
         limit,
         ...(cursor === undefined ? {} : { cursor }),
       });
-    } catch {
+    } catch (error) {
+      if (error instanceof Error && error.message === 'AUD_CHAIN_INVALID')
+        throw new AuditProblemError('AUDIT_INTEGRITY_FAILED');
       throw new AuditProblemError('AUDIT_UNAVAILABLE');
     }

This change also needs the new code in AuditProblemError and a branch in describe inside services/api/src/platform/http/problem-details.filter.ts.

Also applies to: 86-93

🤖 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 `@services/api/src/features/aud/api/audit.controller.ts` around lines 58 - 65,
Update the error handling in the audit controller methods wrapping listEventPage
and listSealPage so AUD_CHAIN_INVALID is rethrown or mapped to a non-retryable
audit code instead of AUDIT_UNAVAILABLE. Add the corresponding code to
AuditProblemError and handle it in describe within the problem-details filter
with a non-retryable response; preserve the existing 503 mapping for other
failures.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

System.Collections.Hashtable.Body

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@BeforeLights, I cannot determine the intended response from System.Collections.Hashtable.Body.

Please clarify whether this refers to the AUD_CHAIN_INVALID error handling finding.

You are interacting with an AI system.

Comment on lines +191 to 196
const existingRow = await this.client.membershipIdentity.findFirst({
where: {
id: membership.id,
organizationId: context.tenantScope.organizationId,
},
});

@coderabbitai coderabbitai Bot Aug 3, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Inspect problem-details mapping for Prisma errors and IAM domain errors.
set -euo pipefail

fd -t f 'problem-details.filter.ts' -x rg -n -C 5 'Prisma|P2002|IAM_|unique'
rg -n -C 3 'PrismaClientKnownRequestError|P2002' --glob '*.ts' services/api/src

Repository: DatabreezeService/databreeze-platform

Length of output: 175


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- target file ---'
cat -n services/api/src/features/iam/adapter/prisma-iam-repository.adapter.ts | sed -n '150,235p'

printf '%s\n' '--- error mappings and IAM conflict definitions ---'
rg -n -C 5 'PrismaClientKnownRequestError|P2002|IAM_REVISION_CONFLICT|problem-details|ProblemDetails' services/api/src --glob '*.ts' || true

printf '%s\n' '--- membershipIdentity access sites ---'
rg -n -C 4 'membershipIdentity|findUnique|findFirst|create\(' services/api/src/features/iam --glob '*.ts' || true

printf '%s\n' '--- repository and domain error files ---'
git ls-files | rg '(^|/)(problem-details|.*iam.*|.*IAM.*|.*error.*|.*exception.*)' | head -200

Repository: DatabreezeService/databreeze-platform

Length of output: 50393


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- problem-details filter ---'
cat -n services/api/src/platform/http/problem-details.filter.ts | sed -n '1,230p'

printf '%s\n' '--- IAM adapter declarations and imports ---'
cat -n services/api/src/features/iam/adapter/prisma-iam-repository.adapter.ts | sed -n '1,115p'

printf '%s\n' '--- membership schema and Prisma error references ---'
rg -n -C 6 'model MembershipIdentity|membershipIdentity|P2002|PrismaClientKnownRequestError' --glob '*.prisma' --glob '*.ts' . | head -300

printf '%s\n' '--- target-file history summary ---'
git diff --stat -- services/api/src/features/iam/adapter/prisma-iam-repository.adapter.ts
git status --short

Repository: DatabreezeService/databreeze-platform

Length of output: 45572


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- IAM conflict usage and error classes ---'
rg -n -C 5 'IAM_REVISION_CONFLICT|IAM_MEMBERSHIP_SCOPE_IMMUTABLE|class .*ProblemError|throw new .*ProblemError' services/api/src services/api/test --glob '*.ts' | head -400

printf '%s\n' '--- complete membership schema ---'
cat -n services/api/prisma/schema/iam.prisma | sed -n '70,105p'

printf '%s\n' '--- repository tests around saveMembership ---'
rg -n -C 8 'saveMembership|revision conflict|cross|organization|same id|create' services/api/test/features/iam/prisma-iam-repository.test.ts | head -300

printf '%s\n' '--- current branch and commit summary ---'
git diff -- services/api/src/features/iam/adapter/prisma-iam-repository.adapter.ts
git log -5 --oneline -- services/api/src/features/iam/adapter/prisma-iam-repository.adapter.ts

Repository: DatabreezeService/databreeze-platform

Length of output: 50393


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- prior and current mutation implementations ---'
git show c48aa8b4^:services/api/src/features/iam/adapter/prisma-iam-repository.adapter.ts 2>/dev/null | sed -n '180,215p' || true
git show c48aa8b4:services/api/src/features/iam/adapter/prisma-iam-repository.adapter.ts 2>/dev/null | sed -n '180,215p' || true

printf '%s\n' '--- saveMembership callers ---'
rg -n -C 6 'saveMembership\(' services/api/src services/api/test --glob '*.ts' | head -300

printf '%s\n' '--- commit details ---'
git show --stat --oneline c48aa8b4
git show --format= --no-ext-diff c48aa8b4 -- services/api/src/features/iam/adapter/prisma-iam-repository.adapter.ts | sed -n '1,180p'

Repository: DatabreezeService/databreeze-platform

Length of output: 20647


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
import re

adapter = Path("services/api/src/features/iam/adapter/prisma-iam-repository.adapter.ts").read_text()
schema = Path("services/api/prisma/schema/iam.prisma").read_text()
filter_source = Path("services/api/src/platform/http/problem-details.filter.ts").read_text()

assert re.search(r"model MembershipIdentity\s*\{.*?\bid\s+String\s+@id\b", schema, re.S)
lookup = re.search(
    r"membershipIdentity\.findFirst\(\{\s*where:\s*\{\s*id:\s*membership\.id,\s*organizationId:\s*context\.tenantScope\.organizationId",
    adapter,
    re.S,
)
assert lookup, "tenant-scoped findFirst was not found"
assert "membershipIdentity.create({ data: membershipRow(validated.value) })" in adapter
assert "PrismaClientKnownRequestError" not in filter_source
assert "P2002" not in filter_source

rows = [
    {"id": "same-id", "organizationId": "other-org"},
]
requested_id = "same-id"
requested_org = "request-org"
visible = next(
    (row for row in rows if row["id"] == requested_id and row["organizationId"] == requested_org),
    None,
)
assert visible is None
assert any(row["id"] == requested_id for row in rows), "collision fixture missing"
print("cross-organization lookup misses the existing primary-key row; create would violate MembershipIdentity.id `@id`")
print("ProblemDetailsFilter has no Prisma P2002 mapping")
PY

Repository: DatabreezeService/databreeze-platform

Length of output: 334


Handle cross-organization membership ID collisions.

MembershipIdentity.id is the primary key. A tenant-scoped findFirst misses the row when the same ID belongs to another organization, so create raises Prisma P2002. Add an ID-only collision check and throw IAM_REVISION_CONFLICT; handle P2002 as a race-safe fallback. ProblemDetailsFilter has no Prisma P2002 mapping.

🤖 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 `@services/api/src/features/iam/adapter/prisma-iam-repository.adapter.ts`
around lines 191 - 196, Update the membership identity creation flow around the
existingRow lookup to check MembershipIdentity by id alone before the
tenant-scoped operation, throwing IAM_REVISION_CONFLICT when the ID already
belongs to another organization. Also catch Prisma P2002 from create and convert
it to IAM_REVISION_CONFLICT as a race-safe fallback, since ProblemDetailsFilter
does not map P2002.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

System.Collections.Hashtable.Body

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@BeforeLights, I cannot determine the requested action from System.Collections.Hashtable.Body.

Please clarify whether you want to address, reject, or further investigate the membership ID-collision finding.

You are interacting with an AI system.

Comment on lines +201 to +211
const parsedMembership = validateMembershipV1({
id: membership.id,
principalType: membership.principalType,
principalId: membership.principalId,
scope: { scopeType: 'organization', organizationId: membership.organizationId },
roleId: membership.roleId,
status: membership.status,
...(membership.startsAt ? { startsAt: timestamp(membership.startsAt) } : {}),
...(membership.expiresAt ? { expiresAt: timestamp(membership.expiresAt) } : {}),
revision: membership.revision,
});

@coderabbitai coderabbitai Bot Aug 3, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Inspect timestamp parsing rules and membership validation of temporal fields.
set -euo pipefail

fd -t f -p 'packages/domain/src' -e ts | xargs rg -n -C 8 'parseStrictUtcTimestampV1'
fd -t f -p 'packages/domain/src' -e ts | xargs rg -n -C 12 'export function validateMembershipV1'

Repository: DatabreezeService/databreeze-platform

Length of output: 50393


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- timestamp schema and parser ---'
rg -n -C 12 'utcTimestampSchemaId|parseStrictUtcTimestampV1|StrictUtcTimestampV1' packages/domain/src/tenant-scope/v1.ts packages/domain/src 2>/dev/null | head -n 180

echo '--- membership validator ---'
rg -n -C 18 'validateMembershipV1|startsAt|expiresAt' packages/domain/src/identity/v1.ts | head -n 220

echo '--- adapter context ---'
sed -n '160,235p' services/api/src/features/iam/adapter/prisma-identity-bootstrap-repository.adapter.ts

Repository: DatabreezeService/databreeze-platform

Length of output: 25114


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- UTC timestamp contract definition ---'
rg -n -C 20 'utc-timestamp|UtcTimestamp|UTC timestamp|format.*date-time|date-time' packages services | head -n 260

echo '--- adapter timestamp helper and row types ---'
rg -n -C 14 'function timestamp|const timestamp|type .*MembershipIdentityDatabaseRowV1|interface .*MembershipIdentityDatabaseRowV1|membership\.startsAt|membership\.expiresAt' services/api/src/features/iam packages | head -n 260

echo '--- parser implementation ---'
rg -n -C 20 'function parseV1Contract|export function parseV1Contract|parseV1Contract<' packages | head -n 220

Repository: DatabreezeService/databreeze-platform

Length of output: 50393


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- contract files ---'
git ls-files packages/contracts | head -n 120

echo '--- timestamp schema references in contracts ---'
rg -n -C 18 'utc-timestamp|UtcTimestamp|date-time|parseV1Contract' packages/contracts --glob '*.ts' --glob '*.mjs' --glob '*.json' --glob '*.yaml' --glob '*.yml' | head -n 260

echo '--- bootstrap timestamp call sites ---'
rg -n -C 10 'timestamp\(' services/api/src/features/iam/adapter/prisma-identity-bootstrap-repository.adapter.ts

Repository: DatabreezeService/databreeze-platform

Length of output: 27515


🏁 Script executed:

#!/bin/bash
set -euo pipefail

cat -n packages/contracts/schemas/v1/utc-timestamp.schema.json

echo '--- generated validator for the same schema ---'
rg -n -C 12 'utc-timestamp|validateUtcTimestamp|validate_utc_timestamp|date-time' packages/contracts/generated/typescript/v1/validation.mjs packages/contracts/generated/python/databreeze_contracts/v1/_validation.py

Repository: DatabreezeService/databreeze-platform

Length of output: 16287


Reject invalid membership timestamps before validation.

A non-null startsAt or expiresAt that timestamp cannot parse is passed as an explicit undefined. validateMembershipV1 treats that value as absent. An invalid expiresAt therefore removes the expiry window. Throw IAM_PERSISTED_MEMBERSHIP_INVALID when a non-null timestamp cannot be converted.

🤖 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
`@services/api/src/features/iam/adapter/prisma-identity-bootstrap-repository.adapter.ts`
around lines 201 - 211, Update the membership mapping before
validateMembershipV1 so non-null membership.startsAt or membership.expiresAt
values must successfully convert via timestamp; otherwise throw
IAM_PERSISTED_MEMBERSHIP_INVALID. Do not pass an unparseable timestamp as
undefined, and preserve the existing optional-field behavior for null or absent
timestamps.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

System.Collections.Hashtable.Body

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@BeforeLights, I cannot map System.Collections.Hashtable.Body to the membership timestamp validation issue.

Please clarify whether you want to address or dismiss the finding. The finding remains open.

You are interacting with an AI system.

Comment on lines +53 to +56
updateMany(input: {
readonly where: Readonly<Record<string, unknown>>;
readonly data: Partial<MfaFactorDatabaseRowV1>;
}): Promise<{ readonly count: number }>;

@coderabbitai coderabbitai Bot Aug 3, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Confirm IAM_MFA_REVISION_CONFLICT is mapped in the problem-details filter.
rg -n "IAM_MFA_REVISION_CONFLICT" services/api/src/platform/http/problem-details.filter.ts services/api/src/features/iam

Repository: DatabreezeService/databreeze-platform

Length of output: 819


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- problem-details filter ---'
fd -i 'problem-details' services/api/src | xargs -r -n1 sh -c 'echo "--- $0"; rg -n -C 8 "IAM_MFA_REVISION_CONFLICT|Error|exception|status" "$0"' 
printf '%s\n' '--- error code definitions and HTTP mappings ---'
rg -n -C 5 "IAM_[A-Z0-9_]+|problem-details|HttpException|ConflictException|statusCode|status" services/api/src/features/iam services/api/src/platform/http

Repository: DatabreezeService/databreeze-platform

Length of output: 50394


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- MFA problem error ---'
cat -n services/api/src/features/iam/application/mfa-problem.error.ts
printf '%s\n' '--- MFA conflict flow ---'
rg -n -C 6 "IAM_MFA_REVISION_CONFLICT|MfaProblemError|saveState|withTransaction|ProblemDetailsFilter" \
  services/api/src/features/iam services/api/src/platform/http \
  | rg -v "mfa.service.ts:[0-9]+:.*(factors|revision|status)" \
  | head -n 240

Repository: DatabreezeService/databreeze-platform

Length of output: 25880


Map IAM_MFA_REVISION_CONFLICT as an HTTP 409 error.

The repository throws a plain Error, and ProblemDetailsFilter only maps MfaProblemError. Add the conflict code to the MFA error path and return HttpStatus.CONFLICT instead of INTERNAL_ERROR.

🤖 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 `@services/api/src/features/iam/adapter/prisma-mfa-repository.adapter.ts`
around lines 53 - 56, Update the MFA error handling path that maps
MfaProblemError codes in ProblemDetailsFilter to include
IAM_MFA_REVISION_CONFLICT, and return HttpStatus.CONFLICT for that code instead
of HttpStatus.INTERNAL_ERROR. Ensure repository errors from the Mfa repository
updateMany flow are classified through this MFA mapping.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

System.Collections.Hashtable.Body

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@BeforeLights, I cannot interpret System.Collections.Hashtable.Body as a response to this finding.

Please clarify whether you want to fix the IAM_MFA_REVISION_CONFLICT HTTP mapping, reject the finding, or defer it to follow-up work.

You are interacting with an AI system.

Comment on lines +168 to +195
@ApiBearerAuth()
@ApiOperation({ summary: 'Revoke a session and clear browser credentials' })
@ApiBody({ type: SessionSignOutDto })
@ApiUnauthorizedResponse({ description: 'The session could not be authenticated.' })
@ApiServiceUnavailableResponse({ description: 'Session persistence is unavailable.' })
async signOut(
@Body() input: SessionSignOutDto,
@Req() request: FastifyRequest,
@Res({ passthrough: true }) reply: FastifyReply,
): Promise<void> {
if (this.sessions === undefined) throw new SessionProblemError('SESSION_UNAVAILABLE');
await this.sessions.revoke(input.sessionId);
try {
if (this.requestContext === undefined) throw new SessionProblemError('SESSION_UNAVAILABLE');
const context = await this.requestContext.resolve(request);
const principal = await this.sessions.findPrincipal(input.sessionId);
if (
!principal ||
principal.userId !== context.actorId ||
principal.organizationId !== context.tenantScope.organizationId ||
(context.tenantScope.scopeType !== 'organization' &&
principal.workspaceId !== context.tenantScope.workspaceId)
)
throw new SessionProblemError('SESSION_INVALID');
await this.sessions.revoke(input.sessionId);
} catch (error) {
if (error instanceof SessionProblemError) throw error;
throw new SessionProblemError('SESSION_UNAVAILABLE');
}

@coderabbitai coderabbitai Bot Aug 3, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- controller structure ---'
ast-grep outline services/api/src/features/iam/api/authentication.controller.ts
printf '%s\n' '--- target method and other resolve() callers ---'
rg -n -C 8 'resolve\(' services/api/src/features/iam/api/authentication.controller.ts
printf '%s\n' '--- relevant contract test ---'
sed -n '650,720p' services/api/test/http-contract.test.ts
printf '%s\n' '--- controller target section ---'
sed -n '40,210p' services/api/src/features/iam/api/authentication.controller.ts

Repository: DatabreezeService/databreeze-platform

Length of output: 11304


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- context port and implementations ---'
rg -n -C 12 'interface RequestTenantContextPortV1|class .*RequestTenantContext|resolve\(request|findPrincipalByAccessToken' services
printf '%s\n' '--- problem error definitions and mapping ---'
rg -n -C 10 'class (SessionProblemError|AuthenticationProblemError)|AUTHENTICATION_UNAVAILABLE|SESSION_UNAVAILABLE' services/api/src services/api/test
printf '%s\n' '--- sign-out tests and controller callers ---'
rg -n -C 12 'sign-out|signOut|/v1/auth/me|resolve\(' services/api/test services/api/src/features/iam

Repository: DatabreezeService/databreeze-platform

Length of output: 50394


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- files defining request context and authentication errors ---'
rg -l 'RequestTenantContextPortV1|findPrincipalByAccessToken|class (SessionProblemError|AuthenticationProblemError)' services/api/src services/api/test | sort
printf '%s\n' '--- exact resolve implementations ---'
rg -n -C 8 'resolve\s*[:(]|async resolve|findPrincipalByAccessToken' services/api/src -g '*.ts' | head -n 240
printf '%s\n' '--- exact problem-code declarations and HTTP mapping ---'
rg -n -C 8 'AUTHENTICATION_UNAVAILABLE|SESSION_UNAVAILABLE|class SessionProblemError|class AuthenticationProblemError' services/api/src -g '*.ts' | head -n 300
printf '%s\n' '--- authentication controller tests only ---'
find services/api/test -type f -iname '*authentication*' -o -iname '*auth*controller*' | sort

Repository: DatabreezeService/databreeze-platform

Length of output: 50393


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- request-context adapter ---'
sed -n '1,125p' services/api/src/platform/http/session-tenant-context.adapter.ts
printf '%s\n' '--- request-context port ---'
cat -n services/api/src/platform/http/request-tenant-context.port.ts
printf '%s\n' '--- context behavior tests ---'
rg -n -C 10 'AUTHENTICATION_UNAVAILABLE|findPrincipalByAccessToken|resolve' services/api/test/platform/http/session-tenant-context.test.ts
printf '%s\n' '--- contract test context around authentication outage ---'
sed -n '600,705p' services/api/test/http-contract.test.ts
printf '%s\n' '--- authentication controller resolve callers ---'
python3 - <<'PY'
from pathlib import Path
p = Path("services/api/src/features/iam/api/authentication.controller.ts")
text = p.read_text()
for i, line in enumerate(text.splitlines(), 1):
    if "resolve(" in line:
        print(f"{i}: {line.strip()}")
PY

Repository: DatabreezeService/databreeze-platform

Length of output: 13196


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
from pathlib import Path

controller = Path("services/api/src/features/iam/api/authentication.controller.ts").read_text()
adapter = Path("services/api/src/platform/http/session-tenant-context.adapter.ts").read_text()
problem_filter = Path("services/api/src/platform/http/problem-details.filter.ts").read_text()

signout = controller[controller.index("async signOut("):]
assert signout.index("try {") < signout.index("this.requestContext.resolve(request)")
assert "if (error instanceof SessionProblemError) throw error;" in signout
assert "throw new SessionProblemError('SESSION_UNAVAILABLE');" in signout

lookup = adapter[adapter.index("try {", adapter.index("public async resolve")):]
assert "await this.sessions.findPrincipalByAccessToken(token)" in lookup
assert "throw new RequestTenantContextProblemError('AUTHENTICATION_UNAVAILABLE');" in lookup

assert "error instanceof RequestTenantContextProblemError" in problem_filter
assert "error.code === 'AUTHENTICATION_UNAVAILABLE'" in problem_filter
assert "code: invalidContext" in problem_filter

print("current signOut catches resolve() before preserving only SessionProblemError")
print("resolve() maps access-token lookup failures to AUTHENTICATION_UNAVAILABLE")
print("the HTTP problem filter preserves that code for RequestTenantContextProblemError")
print("the proposed scope change is required to preserve the authentication problem")
PY

Repository: DatabreezeService/databreeze-platform

Length of output: 484


Move resolve() outside the ownership-check try block.

resolve() can throw AUTHENTICATION_UNAVAILABLE, but the current catch changes it to SESSION_UNAVAILABLE. Keep only findPrincipal() and revoke() inside the catch. The me endpoint already preserves the original problem code.

🤖 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 `@services/api/src/features/iam/api/authentication.controller.ts` around lines
168 - 195, In signOut, call requestContext.resolve(request) before the try block
so its original authentication error propagates unchanged. Keep only
findPrincipal, the ownership validation, and revoke inside the catch that maps
unexpected failures to SESSION_UNAVAILABLE, preserving direct propagation of
SessionProblemError.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

System.Collections.Hashtable.Body

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@BeforeLights, System.Collections.Hashtable.Body does not clarify the signOut() error-handling behavior.

Please clarify whether requestContext.resolve(request) must preserve AUTHENTICATION_UNAVAILABLE, or whether a contract requires it to map to SESSION_UNAVAILABLE.

You are interacting with an AI system.

Comment on lines +91 to +98
return {
code: error.code,
correlationId,
messageKey: `api.error.${error.code.toLowerCase()}`,
retryable: error.code === 'DEVICE_UNAVAILABLE',
status,
};
}

@coderabbitai coderabbitai Bot Aug 3, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Confirm message catalog entries exist for all DeviceIdentityProblemError codes.
rg -n 'api\.error\.device_(not_found|request_rejected|revision_conflict|scope_denied|unavailable)'

Repository: DatabreezeService/databreeze-platform

Length of output: 175


🏁 Script executed:

printf '%s\n' 'Catalog candidates:'
git ls-files | rg -i '(^|/)(message|messages|locale|locales|translation|translations|catalog|i18n)(/|\.|$)|\.(json|yaml|yml|ts)$' | head -200
printf '%s\n' 'Problem error definitions and filter references:'
rg -n 'DeviceIdentityProblemError|DEVICE_(NOT_FOUND|REQUEST_REJECTED|REVISION_CONFLICT|SCOPE_DENIED|UNAVAILABLE)|messageKey' services/api/src

Repository: DatabreezeService/databreeze-platform

Length of output: 14320


🏁 Script executed:

printf '%s\n' 'i18n source map:'
ast-grep outline packages/i18n/src/catalogs-v1.ts
ast-grep outline packages/i18n/src/messages-v1.ts
printf '%s\n' 'Catalog and test references:'
rg -n -C 4 'api\.error|device_(not_found|request_rejected|revision_conflict|scope_denied|unavailable)|DEVICE_(NOT_FOUND|REQUEST_REJECTED|REVISION_CONFLICT|SCOPE_DENIED|UNAVAILABLE)' packages/i18n apps/web/src services/api/src

Repository: DatabreezeService/databreeze-platform

Length of output: 29434


🏁 Script executed:

sed -n '1,175p' packages/i18n/src/catalogs-v1.ts
sed -n '175,315p' packages/i18n/src/catalogs-v1.ts
printf '%s\n' 'Catalog tests:'
sed -n '1,220p' packages/i18n/test/catalogs-v1.test.mjs
sed -n '1,180p' packages/i18n/test/messages-v1.test.mjs

Repository: DatabreezeService/databreeze-platform

Length of output: 24300


🏁 Script executed:

printf '%s\n' 'API problem-detail consumers and message-key validation:'
rg -n -C 5 'formatMessageV1|MESSAGE_KEYS_V1|messageKey|ProblemDetails|problem-details|api\.error\.' --glob '!services/api/src/platform/http/problem-details.filter.ts' .
printf '%s\n' 'All api.error literals in tracked files:'
rg -n 'api\.error\.' --glob '!services/api/src/platform/http/problem-details.filter.ts' . || true

Repository: DatabreezeService/databreeze-platform

Length of output: 50394


🏁 Script executed:

printf '%s\n' 'Message-key consumers in source files:'
rg -n -C 3 'formatMessageV1|MESSAGE_KEYS_V1|messageKey|ProblemDetails' \
  --glob '*.ts' --glob '*.tsx' --glob '*.js' --glob '*.mjs' \
  --glob '!**/openapi/**' --glob '!**/*.generated.*' \
  apps packages services | head -300
printf '%s\n' 'API error keys outside the filter:'
rg -n 'api\.error\.' \
  --glob '*.ts' --glob '*.tsx' --glob '*.js' --glob '*.mjs' --glob '*.json' \
  --glob '!services/api/src/platform/http/problem-details.filter.ts' \
  --glob '!services/api/openapi/**' . || true
printf '%s\n' 'API package dependencies and i18n references:'
rg -n -C 3 '`@databreeze/i18n`|packages/i18n|i18n' services/api/package.json services/api/src apps/web/src packages/*/package.json

Repository: DatabreezeService/databreeze-platform

Length of output: 26809


🏁 Script executed:

python3 - <<'PY'
from pathlib import Path
import re

error_src = Path('services/api/src/features/iam/application/device-identity-problem.error.ts').read_text()
catalog_src = Path('packages/i18n/src/catalogs-v1.ts').read_text()

codes = re.findall(r"\|\s*'([^']+)'", error_src)
keys = set(re.findall(r"'([^']+)'(?::\s*entry|\s*:\s*entry)", catalog_src))
expected = [f"api.error.{code.lower()}" for code in codes]
print("expected:", expected)
print("catalog_matches:", [key for key in expected if key in keys])
print("missing:", [key for key in expected if key not in keys])
PY
sed -n '25,75p' packages/i18n/src/messages-v1.ts

Repository: DatabreezeService/databreeze-platform

Length of output: 2086


Add the device error keys to the message catalog.

All five api.error.device_* keys are absent. formatMessageV1 rejects these keys with MISSING_MESSAGE. Add entries in both locales or map the errors to existing catalog keys.

🤖 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 `@services/api/src/platform/http/problem-details.filter.ts` around lines 91 -
98, Add catalog entries for all five device-related api.error.device_* message
keys in both supported locales, or update the mapping around the error response
construction to use existing catalog keys. Ensure formatMessageV1 resolves every
possible device error code without returning MISSING_MESSAGE.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

System.Collections.Hashtable.Body

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@BeforeLights, I cannot relate System.Collections.Hashtable.Body to this finding.

Please clarify whether this message indicates that the device error keys are already resolved, intentionally unmapped, or out of scope.

You are interacting with an AI system.

Comment on lines +512 to +521
'ALTER TABLE "iam"."sessions"',
'ADD COLUMN "organization_id" UUID NOT NULL',
'ADD COLUMN "workspace_id" UUID NOT NULL',
'CREATE INDEX "sessions_scope_user_status_idx"',
]) {
assert.match(
sessionScopeMigration,
new RegExp(statement.replaceAll(/[.*+?^${}()|[\]\\]/g, '\\$&')),
);
}

@coderabbitai coderabbitai Bot Aug 3, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

migration='services/api/prisma/migrations/20260803010000_iam_session_scope_binding/migration.sql'
test -f "$migration"
rg -n 'ADD COLUMN|SET NOT NULL|UPDATE|DEFAULT|CREATE INDEX' "$migration"
rg -n 'sessions|migration' services/api/test services/api/prisma/migrations

Repository: DatabreezeService/databreeze-platform

Length of output: 12664


🏁 Script executed:

#!/bin/bash
set -euo pipefail

migration='services/api/prisma/migrations/20260803010000_iam_session_scope_binding/migration.sql'
schema='services/api/prisma/migrations/20260802000000_iam_identity_foundation/migration.sql'
test_file='services/api/test/prisma-foundation.test.mjs'

printf '%s\n' '--- migration ---'
cat -n "$migration"
printf '%s\n' '--- sessions table ---'
sed -n '84,110p' "$schema"
printf '%s\n' '--- relevant foundation test ---'
sed -n '480,530p' "$test_file"
printf '%s\n' '--- database test/config references ---'
rg -n --glob '!node_modules' --glob '!dist' \
  'DATABASE_URL|Prisma|postgres|migration.*test|migrate|iam\.sessions' \
  services/api/test services/api/package.json services/api/prisma package.json 2>/dev/null | head -250

Repository: DatabreezeService/databreeze-platform

Length of output: 34901


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- migration and schema semantics ---'
python3 - <<'PY'
from pathlib import Path
migration = Path("services/api/prisma/migrations/20260803010000_iam_session_scope_binding/migration.sql").read_text()
schema = Path("services/api/prisma/migrations/20260802000000_iam_identity_foundation/migration.sql").read_text()
print("migration:")
print(migration)
start = schema.index('CREATE TABLE "iam"."sessions"')
end = schema.index('\n);', start) + 3
print("sessions table:")
print(schema[start:end])
PY

printf '%s\n' '--- test/runtime configuration ---'
rg -n --glob '!node_modules' --glob '!dist' \
  'DATABASE_URL|Prisma|postgres|migration|migrate|iam\.sessions' \
  services/api/test services/api/package.json services/api/prisma package.json 2>/dev/null | head -300

Repository: DatabreezeService/databreeze-platform

Length of output: 37692


Add a migration-path test for existing sessions.

🤖 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 `@services/api/test/prisma-foundation.test.mjs` around lines 512 - 521, Add
coverage in the existing prisma-foundation migration tests for upgrading
databases that already contain sessions, verifying the session-scope migration
handles existing rows before enforcing the new organization_id and workspace_id
requirements. Anchor the test around sessionScopeMigration and preserve the
current assertions for the ALTER TABLE, columns, and index.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

System.Collections.Hashtable.Body

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@BeforeLights, I cannot determine the intended action from System.Collections.Hashtable.Body.

Please clarify whether this comment means the migration-path finding is fixed, not applicable, or deferred.

You are interacting with an AI system.

@BeforeLights

Copy link
Copy Markdown
Contributor Author

CodeRabbit disposition complete. This promotion packet received exactly one review (9880ed80-ef8e-4545-8823-97499ec88913); no second run will be requested.\n\n- All 10 actionable inline comments were reproduced and dispositioned; 9 were accepted and fixed in the dev-bound PR #36, and the legacy-session backfill request was rejected as unsafe under the no-legacy-migration assumption.\n- The two outside-diff entitlement 200-response claims were accepted and fixed in 1add34b.\n- Review-body suggestions, including accepted performance/authority/test improvements and rejected speculative audit scheduling/backfill requests, are recorded here: https://github.com/DatabreezeService/databreeze-platform/blob/dev/docs/operations/coderabbit-pr-35-disposition.md\n- PR #36 merged to dev as 9265e15 with all hosted checks green.\n\nThe reviewed PR #35 can now be merged to main; the remaining dev changes will be promoted in subsequent under-79-commit packets with one CodeRabbit review each.

@BeforeLights
BeforeLights merged commit 112b0f9 into main Aug 3, 2026
6 checks passed
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.

1 participant