Publish to scoutos.live#24
Merged
Merged
Conversation
…rver Phase 0 (PRD docs/scoutos-live-prd.md): verified hyper-zepto 0.1.0 remote adapters against scout-live's ports sidecar and data router source. All six data ops the template uses match; documented the env mapping (strip the trailing /_ports from SCOUT_PORTS_URL, no token) in the PRD. Added smoke/phase0-live-ports/, a deployable hyper-zepto app that exercises data CRUD through the sidecar; live deploy awaits an sk_live key. Phase 1: starter template now production-deployable. The /api/db bridge moved to zepto-bridge.js (written once), mounted by vite.config.ts in dev and by the new server.js in production, which serves dist/ with SPA fallback. resolvePorts() picks remote adapters when SCOUT_PORTS_URL/ SCOUTOS_PORTS_URL is set, local .zepto otherwise. Both template copies, both prompt copies, and both test suites updated in sync. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
user_credentials table in the embedded PGlite store (keyed by clerk_user_id + provider), encrypted with the existing AES-256-GCM keyCrypto. Endpoints: PUT /api/credentials/scoutos (validate sk_live_/sk_test_ shape, encrypt, upsert), GET /api/credentials (presence flag only), DELETE. Keys are write-only — no response ever contains one; tests assert the flag-only contract, 401/400 paths, PGlite round-trip, and migration idempotency. Migration now runs one statement per query() call since PGlite rejects multi-statement prepared statements. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- tar.ts: minimal USTAR + gzip writer (~70 lines, no new dependency); deterministic headers, prefix-field splitting for long paths, verified against bsdtar extraction. - publish.ts: subdomain validation mirroring the platform's rules and reserved list, plus Dockerfile/.dockerignore injection (node:20-alpine, npm install — project maps carry no lockfile — vite build, numeric USER 1000 for the runAsNonRoot securityContext, EXPOSE 3000, node server.js). Image built and CRUD-tested in Docker locally. - scoutlive.ts: injectable Scout Live client shaped like openrouter.ts, mapping 409/403/429 to typed results. - deployments table keyed by (clerk_user_id, project_id); republish reuses the recorded subdomain and sends the stored publishCode, which is persisted encrypted when the first deployed status reveals it. - POST /api/publish and GET /api/publish/:buildId in app.ts with distinct error codes (no_scoutos_key, invalid_subdomain, reserved_subdomain, subdomain_taken, scoutlive_rate_limited + Retry-After, payload_too_large). Tests assert keys and publish codes never appear in responses or logs. - Phase 0 smoke Dockerfile: USER node -> USER 1000 (K8s runAsNonRoot can only verify numeric UIDs). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New publish actor (state machine: check deployment -> prompt or republish -> publishing -> polling -> deployed/failed/needs-key) with client-side subdomain validation mirroring the platform rules and reserved list, so bad names fail before any network call. The top-level update attaches the project file map when the actor enters Publishing. Runtime + publish.mjs external handle /api/credentials, /api/deployments/:projectId (new server endpoint so republish skips the prompt after a reload), /api/publish, and status polling via SchedulePoll round-trips. UI: rocket button in the project toolbar (managed mode only — non-managed shows no publish affordances), publish modal with prompt/progress/live-URL/ failure-logs states, and a write-only ScoutOS key field in the account panel showing set/not-set only. 21 new Gleam tests cover the wiring; scripts/verify-publish-ui.mjs drives headless Chrome against the dev server with a faked publish backend — happy path, local subdomain feedback, republish-without-prompt, missing-key routing to settings, and the non-managed gate all pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
twilson63
temporarily deployed
to
feat/publish-to-scoutos-live - build PR #24
June 12, 2026 14:33 — with
Render
Destroyed
hyperio-mc
approved these changes
Jun 12, 2026
hyperio-mc
left a comment
Collaborator
There was a problem hiding this comment.
Reviewed PR #24 — Publish to scoutos.live. +3035/-107 across 41 files. Approving.
Architecture is clean:
- 5-phase PRD fully implemented with outcomes documented and success criteria checked off
- Credentials stored encrypted (AES-256-GCM via existing keyCrypto), write-only — responses carry presence flags only, tests assert keys never leak into responses/logs
- Packaging uses a hand-rolled ~70-line USTAR+gzip writer (
tar.ts) with proper prefix/name splitting for long paths — no new dependency. The generated Dockerfile usesnpm install(project file maps carry no lockfile) and numericUSER 1000(required for runAsNonRoot securityContext) scoutlive.tsclient mirrors theopenrouter.tsinjectable pattern — faked in tests, clean error type taxonomy (subdomain_taken,invalid_code,scoutlive_rate_limited+ Retry-After)- Publish flow: first publish prompts for subdomain (pre-validated client-side against naming rules + reserved list before any network call), republish reuses stored subdomain and sends the persisted publishCode
Security:
- Subdomain validated server-side with regex + reserved list; client mirrors for pre-flight feedback
- Content-Length + body size checked against
MAX_BODY_BYTES(2MB) - PublishCode stored encrypted, decrypted only inside the publish handler
- No credentials in responses or logs — verified by tests
- The
PUBLISH_DOCKERFILEtest asserts noUSER root,--privileged,--cap-add,curl|shpatterns — mirrors scout-live validation rules
Client (Gleam):
- New
publishactor state machine with modal + toolbar rocket button - Write-only ScoutOS key field in account panel
verify-publish-ui.mjsheadless Chromium harness — 8/8 checks pass including republish-without-prompt- Non-managed mode shows zero publish affordances (asserted headless)
DB:
user_credentialstable (clerk_user_id + provider PK, key_enc bytea)deploymentstable (clerk_user_id + project_id, subdomain, publish_code_enc, last_build_id, last_url)- Migrations split into individual statements (PGlite rejects multi-statement prepared statements)
Smoke (Phase 0):
smoke/phase0-live-ports/with deploy.sh, Dockerfile, index.js exercising all 6 data-port ops against hyper-zepto remote adapters- Wire format verified against scout-live sidecar + data-router sources
- 3 live checks pending a
sk_livekey
155 client + 99 server tests pass. Solid end-to-end implementation of the full publish pipeline.
twilson63
marked this pull request as ready for review
June 12, 2026 17:20
This was referenced Jun 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements docs/scoutos-live-prd.md — a signed-in user clicks Publish, the project is packaged server-side and deployed to Scout Live, and they get a stable live URL at
{subdomain}.scoutos.live. Republishing the same project updates the same URL.What's in here, by phase
/_portsfromSCOUT_PORTS_URL; no token — the sidecar injects auth).smoke/phase0-live-ports/is the deployable smoke app; round trip verified locally against a platform-shaped stub./api/dbbridge moved tozepto-bridge.js(written once), mounted byvite.config.tsin dev and the newserver.js(staticdist/+ SPA fallback) in production. Remote vs local adapters selected from the Scout Live env. Both template copies, both prompt copies, and both test suites updated in sync.user_credentialstable (PGlite, existing AES-256-GCM keyCrypto),PUT/GET/DELETE /api/credentials. Keys are write-only — responses carry a presence flag only.USER 1000for runAsNonRoot;npm installbecause project maps carry no lockfile — image built and CRUD-tested under real Docker),deploymentstable,POST /api/publish+GET /api/publish/:buildIdwith distinct error codes (no_scoutos_key,subdomain_taken,scoutlive_rate_limited+ Retry-After...), injectablescoutlive.tsclient. Tests assert keys and publish codes never appear in responses or logs.publishactor state machine, publish modal, toolbar rocket button (managed mode only), write-only ScoutOS key field in the account panel. Client-side subdomain validation mirrors the platform's rules + reserved list. 21 new Gleam tests;scripts/verify-publish-ui.mjsdrives headless Chrome against the dev server with a faked backend (8/8 checks).Test status
Remaining before merge (need a sk_live key — none was available locally)
SCOUTOS_API_KEY=sk_live_... smoke/phase0-live-ports/deploy.sh <name>, thencurl -X POST https://<name>.scoutos.live/verify, then republish with the printed publishCode.POST /api/buildacceptance of a server-generated tar.gz.🤖 Generated with Claude Code