# Service Configuration Schema — Layer 3 Reusable Entity Type # KNO Schema Version: 0.8.0 # # Layer 3 reusable schema for service configuration bindings. This is the # "domain pivot" — a single schema that describes any provisioned service # attached to a possibility. Adapters read this schema to configure # infrastructure. # # DESIGN DECISIONS (Phase 0): # - Single schema with `service_type` discriminator (not separate auth/storage schemas) # - Auth-specific fields as optional group (auth.model, auth.methods[], etc.) # - Merges the planned auth-config-schema into this single schema # - secrets_xri references OpenBao path — never stores secrets directly # - possibility_xri links back to the owning possibility # # THREE-GATE TEST: # Gate 1 (Distinctness): YES — service configuration is fundamentally # different from the config-schema (which wraps imported YAML files). # config-schema: "this YAML came from docker-compose.yml" # service-config-schema: "this possibility has auth via Keycloak" # Gate 2 (Reusability): YES — every provisioned service is a service-config # Gate 3 (Clarity): YES — single config per service binding is clear # ============================================================================= # SCHEMA DECLARATION (RFC-007) # ============================================================================= $schema: kno@0.0.9 # ============================================================================= # IDENTITY (Layer 1) # ============================================================================= id: 01KGXP4M8N6RT3VE7S2WJCK9QP slug: service-config-schema type: spec version: 0.8.0 # ============================================================================= # STANDARD TIER # ============================================================================= title: "Service Configuration Schema" purpose: | Define the schema for service configuration entities — bindings between a Possibility and a provisioned infrastructure service. **What is a Service Config?** A service-config entity describes how a specific service (auth, storage, database, compute) is configured for a specific possibility. It acts as the "domain pivot" between the possibility entity and the infrastructure adapter. **Distinction from config-schema:** | Aspect | config-schema | service-config-schema | |--------|--------------|----------------------| | Purpose | Wrap imported YAML/config files | Describe provisioned service bindings | | Content | Preserves original file content | Describes configuration state | | Source | Imported from filesystem | Created by provisioning pipeline | | Layer | L2 domain (document extension) | L3 reusable | | Subtypes | docker-compose, k8s, etc. | auth, storage, database, compute | **Service Type Discriminator:** The `service_type` field (auth, storage, database, compute) determines which optional extension fields are relevant: | service_type | Extension Group | Key Fields | |-------------|----------------|------------| | auth | `auth.*` | model, methods[], roles[], callback_uris[], app_type, token_binding, access_rights[] | | storage | `storage.*` | bucket_name, access_policy, quota | | database | `database.*` | db_name, connection_pool, schema_version | | compute | `compute.*` | runtime, memory, timeout | **Security Model:** Service configs NEVER contain secrets. The `secrets_xri` field points to an Infisical path where credentials live. Adapters resolve this reference at runtime. # ============================================================================= # RICH TIER — Provenance & Taxonomy # ============================================================================= provenance: origin: id: 01KGXP4M8N6RT3VE7S2WJCK9QP timestamp: "2026-02-17T00:00:00Z" tool: manual taxonomy: topics: - service-configuration - infrastructure - provisioning - auth - service-bindings keywords: - service-config - keycloak - minio - postgres - provisioning - secrets - auth - storage - database - compute # ============================================================================= # RICH TIER — Relationships # ============================================================================= relationships: depends_on: - xri: "kno://specs/kno-spec" reason: "Conforms to KNO format specification v0.0.9" composes: - xri: "kno://specs/identity-schema" reason: "Layer 1: id, slug, provenance" - xri: "kno://specs/history-schema" reason: "Layer 1: _history, changelog" - xri: "kno://specs/quality-schema" reason: "Layer 1: quality, validation" related_to: - xri: "kno://specs/possibility-schema" reason: "Possibility services[] entries reference service-config entities" - xri: "kno://specs/config-schema" reason: "Sibling pattern — config-schema wraps imported files, service-config describes service bindings" - xri: "kno://specs/capability-schema" reason: "provision_services capability gates creation of service-config entities" enables: - xri: "kno://content/service-configs/*" reason: "Service configuration instance files" # ============================================================================= # RICH TIER — Quality # ============================================================================= quality: completeness: 0.92 last_reviewed: "2026-07-28" review_status: draft reviewed_by: "claude" # ============================================================================= # HISTORY (P9 Temporal) # ============================================================================= _history: retention: full format: changelog changelog: - version: "0.8.0" date: "2026-07-28" author: "claude" summary: "Token binding (DPoP/mTLS) and RAR access rights" changes: - "Added auth.token_binding object for sender-constrained access tokens (DPoP RFC 9449, mTLS RFC 8705)" - "Fields: method (dpop|mtls|none), key_algorithm, nonce_required, proof_max_age" - "Added auth.access_rights array for RAR-style structured authorization (RFC 9396)" - "Fields per entry: type, locations[], actions[], datatypes[]" - "Updated $schema const from 0.4.0 to 0.8.0 (was behind)" - "Updated examples with token_binding and access_rights" - "Additive, backward compatible — both fields are optional" - "Bumped schema version to 0.8.0" - version: "0.7.0" date: "2026-03-15" author: "claude" summary: "Realm provisioning state — close ontological chain with realm_configs" changes: - "Added auth.internal_client_id field for server-to-server client in per-realm" - "Added auth.admin_service_account field for master realm service account" - "Added top-level status_detail field for error messages and status context" - "Closes ontological chain gap: realm_configs DB table is now a cache of .kno entity data" - "Bumped schema version to 0.7.0" - version: "0.6.0" date: "2026-03-14" author: "claude" summary: "Per-realm isolation support, OpenBao secrets" changes: - "Added auth.realm_name field for per-realm Keycloak naming" - "Renamed auth.model enum: 'organization' → 'per_realm', updated descriptions" - "Updated secrets_xri format from Infisical to OpenBao (bao:// URIs)" - "Updated all examples to use bao:// secrets paths and per_realm model" - "Bumped schema version to 0.6.0" - version: "0.5.0" date: "2026-07-22" author: "claude" summary: "Add auth_ux and registration_fields to auth extension group" changes: - "Added auth.auth_ux enum (hosted|custom) for toggling between Possibility hosted auth UX and custom-built" - "Added auth.registration_fields object for per-field visibility (required|optional|hidden)" - "Fields: first_name, last_name, email, password, birthday" - "Part of #704: Registration UX for device flow" - "Bumped schema version 0.4.0 → 0.5.0 (additive, no breaking changes)" - version: "0.4.0" date: "2026-03-10" author: "claude" summary: "Add email sender domain section to auth extension group" changes: - "Added auth.email object for per-possibility email sender domain configuration" - "Fields: sender_domain, sender_address, reply_to, display_name, status, dns_records, sendgrid_domain_id, relay_domain_configured, verified_at, fallback_enabled" - "Email domain lifecycle: not_setup → configuring → dns_pending → verifying → active (+ failed from any)" - "Requires verified custom_domain first (sequential DNS approach)" - "Part of #656: Per-Possibility Email Sender Domains" - "Bumped schema version 0.3.0 → 0.4.0 (additive, no breaking changes)" - version: "0.3.0" date: "2026-03-06" author: "claude" summary: "Add branding and custom_domain sections to auth extension group" changes: - "Added auth.branding object for per-possibility login page customization" - "Fields: display_name, logo_url, favicon_url, primary_color, background_color, support_email, privacy_policy_url, terms_of_service_url" - "Added auth.custom_domain object for custom auth domain configuration (consumed by Phase 6)" - "Fields: domain, status, verification_method, verification_record, verified_at, tls_status, fallback_enabled" - "Bumped schema version 0.2.0 → 0.3.0 (additive, no breaking changes)" - "Part of Phase 5: Tenant Sequestration (#633)" - version: "0.2.0" date: "2026-02-23" author: "claude" summary: "Add protocol_compliance field to auth extension group" changes: - "Added auth.protocol_compliance object for recording OAuth 2.1 audit results" - "Fields: protocol, status, verified_date, audit_ref, baseline_document, checks_passed/total, observations" - "Updated auth example to include compliance data from COMPLY epic (#269)" - "Compliance baseline: 7/7 checks PASS (docs/architecture/oauth2-1-compliance-baseline.md)" - version: "0.1.0" date: "2026-02-17" author: "claude" summary: "Initial service configuration schema" changes: - "Created for auth-as-a-service-v1 milestone (Phase 1)" - "Merges planned auth-config-schema into single discriminated schema" - "service_type discriminator: auth | storage | database | compute" - "Auth extension group: model, methods[], roles[], callback_uris[]" - "secrets_xri for credential reference — never stores secrets directly" # ============================================================================= # SPECIFICATION CONTENT # ============================================================================= spec: status: Draft description: | ## Service Configuration Entity Structure A service-config entity binds a provisioned service to a possibility. Provisioning adapters create these entities, and runtime adapters read them to configure infrastructure. ### Architecture Position ``` ┌─────────────────────────────────────────────────────────────────────┐ │ PROVISIONING FLOW │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ Contributor │ │ │ │ │ ▼ │ │ POST /api/possibilities/{id}/services │ │ │ │ │ ▼ │ │ ┌─────────────────┐ ┌─────────────────┐ │ │ │ Provisioning │────▶│ service-config │ │ │ │ Pipeline │ │ entity created │ │ │ └────────┬────────┘ └────────┬────────┘ │ │ │ │ │ │ ▼ ▼ │ │ ┌─────────────────┐ ┌─────────────────┐ │ │ │ Provider Adapter │ │ possibility │ │ │ │ (Keycloak, MinIO)│ │ services[] │ │ │ └─────────────────┘ │ updated │ │ │ └─────────────────┘ │ │ │ │ Adapters READ service-config to configure infrastructure. │ │ service-config entities are the "domain pivot" between │ │ possibility entities and infrastructure providers. │ │ │ └─────────────────────────────────────────────────────────────────────┘ ``` ### Security: No Secrets in Schema ``` ┌───────────────────────────────────────────────┐ │ service-config entity │ │ ├── service_type: "auth" │ │ ├── provider: "keycloak" │ │ ├── secrets_xri: "bao://pspace/..." │ ─── reference only # pragma: allowlist secret │ └── status: "active" │ └─────────────────────┬─────────────────────────┘ │ resolved at runtime ▼ ┌───────────────────────────────────────────────┐ │ OpenBao │ │ ├── KEYCLOAK_CLIENT_ID: "abc123" │ │ ├── KEYCLOAK_CLIENT_SECRET: "***" │ │ └── KEYCLOAK_REDIRECT_URI: "https://..." │ └───────────────────────────────────────────────┘ ``` # --------------------------------------------------------------------------- # SERVICE TYPES (discriminator) # --------------------------------------------------------------------------- service_types: - id: auth name: "Authentication / Identity" provider_examples: - keycloak - auth0 - logto extension_group: "auth" description: "OIDC/OAuth2 authentication service" - id: storage name: "Object Storage" provider_examples: - minio - s3 - azure-blob extension_group: "storage" description: "File and object storage service" - id: database name: "Database" provider_examples: - postgres - mysql - mongodb extension_group: "database" description: "Relational or document database" - id: compute name: "Compute / Functions" provider_examples: - docker - lambda - cloud-run extension_group: "compute" description: "Serverless or container compute" # --------------------------------------------------------------------------- # SCHEMA DEFINITION # --------------------------------------------------------------------------- schema: type: object required: - id - service_type - provider - possibility_xri - status properties: # ----------------------------------------------------------------------- # BASIC TIER # ----------------------------------------------------------------------- $schema: type: string const: "service-config@0.8.0" description: "Schema declaration" id: type: string format: ulid immutable: true description: "Unique identifier (ULID). Immutable birth identity." type: type: string const: "service-config" description: "Entity type identifier" version: type: string format: semver description: "Entity instance version" # ----------------------------------------------------------------------- # STANDARD TIER # ----------------------------------------------------------------------- slug: type: string format: kebab-case mutable: true description: | Human-readable identifier. Convention: {possibility-slug}-{service-type} Example: "artisan-marketplace-auth" title: type: string description: "Display title" # ----------------------------------------------------------------------- # CORE DOMAIN FIELDS (all service types) # ----------------------------------------------------------------------- service_type: type: string enum: - auth - storage - database - compute required: true description: | Discriminator field — determines which extension group is relevant. Parallels config-schema's `subtype` pattern. provider: type: string required: true description: | The infrastructure provider handling this service. Examples: "keycloak", "minio", "postgres", "docker" possibility_xri: type: string format: xri required: true description: | XRI reference to the possibility this service belongs to. Example: "pspace://possibility:01KGXYZ..." secrets_xri: type: string format: uri description: | Reference to the secrets location (never inline secrets). Uses OpenBao path format. Example: "bao://pspace/possibilities/artisan-marketplace/auth" status: type: string enum: - pending - provisioning - active - degraded - deprovisioning - inactive - error required: true description: | Current lifecycle status of this service binding. | Status | Meaning | |--------|---------| | pending | Requested, not yet started | | provisioning | Creation in progress | | active | Fully operational | | degraded | Partially operational | | deprovisioning | Removal in progress | | inactive | Deprovisioned, preserved for reference | | error | Provisioning or operation failed | provisioned_at: type: string format: date-time description: "When this service was successfully provisioned" provisioned_by: type: string format: xri description: "XRI of the user or agent that triggered provisioning" status_detail: type: string description: | Freeform status context — error messages, warnings, or notes. Populated when status is 'error' or 'degraded' to explain the cause. Cleared when status transitions to 'active'. provider_resource_id: type: string description: | The identifier for this resource in the provider's system. Example: for Keycloak, the client ID. # ----------------------------------------------------------------------- # AUTH EXTENSION GROUP # Only relevant when service_type: auth # ----------------------------------------------------------------------- auth: type: object description: | Auth-specific configuration fields. Only present when service_type is "auth". These fields describe how the auth provider is configured for this possibility. properties: model: type: string enum: - shared - per_realm description: | Auth tenancy model. - shared: Uses the shared Keycloak realm (pspace) with a dedicated client - per_realm: Dedicated Keycloak realm (pspace-{slug}) with full isolation default: "per_realm" realm_name: type: string description: | Keycloak realm name for this possibility's auth. For per_realm model: "pspace-{slug}" (e.g., "pspace-pongogo") For shared model: "pspace" (the platform realm) Automatically derived from possibility slug during provisioning. internal_client_id: type: string description: | Confidential client ID for server-to-server operations within this realm (e.g., "pspace-api-internal"). Used by pspace-api to perform admin operations like user creation, role assignment, and token exchange within the possibility's realm. admin_service_account: type: string description: | Master realm service account username that administers this possibility's realm. Created during realm provisioning. Example: "service-account-pspace-api-internal" methods: type: array items: type: string enum: - email_password - magic_link - social_google - social_github - social_apple - device_code description: "Enabled authentication methods" default: - email_password roles: type: array items: type: object properties: name: type: string description: "Role name in the auth provider" scope: type: string description: "Permission scope string" description: | Roles configured in the auth provider for this possibility. Maps to Keycloak roles or equivalent. callback_uris: type: array items: type: string format: uri description: "OAuth2 redirect URIs" logout_uris: type: array items: type: string format: uri description: "Post-logout redirect URIs" app_type: type: string enum: - SPA - Traditional - Native - MachineToMachine description: "Application type in the auth provider" default: "SPA" consent_required: type: boolean description: "Whether OAuth consent screen is shown (isThirdParty)" default: true protocol_compliance: type: object description: | Records the protocol compliance status for this auth service. Populated by compliance audits (e.g., COMPLY epic #269–#272). properties: protocol: type: string description: | The protocol standard this service conforms to. Example: "oauth2.1", "oidc-core-1.0" status: type: string enum: - verified - partial - non-compliant - unaudited description: "Overall compliance status" verified_date: type: string format: date description: "Date of the most recent compliance verification" audit_ref: type: string format: uri description: | Reference to the audit document or issue. Example: "https://github.com/PossibilityInc/possibility-space/issues/269" baseline_document: type: string description: | Path to the compliance baseline document. Example: "docs/architecture/oauth2-1-compliance-baseline.md" checks_passed: type: integer description: "Number of compliance checks that passed" checks_total: type: integer description: "Total number of compliance checks evaluated" observations: type: array items: type: string description: | Non-blocking observations from the audit. Example: ["implicit in discovery metadata (#289)"] registration: type: string enum: - invite_only - self_service - disabled description: | Controls how new users can join this possibility. - invite_only: Users must be invited by an admin (default) - self_service: Users can register via POST /auth/register - disabled: No new user registration allowed default: "invite_only" default_role: type: string description: | The role slug assigned to newly registered users. Must reference a role defined in auth.roles[].name. Example: "member", "viewer" default: "member" required_consent: type: array items: type: object properties: type: type: string enum: - terms_of_service - privacy_policy - data_processing - marketing description: "The category of consent being collected" version: type: string description: | Semantic version of the consent document. Changing the version requires users to re-consent. label: type: string description: | Human-readable label shown to the user during registration. Example: "I agree to the Terms of Service" url: type: string format: uri description: | URL to the full consent document. Example: "https://possibility.space/legal/terms" required: type: boolean description: | Whether this consent is mandatory for registration. If true, registration fails without this consent. default: true required: - type - version - label description: | Consent items that must be accepted during self-service registration. Each item is versioned; changing the version forces re-consent. Empty array means no consent is required. default: [] branding: type: object description: | Per-possibility branding for auth pages (login, registration, error). When configured, Keycloakify renders the possibility's brand instead of the default Possibility brand. Values are stored in Keycloak client attributes (prefixed `pspace.branding.*`) during provisioning. All fields are optional — omitted fields fall back to Possibility defaults. properties: display_name: type: string description: | Display name shown on login/registration pages. Example: "Artisan Marketplace" logo_url: type: string format: uri description: | URL to the possibility's logo for auth pages. Recommended: SVG or PNG, minimum 128x128px. favicon_url: type: string format: uri description: "Favicon URL for auth pages" primary_color: type: string format: hex-color description: | Brand color for buttons, links, and accents. Must be a valid hex color (e.g., "#d69e5a"). background_color: type: string format: hex-color description: | Page background color. Must be a valid hex color (e.g., "#1c1b18"). support_email: type: string format: email description: "Support contact email shown on auth pages" privacy_policy_url: type: string format: uri description: "Link to privacy policy shown on registration page" terms_of_service_url: type: string format: uri description: "Link to terms of service shown on registration page" custom_domain: type: object description: | Custom auth domain configuration. Allows a possibility to serve auth pages from its own domain (e.g., auth.pongogo.com) instead of auth.possibility.space. Defined here in Phase 5, consumed by Phase 6 custom domain implementation. properties: domain: type: string format: hostname description: | Custom auth domain (e.g., "auth.pongogo.com"). Must be a valid hostname. status: type: string enum: - pending - verifying - active - failed - removed description: | Current domain verification/activation status. | Status | Meaning | |--------|---------| | pending | Domain registered, verification not started | | verifying | DNS verification in progress | | active | Domain verified and serving traffic | | failed | Verification or TLS provisioning failed | | removed | Domain removed by admin | default: "pending" verification_method: type: string enum: - cname - txt description: "DNS verification type" default: "cname" verification_record: type: string description: | Expected DNS record value for verification. Generated during domain registration. verified_at: type: string format: date-time description: "When DNS verification passed" tls_status: type: string enum: - pending - provisioned - failed description: "TLS certificate provisioning status" default: "pending" fallback_enabled: type: boolean description: | Whether to fall back to auth.possibility.space if the custom domain is unavailable. default: true email: type: object description: | Per-possibility email sender domain configuration. Allows a possibility to send transactional email from its own domain (e.g., hello@mail.pongogo.com) instead of the default hello@mail.possibility.space. Requires a verified custom_domain first (sequential DNS approach). Email domain setup is an opt-in second phase after web domain verification. Lifecycle: not_setup → configuring → dns_pending → verifying → active Any intermediate state can transition to "failed". properties: sender_domain: type: string format: hostname description: | Email sender domain (e.g., "mail.pongogo.com"). Typically "mail.{custom_domain}" but can be any verified domain. sender_address: type: string format: email description: | Full sender email address. Example: "noreply@mail.pongogo.com" reply_to: type: string format: email description: | Reply-to address for transactional email. Example: "support@pongogo.com" display_name: type: string description: | Sender display name for email From header. Example: "Pongogo" status: type: string enum: - not_setup - configuring - dns_pending - verifying - active - failed description: | Email sender domain lifecycle status. | Status | Meaning | |--------|---------| | not_setup | Email domain not requested (default) | | configuring | SendGrid domain created, awaiting DNS records | | dns_pending | DNS records returned to admin, awaiting configuration | | verifying | SendGrid verification poll in progress | | active | Fully verified, relay configured, sender ready | | failed | Verification failed or timed out | default: "not_setup" dns_records: type: array items: type: object properties: record_type: type: string enum: [SPF, DKIM, DKIM2, DMARC, MX, CNAME] host: type: string value: type: string ttl: type: integer description: | Required DNS records for email domain verification. Returned by SendGrid during domain authentication setup. Admin must configure these in their DNS provider. sendgrid_domain_id: type: string description: | SendGrid domain authentication ID. Used for verification polling and status checks. relay_domain_configured: type: boolean description: | Whether the domain has been authenticated with the email relay provider (currently SendGrid). default: false verified_at: type: string format: date-time description: "When email domain verification completed" fallback_enabled: type: boolean description: | Whether to fall back to hello@mail.possibility.space if the custom email domain is unavailable. default: true auth_ux: type: string enum: - hosted - custom description: | Whether this possibility uses Possibility's hosted auth UX (sign-in/sign-up pages served by the Keycloak theme) or builds their own registration/login UI. | Value | Meaning | |-------|---------| | hosted | Use Possibility's branded sign-in/sign-up pages | | custom | Possibility builds their own auth UI using the API | default: "hosted" registration_fields: type: object description: | Per-field visibility and requirement configuration for the hosted registration form. Only relevant when auth_ux: hosted and registration: self_service. properties: first_name: type: string enum: [required, optional, hidden] default: "required" last_name: type: string enum: [required, optional, hidden] default: "required" email: type: string enum: [required] default: "required" description: "Always required — cannot be hidden or optional" password: type: string enum: [required] default: "required" description: "Always required — cannot be hidden or optional" birthday: type: string enum: [required, optional, hidden] default: "hidden" token_binding: type: object description: | Token binding configuration for sender-constrained access tokens. When enabled, tokens are cryptographically bound to the client's key pair, preventing token theft and replay attacks. Supports DPoP (RFC 9449) and mTLS (RFC 8705) binding methods. Default: none (standard bearer tokens). Provisioning adapters read this field to configure the auth provider (e.g., Keycloak's dpop.bound.access.tokens attribute). properties: method: type: string enum: - dpop - mtls - none description: | Token binding method. - dpop: Demonstration of Proof-of-Possession (RFC 9449) — client proves possession of a key pair with each request - mtls: Mutual TLS (RFC 8705) — client certificate binds tokens to TLS connection - none: Standard bearer tokens (no binding) default: "none" key_algorithm: type: string enum: - ES256 - ES384 - ES512 - RS256 - RS384 - RS512 - PS256 - PS384 - PS512 - EdDSA description: | Preferred key algorithm for DPoP proofs. ES256 recommended for balance of security and performance. Only relevant when method is "dpop". default: "ES256" nonce_required: type: boolean description: | Whether the authorization server requires a server-supplied nonce in DPoP proofs. Adds replay protection at the cost of an extra round trip. Only relevant when method is "dpop". default: true proof_max_age: type: integer description: | Maximum age of DPoP proof JWT in seconds. Proofs older than this are rejected. Only relevant when method is "dpop". default: 300 access_rights: type: array description: | RAR-style structured access rights (RFC 9396). Defines fine-grained authorization_details types that clients can request via OAuth Rich Authorization Requests. Coexists with traditional OAuth scopes — scopes for coarse-grained access, access_rights for fine-grained resource-specific access. Each entry maps to an `authorization_details` type that clients include in token requests. The auth provider (Keycloak) stores these as client scope + protocol mapper. items: type: object required: - type properties: type: type: string description: | The authorization_details type identifier. Possibility-specific types: - possibility_data: Access to possibility entities and data - possibility_admin: Administrative operations on a possibility - possibility_activity: Activity log access Custom types may be registered per-possibility. locations: type: array items: type: string format: xri description: | XRI references to specific resources this access right covers. Example: ["pspace://possibility/artisan-marketplace"] actions: type: array items: type: string enum: - read - write - delete - admin description: "Permitted actions on the specified resources." datatypes: type: array items: type: string description: | Types of data this access right covers. Examples: entity, activity, user, role, config # ----------------------------------------------------------------------- # STORAGE EXTENSION GROUP # Only relevant when service_type: storage # ----------------------------------------------------------------------- storage: type: object description: "Storage-specific configuration fields" properties: bucket_name: type: string description: "Storage bucket name" access_policy: type: string enum: - private - public-read - authenticated-read description: "Default access policy for the bucket" quota_mb: type: integer description: "Storage quota in megabytes" # ----------------------------------------------------------------------- # DATABASE EXTENSION GROUP # Only relevant when service_type: database # ----------------------------------------------------------------------- database: type: object description: "Database-specific configuration fields" properties: db_name: type: string description: "Database name" schema_name: type: string description: "Schema namespace within the database" connection_pool_size: type: integer description: "Maximum connections in pool" schema_version: type: string format: semver description: "Current migration/schema version" # ----------------------------------------------------------------------- # COMPUTE EXTENSION GROUP # Only relevant when service_type: compute # ----------------------------------------------------------------------- compute: type: object description: "Compute-specific configuration fields" properties: runtime: type: string description: "Runtime environment (node18, python3.11, etc.)" memory_mb: type: integer description: "Memory allocation in megabytes" timeout_seconds: type: integer description: "Execution timeout in seconds" replicas: type: integer description: "Number of replicas/instances" # ============================================================================= # EXAMPLES # ============================================================================= examples: - title: "Auth Service Config (Keycloak, Per-Realm)" description: "A Keycloak auth binding for a possibility using per-realm isolation with branding" content: | $schema: service-config@0.8.0 id: 01KGSVC1AUTH type: service-config version: 1.0.0 slug: artisan-marketplace-auth title: "Artisan Marketplace Auth" service_type: auth provider: keycloak possibility_xri: "pspace://possibility:01KGXP3F5T8KV2NQ4R7WJBM6YH" secrets_xri: "bao://pspace/possibilities/artisan-marketplace/auth" # pragma: allowlist secret status: active provisioned_at: "2026-02-17T12:00:00Z" provisioned_by: "pspace://user:01KGABC123DEF456" provider_resource_id: "keycloak-client-xyz123" auth: model: per_realm realm_name: "pspace-artisan-marketplace" internal_client_id: "pspace-api-internal" admin_service_account: "service-account-pspace-api-internal" methods: - email_password - magic_link callback_uris: - "https://artisan-marketplace.possibility.space/callback" logout_uris: - "https://artisan-marketplace.possibility.space" app_type: SPA consent_required: true protocol_compliance: protocol: "oauth2.1" status: verified verified_date: "2026-02-23" audit_ref: "https://github.com/PossibilityInc/possibility-space/issues/269" baseline_document: "docs/architecture/oauth2-1-compliance-baseline.md" checks_passed: 7 checks_total: 7 observations: - "implicit in discovery metadata (#289)" registration: self_service default_role: member required_consent: - type: terms_of_service version: "1.0.0" label: "I agree to the Terms of Service" url: "https://artisan-marketplace.possibility.space/legal/terms" required: true - type: privacy_policy version: "1.0.0" label: "I agree to the Privacy Policy" url: "https://artisan-marketplace.possibility.space/legal/privacy" required: true branding: display_name: "Artisan Marketplace" logo_url: "https://storage.possibility.space/artisan-marketplace/logo.svg" primary_color: "#e07c3e" background_color: "#1a1a2e" support_email: "support@artisan-marketplace.example.com" privacy_policy_url: "https://artisan-marketplace.possibility.space/legal/privacy" terms_of_service_url: "https://artisan-marketplace.possibility.space/legal/terms" token_binding: method: dpop key_algorithm: ES256 nonce_required: true proof_max_age: 300 access_rights: - type: possibility_data locations: - "pspace://possibility/artisan-marketplace" actions: - read - write datatypes: - entity - activity - type: possibility_admin locations: - "pspace://possibility/artisan-marketplace" actions: - admin datatypes: - user - role - config - title: "Storage Service Config (MinIO)" description: "A MinIO storage binding for a possibility" content: | $schema: service-config@0.8.0 id: 01KGSVC2STOR type: service-config version: 1.0.0 slug: artisan-marketplace-storage title: "Artisan Marketplace Storage" service_type: storage provider: minio possibility_xri: "pspace://possibility:01KGXP3F5T8KV2NQ4R7WJBM6YH" secrets_xri: "bao://pspace/possibilities/artisan-marketplace/storage" # pragma: allowlist secret status: active provisioned_at: "2026-02-17T12:05:00Z" storage: bucket_name: "artisan-marketplace" access_policy: private quota_mb: 5120 - title: "Database Service Config (PostgreSQL)" description: "A PostgreSQL database binding for a possibility" content: | $schema: service-config@0.6.0 id: 01KGSVC3DB type: service-config version: 1.0.0 slug: artisan-marketplace-db title: "Artisan Marketplace Database" service_type: database provider: postgres possibility_xri: "pspace://possibility:01KGXP3F5T8KV2NQ4R7WJBM6YH" secrets_xri: "bao://pspace/possibilities/artisan-marketplace/database" # pragma: allowlist secret status: provisioning database: db_name: "artisan_marketplace" schema_name: "public" connection_pool_size: 10 schema_version: "1.0.0" # ============================================================================= # VALIDATION RULES # ============================================================================= validation: rules: - name: "service_type_required" severity: error message: "Service config must have a service_type" check: "exists(service_type)" - name: "provider_required" severity: error message: "Service config must specify a provider" check: "exists(provider)" - name: "possibility_xri_required" severity: error message: "Service config must reference a possibility via possibility_xri" check: "exists(possibility_xri)" - name: "no_inline_secrets" severity: error message: "Never store secrets inline — use secrets_xri reference" check: "not_contains_pattern(*, '(password|secret|key|token):\\s+[^\\s]')" - name: "auth_fields_when_auth_type" severity: warning message: "Auth extension group should be present when service_type is auth" check: "exists(auth)" when: "service_type == 'auth'" - name: "storage_fields_when_storage_type" severity: warning message: "Storage extension group should be present when service_type is storage" check: "exists(storage)" when: "service_type == 'storage'" - name: "secrets_xri_recommended" severity: warning message: "Service configs should reference a secrets path" check: "exists(secrets_xri)" # ============================================================================= # HISTORY (P9 Temporal) # ============================================================================= source: | # This schema was created for the auth-as-a-service-v1 milestone. # It merges the originally planned auth-config-schema and generic # service-config into a single discriminated schema per Phase 0 # design decision #4. source_format: yaml source_hash: "sha256:tbd" # ============================================================================= # CONTAINER TIER — Navigation Index # ============================================================================= _index: - path: "identity" line: 37 keywords: [id, slug, type, version] - path: "spec/service_types" line: 165 keywords: [auth, storage, database, compute] - path: "spec/schema" line: 195 keywords: [fields, service_type, provider, secrets_xri] - path: "spec/schema/auth" line: 310 keywords: [auth, model, methods, callback, app_type, registration, consent] - path: "examples" line: 400 keywords: [keycloak, minio, postgres, auth, storage] - path: "validation" line: 480 keywords: [rules, secrets, type] contains: - xri: "#spec" role: section title: "Specification Content" keywords: [schema, service_type, provisioning] - xri: "#spec/service_types" role: section title: "Service Types" keywords: [auth, storage, database, compute] - xri: "#spec/schema/auth" role: section title: "Auth Extension Group" keywords: [model, methods, roles, callback, registration, consent, default_role] - xri: "#examples" role: section title: "Usage Examples" keywords: [keycloak, minio, postgres] - xri: "#validation" role: section title: "Validation Rules" keywords: [required, secrets, type]