fix(showcase): reconcile harness-workers SSOT key with the Railway service name

The pool-fleet worker's Railway service is named `harness-workers` (PLURAL),
but the SSOT keyed it `showcase-harness-worker` (singular). The image-ref gate
matches SSOT keys to Railway service names verbatim, so the gate reported
`harness-workers` as an untracked Railway service AND the stale singular key
matched nothing. Rename the SSOT key (and every test/fixture reference) to the
exact Railway name `harness-workers`.

It stays the staging-only, domainless, probe-disabled worker that runs the
shared `showcase-harness` image: serviceId c2aa8a0b-…, staging instance
362c1e37-…, ciBuilt:false, gateIgnore:true, no build slot (so no dispatchName),
single `staging` env with no domain. Add a focused test pinning that shape.
Counts are unchanged (29 services / 26 CI_BUILT) — this is a rename, not an
addition; both harness workers already existed on main.

Verified LOCALLY against Railway: verify-railway-image-refs reports
`54 env-scoped instances verified (2 skipped)` — 0 violations, 0 missing, 0
untracked (harness-workers reconciled, harness-workers + harness-legacy the 2
gateIgnore'd skips). emit --check zero drift, Ruby parity green (borrowed
.up.railway.app host is parity-excluded), full scripts suite + typecheck green.
This commit is contained in:
Jordan Ritter
2026-06-06 07:51:00 -07:00
parent 9b76a2d2a4
commit da03e1626a
6 changed files with 74 additions and 41 deletions
@@ -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",
@@ -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);
}
@@ -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),
+21 -21
View File
@@ -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",
+30 -1
View File
@@ -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");
+7 -3
View File
@@ -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,