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
@@ -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.