# DevPortal Governance Rule Authoring Guide

This guide explains how to author rulesets for DevPortal Governance templates.

## Supported Artifact and Rule Types

Use:

- Artifact type: `APPLICATION`
- Rule type: `APP_INFO` or `APP_OAUTH`

Use `APP_INFO` for application metadata checks. Use `APP_OAUTH` for OAuth
key/client lifecycle checks.

Template-bound rulesets are blocking-only. Any Spectral violation blocks the
operation, regardless of severity.

## APP_INFO Payload

`APP_INFO` rules evaluate application metadata.

Example payload:

```json
{
  "operation": "POST /applications",
  "organization": "carbon.super",
  "templateId": "template-uuid",
  "application": {
    "name": "DEV-Example",
    "description": "Example application",
    "throttlingPolicy": "10PerMin",
    "groups": ["internal-team"],
    "attributes": {
      "department": "engineering"
    }
  }
}
```

Common paths:

| Path | Meaning |
|---|---|
| `$.application.name` | Application name. |
| `$.application.description` | Application description. |
| `$.application.throttlingPolicy` | Application throttling policy. |
| `$.application.groups.*` | Application groups. |
| `$.application.attributes.<name>` | Custom application attribute. |
| `$.templateId` | Source template ID. |
| `$.organization` | Current organization. |

## APP_OAUTH Payload

`APP_OAUTH` rules evaluate OAuth key/client lifecycle operations.

Example payload:

```json
{
  "operation": "POST /applications/{id}/generate-keys",
  "action": "OAUTH_APP_CREATE",
  "organization": "carbon.super",
  "applicationId": "application-uuid",
  "keyManager": "Resident Key Manager",
  "keyType": "PRODUCTION",
  "grantTypesToBeSupported": ["client_credentials"],
  "callbackUrl": "",
  "scopes": [],
  "validityTime": 3600,
  "clientId": null,
  "hasClientSecret": false,
  "additionalProperties": {
    "application_access_token_expiry_time": "3600",
    "user_access_token_expiry_time": "3600",
    "refresh_token_expiry_time": "-1",
    "id_token_expiry_time": "3600",
    "pkceMandatory": "false",
    "pkceSupportPlain": "false",
    "bypassClientCredentials": "false"
  },
  "keyManagerContext": {
    "name": "Resident Key Manager",
    "uuid": "key-manager-uuid",
    "displayName": "Resident Key Manager",
    "type": "default",
    "enabled": true,
    "resident": true,
    "global": false,
    "availableGrantTypes": ["client_credentials"],
    "capabilities": {
      "oauthAppCreation": true,
      "mapOAuthConsumerApps": true,
      "tokenGeneration": true,
      "multipleClientSecrets": true
    }
  }
}
```

Common root paths:

| Path | Meaning |
|---|---|
| `$.action` | OAuth lifecycle action. |
| `$.keyManager` | Selected Key Manager name. |
| `$.keyType` | Production or sandbox key type. |
| `$.grantTypesToBeSupported.*` | Requested grant types. |
| `$.callbackUrl` | OAuth callback URL. |
| `$.validityTime` | Requested token validity time. |
| `$.additionalProperties.<key>` | OAuth client settings. |
| `$.keyManagerContext` | Resolved Key Manager metadata and capabilities. |

Additional property mappings:

| Template field | Runtime path |
|---|---|
| `appAccessTokenExpiry` | `$.additionalProperties.application_access_token_expiry_time` |
| `userAccessTokenExpiry` | `$.additionalProperties.user_access_token_expiry_time` |
| `refreshTokenExpiry` | `$.additionalProperties.refresh_token_expiry_time` |
| `idTokenExpiry` | `$.additionalProperties.id_token_expiry_time` |
| `enablePKCE` | `$.additionalProperties.pkceMandatory` |
| `pkceSupportsPlainText` | `$.additionalProperties.pkceSupportPlain` |
| `publicClient` | `$.additionalProperties.bypassClientCredentials` |

## OAuth Actions

Rules can inspect `$.action` to target specific lifecycle operations.

Common actions include:

- `OAUTH_APP_CREATE`
- `OAUTH_APP_MAP`
- `OAUTH_APP_UPDATE`
- `ACCESS_TOKEN_GENERATE`
- `CONSUMER_SECRET_REGENERATE`
- `OAUTH_APP_CLEANUP`
- `OAUTH_APP_DELETE`

