- Add deployment-name branding to titles, mastheads, and OG tags - Share one grant delivery-state query with lineage across grants surfaces - Show pool status/usage, org owners, and config readiness - Make billing views projection-aware with recency and sync vocabulary - Guard FedWiki creation without domains and render route-aware 404s
9.7 KiB
integration-settings
Purpose
Defines runtime-managed integration configuration: a single core-owned store of operator-set, non-secret overrides layered over the environment (override → environment → default), discovered and validated from each integration's declared configuration spec, applied by a boot-time overlay (changes take effect on restart), and surfaced through a generic per-integration operator settings page. Secrets and bootstrap remain environment-only; this capability never stores or displays a secret.
Requirements
Requirement: Runtime overrides are stored app-level and override-only
The system SHALL store operator-set integration configuration overrides in a
single core-owned table, core.integration_config_overrides, keyed by the
flat configuration key name. A row SHALL exist only while an override is
set; value encoding SHALL be the same string form the environment transport
uses. Each row SHALL record who set it and when. No per-integration
configuration tables SHALL be introduced.
Scenario: Absent row falls through
- WHEN no override row exists for a configuration key
- THEN the key SHALL resolve from environment/flag/config-file, then its declared default
Scenario: Clearing an override removes the row
- WHEN an operator clears an override
- THEN the row SHALL be deleted, restoring resolution from environment/default at next effect
Requirement: Configuration resolves runtime override first
Every declared integration configuration key SHALL resolve in the order: runtime override → environment/flag/config-file → declared default. The order SHALL be uniform across keys and integrations.
Scenario: Override wins over environment
- WHEN a key is set in the environment and an override row exists
- THEN the effective value SHALL be the override
Scenario: Environment wins over default
- WHEN a key has no override row and is set in the environment
- THEN the effective value SHALL be the environment value, not the declared default
Requirement: Overrides take effect at boot
The system SHALL load overrides once at startup — after migrations run, before any route or worker registration — and apply them ahead of all integration configuration reads. Saving an override SHALL NOT alter the running process's effective configuration. The settings surface SHALL state that changes apply on restart and SHALL indicate per key when a stored override differs from the boot-effective value.
Scenario: Save does not change the running process
- WHEN an operator saves an override while the system is running
- THEN behavior governed by that key SHALL be unchanged until restart
- AND the settings surface SHALL mark the key as pending restart
Scenario: Restart applies the override
- WHEN the system restarts with an override row present
- THEN the override SHALL be the effective value everywhere the key is read
Requirement: Secret keys are never runtime-managed
Keys declared secret SHALL NOT be storable as overrides, SHALL NOT be editable from the settings surface, and their values SHALL never be displayed. The settings surface SHALL list secret keys by name with an environment-managed marker only.
Scenario: Secret key rendering
- WHEN the settings page renders an integration whose spec declares a secret key
- THEN the key SHALL appear with its usage text and an environment-managed marker, with no value and no input
Scenario: Secret override write is rejected
- WHEN a write names a secret key as an override target
- THEN the system SHALL reject it and store nothing
Requirement: Override values are validated and typed
Override writes SHALL be validated at save time against the key's declaration: values for keys declaring an enumeration SHALL be members of it. At boot, the overlay SHALL coerce each stored value by the key's declared default type (list keys comma-split, boolean and integer keys parsed) so typed reads behave identically to environment-sourced values. A stored value that fails coercion or enumeration at boot SHALL fail startup with an error naming the key and the remediation.
Scenario: List-typed override reads as a list
- WHEN a key declared with a list default has override value "a.example,b.example"
- THEN typed reads of that key SHALL return two elements after restart
Scenario: Invalid enum value rejected at save
- WHEN an operator submits a value outside a key's declared enumeration
- THEN the save SHALL be rejected and no row written
Scenario: Corrupt stored value fails boot actionably
- WHEN the overrides table contains a value that fails coercion for its key (e.g. written by hand)
- THEN startup SHALL fail with an error naming the key and how to delete the offending row
Requirement: Bootstrap remains environment-only
Boot-time required-configuration validation SHALL continue to run before the database is available and SHALL be satisfiable from the environment alone. Overrides SHALL layer on top of a valid environment configuration and SHALL NOT substitute for required keys at validation time.
Scenario: Required key present only as an override
- WHEN a required key is unset in the environment but has an override row
- THEN boot validation SHALL fail exactly as if the key were unset
Requirement: Operator settings surface is generic and declaration-driven
The operator panel SHALL provide a per-integration settings page at
/operator/integrations/{slug}/settings, rendered by one core handler and
template for all integrations from the integration's declared configuration
spec. For each non-secret key it SHALL show the key, its usage text, the
effective value, and the winning source (override, environment, or
default), with an input to set or clear the override; keys declaring an
enumeration SHALL render as a closed select. Writes SHALL follow the
operator panel's HTMX + CSRF-header pattern and require the operator role.
Registering an integration with declared configuration SHALL surface its
settings page with no per-integration template or handler edits. Override
rows whose key matches no installed integration's declaration SHALL be
listed with a removal affordance.
Scenario: Settings page renders from declarations
- WHEN an operator opens an integration's settings page
- THEN every declared non-secret key SHALL appear with usage text, effective value, and source, and every secret key with an environment-managed marker
Scenario: New integration needs no surface work
- WHEN a new integration declaring configuration is registered
- THEN its settings page SHALL be reachable and complete with no operator template or handler edits
Scenario: Setting and clearing an override
- WHEN an operator saves a valid value for a non-secret key and later clears it
- THEN the save SHALL upsert the override row and the clear SHALL delete it, each reflected on the page
Scenario: Orphan overrides are visible
- WHEN an override row's key matches no installed integration's declaration
- THEN the operator surface SHALL list it as unrecognized with an affordance to delete it
Requirement: Integration status presentation distinguishes registry lifecycle from configuration readiness
Operator surfaces that present per-integration state (the integrations list and the overview's system region) SHALL label the registry lifecycle value for what it is — an operator-managed registry status, defaulting to active and never changed by any health check — and SHALL NOT present it under wording that implies connectivity or live health ("connected", "read live"). Alongside the registry status, these surfaces SHALL show a configuration-readiness signal derived from the same required-key resolution the settings pages already perform: an integration with any required key unresolved SHALL be visibly marked as not configured wherever its status is shown, not only on its own settings page.
Scenario: Unconfigured integration is visibly unconfigured on the list
- WHEN an operator views the integrations list while an integration's required key resolves to no value
- THEN that integration's row shows a not-configured signal alongside its registry status
- AND the page does not describe the integration as connected
Scenario: Registry status is labeled as registry status
- WHEN an operator views any surface showing integration status
- THEN the lifecycle value is labeled so it cannot read as a health check result
Requirement: Secret settings distinguish set from unset
Settings surfaces SHALL render required secret keys with a truthful presence indicator — set (masked) versus not set — rather than one identical masked treatment for both states, so an operator can tell whether a required secret exists without consulting the environment.
Scenario: Missing secret renders as not set
- WHEN an operator views the settings page for an integration whose required secret is absent from the environment
- THEN the secret's row states that no value is set
- AND a present secret renders as set-and-masked, visually distinct from the absent state
Requirement: Switching Stripe to live mode carries a consequence warning
The stripe-mode setting SHALL disclose, adjacent to the control and before submission, that selecting live moves the deployment onto real payment processing with real charges, distinguishing this control from routine settings on the same page.
Scenario: Live mode selection is warned before save
- WHEN an operator selects
liveforstripe-mode - THEN consequence copy adjacent to the control states that real charges result
- AND the copy renders before the operator submits the change