feat(#462): add federation get verb #683

Merged
jason.woltje merged 1 commits from feat/federation-m3-verb-get into next 2026-06-25 03:44:55 +00:00
Owner

Summary

Implements FED-M3-06 get verb for inbound federation reads:

  • POST /api/federation/v1/get/:resource/:id
  • AuthGuard -> ScopeService -> FederationGetQueryService pipeline
  • returns one local-source tagged item on success
  • returns federation error envelopes for 404 not_found and 403 scope_violation
  • keeps audit persistence deferred to M4 / no request or response body persistence

Verification

  • pnpm --filter @mosaicstack/gateway test -- src/federation/server/verbs/tests/get.controller.spec.ts src/federation/server/verbs/tests/get-query.service.spec.ts — pass (2 files / 10 tests)
  • pnpm --filter @mosaicstack/gateway build — pass
  • pnpm build — pass
  • pnpm typecheck — pass
  • pnpm lint — pass
  • pnpm format:check — pass
  • ~/.config/mosaic/tools/codex/codex-code-review.sh --uncommitted — approve, 0 findings

Notes

  • Branch rebased onto main 86e106fcc9 after M3-05 merged.
  • FederationModule registers both list and get verb controllers/services.
  • No self-merge; review-of-record required for this trust-boundary change.

Refs #462

## Summary Implements FED-M3-06 get verb for inbound federation reads: - POST /api/federation/v1/get/:resource/:id - AuthGuard -> ScopeService -> FederationGetQueryService pipeline - returns one local-source tagged item on success - returns federation error envelopes for 404 not_found and 403 scope_violation - keeps audit persistence deferred to M4 / no request or response body persistence ## Verification - pnpm --filter @mosaicstack/gateway test -- src/federation/server/verbs/__tests__/get.controller.spec.ts src/federation/server/verbs/__tests__/get-query.service.spec.ts — pass (2 files / 10 tests) - pnpm --filter @mosaicstack/gateway build — pass - pnpm build — pass - pnpm typecheck — pass - pnpm lint — pass - pnpm format:check — pass - ~/.config/mosaic/tools/codex/codex-code-review.sh --uncommitted — approve, 0 findings ## Notes - Branch rebased onto main 86e106fcc9a1dfa3a18f7846bb477be128794aad after M3-05 merged. - FederationModule registers both list and get verb controllers/services. - No self-merge; review-of-record required for this trust-boundary change. Refs #462
Author
Owner

REVIEW-OF-RECORD — REQUEST CHANGES (#683, head 87a5f479ec). The scoped happy-path tests pass, but the trust-boundary requirements for FED-M3-06 are not met yet.

CRITICAL #1 — getNote uses OR-widening instead of the M3-05 AND-intersect. In get-query.service.ts, the decision is hasPersonalAccess || hasTeamAccess (if (!hasPersonalAccess && !hasTeamAccess) deny). That leaks both directions: (a) any user's note on an authorized/team-visible mission is returned because missionIds.includes(row.missionId) is enough, and (b) the subject user's note on a mission outside the authorized mission/project set is returned because includePersonal && row.userId === subjectUserId is enough. This must mirror the approved list verb: missionTasks.userId = subjectUserId AND missionTasks.missionId in authorizedMissionIds, with no OR personal/team branch.

CRITICAL #2 — missing federation context still becomes a plain Error/500. GetController currently throws new Error('Federation context missing after auth guard'); the required behavior is the structured federation unauthorized envelope (401), matching ListController.

IMPORTANT #3 — unsupported resources do not fail closed. getByResource has no default branch; if an unexpected resource reaches it, query.get() can resolve undefined and the controller dereferences result.status, producing a 500 instead of an explicit deny/invalid-request path.

IMPORTANT #4 — coverage does not prove the trust boundary. The current get-query tests are mocked/unit guardrails only; there is no PGlite/scoped regression proving cross-user note exclusion or subject-note-on-unauthorized-mission exclusion. Please add the same kind of DB-backed regressions that closed #682 for listNotes, plus controller coverage for missing-fed-context -> 401 and unsupported resource fail-closed.

Reviewer verification on head 87a5f479: scoped get tests 10/10 pass; gateway typecheck pass; gateway lint pass; prettier check pass for changed get/module/scratchpad files. These gates are green locally, but they do not cover the blockers above. REQUEST CHANGES.

REVIEW-OF-RECORD — REQUEST CHANGES (#683, head 87a5f479ec01a33e057221778e5d187eafd1073a). The scoped happy-path tests pass, but the trust-boundary requirements for FED-M3-06 are not met yet. CRITICAL #1 — getNote uses OR-widening instead of the M3-05 AND-intersect. In get-query.service.ts, the decision is `hasPersonalAccess || hasTeamAccess` (`if (!hasPersonalAccess && !hasTeamAccess) deny`). That leaks both directions: (a) any user's note on an authorized/team-visible mission is returned because `missionIds.includes(row.missionId)` is enough, and (b) the subject user's note on a mission outside the authorized mission/project set is returned because `includePersonal && row.userId === subjectUserId` is enough. This must mirror the approved list verb: `missionTasks.userId = subjectUserId` AND `missionTasks.missionId in authorizedMissionIds`, with no OR personal/team branch. CRITICAL #2 — missing federation context still becomes a plain Error/500. GetController currently throws `new Error('Federation context missing after auth guard')`; the required behavior is the structured federation unauthorized envelope (401), matching ListController. IMPORTANT #3 — unsupported resources do not fail closed. getByResource has no default branch; if an unexpected resource reaches it, `query.get()` can resolve undefined and the controller dereferences `result.status`, producing a 500 instead of an explicit deny/invalid-request path. IMPORTANT #4 — coverage does not prove the trust boundary. The current get-query tests are mocked/unit guardrails only; there is no PGlite/scoped regression proving cross-user note exclusion or subject-note-on-unauthorized-mission exclusion. Please add the same kind of DB-backed regressions that closed #682 for listNotes, plus controller coverage for missing-fed-context -> 401 and unsupported resource fail-closed. Reviewer verification on head 87a5f479: scoped get tests 10/10 pass; gateway typecheck pass; gateway lint pass; prettier check pass for changed get/module/scratchpad files. These gates are green locally, but they do not cover the blockers above. REQUEST CHANGES.
jason.woltje changed target branch from main to next 2026-06-25 02:46:42 +00:00
jason.woltje added 1 commit 2026-06-25 02:46:42 +00:00
feat(#462): add federation get verb
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
87a5f479ec
jason.woltje force-pushed feat/federation-m3-verb-get from 87a5f479ec to 80a259b206 2026-06-25 03:02:47 +00:00 Compare
Author
Owner

REVIEW-OF-RECORD — APPROVE (#683, head 80a259b206). Re-review confirms all prior blockers from comment 15971 are resolved.

Trust boundary: getNote now mirrors the approved list verb boundary. A note is returned only when row.userId === subjectUserId AND row.missionId is in the authorized mission IDs derived from grant/native-RBAC-visible projects. The old personal/team OR-widening branch is gone, so team scope no longer widens into another user's notes and personal subject ownership no longer bypasses the authorized mission/project set.

Error handling/fail-closed: missing federation context now returns a structured 401 unauthorized federation envelope, not a plain Error/500. getByResource now has an explicit default denial for unsupported resources, which the controller maps to a structured 403 scope violation instead of dereferencing undefined.

Coverage: the new PGlite regressions prove cross-user note exclusion, subject-note-on-unauthorized-mission exclusion, valid subject+authorized-mission intersection, and includePersonal=false exclusion. Controller tests cover missing-fed-context 401 and unsupported-resource fail-closed behavior.

Reviewer verification on head 80a259b2: scoped gateway get tests 17/17 pass; gateway typecheck pass; gateway lint pass; prettier check pass for changed get/module/scratchpad files. PR CI reported green on pipeline 1620. APPROVE.

REVIEW-OF-RECORD — APPROVE (#683, head 80a259b2061df1892a124a0a946049aa8ab78d4e). Re-review confirms all prior blockers from comment 15971 are resolved. Trust boundary: getNote now mirrors the approved list verb boundary. A note is returned only when `row.userId === subjectUserId` AND `row.missionId` is in the authorized mission IDs derived from grant/native-RBAC-visible projects. The old personal/team OR-widening branch is gone, so team scope no longer widens into another user's notes and personal subject ownership no longer bypasses the authorized mission/project set. Error handling/fail-closed: missing federation context now returns a structured `401 unauthorized` federation envelope, not a plain Error/500. `getByResource` now has an explicit default denial for unsupported resources, which the controller maps to a structured 403 scope violation instead of dereferencing undefined. Coverage: the new PGlite regressions prove cross-user note exclusion, subject-note-on-unauthorized-mission exclusion, valid subject+authorized-mission intersection, and includePersonal=false exclusion. Controller tests cover missing-fed-context 401 and unsupported-resource fail-closed behavior. Reviewer verification on head 80a259b2: scoped gateway get tests 17/17 pass; gateway typecheck pass; gateway lint pass; prettier check pass for changed get/module/scratchpad files. PR CI reported green on pipeline 1620. APPROVE.
jason.woltje merged commit 838701bde2 into next 2026-06-25 03:44:55 +00:00
jason.woltje deleted branch feat/federation-m3-verb-get 2026-06-25 03:44:55 +00:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mosaicstack/stack#683