diff --git a/showcase/scripts/__tests__/fixtures/railway-envs.golden.json b/showcase/scripts/__tests__/fixtures/railway-envs.golden.json index dbb1ea7507..f248c0e44c 100644 --- a/showcase/scripts/__tests__/fixtures/railway-envs.golden.json +++ b/showcase/scripts/__tests__/fixtures/railway-envs.golden.json @@ -95,6 +95,15 @@ "repoName": "showcase-harness" } }, + "harness-workers": { + "staging": { + "instanceId": "362c1e37-5f40-45f2-ac7b-0e5adac565f8", + "domain": null, + "probe": false, + "driver": "harness", + "repoName": "showcase-harness" + } + }, "pocketbase": { "prod": { "instanceId": "1ee376e2-13f2-4464-801e-d0aa0bf76532", @@ -239,15 +248,6 @@ "repoName": "showcase-google-adk" } }, - "showcase-harness-worker": { - "staging": { - "instanceId": "362c1e37-5f40-45f2-ac7b-0e5adac565f8", - "domain": null, - "probe": false, - "driver": "harness", - "repoName": "showcase-harness" - } - }, "showcase-langgraph-fastapi": { "prod": { "instanceId": "105b7e01-acd0-48e2-9a09-541e2103e8d2", diff --git a/showcase/scripts/__tests__/railway-envs.golden.test.ts b/showcase/scripts/__tests__/railway-envs.golden.test.ts index 50761e5c7e..bdbbe631fc 100644 --- a/showcase/scripts/__tests__/railway-envs.golden.test.ts +++ b/showcase/scripts/__tests__/railway-envs.golden.test.ts @@ -22,12 +22,12 @@ * placeholder/borrowed values it removes (visible as the only diff to this * fixture in the refactor commit): * - * 1. `showcase-harness-worker.prod` — DROPPED. The old schema required a + * 1. `harness-workers.prod` — DROPPED. The old schema required a * distinct prod UUID per entry, so the worker (a staging-only service) * carried its own serviceId mirrored as a non-functional prod * placeholder that was never dereferenced. The env-map schema simply * omits the prod env. - * 2. `showcase-harness-worker.staging`, `harness-legacy.{prod,staging}` + * 2. `harness-workers.staging`, `harness-legacy.{prod,staging}` * `domain` — null (was a BORROWED control-plane host). These three * envs are domainless workers with `probe:false`, so `domainFor` is * never called for them at runtime; the old schema's `domains{}` @@ -96,7 +96,7 @@ function buildSnapshot(): Record< // NOT a hardcoded ["prod","staging"]. This is what proves the refactor // preserved resolution for every REAL (service, env) pair while // dropping the old schema's non-functional placeholder env entries - // (e.g. showcase-harness-worker's mirrored prod instanceId). + // (e.g. harness-workers's mirrored prod instanceId). for (const env of envsFor(name)) { out[name][env] = resolveServiceEnv(name, env); } diff --git a/showcase/scripts/__tests__/verify-railway-image-refs.test.ts b/showcase/scripts/__tests__/verify-railway-image-refs.test.ts index 97e326d336..ba80829990 100644 --- a/showcase/scripts/__tests__/verify-railway-image-refs.test.ts +++ b/showcase/scripts/__tests__/verify-railway-image-refs.test.ts @@ -23,12 +23,12 @@ describe("ServiceEntry gateIgnore field", () => { it("is optional on the type and defaults to falsy when unset", () => { // Every real SSOT entry has gateIgnore unset (undefined / falsy), // EXCEPT two deliberately gateIgnore:true entries: the staging-only - // `showcase-harness-worker` pool-fleet worker (no public domain, does not + // `harness-workers` pool-fleet worker (no public domain, does not // fit the symmetric dual-env shape the gate validates) and the interim // `harness-legacy` fleet-migration bridge (runs a pinned out-of-band // digest, not the canonical :latest/@sha256 shape). See their SSOT // entries in railway-envs.ts for the rationale. - const GATE_IGNORED = new Set(["showcase-harness-worker", "harness-legacy"]); + const GATE_IGNORED = new Set(["harness-workers", "harness-legacy"]); for (const [name, entry] of Object.entries(SERVICES)) { const gi = (entry as ServiceEntry).gateIgnore; if (GATE_IGNORED.has(name)) { @@ -246,11 +246,11 @@ describe("WS-C: all gate-managed services gateValidated, with correct overrides" it("marks every gate-managed service gateValidated (no Phase-2 holdouts)", () => { // Two intentional gateValidated:false entries: the staging-only - // `showcase-harness-worker` (gateIgnore:true — no public domain) and the + // `harness-workers` (gateIgnore:true — no public domain) and the // interim `harness-legacy` fleet-migration bridge (gateIgnore:true — runs // a pinned out-of-band digest). Every OTHER service must be // gateValidated:true. - const GATE_IGNORED = new Set(["showcase-harness-worker", "harness-legacy"]); + const GATE_IGNORED = new Set(["harness-workers", "harness-legacy"]); const unvalidated = Object.entries(SERVICES) .filter( ([name, entry]) => !entry.gateValidated && !GATE_IGNORED.has(name), diff --git a/showcase/scripts/railway-envs.generated.json b/showcase/scripts/railway-envs.generated.json index 96aa2fb259..743913a5c3 100644 --- a/showcase/scripts/railway-envs.generated.json +++ b/showcase/scripts/railway-envs.generated.json @@ -136,6 +136,27 @@ "driver": "harness" } }, + { + "name": "harness-workers", + "serviceId": "c2aa8a0b-350e-4b76-8541-3012dfac41d0", + "prodInstanceId": "c2aa8a0b-350e-4b76-8541-3012dfac41d0", + "stagingInstanceId": "362c1e37-5f40-45f2-ac7b-0e5adac565f8", + "ciBuilt": false, + "gateValidated": false, + "repoNameOverride": { + "prod": "showcase-harness", + "staging": "showcase-harness" + }, + "domains": { + "staging": "harness-staging-2ee4.up.railway.app", + "prod": "showcase-harness-production.up.railway.app" + }, + "probe": { + "staging": false, + "prod": false, + "driver": "harness" + } + }, { "name": "pocketbase", "serviceId": "ba11e854-d695-4738-9a45-2b0776788824", @@ -306,27 +327,6 @@ "driver": "agent" } }, - { - "name": "showcase-harness-worker", - "serviceId": "c2aa8a0b-350e-4b76-8541-3012dfac41d0", - "prodInstanceId": "c2aa8a0b-350e-4b76-8541-3012dfac41d0", - "stagingInstanceId": "362c1e37-5f40-45f2-ac7b-0e5adac565f8", - "ciBuilt": false, - "gateValidated": false, - "repoNameOverride": { - "prod": "showcase-harness", - "staging": "showcase-harness" - }, - "domains": { - "staging": "harness-staging-2ee4.up.railway.app", - "prod": "showcase-harness-production.up.railway.app" - }, - "probe": { - "staging": false, - "prod": false, - "driver": "harness" - } - }, { "name": "showcase-langgraph-fastapi", "serviceId": "06cccb5c-59f4-46b5-8adc-7113e77011a4", diff --git a/showcase/scripts/railway-envs.test.ts b/showcase/scripts/railway-envs.test.ts index d82e8df33e..dee0a8b99e 100644 --- a/showcase/scripts/railway-envs.test.ts +++ b/showcase/scripts/railway-envs.test.ts @@ -452,10 +452,39 @@ describe("railway-envs SSOT — domains + probe", () => { it("envsFor lists exactly the envs a service declares", () => { // Dual-env services declare both; the staging-only worker declares one. expect(envsFor("aimock")).toEqual(["prod", "staging"]); - expect(envsFor("showcase-harness-worker")).toEqual(["staging"]); + expect(envsFor("harness-workers")).toEqual(["staging"]); expect(envsFor("harness-legacy")).toEqual(["prod", "staging"]); }); + it("harness-workers is a staging-only, domainless, probe-disabled worker", () => { + // The pool-fleet worker is the canonical single-env / domainless shape + // the env-map schema enables (the old schema forced a placeholder prod + // instanceId + a borrowed control-plane domain). Pin every facet: + const worker = SERVICES["harness-workers"]; + // Staging-only: no prod env at all (no placeholder). + expect(worker.environments.prod).toBeUndefined(); + expect(worker.environments.staging).toBeDefined(); + // Domainless: the staging env omits a public host entirely (it is a + // queue consumer, not HTTP-exposed). domainFor MUST throw rather than + // return a borrowed host. + expect(worker.environments.staging.domain).toBeUndefined(); + expect(() => domainFor("harness-workers", "staging")).toThrow( + /malformed\/missing staging domain/, + ); + // Probe disabled (covered by the control-plane harness probe + the + // Railway-internal healthcheck). + expect(probeEnabled("harness-workers", "staging")).toBe(false); + // Runs the shared showcase-harness image; not separately CI-built; kept + // out of both gate directions via gateIgnore. + expect(worker.environments.staging.repoName).toBe("showcase-harness"); + expect(worker.ciBuilt).toBe(false); + expect(worker.gateIgnore).toBe(true); + expect(worker.serviceId).toBe("c2aa8a0b-350e-4b76-8541-3012dfac41d0"); + expect(worker.environments.staging.instanceId).toBe( + "362c1e37-5f40-45f2-ac7b-0e5adac565f8", + ); + }); + it("domainFor returns the no-scheme host for known service+env", () => { expect(domainFor("docs", "staging")).toBe("docs.staging.copilotkit.ai"); expect(domainFor("docs", "prod")).toBe("docs.copilotkit.ai"); diff --git a/showcase/scripts/railway-envs.ts b/showcase/scripts/railway-envs.ts index 404f9b41b2..d964217c72 100644 --- a/showcase/scripts/railway-envs.ts +++ b/showcase/scripts/railway-envs.ts @@ -340,7 +340,11 @@ export const SERVICES: Record< }, }, }, - "showcase-harness-worker": { + // SSOT key MUST equal the EXACT Railway service name. The Railway service + // is `harness-workers` (PLURAL) — the image-ref gate matches SSOT keys to + // Railway service names verbatim, so the key here is `harness-workers`, + // not `showcase-harness-worker`. + "harness-workers": { serviceId: "c2aa8a0b-350e-4b76-8541-3012dfac41d0", // STAGING-ONLY worker (pool-fleet cutover). There is no prod // serviceInstance — the pool-fleet runs in staging only for now. Under @@ -365,7 +369,7 @@ export const SERVICES: Record< // existing `harness` (control-plane) service runs — it is NOT a // separately-built image. The single `showcase-harness` build slot in // showcase_build.yml produces the image both services consume; there is - // no `showcase-harness-worker` build slot. Hence ciBuilt:false. The + // no `harness-workers` build slot. Hence ciBuilt:false. The // repoName override points at `showcase-harness` so the image-ref shape // resolves correctly if the gate ever validates it. // @@ -411,7 +415,7 @@ export const SERVICES: Record< // gateIgnore: deliberately-untracked for the image-ref gate. This // interim service runs a pinned digest (not the canonical :latest / // @sha256 shape the gate enforces) and is short-lived. Mirrors - // showcase-harness-worker exactly (minus the worker's single-env shape — + // harness-workers exactly (minus the worker's single-env shape — // harness-legacy DOES exist in both envs). gateValidated: false, gateIgnore: true,