# Lint configuration for the API contracts (ADR 0013, docs/spec/06-api.md section 15).
# Run from the repository root (pinned version, updated by Renovate):
#   npx -y @redocly/cli@2.62.1 lint --config docs/api/redocly.yaml
# Without file arguments both APIs below are linted. A single file:
#   npx -y @redocly/cli@2.62.1 lint docs/api/openapi.yaml --config docs/api/redocly.yaml
#
# The lists of known actions, rate-limit classes and receive actions between the
# `BEGIN GENERATED` markers are generated from docs/spec/actions.yaml (ADR 0049,
# machine-readable action catalogue): regenerate them with `make catalog`; `make catalog-check`
# fails on a hand edit. A new action enters docs/spec/actions.yaml first, then the contracts.

extends:
  - recommended

apis:
  rest@v1:
    root: ./openapi.yaml
    rules:
      rule/operation-id-camel-case:
        subject:
          type: Operation
          property: operationId
        assertions:
          pattern: /^[a-z][a-zA-Z0-9]*$/
        message: operationId must be lowerCamelCase.
  gateway@v1:
    root: ./asyncapi.yaml
    rules:
      # Authentication is in-band (`identify`/`resume` frames with DPoP proof), not part of the
      # WebSocket handshake, so AsyncAPI security schemes cannot express it.
      security-defined: off

