feat: 完整游戏运维功能

This commit is contained in:
npc0-hue
2026-07-18 09:04:01 +08:00
parent f3b14b7945
commit 48b8ad8d6c
187 changed files with 16607 additions and 1140 deletions
@@ -55,11 +55,11 @@
## 8. Real Distribution Build Repair
- [ ] 8.1 Replace synchronous synthetic run/client artifacts with queued `distribution.build` jobs, building distribution records, authenticated build-input retrieval, and terminal job projection.
- [ ] 8.2 Implement the independent run worker build adapter for trusted run source and approved HTTPS client-manager repositories, including fixed Go builds, isolated workspaces, config packaging, checksums, and chunked artifact upload.
- [ ] 8.3 Drive the platform_web generation dialog from real job progress and terminal state instead of timer-completed stages.
- [ ] 8.4 Add regression coverage proving generation queues a backend job, does not publish JSON plans as artifacts, publishes only uploaded build output, and reports actual progress/failure.
- [ ] 8.5 Run focused platform, run, frontend, OpenSpec, and structure verification and record the evidence below.
- [x] 8.1 Replace synchronous synthetic run/client artifacts with queued `distribution.build` jobs, building distribution records, authenticated build-input retrieval, and terminal job projection.
- [x] 8.2 Implement the independent run worker build adapter for trusted run source and approved HTTPS client-manager repositories, including fixed Go builds, isolated workspaces, config packaging, checksums, and chunked artifact upload.
- [x] 8.3 Drive the platform_web generation dialog from real job progress and terminal state instead of timer-completed stages.
- [x] 8.4 Add regression coverage proving generation queues a backend job, does not publish JSON plans as artifacts, publishes only uploaded build output, and reports actual progress/failure.
- [x] 8.5 Run focused platform, run, frontend, OpenSpec, and structure verification and record the evidence below.
## Verification Evidence
@@ -76,3 +76,17 @@
- `LOCAL_DEBUG_PLATFORM_PORT=18189 LOCAL_DEBUG_WEB_PORT=5183 LOCAL_DEBUG_ROOT=/private/tmp/browser-local-debug-acceptance scripts/browser-acceptance.sh`: passed.
- Browser evidence file: `/private/tmp/browser-local-debug-acceptance/browser-acceptance/browser-acceptance-evidence.json`.
- Browser walkthrough evidence covered 首页、服务器管理、服务器管理 / 运行操作菜单、插件市场、用户管理、AI 提供商管理、服务器详情、服务器详情 / 插件控制, plus desktop/mobile checks for black mecha and magical-girl themes.
### Real Distribution Build Repair Evidence (2026-07-17)
- `cd platform && go test ./service ./validator ./api`: passed.
- `cd run && go test ./runtime ./protocol ./api`: passed; the runtime suite compiled a real run executable from an isolated source copy and uploaded the archive through chunked artifact calls.
- `cd platform_web && npm test -- --run components/RuntimeTaskProgress.test.ts pages/ConsolePages.test.tsx`: passed, 2 files / 13 tests.
- `cd platform && go test ./...`: passed across all platform packages.
- `cd run && go test ./...`: passed across all independent run packages.
- `cd platform_web && npm test`: passed, 15 files / 76 tests.
- `cd platform_web && npm run typecheck`: passed.
- `cd platform_web && npm run build`: passed, Vite production build completed.
- `openspec validate add-run-distribution-and-client-managers --strict`: passed.
- `scripts/check-structure.sh`: passed.
- Regression evidence covers queued `distribution.build` creation with no synthetic artifact, authenticated leased build-input retrieval, rejection of premature success before artifact upload, successful retry after real artifact publication, retained downloadable chunk payloads, isolated trusted run source copies, rejection of credential-bearing/unpinned client repositories, and frontend projection of real running/succeeded/failed job states.
@@ -0,0 +1,35 @@
# Design
## Session records
`AuthSessionRecord` stores only a SHA-256 token hash, user ID, issued/expiry timestamps, revoked timestamp, and rotation generation. The bearer token is returned once at login/rotation and is never stored in a snapshot. `GetCurrentUser` hashes the presented token and loads the record from the repository, rejecting missing, revoked, expired, or disabled-user sessions. The default TTL is bounded (8 hours); rotation revokes the prior generation before creating a new record. FileStore and MySQLStore snapshots load these records before serving requests.
Strict production routers deliver the platform session in an `HttpOnly`, `SameSite=Strict` cookie and omit the token from JSON, so platform_web JavaScript does not persist new raw tokens. Explicit CLI/local tooling may request a bearer response with `X-Auth-Token-Response: bearer`; the explicitly named `NewTestRouterWithCore` compatibility constructor retains the bearer response contract for existing in-process tests. All normal router constructors enforce authorization.
Run control state follows the same shape: a Run session has a bounded TTL, status, generation, capability fingerprint, and last-seen timestamp. Heartbeat/claim/ack/progress/result/cancel/reconcile/log/artifact operations must validate the current session and reject expired or revoked state.
## Signed Run envelope
HTTP Run channel requests carry `X-Run-Endpoint`, `X-Run-Timestamp`, `X-Run-Nonce`, and `X-Run-Signature`. The signature is HMAC-SHA256 over method, path, timestamp, nonce, and SHA-256 request body using the established Run session token as the channel key. Timestamps are accepted only inside a five-minute clock-skew window and each nonce is accepted once per Run session. The body is buffered and restored before JSON decoding. Legacy in-process service calls remain available for existing tests, but HTTP channel handlers reject missing/invalid signatures after session establishment. Signature errors are safe 401/403 failures and never echo token material.
## Authorization matrix
- Public: health, login, registration, and read-only marketplace/plugin discovery that contains no secret or host detail.
- Platform admin: user administration, AI provider metadata, plugin installation/registration/state, Run endpoint administration, platform metrics, audit inspection, and global job/artifact/log metadata.
- Authenticated server owner or assigned server administrator: server list/detail, runtime binding review, lifecycle, config diff/approval, logs, artifacts, distributions, dependency and client-manager actions for servers visible to that user.
- Owner or platform admin only: runtime binding writes, key reset, server administrator membership changes, destructive/archive actions.
- Run service identity: channelized control/jobs/logs/artifacts for its own endpoint and only resources addressed by a validated job/transfer/session; never browser bearer sessions.
Services repeat ownership and role checks even when a route already checked them. Cross-owner reads/writes return `forbidden`; missing/invalid/expired credentials return `unauthorized`.
## Secret boundary
Secret-bearing writes accept only controlled `secret://` references or opaque logical refs already declared by a plugin. Stored records keep references, encrypted component-key material, status, generation, and fingerprints; raw values are never returned. DTOs expose only `configured`, `secret`, `presence`, and safe fingerprint/status fields. Snapshot tests scan serialized JSON and browser responses for secret literals, paths, sockets, and credentials.
## Failure and migration behavior
Older snapshots decode absent auth/session/secret arrays as empty. Existing users remain valid but must log in again after upgrade if no session record exists. Existing Run endpoints without a persisted session require a fresh signed hello. Expired/revoked records remain durable for audit and can be pruned in a later change. No rollback step writes raw secret data.
## Deferred risks
The controlled secret reference is not a production vault and encrypted component-key material still depends on the configured platform protection boundary. KMS/HSM integration, key wrapping, and multi-resource transactions are explicitly deferred and must not be represented as complete by this change.
@@ -0,0 +1,33 @@
# Harden Platform Authentication, Authorization, and Secret Persistence
## Why
Platform account sessions and Run control sessions currently live only in process memory, so a restart invalidates valid clients and leaves revocation/expiry state implicit. Several management routes also rely on the caller reaching a UI path rather than enforcing an authenticated role or resource-ownership boundary at the API/service boundary. Existing component-key and distribution metadata is not included in the durable file/MySQL snapshot, while secret-bearing values must remain platform-owned and redacted.
## What Changes
- Persist hashed platform sessions and Run control session state with explicit issued/expiry/revoked timestamps and safe rotation.
- Require authenticated sessions for sensitive management APIs and enforce platform-admin, server-owner/administrator, and Run-service boundaries in handlers and services.
- Add a signed, timestamped Run request envelope for control/job/log/artifact channel requests where the HTTP boundary can validate a trusted Run session and reject stale/replayed messages.
- Persist existing encrypted component-key and distribution metadata in FileStore/MySQLStore snapshots, plus controlled secret metadata references and presence/fingerprint projections.
- Ensure auth failures are stable 401/403 API errors and the web client clears invalid sessions without rendering token, key, path, socket, or secret literals.
- Add regression coverage for login/reload, expiry/revocation/rotation, signed Run requests, cross-owner/role rejection, replay/clock failures, snapshot recovery, and non-disclosure.
## Goals / Non-Goals
**Goals:**
- Make session and authorization decisions durable and independently enforceable from the UI.
- Keep raw credentials, host paths, direct sockets, and provider keys out of DTOs, logs, snapshots, and browser state.
- Preserve the independent `run` repository boundary and channel priorities.
**Non-Goals:**
- A production KMS/vault, encrypted secret-value storage, durable scheduling, process supervision, logs/artifacts backends, dependency installation, self-update, client-manager lifecycle, or production scaling.
- Re-adding Run source code to this repository or claiming the later roadmap is complete.
## Impact
- `platform/`: auth/session domain, repositories, snapshots, signed Run request validation, route authorization, safe secret metadata.
- `platform_web/`: API error/session handling and safe auth capability projections.
- `plugins/`: no raw credential or direct channel access; manifest/SDK contracts remain unchanged except for regression fixtures if needed.
@@ -0,0 +1,71 @@
# Platform Authentication, Authorization, and Secret Persistence
## ADDED Requirements
### Requirement: Platform sessions are durable and bounded
The platform SHALL persist only hashed bearer-session records with user ownership, issued time, expiry time, revocation time, and rotation generation, and SHALL reject missing, expired, revoked, or disabled-user sessions.
#### Scenario: Session survives restart
- **WHEN** a user logs in, the platform store is closed and reopened, and the same bearer token is presented before expiry
- **THEN** the platform restores the session record and authenticates the user without storing the raw token
#### Scenario: Expired or revoked session is rejected
- **WHEN** an expired or revoked bearer token is presented
- **THEN** the API returns 401 and performs no protected read or write
#### Scenario: Session rotation revokes the old generation
- **WHEN** an authenticated user rotates a session
- **THEN** a new token is issued, the previous generation is revoked durably, and the previous token is rejected
#### Scenario: Browser session token is not script-readable
- **WHEN** a user logs in through the strict production router
- **THEN** the platform sets an HttpOnly SameSite session cookie and omits the raw token from the JSON response
### Requirement: Run requests use a trusted bounded channel
Run control, job, log, and artifact channel requests SHALL validate the current endpoint session and, at the HTTP boundary, a timestamped HMAC signature with a five-minute clock-skew limit and single-use nonce.
#### Scenario: Valid signed request
- **WHEN** a request is signed by the current Run session with an accepted timestamp and unused nonce
- **THEN** the request is processed for that endpoint and the nonce is recorded as used
#### Scenario: Invalid, stale, or replayed request
- **WHEN** the signature is invalid, the timestamp is outside the skew window, or the nonce was already used
- **THEN** the request is rejected with a safe authentication error and no state mutation occurs
### Requirement: Sensitive APIs enforce role and resource ownership
Sensitive user, provider, plugin, server, distribution, job, runtime-binding, audit, and channel metadata APIs SHALL enforce platform-admin, server-owner/administrator, or Run-service identity at the API and service boundaries.
#### Scenario: Cross-owner access
- **WHEN** an authenticated non-owner requests another owner's server binding, job, config, artifact, or action
- **THEN** the platform returns 403 and leaves the resource unchanged
#### Scenario: UI bypass
- **WHEN** a caller invokes a sensitive route directly without the required role or session
- **THEN** the platform rejects the call regardless of UI state or request shape
### Requirement: Core auth and secret metadata are durable and redacted
FileStore and MySQLStore SHALL persist auth sessions, Run session state, encrypted component-key metadata, distributions, and controlled secret references through the repository snapshot contract; raw tokens, keys, credentials, paths, sockets, and provider secret values MUST NOT appear in snapshots, logs, DTOs, or browser responses.
#### Scenario: Snapshot reload preserves safe metadata
- **WHEN** the store is reopened after creating a session, Run identity, component key, or secret reference
- **THEN** safe status/generation/presence metadata remains available and raw values remain absent
#### Scenario: Secret presence projection
- **WHEN** a secret reference is configured
- **THEN** API and web responses expose only presence/configured/secret flags and safe fingerprints, never the reference value or storage location
### Requirement: Web clients handle auth failures safely
The platform web client SHALL treat 401 as a session reset/re-login condition and 403 as a capability/ownership denial, without persisting or rendering raw tokens, credentials, paths, sockets, or secret references.
#### Scenario: API session expires in the console
- **WHEN** an API call returns 401
- **THEN** the client clears the bearer token and exposes a safe re-authentication state
#### Scenario: API authorization is denied
- **WHEN** an API call returns 403
- **THEN** the client reports a safe access-denied error without including secret or infrastructure details
## Deferred Requirements
- Production KMS/HSM/vault encryption and secret-value rotation are deferred.
- Durable scheduling, process supervision, logs/artifacts backends, dependency installation, self-update, client-manager lifecycle, and production scaling are outside this change.
@@ -0,0 +1,22 @@
## 1. Planning and contracts
- [x] 1.1 Add typed durable auth-session and Run-session contracts, repository interfaces, validation rules, and safe DTO projections.
- [x] 1.2 Document the auth/authorization matrix, signed envelope, replay/clock constraints, secret-ref boundary, and deferred production risks.
## 2. Durable authentication and Run trust
- [x] 2.1 Persist hashed user sessions with expiry, revocation, rotation, and reload support in MemoryStore/FileStore/MySQLStore.
- [x] 2.2 Persist Run session state and enforce bounded lifecycle plus signed timestamp/nonce validation at HTTP channel boundaries.
- [x] 2.3 Add login/session-rotation/revocation routes and safe 401/403 error behavior.
## 3. Authorization and secret boundary
- [x] 3.1 Enforce platform-admin, owner/administrator, and Run-service authorization on sensitive routes and repeat checks in services.
- [x] 3.2 Persist component-key/distribution/secret metadata through all durable snapshots without raw secret disclosure.
- [x] 3.3 Add API and service regressions for cross-owner access, expired/revoked credentials, replay, and non-disclosure.
## 4. Web and verification
- [x] 4.1 Update platform_web API/session handling for 401/403 and capability-safe projections with no secret literals.
- [x] 4.2 Add frontend regressions for session reset/denied access and secret/path/socket non-disclosure.
- [x] 4.3 Run platform, plugin, web, OpenSpec strict validation, structure checks, and risk-relevant independent Run tests; record evidence and leave later roadmap work explicitly deferred.
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-07-17
@@ -0,0 +1,84 @@
## Context
Task 05 made lifecycle/config/file jobs real and Task 06 made logs, artifacts, metrics, backups, and remote adapters durable. Distribution APIs already create `dependencies.check`, `dependencies.install`, and `run.self-update` jobs, but Run currently returns immediate synthetic success. Platform also accepts any available same-owner-visible artifact for an update and has no Run-only artifact read route, immutable dependency-plan approval, terminal dependency projection, or update activation journal.
The implementation spans the main repository and the independent `run/` repository. Platform remains authoritative for user ownership/admin scope, installed plugin/version, server/runtime binding, selected endpoint, Run session/signature, attempt/lease/cancel fencing, artifact ownership, immutable plan digest, and audit. Run owns machine-local resolution, typed adapter execution, staging, replacement, health confirmation, and rollback. Plugins declare safe logical plans; platform_web receives only safe projections.
## Goals / Non-Goals
**Goals:**
- Execute declared dependency probes and install plans through fixed, testable adapters with bounded time, output, retries, and cancellation.
- Make the exact platform/architecture-specific plan reviewable and bind installation approval to its digest.
- Persist dependency status and update phases across Platform and Run restarts.
- Download only an approved same-server Run distribution through a signed, active-attempt-only, resumable contract.
- Verify archive and binary bounds/checksums, preserve the current package configuration, stage durably, activate only after the terminal result is accepted, confirm startup health, and roll back on failure.
- Keep heartbeat, job ack/result/cancel polling, logs, and artifact upload independent from slow dependency/update work.
**Non-Goals:**
- Arbitrary shell/script execution, user-supplied command vectors, generic root package management, unverified HTTP downloads, or undeclared host/credential access.
- Production code-signing/KMS, release rings, fleet rollout, centralized binary mirrors, dependency solving, Run service-manager installation, client-manager lifecycle, plugin lifecycle, production scaling/alerts, or real AI-provider integration.
## Decisions
### Decision 1: Platform snapshots declared inputs and approves an immutable digest
Platform derives a safe dependency catalog from the installed plugin version, selected runtime profile, endpoint OS/architecture, and declared probes/plans. A canonical digest covers the declaration and non-secret logical binding generation. An install request must include the digest returned by the catalog. Dispatch re-resolves the declaration and rejects changed plugin versions, plan steps, target platform, bindings, endpoint, or digest before creating the job.
Alternative considered: accept only a plan key and resolve it at execution time. Rejected because an operator could approve one plan and execute a later plugin revision.
### Decision 2: Private execution inputs use active fenced Run routes
Dependency declarations/resolved target values and Run update manifests are not placed in browser-visible job DTOs. Run retrieves them through signed `/api/v1/run/jobs/dependency-input`, `/api/v1/run/jobs/update-input`, and `/api/v1/run/jobs/update-chunk` routes carrying endpoint, session, job, attempt, and lease. Platform uses the existing fenced-job check and requires an active matching capability. Update chunks are bounded and range-addressed; no browser download token or raw storage path is returned.
Alternative considered: embed all data in `Job.ExecutionInput.Content`. Rejected because it weakens type separation and increases the chance of private binding or package data entering general job projections.
### Decision 3: Dependency work uses a closed adapter registry
Run maps probe kinds and install step types to fixed implementations. Package steps map a whitelisted manager to fixed argument builders and validate package/version tokens. Verified downloads require HTTPS, a declared SHA-256 checksum, a size limit, and a scoped destination. SteamCMD uses a fixed executable/argument shape. Manual steps return a safe blocked result and never claim installation. No adapter invokes a shell, evaluates manifest text, accepts environment overrides, or returns command output/paths.
The executor has injected command/download/filesystem interfaces for deterministic tests; production implementations use `exec.CommandContext`, bounded HTTPS, owner-only workspaces, and atomic files.
Alternative considered: translate declarations into shell scripts. Rejected because shell parsing defeats the declared capability boundary.
### Decision 4: Dependency state is projected from terminal evidence
Run returns a typed execution result containing only declaration key, present/missing/installed/failed classification, bounded version evidence, completed step count, and plan digest. Platform verifies that evidence against the job snapshot before updating `DependencyStatus`. Attempts and local journals are idempotent; retry/cancel/stale results remain governed by the existing scheduler. Audit summaries never include resolved paths, commands, package-manager output, or credentials.
### Decision 5: Self-update is a durable two-process transaction
Run streams the approved distribution archive into an owner-only transaction directory, persists offset/hash metadata, verifies the final artifact checksum, safely extracts exactly one expected Run binary, and records a staged manifest. Archive traversal, links, devices, duplicate executables, excess entries, oversized bodies, target mismatch, and bundled configuration replacement are rejected.
After Platform accepts the successful staged job result and the Run journal has persisted the acknowledgement, the worker launches the staged binary in helper mode and exits. The helper waits for the old PID, backs up the current executable, copies the staged binary through an atomic temporary target, starts the new executable with helper-only environment removed, and waits for a startup-health marker. The new worker writes that marker and sends a signed update-health report only after registration and job reconciliation succeed; Platform keeps the safe phase at `restart-requested/activating` until that report matches the terminal update job, endpoint, attempt, lease proof, target release, and current session. Failure restores the backup and restarts the previous binary. The package's existing `config.json` remains untouched.
Alternative considered: replace the executable before reporting the job. Rejected because Platform could retain a running lease with no terminal result. Alternative considered: report success after merely staging. Rejected because the update record would misrepresent activation; the safe projection distinguishes `staged/restart-requested`, `activating`, `succeeded`, `rolled-back`, and `failed` phases.
### Decision 6: Recovery is driven by journals, not process memory
Dependency executions store completed step indexes and immutable digests under the scoped workspace. Update transactions store artifact offset, expected checksum, staged binary checksum, current/backup logical locations, phase, attempt, and timestamps. Startup recovery removes invalid partial data, resumes eligible downloads, confirms a healthy activated transaction, or rolls back an interrupted activation. Attempt/lease values are used for fencing but never exposed in safe status or logs.
### Decision 7: Endpoint target identity and channel priority remain explicit
Run endpoint records persist OS/architecture from hello so Platform can reject cross-target distributions and plans. Downloading dependencies or update chunks occurs inside the claimed job goroutine; heartbeat and durable log/artifact upload loops remain separate. Progress/cancel polling uses bounded contexts. Tests block download/adapters while asserting heartbeat, ack/result, and log upload deadlines.
## Risks / Trade-offs
- [Package managers vary across distributions and may require privilege] → Validate the endpoint OS, use manager-specific fixed arguments, surface a safe permission failure, and never auto-escalate through sudo/shell.
- [A process can crash between staged result and helper activation] → Persist the post-ack activation request and recover it at startup; Platform distinguishes staging from confirmed version/health.
- [Windows executable replacement differs from Unix rename behavior] → Helper copies from the staged executable after the parent exits and uses backup/temporary targets instead of renaming a running binary.
- [A newly started binary can launch but fail registration] → New Run writes health only after successful registration/reconciliation; helper times out and restores the previous executable.
- [Old records lack endpoint OS/architecture or plan digests] → Existing endpoints re-register before real actions become available; legacy queued placeholder jobs are not retroactively executed.
- [Large update archives can consume disk/network] → Enforce artifact/archive/binary limits, bounded chunks, resumable offsets, owner-only roots, and cleanup after terminal retention.
## Migration Plan
1. Add backward-compatible endpoint target fields, dependency/update records, DTOs, protocols, repositories, and private routes.
2. Require endpoint re-registration to advertise a supported OS/architecture before enabling dependency install or self-update.
3. Publish safe dependency catalog/status and update phase projections; existing generic job views remain compatible.
4. Enable real Run capabilities only when the typed executors and journals initialize successfully.
5. On rollback, stop advertising the real capabilities and leave private journals/artifacts for a compatible binary to recover; do not delete or reinterpret prior Platform records.
## Open Questions
- Production signing policy and rollout rings remain a future change; this task enforces artifact ownership, target match, content checksum, and optional signature metadata without claiming a production PKI.
@@ -0,0 +1,32 @@
## Why
Platform can currently queue dependency and Run update jobs, but Run returns synthetic success without executing a declared install step or downloading, verifying, staging, activating, and recovering an update. Operators therefore see completion for work that did not happen, and the existing job/artifact security boundaries are not yet sufficient for real machine mutation.
## What Changes
- Resolve plugin-declared dependency probes and install plans through Platform ownership, installed-plugin, runtime-profile, binding, endpoint, platform, session, attempt, and lease checks.
- Expose a safe, reviewable dependency catalog and require approval of the exact immutable plan digest before dispatching an install.
- Execute only typed package, verified-download, and SteamCMD steps through fixed adapters; reject arbitrary shell, scripts, unsafe package arguments, unapproved downloads, undeclared targets, stale plans, and unsupported operating systems.
- Persist dependency execution status/evidence and project terminal job results into bounded, redacted Platform records and UI status.
- Add a Run-only, fenced, resumable artifact download contract for approved same-server Run distributions.
- Download, checksum-verify, safely extract, stage, and durably journal Run updates; activate them through a post-result helper, verify startup health, and roll back on activation failure.
- Persist Run update phases and audit outcomes without exposing host paths, Run/session/lease tokens, secret refs, credentials, PIDs, sockets, or private plan bindings.
- Preserve control/job/log/artifact channel isolation so dependency downloads and update transfer/activation do not delay heartbeat, job acknowledgement/result, cancellation polling, or log upload.
## Capabilities
### New Capabilities
- `durable-dependency-execution`: reviewable declared dependency plans, fenced Run input, typed probes/install adapters, persistence, recovery, cancellation, and safe projections.
- `transactional-run-self-update`: approved artifact download, resumable verification, durable staging, post-result activation, startup health confirmation, rollback, and audit semantics.
### Modified Capabilities
- `run-distribution-and-client-managers`: dependency and self-update jobs now perform real bounded machine work instead of success-only hooks.
- `artifact-transfer-channel`: authenticated Run jobs can read approved distribution artifacts in bounded resumable chunks without using browser download sessions.
## Impact
- Affects `plugins/` dependency declaration validation/SDK examples, `platform/` domain/DTO/model/repository/service/protocol/validator/API layers, projection-only `platform_web/` dependency/update status, and the independent `run/` protocol/runtime/config/shared layers.
- Adds no arbitrary shell capability and no raw host path, credential, socket, token, lease, session hash, or secret projection to plugins or platform_web.
- Does not implement client-manager lifecycle, dependency installation outside declared adapters, Run distribution signing infrastructure/KMS, production rollout rings/fleet orchestration, plugin lifecycle, production scaling/alerts, or real AI-provider integration.
@@ -0,0 +1,16 @@
## ADDED Requirements
### Requirement: Active Run update jobs can read approved artifact ranges
The artifact channel SHALL provide a signed, bounded, resumable read contract exclusively for an active fenced `run.self-update` attempt whose artifact is an available same-server target-matched Run distribution.
#### Scenario: Run reads the next update range
- **WHEN** Run presents the selected endpoint/session/job/attempt/lease and a valid offset and length
- **THEN** Platform MUST return only that bounded artifact range plus artifact ID, offset, total size, checksum, and completion metadata
#### Scenario: Run requests unrelated artifact data
- **WHEN** the job is inactive, the artifact/distribution/server/endpoint/target differs, or the range exceeds bounds
- **THEN** Platform MUST reject the request without returning bytes, paths, credentials, browser download sessions, secret refs, or cross-owner metadata
#### Scenario: Update transfer is slow
- **WHEN** an update range read or network response is blocked
- **THEN** control, job ack/result/cancel, log ingest, and independent artifact upload routes MUST continue without waiting on the read
@@ -0,0 +1,68 @@
## ADDED Requirements
### Requirement: Dependency plans are declared and reviewable
Platform SHALL derive a safe dependency catalog from the installed plugin version, selected runtime profile, complete binding, and Run target, and SHALL require approval of the exact plan digest before installation.
#### Scenario: Operator reviews an install plan
- **WHEN** an authorized owner or platform administrator queries dependency actions for a server
- **THEN** Platform MUST return declared probe keys, plan titles, target OS/architecture, typed step summaries, current safe status, and a deterministic plan digest without host paths, commands, credentials, secret refs, sockets, tokens, leases, sessions, hashes used for fencing, or PIDs
#### Scenario: Approved plan changes before dispatch
- **WHEN** the plugin version, runtime profile, target, binding generation, plan steps, or digest no longer matches the reviewed plan
- **THEN** Platform MUST reject installation and record a safe denied audit event before creating a job
#### Scenario: Caller crosses server ownership
- **WHEN** a non-owner without server-admin or platform-admin scope requests a catalog, check, or install
- **THEN** Platform MUST return the existing unauthorized/forbidden semantics and MUST NOT reveal whether private bindings or plans exist
### Requirement: Dependency execution input is fenced and private
Run SHALL receive dependency declarations and resolved target values only through a signed Platform route scoped to the active endpoint, session, job, attempt, and lease.
#### Scenario: Active Run loads dependency input
- **WHEN** the selected Run requests input for its active dependency attempt
- **THEN** Platform MUST verify endpoint ownership, session/signature, job capability/state, attempt/lease, server/plugin/profile/target, immutable digest, and cancellation state before returning the bounded typed input
#### Scenario: Stale or cross-endpoint Run requests input
- **WHEN** the endpoint, session, attempt, lease, server, plugin version, profile, or capability does not match the active job
- **THEN** Platform MUST reject the request without returning declarations, bindings, host targets, or plan data
### Requirement: Dependency probes and installs use closed typed adapters
Run SHALL execute only supported declared probe kinds and install step types through fixed adapters and SHALL never evaluate arbitrary shell, script text, environment overrides, or caller-supplied command vectors.
#### Scenario: Declared probe executes
- **WHEN** a supported command-version, Java, Docker, package, service, Steam app, or file probe is requested for the current Run platform
- **THEN** Run MUST resolve only the approved target, enforce timeout/output bounds, and return a safe present/missing/version classification
#### Scenario: Typed package plan executes
- **WHEN** an approved package step names a supported manager, safe package token, optional safe version, and matching platform
- **THEN** Run MUST use the fixed manager adapter, respect cancellation and timeout, persist step completion idempotently, and never invoke a shell or unapproved privilege escalation
#### Scenario: Verified download executes
- **WHEN** an approved verified-download step uses HTTPS, an allowed host, a SHA-256 checksum, a bounded size, and a scoped logical destination
- **THEN** Run MUST stream to an owner-only temporary file, verify checksum before atomic publication, and remove invalid partial data
#### Scenario: Unsafe or unsupported step is requested
- **WHEN** a declaration contains shell syntax, an unsafe package/version token, HTTP or credential-bearing URL, missing checksum, undeclared target, unsupported platform/manager/type, symlink escape, or manual-only step
- **THEN** validation or Run MUST reject it without machine mutation and return a bounded safe failure
### Requirement: Dependency execution is durable and auditable
Platform and Run SHALL make dependency execution restart-safe, idempotent, cancellable, retry-bounded, and auditable.
#### Scenario: Run restarts during a multi-step install
- **WHEN** Run recovers an active attempt with a matching immutable digest
- **THEN** it MUST resume after the last durably completed idempotent step and MUST NOT repeat a completed step or accept a stale attempt
#### Scenario: Cancellation arrives during a blocked adapter
- **WHEN** Platform records cancellation for the active dependency job
- **THEN** Run MUST cancel the adapter context, stop before the next step, preserve recoverable evidence, and report a fenced cancelled result
#### Scenario: Terminal dependency evidence is accepted
- **WHEN** Platform accepts a current terminal probe or install result
- **THEN** it MUST update the durable dependency status and audit actor, server, plugin, probe/plan, attempt outcome, and safe summary without private execution details
### Requirement: Dependency work preserves channel deadlines
Slow package managers and downloads SHALL NOT block Run control heartbeat, job acknowledgement/result, cancellation polling, log upload, or artifact channel progress.
#### Scenario: Dependency adapter is blocked
- **WHEN** a dependency command or download remains blocked beyond a heartbeat interval
- **THEN** heartbeat, log acknowledgement, cancellation polling, and unrelated job-channel requests MUST continue through independent bounded operations
@@ -0,0 +1,31 @@
## MODIFIED Requirements
### Requirement: Dependency checks and installs are typed
Run SHALL check dependencies through plugin-declared probes and SHALL install missing dependencies only through approved, typed, reviewable, immutable plans executed by fixed adapters.
#### Scenario: Dependency check reports missing runtime
- **WHEN** Run evaluates a declared probe for a required runtime, service, package, toolchain, Steam app, Java runtime, Docker runtime, or file and finds it missing
- **THEN** Platform MUST persist and show the safe dependency status and a reviewable platform-matched install plan when the installed plugin declares one
#### Scenario: Dependency install is approved
- **WHEN** an authorized operator approves the current immutable plan digest
- **THEN** Platform MUST queue a fenced job and Run MUST execute only the typed package, verified-download, or SteamCMD steps, persist resumable evidence, and reject arbitrary shell or stale plan input
#### Scenario: Dependency result is synthetic
- **WHEN** Run has not executed and verified the declared probe or install steps
- **THEN** it MUST NOT report the dependency present, installed, or successfully completed
### Requirement: Online run endpoints self-update through platform jobs
The platform SHALL update online Run endpoints through a bounded job that reads an approved same-server target-matched distribution, and Run SHALL durably download, verify, stage, activate, health-check, and roll back the update without receiving raw shell commands.
#### Scenario: Online Run accepts update
- **WHEN** the assigned endpoint is online, advertises real self-update capability, and the artifact matches its server and OS/architecture
- **THEN** Platform MUST queue a fenced update job and Run MUST download by bounded ranges, verify checksum, stage safely, report the result, and activate only after Platform accepts that result
#### Scenario: Update verification fails
- **WHEN** Run cannot verify or stage the artifact
- **THEN** Run MUST keep the current executable and configuration, report a bounded failure, preserve heartbeat/status, and never launch the update helper
#### Scenario: Updated Run fails health confirmation
- **WHEN** the replacement cannot start or authenticate/reconcile with the same identity before timeout
- **THEN** Run MUST restore and restart the previous executable and Platform MUST project a rolled-back/failed outcome rather than success
@@ -0,0 +1,74 @@
## ADDED Requirements
### Requirement: Run updates use approved target-matched distributions
Platform SHALL dispatch self-update only for an available Run distribution owned by the same server, built for the registered endpoint OS/architecture, and matching the recorded artifact checksum.
#### Scenario: Authorized update is queued
- **WHEN** an authorized owner or platform administrator selects an available same-server distribution for the online endpoint
- **THEN** Platform MUST bind the update record and job to the distribution, artifact, checksum, target, endpoint, and idempotency key and record a queued audit event
#### Scenario: Artifact is cross-owner or cross-target
- **WHEN** the artifact belongs to another server/job, is not an available Run distribution, has a different checksum, or targets another OS/architecture
- **THEN** Platform MUST reject the update before job creation without revealing artifact contents or private ownership metadata
### Requirement: Update artifact reads are resumable and fenced
Run SHALL download update artifacts through a signed active-attempt-only chunk contract with bounded offsets, lengths, total size, and checksum metadata.
#### Scenario: Download resumes after interruption
- **WHEN** Run restarts or a chunk request fails after a durable offset was recorded
- **THEN** Run MUST request the next bounded range, verify every returned offset/length and the final checksum, and MUST NOT redownload already verified bytes
#### Scenario: Stale attempt requests a chunk
- **WHEN** a cancelled, expired, wrong-endpoint, wrong-session, wrong-lease, or superseded attempt requests update metadata or bytes
- **THEN** Platform MUST reject it and MUST NOT return artifact bytes, storage paths, browser tokens, secret refs, or fencing hashes
### Requirement: Run stages updates safely
Run SHALL safely validate and stage exactly the expected Run executable from the approved distribution while preserving the installed package configuration.
#### Scenario: Valid package is staged
- **WHEN** all artifact bytes and the archive checksum are verified
- **THEN** Run MUST reject archive traversal/links/devices/duplicates, enforce entry and binary size limits, extract the target-matched executable into an owner-only transaction directory, verify its checksum, fsync the journal, and leave the current executable/configuration unchanged
#### Scenario: Package verification fails
- **WHEN** checksum, target, format, entry bounds, executable identity, or extraction validation fails
- **THEN** Run MUST keep the current executable, remove or quarantine invalid partial data, report a bounded failure, and remain able to heartbeat and accept cancellation
### Requirement: Activation occurs only after fenced result acceptance
Run SHALL activate a staged update only after Platform accepts the terminal staged result for the current attempt and the local result acknowledgement is durable.
#### Scenario: Staging result is rejected
- **WHEN** Platform rejects the result because the session, attempt, lease, cancellation state, or terminal fingerprint is stale
- **THEN** Run MUST NOT launch the update helper or replace the executable
#### Scenario: Staging result is accepted
- **WHEN** Platform accepts the current staged result
- **THEN** Run MUST persist the post-ack activation request, launch the staged helper, stop the old worker without dropping the accepted result, and project the update as restart-requested/activating until health is confirmed
### Requirement: Activation is health-checked and rollback-safe
The update helper SHALL back up, replace, launch, confirm, and finalize an update transaction, and SHALL restore the previous executable if activation fails.
#### Scenario: New Run becomes healthy
- **WHEN** the new executable starts, authenticates, registers the same endpoint/server identity, reconciles jobs, and writes the transaction health marker before timeout
- **THEN** it MUST submit a signed current-session health report fenced to the terminal update job/attempt/lease, the helper MUST mark the transaction succeeded, retain bounded rollback evidence, and Platform MUST confirm the endpoint's new release/checksum in the safe update projection only after accepting that report
#### Scenario: Replacement or health confirmation fails
- **WHEN** copy/rename/start fails, the new process exits, identity differs, or health is not confirmed before timeout
- **THEN** the helper MUST atomically restore the backup where possible, restart the previous executable, mark rolled-back/failed recovery state, and never claim update success
#### Scenario: Run restarts with an interrupted transaction
- **WHEN** startup finds a durable downloading, staged, activating, or rollback transaction
- **THEN** it MUST resume the safe phase, clean invalid state, or roll back deterministically without applying a different artifact or stale attempt
### Requirement: Update status and audit projections are safe
Platform_web and plugins SHALL receive only bounded update identity, artifact checksum, target, phase, progress, timestamps, rollback outcome, endpoint version/release, and safe audit summaries.
#### Scenario: Update status is queried
- **WHEN** an authorized user opens server runtime status
- **THEN** the response MUST omit host/executable/staging/backup paths, raw artifact bodies, credentials, Run tokens, session/lease values or hashes, secret refs, helper PIDs, sockets, and private package configuration
### Requirement: Update transfer and activation preserve channel deadlines
Slow update downloads and helper preparation SHALL NOT block control heartbeat, job acknowledgement/result, cancellation polling, logs, or unrelated artifact uploads.
#### Scenario: Update download is slow
- **WHEN** update chunk transfer is delayed or the artifact is large
- **THEN** heartbeat, active job lease renewal, cancellation polling, log upload, and unrelated result reporting MUST continue through separate bounded loops
@@ -0,0 +1,45 @@
## 1. Contracts And Persistence
- [x] 1.1 Add Platform domain, DTO, model, repository, protocol, validator, and safe projection contracts for dependency catalogs/snapshots/results and Run update manifests/chunks/phases.
- [x] 1.2 Persist Run endpoint OS/architecture, dependency execution evidence, plan digests, and update transaction/rollback status through memory, file, and MySQL snapshot repositories.
- [x] 1.3 Add independent Run protocol/runtime/config/shared types for private dependency input, resumable update reads, typed evidence, and durable update journals without importing main-repository source.
## 2. Platform Authorization And Orchestration
- [x] 2.1 Implement authorized dependency catalog/status queries with deterministic safe plan digests and exact installed-plugin/profile/target/binding projections.
- [x] 2.2 Require current plan-digest approval for installs and dispatch only declared platform-matched probes/plans to the selected online endpoint.
- [x] 2.3 Implement signed, session/attempt/lease/cancel-fenced Run dependency-input, update-input, and bounded update-chunk routes with same-server distribution and target checks.
- [x] 2.4 Project accepted terminal dependency/update evidence into durable status/phase/audit records while rejecting stale, cross-owner, cross-endpoint, cross-target, or conflicting results.
## 3. Real Dependency Execution
- [x] 3.1 Implement Run probe adapters for supported declared command/version, Java/Docker/package/service/Steam/file checks with bounded redacted evidence.
- [x] 3.2 Implement fixed package-manager, verified HTTPS download, and SteamCMD install adapters with no shell, safe tokens/hosts/checksums/paths, timeouts, and cancellation.
- [x] 3.3 Add durable dependency journals, step idempotency, retry/restart recovery, digest fencing, safe failures for manual/unsupported steps, and tests.
## 4. Transactional Run Self-Update
- [x] 4.1 Implement bounded resumable update download with durable offsets, per-range/final checksum verification, cancellation, and restart recovery.
- [x] 4.2 Implement safe zip/tar extraction of the expected target binary, archive/binary limits, owner-only staging, configuration preservation, and durable transaction manifests.
- [x] 4.3 Implement post-result-ack helper activation, parent exit coordination, backup/atomic replacement, helper-environment cleanup, startup identity/health confirmation, rollback, and interrupted-transaction recovery.
- [x] 4.4 Add Run self-update tests for wrong artifact/target/checksum, partial resume, stale fencing, result rejection, activation success, health timeout rollback, and journal restart.
## 5. Plugins And Platform Web
- [x] 5.1 Tighten plugin manifest/SDK dependency declarations and example plans for fixed adapters, approved download hosts/checksums, step bounds, and unsafe shell/URL/token rejection.
- [x] 5.2 Add platform_web safe dependency catalog/status and Run update phase/checksum/rollback/audit views using existing black-mecha/magical-girl components and existing 401/403 behavior.
## 6. Regression And Verification
- [x] 6.1 Add Platform/Run regression coverage for owner/endpoint/signature/session/attempt/lease/target rejection, persistence/recovery/idempotency/cancel, redaction, and blocked transfer/adapter channel isolation.
- [x] 6.2 Update protocol/API/domain documentation with implemented limits and explicitly excluded client-manager lifecycle, production signing/fleet rollout/scaling/alerts/plugin lifecycle, and real AI-provider integration.
- [x] 6.3 Run plugin manifest/SDK tests, Platform tests, platform_web tests/typecheck/build, independent Run tests, strict OpenSpec validation, structure, shell/compose checks, and both repository diff checks; record only passing evidence.
## Verification Evidence
- `plugins`: `npm run validate:manifest`, `npm run typecheck`, and `npm test` passed (18 tests).
- `platform`: `go test ./...` passed across api/config/domain/dto/model/repo/service/validator.
- `platform_web`: `npm test` passed (104 tests), `npm run typecheck`, and `npm run build` passed.
- independent `run`: `go test ./...` passed across api/config/protocol/runtime/spool.
- `openspec validate implement-dependency-installation-and-run-self-update --strict` passed.
- `scripts/check-structure.sh`, `bash -n scripts/*.sh`, `docker compose config`, `git diff --check`, and `git -C run diff --check` passed.
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-07-17
@@ -0,0 +1,78 @@
## Context
`domain.Job` is already the durable unit stored by MemoryStore, FileStore, and the repository-backed MySQLStore, but it currently contains only the user-visible lifecycle projection. `CoreService` separately owns an in-memory `jobLeases` map containing raw Run session tokens, lease tokens, attempts, cancel intent, and terminal fingerprints. Restarting the platform therefore makes accepted/running jobs impossible to authenticate or complete safely. The independent Run repository similarly uses an in-memory active assignment map and does not reconcile during startup.
The existing HTTP surface already separates control, jobs, logs, and artifacts and signs non-hello Run requests with the active Run session. This change must deepen those boundaries without introducing an all-in-one transport, exposing credentials to the browser/plugins, or implementing later execution/storage roadmap items.
## Goals / Non-Goals
**Goals:**
- Make every scheduling decision recoverable from the Job repository after platform process restart or FileStore/MySQLStore reload.
- Fence every ack, progress, result, cancel poll, and reconcile report by endpoint, active authenticated session, per-job attempt, and a lease credential whose persisted form is a hash.
- Define bounded ack and execution leases, monotonic per-job attempts and progress sequences, exponential retry backoff, terminal idempotency, and durable cancel intent/result.
- Make Run persist active assignments atomically under its workspace, reconcile immediately after registration, and recover or discard work according to the platform response.
- Keep safe browser projections and owner/platform-admin authorization aligned with existing server ownership checks.
- Preserve independent control/job/log/artifact paths and prove large transfer requests cannot block control or job requests.
**Non-Goals:**
- Production-grade distributed scheduling or multi-platform-replica compare-and-swap coordination.
- Process supervision, real config/file mutation, durable logs/artifacts/metrics/backups, dependency installation, Run self-update, client-manager lifecycle, or production scaling.
- Persisting raw Run session tokens, lease tokens, host paths, sockets, AI keys, component keys, or other credentials.
## Decisions
### Persist scheduling metadata on the Job aggregate
`domain.Job` and `model.Job` gain a nested retry policy plus queue, attempt, hashed lease, ack/lease deadline, last progress sequence, cancel, terminal, and reconciliation fields. The existing Job repository remains the only storage boundary, so MemoryStore, FileStore, and MySQLStore inherit the behavior through their existing typed snapshot/repository implementations.
This is preferred over retaining a service cache or introducing a second lease repository because transitions need one recoverable aggregate and the current stores do not provide cross-repository transactions. Raw lease tokens are generated with cryptographic randomness, returned only over the signed Run job channel, and stored as SHA-256 hashes.
### Use a deterministic per-job state machine
New jobs default to `queued`, attempt zero, a bounded retry policy, and an immediately eligible queue timestamp. Claim sweeps expired work for the endpoint, selects eligible `queued` or `retrying` work in stable creation order, increments that job's attempt, stores the lease hash and current Run session generation, and moves it to `accepted` with an ack deadline and execution lease deadline.
Ack before the deadline moves the attempt to `running` and renews its lease. Monotonic progress renews the running lease. Ack timeout, execution lease expiry, or an explicitly retryable failed result clears the lease and moves the job to `retrying` with exponential backoff when attempts remain; otherwise it records a terminal `failed` result. A pending cancel intent always resolves to `cancelled` rather than retrying when the lease expires. Old attempts and old lease tokens are rejected even after a newer attempt exists.
### Model cancellation as durable intent followed by durable result
Cancel before claim atomically records intent and a terminal cancelled result. Cancel after claim records idempotent intent for the assigned Run endpoint; polling is fenced by the current attempt and lease, and Run returns a normal terminal cancelled result. Repeated identical or compatible cancel requests return the existing intent/result projection. Cancellation never consumes another retry attempt.
### Reconcile with full active-attempt evidence
Run reports persisted active assignment evidence: job ID, attempt, and raw lease token over the signed job channel. Platform verifies the endpoint, stored attempt, lease hash, nonterminal state, and active Run session, then rebinds the lease to the current session generation and renews its deadline. The raw token is echoed only in the signed response and is never persisted.
Reported stale/unknown jobs are returned as discard IDs. Platform-active jobs absent from the Run journal are treated as abandoned and enter cancel resolution or retry policy. Reconciliation timestamps, counts, and outcome are persisted on every affected Job. This is preferred over recreating leases during reconciliation because recreating them would let stale attempts regain authority.
### Persist Run journal atomically and reconcile before claiming
Run stores a versioned JSON journal under `WorkspaceRoot`, using a temporary file plus rename and owner-only permissions. It writes the assignment before ack, updates it after ack/progress, and removes it only after an accepted terminal response or explicit platform discard. Worker startup registers, reconciles the loaded journal before any new claim, and re-executes platform-confirmed assignments under the same attempt. Corrupt journal data fails worker construction instead of silently forgetting active work.
### Keep API and UI projections credential-free
Platform user APIs expose attempt, max attempts, next retry time, ack/lease deadlines, cancel state/timestamps, terminal time, and last reconcile outcome. They omit lease hashes, raw lease tokens, session generation, secret refs, and host/runtime credentials. Existing `GetJobForSession`, `ListJobsForSession`, and cancel authorization continue to derive access from server owner/admin or platform-admin rules. Run-only handlers continue to require both the active session and request signature middleware.
### Preserve channel independence
Control, jobs, logs, and artifacts remain separate HTTP routes and clients. Run's worker loop executes heartbeat and scheduling work independently from spool/artifact transfer queues; neither the job protocol nor safe browser projection carries log bodies or artifact payloads. Isolation tests block log/artifact handlers while asserting heartbeat, ack, result, cancel, and reconcile complete.
## Risks / Trade-offs
- [FileStore and the current MySQLStore are process-local snapshot implementations, not distributed CAS schedulers] -> Serialize transitions with the existing service mutex and explicitly keep multi-replica production scheduling out of scope.
- [At-least-once recovery can repeat an interrupted operation] -> Preserve idempotency keys, fence attempts, and require bounded Run executors to be idempotent; later process supervision will refine resumability.
- [Legacy persisted jobs lack new fields] -> Normalize zero-value scheduling fields when read/claimed so existing queued and terminal records remain valid without destructive migration.
- [Hash-only lease storage means platform cannot recreate a lost Run lease credential] -> Require the durable Run journal to present the original token; otherwise the platform retries with a new attempt after reconciliation/expiry.
- [Clock skew between Run and platform] -> Treat platform timestamps as authoritative; Run does not decide lease validity locally.
## Migration Plan
1. Deploy the expanded model/repository projection and zero-value normalization before relying on new states.
2. Deploy platform protocol and scheduler behavior with compatibility for an empty legacy reconcile evidence list.
3. Deploy Run protocol and persistent journal, which reconciles immediately after registration before claiming.
4. Deploy the safe web projection and tests.
5. Rollback may read the expanded JSON while ignoring unknown fields, but accepted/running jobs should be allowed to reconcile or expire before rolling back to code that lacks durable fencing.
## Open Questions
None for this single-process scheduling milestone. Cross-replica transactional claiming and resumable supervised processes remain explicit later design work.
@@ -0,0 +1,32 @@
## Why
The platform currently persists the visible Job record but keeps leases, attempts, cancellation intent, and terminal idempotency in `CoreService` memory, so a platform restart loses fencing and recovery state. Run also keeps its active-job journal only in memory, preventing reliable reconciliation after Run restart and leaving ack timeouts, retry backoff, and late-result rejection underspecified.
## What Changes
- Persist queue scheduling, attempt, lease hash and deadlines, retry/backoff, terminal fingerprint, cancellation intent/result, progress sequence, and reconciliation metadata through the existing MemoryStore, FileStore, and MySQLStore Job repository boundary.
- Define claim, ack, lease renewal, retry-wait, terminal, cancel, and reconciliation transitions with attempt fencing and deterministic late-message rejection.
- Rebind a valid persisted lease to a newly authenticated Run session generation only through endpoint-scoped reconciliation; never persist raw Run session or lease credentials.
- Persist Run's active assignment journal locally, reconcile it after registration and restart, continue valid attempts, and discard platform-rejected or unknown work.
- Keep control, jobs, logs, artifacts, and the optional game-client bridge as independent request paths and execution queues so log/artifact backpressure cannot block heartbeat or job acknowledgement/result traffic.
- Extend platform APIs and the management console only with safe scheduling projections such as state, attempt, retry timing, cancel status, and reconcile status. Raw tokens, secret references, host paths, sockets, and credentials remain excluded.
- Add cross-store, API, frontend, and independent Run regression coverage for reload recovery, deadlines, fencing, retry, cancellation, reconciliation, authorization/signature failure, and channel isolation.
- Explicitly leave process supervision, real config/file execution, durable log/artifact/metric/backup storage, dependency installation, self-update, client-manager lifecycle, and production scaling to later changes.
## Capabilities
### New Capabilities
- `durable-job-scheduling`: Defines durable platform scheduling and Run reconciliation semantics, retry and cancellation state, security fencing, safe projections, and channel isolation.
### Modified Capabilities
None.
## Impact
- `platform/`: Job domain/model/repository projections, scheduler service, Run job protocol DTOs and validators, owner/admin/Run-service authorization, API handlers, and persistence/reload tests.
- `run/` independent repository: job protocol contracts, persistent journal, worker startup reconciliation, cancellation/result behavior, and isolated HTTP channel tests.
- `platform_web/`: safe Job API types, schemas, task-status presentation, and 401/403 regression coverage without visual-system redesign.
- `plugins/`: existing SDK and manifests are verified to remain platform-mediated; no raw Run credential or host access is added.
- OpenSpec: adds a cross-repository behavioral contract and verification checklist while leaving earlier completed changes unarchived.
@@ -0,0 +1,117 @@
## ADDED Requirements
### Requirement: Durable scheduling state
The platform SHALL persist queue eligibility, retry policy, attempt, lease hash and deadlines, progress sequence, cancellation intent/result, terminal fingerprint, and reconciliation metadata through the Job repository used by MemoryStore, FileStore, and MySQLStore. It MUST NOT require a `CoreService` memory map to recover active scheduling state and MUST NOT persist raw Run session or lease tokens.
#### Scenario: Platform reload preserves active attempt
- **WHEN** a claimed or running job is reloaded into a new platform service instance
- **THEN** the stored endpoint, attempt, hashed lease, deadlines, cancel intent, and retry metadata remain authoritative and a correctly signed current-session request with the matching lease is accepted
#### Scenario: File and MySQL reload preserve queued work
- **WHEN** queued or retry-wait work is persisted and the store is reopened
- **THEN** the same job becomes claimable only at its persisted eligibility time with its prior attempt count intact
### Requirement: Lease and attempt fencing
The platform SHALL issue cryptographically random per-attempt lease tokens, persist only their hashes, and fence job messages by Run endpoint, authenticated active session generation, job ID, monotonic per-job attempt, and matching lease token. Accepted jobs SHALL have an acknowledgement deadline and running jobs SHALL have a renewable execution lease.
#### Scenario: Ack deadline expires
- **WHEN** Run does not acknowledge a claimed job before its acknowledgement deadline
- **THEN** the platform rejects the late acknowledgement and schedules the job for a later attempt or records terminal failure when retry budget is exhausted
#### Scenario: Execution lease expires
- **WHEN** a running attempt sends no accepted progress or reconciliation before its lease expires
- **THEN** the platform clears that lease and applies retry or terminal policy durably
#### Scenario: Old attempt arrives late
- **WHEN** an ack, progress update, result, cancel poll, or reconciliation entry references an older attempt or lease
- **THEN** the platform rejects it without changing the current attempt or terminal result
#### Scenario: Invalid endpoint or session
- **WHEN** otherwise valid attempt evidence is signed by another endpoint, an expired or rotated session, or an invalid signature
- **THEN** the platform returns an authentication or authorization failure and leaves the job unchanged
### Requirement: Retry and terminal policy
Each job SHALL have a bounded retry policy with a maximum attempt count and exponential backoff capped by a maximum delay. Ack timeout, lease expiry, and explicitly retryable failure SHALL enter durable `retrying` state when budget remains. Succeeded, non-retryable failed, cancelled, and exhausted jobs SHALL be terminal and terminal replay SHALL be idempotent only for the same attempt and result fingerprint.
#### Scenario: Retry waits for backoff
- **WHEN** an attempt fails retryably and attempts remain
- **THEN** the job records the next eligible time and cannot be claimed before that time
#### Scenario: Retry claim increments attempt
- **WHEN** backoff has elapsed and Run claims the job again
- **THEN** the platform increments the per-job attempt and issues a different lease token
#### Scenario: Retry budget is exhausted
- **WHEN** another retryable failure occurs on the maximum attempt
- **THEN** the job becomes terminal failed and is never returned by claim
#### Scenario: Terminal replay conflicts
- **WHEN** Run replays the same terminal result fingerprint for the current terminal attempt
- **THEN** the platform returns the existing accepted result, while a different fingerprint or attempt is rejected
### Requirement: Idempotent durable cancellation
The platform SHALL authorize cancellation through existing owner/server-admin/platform-admin resource checks, persist cancellation intent, and persist its terminal result. Cancellation before claim SHALL complete immediately; cancellation after claim SHALL be delivered only to the fenced active attempt and SHALL resolve idempotently.
#### Scenario: Cancel before claim
- **WHEN** an authorized user cancels queued or retry-wait work
- **THEN** the job records both cancel intent and terminal cancelled result without being claimed
#### Scenario: Cancel after claim
- **WHEN** an authorized user cancels accepted or running work
- **THEN** matching Run cancel polling observes the durable intent and a cancelled result records durable completion
#### Scenario: Cancel is repeated
- **WHEN** the same authorized cancellation is requested or polled more than once
- **THEN** the platform returns the existing intent/result without creating another attempt or conflicting terminal state
#### Scenario: Cross-owner cancellation is denied
- **WHEN** a non-admin user attempts to cancel a job for a server they do not own or administer
- **THEN** the platform returns forbidden and does not persist cancel intent
### Requirement: Run restart and platform reconciliation
Run SHALL persist active assignments atomically before acknowledgement and reconcile them immediately after every registration before claiming new work. Reconciliation SHALL report job ID, attempt, and lease evidence; platform SHALL confirm only matching active attempts, rebind them to the current authenticated session generation, persist reconciliation metadata, and direct Run to discard stale or unknown entries. Platform-active entries absent from Run's report SHALL enter cancellation resolution or retry policy.
#### Scenario: Run restart resumes confirmed attempt
- **WHEN** Run restarts with a valid persisted active assignment and registers a rotated session
- **THEN** reconciliation confirms and rebinds the same attempt before Run resumes it or claims other work
#### Scenario: Run reports stale journal entry
- **WHEN** Run reports a terminal, unknown, wrong-endpoint, wrong-attempt, or wrong-lease journal entry
- **THEN** the platform does not reactivate it and instructs Run to discard it
#### Scenario: Platform restart accepts reconciliation
- **WHEN** platform restarts while Run retains a valid active journal entry
- **THEN** the platform validates it against persisted job metadata without relying on prior process memory
#### Scenario: Platform active work is missing from Run
- **WHEN** authenticated reconciliation omits an accepted or running job assigned to that endpoint
- **THEN** the platform records reconciliation loss and applies cancel or retry policy rather than silently leaving unrecoverable active work
### Requirement: Credential-free user projection
The platform user API and platform_web SHALL expose only safe scheduling projections, including state, attempt counts, retry timing, cancel status, and reconcile outcome. They MUST NOT expose raw or hashed lease tokens, Run sessions, secret references, host paths, sockets, or credentials, and SHALL preserve existing 401/403 handling and crystal-moonlight console styling.
#### Scenario: Authorized user reads job scheduling status
- **WHEN** a server owner, server administrator, or platform administrator reads an accessible job
- **THEN** the response includes safe attempt, retry, cancellation, terminal, and reconcile fields without credential material
#### Scenario: Unauthorized user reads another owner's job
- **WHEN** a user without resource access requests another server's job
- **THEN** the API returns forbidden or not found according to the existing resource policy and platform_web follows existing 401/403 handling
### Requirement: Independent channel priority
Platform and Run SHALL keep control, jobs, logs, and artifacts on independent request paths and execution queues. Blocking or retrying log/artifact transfer MUST NOT block control heartbeat or job claim, ack, progress, result, cancel, or reconciliation traffic, and job/control messages MUST NOT carry log bodies or artifact payloads.
#### Scenario: Artifact transfer blocks
- **WHEN** an artifact chunk request remains blocked
- **THEN** heartbeat and job acknowledgement/result/cancel/reconcile requests still complete within their own deadlines
#### Scenario: Log ingest blocks
- **WHEN** a log batch upload remains blocked or retries
- **THEN** control heartbeat and job lifecycle traffic continue independently
### Requirement: Explicit roadmap boundary
This change SHALL NOT claim production readiness for process supervision, real config/file execution, durable log/artifact/metric/backup storage, dependency installation, Run self-update, client-manager lifecycle, or production multi-replica scaling.
#### Scenario: Completion is reported
- **WHEN** the durable scheduling change passes implementation and verification
- **THEN** its handoff identifies those capabilities as remaining later-route work
@@ -0,0 +1,43 @@
## 1. Durable Platform Model
- [x] 1.1 Add Job domain/model scheduling, retry, lease, cancellation, terminal, and reconciliation fields with legacy zero-value normalization and validation.
- [x] 1.2 Persist and reload the expanded Job aggregate through MemoryStore, FileStore, and MySQLStore repository paths without raw lease or session credentials.
## 2. Platform Scheduler State Machine
- [x] 2.1 Replace the CoreService in-memory job lease map with repository-backed claim, ack, lease renewal, progress sequencing, retry backoff, and terminal fencing transitions.
- [x] 2.2 Implement idempotent cancel-before-claim, cancel-after-claim polling/result, deadline expiry, late-message rejection, and exhausted retry behavior.
- [x] 2.3 Implement endpoint/session-generation reconciliation using attempt and lease evidence, including missing/stale work handling and persisted reconcile outcomes.
## 3. Contracts, Security, And API
- [x] 3.1 Extend platform Run job DTO/domain/validator/protocol contracts for deadlines, retryable result, full reconciliation evidence, and discard outcomes.
- [x] 3.2 Preserve signed Run-service endpoint/session checks and owner/server-admin/platform-admin authorization for job read and cancellation APIs.
- [x] 3.3 Expose only credential-free user Job scheduling projections and update API contract documentation.
## 4. Independent Run Recovery
- [x] 4.1 Extend the independent Run job protocol and client for deadlines, retry outcomes, reconciliation evidence, and discard instructions.
- [x] 4.2 Implement an atomic, owner-only persistent Run job journal and startup reconciliation before new claims.
- [x] 4.3 Make the worker recover confirmed attempts, handle cancellation idempotently, retain unaccepted results for later reconciliation, and discard platform-rejected entries.
- [x] 4.4 Prove control/job requests remain independent while log or artifact transfers block.
## 5. Safe Console Projection
- [x] 5.1 Extend platform_web API types and schemas with safe attempt, retry, cancel, terminal, and reconcile fields while preserving shared visual styles and 401/403 behavior.
- [x] 5.2 Update runtime task status presentation and tests without exposing lease/session/secret/host data or redesigning the console.
## 6. Regression And Completion Evidence
- [x] 6.1 Add platform tests for queue/store reload, lease and ack expiry, attempt fencing, retry/backoff, cancellation timing/idempotency, late messages, restart reconciliation, endpoint/owner rejection, and signature/session failure.
- [x] 6.2 Add independent Run tests for journal reload, restart reconciliation/recovery, cancel/result retention, stale discard, and channel isolation.
- [x] 6.3 Run plugin manifest/SDK tests, platform Go tests, platform_web tests/typecheck/build, independent Run tests, strict OpenSpec validation, structure check, shell/compose checks where affected, and both repositories' diff checks; then record evidence before checking tasks complete.
## Verification Evidence (2026-07-18)
- `platform`: `go test -count=1 ./...` passed across API, config, domain, DTO, model, repository, service, and validator packages.
- `platform_web`: 92 tests passed across 17 files; `npm run typecheck` and production `npm run build` passed.
- `plugins`: 17 manifest/SDK tests passed; TypeScript typecheck passed; dev, SCUM, and Minecraft example manifests validated.
- Independent `run`: `go test -count=1 ./...` passed across API, config, protocol, runtime, and spool packages.
- `openspec validate implement-durable-job-scheduling-and-reconciliation --strict`, `scripts/check-structure.sh`, shell syntax checks, and `docker compose config --quiet` passed.
- `git diff --check` passed in both `/Users/tasia/Desktop/code/browser` and the independent `/Users/tasia/Desktop/code/browser/run` repositories.
@@ -0,0 +1,53 @@
## Context
Earlier changes introduced channelized log ingest and artifact transfer contracts, but the Platform service keeps log batches, artifact transfer state, and artifact payload bytes in process memory. Metrics are currently generated from a live snapshot, backups are not represented, and Run remote access execution is a success-only placeholder. The repository already has file and MySQL metadata snapshots, signed Run requests, endpoint/session fencing, scoped plugin permissions, and safe frontend projections. This change extends those boundaries without replacing them.
## Goals
- Make restart behavior explicit and testable for logs, artifacts, metrics, and backups.
- Keep large bodies outside lightweight job/control payloads and expose only bounded, redacted projections to users, plugins, and platform_web.
- Make remote adapters declaration-driven and auditable, with cancellation and lease/attempt fencing handled by the existing job channel.
- Preserve independent channel priorities so slow artifact or adapter work cannot delay control heartbeat, job ack/result, or log acknowledgement.
## Decisions
### Decision 1: File-backed durable bodies behind service interfaces
Log segments, artifact chunks, and completed artifact content use owner-only files under configured private roots. Metadata and cursors remain in the existing repository snapshot (file or MySQL). Atomic temp-file rename, bounded reads, checksum verification, and startup reconstruction keep writes recoverable. Memory stores remain available for unit tests.
### Decision 2: Transfer manifests are the recovery source of truth
Artifact transfer sessions persist an idempotency key, owner scope, direction, size/chunk limits, received indexes, and final checksum. A restart reloads incomplete manifests and reconstructs next-missing state without exposing payload paths. A chunk is removed from the Run queue only after an acknowledgement for the exact transfer/artifact/index.
### Decision 3: Retention is bounded and deterministic
Log streams carry bounded retention count/age metadata; metric samples and backup records have configurable maximum records/bytes and oldest-first pruning. Pruning emits an audit record and never changes an acknowledged log sequence or an available artifact checksum. Recovery marks interrupted backups/receipts as recoverable failure rather than claiming success.
### Decision 4: Metrics and backups are append-only records with safe projections
Metric samples store server/run identity, timestamp, bounded numeric values, and source. Backup records store logical scope, artifact reference, checksum, size, state, and recovery/audit status. Host paths, credentials, sockets, PID, session/lease tokens, secret refs, and hashes used for fencing are excluded from response DTOs.
### Decision 5: Remote adapters are a constrained registry, not a command tunnel
The Platform registers adapter declarations from an installed plugin/runtime profile and authorizes a request only when owner/admin scope, server instance, selected endpoint, declared capability, and target allowlist all match. Run accepts a typed adapter kind and logical target key, validates timeout and retry bounds, checks context cancellation before and during work, and returns a safe result reference. Shell source, arbitrary command vectors, raw socket addresses, unapproved hosts, and embedded credentials are rejected. Existing Job attempt/lease/session fencing remains authoritative.
### Decision 6: Lightweight routes stay isolated
Control and job routes continue to reject log/artifact payload fields. Log ingest and artifact transfer use separate clients/queues. Remote adapter work is scheduled as a job and its result is metadata-only; it cannot write through control or log endpoints. Tests exercise interleaving and blocked/slow operations with bounded deadlines.
## Data Flow
1. Run appends a bounded log batch or artifact chunk to its owner-only spool/queue before upload.
2. A low-priority uploader sends the batch/chunk on its dedicated route; the Platform validates signature, endpoint/session, owner scope, sequence/range/checksum, and idempotency before durable append.
3. Platform persists metadata and body/manifests atomically, returns an acknowledgement, and the Run removes only the acknowledged item.
4. Metrics and backup records are written through bounded service methods, pruned deterministically, and queried through authorized safe DTOs.
5. A declared remote adapter request becomes a fenced job. Run executes only the typed adapter implementation, returns a bounded status/result ref, and Platform audits/project results after terminal fencing.
## Non-Goals And Follow-Ups
- No dependency installation, Run self-update, client-manager lifecycle, plugin lifecycle, production scaling/alerts, external object stores, arbitrary FTP/rsync/DB/RCON network access, or real AI provider integration.
- Native OS process birth tokens and distributed transaction guarantees remain outside this change.
## Rollback
The additive protocol and repository fields are backward compatible. Removing the new capabilities stops advertising durable/adapter features and leaves old metadata untouched; incomplete transfer manifests remain private and can be retried by a compatible release.
@@ -0,0 +1,29 @@
## Why
The platform has typed log and artifact routes, but restart recovery is incomplete: log acknowledgements and artifact transfer state are not durable, metrics are derived on demand, and backups and restricted remote adapters have no durable ownership/audit model. This change makes those existing channels operationally durable while preserving the control/job/log/artifact priority boundaries established by earlier changes.
## What Changes
- Persist log stream batches, acknowledgement cursors, retention metadata, and bounded queries across Platform restarts; keep Run local spool files retryable and recoverable.
- Persist artifact metadata, transfer sessions, chunk manifests, checksums, and content through a restart-safe bounded file-backed store; keep chunk retries idempotent and lower priority than logs/jobs/control.
- Add bounded metrics samples and backup records with retention, size limits, recovery status, and audit events; expose only safe projections.
- Add declaration-backed remote adapter requests for approved FTP/rsync/run-file/process/database/RCON operations with scoped targets, timeout/cancel/retry/fencing, and audit outcomes. No arbitrary shell, raw socket, unapproved host/credential, or bypass of Platform ownership/endpoint/session checks.
- Add Platform and Run protocol/client/runtime contracts plus platform_web safe status projections and regression coverage.
## Capabilities
### New Capabilities
- `durable-observability`: durable logs, artifacts, metrics, backups, retention, recovery, and safe query projections.
- `scoped-remote-adapters`: declared and authorized remote adapter execution with bounded lifecycle and audit semantics.
### Modified Capabilities
- `log-ingest-pipeline`: durable acknowledgement and restart recovery replace the earlier in-memory service assumption.
- `artifact-transfer-channel`: transfer manifests and content survive restart and retain idempotent chunk/checksum behavior.
## Impact
- Affects `platform/`, independent `run/`, and projection-only `platform_web/` contracts/views.
- Adds dedicated domain, DTO, model, repository, service, protocol, validator, and runtime types; no Run source is copied into the main repository.
- Does not implement dependency installation, Run self-update, client-manager lifecycle, plugin lifecycle, production scaling/alerts, or real third-party AI provider integration.
@@ -0,0 +1,61 @@
## ADDED Requirements
### Requirement: Durable logs recover after restart
The system SHALL persist accepted log batches, acknowledged sequence state, retention metadata, and bounded query indexes so a Platform restart does not duplicate or lose acknowledged ranges.
#### Scenario: Restart preserves log cursor
- **WHEN** a batch is acknowledged, Platform restarts, and a caller queries after a cursor
- **THEN** the ordered entries and latest acknowledged sequence MUST be available from the persisted store
#### Scenario: Retry remains idempotent
- **WHEN** Run retries an acknowledged batch with the same stream, range, and checksum
- **THEN** Platform MUST return an idempotent acknowledgement without duplicating entries
### Requirement: Run log spool is restart-safe
The Run log spool SHALL atomically persist unacknowledged batches, tolerate a process restart, and remove a batch only when an acknowledgement covers its full stream range.
#### Scenario: Interrupted enqueue
- **WHEN** a process restarts after an incomplete temporary spool write
- **THEN** the next spool load MUST ignore temporary files and retain every committed unacknowledged batch
### Requirement: Durable artifacts recover with checksum and chunk bounds
The system SHALL persist artifact metadata, transfer manifests, received chunk indexes, chunk checksums, final checksums, and bounded content so uploads can resume after restart.
#### Scenario: Resume missing chunk
- **WHEN** a transfer has received some chunks and Platform restarts
- **THEN** status MUST return the same received indexes and next missing index without exposing storage paths
#### Scenario: Checksum conflict is rejected
- **WHEN** a retry uses a different payload or checksum for an already received chunk
- **THEN** Platform MUST reject it and leave the original chunk and transfer state unchanged
### Requirement: Metrics and backups are durable and bounded
The system SHALL persist metric samples and backup records, apply explicit age/count/byte retention, support recovery status, and expose only owner-authorized safe projections.
#### Scenario: Retention prunes oldest records
- **WHEN** a metric or backup append exceeds its configured bound
- **THEN** the oldest records MUST be pruned deterministically and an audit event MUST record the retention result
#### Scenario: Interrupted backup is recoverable
- **WHEN** a backup remains in an incomplete state during restart
- **THEN** it MUST be projected as failed/recoverable with an audit outcome and MUST NOT claim an available artifact
### Requirement: Safe queries enforce ownership
The system SHALL authorize log, artifact, metric, and backup queries by platform session and server ownership/admin scope, returning bounded pages/cursors and never returning host paths, credentials, sockets, Run tokens, leases, session hashes, or secret references.
#### Scenario: Cross-owner query
- **WHEN** a user queries another owner's resource
- **THEN** the service MUST reject with the existing 403 behavior and MUST NOT reveal whether private body data exists
@@ -0,0 +1,47 @@
## ADDED Requirements
### Requirement: Remote adapters are declared and scoped
The system SHALL accept only typed adapter kinds and logical target keys declared by the installed plugin/runtime profile and selected Run endpoint.
#### Scenario: Undeclared adapter
- **WHEN** a request names an adapter or target not declared for the server and endpoint
- **THEN** Platform MUST reject it before creating a job
#### Scenario: Unsafe target data
- **WHEN** a request contains shell source, raw socket addresses, host paths, credentials, or unbounded inline query/command data
- **THEN** validation MUST reject it and MUST NOT persist the unsafe fields
### Requirement: Adapter lifecycle is bounded and fenced
The system SHALL enforce timeout, cancellation, retry, endpoint/session, attempt, and lease fencing using the existing job channel.
#### Scenario: Cancelled adapter
- **WHEN** cancellation arrives before or during adapter execution
- **THEN** Run MUST stop at a bounded checkpoint and return a cancelled safe result; Platform MUST not apply a stale terminal result
#### Scenario: Stale attempt result
- **WHEN** an older attempt reports success after a newer attempt owns the lease
- **THEN** Platform MUST reject the result and retain the newer job state
### Requirement: Adapter results are auditable projections
The system SHALL persist an audit event for authorization, timeout, cancellation, success, and failure outcomes and expose only adapter kind, target key, status, bounded message, and safe result references.
#### Scenario: Successful scoped adapter
- **WHEN** a declared adapter completes within its deadline
- **THEN** the operator MUST see a safe status and audit summary without raw host/credential/socket details
### Requirement: Channel isolation is maintained
Remote adapter work and artifact transfer SHALL use lower-priority independent work paths and MUST NOT delay control heartbeat, job ack/result, or log upload acknowledgement beyond their deadlines.
#### Scenario: Slow adapter and artifact transfer
- **WHEN** adapter or chunk work blocks or retries
- **THEN** control, job lifecycle, and log acknowledgement calls MUST remain independently completable
@@ -0,0 +1,29 @@
## 1. OpenSpec And Contracts
- [x] 1.1 Add durable log/artifact/metrics/backup/remote adapter domain, DTO, model, protocol, and validator contracts with forbidden-field tests.
- [x] 1.2 Extend file/MySQL snapshot and repository interfaces for durable bodies, transfer manifests, samples, backups, and audit projections.
- [x] 1.3 Update route/protocol/API contracts and platform_web safe types without exposing Run tokens, lease/session hashes, host paths, credentials, sockets, PID, or secret refs.
## 2. Durable Logs And Artifacts
- [x] 2.1 Make Platform log batches, cursors, retention, and file-backed body storage restart-safe and queryable with idempotent ack/retry.
- [x] 2.2 Make artifact transfer sessions/chunks/content durable, bounded, resumable, checksum-verified, and idempotent across restart.
- [x] 2.3 Make Run spool/queues atomic and restart-safe, and keep log/artifact upload scheduling independent from control/jobs.
## 3. Metrics, Backups, And Audit
- [x] 3.1 Persist bounded metrics samples with retention and authorized paginated safe queries.
- [x] 3.2 Persist backup records and recovery transitions with size/checksum/retention bounds and audit events.
- [x] 3.3 Add cross-owner/endpoint/signature/session rejection and safe projection tests for logs, artifacts, metrics, backups, and audits.
## 4. Scoped Remote Adapters
- [x] 4.1 Add declaration-backed adapter registry and Platform authorization/dispatch with timeout, cancel, retry, fencing, and audit semantics.
- [x] 4.2 Implement Run typed adapter execution checkpoints without arbitrary shell, raw sockets, unapproved hosts/credentials, or direct plugin access.
- [x] 4.3 Add remote adapter protocol/client/runtime tests for timeout, cancellation, stale attempt, wrong owner/endpoint, and safe result projection.
## 5. Frontend And Verification
- [x] 5.1 Add platform_web logs/artifacts/metrics/backups/adapter status, pagination/sequence/checksum/audit projections using existing theme primitives and auth handling.
- [x] 5.2 Add regression tests for restart/recovery/retention/checksum/idempotency and control/job/log/artifact channel isolation.
- [x] 5.3 Run plugin manifest/SDK tests, Platform Go tests, platform_web tests/typecheck/build, Run tests, strict OpenSpec validation, structure, shell/compose checks, and both repository diff checks; record only passing evidence.
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-07-17
@@ -0,0 +1,86 @@
## Context
Platform already persists server runtime profiles/bindings and durable Job scheduling metadata. Run already persists active assignments and pending terminal results, reconciles them after session rotation, signs Run-service requests, and keeps control/jobs/logs/artifacts on separate routes. The missing execution layer is narrower but security-sensitive: lifecycle start/stop are currently one-shot `exec.CommandContext` calls, config reads are platform-derived, and config/file jobs do not carry durable approved bytes or produce typed execution results.
The independent Run repository must remain separate. Browser/plugin callers name only a server, declared capability, logical target, and scoped input/artifact reference. Platform remains the authority for ownership, profile selection, approval, job attempt/session fencing, and safe projection. Run alone maps the logical scope into its private workspace and may persist PID or filesystem details, none of which can cross into platform_web or plugin DTOs.
## Goals / Non-Goals
**Goals:**
- Supervise one declared local game process per server/profile scope with real spawn, liveness, graceful stop, exit observation, idempotent operations, and restart reconciliation.
- Execute approved config/file reads and writes with strict containment, symlink/device rejection, bounded I/O, atomic replacement, and compare-and-swap version/checksum semantics.
- Preserve private execution input and typed result state across Platform/Run restarts without weakening task 04 lease, attempt, session-generation, signature, cancellation, or reconciliation rules.
- Project only safe process state, exit classification, config/file version, checksum, size, and audit summary to authorized owners/admins and platform_web.
- Keep current visual language and existing 401/403 handling.
**Non-Goals:**
- General shell execution, arbitrary interpreters, host path selection, direct sockets, credential injection, multiple unmanaged processes per server, or OS service/container orchestration.
- Durable log/artifact/metric/backup storage, remote FTP/rsync/database/RCON adapters, dependency installation, Run self-update, client-manager lifecycle, production scaling/alerts/plugin lifecycle, or real AI-provider integration.
- Claiming that example action declarations install or ship commercial game binaries.
## Decisions
### Decision 1: Extend the durable Job aggregate with private execution input and typed result
`Job` will persist a nested execution input containing the workspace profile key, approved content, expected version/checksum, and bounded read limit, plus a nested typed result containing kind, process state, exit classification, version, checksum, size, and audit summary. Model conversions and file/MySQL snapshot tests will cover the fields. The user `JobResponse` will omit approved content and expose only the safe result subset; the signed Run assignment carries the private input only after a matching claim.
This is preferred over an in-memory input map because queued/retrying work must survive Platform restart. A separate input repository was considered, but the input has the same lifecycle, idempotency, ownership, and retention as its Job and would add cross-record transaction failure without providing reuse.
### Decision 2: Resolve lifecycle action ref and workspace scope at dispatch
Platform will load the server's persisted runtime binding and selected plugin lifecycle profile, verify the requested capability/action is declared, set the Job target to that action's relative JSON ref, and set the private workspace scope to the selected profile key. Config/file dispatch uses the same binding-derived scope and validates the endpoint, plugin permission, server ownership, logical target, and scoped input/artifact ref before Job creation.
This replaces the current mismatch where lifecycle jobs send the profile key as `targetKey` while Run expects an action file ref. Platform will never send a host path or binding value to browser/plugin callers.
### Decision 3: Typed action files distinguish one-shot and supervised operations
Run will decode action JSON with unknown-field rejection, bounded size, and an explicit action matching the Job capability. `start` requires a workspace-relative executable key and argument vector; `stop` and `status` operate only on the persisted supervised identity for that server/profile; `install` remains a bounded one-shot declaration and does not represent dependency installation. Executable resolution rejects symlinks, non-regular/non-executable files, shells, interpreter escape forms, unsafe environment names/values, and paths outside the scoped workspace.
This keeps plugins declarative and supports actual process execution without accepting shell source or a generic command string. Keeping the existing unrestricted PATH lookup was rejected because names such as interpreters can turn a safe-looking vector into arbitrary execution.
### Decision 4: Run owns a private process journal and liveness probe
Run will atomically persist owner-only process records under its state directory. Records include logical server/profile identity, private PID, start time, command fingerprint, state, exit classification, and the latest owning Job attempt/lease hash, but not raw session tokens or environment credentials. A live in-process waiter records unexpected exits. On Run startup, reconciliation probes each recorded PID, retains only matching live identities, and marks missing identities exited before new claims execute.
Start is idempotent when a matching process is live; stop is idempotent when absent/stopped. Cancellation or timeout before start commit terminates the child. Attempt evidence prevents an older recovered assignment from replacing or stopping a newer process record. PID is intentionally private and never appears in Platform protocol results.
OS liveness primitives provide bounded best-effort identity validation. Strong native start-token adapters for every supported OS remain future hardening; command fingerprint and journal start metadata reduce PID-reuse ambiguity in this implementation.
### Decision 5: Secure workspace access validates every path component
The effective workspace is `<private-root>/instances/<server-id>/<profile-key>`, constructed only from validated logical identifiers. Reads and writes walk components with `Lstat`, reject symlinks, absolute/traversal/backslash keys, reserved Run state/action targets for generic writes, non-directory parents, device/FIFO/socket files, and any resolved path outside the scope. Reads require regular files and enforce a configured maximum before and during reading.
Writes compare current file metadata with expected version/checksum, create an owner-only temporary regular file in the same verified directory, write and fsync bounded bytes, recheck containment, rename atomically, fsync the parent where supported, and atomically update an owner-only file metadata journal. A conflict performs no rename. This is preferred over `os.WriteFile`, which can follow symlinks and expose partial content.
### Decision 6: Platform applies terminal typed results only after existing fencing
Run returns typed execution results on the existing Job result route with Job ID, attempt, lease token, and current signed session. Platform validates type/capability consistency, bounds/redacts fields, includes the typed result in the terminal fingerprint, and stores/applies it only after the existing endpoint/session-generation/attempt/lease/deadline/cancel checks pass.
A successful config write advances the persisted server config version and checksum and stores the approved content already attached to that Job. File reads store private bounded content on the Job but expose only checksum/size/version in the normal job projection. Lifecycle process state updates the server projection without trusting Run-supplied PID or path data. Stale/conflicting terminal results cannot mutate server config or process projection.
### Decision 7: Frontend changes are projection-only
`platform_web` will add schema validation for the safe execution result, show config checksum/version and process/file audit outcomes in existing server detail/job surfaces, and continue to use the current preview-then-approve flow for AI or manual config changes. It will not render approved private bytes from Job records, PID, host paths, Run tokens, leases, secret refs, sockets, or credentials, and it will reuse shared theme surfaces.
## Risks / Trade-offs
- [A process can exit between a liveness probe and an idempotent response] -> Record the latest observed state, keep probes bounded, and treat later status as authoritative rather than claiming continuous availability.
- [PID reuse after a long Run outage can produce ambiguous recovery on some OSes] -> Persist start metadata and command fingerprint, reject inconsistent identities, document best-effort recovery, and leave stronger per-OS birth-token adapters as follow-up hardening.
- [Platform snapshot persistence of approved config bytes increases metadata size] -> Keep input/read limits at 64 KiB and never store large artifacts in Job input; larger payloads stay artifact-referenced and are outside this change's durable artifact claim.
- [Atomic rename durability differs by filesystem] -> fsync file and parent where supported, keep same-directory temporary files, and test visibility/cleanup semantics without claiming distributed-filesystem guarantees.
- [Existing example workspaces may not contain supervised executables] -> Preserve bounded install behavior, make start failures explicit and retry policy-aware, and use controlled executable fixtures in Run tests.
## Migration Plan
1. Add backward-compatible zero-value execution input/result and config checksum/content fields to Platform domain/model/snapshot conversions.
2. Add protocol DTOs/validators and result projection before enabling Run capabilities.
3. Deploy Run secure workspace, process/file journals, executor, and worker dispatch; old journals load with empty new fields.
4. Update plugin action schema/examples and Platform lifecycle target resolution.
5. Enable safe frontend projections after API fields are available.
6. Rollback ignores additive snapshot fields and stops advertising new Run capabilities; supervised child processes must be stopped through the old Run instance or operator-controlled host procedure before removing its private journal.
## Open Questions
- Strong native process birth-token verification for every supported OS is intentionally deferred; this change uses the bounded liveness/fingerprint strategy described above.
@@ -0,0 +1,31 @@
## Why
Run currently executes lifecycle declarations as short-lived commands and platform config/file APIs only enqueue logical placeholders. The system therefore cannot supervise a real game process across Run restarts or prove that an approved, fenced config/file job performed a bounded workspace operation.
## What Changes
- Replace one-shot start/stop behavior with a restricted process supervisor that consumes plugin-declared typed action files and argument vectors, persists private process identity, reconciles surviving processes after Run restart, and provides idempotent start/stop/status outcomes without arbitrary shell execution.
- Execute `config.write`, `files.read`, and `files.write` jobs inside a profile-scoped Run workspace with lexical and filesystem containment, symlink/device rejection, bounded reads, atomic writes, version/checksum compare-and-swap, cancellation, and attempt fencing.
- Persist approved execution inputs and typed safe results on platform jobs so Platform restart does not lose a queued write body, expected version/checksum, process state, file checksum, size, or audit summary.
- Resolve lifecycle action refs and workspace scope from the server's persisted plugin runtime profile/binding while retaining owner/platform-admin/Run-service authorization and signed Run channel boundaries.
- Project successful lifecycle and config results into durable server state/config metadata and expose only safe process/config/file result fields to platform_web with existing 401/403 behavior and visual system.
- Extend plugin manifest validation and examples for typed lifecycle action contracts and add cross-repository regression coverage for process reconciliation, stale/cancelled attempts, path safety, atomic versioned file operations, ownership, signatures, and channel isolation.
- Keep durable logs/artifacts/metrics/backups, remote adapters, dependency installation, Run self-update, client-manager lifecycle, production scaling/alerts/plugin lifecycle, and real AI-provider integration outside this change.
## Capabilities
### New Capabilities
- `bounded-process-supervision`: Restricted typed process start/stop/status, private durable identity, idempotency, exit observation, and Run restart reconciliation.
- `scoped-config-file-execution`: Platform-approved durable inputs and typed results for versioned, atomic, bounded config/file operations inside a contained Run workspace.
### Modified Capabilities
## Impact
- `plugins/`: lifecycle action schema/types, example declarations, manifest validation, and SDK tests; plugins still receive no Run transport or machine details.
- `platform/`: job/domain/model persistence fields, lifecycle/config/file services, authorization, Run protocol DTOs and validators, result projection, routes/contracts, and tests.
- Independent `run` repository: protocol mirrors, persistent process/file journals, secure workspace resolver, process supervisor, config/file executor, worker dispatch/recovery, and channel tests.
- `platform_web/`: safe API/job schemas and server detail status/config checksum presentation only; no unrelated redesign.
- Public API responses gain safe execution result metadata, while private approved content, PID, host paths, credentials, sessions, leases, and hashes remain outside user DTOs.
@@ -0,0 +1,68 @@
## ADDED Requirements
### Requirement: Lifecycle execution uses declared typed actions
Platform and Run SHALL execute local lifecycle jobs only from the server's selected plugin runtime profile and a bounded typed action declaration whose action matches the requested capability. Run MUST reject arbitrary shell, unrestricted PATH execution, absolute executables, undeclared environment fields, and unsafe argument content.
#### Scenario: Declared start action executes
- **WHEN** an authorized start Job carries the selected profile scope and its declared relative action ref
- **THEN** Run validates the typed start declaration and starts only the workspace-contained executable with the declared argument vector
#### Scenario: Shell or mismatched action is rejected
- **WHEN** an action declaration contains shell execution, an unsafe executable, or an action different from the Job capability
- **THEN** Run fails the Job without creating a supervised process
### Requirement: Process start and stop are real and idempotent
Run SHALL supervise at most one matching game process per server/profile scope and SHALL make repeated start and stop operations converge without creating duplicate processes or failing solely because the desired state already exists.
#### Scenario: Start already-running process
- **WHEN** a start Job targets a scope whose matching supervised process is alive
- **THEN** Run returns a successful typed `running` result and does not spawn another process
#### Scenario: Stop running process
- **WHEN** a stop Job targets a live supervised process
- **THEN** Run requests bounded graceful termination, escalates only within the declared policy, records the exit, and returns a safe `stopped` result
#### Scenario: Stop already-stopped process
- **WHEN** a stop Job targets a scope with no live supervised process
- **THEN** Run returns an idempotent successful `stopped` result without exposing process identifiers
### Requirement: Process state survives Run restart reconciliation
Run SHALL persist private controlled process identity/state atomically with owner-only permissions and SHALL reconcile every record against OS liveness before accepting new lifecycle work after startup or session rotation.
#### Scenario: Live process survives Run restart
- **WHEN** Run restarts while a recorded supervised process remains alive
- **THEN** startup reconciliation retains the logical process as `running` and a later status/start operation observes the same process rather than spawning a duplicate
#### Scenario: Process exited while Run was offline
- **WHEN** a recorded process is no longer alive during startup reconciliation
- **THEN** Run records a safe exited state and does not treat the stale PID as running
### Requirement: Unexpected exits and status queries are typed
Run SHALL observe exits of processes it starts and SHALL return bounded typed state, exit classification, timestamps, and audit summary for status Jobs without returning PID, host path, command bytes, environment credentials, sockets, sessions, leases, or hashes.
#### Scenario: Managed process exits unexpectedly
- **WHEN** a supervised process exits without a completed stop operation
- **THEN** Run records an unexpected-exit classification and a subsequent status result reports `exited` with bounded safe evidence
#### Scenario: User reads process result
- **WHEN** an authorized owner or administrator reads the completed lifecycle/status Job
- **THEN** Platform returns safe process state and exit classification and omits all private machine identity and fencing fields
### Requirement: Process operations honor cancellation and attempt fencing
Run SHALL bind process mutations to the current reconciled Job attempt and Platform SHALL apply typed terminal results only after endpoint, session generation, attempt, lease, deadline, cancellation, and signature checks succeed.
#### Scenario: Start is cancelled before commit
- **WHEN** the current start attempt is cancelled or times out before Run commits its process record
- **THEN** Run terminates any child created by that attempt and returns a cancelled result
#### Scenario: Stale attempt reports process result
- **WHEN** an older attempt or stale session submits a process result after retry/reconciliation
- **THEN** Platform rejects it and does not change the server process projection
### Requirement: Process traffic remains channel-isolated
Process execution, monitoring, and result reporting SHALL use the existing Job channel and MUST NOT block control heartbeat or Job acknowledgement/result traffic when log or artifact work is blocked.
#### Scenario: Artifact or log request blocks during process operation
- **WHEN** a log upload or artifact transfer remains blocked while a process Job completes
- **THEN** control heartbeat and the process Job acknowledgement/result continue through their independent paths
@@ -0,0 +1,104 @@
## ADDED Requirements
### Requirement: Platform persists approved bounded execution input
Platform SHALL authorize config/file operations against server ownership, plugin permissions, endpoint ownership, selected runtime binding, declared logical target, and scoped input/artifact reference before creating a Job. Approved bounded bytes, workspace profile, expected version/checksum, and read limit SHALL persist with the Job and SHALL remain private from user Job DTOs.
#### Scenario: Approved config survives Platform restart
- **WHEN** a reviewed config write is queued and Platform restarts before Run claims it
- **THEN** the same approved content, logical ref, expected version/checksum, and workspace scope remain available to the fenced Run assignment
#### Scenario: Cross-owner or wrong-endpoint dispatch is attempted
- **WHEN** a caller lacks server authority or a Job/ref belongs to another server or endpoint
- **THEN** Platform rejects the request without persisting or dispatching execution input
### Requirement: Workspace resolution prevents boundary escape
Run SHALL map the Platform-approved server ID and profile key into a private workspace and SHALL reject absolute paths, traversal, backslashes, symlinked components, symlink targets, reserved state/action targets, non-directory parents, device files, FIFOs, sockets, and any path outside the selected scope.
#### Scenario: Traversal or absolute target is submitted
- **WHEN** a config/file Job contains a traversal, absolute, or otherwise invalid logical target
- **THEN** Platform or Run rejects it before filesystem access
#### Scenario: Symlink escapes workspace
- **WHEN** any parent or final target is a symlink that resolves inside or outside the workspace
- **THEN** Run rejects the operation and leaves the referenced file unchanged
#### Scenario: Device or special file is targeted
- **WHEN** a read or write resolves to a device, FIFO, socket, or other non-regular file
- **THEN** Run rejects the operation without opening the special file
### Requirement: Config and file writes are atomic compare-and-swap operations
Run SHALL enforce bounded input, compare the current controlled version/checksum to the expected values, write an owner-only temporary regular file in the verified target directory, fsync and atomically rename it, and persist updated version/checksum metadata only after success.
#### Scenario: Atomic write succeeds
- **WHEN** expected version/checksum match and the approved input is valid
- **THEN** readers observe either the complete old content or complete new content and Run returns the incremented version, checksum, size, and safe audit summary
#### Scenario: Expected version conflicts
- **WHEN** the current controlled version differs from `expectedVersion`
- **THEN** Run returns a typed conflict and does not replace the file or metadata
#### Scenario: Expected checksum conflicts
- **WHEN** the current file checksum differs from `expectedChecksum`
- **THEN** Run returns a typed conflict and leaves content/version unchanged
#### Scenario: Write is cancelled before rename
- **WHEN** cancellation or attempt invalidation is observed before atomic commit
- **THEN** Run removes the temporary file and leaves the prior target/version unchanged
### Requirement: Reads are bounded and typed
Run SHALL read only regular contained files, enforce the assignment's maximum before and during I/O, compute SHA-256, and return a typed result with private bounded content plus safe version/checksum/size/audit metadata.
#### Scenario: Bounded read succeeds
- **WHEN** a contained regular file is no larger than the approved limit
- **THEN** Run returns its exact bounded content privately and reports matching checksum, size, and controlled version
#### Scenario: File exceeds read limit
- **WHEN** file metadata or streamed bytes exceed the approved limit
- **THEN** Run fails with a bounded size error and does not return partial content
### Requirement: Terminal config results update durable Platform state
Platform SHALL validate typed result/capability consistency after existing fencing, persist the safe result, and only then project a successful config write into the server's durable config content, checksum, version, and update time. Failed, cancelled, stale, or conflicting results MUST NOT mutate config state.
#### Scenario: Config write result is accepted
- **WHEN** the current fenced attempt returns a successful config result matching its approved content checksum and next version
- **THEN** Platform updates the server config and authorized config reads return the new content/version/checksum
#### Scenario: Stale config result arrives
- **WHEN** a stale attempt, invalid signature/session, wrong endpoint, or cancelled attempt returns a config result
- **THEN** Platform rejects it and preserves the prior config content/version/checksum
### Requirement: AI suggestions remain review-before-write
AI-assisted configuration SHALL continue to produce a reviewable diff and MUST NOT dispatch a config write until an authorized user approves that diff with the current expected version/checksum.
#### Scenario: AI suggestion is generated
- **WHEN** AI proposes configuration content
- **THEN** Platform and platform_web show a reviewable diff without creating a Run write Job
#### Scenario: User approves suggestion
- **WHEN** an authorized user approves the current diff
- **THEN** Platform persists the approved input and dispatches it through the normal fenced config Job path
### Requirement: User projections remain credential-free
Platform and platform_web SHALL expose only authorized process/config/file state, version, checksum, size, conflict/error classification, and bounded audit summary, and MUST NOT expose approved private Job content, raw AI keys, Run tokens, leases/hashes, secret refs, host paths, PID, sockets, or credentials.
#### Scenario: Authorized result is rendered
- **WHEN** an authorized user views config or operation history
- **THEN** platform_web renders safe typed metadata using existing theme surfaces and existing 401/403 handling
#### Scenario: Plugin requests a file operation
- **WHEN** a plugin page submits a declared scoped file request
- **THEN** it receives only the Platform-owned Job/safe result projection and no direct Run or workspace information
### Requirement: Config/file traffic remains channel-isolated
Config/file execution SHALL use bounded Job payloads and MUST NOT carry log batches or artifact chunks. Slow config/file I/O MUST NOT block control heartbeat, Job acknowledgement/result, or the independent log/artifact routes.
#### Scenario: File execution blocks
- **WHEN** a file executor is deliberately blocked
- **THEN** control heartbeat and unrelated Job acknowledgement/result requests continue within their own deadlines
### Requirement: Roadmap boundary remains explicit
Completion of this capability MUST NOT be reported as readiness for durable logs/artifacts/metrics/backups, remote adapters, dependency installation, Run self-update, client-manager lifecycle, production scaling/alerts/plugin lifecycle, or real AI-provider integration.
#### Scenario: Change is handed off
- **WHEN** implementation and verification complete
- **THEN** the handoff identifies those later-route capabilities as not implemented by this change
@@ -0,0 +1,33 @@
## 1. Contracts And Persistence
- [x] 1.1 Extend plugin manifest schema, SDK types, examples, and validation tests for bounded typed lifecycle action declarations and refs.
- [x] 1.2 Add Platform domain/model fields for private Job execution input, typed safe/private result, and durable server config content/checksum with copy/conversion tests.
- [x] 1.3 Extend Platform and independent Run Job protocol DTOs/validators for workspace scope, expected version/checksum, bounded content/read limit, and typed results without changing lease/session fencing.
- [x] 1.4 Verify file and MySQL snapshot round trips preserve approved inputs/results/config metadata without exposing them through user Job DTOs.
## 2. Platform Dispatch And Projection
- [x] 2.1 Resolve lifecycle action refs and workspace scope from the persisted selected runtime profile/binding and add typed process status dispatch.
- [x] 2.2 Persist approved config/file bytes or bounded artifact payloads with expected version/checksum and enforce owner/admin, plugin, endpoint, and logical target authorization.
- [x] 2.3 Validate and persist typed Run terminal results only after existing signature/session/attempt/lease/deadline/cancel fencing.
- [x] 2.4 Project successful process/config results into durable server state/config metadata and add safe audit fields while rejecting stale, conflicting, cross-owner, and wrong-endpoint mutations.
- [x] 2.5 Update Platform routes/contracts/tests for process status, config checksum approval, bounded file inputs, safe typed Job results, and existing 401/403 behavior.
## 3. Independent Run Execution
- [x] 3.1 Implement a shared secure workspace resolver that rejects traversal, absolute/backslash keys, symlink components/targets, reserved writes, and special files.
- [x] 3.2 Implement an owner-only atomic file metadata journal and real bounded read/atomic CAS write executor with checksum/version conflicts and cancellation cleanup.
- [x] 3.3 Implement an owner-only atomic process journal, contained executable validation, real spawn/wait/stop/status supervision, idempotency, timeout, and unexpected-exit recording.
- [x] 3.4 Reconcile persisted process identities on Run startup/session rotation and fence stale attempts without persisting raw session tokens.
- [x] 3.5 Wire config/file/process capabilities through Worker claim/ack/progress/cancel/result/recovery and keep control/jobs independent from blocked log/artifact/file work.
## 4. Safe Frontend Projection
- [x] 4.1 Extend platform_web API types/schemas/tests for safe process/config/file result metadata and config checksum while rejecting forbidden machine/fencing fields.
- [x] 4.2 Render process/config/file version, checksum, size, and audit outcome in existing server detail/job surfaces using shared black-mecha/magical-girl theme primitives and existing auth error handling.
## 5. Regression And Verification
- [x] 5.1 Add Run regressions for idempotent start/stop, unexpected exit, restart reconciliation, stale/cancel, traversal/symlink/device escape, atomic write, CAS conflicts, bounded reads, and channel isolation.
- [x] 5.2 Add Platform regressions for durable private input, typed projection, config application, ownership/endpoint rejection, stale attempt, signature/session failure, AI review-before-approval, and channel isolation.
- [x] 5.3 Run plugin tests/typecheck/all manifest validation, Platform Go tests, platform_web tests/typecheck/build, independent Run tests, strict OpenSpec validation, structure check, shell/compose checks, and both repositories' `git diff --check`; record only passing evidence before marking complete.
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-07-17
@@ -0,0 +1,107 @@
## Context
The existing distribution workflow validates a plugin-declared client-manager profile, queues a real `distribution.build` job, injects a separate component key through authenticated build input, and publishes an available artifact only after chunked upload succeeds. It does not represent an installed instance, deploy the artifact through Run, supervise the companion process, authenticate the component as its own actor, reconcile health after Platform or Run restarts, or provide update/rollback/uninstall workflows.
The implementation spans the plugin contract, Platform persistence and APIs, the independent Run repository, and platform_web. Existing boundaries remain mandatory: Run is not reintroduced into this repository; browser/plugin callers never receive raw secrets, paths, PIDs, sockets, or endpoint addresses; job, artifact, log, control, and optional game-client traffic remain isolated; and existing dirty changes in both repositories must be preserved.
## Goals / Non-Goals
**Goals:**
- Carry a plugin-declared client manager from a real build artifact through authorized deployment, installation, registration, health, control, update/rollback, revocation, and safe uninstall.
- Persist a durable Platform aggregate and a Run-local journal so retries, cancellation, stale attempts, lease expiry, and restarts converge instead of reporting synthetic success.
- Authenticate Client Manager directly as a separate component identity with a short-lived session and heartbeat contract, while retaining the distinct singleton component key and generation already used at build time.
- Restrict Run execution to typed deployment and lifecycle operations inside a controlled workspace with checksummed artifacts and declarative executable/health metadata.
- Project safe, useful lifecycle state, real job progress, recovery actions, and audited confirmations into the existing game-operations console.
**Non-Goals:**
- Billing, cloud host sales, provider marketplaces, host provisioning, or a general remote administration surface.
- Arbitrary shell commands, plugin-selected host paths, direct browser/plugin access to Run or Client Manager sockets, or reuse of Run sessions/leases/keys for Client Manager.
- Production KMS, public code-signing trust, private-source credential management, or fleet-wide rollout orchestration. Existing envelope encryption and checksum verification remain the bounded first-party mechanisms.
- Claiming production readiness for client-manager fleets, production sandboxing, or later operations-console work.
## Decisions
### Decision 1: A durable installation aggregate owns lifecycle state
Platform adds one `ClientManagerInstallation` per server instance and profile. It references, but is not the same record as, a `ClientManagerDistribution`. The aggregate stores the assigned Run endpoint, target tuple, desired/active/previous artifact and version metadata, current component-key generation, deployment generation, lifecycle status and phase, current job, last successful job, health summary, last seen, retryable failure, and timestamps. Distribution states remain `building`, `available`, `failed`, or `revoked`; installation states cover `requested`, `building`, `available`, `deploying`, `installed`, `registering`, `online`, `degraded`, `offline`, `updating`, `rolling_back`, `stopping`, `uninstalled`, and `failed`.
All transitions are validated in the service layer and persisted before dispatch. Terminal job projection and component heartbeat advance the aggregate idempotently. A startup/periodic reconciler rebuilds missing projections from durable jobs/sessions and moves timed-out health to `degraded` then `offline` without deleting history.
Alternative: derive lifecycle state from the latest build job and heartbeat. Rejected because it loses desired state, previous deployment, retries, uninstall history, and restart reconciliation.
### Decision 2: Run executes typed lifecycle jobs with strict fencing
Platform uses dedicated job kinds for `client-manager.deploy`, `client-manager.control`, `client-manager.update`, `client-manager.rollback`, and `client-manager.uninstall`. Payloads name logical installation/profile/artifact IDs, target tuple, version/revision, component-key generation, deployment generation, action, checksum, and idempotency key only. Platform accepts them only when user/server/plugin/endpoint authorization, profile capability, endpoint capability, artifact ownership, target/revision compatibility, current key generation, and allowed state transition all agree.
Run validates the same immutable fields, leases jobs through the existing durable scheduler, and records attempt plus fencing generations in a local journal. Duplicate idempotency keys return the recorded outcome; a stale attempt or deployment generation cannot replace a newer active deployment. Cancellation is checked between artifact chunks and activation steps. Retriable failures retain staging state; permanent validation failures never execute.
Alternative: model lifecycle as generic commands or reuse game-server lifecycle jobs. Rejected because arbitrary commands are unsafe and client-manager identity/deployment semantics differ from both Run and the managed game server.
### Decision 3: Deployment uses controlled slots and atomic activation
Run owns a configured client-manager workspace below its data root. Each installation receives stable internal `active`, `previous`, and `staging/<deployment-generation>` slots. Artifact bytes are downloaded on the artifact channel with offset/checksum resume metadata, extracted with traversal/link checks, and verified against the Platform checksum before activation. The executable/config paths are resolved from the approved profile/package contract, never from the API caller. Activation uses an atomic rename where supported; the previous slot is kept for one bounded rollback generation.
Uninstall stops the supervised process, revokes/forgets the local component session material, and removes only the installation's controlled slots and journal entry. It never follows symlinks or deletes server/shared roots. History and audits remain in Platform.
Alternative: unpack directly over the active files. Rejected because cancellation, partial download, checksum failure, and rollback would leave an indeterminate executable.
### Decision 4: Client Manager has a separate signed identity and session
The generated package retains the current client-manager component key and generation. Initial registration signs a canonical request with that key, timestamp, and nonce. Platform resolves the same-server/component key, verifies generation and ownership, rejects expired timestamps or replayed nonces, and checks installed artifact version/revision/capabilities against the active deployment. Successful registration creates a randomly generated short-lived Client Manager session, persists only its hash and metadata, and returns the bearer token only to the Client Manager process.
Heartbeat and capability reports use that component session, not the Run control session or Run job lease. Sessions are bound to installation ID, server ID, profile, key generation, deployment generation, and active artifact. Reset, explicit revoke, update activation, rollback, uninstall, ownership/endpoint reassignment, or expiry revokes the session. A new deployment must register again. Platform stores bounded replay nonces and prunes them after the signature window.
Alternative: let Run proxy its own Platform session for the child process. Rejected because it would let a client-manager compromise inherit Run's broader machine authority and would couple component health to Run control traffic.
### Decision 5: Supervision and health are declarative and bounded
Profiles declare a fixed executable relative path, fixed argument keys/placeholders, startup timeout, stop timeout, health mode, health interval/timeout, required capability names, and whether start/restart/update/rollback are allowed. Validation rejects shell metacharacters, absolute/traversing paths, environment secrets, raw sockets, and unknown capabilities. Run launches only the approved relative executable from the active slot, captures bounded diagnostics, and reports logical process/health states without PIDs or paths.
Health can be process-presence or a bounded component self-report contract. Platform uses signed component heartbeats as the authoritative online signal, with Run process state as deployment/control evidence. Missing heartbeats transition online to degraded and then offline according to profile bounds.
Alternative: accept plugin-provided shell start/health commands. Rejected because it creates an unrestricted execution and data-exfiltration path.
### Decision 6: Updates are single-installation staged transactions
An update requires an available current-generation artifact with the same server/profile/target, a compatible declared version/revision, explicit operator approval, and a healthy installed baseline unless force recovery is explicitly allowed. Run downloads and verifies the new artifact in a staging slot, stops the old process only at activation, swaps slots, starts the candidate, and waits for bounded process/component health. Success commits the active/previous references; failure automatically restores the previous slot and reports `rolling_back` followed by the real result.
Platform rejects revoked artifacts, stale key/deployment generations, cross-target or cross-owner artifacts, and replayed update requests. Restart reconciliation resumes from the durable phase or safely rolls back; it never marks a later phase complete based on a timer.
Alternative: overwrite and restart immediately. Rejected because it cannot prove health or recover from a broken package.
### Decision 7: API and UI expose a safe action projection
Platform provides installation summary/detail, deploy, control, update, rollback, session revoke, retry, and uninstall APIs plus component-only register/heartbeat endpoints. Operator endpoints require the existing session/role/server authorization; component endpoints use the separate signature/session authenticator. Action availability is computed from installed plugin declarations, runtime binding completeness, server ownership, endpoint online/capabilities, distribution state/ownership/target/key generation, installation state, and current user permission.
platform_web renders a Client Manager operations section on Server Detail and compact availability in server actions. It shows profile, target, desired/active/previous versions, artifact and job IDs, deployment generation, safe health/last-seen reason, build/deploy/register/control/update/rollback/uninstall phases, retry guidance, and destructive confirmations. It never renders a raw key, token, secret ref/value, path, PID, socket, credential, endpoint address, DSN, or RCON password.
### Decision 8: Auditing and channel isolation are first-class invariants
Every build/deploy/register/start/stop/restart/update/rollback/revoke/uninstall success, failure, and denial records a durable audit with actor type, safe actor ID, server, profile/component, installation/job/artifact IDs, result, and redacted reason. Component heartbeats are summarized as health state rather than producing an unbounded audit event per pulse.
Artifact download remains resumable and lower priority; Run control heartbeat, job ack/result/cancel, log spool upload, and optional client-manager traffic use independent workers/queues. Channel-isolation tests exercise a stalled client-manager download and prove the other channels progress.
## Risks / Trade-offs
- [Risk] A malicious or compromised source repository can still produce a hostile binary. → Continue requiring approved HTTPS repositories, pinned revisions, fixed build adapters, isolated build workspaces, bounded logs, and explicit operator deployment; do not claim public untrusted builds are production safe.
- [Risk] Atomic rename and executable replacement differ across operating systems. → Keep platform-neutral slot semantics, isolate OS-specific activation in Run, retain the prior slot, and fail without changing active state when atomic activation is unavailable.
- [Risk] Platform and Run can observe different phases during network loss. → Persist intent before dispatch, use idempotency/deployment generations, reconcile job/session/journal state, and favor safe `degraded`/`failed` projections over inferred success.
- [Risk] Key reset immediately invalidates an online component. → Revoke sessions and old distributions, mark the installation as requiring a current-generation rebuild/redeploy, explain recovery in UI, and never silently rotate a package secret.
- [Risk] Heartbeat writes and nonces can grow storage. → Store bounded summaries, unique nonce digests within a short verification window, and prune expired sessions/nonces during reconciliation.
- [Risk] Safe extraction and cleanup are security-sensitive. → Reject traversal, links, device files, unexpected package layouts, and any deletion outside the configured client-manager workspace; cover these cases with tests.
## Migration Plan
1. Extend and validate plugin profile declarations without changing existing installed profile records; profiles lacking the new deployment contract remain build/download-only and lifecycle actions are unavailable with a safe reason.
2. Add Platform models/repositories and initialize installation/session/nonce state without mutating existing distributions or component keys.
3. Add typed Platform APIs/jobs/reconciliation and independent component authentication behind capability gating.
4. Add Run protocol/runtime support, controlled workspace/journal, supervisor, update/rollback, and uninstall safety.
5. Enable the full profile for the first-party SCUM example and add platform_web lifecycle management only when the API projection advertises actions.
6. Verify both repositories and all consumers. Rollback hides new actions and stops dispatching lifecycle jobs; existing distribution download remains available and durable lifecycle/audit history is retained.
## Open Questions
- Production code-signing, KMS-backed component keys, private repository credentials, multi-node fleet rollout, and long-term deployment artifact retention remain explicit follow-up work.
- The first implementation supports one active installation per server/profile and one retained previous slot; multi-instance client-manager replicas require a later contract.
@@ -0,0 +1,32 @@
## Why
Client-manager support currently ends after a real package is built and downloaded: Platform does not durably deploy, register, supervise, update, roll back, revoke, or uninstall the companion process. Operators therefore cannot complete a secure build-to-online-to-retired lifecycle or distinguish real machine state from package availability.
## What Changes
- Extend plugin client-manager profiles with version/revision, deployment mode, required capabilities, health contract, lifecycle actions, compatibility constraints, and update policy while continuing to reject arbitrary commands and secret-bearing declarations.
- Add a durable Platform client-manager installation aggregate and state machine covering request, build, availability, deployment, installation, registration, online health, degradation/offline detection, update/rollback, stop, failure, revocation, and uninstall history.
- Add typed `client-manager.deploy`, lifecycle control, update, rollback, and uninstall jobs that only an authorized, online, capable Run endpoint may execute for a same-server, same-component, target-compatible, current-generation available artifact.
- Add a client-manager component identity, signed registration/session/heartbeat protocol, capability fencing, expiry/replay protection, and revocation that is separate from Run control registration, jobs, leases, and credentials.
- Add Run-side staged/resumable artifact deployment, checksum verification, atomic activation, bounded process supervision, durable local journals, reconciliation, retry/cancel/idempotency fences, health checks, update rollback, and safe uninstall of controlled workspaces only.
- Add server-list/detail Client Manager management workflows for version/build/deployment/registration/health, start/stop/restart, update/rollback, key reset recovery, failure retry, revoke, and uninstall, backed by real job progress and confirmation flows.
- Add durable audit and safe status projections for every sensitive operation while preventing plugins and platform_web from receiving raw keys, sessions, secret refs/values, host paths, PIDs, sockets, credentials, or direct Run endpoint details.
- Preserve independent control, job, log, artifact, and optional client-manager bridge channels so client downloads and traffic cannot block Run heartbeat, job results, or log upload.
## Capabilities
### New Capabilities
- `secure-client-manager-lifecycle`: Secure deployment, component identity, durable state, health, bounded control, update/rollback, revocation, uninstall, reconciliation, auditing, and operator workflows for plugin-declared client managers.
### Modified Capabilities
- `run-distribution-and-client-managers`: Client-manager build artifacts become inputs to a real deployment lifecycle, and key reset/revocation must fence installed instances and require a current-generation redeploy.
## Impact
- `plugins/`: manifest schema, SDK/bridge contracts, SCUM and Minecraft examples, unsafe fixtures, validation, and documentation.
- `platform/`: domain/model/repository state, validators, signed component-session protocol, job orchestration, reconciliation, audit, DTOs, API routes, authorization, and documentation.
- Independent `run/` repository: protocol contracts, artifact deployment, local journal/workspace safety, process supervision, health reporting, lifecycle execution, update/rollback, and channel-isolation tests.
- `platform_web/`: API types/schemas/client, Server Detail Client Manager workspace, server action availability, status/progress/confirmation/error states, tests, and browser acceptance.
- No billing, cloud-host/provider marketplace, arbitrary shell, general remote control, production KMS/code-signing, or fleet-orchestration claim is introduced.
@@ -0,0 +1,35 @@
## MODIFIED Requirements
### Requirement: Run and client-manager keys are isolated singletons
Run executors and plugin-declared client managers SHALL use different authentication secrets, and each server/component SHALL have exactly one current active key stored encrypted in the platform database. A deployed Client Manager SHALL exchange proof of its current component key for a separate short-lived component session and SHALL never use a Run control session or job lease.
#### Scenario: Client manager is generated after run
- **WHEN** a plugin-declared client-manager package is generated for a server that already has a run package
- **THEN** platform MUST create or reuse the server's current encrypted client-manager key and MUST NOT reuse, reveal through API metadata, or derive it from the run key
#### Scenario: Component key reset is requested
- **WHEN** an operator resets a server's run key or client-manager key
- **THEN** platform MUST replace the encrypted database key for that component, increment the key generation, revoke all packages generated with prior generations, revoke matching component sessions and installed deployment fences, mark the affected installation as requiring current-generation rebuild and redeploy, and record an audit event identifying the component kind without logging raw key material
#### Scenario: Old package authenticates after reset
- **WHEN** a run or client-manager package generated before the latest key reset attempts to authenticate, register, heartbeat, deploy, or execute lifecycle work
- **THEN** platform MUST reject the old key, session, artifact, or generation and require the operator to regenerate and redeploy the corresponding run or client-manager package
### Requirement: Client-manager packages are plugin-declared builds
The platform SHALL support plugin-declared client-manager build profiles for companion executables that require source checkout, configuration injection, and compilation before download or secure lifecycle deployment. Only a real available build artifact with a current component-key generation SHALL be eligible for deployment.
#### Scenario: SCUM-style client manager is generated
- **WHEN** a plugin declares a client-manager build profile with repository, revision policy, supported target platform, build system, config template, output artifact paths, deployment contract, lifecycle capabilities, health contract, compatibility constraints, and update policy
- **THEN** platform MUST create a run-worker build job that checks out the approved source and revision, injects configuration obtained through the authenticated job-input channel, compiles the target executable, uploads the downloadable artifact through the artifact channel, records deployable version/target/key-generation metadata, and redacts secrets and workspace paths from progress and build results
#### Scenario: Client-manager build is still running
- **WHEN** the source checkout, environment check, dependency download, compile, artifact upload, or publication stage is incomplete
- **THEN** platform_web MUST display the corresponding real job progress and MUST NOT mark later build or deployment stages complete on a local timer
#### Scenario: Unsupported client-manager target is requested
- **WHEN** an operator requests a client-manager build for an OS/architecture not declared by the plugin profile
- **THEN** platform MUST reject the request before cloning source or creating a credential
#### Scenario: Built artifact is selected for deployment
- **WHEN** an operator selects a client-manager distribution for lifecycle deployment
- **THEN** platform MUST require status available, current key generation, matching server/profile/component/target, approved revision and compatibility metadata, authorized server access, complete runtime binding, and an online assigned Run endpoint with the declared deployment capabilities before creating a typed deploy job
@@ -0,0 +1,153 @@
## ADDED Requirements
### Requirement: Plugins declare bounded client-manager lifecycle contracts
Game plugins SHALL declare client-manager version/revision metadata, supported targets, deployment mode, required Run and component capabilities, relative executable contract, bounded lifecycle actions, health contract, compatibility constraints, and update policy before Platform enables lifecycle operations.
#### Scenario: Valid lifecycle profile is installed
- **WHEN** a plugin declares a client-manager profile with a supported target, pinned or policy-approved revision, fixed build adapter, safe relative executable, bounded start/stop/restart and health settings, and known capability names
- **THEN** plugin and Platform validation MUST preserve the declaration and Platform MUST derive lifecycle availability from the installed declaration, runtime binding, server ownership, and assigned endpoint capabilities
#### Scenario: Unsafe lifecycle profile is submitted
- **WHEN** a plugin declaration contains arbitrary shell, an absolute or traversing path, raw credentials, secret or token values, direct sockets, host endpoints, environment secrets, unknown capabilities, or an unbounded health/control action
- **THEN** plugin and Platform validation MUST reject it before registration or lifecycle dispatch
### Requirement: Platform gates every lifecycle action against current ownership and capability state
Platform SHALL authorize client-manager build, deploy, register, control, update, rollback, revoke, retry, and uninstall independently using the current actor, server visibility, installed plugin declaration, runtime binding, assigned Run endpoint, artifact ownership, target, revision, component key generation, and lifecycle state.
#### Scenario: Authorized owner deploys an available build
- **WHEN** a server owner or authorized administrator selects an available current-generation distribution for the same server, profile, target, and approved revision while the assigned Run endpoint is online and declares client-manager deployment capability
- **THEN** Platform MUST create or reuse one typed deployment intent and job and MUST expose its real state and progress
#### Scenario: Mismatched lifecycle input is requested
- **WHEN** an actor supplies another owner's server, another server or component artifact, another Run endpoint, a mismatched target or revision, an expired or revoked distribution, or a stale component-key generation
- **THEN** Platform MUST deny the operation before job creation, MUST record a redacted denial audit, and MUST NOT reveal whether an inaccessible resource exists
#### Scenario: Service or component credential calls an operator endpoint
- **WHEN** a Run service credential or Client Manager component session calls an operator lifecycle endpoint without the required operator role
- **THEN** Platform MUST return an authorization failure and MUST NOT broaden that credential into an operator session
### Requirement: Platform persists and reconciles a real lifecycle state machine
Platform SHALL durably persist desired state, active and previous deployment references, component-key and deployment generations, job linkage, health summary, failure detail, and lifecycle timestamps for each server/profile installation.
#### Scenario: Lifecycle advances through real evidence
- **WHEN** build, deployment, registration, control, update, rollback, or uninstall work changes phase
- **THEN** Platform MUST transition only through valid requested, building, available, deploying, installed, registering, online, degraded, offline, updating, rolling_back, stopping, uninstalled, or failed states using durable job results, Run reports, or authenticated component heartbeats rather than local UI timers
#### Scenario: Platform restarts with in-flight work
- **WHEN** Platform restarts while a lifecycle job or component session is in progress
- **THEN** reconciliation MUST restore the persisted intent, project the durable job/session state idempotently, reject stale attempts, and either resume, retry, roll back, or fail safely without creating a duplicate activation
#### Scenario: Duplicate lifecycle request is retried
- **WHEN** the same actor repeats a request with the same idempotency key and immutable inputs
- **THEN** Platform MUST return the original installation/job result, while the same idempotency key with different immutable inputs MUST be rejected
### Requirement: Run deploys client managers through a checksummed controlled workspace
Run SHALL execute client-manager deployment only through the typed job contract and SHALL download, resume, verify, stage, and atomically activate an authorized distribution inside its configured client-manager workspace.
#### Scenario: Deployment completes after an interrupted transfer
- **WHEN** a current leased deployment downloads an available artifact in chunks and the transfer is interrupted
- **THEN** Run MUST persist offset and checksum state, resume without re-downloading acknowledged bytes, verify the final checksum and safe package layout, activate the staged slot, and report installed only after real activation succeeds
#### Scenario: Deployment payload or package is unsafe
- **WHEN** a deployment contains arbitrary commands, raw host paths, sockets, credentials, a stale attempt/deployment/key generation, a mismatched artifact/target/component, a checksum failure, traversal, symlink, device file, or unexpected executable layout
- **THEN** Run MUST reject or fail the job without changing the active slot and MUST return only bounded redacted diagnostics
#### Scenario: Deployment is cancelled or retried
- **WHEN** cancellation arrives between chunks or activation phases, a lease expires, or a retry uses the same idempotency and deployment generation
- **THEN** Run MUST honor the current fence, retain only safe resumable staging state, never let a stale attempt replace a newer activation, and converge on one recorded outcome
### Requirement: Client Manager authenticates as an independent component
Client Manager SHALL register with Platform using its own current component key and generation and SHALL receive a short-lived component session that is separate from Run control registration, job leases, credentials, and channels.
#### Scenario: Installed component registers successfully
- **WHEN** a deployed Client Manager signs a canonical registration request with a fresh timestamp and nonce and reports the active installation, artifact, version/revision, deployment generation, and declared capabilities
- **THEN** Platform MUST verify the same server/profile ownership, current component-key generation, active deployment, target/revision, signature, nonce, and capabilities, persist only a hash of a new expiring component session, return the token only to the component, and move the installation toward online health
#### Scenario: Registration signature is stale, replayed, revoked, or mismatched
- **WHEN** registration uses an expired timestamp, repeated nonce, revoked or previous-generation key, another server/component identity, inactive artifact, stale deployment generation, or undeclared capabilities
- **THEN** Platform MUST reject registration, record a safe denial audit, and MUST NOT create or reveal a component session
#### Scenario: Run identity is presented as Client Manager identity
- **WHEN** a caller presents a Run key, Run bearer session, Run job lease, or Run endpoint identity to the Client Manager registration or heartbeat contract
- **THEN** Platform MUST reject it and MUST NOT reuse Run authentication state
### Requirement: Component heartbeats drive safe health projection
Platform SHALL accept bounded heartbeat and capability reports only from a valid component session and SHALL project logical health and last-seen state without exposing local process details.
#### Scenario: Healthy component heartbeat arrives
- **WHEN** an unexpired, unrevoked session bound to the active installation reports a monotonic heartbeat with declared capabilities and a safe health code
- **THEN** Platform MUST update last seen and logical health idempotently and MUST expose only version, status, health code/reason, capabilities, and timestamps to authorized operators
#### Scenario: Heartbeat expires
- **WHEN** a component misses its declared heartbeat grace and offline thresholds
- **THEN** reconciliation MUST transition the installation from online to degraded and then offline using safe reasons while preserving the last successful deployment and audit history
#### Scenario: Session heartbeat is replayed or fenced
- **WHEN** a heartbeat sequence repeats, the session is expired/revoked, or its key, deployment, endpoint ownership, or artifact fence is no longer current
- **THEN** Platform MUST reject it without mutating health and require a new valid registration
### Requirement: Run performs bounded client-manager process control
Run SHALL start, stop, restart, and inspect a deployed Client Manager only through the plugin-declared executable and health contract and the typed lifecycle job.
#### Scenario: Operator starts or restarts a deployed component
- **WHEN** Platform dispatches an authorized current-generation control job whose action is declared by the profile
- **THEN** Run MUST supervise the fixed relative executable from the active slot, use bounded timeouts, persist the logical process state, and report progress and outcome without returning a PID, host path, environment secret, or socket
#### Scenario: Unsupported or stale control is requested
- **WHEN** a control action is undeclared, the installation is uninstalled, the attempt or deployment generation is stale, or another process already owns the active fence
- **THEN** Run MUST reject the operation idempotently without executing a command or disrupting the newer process
### Requirement: Updates are staged, health-checked, and rollback-safe
Platform and Run SHALL treat a Client Manager update as a same-installation transaction with explicit approval, compatible current-generation artifact selection, staged activation, bounded health confirmation, and a retained previous deployment.
#### Scenario: Compatible update becomes healthy
- **WHEN** an authorized operator approves a newer compatible artifact for the same server/profile/target and Run verifies, stages, activates, starts, and observes required health
- **THEN** Platform MUST set the new artifact/version as active, retain the prior deployment as rollback candidate, revoke the superseded component session, require new registration, and record real update progress and audit evidence
#### Scenario: Candidate update fails health
- **WHEN** download, checksum, activation, startup, registration, or health confirmation fails after an update begins
- **THEN** Run MUST preserve or restore the previous slot, Platform MUST project rolling_back and the real rollback result, and success MUST NOT be reported unless the restored deployment is active and healthy
#### Scenario: Invalid update or rollback is requested
- **WHEN** an artifact is revoked, from another server/profile/target, has a stale key generation, violates compatibility/version policy, or the previous slot no longer exists
- **THEN** Platform and Run MUST reject the request before activation and preserve the current deployment
### Requirement: Revocation and uninstall are safe and idempotent
Platform SHALL support session revocation and Run SHALL stop and uninstall a Client Manager without deleting server or shared files, while retaining Platform lifecycle and audit history.
#### Scenario: Key or session is revoked
- **WHEN** an authorized operator resets the component key, explicitly revokes the component session, reassigns ownership/endpoint, activates an update/rollback, or begins uninstall
- **THEN** Platform MUST revoke matching sessions, reject subsequent heartbeats, fence old artifacts/deployments as applicable, and show that rebuild/redeploy or registration is required
#### Scenario: Installed component is uninstalled
- **WHEN** an authorized operator confirms uninstall and Run completes the typed job
- **THEN** Run MUST stop the supervised process, remove only controlled active/previous/staging slots and local session/journal material for that installation, Platform MUST mark it uninstalled, and build/distribution/audit history MUST remain available
#### Scenario: Uninstall is repeated or interrupted
- **WHEN** uninstall is retried after partial cleanup, cancellation, lease expiry, or an already-uninstalled result
- **THEN** Run and Platform MUST converge idempotently without following links, escaping the configured workspace, or deleting game server/shared data
### Requirement: Lifecycle operations are durably audited and redacted
Platform SHALL record durable success, failure, and denial audits for build, deploy, register, start, stop, restart, update, rollback, revoke, retry, and uninstall using safe identifiers and bounded reasons.
#### Scenario: Lifecycle result is audited
- **WHEN** an operator, Run endpoint, or Client Manager performs or is denied a sensitive lifecycle action
- **THEN** the audit MUST include actor type and safe actor ID, server/profile/component, installation, job or artifact ID where applicable, operation, result, and redacted reason without raw keys, tokens, secret refs/values, credentials, paths, PIDs, sockets, endpoint addresses, DSNs, RCON passwords, or large output
### Requirement: Client Manager traffic remains isolated from Run channels
Client-manager registration, heartbeat, deployment transfer, process control, and optional game-client traffic SHALL remain separated from Run control heartbeat, job acknowledgement/result/cancel, log ingest, and artifact upload scheduling.
#### Scenario: Client-manager artifact transfer stalls
- **WHEN** a large or stalled client-manager download or component traffic stream is active
- **THEN** Run heartbeat, job ack/result/cancel polling, log spool upload, and unrelated artifact progress MUST continue independently within their bounded queues
### Requirement: Platform web provides a complete safe Client Manager workspace
platform_web SHALL provide authorized operators a Client Manager management workspace that reflects real backend state and preserves the existing black-mecha and magical-girl crystal-moonlight game-operations visual system.
#### Scenario: Operator manages the complete lifecycle
- **WHEN** an authorized operator opens Server Detail for a declared Client Manager
- **THEN** the UI MUST show safe profile/target/version/revision, build and artifact state, deployment/registration/online health, last seen, active/previous deployment, current job progress, permitted start/stop/restart, update/rollback, retry/redeploy after key reset, session revoke, and confirmed uninstall actions using real API projections
#### Scenario: Action is unavailable or destructive
- **WHEN** an action lacks permission, declaration, binding, online endpoint, capability, compatible artifact, current key generation, allowed lifecycle state, or confirmation
- **THEN** the UI MUST disable or hide it with a safe reason, require explicit confirmation for key reset/revoke/rollback/uninstall, preserve 401/403 handling, and MUST NOT fabricate progress or expose raw secrets, sessions, paths, PIDs, sockets, credentials, or endpoint addresses
@@ -0,0 +1,58 @@
## 1. Plugin Lifecycle Contracts
- [x] 1.1 Extend client-manager manifest and SDK types with safe version/revision, deployment, executable, lifecycle capability, health, compatibility, and update-policy declarations.
- [x] 1.2 Validate bounded relative executables, target/capability enums, timeouts, version rules, and reject arbitrary shell, traversal, raw secrets, endpoints, sockets, and credential-bearing declarations.
- [x] 1.3 Update SCUM and Minecraft example profiles, SDK bridge contracts, docs, and unsafe fixtures for complete lifecycle declarations and safe lifecycle requests/status.
- [x] 1.4 Add plugin manifest, SDK, and bridge tests covering accepted contracts, unsafe declarations, typed operations, and redaction.
## 2. Platform Durable Lifecycle Model
- [x] 2.1 Add domain/model/DTO types for installation states, action availability, deployment slots, health, desired/active/previous versions, lifecycle requests/results, component sessions, and replay nonces.
- [x] 2.2 Extend repository interfaces plus file and MySQL stores with durable client-manager installations, sessions, nonce fences, idempotent lookups, list/update/revoke operations, and safe persistence tests.
- [x] 2.3 Add validators for lifecycle transitions, action/target/version compatibility, deployment/control/update/rollback/uninstall inputs, component registration, heartbeat sequences, and redacted bounded results.
- [x] 2.4 Implement installation state transitions and terminal job projection using real durable job evidence, including idempotency, stale attempt/deployment/key fences, retryable failure, and active/previous deployment commits.
- [x] 2.5 Implement startup/periodic reconciliation for in-flight lifecycle jobs, expired sessions/nonces, heartbeat degraded/offline thresholds, restart recovery, and key-reset/endpoint-reassignment fencing.
## 3. Independent Component Identity
- [x] 3.1 Add canonical Client Manager registration signature and session contracts that are separate from Run control registration and job leases.
- [x] 3.2 Implement current component-key/generation signature verification, timestamp/nonce replay protection, ownership/artifact/target/revision/deployment/capability checks, hashed expiring session issuance, and denied audits.
- [x] 3.3 Implement component-session heartbeat authentication, monotonic sequence fencing, safe health projection, expiry/revocation, and explicit session revoke behavior.
- [x] 3.4 Add registration/session/heartbeat tests for current identity plus cross-owner/server/component/artifact/target/revision/key generation, expired, revoked, replayed, and Run-credential rejection paths.
## 4. Platform Job Orchestration and APIs
- [x] 4.1 Add typed client-manager deploy, control, update, rollback, and uninstall job kinds, payload validation, endpoint capability declarations, and safe job progress/result projection.
- [x] 4.2 Implement service authorization and action gating across actor role, server visibility, installed plugin/profile, runtime binding, endpoint online/capabilities, distribution availability/ownership/target/revision/key generation, and lifecycle state.
- [x] 4.3 Implement deploy/control/update/rollback/retry/revoke/uninstall services with durable intent-before-dispatch, idempotency, cancel/retry/stale-attempt handling, session fencing, and audit events.
- [x] 4.4 Add operator lifecycle summary/detail/action routes and component register/heartbeat routes with named DTOs, OpenAPI-style comments, session separation, safe errors, and API documentation.
- [x] 4.5 Add platform service/API tests covering owner/admin/service auth, 401/403, build-to-deploy, cross-boundary denial, restart reconcile, cancellation/retry/idempotency, health timeout, update rollback, key reset recovery, uninstall safety, auditing, and redaction.
## 5. Run Deployment and Supervision
- [ ] 5.1 Add Run protocol payloads and validation for client-manager deploy/control/update/rollback/uninstall, immutable fences, lifecycle progress/results, and endpoint capabilities.
- [ ] 5.2 Add a scoped client-manager workspace and durable local journal for installation slots, chunk offsets/checksums, deployment/attempt/key generations, idempotency outcomes, process state, and restart reconciliation.
- [ ] 5.3 Implement resumable artifact download, checksum verification, safe archive extraction, staging, atomic active/previous activation, cancellation checkpoints, and rejection of traversal/symlinks/device files/unexpected layouts.
- [ ] 5.4 Implement bounded declarative client-manager start/stop/restart/status supervision with fixed relative executable, safe timeouts, logical health, and no path/PID/socket projection.
- [ ] 5.5 Implement staged update health confirmation, automatic rollback, explicit rollback, stale/revoked generation rejection, re-entry after restart, and real phase reporting.
- [ ] 5.6 Implement idempotent safe uninstall that stops the process and deletes only controlled installation slots/journal/session material without following links or touching server/shared files.
- [ ] 5.7 Add Run tests for deploy resume/checksum, fences, cancel/retry/stale attempts, reconciliation, supervision, health failure rollback, uninstall safety, redaction, and channel isolation under stalled client-manager traffic.
## 6. platform_web Lifecycle Workspace
- [ ] 6.1 Add API types, schemas, client methods, safe action projections, polling/job progress integration, and tests for Client Manager lifecycle summaries and commands.
- [ ] 6.2 Build a rich Server Detail Client Manager workspace showing build/artifact, desired/active/previous version, deployment/registration/online health, last seen, real current job phases, retry guidance, and action availability.
- [ ] 6.3 Add start/stop/restart, deploy/redeploy, update/rollback, session revoke, key reset recovery, retry, and uninstall confirmation/error flows while preserving compact server action menus and both existing themes.
- [ ] 6.4 Add frontend tests and browser acceptance for full state/action coverage, real progress/failure recovery, destructive confirmations, 401/403 behavior, responsive layouts, theme preservation, and secret/path/PID/socket redaction.
## 7. Documentation and Verification
- [ ] 7.1 Update Platform, Run, plugins, SDK, platform_web, route, domain, protocol, and deployment docs with lifecycle states, security/session boundaries, operations, recovery, and explicit production non-goals.
- [ ] 7.2 Run plugin manifest validation, plugin SDK/tests/typecheck, Platform `go test -count=1 ./...`, independent Run `go test -count=1 ./...`, and focused race/restart checks where practical.
- [ ] 7.3 Run platform_web tests, typecheck, production build, and a browser walkthrough for the touched Server Detail and server action workflows in both visual themes.
- [ ] 7.4 Run shell/compose checks, `scripts/check-structure.sh`, and `git diff --check` in both the main repository and independent Run checkout.
- [ ] 7.5 Run `openspec validate implement-secure-client-manager-lifecycle --strict` and record all verification evidence below before marking implementation complete.
## Verification Evidence
Pending implementation and verification.
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-07-17
@@ -0,0 +1,75 @@
## 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.
@@ -0,0 +1,28 @@
## Why
Plugin manifests already describe runtime profiles, but platform registration discards those declarations and runtime bindings live only in an in-memory repository that is absent from durable snapshots. As a result, server creation cannot select a real profile or persist its logical bindings, and action gating incorrectly treats a server with no bindings as complete.
## What Changes
- Persist the complete safe runtime-profile contract from plugin manifest registration through domain, DTO, model, repositories, and durable file/MySQL snapshots.
- Add authorized server runtime-binding APIs and service operations for listing and updating one selected profile with validated logical values or secret references.
- Extend server creation to select a declared lifecycle profile and submit its initial logical bindings atomically with the instance workflow.
- Gate lifecycle and distribution actions on the selected profile and its required binding keys, returning only safe logical missing reasons.
- Add server creation and detail UI for choosing, reviewing, completing, and changing runtime bindings without displaying raw host paths, sockets, credentials, or secret storage details.
- Add plugin, platform, persistence, API, and frontend regression coverage for manifest projection, reload durability, invalid/missing binding rejection, action gating, and non-disclosure.
## Capabilities
### New Capabilities
- `runtime-profile-bindings`: Persist plugin-declared runtime profiles and provide server-scoped profile selection, logical binding management, secure projections, and action readiness.
### Modified Capabilities
## Impact
- `plugins/`: manifest/SDK validation and fixtures remain the source contract and gain persistence-oriented regression coverage where needed.
- `platform/`: runtime profile domain/DTO/model validation, store snapshots, server lifecycle creation, binding services/routes, authorization, and action gating.
- `platform_web/`: API contracts, create-server form, server-detail binding workflow, and focused tests.
- Public platform API requests and responses gain runtime profile and binding fields/routes; no raw machine location or credential data crosses into the web or plugin page boundary.
@@ -0,0 +1,86 @@
## ADDED Requirements
### Requirement: Platform persists plugin runtime profiles
The platform SHALL decode, validate, store, and return safe plugin runtime profiles covering server discovery, lifecycle, dependencies and install plans, log sources, transports, and client-manager declarations.
#### Scenario: Manifest registration survives reload
- **WHEN** an operator registers a valid plugin manifest with runtime profiles and the durable store is reopened
- **THEN** the installed plugin retains the same validated runtime-profile contract
#### Scenario: Unsafe runtime declaration is rejected
- **WHEN** a manifest runtime profile includes a raw host path, direct socket, credential, secret value, or arbitrary shell content
- **THEN** registration fails without persisting the unsafe declaration
### Requirement: Server selects a declared runtime profile
Each server runtime binding SHALL select a lifecycle profile declared by its installed plugin and SHALL derive required logical keys from that profile and its referenced runtime declarations.
#### Scenario: Valid profile selection
- **WHEN** an authorized operator selects a declared lifecycle profile for a server
- **THEN** the platform stores the server, plugin, profile, mode, derived required keys, and readiness state
#### Scenario: Undeclared profile is rejected
- **WHEN** a caller selects a profile key or logical binding key not declared by the server's plugin
- **THEN** the platform rejects the request without changing the stored binding
### Requirement: Runtime bindings are durable and authorized
The platform SHALL persist runtime bindings in memory, file, and MySQL-backed repository contracts and SHALL authorize server-scoped reads and owner/admin-scoped changes.
#### Scenario: Binding survives durable reload
- **WHEN** a valid binding is written through a durable store and the store is reopened
- **THEN** the selected profile and readiness metadata remain available for that server
#### Scenario: Unauthorized binding update
- **WHEN** a user who is neither platform admin nor server owner attempts to change a server binding
- **THEN** the platform denies the update and leaves the binding unchanged
### Requirement: Binding projections do not disclose runtime details
Runtime binding responses SHALL expose only profile metadata, declared logical keys, configured/secret flags, missing keys, status, and timestamps; they MUST NOT expose stored values, raw host paths, direct sockets, credentials, DSNs, or internal secret-storage locations.
#### Scenario: Secret reference is configured
- **WHEN** a stored logical binding uses a secret reference
- **THEN** the API reports that the logical key is configured and secret-backed without returning the reference value
#### Scenario: Missing logical binding is reviewed
- **WHEN** a required logical key is absent
- **THEN** the API and web console display the logical key and a safe configuration reason without storage details
### Requirement: Actions require a complete runtime binding
The platform SHALL gate lifecycle and runtime-dependent actions on the presence of a valid, complete runtime binding for the server's current plugin and selected profile.
#### Scenario: No binding is not complete
- **WHEN** a server has no runtime binding
- **THEN** lifecycle and runtime-dependent actions are disabled or rejected with a safe profile-not-configured reason
#### Scenario: Missing binding blocks dispatch
- **WHEN** a selected profile has one or more missing required logical keys
- **THEN** the platform does not dispatch the requested action and reports only the missing logical keys
#### Scenario: Complete binding permits normal validation
- **WHEN** the selected profile has all required logical keys configured
- **THEN** action handling proceeds to existing permission, endpoint capability, state, and idempotency checks
### Requirement: Server creation submits a real profile and bindings
The server creation workflow SHALL require a declared runtime profile and SHALL persist validated initial bindings before dispatching the install job.
#### Scenario: Complete create request
- **WHEN** an operator submits a server, declared profile, and all required logical bindings
- **THEN** the platform persists the server and binding and queues the install job with the selected profile context
#### Scenario: Incomplete create request
- **WHEN** a create request omits a required logical binding
- **THEN** the platform rejects creation before dispatch and identifies only the missing logical key
### Requirement: Operators can review and amend bindings
The management console SHALL derive profile choices and logical binding inputs from plugin declarations and SHALL provide a server-detail workflow to review or amend the selected profile and binding completeness.
#### Scenario: Create form submits selected contract
- **WHEN** an operator selects a plugin and profile and completes declared logical fields
- **THEN** the web client submits the actual profile key and binding map in the create workflow request
#### Scenario: Detail workflow updates bindings safely
- **WHEN** an authorized operator changes a stopped or draft server's profile or logical bindings
- **THEN** the console saves through the runtime-binding API and refreshes the redacted readiness projection
#### Scenario: UI does not render sensitive runtime data
- **WHEN** a binding response is rendered in create or detail workflows
- **THEN** the UI contains no secret value, raw path, socket, DSN, or internal storage reference
@@ -0,0 +1,32 @@
## 1. Runtime Profile Contract
- [x] 1.1 Add typed runtime profile declarations to platform domain, DTO, model, copy, and response projections.
- [x] 1.2 Validate profile keys, references, capabilities, safe strings, and cross-profile references during manifest registration.
- [x] 1.3 Persist registered runtime profiles through file/MySQL snapshots and prove reload behavior with tests.
## 2. Runtime Binding Persistence and API
- [x] 2.1 Add runtime binding snapshot persistence and repository reload coverage.
- [x] 2.2 Implement required logical-key derivation and binding validation against the selected plugin profile.
- [x] 2.3 Implement authorized list/update binding services and redacted DTO projections.
- [x] 2.4 Add documented server runtime-binding routes and API authorization/non-disclosure tests.
## 3. Lifecycle and Action Gating
- [x] 3.1 Extend server creation DTO/domain flow with a required profile key and initial logical bindings.
- [x] 3.2 Persist the binding before install dispatch and reject incomplete or undeclared create inputs.
- [x] 3.3 Gate start/stop and runtime-dependent actions on a present, current, complete binding with safe reasons.
- [x] 3.4 Add service/API regressions for missing/invalid bindings, successful dispatch, and reload survival.
## 4. Management Console
- [x] 4.1 Add frontend API types/client methods and form contracts for runtime profiles and redacted bindings.
- [x] 4.2 Connect plugin profile selection and declared logical binding inputs to the create-server workflow.
- [x] 4.3 Add a server-detail review/update workflow with safe missing reasons and no runtime value disclosure.
- [x] 4.4 Add frontend regressions proving real request submission, review/update behavior, and secret/path/socket non-disclosure.
## 5. Verification and Documentation
- [x] 5.1 Update platform, plugin, and web API/domain documentation for persisted profile and binding behavior without claiming later roadmap readiness.
- [x] 5.2 Run plugin manifest/SDK tests, platform Go tests, platform_web tests/typecheck/build, and risk-relevant run tests if run changes are required.
- [x] 5.3 Run `openspec validate persist-runtime-profiles-and-server-bindings --strict` and `scripts/check-structure.sh`, then record only evidence-backed completion.