openapi: 3.1.0
info:
  title: Remembrance Dashboard API
  version: 0.1.0
  description:
    "Browser-session endpoints backing the Remembrance dashboard. Kept
    SEPARATE from the agent API on purpose: every operation here authenticates
    with a Clerk browser session, so an agent holding X-Remembrance-API-Key
    cannot call any of them and receives 401. They previously lived in
    docs/openapi.yaml, where sitting in an otherwise agent-facing document
    invited the wrong inference — that a documented /api/v1 path is reachable
    with an agent credential. For the agent API see docs/openapi.yaml."
servers:
  - url: http://localhost:3000
    description: Local Docker or Next dev server
  - url: https://dev.remembrance.dev
    description: Dev preview
  - url: https://remembrance.dev
paths:
  /api/v1/enterprise/api-keys:
    get:
      summary:
        List the calling Clerk organization's enterprise API keys (summaries
        only; never the secret).
      security:
        - ClerkSession: []
      responses:
        "200":
          description: API key summaries for the organization.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EnterpriseApiKeyListResponse"
        "401":
          description: Sign in required.
        "403":
          description: Organization admin role required.
        "409":
          description: No Clerk organization selected.
    post:
      summary:
        Mint a new enterprise API key for the calling Clerk organization. The
        plaintext api_key is returned only once.
      security:
        - ClerkSession: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/EnterpriseApiKeyCreateRequest"
      responses:
        "201":
          description: API key created. The api_key field is shown only once.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EnterpriseApiKeyCreateResponse"
        "401":
          description: Sign in required.
        "403":
          description: Cross-origin request rejected.
        "409":
          description: No Clerk organization selected.
        "422":
          description: Invalid JSON body or API key creation payload.
        "429":
          description: Rate limit exceeded.
    delete:
      summary: Revoke an enterprise API key owned by the calling Clerk organization.
      security:
        - ClerkSession: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/EnterpriseApiKeyDeleteRequest"
      responses:
        "200":
          description: Key revoked.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EnterpriseApiKeyDeleteResponse"
        "401":
          description: Sign in required.
        "403":
          description: Cross-origin request rejected.
        "404":
          description: Key not found for this organization.
        "422":
          description: Invalid JSON body or API key revoke payload.
        "429":
          description: Rate limit exceeded.
  /api/v1/enterprise/agent-principals:
    get:
      summary: List stable organization agent principals and monthly activity.
      security:
        - ClerkSession: []
      responses:
        "200":
          description: Agent inventory and bounded activity history.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EnterpriseAgentPrincipalListResponse"
        "401":
          description: Sign in required.
        "403":
          description: Organization admin role required.
    post:
      summary: Register a stable organization agent or inherited ephemeral subagent.
      security:
        - ClerkSession: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/EnterpriseAgentPrincipalCreateRequest"
      responses:
        "201":
          description: Agent principal registered.
        "403":
          description: Same-origin or organization-admin access required.
        "422":
          description: Invalid principal or inactive parent principal.
    patch:
      summary: Deactivate or reactivate an organization agent principal.
      security:
        - ClerkSession: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/EnterpriseAgentPrincipalUpdateRequest"
      responses:
        "200":
          description: Principal status updated.
        "404":
          description: Principal not found in the organization.
    put:
      summary: Bind an organization API key to a stable agent principal.
      description:
        API-key rotation may bind the replacement key to the same principal
        and never creates another billable agent identity.
      security:
        - ClerkSession: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/EnterpriseAgentPrincipalKeyBindingRequest"
      responses:
        "200":
          description: API key bound to the principal.
        "404":
          description: API key not found in the organization.
        "422":
          description: Principal is not active in the organization.
  /api/v1/enterprise/agent-instances:
    get:
      summary: List installation principals, runtime profiles, and member assignments.
      description: >-
        Members see only installations linked to themselves. Organization
        administrators with API-key management permission see the organization
        inventory and may assign installations. The response exposes safe labels
        and opaque IDs only; Clerk IDs, email addresses, hostnames, usernames,
        config paths, and repository paths are never returned. The installation
        list is bounded and cursor-paged; runtime profiles and member bindings
        are scoped to the returned installation page rather than the whole
        organization, so a response never grows with binding churn.
      security:
        - ClerkSession: []
      responses:
        "200":
          description: Organization identity inventory visible to the caller.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentInstanceInventoryResponse"
        "401":
          description: Sign in required.
        "409":
          description: No Clerk organization selected.
      parameters:
        - name: limit
          in: query
          required: false
          description:
            Installations per page. Clamped to 1..500; omitted or unparseable
            values use the default of 200.
          schema:
            type: integer
            minimum: 1
            maximum: 500
            default: 200
        - name: cursor
          in: query
          required: false
          description: Opaque cursor from a previous response's next_cursor. An
            unrecognized cursor returns the first page rather than an error, so
            a stale bookmark cannot break the dashboard.
          schema:
            type: string
        - name: validation_limit
          in: query
          required: false
          description: Current preference-validation observations per page, clamped to 1..200.
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
        - name: validation_cursor
          in: query
          required: false
          description: Opaque cursor from next_validation_cursor.
          schema:
            type: string
        - name: member_limit
          in: query
          required: false
          description:
            Organization members per page for administrators. Clamped to
            1..500; omitted or unparseable values use the default of 200.
          schema:
            type: integer
            minimum: 1
            maximum: 500
            default: 200
        - name: member_cursor
          in: query
          required: false
          description:
            Opaque cursor from next_member_cursor. It is independent from the
            installation cursor so loading either list cannot skip the other.
          schema:
            type: string
    patch:
      summary: Manage an installation principal or label a runtime profile.
      description: >-
        A linked member may rename their own installation. Assignment, unlink,
        activation, deactivation, and runtime-profile labeling require
        organization-admin permission.
      security:
        - ClerkSession: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AgentInstallationManagementRequest"
      responses:
        "200":
          description: Installation or assignment updated.
        "401":
          description: Sign in required.
        "403":
          description: Same-origin, ownership, or organization-admin requirement failed.
        "404":
          description: Installation, runtime profile, or active assignment not found.
        "409":
          description: No Clerk organization selected or assignment conflicts.
        "422":
          description: Invalid management action.
  /api/v1/enterprise/agent-member-link:
    post:
      summary: Create a one-time member link for Install on this device.
      description: >-
        Creates a single-use token for the signed-in organization member. The
        token expires after ten minutes and is intended only for a dashboard
        setup command on the current device. Reusable organization key
        distribution commands must not include it.
      security:
        - ClerkSession: []
      responses:
        "201":
          description: One-time member-link token created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DashboardMemberLinkResponse"
        "401":
          description: Sign in required.
        "403":
          description: Cross-origin request rejected.
        "409":
          description: No Clerk organization selected.
  /api/v1/enterprise/agent-preferences:
    get:
      summary: List visible active and historical preference profile revisions.
      description: >-
        Members see organization guidance and their own profiles. Organization
        policy administrators see the full organization inventory. Records are
        typed settings and aggregate confidence/provenance only; raw prompts and
        feedback text are not stored or returned.
      security:
        - ClerkSession: []
      parameters:
        - name: limit
          in: query
          required: false
          description:
            Preference revisions per page. Clamped to 1..200; omitted or
            unparseable values use the default of 50.
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
        - name: cursor
          in: query
          required: false
          description:
            Opaque cursor from next_cursor. An unrecognized cursor returns the
            first page rather than failing the dashboard.
          schema:
            type: string
      responses:
        "200":
          description: Preference profiles visible to the caller.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DashboardPreferenceListResponse"
        "401":
          description: Sign in required.
        "409":
          description: No Clerk organization selected.
    patch:
      summary: Set, undo, or reset an organization-local preference revision.
      description: >-
        Members may set and manage their own explicit preferences. Organization
        guidance requires manage_org_policy and may be Recommended or Required.
        Every activation is a versioned, reversible revision.
      security:
        - ClerkSession: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/DashboardPreferenceMutationRequest"
      responses:
        "200":
          description: Preference updated, undone, or reset.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DashboardPreferenceMutationResponse"
        "400":
          description: Malformed JSON request body.
        "401":
          description: Sign in required.
        "403":
          description: Same-origin, ownership, or organization-policy permission failed.
        "404":
          description: Preference revision not found.
        "409":
          description: No Clerk organization selected.
        "413":
          description: Request exceeds the configured body limit.
        "422":
          description: Invalid preference mutation.
  /api/v1/enterprise/preference-compatibility:
    get:
      summary: Inspect private preference compatibility progress and material edges.
      description: >-
        Returns organization-private classification progress plus only material
        supports/conflicts edges for exact skill versions. Members see
        organization-wide edges and their own residuals. Organization policy
        administrators see the full scoped inventory. Neutral pairs are
        represented by sweep coverage and are not persisted or listed. Summary
        counts cover every visible edge even when the edge inventory is paged.
      security:
        - ClerkSession: []
      parameters:
        - in: query
          name: edge_limit
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 100
          description: Maximum material compatibility edges returned on this page.
        - in: query
          name: edge_cursor
          schema:
            type: string
          description: Opaque continuation cursor from next_edge_cursor.
        - in: query
          name: definition_limit
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 100
          description: Maximum visible preference definitions and current sweeps returned.
        - in: query
          name: definition_cursor
          schema:
            type: string
          description: Opaque continuation cursor from next_definition_cursor.
        - in: query
          name: summary_only
          schema:
            type: integer
            enum: [1]
          description: Return only the cheap materialized organization summary for polling.
        - in: query
          name: relationship
          schema:
            enum: [supports, conflicts]
        - in: query
          name: skill_source
          schema:
            enum: [public, org_overlay]
        - in: query
          name: compatibility_source
          schema:
            enum:
              [classifier, member_feedback, organization_override, legacy_trait]
        - in: query
          name: scope
          schema:
            enum: [organization, member, member_runtime, installation]
        - in: query
          name: skill
          schema:
            type: string
            maxLength: 96
          description: Normalized skill-slug prefix filter.
        - in: query
          name: preference
          schema:
            type: string
            maxLength: 96
          description: Exact preference fingerprint filter.
      responses:
        "200":
          description: Private preference compatibility overview.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DashboardPreferenceCompatibilityOverview"
        "401":
          description: Sign in required.
        "409":
          description: No Clerk organization selected.
    patch:
      summary: Override, reset, or reclassify one private compatibility edge.
      description: >-
        Requires manage_org_policy. Overrides are exact-version,
        organization-private decisions. Reset removes only the active
        organization override and queues fresh classification; reclassify
        removes the current classifier edge and queues fresh classification.
        Every action is recorded in the tamper-evident administrator audit log.
      security:
        - ClerkSession: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/DashboardPreferenceCompatibilityAction"
      responses:
        "200":
          description: Compatibility action applied.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required: [ok, reclassification_status, reclassification_queued]
                properties:
                  ok:
                    const: true
                  reclassification_status:
                    enum: [queued, already_queued, not_required]
                  reclassification_queued:
                    type: boolean
                    description: Compatibility alias; true only when this request newly queued work.
        "400":
          description: Malformed JSON request body.
        "401":
          description: Sign in required.
        "403":
          description: Same-origin or manage_org_policy authorization failed.
        "404":
          description: Preference definition or exact compatibility edge not found.
        "409":
          description: No Clerk organization selected.
        "413":
          description: Request exceeds the configured body limit.
        "422":
          description: Invalid compatibility action.