rules:
  # Schemas for gateway frames are only referenced from asyncapi.yaml.
  no-unused-components: off
  # OpenAPI 3.1 allows `description` next to `$ref` (JSON Schema 2020-12); we use it for field docs.
  spec-ref-siblings: off
  # Replaced by rule/operation-4xx-response below, which exempts operations with rate-limit
  # class `none` (webhook event descriptions under `webhooks:`): they have no client error.
  operation-4xx-response: off
  rule/operation-4xx-response:
    subject:
      type: Responses
    where:
      - subject:
          type: Operation
          property: x-rate-limit-class
        assertions:
          notPattern: /^none$/
    assertions:
      requireAny: ['400', '401', '403', '404', '409', '410', '412', '413', '415', '416', '422', '428', '429']
    message: Operation must document at least one 4xx response.
  operation-summary: error
  tag-description: error
  no-invalid-media-type-examples: error
  no-invalid-schema-examples: error

  # 05-actions.md rule 3: every operation names exactly one known action.
  rule/authz-action-required:
    subject:
      type: Operation
    assertions:
      required:
        - x-authz-action
    message: Every operation needs x-authz-action from docs/spec/05-actions.md.
  # System actions (right `system`, 05 rule 10) are never on a route, op or event, so the
  # generated lists leave them out.
  rule/authz-action-known:
    subject:
      type: Operation
      property: x-authz-action
    assertions:
      enum:
        # BEGIN GENERATED: authz_actions (make catalog)
        - access.explain
        - access.hints.read
        - access.preview
        - access.view_as
        - access.view_as.end
        - agent.billing_mode.set
        - agent.billing_mode.set_all
        - agent.config.read
        - agent.create
        - agent.credential_holder.set
        - agent.disable
        - agent.enable
        - agent.events.consume
        - agent.heartbeat
        - agent.invoke
        - agent.read
        - agent.tool_class.set
        - agent.triggerers.read
        - agent.update
        - agent_session.bind
        - agent_session.read
        - agent_session.report
        - agent_session.reset
        - app.install
        - app.read
        - app.resume
        - app.scopes.update
        - app.suspend
        - app.uninstall
        - audit.read
        - audit.verify
        - auth.login.begin
        - auth.login.complete
        - auth.logout
        - auth.step_up
        - auth.token.refresh
        - channel.archive
        - channel.behavior.read
        - channel.behavior.update
        - channel.create
        - channel.operator.set
        - channel.project.bind
        - channel.read
        - channel.read_only.set
        - channel.reorder
        - channel.update
        - client_metrics.submit
        - command.autocomplete
        - command.autocomplete.respond
        - command.invoke
        - command.read
        - command.register
        - command.remove
        - component.update
        - console.csp_report
        - cost.read.aggregate
        - cost.read.thread
        - cost.reconcile
        - cost.reconcile.read
        - cost.reconcile.submit
        - dev_environment.manage
        - dev_environment.read
        - dev_environment.report
        - device.approve
        - device.list
        - device.revoke
        - dm.create
        - dm.leave
        - draft.read
        - draft.write
        - events.catch_up
        - export.channel
        - feature_flag.read
        - feature_flag.update
        - file.delete
        - file.read
        - file.transcribe
        - file.transcript.submit
        - file.upload
        - forum_tag.manage
        - gateway.connect
        - gateway.focus
        - gdpr.export.download
        - gdpr.request.create
        - gdpr.request.decide
        - gdpr.request.read
        - grant.create
        - grant.read
        - grant.revoke
        - group.create
        - group.delete
        - group.member.add
        - group.member.remove
        - group.read
        - group.update
        - host.connect
        - host.manage
        - host.pair
        - host.read
        - identity.link
        - identity.read
        - identity.unlink
        - identity_provider.manage
        - identity_provider.read
        - import.apply
        - import.manage
        - import.read
        - instance.branding.update
        - instance.discovery.read
        - instance.read
        - instance.settings.read
        - instance.settings.update
        - interaction.create
        - interaction.respond
        - local_credential.set
        - mention.list
        - message.create
        - message.delete
        - message.read
        - message.revisions.read
        - message.update
        - notification_pref.update
        - owner.add
        - owner.remove
        - passkey.register
        - passkey.revoke
        - pin.create
        - pin.delete
        - price.manage
        - price.read
        - push.deliver
        - push.register
        - push.unregister
        - quiet_hours.update
        - reaction.add
        - reaction.remove
        - read_state.mark_unread
        - read_state.update
        - run.cancel
        - run.context.read
        - run.finish
        - run.followup.create
        - run.overview.read
        - run.read
        - run.start
        - run.token.renew
        - run.update
        - search.query
        - self.profile.update
        - self.read
        - session.extension.create
        - session.list
        - session.revoke
        - settings.read
        - settings.update
        - sidebar.update
        - signing_key.rotate
        - space.archive
        - space.create
        - space.operator.set
        - space.read
        - space.reorder
        - space.update
        - stats.read
        - status.manage
        - status.public.read
        - status.read
        - sync.snapshot.read
        - template.manage
        - template.read
        - terminal.attach
        - terminal.close
        - terminal.open
        - terminal.read_own
        - terminal.stream
        - thread.close
        - thread.create
        - thread.move
        - thread.read
        - thread.reopen
        - thread.tags.set
        - thread.title.update
        - token.create
        - token.list
        - token.revoke
        - typing.start
        - user.create
        - user.disable
        - user.enable
        - user.read
        - user.update
        - webhook_in.create
        - webhook_in.post
        - webhook_in.read
        - webhook_in.revoke
        - webhook_out.deliver
        - webhook_out.manage
        - webhook_out.read
        - webhook_out.redeliver
        # END GENERATED: authz_actions
    message: x-authz-action is not in the action catalogue.

  rule/rate-limit-class-required:
    subject:
      type: Operation
    assertions:
      required:
        - x-rate-limit-class
    message: Every operation needs x-rate-limit-class.
  rule/rate-limit-class-known:
    subject:
      type: Operation
      property: x-rate-limit-class
    assertions:
      enum:
        # BEGIN GENERATED: rate_limit_classes (make catalog)
        - none
        - rl.admin
        - rl.agent_runtime
        - rl.export
        - rl.gateway_connect
        - rl.host
        - rl.import
        - rl.interaction
        - rl.message
        - rl.public_auth
        - rl.read
        - rl.search
        - rl.terminal
        - rl.trigger
        - rl.typing
        - rl.upload
        - rl.webhook_in
        - rl.write
        # END GENERATED: rate_limit_classes
    message: x-rate-limit-class is not a class of docs/spec/05-actions.md section 3 (or none).

  # 05-actions.md rule 4: every event type names its receive action.
  rule/event-receive-action-required:
    subject:
      type: Message
    where:
      - subject:
          type: Message
          property: x-event-kind
        assertions:
          defined: true
    assertions:
      required:
        - x-receive-action
    message: Every event message needs x-receive-action.
  # Receive actions: catalogue actions plus `self` (05-actions.md section 6).
  rule/receive-action-known-message:
    subject:
      type: Message
      property: x-receive-action
    assertions:
      enum: &receive_actions
        # BEGIN GENERATED: receive_actions (make catalog)
        - access.explain
        - access.hints.read
        - access.preview
        - access.view_as
        - access.view_as.end
        - agent.billing_mode.set
        - agent.billing_mode.set_all
        - agent.config.read
        - agent.create
        - agent.credential_holder.set
        - agent.disable
        - agent.enable
        - agent.events.consume
        - agent.heartbeat
        - agent.invoke
        - agent.read
        - agent.tool_class.set
        - agent.triggerers.read
        - agent.update
        - agent_session.bind
        - agent_session.read
        - agent_session.report
        - agent_session.reset
        - app.install
        - app.read
        - app.resume
        - app.scopes.update
        - app.suspend
        - app.uninstall
        - audit.read
        - audit.verify
        - auth.login.begin
        - auth.login.complete
        - auth.logout
        - auth.step_up
        - auth.token.refresh
        - channel.archive
        - channel.behavior.read
        - channel.behavior.update
        - channel.create
        - channel.operator.set
        - channel.project.bind
        - channel.read
        - channel.read_only.set
        - channel.reorder
        - channel.update
        - client_metrics.submit
        - command.autocomplete
        - command.autocomplete.respond
        - command.invoke
        - command.read
        - command.register
        - command.remove
        - component.update
        - console.csp_report
        - cost.read.aggregate
        - cost.read.thread
        - cost.reconcile
        - cost.reconcile.read
        - cost.reconcile.submit
        - dev_environment.manage
        - dev_environment.read
        - dev_environment.report
        - device.approve
        - device.list
        - device.revoke
        - dm.create
        - dm.leave
        - draft.read
        - draft.write
        - events.catch_up
        - export.channel
        - feature_flag.read
        - feature_flag.update
        - file.delete
        - file.read
        - file.transcribe
        - file.transcript.submit
        - file.upload
        - forum_tag.manage
        - gateway.connect
        - gateway.focus
        - gdpr.export.download
        - gdpr.request.create
        - gdpr.request.decide
        - gdpr.request.read
        - grant.create
        - grant.read
        - grant.revoke
        - group.create
        - group.delete
        - group.member.add
        - group.member.remove
        - group.read
        - group.update
        - host.connect
        - host.manage
        - host.pair
        - host.read
        - identity.link
        - identity.read
        - identity.unlink
        - identity_provider.manage
        - identity_provider.read
        - import.apply
        - import.manage
        - import.read
        - instance.branding.update
        - instance.discovery.read
        - instance.read
        - instance.settings.read
        - instance.settings.update
        - interaction.create
        - interaction.respond
        - local_credential.set
        - mention.list
        - message.create
        - message.delete
        - message.read
        - message.revisions.read
        - message.update
        - notification_pref.update
        - owner.add
        - owner.remove
        - passkey.register
        - passkey.revoke
        - pin.create
        - pin.delete
        - price.manage
        - price.read
        - push.deliver
        - push.register
        - push.unregister
        - quiet_hours.update
        - reaction.add
        - reaction.remove
        - read_state.mark_unread
        - read_state.update
        - run.cancel
        - run.context.read
        - run.finish
        - run.followup.create
        - run.overview.read
        - run.read
        - run.start
        - run.token.renew
        - run.update
        - search.query
        - self
        - self.profile.update
        - self.read
        - session.extension.create
        - session.list
        - session.revoke
        - settings.read
        - settings.update
        - sidebar.update
        - signing_key.rotate
        - space.archive
        - space.create
        - space.operator.set
        - space.read
        - space.reorder
        - space.update
        - stats.read
        - status.manage
        - status.public.read
        - status.read
        - sync.snapshot.read
        - template.manage
        - template.read
        - terminal.attach
        - terminal.close
        - terminal.open
        - terminal.read_own
        - terminal.stream
        - thread.close
        - thread.create
        - thread.move
        - thread.read
        - thread.reopen
        - thread.tags.set
        - thread.title.update
        - token.create
        - token.list
        - token.revoke
        - typing.start
        - user.create
        - user.disable
        - user.enable
        - user.read
        - user.update
        - webhook_in.create
        - webhook_in.post
        - webhook_in.read
        - webhook_in.revoke
        - webhook_out.deliver
        - webhook_out.manage
        - webhook_out.read
        - webhook_out.redeliver
        # END GENERATED: receive_actions
  rule/receive-action-known-webhook:
    subject:
      type: Operation
      property: x-receive-action
    assertions:
      enum: *receive_actions

  # Conservative schemas for code generators (oapi-codegen, dart-dio): no null type,
  # no type arrays, oneOf only with a discriminator.
  rule/no-type-arrays-or-null:
    subject:
      type: Schema
      property: type
    assertions:
      pattern: /^(string|integer|number|boolean|object|array)$/
    message: Use one JSON type per schema; express "no value" by omitting the member.
  rule/oneof-needs-discriminator:
    subject:
      type: Schema
    assertions:
      mutuallyRequired:
        - oneOf
        - discriminator
    message: oneOf is only allowed together with a discriminator.
  rule/no-anyof:
    subject:
      type: Schema
    assertions:
      disallowed:
        - anyOf
    message: anyOf is not allowed; use oneOf with a discriminator.