## Key Manager Context

The backend resolves the selected Key Manager and adds `keyManagerContext` to the
payload when available.

Useful fields:

| Path | Meaning |
|---|---|
| `$.keyManagerContext.name` | Key Manager name. |
| `$.keyManagerContext.uuid` | Key Manager UUID. |
| `$.keyManagerContext.displayName` | Display name. |
| `$.keyManagerContext.type` | Key Manager type. |
| `$.keyManagerContext.enabled` | Whether the Key Manager is enabled. |
| `$.keyManagerContext.resident` | Whether it is the resident Key Manager. |
| `$.keyManagerContext.global` | Whether it is globally configured. |
| `$.keyManagerContext.availableGrantTypes.*` | Grant types advertised by the Key Manager. |
| `$.keyManagerContext.capabilities.oauthAppCreation` | Whether OAuth app creation is supported. |
| `$.keyManagerContext.capabilities.mapOAuthConsumerApps` | Whether out-of-band app mapping is supported. |
| `$.keyManagerContext.capabilities.tokenGeneration` | Whether APIM-side token generation is supported. |
| `$.keyManagerContext.capabilities.multipleClientSecrets` | Whether multiple secrets are supported. |

## Example APP_INFO Rules

```yaml
name: Internal Application Rules
description: Application metadata checks for internal templates.
ruleCategory: SPECTRAL
ruleType: APP_INFO
artifactType: APPLICATION
provider: WSO2
rulesetContent:
  rules:
    application-name-prefix:
      given:
        - $.application.name
      severity: error
      then:
        function: pattern
        functionOptions:
          match: '^INT-[A-Za-z0-9 _-]+$'
      message: Application name must start with INT-.

    application-description-required:
      given:
        - $.application.description
      severity: error
      then:
        function: truthy
      message: Application description is required.

    department-required:
      given:
        - $.application.attributes.department
      severity: error
      then:
        function: truthy
      message: Department attribute is required.
```

## Example APP_OAUTH Rules

```yaml
name: OAuth Client Rules
description: OAuth client checks for governed templates.
ruleCategory: SPECTRAL
ruleType: APP_OAUTH
artifactType: APPLICATION
provider: WSO2
rulesetContent:
  rules:
    no-password-grant:
      given:
        - $.grantTypesToBeSupported.*
      severity: error
      then:
        function: pattern
        functionOptions:
          notMatch: '^password$'
      message: Password grant is not allowed.

    callback-required-for-authorization-code:
      given:
        - '$[?(@.action == "OAUTH_APP_CREATE" || @.action == "OAUTH_APP_UPDATE")]'
      severity: error
      then:
        function: schema
        functionOptions:
          schema:
            type: object
            if:
              properties:
                grantTypesToBeSupported:
                  type: array
                  contains:
                    const: authorization_code
              required:
                - grantTypesToBeSupported
            then:
              properties:
                callbackUrl:
                  type: string
                  minLength: 1
              required:
                - callbackUrl
      message: Callback URL is required for authorization code.

    public-client-requires-pkce:
      given:
        - '$.additionalProperties[?(@.bypassClientCredentials == "true" || @.bypassClientCredentials == true)]'
      severity: error
      then:
        field: pkceMandatory
        function: truthy
      message: Public clients must enable PKCE.
```

## Publish-Time Validation Impact

Rules also participate in template publish-time hidden default validation.

When an admin publishes a template, the backend builds dummy payloads from hidden
defaults and evaluates the template's bound rulesets. If a rule targets a hidden
default path and fails, publishing is blocked.

For authoring, this means:

- Rules targeting locked defaults must be compatible with the template defaults.
- Rules targeting visible developer input should use paths that are present at
  runtime and should not rely on placeholder values.
- OAuth rules scoped to a Key Manager only validate that Key Manager during
  publish-time checks and runtime checks.

## Authoring Tips

- Prefer clear rule messages. Developers and admins see them directly.
- Use `$.action` to avoid applying create/update-only checks to unrelated OAuth
  actions.
- Use `keyManagerContext` for environment-aware checks.
- Keep grant type rules on `$.grantTypesToBeSupported.*`.
- Keep token expiry rules on `$.additionalProperties.<expiry_key>`.
- Remember that template-bound rules are blocking even when severity is `warn`
  or `info`.
