# RBAC Policy Configuration Type Contracts
#
# Defines types for the file-based RBAC layer.
# Identity (who the user is) comes from JWT claims / AuthContext.
# This file defines what groups are allowed to do (policy rules + effects).

let EffectTypes = ["redirect", "log", "audit_log", "notify", "response"] in
let ResourceKinds = ["endpoint", "page", "asset"] in
let Outcomes = ["allow", "deny"] in

{
  # Validate that a value belongs to a fixed set
  OneOf = fun allowed => std.contract.from_predicate (fun v => std.array.elem v allowed),

  # Effect type discriminator contract
  EffectType
    | doc "Possible effect types: redirect | log | audit_log | notify | response"
    = OneOf EffectTypes,

  # Policy effect: describes what to do when a rule matches
  # The `type` field discriminates the variant
  PolicyEffect = {
    type
      | OneOf EffectTypes
      | doc "Effect variant discriminator",

    # --- redirect: issue HTTP redirect to another URL ------------------
    to
      | String
      | doc "redirect: destination URL. Supports {request.path}, {user.email}, {user.id}"
      | optional,

    # --- log | audit_log: write to application or audit log -----------
    level
      | String
      | doc "log|audit_log: severity level (debug|info|warn|error)"
      | default = "info",

    message
      | String
      | doc "log|audit_log|notify: message template with interpolation support"
      | optional,

    event
      | String
      | doc "audit_log: semantic event name (e.g. 'access_denied')"
      | optional,

    # --- notify: send to external channel ----------------------------
    channel
      | String
      | doc "notify: destination channel (security|slack|webhook|email)"
      | optional,

    # --- response: return HTTP response directly ---------------------
    status
      | Number
      | doc "response: HTTP status code (401, 403, 302 ...)"
      | default = 403,

    body
      | { _ : Dyn }
      | doc "response: JSON response body"
      | optional,
  },

  # A single access rule: matches resource kind, URL pattern, HTTP methods
  PolicyRule = {
    resource
      | OneOf ResourceKinds
      | doc "Resource kind: endpoint | page | asset",

    pattern
      | String
      | doc "Glob pattern matched against the request path (e.g. '/api/*', '/*')",

    outcome
      | OneOf Outcomes
      | doc "Decision when this rule matches: allow | deny",

    methods
      | Array String
      | doc "HTTP methods this rule applies to. Empty array = all methods"
      | default = [],

    effects
      | Array PolicyEffect
      | doc "Effects executed when this rule matches"
      | default = [],
  },

  # Group definition with optional inheritance
  FileRbacGroup = {
    name
      | String
      | doc "Group identifier (must match role names from JWT, e.g. 'admin', 'moderator')",

    parent_groups
      | Array String
      | doc "Groups whose permissions are inherited (BFS expansion)"
      | default = [],

    permissions
      | Array PolicyRule
      | doc "Access rules for this group (first match wins)"
      | default = [],
  },

  # Per-user permission override (highest priority, matched by email or sub)
  FileRbacUserOverride = {
    id
      | String
      | doc "User identifier: email address or JWT sub claim",

    permissions
      | Array PolicyRule
      | doc "User-specific rules (override all group rules)"
      | default = [],
  },

  # Default rules for unauthenticated and fallback behaviour
  FileRbacDefaults = {
    unauthenticated_allow
      | Array PolicyRule
      | doc "Explicit allow rules for unauthenticated requests (first match wins)"
      | default = [],

    unauthenticated_deny
      | Array PolicyRule
      | doc "Explicit deny rules for unauthenticated requests (evaluated after allow)"
      | default = [],

    authenticated_fallback
      | OneOf Outcomes
      | doc "Outcome when an authenticated user matches no rule"
      | default = "deny",

    unauthenticated_fallback
      | OneOf Outcomes
      | doc "Outcome when an unauthenticated user matches no rule"
      | default = "deny",

    fallback_effects
      | Array PolicyEffect
      | doc "Effects applied when the fallback outcome is triggered"
      | default = [],
  },

  # Root RBAC configuration record
  FileRbacConfig = {
    groups
      | Array FileRbacGroup
      | doc "Group definitions with their permissions"
      | default = [],

    user_overrides
      | Array FileRbacUserOverride
      | doc "Per-user permission overrides (highest priority)"
      | default = [],

    defaults
      | FileRbacDefaults
      | doc "Fallback rules for unmatched requests"
      | default = {},
  },
}