components:
  securitySchemes:
    ClerkSession:
      type: apiKey
      in: cookie
      name: __session
      description:
        Clerk session cookie (browser dashboard only). Same-origin enforced
        on writes.
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      schema:
        type: string
      description: Stable client key used to deduplicate repeated agent submissions.
    RemembranceApiKey:
      name: X-Remembrance-Api-Key
      in: header
      required: false
      schema:
        type: string
      description:
        Optional enterprise API key. Missing keys are allowed on public
        agent/network endpoints; invalid keys are rejected. Private or
        org-scoped payloads require an API key.
    PrincipalSession:
      name: X-Remembrance-Principal-Session
      in: header
      required: false
      schema:
        type: string
      description: >-
        Preferred opaque 24-hour principal session. It binds a verified local
        installation, optional organization member, runtime profile, and
        organization scope. Query and invocation reads ignore unusable or
        expired session context and continue under the API-key or anonymous
        scope; identity, linking, and preference mutations reject that context.
        An organization-owned session must accompany a valid same-organization
        API key. It is not an API key or billing credential. When both session
        headers are present, this preferred header takes precedence.
    EconomicsSession:
      name: X-Remembrance-Economics-Session
      in: header
      required: false
      schema:
        type: string
      deprecated: true
      description:
        Backward-compatible alternative to X-Remembrance-Principal-Session.
        New clients should send only the principal-session header; when both are
        present, the principal-session header takes precedence.
  schemas:
    PreferenceSetting:
      type: object
      additionalProperties: false
      required: [key, value]
      description: >-
        Built-in settings need only key/value. An extensible setting requires
        label, behavior, and effect, and may choose prefer or avoid. Working
        preferences can cover discretionary presentation, workflow, or strategy
        selection without weakening mandatory policy or skill requirements.
      properties:
        key:
          type: string
          minLength: 1
          maxLength: 96
          pattern: "^[a-z][a-z0-9]*(?:[._-][a-z0-9]+)*$"
        value:
          type: string
          minLength: 1
          maxLength: 96
          pattern: "^[a-z][a-z0-9]*(?:[._-][a-z0-9]+)*$"
        label:
          type: string
          minLength: 1
          maxLength: 96
        behavior:
          type: string
          minLength: 1
          maxLength: 320
        effect:
          enum: [presentation, workflow, strategy_selection]
        strength:
          enum: [prefer, avoid]
          default: prefer
        definition_version:
          type: integer
          minimum: 1
          maximum: 1000000
          default: 1
    EnterpriseOrganizationSummary:
      type: object
      required: [id, slug, name]
      properties:
        id:
          type: string
        slug:
          type: string
        name:
          type: string
    EnterpriseApiKeyScope:
      type: string
      enum:
        - agent:query
        - submission:create
        - verification:create
        - admin:review
    EnterpriseApiKeySummary:
      type: object
      required: [id, name, key_prefix, scopes, status, created_at, updated_at]
      properties:
        id:
          type: string
        name:
          type: string
        key_prefix:
          type: string
          description: Short non-secret prefix shown for identification.
        scopes:
          type: array
          items:
            $ref: "#/components/schemas/EnterpriseApiKeyScope"
        status:
          type: string
          enum: [active, revoked, expired]
        agent_principal_id:
          type: [string, "null"]
          description:
            Stable registered agent identity bound to this key. Key rotation
            may preserve this value.
        expires_at:
          type: [string, "null"]
          format: date-time
        last_used_at:
          type: [string, "null"]
          format: date-time
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    EnterpriseApiKeyListResponse:
      type: object
      required: [organization, keys]
      properties:
        organization:
          $ref: "#/components/schemas/EnterpriseOrganizationSummary"
        keys:
          type: array
          items:
            $ref: "#/components/schemas/EnterpriseApiKeySummary"
    EnterpriseApiKeyCreateRequest:
      type: object
      properties:
        name:
          type: string
          maxLength: 80
        scopes:
          type: array
          items:
            $ref: "#/components/schemas/EnterpriseApiKeyScope"
        ttl_days:
          type: [integer, "null"]
          minimum: 1
          maximum: 3650
          description:
            Number of days before this enterprise API key expires. Null means
            no expiration; omitted uses the server default.
    EnterpriseApiKeyCreateResponse:
      type: object
      required: [api_key, key, organization]
      properties:
        api_key:
          type: string
          description: Plaintext enterprise API key. Returned only once.
        key:
          $ref: "#/components/schemas/EnterpriseApiKeySummary"
        organization:
          $ref: "#/components/schemas/EnterpriseOrganizationSummary"
    EnterpriseApiKeyDeleteRequest:
      type: object
      required: [id]
      properties:
        id:
          type: string
    EnterpriseApiKeyDeleteResponse:
      type: object
      required: [revoked]
      properties:
        revoked:
          type: boolean
    EnterpriseAgentPrincipal:
      type: object
      required:
        [
          id,
          kind,
          status,
          display_name,
          provider,
          billing_principal_id,
          verified,
          registered_at,
        ]
      properties:
        id:
          type: string
        kind:
          enum: [registered_agent, ephemeral_subagent]
        status:
          enum: [active, inactive]
        display_name:
          type: string
        provider:
          type: string
        runtime_version:
          type: [string, "null"]
        parent_principal_id:
          type: [string, "null"]
        billing_principal_id:
          type: string
          description:
            Ephemeral subagents inherit their parent's billing principal unless
            separately registered.
        verified:
          type: boolean
        registered_at:
          type: string
          format: date-time
        deactivated_at:
          type: [string, "null"]
          format: date-time
        last_activity_at:
          type: [string, "null"]
          format: date-time
    EnterpriseAgentActivity:
      type: object
      required:
        [
          month,
          agent_principal_id,
          billing_principal_id,
          first_activity_at,
          last_activity_at,
          activity_count,
        ]
      properties:
        month:
          type: string
          pattern: "^[0-9]{4}-[0-9]{2}$"
        agent_principal_id:
          type: string
        billing_principal_id:
          type: string
        first_activity_at:
          type: string
          format: date-time
        last_activity_at:
          type: string
          format: date-time
        activity_count:
          type: integer
          minimum: 1
    EnterpriseAgentPrincipalListResponse:
      type: object
      required: [principals, activity]
      properties:
        principals:
          type: array
          items:
            $ref: "#/components/schemas/EnterpriseAgentPrincipal"
        activity:
          type: array
          items:
            $ref: "#/components/schemas/EnterpriseAgentActivity"
    EnterpriseAgentPrincipalCreateRequest:
      type: object
      additionalProperties: false
      required: [display_name, provider]
      properties:
        display_name:
          type: string
        provider:
          enum:
            [codex, cursor, claude, openclaw, vscode, opencode, generic, other]
        runtime_version:
          type: [string, "null"]
        parent_principal_id:
          type: [string, "null"]
    EnterpriseAgentPrincipalUpdateRequest:
      type: object
      additionalProperties: false
      required: [principal_id, action]
      properties:
        principal_id:
          type: string
        action:
          enum: [deactivate, reactivate]
    EnterpriseAgentPrincipalKeyBindingRequest:
      type: object
      additionalProperties: false
      required: [principal_id, api_key_id]
      properties:
        principal_id:
          type: string
        api_key_id:
          type: string
    AgentInstallation:
      type: object
      additionalProperties: false
      required:
        [
          id,
          display_name,
          provider,
          kind,
          status,
          billing_principal_id,
          created_at,
        ]
      properties:
        id:
          type: string
        display_name:
          type: string
        provider:
          type: string
        kind:
          const: installation
        status:
          enum: [active, inactive]
        parent_principal_id:
          type: [string, "null"]
        billing_principal_id:
          type: string
          description: Runtime profiles beneath this installation do not consume
            additional agent slots.
        last_activity_at:
          type: [string, "null"]
          format: date-time
        created_at:
          type: string
          format: date-time
    AgentRuntimeProfile:
      type: object
      additionalProperties: false
      required:
        [
          id,
          installation_principal_id,
          status,
          runtime,
          surface,
          host_surface,
          client_name,
          display_name,
          last_seen_at,
        ]
      properties:
        id:
          type: string
        installation_principal_id:
          type: string
        status:
          enum: [active, inactive]
        runtime:
          enum:
            [
              codex,
              claude_code,
              cursor,
              openclaw,
              vs_code,
              opencode,
              other,
              unknown,
            ]
        surface:
          enum: [plugin_hook, mcp, rest, unknown]
        host_surface:
          enum: [desktop, cli, extension, gateway, unknown]
          description: Private-safe host category; never a hostname or local path.
        client_name:
          type: string
        client_version:
          type: [string, "null"]
        runtime_version:
          type: [string, "null"]
        display_name:
          type: string
        last_seen_at:
          type: string
          format: date-time
    OrganizationMemberPrincipal:
      type: object
      additionalProperties: false
      required: [id, display_label, status]
      properties:
        id:
          type: string
        display_label:
          type: string
        status:
          enum: [active, inactive]
    AgentMemberBinding:
      type: object
      additionalProperties: false
      required: [id, installation_principal_id, member_principal_id, status]
      properties:
        id:
          type: string
        installation_principal_id:
          type: string
        member_principal_id:
          type: string
        status:
          enum: [active, revoked]
    AgentInstanceInventoryResponse:
      type: object
      additionalProperties: false
      required:
        - can_manage
        - current_member_id
        - installations
        - runtime_profiles
        - bindings
        - members
        - next_cursor
        - next_member_cursor
        - child_records_truncated
      properties:
        can_manage:
          type: boolean
        current_member_id:
          type: string
        installations:
          type: array
          items:
            $ref: "#/components/schemas/AgentInstallation"
        runtime_profiles:
          type: array
          items:
            $ref: "#/components/schemas/AgentRuntimeProfile"
        bindings:
          type: array
          items:
            $ref: "#/components/schemas/AgentMemberBinding"
        members:
          type: array
          items:
            $ref: "#/components/schemas/OrganizationMemberPrincipal"
        next_cursor:
          type:
            - string
            - "null"
          description: Cursor for the next page, or null on the final page.
        next_member_cursor:
          type:
            - string
            - "null"
          description:
            Independent cursor for the next organization-member page, or null
            on the final page.
        child_records_truncated:
          type: boolean
          description:
            True when a returned installation has more runtime profiles than
            the bounded response includes.
    AgentInstallationManagementRequest:
      oneOf:
        - type: object
          additionalProperties: false
          required: [action, installation_id, display_name]
          properties:
            action:
              const: rename
            installation_id:
              type: string
            display_name:
              type: string
        - type: object
          additionalProperties: false
          required: [action, runtime_profile_id, display_name]
          properties:
            action:
              const: rename_runtime_profile
            runtime_profile_id:
              type: string
            display_name:
              type: string
        - type: object
          additionalProperties: false
          required: [action, installation_id, status]
          properties:
            action:
              const: set_status
            installation_id:
              type: string
            status:
              enum: [active, inactive]
        - type: object
          additionalProperties: false
          required: [action, installation_id, member_principal_id]
          properties:
            action:
              const: assign_member
            installation_id:
              type: string
            member_principal_id:
              type: string
        - type: object
          additionalProperties: false
          required: [action, installation_id]
          properties:
            action:
              const: unlink_member
            installation_id:
              type: string
    DashboardMemberLinkResponse:
      type: object
      additionalProperties: false
      required: [token, expires_at]
      properties:
        token:
          type: string
          pattern: "^mlink_[A-Za-z0-9_-]{24,160}$"
          description: Secret shown once; expires in ten minutes and is consumed once.
        expires_at:
          type: string
          format: date-time
    DashboardPreferenceProfile:
      type: object
      additionalProperties: false
      required:
        [
          id,
          member_principal_id,
          runtime_profile_id,
          setting,
          scope,
          source,
          confidence,
          observation_count,
          distinct_task_count,
          distinct_day_count,
          skill_slug,
          domain,
          revision,
          status,
          previous_revision_id,
          activated_at,
          last_observed_at,
        ]
      properties:
        id:
          type: string
        member_principal_id:
          type: [string, "null"]
        runtime_profile_id:
          type: [string, "null"]
        setting:
          $ref: "#/components/schemas/PreferenceSetting"
        scope:
          enum:
            [
              organization,
              member,
              member_runtime,
              installation,
              project,
              skill,
              domain,
            ]
          description: >-
            `project` is an opaque local context derived on the installation.
            It is not a Remembrance tenant or an administrator-managed object.
        source:
          type: string
        confidence:
          type: number
          minimum: 0
          maximum: 1
        observation_count:
          type: integer
          minimum: 0
        distinct_task_count:
          type: integer
          minimum: 0
        distinct_day_count:
          type: integer
          minimum: 0
        skill_slug:
          type: [string, "null"]
        domain:
          type: [string, "null"]
        revision:
          type: integer
          minimum: 1
        status:
          enum: [active, superseded, reset]
        previous_revision_id:
          type: [string, "null"]
        activated_at:
          type: string
          format: date-time
        last_observed_at:
          type: string
          format: date-time
    DashboardPreferenceListResponse:
      type: object
      additionalProperties: false
      required:
        [
          can_manage,
          current_member_id,
          current_profiles,
          profiles,
          validation_observations,
          next_cursor,
          next_validation_cursor,
        ]
      properties:
        can_manage:
          type: boolean
        current_member_id:
          type: string
        current_profiles:
          type: array
          description: >-
            Active organization guidance and the signed-in member's active
            general preferences. Returned independently so a long history page
            cannot hide the settings currently in force.
          maxItems: 32
          items:
            $ref: "#/components/schemas/DashboardPreferenceProfile"
        profiles:
          type: array
          items:
            $ref: "#/components/schemas/DashboardPreferenceProfile"
        validation_observations:
          type: array
          maxItems: 200
          items:
            $ref: "#/components/schemas/DashboardPreferenceValidationObservation"
        next_cursor:
          type: [string, "null"]
        next_validation_cursor:
          type: [string, "null"]
    DashboardPreferenceValidationObservation:
      type: object
      additionalProperties: false
      required:
        [
          id,
          member_principal_id,
          runtime_profile_id,
          setting,
          normalized_setting,
          scope,
          source_category,
          skill_slug,
          domain,
          confidence,
          validation_status,
          validation_version,
          validation_reason_code,
          validation_attempts,
          validation_due_at,
          validated_at,
          observed_at,
          created_at,
          updated_at,
        ]
      properties:
        id:
          type: string
        member_principal_id:
          type: [string, "null"]
        runtime_profile_id:
          type: [string, "null"]
        setting:
          $ref: "#/components/schemas/PreferenceSetting"
        normalized_setting:
          anyOf:
            - $ref: "#/components/schemas/PreferenceSetting"
            - type: "null"
        scope:
          enum:
            [
              organization,
              member,
              member_runtime,
              installation,
              project,
              skill,
              domain,
            ]
        source_category:
          enum: [explicit_user, agent_observed, admin_override, admin_required]
        skill_slug:
          type: [string, "null"]
        domain:
          type: [string, "null"]
        confidence:
          type: number
          minimum: 0
          maximum: 1
        validation_status:
          enum: [approved, pending, blocked, rejected]
        validation_version:
          type: [string, "null"]
        validation_reason_code:
          type: [string, "null"]
          enum:
            [
              built_in,
              normalized,
              external_ai_disabled,
              unsafe_instruction,
              malformed,
              uncertain,
              retry_exhausted,
              null,
            ]
        validation_attempts:
          type: integer
          minimum: 0
        validation_due_at:
          type: [string, "null"]
          format: date-time
        validated_at:
          type: [string, "null"]
          format: date-time
        observed_at:
          type: string
          format: date-time
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    DashboardPreferenceMutationRequest:
      oneOf:
        - type: object
          additionalProperties: false
          required: [action, setting, scope]
          properties:
            action:
              const: set
            setting:
              $ref: "#/components/schemas/PreferenceSetting"
            scope:
              enum: [member, organization]
            enforcement:
              enum: [recommended, required]
              default: recommended
              description: >-
                Applies only to organization scope. Required guidance wins
                over task and personal working preferences; recommended
                guidance remains an overridable organization default.
        - type: object
          additionalProperties: false
          required: [action, revision_id]
          properties:
            action:
              enum: [reset, undo]
            revision_id:
              type: string
    DashboardPreferenceMutationResponse:
      oneOf:
        - type: object
          additionalProperties: false
          required:
            [
              profile,
              validation_status,
              validation_reason_code,
              normalized_activation_state,
              normalized_setting,
            ]
          properties:
            profile:
              anyOf:
                - $ref: "#/components/schemas/DashboardPreferenceProfile"
                - type: "null"
            validation_status:
              enum: [approved, pending, blocked, rejected]
            validation_reason_code:
              type: [string, "null"]
              enum:
                [
                  built_in,
                  normalized,
                  external_ai_disabled,
                  unsafe_instruction,
                  malformed,
                  uncertain,
                  retry_exhausted,
                  null,
                ]
            normalized_activation_state:
              enum: [active, pending_validation, inactive]
            normalized_setting:
              anyOf:
                - $ref: "#/components/schemas/PreferenceSetting"
                - type: "null"
        - type: object
          additionalProperties: false
          required: [ok, profile]
          properties:
            ok:
              type: boolean
            profile:
              anyOf:
                - $ref: "#/components/schemas/DashboardPreferenceProfile"
                - type: "null"
    DashboardPreferenceCompatibilityOverview:
      type: object
      additionalProperties: false
      required:
        [
          can_manage,
          summary,
          definitions,
          sweeps,
          compatibility,
          policy_blocked_skills,
          policy_blocked_summary,
          next_edge_cursor,
          next_definition_cursor,
        ]
      properties:
        can_manage:
          type: boolean
        summary:
          $ref: "#/components/schemas/DashboardPreferenceCompatibilitySummary"
        definitions:
          type: array
          maxItems: 200
          items:
            $ref: "#/components/schemas/DashboardPreferenceDefinition"
        sweeps:
          type: array
          items:
            $ref: "#/components/schemas/DashboardPreferenceCompatibilitySweep"
        compatibility:
          type: array
          maxItems: 200
          items:
            $ref: "#/components/schemas/DashboardPreferenceCompatibilityEdge"
        policy_blocked_skills:
          type: array
          maxItems: 50
          description:
            Skills a Required preference currently makes ineligible organization
            wide, so administrators can see the effect of their own policy. A
            model classification alone never appears here; entries come only
            from an administrator override, a required step the skill declares
            itself, or a classifier lock corroborated by verified members. Empty
            on summary_only reads.
          items:
            $ref: "#/components/schemas/DashboardPolicyBlockedSkill"
        policy_blocked_summary:
          $ref: "#/components/schemas/DashboardPolicyBlockedSummary"
        next_edge_cursor:
          type: [string, "null"]
          description: Opaque cursor for the next material-edge page, or null when complete.
        next_definition_cursor:
          type: [string, "null"]
          description: Opaque cursor for the next visible preference-progress page, or null when complete.
    DashboardPolicyBlockedSummary:
      type: object
      additionalProperties: false
      required:
        [
          blocked_skill_count,
          blocking_relationship_count,
          returned_skill_count,
          returned_relationship_count,
          truncated,
        ]
      properties:
        blocked_skill_count:
          type: integer
          minimum: 0
          description: Distinct skills currently unavailable because of required preference relationships.
        blocking_relationship_count:
          type: integer
          minimum: 0
          description: Total required preference-to-skill conflict relationships.
        returned_skill_count:
          type: integer
          minimum: 0
          description: Distinct skills included in the bounded policy_blocked_skills payload.
        returned_relationship_count:
          type: integer
          minimum: 0
          description: Relationships included in the bounded policy_blocked_skills payload.
        truncated:
          type: boolean
          description: True when more blocked skills and their relationships exist than this response includes.
    DashboardPolicyBlockedSkill:
      type: object
      additionalProperties: false
      required:
        [
          preference_fingerprint,
          preference_key,
          preference_value,
          skill_id,
          skill_slug,
          skill_version,
          skill_version_id,
          skill_source,
          source,
          rationale,
          assessed_at,
        ]
      properties:
        preference_fingerprint:
          type: string
        preference_key:
          type: string
        preference_value:
          type: string
        skill_id:
          type: string
        skill_slug:
          type: string
        skill_version:
          type: string
        skill_version_id:
          type: string
        skill_source:
          type: string
          enum: [public, org_overlay]
        source:
          type: string
          description:
            Never "classifier"; only human-authorized or corroborated sources
            can block a skill.
          enum: [organization_override, member_feedback, legacy_trait]
        rationale:
          type: string
          description:
            Bounded trusted copy for an administrator override, corroborated
            member feedback, or a reviewed skill requirement. Never replayed
            model text, so this is safe to show to any member who can see the
            panel.
        assessed_at:
          type: string
          format: date-time
    DashboardPreferenceCompatibilitySummary:
      type: object
      additionalProperties: false
      required:
        [
          active_preferences,
          complete_preferences,
          queued_preferences,
          runnable_preferences,
          running_preferences,
          blocked_preferences,
          retry_exhausted_preferences,
          failed_preferences,
          supports,
          conflicts,
          public_edges,
          private_edges,
          oldest_backlog_at,
          summary_computed_at,
        ]
      properties:
        active_preferences:
          type: integer
          minimum: 0
        complete_preferences:
          type: integer
          minimum: 0
        queued_preferences:
          type: integer
          minimum: 0
          description: Preferences with queued work; blocked-only work is excluded.
        runnable_preferences:
          type: integer
          minimum: 0
          description: Preferences with queued or failed work that is currently due.
        running_preferences:
          type: integer
          minimum: 0
        blocked_preferences:
          type: integer
          minimum: 0
          description: Active preferences whose current work is blocked.
        retry_exhausted_preferences:
          type: integer
          minimum: 0
          description: Blocked preferences that exhausted automatic retries and require operator action.
        failed_preferences:
          type: integer
          minimum: 0
        supports:
          type: integer
          minimum: 0
        conflicts:
          type: integer
          minimum: 0
        public_edges:
          type: integer
          minimum: 0
        private_edges:
          type: integer
          minimum: 0
        oldest_backlog_at:
          type: [string, "null"]
          format: date-time
        summary_computed_at:
          type: [string, "null"]
          format: date-time
    DashboardPreferenceDefinition:
      type: object
      additionalProperties: false
      required:
        [
          id,
          fingerprint,
          setting,
          generation,
          status,
          last_active_at,
          updated_at,
        ]
      properties:
        id:
          type: string
        fingerprint:
          type: string
          pattern: "^sha256:[a-f0-9]{64}$"
        setting:
          $ref: "#/components/schemas/PreferenceSetting"
        generation:
          type: integer
          minimum: 1
        status:
          enum: [active, dormant]
        last_active_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    DashboardPreferenceCompatibilitySweep:
      type: object
      additionalProperties: false
      required:
        [
          id,
          preference_fingerprint,
          generation,
          kind,
          target_skill_id,
          target_skill_slug,
          target_skill_source,
          status,
          blocked_reason,
          catalog_count,
          assessed_count,
          supports_count,
          conflicts_count,
          neutral_count,
          uncertain_count,
          candidate_similarity_threshold,
          classifier_version,
          classifier_model,
          due_at,
          started_at,
          completed_at,
          updated_at,
        ]
      properties:
        id:
          type: string
        preference_fingerprint:
          type: string
          pattern: "^sha256:[a-f0-9]{64}$"
        generation:
          type: integer
          minimum: 1
        kind:
          enum: [catalog, skill_delta]
        target_skill_id:
          type: [string, "null"]
        target_skill_slug:
          type: [string, "null"]
        target_skill_source:
          type: [string, "null"]
          enum: [public, org_overlay, null]
        status:
          enum: [queued, running, complete, failed, blocked]
        blocked_reason:
          type: [string, "null"]
          enum:
            [organization_inactive, external_ai_disabled, retry_exhausted, null]
        catalog_count:
          type: integer
          minimum: 0
        assessed_count:
          type: integer
          minimum: 0
        supports_count:
          type: integer
          minimum: 0
        conflicts_count:
          type: integer
          minimum: 0
        neutral_count:
          type: integer
          minimum: 0
        uncertain_count:
          type: integer
          minimum: 0
        candidate_similarity_threshold:
          type: number
          minimum: -1
          maximum: 1
        classifier_version:
          const: preference-compatibility-v3
        classifier_model:
          type: string
        due_at:
          type: string
          format: date-time
        started_at:
          type: [string, "null"]
          format: date-time
        completed_at:
          type: [string, "null"]
          format: date-time
        updated_at:
          type: string
          format: date-time
    DashboardPreferenceCompatibilityEdge:
      type: object
      additionalProperties: false
      required:
        [
          id,
          preference_fingerprint,
          preference_key,
          preference_value,
          skill_slug,
          skill_version_id,
          skill_version,
          skill_source,
          relationship,
          confidence,
          reason_code,
          rationale,
          classifier_version,
          source,
          locked,
          scope,
          assessed_at,
        ]
      properties:
        id:
          type: string
        preference_fingerprint:
          type: string
          pattern: "^sha256:[a-f0-9]{64}$"
        preference_key:
          type: string
        preference_value:
          type: string
        skill_slug:
          type: string
        skill_version_id:
          type: string
        skill_version:
          type: string
        skill_source:
          enum: [public, org_overlay]
        relationship:
          enum: [supports, conflicts]
        confidence:
          type: number
          minimum: 0
          maximum: 1
        reason_code:
          enum:
            [
              behavior_supported,
              behavior_conflicted,
              required_step_conflict,
              insufficient_evidence,
              not_applicable,
              unsafe_or_malformed_input,
            ]
        rationale:
          type: string
          description: Raw bounded classifier rationale is visible only to policy administrators; members receive a trusted server-authored explanation.
        classifier_version:
          const: preference-compatibility-v3
        source:
          enum:
            [classifier, member_feedback, organization_override, legacy_trait]
        locked:
          type: boolean
        scope:
          enum: [organization, member, member_runtime, installation]
        member_principal_id:
          type: [string, "null"]
          description: Present only for organization policy administrators.
        runtime_profile_id:
          type: [string, "null"]
          description: Present only for organization policy administrators.
        assessed_at:
          type: string
          format: date-time
    DashboardPreferenceCompatibilityAction:
      oneOf:
        - type: object
          additionalProperties: false
          required: [action, preference_fingerprint, skill_version_id]
          properties:
            action:
              enum: [reclassify, reset]
            preference_fingerprint:
              type: string
              pattern: "^sha256:[a-f0-9]{64}$"
            skill_version_id:
              type: string
        - type: object
          additionalProperties: false
          required:
            [
              action,
              preference_fingerprint,
              skill_version_id,
              relationship,
              rationale,
            ]
          properties:
            action:
              const: override
            preference_fingerprint:
              type: string
              pattern: "^sha256:[a-f0-9]{64}$"
            skill_version_id:
              type: string
            relationship:
              enum: [supports, conflicts]
            rationale:
              type: string
              minLength: 1
              maxLength: 200
            locked:
              type: boolean
              default: false
