@norskvideo/ctl-dev-kit 0.2.86 → 0.2.88

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -101,6 +101,21 @@ On 2026-10-02 a reuters tier and a commentary tier each `compose up`ed
101
101
  port (bound inside the runner container, so not shared). In `direct` reach
102
102
  mode with `--internal-only` the studio port is not published at all.
103
103
 
104
+ ### One suite per host
105
+
106
+ Names can be made unique; CPU cannot. A runner container is pinned to its own
107
+ cores, but the media containers its suite launches are host siblings and run
108
+ anywhere, so two suites on one host starve each other's engines. On 2026-10-15/16
109
+ commentary's timing tests (ears, realtime drift, soak preview) failed in 10 of the
110
+ 12 test2 runs that overlapped another suite, most often a second commentary run on
111
+ the other runner. The canonical `integration.yml` therefore runs its suite under
112
+ an `flock` on `/srv/ci-scratch/locks/norsk-ctl-integration.lock`:
113
+ `/srv/ci-scratch` is bind-mounted at that path into every jankings runner on a
114
+ host, so the file is shared by that host's runners and by no other host's. A
115
+ runner without the mount runs unserialised and says so in the log; a suite that
116
+ has waited an hour runs anyway with a warning. A hand-owned `integration.yml`
117
+ does not take the lock until it converges onto the canonical.
118
+
104
119
  ## Reaching an image product's control plane
105
120
 
106
121
  `product add --image` loopback-publishes the control plane on a
@@ -255,8 +270,11 @@ that way. Hand the suite a value by having both sides derive it (a tiny script
255
270
  the hook runs and the test imports), not through `$GITHUB_ENV`.
256
271
 
257
272
  `bun run test:integration` is the product's claim of what its tier is, and
258
- ctl's release gate believes it: a `ctl-candidate` dispatch runs this workflow
259
- and the reported result decides whether `latest` moves.
273
+ two things believe it: the repo's nightly bump PR runs it on a new ctl pin and
274
+ merges only when it is green, and ctl's release gate sends a `ctl-candidate`
275
+ dispatch that runs this workflow and shows the result on the CI board. The
276
+ gate is advisory — the result does not hold ctl's `latest` — so the bump PR
277
+ is what keeps a ctl that breaks this product out of it.
260
278
 
261
279
  **Converging a repo.** The gate only takes over a copy whose first line is the
262
280
  canonical's marker (`# ctl-dev-kit convention: integration`); a copy without it
@@ -12,9 +12,10 @@
12
12
  # What the tier is: the product's slow suite (daemon + Docker + licence) run
13
13
  # against the RELEASED norsk-ctl binary, exactly as a customer would run it — a
14
14
  # split product repo has no ctl source. `bun run test:integration` is the
15
- # product's claim of what that suite is, and ctl's release gate believes it: a
16
- # ctl-candidate dispatch runs this and the result decides whether `latest`
17
- # moves.
15
+ # product's claim of what that suite is, and two things believe it: this
16
+ # repo's nightly bump PR, which runs it on a new ctl pin and merges only when
17
+ # green, and ctl's (advisory) release gate, whose ctl-candidate dispatch runs it
18
+ # and shows the result on the CI board.
18
19
  #
19
20
  # Per-repo variation lives in three optional hooks, not in this file:
20
21
  # scripts/integration/prepull-extra.sh extra images the suite launches (a
@@ -57,7 +58,7 @@ on:
57
58
  workflow_dispatch:
58
59
  inputs:
59
60
  ctl_channel:
60
- description: "norsk-ctl S3 channel to test against in place of the repo's pin (flake.nix ctlVersion). Empty runs the pin; release-gate dispatches 'rc' to validate a candidate before promoting latest."
61
+ description: "norsk-ctl S3 channel to test against in place of the repo's pin (flake.nix ctlVersion). Empty runs the pin; release-gate dispatches 'rc' to report on a candidate (advisory: latest is not held on it)."
61
62
  type: string
62
63
  default: ""
63
64
 
@@ -66,7 +67,8 @@ permissions:
66
67
 
67
68
  # A ctl-candidate validation must not be cancellable out of a result, because
68
69
  # release-collect scores anything other than a literal success as red — a
69
- # CANCELLED run included — and answers a red with one re-dispatch.
70
+ # CANCELLED run included. (Until the gate went advisory on 2026-10-17 it also
71
+ # answered a red with one re-dispatch, which is what made this bite.)
70
72
  #
71
73
  # Keying on event_name alone was not enough: every candidate is the SAME event,
72
74
  # so they shared a group and cancelled each other, and the re-dispatch cancelled
@@ -428,7 +430,34 @@ jobs:
428
430
  fi
429
431
  '
430
432
  }
