@norskvideo/ctl-dev-kit 0.1.87 → 0.1.89
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.
|
@@ -55,12 +55,31 @@ on:
|
|
|
55
55
|
permissions:
|
|
56
56
|
contents: read
|
|
57
57
|
|
|
58
|
-
#
|
|
59
|
-
#
|
|
60
|
-
#
|
|
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:
|
|
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
|
@@ -25,13 +25,13 @@ way resolved defaults (segment durations, as-rendered resolutions, output count)
|
|
|
25
25
|
are guarded too, not only literal fields. When a code comment carries one of
|
|
26
26
|
these rules, cite the ID there too instead of restating the rule.
|
|
27
27
|
|
|
28
|
-
| ID | Invariant
|
|
29
|
-
| ---------------- |
|
|
30
|
-
| INV-PLAYLIST-001 | This customer serves legacy playlists only — every composed CMAF playlist is non-low-latency, never LL-HLS
|
|
31
|
-
| INV-SEG-001 | CMAF segment duration is 4s on every rendition (a resolved default — only guardable via the composed graph)
|
|
32
|
-
| INV-OUT-001 | Exactly three delivery rungs in a single named CMAF group, one playlist per rung
|
|
33
|
-
| INV-RES-001 | The delivery resolutions are exactly the ladder the customer signed off (e.g. 1280x720 / 640x360 / 320x180)
|
|
34
|
-
| INV-MOQ-001 | The low-latency return / monitor rides MoQ, never a WHIP/RTMP fallback
|
|
28
|
+
| ID | Invariant | Guarded by |
|
|
29
|
+
| ---------------- | ----------------------------------------------------------------------------------------------------------- | ---------------------------------- |
|
|
30
|
+
| INV-PLAYLIST-001 | This customer serves legacy playlists only — every composed CMAF playlist is non-low-latency, never LL-HLS | `<slug>/<slug>-invariants.test.ts` |
|
|
31
|
+
| INV-SEG-001 | CMAF segment duration is 4s on every rendition (a resolved default — only guardable via the composed graph) | `<slug>/<slug>-invariants.test.ts` |
|
|
32
|
+
| INV-OUT-001 | Exactly three delivery rungs in a single named CMAF group, one playlist per rung | `<slug>/<slug>-invariants.test.ts` |
|
|
33
|
+
| INV-RES-001 | The delivery resolutions are exactly the ladder the customer signed off (e.g. 1280x720 / 640x360 / 320x180) | `<slug>/<slug>-invariants.test.ts` |
|
|
34
|
+
| INV-MOQ-001 | The low-latency return / monitor rides MoQ, never a WHIP/RTMP fallback | `<slug>/<slug>-invariants.test.ts` |
|
|
35
35
|
|
|
36
36
|
The rows above are worked examples (drawn from the datum commentary deployment);
|
|
37
37
|
delete them when seeding a new customer.
|