@norskvideo/ctl-dev-kit 0.1.76 → 0.1.78

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.
@@ -56,9 +56,27 @@ jobs:
56
56
  docker run --rm --user 0:0 -v "$tt":/t alpine sh \
57
57
  -c 'rm -rf /t/* /t/.[!.]* 2>/dev/null || true' || rm -rf "$tt"/* 2>/dev/null || true
58
58
 
59
+ # Root-owned leftovers under test-temp/ are what `clean: false` used to route
60
+ # around: the integration suite bind-mounts host dirs into containers, and a
61
+ # non-root git clean then EACCESes on what they wrote -- taking down whatever
62
+ # innocent job checked out next. Skipping the clean cured that symptom and
63
+ # caused another: every gitignored artifact survived between runs, so a test
64
+ # could pass on build output an earlier job left behind and go red the first
65
+ # time it landed on a cold runner (probe, 2026-09-01). Clear the leftovers
66
+ # from a root container FIRST, and the checkout can clean properly again.
67
+ # Best-effort by construction: no docker, no test-temp, or a failed run all
68
+ # fall through without failing the job.
69
+ - name: Clear stale root-owned test-temp (pre-checkout)
70
+ run: |
71
+ set -uo pipefail
72
+ tt="$GITHUB_WORKSPACE/test-temp"
73
+ [ -d "$tt" ] || exit 0
74
+ docker run --rm --user 0:0 -v "$tt":/t alpine sh \
75
+ -c 'rm -rf /t/* /t/.[!.]* 2>/dev/null || true' || rm -rf "$tt"/* 2>/dev/null || true
76
+
59
77
  - uses: actions/checkout@v5
60
78
  with:
61
- clean: false
79
+ clean: true
62
80
 
63
81
  - name: Write the Norsk license (from the org secret)
64
82
  env:
@@ -113,7 +131,7 @@ jobs:
113
131
  run: |
114
132
  nix develop .#build --command bash -c '
115
133
  set -euo pipefail
116
- # clean:false persists node_modules between runs for speed, but a
134
+ # Belt-and-braces now that the checkout cleans: a persisted node_modules
117
135
  # workspace dep whose version moved leaves stale copies that
118
136
  # --frozen-lockfile does not reliably relink, so a new export is "not
119
137
  # found". Nuke EVERY node_modules — including nested per-workspace ones
@@ -66,9 +66,27 @@ jobs:
66
66
  # publish job gates on this output instead of on PUBLISH_IMAGE directly.
67
67
  publish: ${{ steps.gen.outputs.publish }}
68
68
  steps:
69
+ # Root-owned leftovers under test-temp/ are what `clean: false` used to route
70
+ # around: the integration suite bind-mounts host dirs into containers, and a
71
+ # non-root git clean then EACCESes on what they wrote -- taking down whatever
72
+ # innocent job checked out next. Skipping the clean cured that symptom and
73
+ # caused another: every gitignored artifact survived between runs, so a test
74
+ # could pass on build output an earlier job left behind and go red the first
75
+ # time it landed on a cold runner (probe, 2026-09-01). Clear the leftovers
76
+ # from a root container FIRST, and the checkout can clean properly again.
77
+ # Best-effort by construction: no docker, no test-temp, or a failed run all
78
+ # fall through without failing the job.
79
+ - name: Clear stale root-owned test-temp (pre-checkout)
80
+ run: |
81
+ set -uo pipefail
82
+ tt="$GITHUB_WORKSPACE/test-temp"
83
+ [ -d "$tt" ] || exit 0
84
+ docker run --rm --user 0:0 -v "$tt":/t alpine sh \
85
+ -c 'rm -rf /t/* /t/.[!.]* 2>/dev/null || true' || rm -rf "$tt"/* 2>/dev/null || true
86
+
69
87
  - uses: actions/checkout@v5
70
88
  with:
71
- clean: false
89
+ clean: true
72
90
  - id: gen
73
91
  run: |
74
92
  label="$(date -u +%Y-%m-%d)-$(git rev-parse --short=8 HEAD)"
@@ -91,13 +109,27 @@ jobs:
91
109
  arch: arm64
92
110
  runs-on: ${{ matrix.runner }}
93
111
  steps:
94
- # clean: false -- the integration suite (shared pool) leaves root-owned
95
- # bind-mount dirs under test-temp/ that a non-root git clean can't remove
96
- # (EACCES). This job never needs them; skip the clean so it doesn't choke on
97
- # another workflow's leftovers.
112
+ # Root-owned leftovers under test-temp/ are what `clean: false` used to route
113
+ # around: the integration suite bind-mounts host dirs into containers, and a
114
+ # non-root git clean then EACCESes on what they wrote -- taking down whatever
115
+ # innocent job checked out next. Skipping the clean cured that symptom and
116
+ # caused another: every gitignored artifact survived between runs, so a test
117
+ # could pass on build output an earlier job left behind and go red the first
118
+ # time it landed on a cold runner (probe, 2026-09-01). Clear the leftovers
119
+ # from a root container FIRST, and the checkout can clean properly again.
120
+ # Best-effort by construction: no docker, no test-temp, or a failed run all
121
+ # fall through without failing the job.
122
+ - name: Clear stale root-owned test-temp (pre-checkout)
123
+ run: |
124
+ set -uo pipefail
125
+ tt="$GITHUB_WORKSPACE/test-temp"
126
+ [ -d "$tt" ] || exit 0
127
+ docker run --rm --user 0:0 -v "$tt":/t alpine sh \
128
+ -c 'rm -rf /t/* /t/.[!.]* 2>/dev/null || true' || rm -rf "$tt"/* 2>/dev/null || true
129
+
98
130
  - uses: actions/checkout@v5
99
131
  with:
100
- clean: false
132
+ clean: true
101
133
 
102
134
  - name: Build product image (copy-only, via the dev-kit driver, native ${{ matrix.arch }})
103
135
  run: |
@@ -133,9 +165,27 @@ jobs:
133
165
  if: ${{ needs.label.outputs.publish == 'true' && !inputs.dry_run }}
134
166
  runs-on: x64
135
167
  steps:
168
+ # Root-owned leftovers under test-temp/ are what `clean: false` used to route
169
+ # around: the integration suite bind-mounts host dirs into containers, and a
170
+ # non-root git clean then EACCESes on what they wrote -- taking down whatever
171
+ # innocent job checked out next. Skipping the clean cured that symptom and
172
+ # caused another: every gitignored artifact survived between runs, so a test
173
+ # could pass on build output an earlier job left behind and go red the first
174
+ # time it landed on a cold runner (probe, 2026-09-01). Clear the leftovers
175
+ # from a root container FIRST, and the checkout can clean properly again.
176
+ # Best-effort by construction: no docker, no test-temp, or a failed run all
177
+ # fall through without failing the job.
178
+ - name: Clear stale root-owned test-temp (pre-checkout)
179
+ run: |
180
+ set -uo pipefail
181
+ tt="$GITHUB_WORKSPACE/test-temp"
182
+ [ -d "$tt" ] || exit 0
183
+ docker run --rm --user 0:0 -v "$tt":/t alpine sh \
184
+ -c 'rm -rf /t/* /t/.[!.]* 2>/dev/null || true' || rm -rf "$tt"/* 2>/dev/null || true
185
+
136
186
  - uses: actions/checkout@v5
137
187
  with:
138
- clean: false
188
+ clean: true
139
189
 
140
190
  - name: Log in to Docker Hub
141
191
  uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
@@ -166,9 +216,27 @@ jobs:
166
216
  if: ${{ !cancelled() }}
167
217
  runs-on: x64
168
218
  steps:
219
+ # Root-owned leftovers under test-temp/ are what `clean: false` used to route
220
+ # around: the integration suite bind-mounts host dirs into containers, and a
221
+ # non-root git clean then EACCESes on what they wrote -- taking down whatever
222
+ # innocent job checked out next. Skipping the clean cured that symptom and
223
+ # caused another: every gitignored artifact survived between runs, so a test
224
+ # could pass on build output an earlier job left behind and go red the first
225
+ # time it landed on a cold runner (probe, 2026-09-01). Clear the leftovers
226
+ # from a root container FIRST, and the checkout can clean properly again.
227
+ # Best-effort by construction: no docker, no test-temp, or a failed run all
228
+ # fall through without failing the job.
229
+ - name: Clear stale root-owned test-temp (pre-checkout)
230
+ run: |
231
+ set -uo pipefail
232
+ tt="$GITHUB_WORKSPACE/test-temp"
233
+ [ -d "$tt" ] || exit 0
234
+ docker run --rm --user 0:0 -v "$tt":/t alpine sh \
235
+ -c 'rm -rf /t/* /t/.[!.]* 2>/dev/null || true' || rm -rf "$tt"/* 2>/dev/null || true
236
+
169
237
  - uses: actions/checkout@v5
170
238
  with:
171
- clean: false
239
+ clean: true
172
240
  - id: meta
173
241
  run: |
174
242
  if [ "${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}" = "true" ]; then
@@ -30,22 +30,36 @@ jobs:
30
30
  drift:
31
31
  runs-on: x64
32
32
  steps:
33
- # clean: false -- the integration suite (shared x64 pool) leaves root-owned
34
- # bind-mount dirs under test-temp/ that a non-root git clean can't remove
35
- # (EACCES). These jobs never need those files; skip the clean so an innocent
36
- # bystander checkout doesn't choke on another workflow's leftovers.
33
+ # Root-owned leftovers under test-temp/ are what `clean: false` used to route
34
+ # around: the integration suite bind-mounts host dirs into containers, and a
35
+ # non-root git clean then EACCESes on what they wrote -- taking down whatever
36
+ # innocent job checked out next. Skipping the clean cured that symptom and
37
+ # caused another: every gitignored artifact survived between runs, so a test
38
+ # could pass on build output an earlier job left behind and go red the first
39
+ # time it landed on a cold runner (probe, 2026-09-01). Clear the leftovers
40
+ # from a root container FIRST, and the checkout can clean properly again.
41
+ # Best-effort by construction: no docker, no test-temp, or a failed run all
42
+ # fall through without failing the job.
43
+ - name: Clear stale root-owned test-temp (pre-checkout)
44
+ run: |
45
+ set -uo pipefail
46
+ tt="$GITHUB_WORKSPACE/test-temp"
47
+ [ -d "$tt" ] || exit 0
48
+ docker run --rm --user 0:0 -v "$tt":/t alpine sh \
49
+ -c 'rm -rf /t/* /t/.[!.]* 2>/dev/null || true' || rm -rf "$tt"/* 2>/dev/null || true
50
+
37
51
  - uses: actions/checkout@v5
38
52
  with:
39
- clean: false
53
+ clean: true
40
54
  - name: Shared-conventions drift-check (Workstream I)
41
55
  run: |
42
56
  nix develop .#build --command bash -c '
43
57
  set -euo pipefail
44
- # clean:false persists node_modules between runs, and a frozen install
45
- # will NOT relink a dep/workspace whose pinned version moved -- e.g. a
46
- # dev-kit bump leaves a stale node_modules/@norskvideo/ctl-dev-kit, so
47
- # check:drift would run the OLD checker against the new canonical. Nuke
48
- # every node_modules first so the pinned versions are what actually run.
58
+ # Belt-and-braces now that the checkout cleans: a persisted
59
+ # node_modules would defeat a frozen install, which does NOT relink a
60
+ # dep whose pinned version moved (a dev-kit bump leaves a stale
61
+ # node_modules/@norskvideo/ctl-dev-kit, so check:drift would run the
62
+ # OLD checker against the new canonical). Cheap when already gone.
49
63
  find . -name node_modules -type d -prune -exec rm -rf {} + 2>/dev/null || true
50
64
  bun install --frozen-lockfile
51
65
  bun run check:drift
@@ -54,22 +68,36 @@ jobs:
54
68
  quality:
55
69
  runs-on: x64
56
70
  steps:
57
- # clean: false -- the integration suite (shared x64 pool) leaves root-owned
58
- # bind-mount dirs under test-temp/ that a non-root git clean can't remove
59
- # (EACCES). These jobs never need those files; skip the clean so an innocent
60
- # bystander checkout doesn't choke on another workflow's leftovers.
71
+ # Root-owned leftovers under test-temp/ are what `clean: false` used to route
72
+ # around: the integration suite bind-mounts host dirs into containers, and a
73
+ # non-root git clean then EACCESes on what they wrote -- taking down whatever
74
+ # innocent job checked out next. Skipping the clean cured that symptom and
75
+ # caused another: every gitignored artifact survived between runs, so a test
76
+ # could pass on build output an earlier job left behind and go red the first
77
+ # time it landed on a cold runner (probe, 2026-09-01). Clear the leftovers
78
+ # from a root container FIRST, and the checkout can clean properly again.
79
+ # Best-effort by construction: no docker, no test-temp, or a failed run all
80
+ # fall through without failing the job.
81
+ - name: Clear stale root-owned test-temp (pre-checkout)
82
+ run: |
83
+ set -uo pipefail
84
+ tt="$GITHUB_WORKSPACE/test-temp"
85
+ [ -d "$tt" ] || exit 0
86
+ docker run --rm --user 0:0 -v "$tt":/t alpine sh \
87
+ -c 'rm -rf /t/* /t/.[!.]* 2>/dev/null || true' || rm -rf "$tt"/* 2>/dev/null || true
88
+
61
89
  - uses: actions/checkout@v5
62
90
  with:
63
- clean: false
91
+ clean: true
64
92
  - name: Lint + typecheck + unit tests
65
93
  run: |
66
94
  nix develop .#build --command bash -c '
67
95
  set -euo pipefail
68
- # clean:false persists node_modules between runs, and a frozen install
69
- # will NOT relink a dep/workspace whose pinned version moved -- e.g. a
70
- # dev-kit bump leaves a stale node_modules/@norskvideo/ctl-dev-kit, so
71
- # check:drift would run the OLD checker against the new canonical. Nuke
72
- # every node_modules first so the pinned versions are what actually run.
96
+ # Belt-and-braces now that the checkout cleans: a persisted
97
+ # node_modules would defeat a frozen install, which does NOT relink a
98
+ # dep whose pinned version moved (a dev-kit bump leaves a stale
99
+ # node_modules/@norskvideo/ctl-dev-kit, so check:drift would run the
100
+ # OLD checker against the new canonical). Cheap when already gone.
73
101
  find . -name node_modules -type d -prune -exec rm -rf {} + 2>/dev/null || true
74
102
  bun install --frozen-lockfile
75
103
  bun run lint
@@ -132,13 +160,27 @@ jobs:
132
160
  if: ${{ !cancelled() && github.event_name == 'push' }}
133
161
  runs-on: x64
134
162
  steps:
135
- # clean: false -- the integration suite (shared x64 pool) leaves root-owned
136
- # bind-mount dirs under test-temp/ that a non-root git clean can't remove
137
- # (EACCES). These jobs never need those files; skip the clean so an innocent
138
- # bystander checkout doesn't choke on another workflow's leftovers.
163
+ # Root-owned leftovers under test-temp/ are what `clean: false` used to route
164
+ # around: the integration suite bind-mounts host dirs into containers, and a
165
+ # non-root git clean then EACCESes on what they wrote -- taking down whatever
166
+ # innocent job checked out next. Skipping the clean cured that symptom and
167
+ # caused another: every gitignored artifact survived between runs, so a test
168
+ # could pass on build output an earlier job left behind and go red the first
169
+ # time it landed on a cold runner (probe, 2026-09-01). Clear the leftovers
170
+ # from a root container FIRST, and the checkout can clean properly again.
171
+ # Best-effort by construction: no docker, no test-temp, or a failed run all
172
+ # fall through without failing the job.
173
+ - name: Clear stale root-owned test-temp (pre-checkout)
174
+ run: |
175
+ set -uo pipefail
176
+ tt="$GITHUB_WORKSPACE/test-temp"
177
+ [ -d "$tt" ] || exit 0
178
+ docker run --rm --user 0:0 -v "$tt":/t alpine sh \
179
+ -c 'rm -rf /t/* /t/.[!.]* 2>/dev/null || true' || rm -rf "$tt"/* 2>/dev/null || true
180
+
139
181
  - uses: actions/checkout@v5
140
182
  with:
141
- clean: false
183
+ clean: true
142
184
  - id: meta
143
185
  run: |
144
186
  if [ "${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}" = "true" ]; then
@@ -55,9 +55,27 @@ jobs:
55
55
  docker run --rm --user 0:0 -v "$tt":/t alpine sh \
56
56
  -c 'rm -rf /t/* /t/.[!.]* 2>/dev/null || true' || rm -rf "$tt"/* 2>/dev/null || true
57
57
 
58
+ # Root-owned leftovers under test-temp/ are what `clean: false` used to route
59
+ # around: the integration suite bind-mounts host dirs into containers, and a
60
+ # non-root git clean then EACCESes on what they wrote -- taking down whatever
61
+ # innocent job checked out next. Skipping the clean cured that symptom and
62
+ # caused another: every gitignored artifact survived between runs, so a test
63
+ # could pass on build output an earlier job left behind and go red the first
64
+ # time it landed on a cold runner (probe, 2026-09-01). Clear the leftovers
65
+ # from a root container FIRST, and the checkout can clean properly again.
66
+ # Best-effort by construction: no docker, no test-temp, or a failed run all
67
+ # fall through without failing the job.
68
+ - name: Clear stale root-owned test-temp (pre-checkout)
69
+ run: |
70
+ set -uo pipefail
71
+ tt="$GITHUB_WORKSPACE/test-temp"
72
+ [ -d "$tt" ] || exit 0
73
+ docker run --rm --user 0:0 -v "$tt":/t alpine sh \
74
+ -c 'rm -rf /t/* /t/.[!.]* 2>/dev/null || true' || rm -rf "$tt"/* 2>/dev/null || true
75
+
58
76
  - uses: actions/checkout@v5
59
77
  with:
60
- clean: false
78
+ clean: true
61
79
 
62
80
  - name: Write the Norsk license (from the org secret)
63
81
  env:
@@ -112,7 +130,7 @@ jobs:
112
130
  run: |
113
131
  nix develop .#build --command bash -c '
114
132
  set -euo pipefail
115
- # clean:false persists node_modules between runs for speed, but a
133
+ # Belt-and-braces now that the checkout cleans: a persisted node_modules
116
134
  # workspace dep whose version moved leaves stale copies that
117
135
  # --frozen-lockfile does not reliably relink, so a new export is "not
118
136
  # found". Nuke EVERY node_modules — including nested per-workspace ones
@@ -66,7 +66,7 @@ jobs:
66
66
 
67
67
  - uses: actions/checkout@v5
68
68
  with:
69
- clean: false
69
+ clean: true
70
70
 
71
71
  # A containerised runner (docker-outside-of-docker) reaches host-published
72
72
  # ports via the host-gateway alias and can join norsk-net for direct reach;
@@ -238,9 +238,20 @@ jobs:
238
238
  if: ${{ !cancelled() && github.event_name != 'repository_dispatch' }}
239
239
  runs-on: x64
240
240
  steps:
241
+ # Same reason as the first checkout in this file: clear root-owned leftovers
242
+ # from a root container so the checkout below can clean properly. Best-effort
243
+ # by construction -- no docker, no test-temp, or a failed run all fall through.
244
+ - name: Clear stale root-owned test-temp (pre-checkout)
245
+ run: |
246
+ set -uo pipefail
247
+ tt="$GITHUB_WORKSPACE/test-temp"
248
+ [ -d "$tt" ] || exit 0
249
+ docker run --rm --user 0:0 -v "$tt":/t alpine sh \
250
+ -c 'rm -rf /t/* /t/.[!.]* 2>/dev/null || true' || rm -rf "$tt"/* 2>/dev/null || true
251
+
241
252
  - uses: actions/checkout@v5
242
253
  with:
243
- clean: false
254
+ clean: true
244
255
  - id: meta
245
256
  run: |
246
257
  if [ "${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}" = "true" ]; then
@@ -41,16 +41,28 @@ jobs:
41
41
  sync:
42
42
  runs-on: x64
43
43
  steps:
44
- # CI_DISPATCH_TOKEN, not the automatic GITHUB_TOKEN: a PR opened by
45
- # GITHUB_TOKEN raises no workflow runs, so the product's own build, smoke
46
- # and integration would never execute against the new pins -- which is the
47
- # entire reason for opening it. clean: false for the same reason as the
48
- # other shared jobs: the suites leave root-owned bind-mount dirs under
49
- # test-temp/ that a non-root git clean cannot remove.
44
+ # Root-owned leftovers under test-temp/ are what `clean: false` used to route
45
+ # around: the integration suite bind-mounts host dirs into containers, and a
46
+ # non-root git clean then EACCESes on what they wrote -- taking down whatever
47
+ # innocent job checked out next. Skipping the clean cured that symptom and
48
+ # caused another: every gitignored artifact survived between runs, so a test
49
+ # could pass on build output an earlier job left behind and go red the first
50
+ # time it landed on a cold runner (probe, 2026-09-01). Clear the leftovers
51
+ # from a root container FIRST, and the checkout can clean properly again.
52
+ # Best-effort by construction: no docker, no test-temp, or a failed run all
53
+ # fall through without failing the job.
54
+ - name: Clear stale root-owned test-temp (pre-checkout)
55
+ run: |
56
+ set -uo pipefail
57
+ tt="$GITHUB_WORKSPACE/test-temp"
58
+ [ -d "$tt" ] || exit 0
59
+ docker run --rm --user 0:0 -v "$tt":/t alpine sh \
60
+ -c 'rm -rf /t/* /t/.[!.]* 2>/dev/null || true' || rm -rf "$tt"/* 2>/dev/null || true
61
+
50
62
  - uses: actions/checkout@v5
51
63
  with:
52
64
  token: ${{ secrets.CI_DISPATCH_TOKEN }}
53
- clean: false
65
+ clean: true
54
66
 
55
67
  - name: Resolve the newest published ctl-* packages and rewrite the pins
56
68
  id: bump
@@ -43,21 +43,28 @@ jobs:
43
43
  sync:
44
44
  runs-on: x64
45
45
  steps:
46
- # Check out with CI_DISPATCH_TOKEN, not the automatic GITHUB_TOKEN: this job
47
- # pushes re-synced copies, and the dev-kit single-sources the shared
48
- # .github/workflows/ files -- so a sync can rewrite a workflow file, and
49
- # GITHUB_TOKEN is categorically barred from pushing under .github/workflows/
50
- # (no permissions: key unlocks it). CI_DISPATCH_TOKEN carries the fine-grained
51
- # Workflows:write permission, so its push is allowed. The checkout credential
52
- # is what both the green push:main and the red branch push authenticate with.
53
- # clean: false -- the integration suite (shared x64 pool) leaves root-owned
54
- # bind-mount dirs under test-temp/ a non-root git clean can't remove
55
- # (EACCES); this job never needs them. `git add -A` below still ignores
56
- # them (test-temp is gitignored), so the sync commit stays clean.
46
+ # Root-owned leftovers under test-temp/ are what `clean: false` used to route
47
+ # around: the integration suite bind-mounts host dirs into containers, and a
48
+ # non-root git clean then EACCESes on what they wrote -- taking down whatever
49
+ # innocent job checked out next. Skipping the clean cured that symptom and
50
+ # caused another: every gitignored artifact survived between runs, so a test
51
+ # could pass on build output an earlier job left behind and go red the first
52
+ # time it landed on a cold runner (probe, 2026-09-01). Clear the leftovers
53
+ # from a root container FIRST, and the checkout can clean properly again.
54
+ # Best-effort by construction: no docker, no test-temp, or a failed run all
55
+ # fall through without failing the job.
56
+ - name: Clear stale root-owned test-temp (pre-checkout)
57
+ run: |
58
+ set -uo pipefail
59
+ tt="$GITHUB_WORKSPACE/test-temp"
60
+ [ -d "$tt" ] || exit 0
61
+ docker run --rm --user 0:0 -v "$tt":/t alpine sh \
62
+ -c 'rm -rf /t/* /t/.[!.]* 2>/dev/null || true' || rm -rf "$tt"/* 2>/dev/null || true
63
+
57
64
  - uses: actions/checkout@v5
58
65
  with:
59
66
  token: ${{ secrets.CI_DISPATCH_TOKEN }}
60
- clean: false
67
+ clean: true
61
68
 
62
69
  - name: Resolve the newest published dev-kit and bump the pin
63
70
  id: sync
@@ -194,11 +201,27 @@ jobs:
194
201
  if: ${{ !cancelled() }}
195
202
  runs-on: x64
196
203
  steps:
197
- # clean: false -- see the sync job; avoid a git-clean EACCES on integration's
198
- # root-owned test-temp leftovers on the shared x64 pool.
204
+ # Root-owned leftovers under test-temp/ are what `clean: false` used to route
205
+ # around: the integration suite bind-mounts host dirs into containers, and a
206
+ # non-root git clean then EACCESes on what they wrote -- taking down whatever
207
+ # innocent job checked out next. Skipping the clean cured that symptom and
208
+ # caused another: every gitignored artifact survived between runs, so a test
209
+ # could pass on build output an earlier job left behind and go red the first
210
+ # time it landed on a cold runner (probe, 2026-09-01). Clear the leftovers
211
+ # from a root container FIRST, and the checkout can clean properly again.
212
+ # Best-effort by construction: no docker, no test-temp, or a failed run all
213
+ # fall through without failing the job.
214
+ - name: Clear stale root-owned test-temp (pre-checkout)
215
+ run: |
216
+ set -uo pipefail
217
+ tt="$GITHUB_WORKSPACE/test-temp"
218
+ [ -d "$tt" ] || exit 0
219
+ docker run --rm --user 0:0 -v "$tt":/t alpine sh \
220
+ -c 'rm -rf /t/* /t/.[!.]* 2>/dev/null || true' || rm -rf "$tt"/* 2>/dev/null || true
221
+
199
222
  - uses: actions/checkout@v5
200
223
  with:
201
- clean: false
224
+ clean: true
202
225
  - id: meta
203
226
  run: |
204
227
  if [ "${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}" = "true" ]; then
@@ -10,12 +10,18 @@
10
10
  # norsk-ctl daemon to the `latest` channel (dev-only; CI downloads ctl
11
11
  # directly, so a stale pin never affects CI or customers).
12
12
  #
13
- # Commits the bump straight to main (nightly cron + on demand). The push is
14
- # made with CI_DISPATCH_TOKEN (a PAT), so it DOES fire the product's push-
15
- # triggered workflows (checks, build-image, integration) against the bumped
16
- # pins; a GITHUB_TOKEN push would not (Actions' recursion guard), and the bump
17
- # would land unverified until the next human push. A red run at 04:2x on main
18
- # is the signal that a Norsk nightly is bad and the bump needs reverting.
13
+ # Nightly cron + on demand. The bump runs the product's own gate (lint,
14
+ # typecheck, unit tests) BEFORE it lands, exactly as sync-dev-kit does: green
15
+ # lands on main, red opens a reconciliation PR and main stays green.
16
+ #
17
+ # It used to commit straight to main and rely on the push-triggered run for its
18
+ # verdict. The push is made with CI_DISPATCH_TOKEN (a PAT), so that run really
19
+ # does happen -- a GITHUB_TOKEN push would fire nothing (Actions' recursion
20
+ # guard) -- but it makes a RED `main` the alarm, and nobody watches a product
21
+ # repo's own `main`: playout sat red ~12h, then funke, then probe, inside three
22
+ # days (fleet review 04c, class 8). Gating first turns "a bad Norsk nightly reds
23
+ # six repos overnight" into "a bad Norsk nightly opens a PR that says so".
24
+ # Pinned for the class by conventions/gate-before-main.test.ts.
19
25
  #
20
26
  # Single-sourced in @norskvideo/ctl-dev-kit (conventions/upgrade-latest.yml) and
21
27
  # copied verbatim into each product repo; the check:drift gate fails CI if a copy
@@ -39,18 +45,28 @@ jobs:
39
45
  changed: ${{ steps.bump.outputs.changed }}
40
46
  summary: ${{ steps.bump.outputs.summary }}
41
47
  steps:
42
- # Check out with CI_DISPATCH_TOKEN, not the automatic GITHUB_TOKEN: the
43
- # checkout credential is what the push to main below authenticates with,
44
- # and only a PAT push triggers the push-driven workflows that validate the
45
- # new pins (see the header). Same arrangement as sync-dev-kit.yml.
46
- # clean: false -- the integration suite (shared x64 pool) leaves root-owned
47
- # bind-mount dirs under test-temp/ that a non-root git clean can't remove
48
- # (EACCES). These jobs never need those files; skip the clean so an innocent
49
- # bystander checkout doesn't choke on another workflow's leftovers.
48
+ # Root-owned leftovers under test-temp/ are what `clean: false` used to route
49
+ # around: the integration suite bind-mounts host dirs into containers, and a
50
+ # non-root git clean then EACCESes on what they wrote -- taking down whatever
51
+ # innocent job checked out next. Skipping the clean cured that symptom and
52
+ # caused another: every gitignored artifact survived between runs, so a test
53
+ # could pass on build output an earlier job left behind and go red the first
54
+ # time it landed on a cold runner (probe, 2026-09-01). Clear the leftovers
55
+ # from a root container FIRST, and the checkout can clean properly again.
56
+ # Best-effort by construction: no docker, no test-temp, or a failed run all
57
+ # fall through without failing the job.
58
+ - name: Clear stale root-owned test-temp (pre-checkout)
59
+ run: |
60
+ set -uo pipefail
61
+ tt="$GITHUB_WORKSPACE/test-temp"
62
+ [ -d "$tt" ] || exit 0
63
+ docker run --rm --user 0:0 -v "$tt":/t alpine sh \
64
+ -c 'rm -rf /t/* /t/.[!.]* 2>/dev/null || true' || rm -rf "$tt"/* 2>/dev/null || true
65
+
50
66
  - uses: actions/checkout@v5
51
67
  with:
52
68
  token: ${{ secrets.CI_DISPATCH_TOKEN }}
53
- clean: false
69
+ clean: true
54
70
 
55
71
  - name: Resolve the nightly channel and rewrite the coupled pins
56
72
  id: bump
@@ -202,8 +218,23 @@ jobs:
202
218
  done
203
219
  '
204
220
 
205
- - name: Commit the bump to main
221
+ - name: Run the product gate against the bumped pins
222
+ id: gate
206
223
  if: steps.bump.outputs.changed == '1'
224
+ # continue-on-error so a red gate can open a PR below instead of failing
225
+ # the workflow -- which is why the steps after it read `outcome` and not
226
+ # `conclusion` (continue-on-error rewrites conclusion to success).
227
+ continue-on-error: true
228
+ run: |
229
+ nix develop .#build --command bash -c '
230
+ set -euo pipefail
231
+ bun run lint
232
+ bun run typecheck
233
+ bun run test:unit
234
+ '
235
+
236
+ - name: Land the bump on main (gate green)
237
+ if: steps.bump.outputs.changed == '1' && steps.gate.outcome == 'success'
207
238
  run: |
208
239
  set -euo pipefail
209
240
  git config user.name "github-actions[bot]"
@@ -230,6 +261,55 @@ jobs:
230
261
  done
231
262
  echo "push still failing after 5 rebase attempts"; exit 1
232
263
 
264
+ - name: Open a reconciliation PR (gate red)
265
+ if: steps.bump.outputs.changed == '1' && steps.gate.outcome == 'failure'
266
+ env:
267
+ API_TOKEN: ${{ secrets.CI_DISPATCH_TOKEN }}
268
+ run: |
269
+ set -euo pipefail
270
+ branch="upgrade-latest/nightly"
271
+ git config user.name "github-actions[bot]"
272
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
273
+ git checkout -b "$branch"
274
+ git add -A
275
+ git commit -m "chore: bump Norsk nightly + ctl pins to latest channel (needs reconciliation)
276
+
277
+ ${{ steps.bump.outputs.summary }}"
278
+ git push -f origin "$branch"
279
+ title="Bump Norsk nightly + ctl pins to latest channel"
280
+ body="$(printf '%s\n' \
281
+ "Automated nightly pin bump: the Norsk nightly channel moved and the coupled" \
282
+ "pins came with it -- but the product gate is **red**, so this bump needs a" \
283
+ "human before it reaches main." \
284
+ "" \
285
+ " ${{ steps.bump.outputs.summary }}" \
286
+ "" \
287
+ "A red gate here usually means the new Norsk nightly is bad, or genuinely" \
288
+ "changed something the product depends on. Run it to see which:" \
289
+ "" \
290
+ " bun run lint && bun run typecheck && bun run test:unit" \
291
+ "" \
292
+ "Fix it on this branch and merge, or close this PR to skip the bump -- the" \
293
+ "next nightly run reopens it against whatever the channel holds then.")"
294
+ # gh is not on the self-hosted runner PATH (it lives only inside
295
+ # `nix develop`, and this step runs outside it), so create/update the PR
296
+ # straight against the REST API with the same PAT that pushed the branch.
297
+ # The PAT (not GITHUB_TOKEN) is what lets the PR's own checks run, which
298
+ # is the whole point: the bump is judged before it lands, not after.
299
+ repo="${{ github.repository }}"; owner="${repo%%/*}"
300
+ api="https://api.github.com/repos/$repo/pulls"
301
+ curl_api=(--fail-with-body -sS -H "Authorization: Bearer $API_TOKEN" -H "Accept: application/vnd.github+json")
302
+ existing="$(curl "${curl_api[@]}" "$api?head=$owner:$branch&state=open" | jq -r '.[0].number // empty')"
303
+ if [ -n "$existing" ]; then
304
+ curl "${curl_api[@]}" -X PATCH "$api/$existing" \
305
+ --data "$(jq -nc --arg t "$title" --arg b "$body" '{title:$t, body:$b}')" > /dev/null
306
+ echo "updated PR #$existing"
307
+ else
308
+ curl "${curl_api[@]}" -X POST "$api" \
309
+ --data "$(jq -nc --arg t "$title" --arg b "$body" --arg h "$branch" \
310
+ '{title:$t, body:$b, head:$h, base:"main"}')" | jq -r '"opened PR #\(.number)"'
311
+ fi
312
+
233
313
  - name: Report "already fresh"
234
314
  if: steps.bump.outputs.changed != '1'
235
315
  run: echo "Nightly pins already current — nothing to bump."
@@ -243,13 +323,27 @@ jobs:
243
323
  if: ${{ !cancelled() }}
244
324
  runs-on: x64
245
325
  steps:
246
- # clean: false -- the integration suite (shared x64 pool) leaves root-owned
247
- # bind-mount dirs under test-temp/ that a non-root git clean can't remove
248
- # (EACCES). These jobs never need those files; skip the clean so an innocent
249
- # bystander checkout doesn't choke on another workflow's leftovers.
326
+ # Root-owned leftovers under test-temp/ are what `clean: false` used to route
327
+ # around: the integration suite bind-mounts host dirs into containers, and a
328
+ # non-root git clean then EACCESes on what they wrote -- taking down whatever
329
+ # innocent job checked out next. Skipping the clean cured that symptom and
330
+ # caused another: every gitignored artifact survived between runs, so a test
331
+ # could pass on build output an earlier job left behind and go red the first
332
+ # time it landed on a cold runner (probe, 2026-09-01). Clear the leftovers
333
+ # from a root container FIRST, and the checkout can clean properly again.
334
+ # Best-effort by construction: no docker, no test-temp, or a failed run all
335
+ # fall through without failing the job.
336
+ - name: Clear stale root-owned test-temp (pre-checkout)
337
+ run: |
338
+ set -uo pipefail
339
+ tt="$GITHUB_WORKSPACE/test-temp"
340
+ [ -d "$tt" ] || exit 0
341
+ docker run --rm --user 0:0 -v "$tt":/t alpine sh \
342
+ -c 'rm -rf /t/* /t/.[!.]* 2>/dev/null || true' || rm -rf "$tt"/* 2>/dev/null || true
343
+
250
344
  - uses: actions/checkout@v5
251
345
  with:
252
- clean: false
346
+ clean: true
253
347
  - id: meta
254
348
  run: |
255
349
  if [ "${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}" = "true" ]; then
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@norskvideo/ctl-dev-kit",
3
- "version": "0.1.76",
3
+ "version": "0.1.78",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "./create-product": "./create-product/create-product.ts",