431
- suite 2>&1 | tee "$RUNNER_TEMP/integration.log"
433
+ # One suite per HOST. A host's runners are sibling containers on one
434
+ # docker daemon, and the media containers a suite launches are not
435
+ # confined to its runner's cpuset, so two suites side by side starve
436
+ # each other's engines: on 2026-10-15/16 commentary's timing tests
437
+ # failed in 10 of the 12 test2 runs that overlapped another suite on the
438
+ # other runner, mostly commentary against itself, which a per-repo
439
+ # concurrency group cannot stop without dropping a queued candidate.
440
+ # /srv/ci-scratch is bind-mounted at that path into every runner on a
441
+ # jankings host, so an flock there is the host's mutex; the kernel
442
+ # drops it when this shell exits, however the job ends. Children close
443
+ # fd 9 so a straggler cannot hold the host after the step. After an
444
+ # hour the suite runs anyway, warned, rather than miss its result.
445
+ # integration-host-lock.test.ts.
446
+ if [ -d /srv/ci-scratch ] && [ -w /srv/ci-scratch ]; then
447
+ lock=/srv/ci-scratch/locks/norsk-ctl-integration.lock
448
+ mkdir -p "${lock%/*}"
449
+ exec 9>>"$lock"
450
+ echo "waiting for this host's integration lock ($lock)"
451
+ waited=$SECONDS
452
+ if flock -w 3600 9; then
453
+ echo "holding this host's integration lock after $((SECONDS - waited))s"
454
+ else
455
+ echo "::warning title=integration ran unserialised::waited an hour for $lock on $(hostname); timing-sensitive tests may fail for a reason that is not theirs"
456
+ fi
457
+ else
458
+ echo "no writable /srv/ci-scratch on this runner: the suite runs without the host lock"
459
+ fi
460
+ suite 9>&- 2>&1 | tee "$RUNNER_TEMP/integration.log"
432
461
 
433
462
  # The log AND the harness evidence. Playwright and the harness write
434
463
  # screenshots, traces and captured container logs under test-results/;
@@ -583,8 +612,9 @@ jobs:
583
612
  fi
584
613
 
585
614
  # Report the rc-candidate validation result to the norsk-ctl release gate. Only
586
- # on a ctl-candidate dispatch; release-collect tallies these and promotes
587
- # latest once every gating product is green on the candidate.
615
+ # on a ctl-candidate dispatch; release-collect records it for the CI board.
616
+ # Advisory: ctl promotes latest without waiting on it, and this repo takes a
617
+ # new ctl only through its bump PR, which runs this tier on the new pin.
588
618
  report-candidate:
589
619
  needs: integration
590
620
  if: ${{ always() && github.event_name == 'repository_dispatch' && github.event.action == 'ctl-candidate' }}
@@ -602,7 +632,8 @@ jobs:
602
632
  set -euo pipefail
603
633
  state=failure; [ "$RESULT" = "success" ] && state=success
604
634
  # `tier: integration`, named rather than defaulted: the release gate
605
- # tallies per tier, and this is the one that gates (docs/release-gate.md).
635
+ # tallies per tier, and this is the one that launches instances
636
+ # (docs/release-gate.md).
606
637
  body="$(jq -nc --arg p "$product" --arg v "$VERSION" --arg r "$state" \
607
638
  '{event_type:"ctl-candidate-result",client_payload:{product:$p,version:$v,result:$r,tier:"integration"}}')"
608
639
  code="$(curl -sS -o /dev/null -w '%{http_code}' -X POST \
@@ -30,7 +30,7 @@ on:
30
30
  workflow_dispatch:
31
31
  inputs:
32
32
  ctl_channel:
33
- description: "norsk-ctl S3 channel to test against in place of the repo's pin (flake.nix ctlVersion). Empty runs the pin; release-gate dispatches 'rc' to validate a candidate before promoting latest."
33
+ description: "norsk-ctl S3 channel to test against in place of the repo's pin (flake.nix ctlVersion). Empty runs the pin; release-gate dispatches 'rc' to report on a candidate (advisory: latest is not held on it)."
34
34
  type: string
35
35
  default: ""
36
36
 
@@ -503,8 +503,9 @@ jobs:
503
503
  fi
504
504
 
505
505
  # Report the rc-candidate validation result to the norsk-ctl release gate. Only
506
- # on a ctl-candidate dispatch; release-collect tallies these and promotes
507
- # latest once every gating product is green on the candidate.
506
+ # on a ctl-candidate dispatch; release-collect records it for the CI board.
507
+ # Advisory: ctl promotes latest without waiting on it, and this repo takes a
508
+ # new ctl only through its bump PR, which runs this tier on the new pin.
508
509
  report-candidate:
509
510
  needs: smoke
510
511
  if: ${{ always() && github.event_name == 'repository_dispatch' && github.event.action == 'ctl-candidate' }}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@norskvideo/ctl-dev-kit",
3
- "version": "0.2.86",
3
+ "version": "0.2.88",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "./create-product": "./create-product/create-product.ts",