promote: development history slice 4 - #35
Conversation
…y-reconciliation fix: reconcile identity audit and entitlement foundations
📝 WalkthroughWalkthroughThe 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. ChangesPlatform reconciliation and hardening
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
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
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 winAdd a
200response schema for the entitlement usage endpoint.The
responsesobject forGET /v1/entitlements/usagedocuments400,500, and503, but it does not document a200success response. Add the success schema for the append-only usage ledger state so the contract is complete, consistent with/v1/audit/eventsand/v1/audit/seals, which both retain their200blocks.🤖 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 winAdd a
200response schema for the entitlement snapshot endpoint.The
responsesobject forGET /v1/entitlements/snapshots/{snapshotId}documents400,404,500, and503, but it does not document a200success 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 winReconsider citing the reconciliation record for requirements that stay
planned.This entry keeps
statusandcoverageasplanned, but it now listsdocs/operations/identity-audit-entitlement-reconciliation-2026-08-03.mdas its onlyreleaseEvidence. 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 remainplannedin 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
plannedrequirements, and add the reconciliation document only wherestatusispartial.♻️ 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 winResolve 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 ininventory.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 winShare the reservation transition rule between both adapters.
sameReservationExceptStatusis now a pass-through wrapper oversameUsageReservationExceptStatusV1.validReservationTransitionencodes a domain invariant, andservices/api/src/features/bua/adapter/prisma-entitlement-repository.adapter.tsdefines the same rule again around lines 456 and 460. If one copy changes, then one persistence path accepts a transition that the other rejects. MovevalidReservationTransitioninto../application/entitlement-equality.jsor a sibling transitions module, import it in both adapters, and callsameUsageReservationExceptStatusV1directly.🤖 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 winCover the remaining rejection branches.
parseAuditPageCursorV1also rejects a non-integer or negativeoffsetand 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 | 🔵 TrivialConfirm that descendant scopes still get sealed.
sealnow reads only the exactcontext.tenantScopechain. 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 valueDocument the page response body.
The response body changed from an array to a page object with
itemsand an optionalnextCursor.@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 winExtract the duplicated
pageOffsethelper. Both audit adapters define an identicalpageOffsetfunction with the same 1-to-100 limit bound, the same cursor parse call, and the sameAUD_PAGE_LIMIT_INVALIDandAUD_CURSOR_INVALIDerror strings. The shared root cause is one page-input validation rule copied into two adapters. The controllerpageLimitinservices/api/src/features/aud/api/audit.controller.tsrepeats 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_V1constant inservices/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 winAlign page ordering with the Prisma adapter.
This adapter orders event pages by
occurredAttheneventId, and seal pages bysealedAtthenrootDigest. The Prisma adapter orders both bycreatedAtthenid. Both classes implementAuditRepositoryPortV1, so callers and tests observe two different page orders for the same API. Document the ordering contract inAuditRepositoryPortV1, 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 winRemove unused transaction methods from the port.
listEventsandlistSealshave no production callers. Remove both declarations fromAuditTransactionPortV1. Update the test that callstransaction.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 winExtract a shared
scopeKeyhelper. 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 winFlattening per-scope-key query results loses ordering and adds avoidable round trips.
listUsageStateissues onefindManyper inherited scope key (up to three for a project context) for entries and again for reservations, each ordered independently bysequence/createdAt. AfterPromise.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 bysequence/createdAtglobally. If any downstream logic (for example, computing the nextsequencevalue or determining chronological reservation state) relies onstate.entries/state.reservationsbeing 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/releaseall calllistUsageState) adds unnecessary round trips. A single query per row type withscopeKey: { in: scopeKeys }(ororganizationIdfilter as before) would preserve a single globalorderByand 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.reservationsordering 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 winReuse
scopeKey()for inherited scope keys.The current literals match
scopeKey(), but they duplicate its format. BuildTenantScopeV1objects and pass them toscopeKey()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 valueLoose 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 winConsider reusing
revokeRefreshFamily()insiderevoke().
revokeRefreshFamily()andexpireSession()duplicate logic that already exists, in slightly different form, in the unchangedrevoke()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 existingrevokedAtviasession.revokedAt ?? now, whilerevokeRefreshFamily()always writesnow. 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 winRecord a signal when a persisted membership row is skipped.
membershipFromRowOrSkipdiscards every validation error without any trace. A corruptediam.membership_identityrow then disappears fromfindMembershipandlistMembershipsresults, 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 tradeoffBound the organization-wide membership read.
listMembershipsselects every membership row for the organization, then appliesvisibleInScopein memory. The query has notakelimit 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
whereclause, 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 winExtract the shared membership-authority selection into one helper.
scopeSpecificityand the filter-sort-first selection now exist twice. The same code exists inservices/api/src/features/iam/adapter/prisma-iam-repository.adapter.tsat 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
scopeSpecificityand aselectAuthoritativeMembership(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 winAdd 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.tsline 290 that throwsIAM_PERSISTED_ORGANIZATION_INVALIDwhen 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 tradeoffReplace the per-candidate organization query with one batched read.
The loop awaits
organizationIdentity.findUniqueonce 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
findManyto the organization delegate and select all candidate ids in one query withpersonal: true, then apply the deterministic ordering in memory. The test double inservices/api/test/features/iam/prisma-identity-bootstrap-repository.test.tsat lines 69-76 must gain the matchingfindManyimplementation.🤖 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 winDerive 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 | 🔵 TrivialRecreate databases with existing sessions before applying this migration.
This migration adds
organization_idandworkspace_idasNOT NULLwithout backfilling. It fails on any non-emptyiam.sessionstable.🤖 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
📒 Files selected for processing (84)
.gitattributesapps/web/test/error-privacy-query.test.tsxdocs/operations/coderabbit-pr-29-disposition.mddocs/operations/identity-audit-entitlement-reconciliation-2026-08-03.mddocs/plans/000-platform-program.mddocs/plans/003-luna-handoff-runbook.mddocs/plans/004-luna-max-execution-plan.mddocs/plans/execution-orchestration.jsondocs/plans/requirement-traceability.jsoninfrastructure/local/.env.exampleinfrastructure/local/compose.ymlinfrastructure/local/minio/bootstrap-buckets.shpackages/domain/src/audit/v1.tspackages/domain/src/identity/v1.tspackages/domain/src/mfa/v1.tspackages/domain/test/audit-v1.test.mjspackages/domain/test/identity-v1.test.mjsservices/api/openapi/v1.jsonservices/api/prisma/migrations/20260803010000_iam_session_scope_binding/migration.sqlservices/api/prisma/migrations/20260803020000_bua_project_usage_scope/migration.sqlservices/api/prisma/schema/bua.prismaservices/api/prisma/schema/iam.prismaservices/api/src/app.module.tsservices/api/src/features/aud/adapter/in-memory-audit-repository.adapter.tsservices/api/src/features/aud/adapter/prisma-audit-repository.adapter.tsservices/api/src/features/aud/api/audit.controller.tsservices/api/src/features/aud/application/audit-equality.tsservices/api/src/features/aud/application/audit-ledger.service.tsservices/api/src/features/aud/application/audit-page-cursor.tsservices/api/src/features/aud/application/audit-problem.error.tsservices/api/src/features/aud/application/audit-repository.port.tsservices/api/src/features/bua/adapter/in-memory-entitlement-repository.adapter.tsservices/api/src/features/bua/adapter/prisma-entitlement-repository.adapter.tsservices/api/src/features/bua/api/entitlement.controller.tsservices/api/src/features/bua/application/entitlement-equality.tsservices/api/src/features/bua/application/entitlement-problem.error.tsservices/api/src/features/iam/adapter/in-memory-iam-repository.adapter.tsservices/api/src/features/iam/adapter/in-memory-session-lifecycle.adapter.tsservices/api/src/features/iam/adapter/prisma-credential-lookup.adapter.tsservices/api/src/features/iam/adapter/prisma-device-identity-repository.adapter.tsservices/api/src/features/iam/adapter/prisma-iam-repository.adapter.tsservices/api/src/features/iam/adapter/prisma-identity-bootstrap-repository.adapter.tsservices/api/src/features/iam/adapter/prisma-mfa-repository.adapter.tsservices/api/src/features/iam/adapter/prisma-session-lifecycle.adapter.tsservices/api/src/features/iam/api/authentication.controller.tsservices/api/src/features/iam/api/device-identity.controller.tsservices/api/src/features/iam/api/mfa.controller.tsservices/api/src/features/iam/api/mfa.dto.tsservices/api/src/features/iam/api/session-cookies.tsservices/api/src/features/iam/api/session-refresh-response.dto.tsservices/api/src/features/iam/application/device-identity-problem.error.tsservices/api/src/features/iam/application/mfa.service.tsservices/api/src/features/iam/iam.module.tsservices/api/src/platform/http/csrf-protection.tsservices/api/src/platform/http/problem-details.filter.tsservices/api/src/platform/http/request-context.tsservices/api/src/platform/http/session-tenant-context.adapter.tsservices/api/test/features/aud/audit-ledger.service.test.tsservices/api/test/features/aud/audit-page-cursor.test.tsservices/api/test/features/aud/audit-repository.test.tsservices/api/test/features/aud/prisma-audit-repository.test.tsservices/api/test/features/bua/entitlement-repository.test.tsservices/api/test/features/bua/prisma-entitlement-repository.test.tsservices/api/test/features/foundation-module-composition.test.tsservices/api/test/features/iam/device-identity.controller.test.tsservices/api/test/features/iam/mfa.service.test.tsservices/api/test/features/iam/prisma-credential-lookup.test.tsservices/api/test/features/iam/prisma-device-identity-repository.test.tsservices/api/test/features/iam/prisma-iam-repository.test.tsservices/api/test/features/iam/prisma-identity-bootstrap-repository.test.tsservices/api/test/features/iam/prisma-mfa-repository.test.tsservices/api/test/features/iam/prisma-session-lifecycle.test.tsservices/api/test/features/iam/scoped-repository.test.tsservices/api/test/features/iam/session-cookies.test.tsservices/api/test/features/iam/session-lifecycle.test.tsservices/api/test/http-contract.test.tsservices/api/test/openapi.test.tsservices/api/test/platform/http/csrf-protection.test.tsservices/api/test/platform/http/session-tenant-context.test.tsservices/api/test/prisma-foundation.test.mjstools/repo-cli/src/check-execution-orchestration.mjstools/repo-cli/src/local-services.mjstools/repo-cli/test/execution-orchestration.test.mjstools/repo-cli/test/local-infrastructure.test.mjs
| 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. |
There was a problem hiding this comment.
🗄️ 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-cliRepository: 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.mjsRepository: 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}")
PYRepository: 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.mdRepository: 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.
There was a problem hiding this comment.
System.Collections.Hashtable.Body
There was a problem hiding this comment.
@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.
| 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 }); | ||
| } |
There was a problem hiding this comment.
🗄️ 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.tsRepository: 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 300Repository: 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 400Repository: 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.tsRepository: 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]
PYRepository: 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]
PYRepository: 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.
There was a problem hiding this comment.
System.Collections.Hashtable.Body
There was a problem hiding this comment.
@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.
| try { | ||
| return await this.repository.listEventPage(context, { | ||
| limit, | ||
| ...(cursor === undefined ? {} : { cursor }), | ||
| }); | ||
| } catch { | ||
| throw new AuditProblemError('AUDIT_UNAVAILABLE'); | ||
| } |
There was a problem hiding this comment.
🩺 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.
There was a problem hiding this comment.
System.Collections.Hashtable.Body
There was a problem hiding this comment.
@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.
| const existingRow = await this.client.membershipIdentity.findFirst({ | ||
| where: { | ||
| id: membership.id, | ||
| organizationId: context.tenantScope.organizationId, | ||
| }, | ||
| }); |
There was a problem hiding this comment.
🩺 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/srcRepository: 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 -200Repository: 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 --shortRepository: 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.tsRepository: 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")
PYRepository: 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.
There was a problem hiding this comment.
System.Collections.Hashtable.Body
There was a problem hiding this comment.
@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.
| 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, | ||
| }); |
There was a problem hiding this comment.
🔒 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.tsRepository: 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 220Repository: 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.tsRepository: 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.pyRepository: 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.
There was a problem hiding this comment.
System.Collections.Hashtable.Body
There was a problem hiding this comment.
@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.
| updateMany(input: { | ||
| readonly where: Readonly<Record<string, unknown>>; | ||
| readonly data: Partial<MfaFactorDatabaseRowV1>; | ||
| }): Promise<{ readonly count: number }>; |
There was a problem hiding this comment.
🗄️ 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/iamRepository: 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/httpRepository: 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 240Repository: 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.
There was a problem hiding this comment.
System.Collections.Hashtable.Body
There was a problem hiding this comment.
@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.
| @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'); | ||
| } |
There was a problem hiding this comment.
🎯 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.tsRepository: 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/iamRepository: 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*' | sortRepository: 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()}")
PYRepository: 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")
PYRepository: 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.
There was a problem hiding this comment.
System.Collections.Hashtable.Body
There was a problem hiding this comment.
@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.
| return { | ||
| code: error.code, | ||
| correlationId, | ||
| messageKey: `api.error.${error.code.toLowerCase()}`, | ||
| retryable: error.code === 'DEVICE_UNAVAILABLE', | ||
| status, | ||
| }; | ||
| } |
There was a problem hiding this comment.
📐 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/srcRepository: 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/srcRepository: 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.mjsRepository: 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' . || trueRepository: 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.jsonRepository: 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.tsRepository: 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.
There was a problem hiding this comment.
System.Collections.Hashtable.Body
There was a problem hiding this comment.
@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.
| '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, '\\$&')), | ||
| ); | ||
| } |
There was a problem hiding this comment.
🗄️ 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/migrationsRepository: 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 -250Repository: 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 -300Repository: 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.
There was a problem hiding this comment.
System.Collections.Hashtable.Body
There was a problem hiding this comment.
@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.
|
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. |
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
Summary by CodeRabbit