Skip to content

Commit da03e16

Browse files
committed
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.
1 parent 9b76a2d commit da03e16

6 files changed

Lines changed: 74 additions & 41 deletions

File tree

showcase/scripts/__tests__/fixtures/railway-envs.golden.json

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,15 @@
9595
"repoName": "showcase-harness"
9696
}
9797
},
98+
"harness-workers": {
99+
"staging": {
100+
"instanceId": "362c1e37-5f40-45f2-ac7b-0e5adac565f8",
101+
"domain": null,
102+
"probe": false,
103+
"driver": "harness",
104+
"repoName": "showcase-harness"
105+
}
106+
},
98107
"pocketbase": {
99108
"prod": {
100109
"instanceId": "1ee376e2-13f2-4464-801e-d0aa0bf76532",
@@ -239,15 +248,6 @@
239248
"repoName": "showcase-google-adk"
240249
}
241250
},
242-
"showcase-harness-worker": {
243-
"staging": {
244-
"instanceId": "362c1e37-5f40-45f2-ac7b-0e5adac565f8",
245-
"domain": null,
246-
"probe": false,
247-
"driver": "harness",
248-
"repoName": "showcase-harness"
249-
}
250-
},
251251
"showcase-langgraph-fastapi": {
252252
"prod": {
253253
"instanceId": "105b7e01-acd0-48e2-9a09-541e2103e8d2",

showcase/scripts/__tests__/railway-envs.golden.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@
2222
* placeholder/borrowed values it removes (visible as the only diff to this
2323
* fixture in the refactor commit):
2424
*
25-
* 1. `showcase-harness-worker.prod` — DROPPED. The old schema required a
25+
* 1. `harness-workers.prod` — DROPPED. The old schema required a
2626
* distinct prod UUID per entry, so the worker (a staging-only service)
2727
* carried its own serviceId mirrored as a non-functional prod
2828
* placeholder that was never dereferenced. The env-map schema simply
2929
* omits the prod env.
30-
* 2. `showcase-harness-worker.staging`, `harness-legacy.{prod,staging}`
30+
* 2. `harness-workers.staging`, `harness-legacy.{prod,staging}`
3131
* `domain` — null (was a BORROWED control-plane host). These three
3232
* envs are domainless workers with `probe:false`, so `domainFor` is
3333
* never called for them at runtime; the old schema's `domains{}`
@@ -96,7 +96,7 @@ function buildSnapshot(): Record<
9696
// NOT a hardcoded ["prod","staging"]. This is what proves the refactor
9797
// preserved resolution for every REAL (service, env) pair while
9898
// dropping the old schema's non-functional placeholder env entries
99-
// (e.g. showcase-harness-worker's mirrored prod instanceId).
99+
// (e.g. harness-workers's mirrored prod instanceId).
100100
for (const env of envsFor(name)) {
101101
out[name][env] = resolveServiceEnv(name, env);
102102
}

showcase/scripts/__tests__/verify-railway-image-refs.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@ describe("ServiceEntry gateIgnore field", () => {
2323
it("is optional on the type and defaults to falsy when unset", () => {
2424
// Every real SSOT entry has gateIgnore unset (undefined / falsy),
2525
// EXCEPT two deliberately gateIgnore:true entries: the staging-only
26-
// `showcase-harness-worker` pool-fleet worker (no public domain, does not
26+
// `harness-workers` pool-fleet worker (no public domain, does not
2727
// fit the symmetric dual-env shape the gate validates) and the interim
2828
// `harness-legacy` fleet-migration bridge (runs a pinned out-of-band
2929
// digest, not the canonical :latest/@sha256 shape). See their SSOT
3030
// entries in railway-envs.ts for the rationale.
31-
const GATE_IGNORED = new Set(["showcase-harness-worker", "harness-legacy"]);
31+
const GATE_IGNORED = new Set(["harness-workers", "harness-legacy"]);
3232
for (const [name, entry] of Object.entries(SERVICES)) {
3333
const gi = (entry as ServiceEntry).gateIgnore;
3434
if (GATE_IGNORED.has(name)) {
@@ -246,11 +246,11 @@ describe("WS-C: all gate-managed services gateValidated, with correct overrides"
246246

247247
it("marks every gate-managed service gateValidated (no Phase-2 holdouts)", () => {
248248
// Two intentional gateValidated:false entries: the staging-only
249-
// `showcase-harness-worker` (gateIgnore:true — no public domain) and the
249+
// `harness-workers` (gateIgnore:true — no public domain) and the
250250
// interim `harness-legacy` fleet-migration bridge (gateIgnore:true — runs
251251
// a pinned out-of-band digest). Every OTHER service must be
252252
// gateValidated:true.
253-
const GATE_IGNORED = new Set(["showcase-harness-worker", "harness-legacy"]);
253+
const GATE_IGNORED = new Set(["harness-workers", "harness-legacy"]);
254254
const unvalidated = Object.entries(SERVICES)
255255
.filter(
256256
([name, entry]) => !entry.gateValidated && !GATE_IGNORED.has(name),

showcase/scripts/railway-envs.generated.json

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,27 @@
136136
"driver": "harness"
137137
}
138138
},
139+
{
140+
"name": "harness-workers",
141+
"serviceId": "c2aa8a0b-350e-4b76-8541-3012dfac41d0",
142+
"prodInstanceId": "c2aa8a0b-350e-4b76-8541-3012dfac41d0",
143+
"stagingInstanceId": "362c1e37-5f40-45f2-ac7b-0e5adac565f8",
144+
"ciBuilt": false,
145+
"gateValidated": false,
146+
"repoNameOverride": {
147+
"prod": "showcase-harness",
148+
"staging": "showcase-harness"
149+
},
150+
"domains": {
151+
"staging": "harness-staging-2ee4.up.railway.app",
152+
"prod": "showcase-harness-production.up.railway.app"
153+
},
154+
"probe": {
155+
"staging": false,
156+
"prod": false,
157+
"driver": "harness"
158+
}
159+
},
139160
{
140161
"name": "pocketbase",
141162
"serviceId": "ba11e854-d695-4738-9a45-2b0776788824",
@@ -306,27 +327,6 @@
306327
"driver": "agent"
307328
}
308329
},
309-
{
310-
"name": "showcase-harness-worker",
311-
"serviceId": "c2aa8a0b-350e-4b76-8541-3012dfac41d0",
312-
"prodInstanceId": "c2aa8a0b-350e-4b76-8541-3012dfac41d0",
313-
"stagingInstanceId": "362c1e37-5f40-45f2-ac7b-0e5adac565f8",
314-
"ciBuilt": false,
315-
"gateValidated": false,
316-
"repoNameOverride": {
317-
"prod": "showcase-harness",
318-
"staging": "showcase-harness"
319-
},
320-
"domains": {
321-
"staging": "harness-staging-2ee4.up.railway.app",
322-
"prod": "showcase-harness-production.up.railway.app"
323-
},
324-
"probe": {
325-
"staging": false,
326-
"prod": false,
327-
"driver": "harness"
328-
}
329-
},
330330
{
331331
"name": "showcase-langgraph-fastapi",
332332
"serviceId": "06cccb5c-59f4-46b5-8adc-7113e77011a4",

showcase/scripts/railway-envs.test.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -452,10 +452,39 @@ describe("railway-envs SSOT — domains + probe", () => {
452452
it("envsFor lists exactly the envs a service declares", () => {
453453
// Dual-env services declare both; the staging-only worker declares one.
454454
expect(envsFor("aimock")).toEqual(["prod", "staging"]);
455-
expect(envsFor("showcase-harness-worker")).toEqual(["staging"]);
455+
expect(envsFor("harness-workers")).toEqual(["staging"]);
456456
expect(envsFor("harness-legacy")).toEqual(["prod", "staging"]);
457457
});
458458

459+
it("harness-workers is a staging-only, domainless, probe-disabled worker", () => {
460+
// The pool-fleet worker is the canonical single-env / domainless shape
461+
// the env-map schema enables (the old schema forced a placeholder prod
462+
// instanceId + a borrowed control-plane domain). Pin every facet:
463+
const worker = SERVICES["harness-workers"];
464+
// Staging-only: no prod env at all (no placeholder).
465+
expect(worker.environments.prod).toBeUndefined();
466+
expect(worker.environments.staging).toBeDefined();
467+
// Domainless: the staging env omits a public host entirely (it is a
468+
// queue consumer, not HTTP-exposed). domainFor MUST throw rather than
469+
// return a borrowed host.
470+
expect(worker.environments.staging.domain).toBeUndefined();
471+
expect(() => domainFor("harness-workers", "staging")).toThrow(
472+
/malformed\/missing staging domain/,
473+
);
474+
// Probe disabled (covered by the control-plane harness probe + the
475+
// Railway-internal healthcheck).
476+
expect(probeEnabled("harness-workers", "staging")).toBe(false);
477+
// Runs the shared showcase-harness image; not separately CI-built; kept
478+
// out of both gate directions via gateIgnore.
479+
expect(worker.environments.staging.repoName).toBe("showcase-harness");
480+
expect(worker.ciBuilt).toBe(false);
481+
expect(worker.gateIgnore).toBe(true);
482+
expect(worker.serviceId).toBe("c2aa8a0b-350e-4b76-8541-3012dfac41d0");
483+
expect(worker.environments.staging.instanceId).toBe(
484+
"362c1e37-5f40-45f2-ac7b-0e5adac565f8",
485+
);
486+
});
487+
459488
it("domainFor returns the no-scheme host for known service+env", () => {
460489
expect(domainFor("docs", "staging")).toBe("docs.staging.copilotkit.ai");
461490
expect(domainFor("docs", "prod")).toBe("docs.copilotkit.ai");

showcase/scripts/railway-envs.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,11 @@ export const SERVICES: Record<
340340
},
341341
},
342342
},
343-
"showcase-harness-worker": {
343+
// SSOT key MUST equal the EXACT Railway service name. The Railway service
344+
// is `harness-workers` (PLURAL) — the image-ref gate matches SSOT keys to
345+
// Railway service names verbatim, so the key here is `harness-workers`,
346+
// not `showcase-harness-worker`.
347+
"harness-workers": {
344348
serviceId: "c2aa8a0b-350e-4b76-8541-3012dfac41d0",
345349
// STAGING-ONLY worker (pool-fleet cutover). There is no prod
346350
// serviceInstance — the pool-fleet runs in staging only for now. Under
@@ -365,7 +369,7 @@ export const SERVICES: Record<
365369
// existing `harness` (control-plane) service runs — it is NOT a
366370
// separately-built image. The single `showcase-harness` build slot in
367371
// showcase_build.yml produces the image both services consume; there is
368-
// no `showcase-harness-worker` build slot. Hence ciBuilt:false. The
372+
// no `harness-workers` build slot. Hence ciBuilt:false. The
369373
// repoName override points at `showcase-harness` so the image-ref shape
370374
// resolves correctly if the gate ever validates it.
371375
//
@@ -411,7 +415,7 @@ export const SERVICES: Record<
411415
// gateIgnore: deliberately-untracked for the image-ref gate. This
412416
// interim service runs a pinned digest (not the canonical :latest /
413417
// @sha256 shape the gate enforces) and is short-lived. Mirrors
414-
// showcase-harness-worker exactly (minus the worker's single-env shape —
418+
// harness-workers exactly (minus the worker's single-env shape —
415419
// harness-legacy DOES exist in both envs).
416420
gateValidated: false,
417421
gateIgnore: true,

0 commit comments

Comments
 (0)