Review feedback + follow-up on the review-stamps feature:
- The 409 trust gate now distinguishes reason 'unreviewed' (community
code not vetted) from 'unverified' (no pinned checksum, possible even
for first_party), each with an accurate message; the risk dialog
carries the reason and matches its title/body to the actual case
instead of always claiming 'unreviewed community extension'
- Unreviewed registry entries are now developer-stage content: hidden
from the Marketplace catalog and refused at the install endpoint
(404) unless the panel runs in a development context (Flask debug /
testing config, or the dev_mode setting). Hidden means not
installable; the 409 acknowledgment flow only exists where they list
- dev_mode setting description mentions the Marketplace effect
- tests: reason assertions on both 409 cases; catalog visibility and
404-on-hidden-install coverage (38 passing)
The registry index (schema v3) can now carry a review object per entry
(reviewer, date, sha256, optional commit/notes) asserting that someone
inspected the exact artifact being listed. The stamp is bound to the
artifact hash: review.sha256 must equal the entry sha256, so any change
to the released zip invalidates the review.
- registry_service: keep review through normalization (validated),
derive trust per entry: first_party, reviewed (stamp matches the
pinned hash), or unreviewed
- marketplace install route: unreviewed (or sha256-less) entries return
409 requires_acknowledgment unless the admin posts
acknowledge_risk: true; acknowledged installs record trust in the
audit log. Consent gate, consistent with ADR 0002
- Marketplace UI: Reviewed badge (reviewer + date tooltip) and
Unreviewed warning badge on registry cards, plus a risk confirmation
modal that retries the install with acknowledge_risk
- tests: trust derivation and the install gate (409 + acknowledge path)
- docs: EXTENSIONS_REGISTRY.md documents the field and the flow
Plan 55 Phase 1. The sweep found zero vulnerabilities: every raw-HTML sink is
safe by construction (escape-first SQL/file highlighters, sanitizeSvgInner for
extension icons, the escape-then-allowlist markdown renderer) and the backend
has no |safe/Markup(/render_template_string.
To keep it that way: scripts/check-html-sinks.mjs (wired into npm run lint) and a
Python mirror test (test_html_sink_sweep.py, runs in the backend suite) fail on
any raw-HTML sink that neither references an allowlisted sanitizer nor carries a
'sink-safe:' annotation. Standardized annotations on the three variable-fed sinks
(markdown, SQL editor, marketplace icon) and documented the rule in SECURITY.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The index=True columns emitted separate CREATE INDEX statements that, once the
ext_serverkit_minecraft_* models register on the shared test metadata mid-session
(when the install test runs), collided during a later test's create_app schema
sync — aborting create_all before system_settings and cascading into errors
across the suite. A scaffold needs no secondary indexes; drop them (they can be
reintroduced with proper migration handling when the Docker create-flow lands).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
First vertical of the game-server family (plan 53 Phase 0-1, backend core):
- gamekit/ — the reusable, dependency-free framework (D1): a Source RCON client
(framing/auth/timeouts), a container-log event parser (join/leave/started/
stopped/crashed, Java + Bedrock), a server.properties config-form builder over
a JSON sidecar, and the save-aware hot-backup sequence (save-off -> flush ->
zip -> save-on, always resumes) with retention.
- server_properties_meta.json sidecar (Phase 0 authoring task): grouped, labelled,
restart-required metadata for the settings form.
- Extension scaffold: manifest (category 'games', models:register), ext_serverkit_
minecraft_* models keyed by application (D9), and the /api/v1/minecraft blueprint
(list/get, RCON proxy, backups).
Unit-tested (fake RCON socket, fixture logs, temp worlds) + install-wiring-tested
(install registers routes + creates ext tables + uninstall). The create/console/
UI/Docker flow is the dev-box phase and is intentionally not claimed here.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Split the WP-specific jail service into a generic engine + thin WP wrappers:
enable_jail(key, filter_name, filter_content, logpath, port, ...), a generic
ensure_filter(name, content), remove_jail(key), and jail_name_for_key(). The
jail template's port is now parametrized. enable_wp_jail/disable_jail/get_status
keep byte-identical behavior (existing WP suites unchanged and green), so the WP
extension's callers are untouched. This gives other verticals (game-server
login/RCON jails, plan 53) a reusable per-service brute-force jail seam.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The audit overstated the WP frontend as contributed by the extension; in the
tree the extension index.jsx is still a re-export shell over core pages/
components/SCSS. Corrected to match reality before plan 52 Phase 4 moves it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 1 housekeeping for the full-isolation round:
- Move DatabaseSnapshot + SyncJob out of models/wordpress_site.py into a neutral
models/db_snapshot.py (pure module move, tables unchanged); wordpress_site.py
re-exports both for import compatibility.
- Untrack the stale committed live copies of serverkit-gui (sibling repo, no
in-tree source) and serverkit-email (source of truth: builtin-extensions/
serverkit-email/), matching how every other extracted builtin is already
gitignored. Files stay on disk; only removed from the index.
- .gitignore: add the email + gui frontend/backend artifact paths.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wire the server half of runtime-frontend delivery: get_active_contributions()
now emits panel sdk_version and a 'frontends' descriptor map {slug: {entry,
hashes, sdk_version}} for every active extension whose frontend_entry is a .mjs
ESM bundle. The bundle sha256 is pinned at install time into the plugin config
under the reserved _frontend_hashes key (no migration — reuses config_json);
update_plugin_config() and the config API preserve/hide reserved keys. Add the
extensions.runtime_frontend kill switch (default on). The client loader already
consumed this shape, so an installed extension's UI now renders on a pre-built
panel with no Vite rebuild. Un-skips the restored plan-25 recovery suites.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Deploy Console adds test_run_log_stream.py and test_deploy_console.py
(+77 collected tests). Full backend suite green: 2454 passed, 53 skipped.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add docs/DEPLOY_CONSOLE.md (the feature + architecture), an architecture section
in docs/ARCHITECTURE.md, a CHANGELOG (Unreleased) entry, and move the Deploy
Console into the ROADMAP's Recently Completed list with its Active Development
checkboxes ticked.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The /deployments index is now a single table whose rows open the full-page Deploy
Console (the old inline detail pane is removed). A small global pill appears while
any deploy is pending/running and links straight to its console — so you can
navigate away mid-deploy and always find your way back (plan 51 §1.3, task 26/28).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Template install (InstallModal), New Service repo create, Service ▸ Deploy Latest
Commit, and the Build tab's Deploy Now all now navigate to /deployments/<jobId>
on job start instead of embedding progress or dropping the user on a page with no
watcher (plan 51 Phase 2). The template modal's embedded progress block is
removed; the deploy API client gains retryDeploymentJob and a plan option.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New /deployments/:jobId page (plan 51 §1.1, Phase 3): humanized title, status
pill, live elapsed timer and step counter; a step rail (✓ with per-step duration,
spinner, ✗, ○) that scrolls the log pane to a step; a dark monospace log pane
with follow/wrap/timestamps/level-filter/search/copy/download and a "Jump to live"
chip; a pinned error card (failed step, real failure tail, plain-language hint,
Retry / Copy / Ask-AI); a success banner (duration, step timings, Open app / View
service / View runtime logs); plus queued, degraded (poll-fallback) and history
states. Design-system SCSS only, bespoke .deploy-console__* BEM (D12).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add the deploy_log/deploy_status handlers and subscribeDeploy/unsubscribeDeploy
to the socket singleton, and useDeployJobStream — one hook for the console, the
compact widget, the LogsTab banner and the topbar pill. It boots from a full
snapshot, prefers the live socket channel, de-dupes log rows by DB id, re-syncs
with after_id on every (re)connect, and falls back to 2s polling when sockets
are unavailable (plan 51 D2/§5.5). DeploymentJobProgress now consumes the hook
and links to the full console.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
POST /builds/apps/<id>/deploy now enqueues an app_deploy job and returns 202 +
{deploy_job_id} so the UI opens the live Deploy Console; ?wait=true preserves the
old synchronous contract for CLI/tests (plan 51 D7). Deploy options (no_cache,
version_tag) ride the job plan through to the async runner.
Add POST /deployment-jobs/<id>/retry: clones a FAILED job (both kinds) and
enqueues a fresh run on a new console URL (D8). Extend GET /deployment-jobs/<id>
with ?plan=true so the console can render the step rail.
Tests cover the socket emit helpers (room + payload), the subscribe_deploy auth
guard, the 202/wait contract, retry guards + clone fields, plan-in-GET, and the
streaming-compose variant incl. a failure persisting a real stdout-bearing tail.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Introduce app/services/run_log_service.py — the single crash-proof write seam
for all deploy-path logging (plan 51 D4/D5/D6/D10). It batches writes (one DB
commit + one socket emit per flush instead of commit-per-line), keeps a truthful
80-line in-memory failure tail, records per-step timings, sanitizes ANSI/\r,
caps persisted rows at 5000, and matches a plain-language failure hint. The
DeploymentPlanRunner now funnels log/step/close through it.
Add DockerService.compose_up_streaming (Popen + --ansi never --progress plain,
line callback) so template installs stream the real pull/build transcript live
and a failure persists a meaningful tail — the biggest reason install errors
were unreadable. The runner's local compose.up step adopts it.
Replace the orphaned build_* socket surface (zero subscribers, grep-verified)
with the Deploy Console channel: emit_deploy_log / emit_deploy_status to room
deploy_{job_id}, plus subscribe_deploy/unsubscribe_deploy (authenticated).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The roadmap still said v1.6.0 / last-updated March 2026 while dev sits at
v1.7.52. Reframe the status note (actively developed, three tracks), refresh
Recently Completed with the actual 1.7.x highlights, add an Active Development
section (deploy console, extension platform, game server extensions, agent
streaming, release engineering), fix the milestones table incoherence, and
retitle the long-completed 'Upcoming Development' block.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The standalone card below the servers list was UI drift — the link flow
('Link panel') now lives inside the existing Add Server modal next to
'Connection string' and 'Pair code', using the drawer's own form styles.
Adds the Trendshift repository badge to the main README and the Spanish, Portuguese, and Simplified Chinese README files so project visibility/trend metrics are shown consistently across all language entry points.
The deployment runner forwards the plan's compose_file in the project_path
field; the embedded agent now splits file paths into (dir, compose_file)
before calling DockerService, fixing '[Errno 20] Not a directory' on
remote template installs.
Lets a panel link itself to a master ServerKit panel as a worker WITHOUT
installing the standalone Go agent: the panel registers via the master's
existing token flow, then runs an embedded agent client (HMAC auth + HTTP
long-poll transport) that dispatches agent commands (file:write,
docker:compose:up/ps/logs, system:info/metrics) onto its own local
services. The master needs zero changes — a linked panel is just another
agent-backed Server row with scoped, revocable credentials.
- linked_panel_config table (migration 077) + Fernet-encrypted secret
- embedded agent client: connect/poll/result loop, heartbeat metrics,
per-command threads, allowed-roots guard on file:write
- API: GET/POST/DELETE /api/v1/linked-panel (+ startup resume hook)
- Servers page: LinkedPanelCard (link form, live status, unlink)
A template file with a relative path (prompture-hub's Dockerfile) was
bind-mounted as './Dockerfile:Dockerfile', failing compose up with
'invalid mount config: mount path must be absolute'.