Skip to content

[Fusion] Add the operation authorization middleware with denial decisions and the denial error contract - #39

Open
michaelstaib wants to merge 17 commits into
mst/policy-v2from
mst/auth-middleware
Open

michaelstaib wants to merge 17 commits into
mst/policy-v2from
mst/auth-middleware

Conversation

@michaelstaib

Copy link
Copy Markdown
Owner

Ticket: hc-fork-2-jzk.4 (Authorization middleware, decisions and error contract)

Stacked on #38. This PR's diff is against mst/auth-gateway-options.

  • Middleware. OperationAuthorizationMiddleware runs after planning. For each variable set it evaluates every policy once with all its occurrences, builds one immutable AuthorizationDecisions, and sets the synthetic $__fusion_auth_N variables the planner emitted. There is no re-planning. An unprotected operation costs one null check.
  • Evaluation rules.
    • A missing principal skips scopes and named policies and records them as denied, unauthenticated.
    • A second verdict on an entry throws.
    • An unresolved policy name is a generic 500 for any principal, with the policy name only in diagnostics.
    • Policies receive arguments with variables resolved, including variables nested in input objects and lists.
    • Entries of incremental plans that do not run for a variable set (@defer(if: false)) are skipped.
  • Results. A denied field always renders null.
    • Error mode adds one error per occurrence with a full indexed path, through the finalization pass.
    • Null mode is silent, except that a denied non-null field reports the generic non-null violation and propagates.
  • Error contract.
    • Two denial codes, unauthenticated and unauthorized, with messages that do not reveal the model.
    • Opt-in attribution adds directive, policy (named policies only) and requiredScopes (@requiresScopes only) from the first denying descriptor.
    • Escalated denials (RejectRequestOn) are request errors with no data key and nothing fetched, plus coordinate when attributed.
  • Plan. OperationPlan.Authorization is built once per plan. The plan JSON, the plan id and OperationPlanner.Version are unchanged.

History: two agents were lost to network errors along the way, and the work was recovered and continued. It took two review rounds. Review found two real bugs, both fixed and tested: nested variables were not resolved for policy arguments, and @defer(if: false) produced phantom duplicate policy entries.

Verified at 3a8312c:

  • dotnet build src/All.slnx: 0 errors.
  • Fusion.Execution.Tests on net10: 1368 passed, 2 skipped.
  • Fusion.AspNetCore.Tests on net10: 637 passed, 48 skipped.
  • The Authorization namespace also passes on net8 and net9.

Not verified:

  • Full suites on net8, net9 and net11.
  • Subscription events (jzk.6).
  • HTTP status over the wire for 401, 403 and 500 (fmr.1).
  • ErrorHandlingMode.Halt with denials.
  • Allocation claims, which were checked by reading the code and not measured.

Minor review notes, not fixed:

  • The "which incremental plans run" rule exists in both the executor and the evaluator. If they drift apart, the evaluator would fail open.
  • No committed test covers a denial under @defer(if:). The reviewer probed it and it behaved correctly.
  • Some escalation tests use absence assertions, and one names more than it proves.
  • Two test doubles expose IReadOnlyCollection.
  • 3a8312c also moves the executor's delivery-group check into DeliveryGroup.IsActive.

Existing gateway behavior found while reviewing, not caused by this PR:

  • Conditional @skip and @include selections are returned after unconditional ones, which is not request order. One snapshot here pins that order.
  • An unconditional @defer nested inside @defer(if: false) is never delivered.

Copilot AI balanced review requested due to automatic review settings October 7, 2026 20:58

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

…al in the defer agreement tests and share the payload reader
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants