@norskvideo/ctl-dev-kit 0.1.86 → 0.1.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.
package/build/flake.nix CHANGED
@@ -87,6 +87,15 @@
87
87
  buildTools = [
88
88
  biome
89
89
  pkgs.bun
90
+ pkgs.nodejs # @biomejs/biome's bin is a JS wrapper, not the binary —
91
+ # it is what reads BIOME_BINARY below and execs the nix
92
+ # build. Something has to *parse* that wrapper first, so a
93
+ # modern node has to be on PATH or the host's is used: on
94
+ # an older distro node (Ubuntu noble ships v12) `bun run
95
+ # lint` dies on the wrapper's optional chaining before
96
+ # biome ever starts. norsk-ctl carries nodejs for kubb and
97
+ # so never hit this; the split-out products had nothing
98
+ # putting node in the shell.
90
99
  pkgs.cargo # builds the native product-signer addon (probe/native)
91
100
  pkgs.rustc
92
101
  pkgs.git
@@ -55,12 +55,31 @@ on:
55
55
  permissions:
56
56
  contents: read
57
57
 
58
- # Key the group on event_name so a push never cancels an in-flight ctl-candidate
59
- # validation: pushes cancel pushes, candidate validations cancel only newer
60
- # candidates. A cancelled validation would score as a red at release-collect.
58
+ # A ctl-candidate validation must not be cancellable out of a result, because
59
+ # release-collect scores anything other than a literal success as red — a
60
+ # CANCELLED run included and answers a red with one re-dispatch.
61
+ #
62
+ # Keying on event_name alone was not enough: every candidate is the SAME event,
63
+ # so they shared a group and cancelled each other, and the re-dispatch cancelled
64
+ # the very run it was asking for. On 2026-09-05 commentary was dispatched four
65
+ # times in thirteen minutes, every run cancelled before it could start on the
66
+ # backlogged pool, and rc-gate/0.1.0-2026-09-05-98e09d1/FAILED was written for a
67
+ # candidate on which nothing had actually failed — while playout, studio and
68
+ # reuters, fast enough to finish inside the window, all went green.
69
+ #
70
+ # So: separate the candidates (client_payload.version — empty on push/PR, which
71
+ # leaves those groups exactly as they were), and do not cancel a dispatch run.
72
+ # A superseded PENDING run is dropped before it starts, fires no
73
+ # report-candidate, and scores nothing — which is why queueing is safe here
74
+ # where cancelling is not. Pushes still cancel pushes.
75
+ #
76
+ # Trade-off, stated: two candidates' validations can now overlap in one repo,
77
+ # and their tiers derive host ports from the same per-slug bands. The bands were
78
+ # moved out of the kernel's ephemeral range (ctl-test-harness 0.1.42); an
79
+ # ephemeral daemon port is the durable answer and is not wired yet.
61
80
  concurrency:
62
- group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
63
- cancel-in-progress: true
81
+ group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}-${{ github.event.client_payload.version }}
82
+ cancel-in-progress: ${{ github.event_name != 'repository_dispatch' }}
64
83
 
65
84
  jobs:
66
85
  integration:
@@ -161,3 +161,37 @@ export function jobsMissingTestTempStep(yaml: string): string[] {
161
161
  settle();
162
162
  return bad;
163
163
  }
164
+
165
+ /** A `ctl-candidate` validation that another candidate, or its own retry, can
166
+ * cancel.
167
+ *
168
+ * release-collect scores anything other than a literal success as red — a
169
+ * CANCELLED validation included — and answers a red with one re-dispatch. So a
170
+ * concurrency group shared across candidates makes the gate eat itself:
171
+ * candidate B's dispatch cancels A's in-flight run, A scores red, A retries,
172
+ * and that retry cancels B's run. Observed 2026-09-05: commentary dispatched
173
+ * four times in thirteen minutes, every run cancelled, and
174
+ * `rc-gate/0.1.0-2026-09-05-98e09d1/FAILED` written for a candidate on which
175
+ * nothing had actually failed.
176
+ *
177
+ * Both halves are needed. The group must SEPARATE candidates
178
+ * (`client_payload.version`), so a different candidate never shares one; and a
179
+ * dispatch run must not be cancellable, so a retry queues behind the run it is
180
+ * asking for rather than killing it. A superseded PENDING run is dropped
181
+ * before it starts, so it fires no `report-candidate` and scores nothing —
182
+ * which is why queueing is safe where cancelling is not. */
183
+ export function candidateConcurrencyProblems(yaml: string): string[] {
184
+ const block = yaml.match(/^concurrency:\n((?:[ \t]+.*\n)+)/m)?.[1] ?? "";
185
+ const group = block.match(/^\s*group:\s*(.*)$/m)?.[1] ?? "";
186
+ const cancel = (block.match(/^\s*cancel-in-progress:\s*(.*)$/m)?.[1] ?? "").trim();
187
+ const problems: string[] = [];
188
+ if (!/client_payload\.version/.test(group)) {
189
+ problems.push(
190
+ "group does not separate candidates: a newer candidate cancels an older validation, which scores red",
191
+ );
192
+ }
193
+ if (cancel === "true") {
194
+ problems.push("cancel-in-progress is unconditionally true: the re-dispatch cancels the run it is asking for");
195
+ }
196
+ return problems;
197
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@norskvideo/ctl-dev-kit",
3
- "version": "0.1.86",
3
+ "version": "0.1.88",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "./create-product": "./create-product/create-product.ts",