## Context The plugin JSON schema and TypeScript SDK already define discovery probes, lifecycle profiles, dependency probes/install plans, log sources, transports, and client-manager profiles. Platform manifest DTOs do not decode `runtimeProfiles`, `GamePlugin` does not store them, and durable snapshots omit `RuntimeBinding`, so those declarations and bindings disappear before they can drive a server workflow. The existing action gate also reports complete when no binding rows exist. The platform uses typed domain/model records behind repository interfaces. File and MySQL backends durably serialize the same `StoreSnapshot`, while tests use `MemoryStore`. The web console consumes platform-owned safe DTOs and must never receive raw host paths, direct sockets, credentials, or secret storage values. ## Goals / Non-Goals **Goals:** - Preserve every supported safe runtime-profile declaration during manifest registration and durable reload. - Select exactly one declared lifecycle profile per server runtime binding and derive its required logical keys deterministically. - Create or update bindings through owner/admin-authorized APIs, persist them in every durable store, and expose only redacted readiness metadata. - Require a complete binding before lifecycle and runtime actions whose execution depends on the selected profile. - Make profile selection and binding completion available in the create-server and server-detail workflows. **Non-Goals:** - Storing raw credentials, direct sockets, or host filesystem paths in platform metadata. - Implementing a general secret vault, run-side path resolver, durable scheduler, process supervisor, log/artifact backend, dependency installer, self-update system, or client-manager deployment lifecycle. - Changing the independent run repository or declaring the wider production-readiness roadmap complete. ## Decisions ### Decision 1: Persist typed profiles on the installed plugin record Platform will mirror the existing manifest/SDK runtime profile structures in domain, DTO, and model packages and copy them into `GamePlugin` at registration. File/MySQL snapshots already persist plugin records, so this preserves the immutable installed-version contract without reaching across repository roots or storing arbitrary manifest JSON. Alternative considered: keep only the manifest artifact reference and re-read the artifact for every request. This was rejected because artifact availability is a separate lifecycle, it makes validation/reload behavior non-deterministic, and it leaves action gating dependent on external content. ### Decision 2: One server-scoped binding aggregate selects one lifecycle profile Each server has one `RuntimeBinding` identified deterministically from its server ID. It records the plugin ID/version contract, selected lifecycle `profileKey`, profile mode, logical binding refs, derived missing keys, and readiness status. Required keys are derived from the selected lifecycle profile and the referenced discovery, dependency, log, transport, and client-manager declarations; callers cannot self-assert `complete` or `missingKeys`. Alternative considered: one row per logical key. This was rejected for now because profile changes need atomic validation and readiness projection, while the existing repository abstraction has aggregate create/update semantics. ### Decision 3: Accept safe opaque values, return redacted metadata Write requests accept logical references and `secret://` references only. Values containing raw absolute paths, URI sockets/DSNs, inline credentials, traversal, or other unsafe material are rejected. Read responses return each logical key with a `configured` boolean and `secret` boolean, never the stored value or internal secret-storage location. Missing reasons name only declared logical keys. Alternative considered: return stored logical refs directly. This was rejected because even non-secret refs can encode topology or storage details and the browser does not need them to review readiness. ### Decision 4: Create workflow persists the binding before dispatch Server creation requires a declared profile key and optional initial binding values. The service validates the plugin/profile, creates the server and its binding, verifies completeness, and only then queues install. If required keys are missing, the request fails with logical missing-key details and no install job is dispatched. Repository rollback is limited by the current non-transactional abstraction; validation is therefore completed before the first write, and a binding persistence failure prevents dispatch and is surfaced explicitly. Existing stored servers without a binding remain readable but are action-gated with a safe `runtime profile is not configured` reason until an owner or platform admin configures one. ### Decision 5: Authorization reuses server ownership rules Listing a binding uses server visibility; changing it requires server ownership or platform-admin authority. Plugin pages receive no direct binding mutation surface. Lifecycle services independently check binding readiness so bypassing the UI or runtime-action projection cannot dispatch work. ### Decision 6: Web forms use declared contract data `GamePluginResponse` exposes safe runtime-profile declarations required for selection and labels. The create form renders the selected plugin's lifecycle profiles and declared logical keys, submits the real profile and bindings, and avoids path/socket/credential terminology. The server detail view loads the redacted binding, supports profile changes and logical-key updates, and shows safe missing reasons. ## Risks / Trade-offs - [Snapshot writes are aggregate and not transactional across server and binding repositories] -> Perform all validation before writes, persist the binding before job dispatch, and add failure/reload tests; a later durable-job change can introduce transactions. - [Profile key derivation can over-require unrelated declarations] -> Scope derivation to the selected lifecycle profile and directly referenced transports/client manager, plus required global discovery/dependency/log targets. - [Opaque safe refs cannot prove run-side resolvability] -> Treat platform completeness as contract completeness only; run-side resolution/health remains a later lifecycle responsibility. - [Existing servers become gated after upgrade] -> Keep them readable and return a safe configuration-required reason; operators can select a profile in server detail. - [Changing an active server profile could invalidate running work] -> Reject binding updates while the server is installing or running; require a stable non-active state. ## Migration Plan 1. Deploy profile-aware decoding and snapshot fields with backward-compatible empty defaults. 2. Existing plugin records without persisted profiles remain listable but cannot configure a runtime binding until the manifest is re-registered. 3. Existing servers without bindings remain visible with lifecycle/runtime actions disabled. 4. Re-register manifests, then configure each server binding through the authorized detail workflow. 5. Rollback can ignore the additive JSON fields; no raw secret values are introduced by this change. ## Open Questions - Transactional multi-resource creation and encrypted secret material persistence are deferred to the next security/persistence task rather than being represented as complete here.