Skip to content

fix(ri-exchange): exchange records never carry a creator, so four-eyes is a no-op and audit attribution is lost #518

Closed

Description

@cristim

Summary

RI exchange records never carry a creator, so the four-eyes check from #501 is a no-op in production and approver attribution is incomplete.

Location (origin/main 496d9d7)

  • internal/server/handler_ri_exchange.go:329 exchangeToConfigRecord copies every field except CreatedByUserID, so every production exchange row has a NULL creator.
  • internal/api/handler_ri_exchange.go:2390 requireDifferentRIExchangeApprover returns nil when the creator is NULL, so the check is skipped for all rows. Owner policy for creator-less rows is open in fix(purchase): scheduler-created executions have no creator and cannot pass four-eyes #233.
  • internal/api/handler_ri_exchange.go:2220-2222 token approval calls TransitionRIExchangeStatus(..., nil), so transitioned_by is NULL. StampRIExchangeApprovedBy is only called on the session path (~2281), so approved_by is never set for token approvals even when a session resolves.
  • Wrong comments: internal/api/handler_ri_exchange.go:2220 ("no session user"), internal/config/types.go:963-965 ("populated for dashboard-initiated exchanges" although no such writer exists).

Failure scenario

A user creates an exchange, then approves it themselves. Four-eyes does not fire because the stored creator is NULL. Audit rows show no actor for token approvals.

Context

Found in the #501 review. Related: #233, #82.

Acceptance

Exchange rows created by a session user store created_by_user_id, four-eyes rejects a self-approval in a test using the real adapter, and token approvals record the resolved actor when one exists.

Activity

  1. cristim commented on Oct 6, 2026

    @cristim
    MemberAuthor

    Status after PR #602 (Refs, in review): leg (c) is met: token-path RI-exchange approvals now record the resolved actor (transitioned_by) and stamp approved_by through a helper shared with the session path (new test fails on main). Still open, needing an owner decision: (a) copying a creator onto exchange rows: cloud-commitments-go pkg/exchange.ExchangeRecord has no creator field and every writer of ri_exchange_history is the scheduler or the ladder (no session-created exchange path exists), so first decide which creator scheduler exchanges should carry (a system user, the config owner, or NULL by design) and whether the exchange library needs a creator field; (b) four-eyes on RI exchanges with a real adapter: requireDifferentRIExchangeApprover (handler_ri_exchange.go ~2423) returns nil when CreatedByUserID is nil, and #233 (NULL-creator policy) is written for purchase executions only, so an RI-exchange counterpart may be needed.

  2. IAmJSD commented on Oct 16, 2026

    @IAmJSD
    Collaborator

    Claiming: working on this

  3. self-assigned this
    on Oct 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions