@norskvideo/ctl-dev-kit 0.1.84 → 0.1.86
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/conventions/build-docs.yml +102 -21
- package/conventions/build-image.yml +204 -52
- package/conventions/check-drift.ts +37 -0
- package/conventions/checks.yml +153 -39
- package/conventions/integration-testing.md +40 -0
- package/conventions/integration.yml +435 -0
- package/conventions/publish-docs.yml +102 -21
- package/conventions/smoke.yml +102 -13
- package/conventions/sync-ctl-packages.yml +51 -13
- package/conventions/sync-dev-kit.yml +102 -26
- package/conventions/sync-drift.ts +30 -0
- package/conventions/upgrade-latest.yml +102 -26
- package/conventions/workflow-shell.ts +136 -0
- package/package.json +1 -1
package/conventions/smoke.yml
CHANGED
|
@@ -52,17 +52,61 @@ jobs:
|
|
|
52
52
|
- runner: '["ar-arm-vm"]'
|
|
53
53
|
runs-on: ${{ fromJSON(matrix.runner) }}
|
|
54
54
|
steps:
|
|
55
|
-
# BEFORE checkout:
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
#
|
|
59
|
-
|
|
55
|
+
# Runs BEFORE checkout, and does two things: points the harness's temp base
|
|
56
|
+
# out of the workspace for every later step in this job, and clears whatever
|
|
57
|
+
# the old repo-local default left behind on this runner. The body carries the
|
|
58
|
+
# why. It fails the job only when legacy litter survives -- which would take
|
|
59
|
+
# the checkout down a step later anyway, but report nothing useful.
|
|
60
|
+
- name: Route test-temp out of the workspace (pre-checkout)
|
|
60
61
|
run: |
|
|
61
62
|
set -uo pipefail
|
|
63
|
+
# These runners are docker-OUTSIDE-of-docker, so a bind-mount source the
|
|
64
|
+
# HOST daemon has not seen before is created by dockerd, as ROOT, on the
|
|
65
|
+
# host -- even when the launch passes --container-user (reproduced
|
|
66
|
+
# 2026-09-04). The harness's default base is repo-local for an OrbStack
|
|
67
|
+
# inotify constraint that binds macOS dev and nothing here, so every run
|
|
68
|
+
# seeded the CHECKOUT with root-owned dirs a non-root `git clean -ffdx`
|
|
69
|
+
# cannot remove. That took commentary's checks AND integration red on
|
|
70
|
+
# 2026-09-03. Use the sanctioned override, per-run and outside the
|
|
71
|
+
# checkout -- as 8ff17a8d did in June for one debug workflow, and stopped.
|
|
72
|
+
base="$RUNNER_TEMP/nctt-$GITHUB_RUN_ID-$GITHUB_RUN_ATTEMPT"
|
|
73
|
+
mkdir -p "$base"
|
|
74
|
+
echo "NORSK_CTL_TEST_TMP=$base" >> "$GITHUB_ENV"
|
|
75
|
+
# Yesterday's bases hold dockerd-created root dirs, so a non-root rm
|
|
76
|
+
# EACCESes. They cannot wedge a checkout, but they do fill the disk.
|
|
77
|
+
for old in $(find "$RUNNER_TEMP" -maxdepth 1 -name 'nctt-*' -mtime +0 -printf '%f\n' 2>/dev/null || true); do
|
|
78
|
+
docker run --rm --user 0:0 -v "$RUNNER_TEMP":/t alpine sh -c 'rm -rf -- "/t/$1"' sh "$old" >/dev/null 2>&1 || true
|
|
79
|
+
done
|
|
80
|
+
# LEGACY: a runner that ran the old default still carries a workspace
|
|
81
|
+
# test-temp, and a checkout with clean:true dies on it. Reap the holders
|
|
82
|
+
# first -- rm cannot unlink a live mountpoint even as root (EBUSY), and a
|
|
83
|
+
# RESTARTING leaked container re-creates the path as root seconds after
|
|
84
|
+
# any clean. Match on mount SOURCE, not a label: the holders are untracked
|
|
85
|
+
# by construction, because a killed run labels nothing.
|
|
62
86
|
tt="$GITHUB_WORKSPACE/test-temp"
|
|
63
87
|
[ -d "$tt" ] || exit 0
|
|
64
|
-
|
|
65
|
-
|
|
88
|
+
stuck=""
|
|
89
|
+
for c in $(docker ps -aq 2>/dev/null || true); do
|
|
90
|
+
if docker inspect -f '{{range .Mounts}}{{println .Source}}{{end}}' "$c" 2>/dev/null | grep -q "^$tt/"; then
|
|
91
|
+
stuck="$stuck $c"
|
|
92
|
+
fi
|
|
93
|
+
done
|
|
94
|
+
if [ -n "$stuck" ]; then
|
|
95
|
+
echo "reaping containers holding mounts under $tt:$stuck"
|
|
96
|
+
docker rm -f $stuck || true
|
|
97
|
+
fi
|
|
98
|
+
# The DIRECTORY, not just its contents: an empty but root-owned base
|
|
99
|
+
# still EACCESes a later mkdtemp. Mounting the PARENT is what lets a root
|
|
100
|
+
# container unlink the leaf.
|
|
101
|
+
docker run --rm --user 0:0 -v "$GITHUB_WORKSPACE":/w alpine \
|
|
102
|
+
sh -c 'rm -rf /w/test-temp' || rm -rf "$tt" || true
|
|
103
|
+
# Fail HERE if it survived. The checkout fails on it either way, but
|
|
104
|
+
# reports only an EACCES rmdir with no clue what was holding the path.
|
|
105
|
+
if [ -e "$tt" ]; then
|
|
106
|
+
echo "::error::$tt survived the pre-checkout clean"
|
|
107
|
+
ls -lan "$tt" || true
|
|
108
|
+
exit 1
|
|
109
|
+
fi
|
|
66
110
|
|
|
67
111
|
- uses: actions/checkout@v5
|
|
68
112
|
with:
|
|
@@ -246,16 +290,61 @@ jobs:
|
|
|
246
290
|
if: ${{ !cancelled() && github.event_name != 'repository_dispatch' }}
|
|
247
291
|
runs-on: x64
|
|
248
292
|
steps:
|
|
249
|
-
#
|
|
250
|
-
#
|
|
251
|
-
#
|
|
252
|
-
|
|
293
|
+
# Runs BEFORE checkout, and does two things: points the harness's temp base
|
|
294
|
+
# out of the workspace for every later step in this job, and clears whatever
|
|
295
|
+
# the old repo-local default left behind on this runner. The body carries the
|
|
296
|
+
# why. It fails the job only when legacy litter survives -- which would take
|
|
297
|
+
# the checkout down a step later anyway, but report nothing useful.
|
|
298
|
+
- name: Route test-temp out of the workspace (pre-checkout)
|
|
253
299
|
run: |
|
|
254
300
|
set -uo pipefail
|
|
301
|
+
# These runners are docker-OUTSIDE-of-docker, so a bind-mount source the
|
|
302
|
+
# HOST daemon has not seen before is created by dockerd, as ROOT, on the
|
|
303
|
+
# host -- even when the launch passes --container-user (reproduced
|
|
304
|
+
# 2026-09-04). The harness's default base is repo-local for an OrbStack
|
|
305
|
+
# inotify constraint that binds macOS dev and nothing here, so every run
|
|
306
|
+
# seeded the CHECKOUT with root-owned dirs a non-root `git clean -ffdx`
|
|
307
|
+
# cannot remove. That took commentary's checks AND integration red on
|
|
308
|
+
# 2026-09-03. Use the sanctioned override, per-run and outside the
|
|
309
|
+
# checkout -- as 8ff17a8d did in June for one debug workflow, and stopped.
|
|
310
|
+
base="$RUNNER_TEMP/nctt-$GITHUB_RUN_ID-$GITHUB_RUN_ATTEMPT"
|
|
311
|
+
mkdir -p "$base"
|
|
312
|
+
echo "NORSK_CTL_TEST_TMP=$base" >> "$GITHUB_ENV"
|
|
313
|
+
# Yesterday's bases hold dockerd-created root dirs, so a non-root rm
|
|
314
|
+
# EACCESes. They cannot wedge a checkout, but they do fill the disk.
|
|
315
|
+
for old in $(find "$RUNNER_TEMP" -maxdepth 1 -name 'nctt-*' -mtime +0 -printf '%f\n' 2>/dev/null || true); do
|
|
316
|
+
docker run --rm --user 0:0 -v "$RUNNER_TEMP":/t alpine sh -c 'rm -rf -- "/t/$1"' sh "$old" >/dev/null 2>&1 || true
|
|
317
|
+
done
|
|
318
|
+
# LEGACY: a runner that ran the old default still carries a workspace
|
|
319
|
+
# test-temp, and a checkout with clean:true dies on it. Reap the holders
|
|
320
|
+
# first -- rm cannot unlink a live mountpoint even as root (EBUSY), and a
|
|
321
|
+
# RESTARTING leaked container re-creates the path as root seconds after
|
|
322
|
+
# any clean. Match on mount SOURCE, not a label: the holders are untracked
|
|
323
|
+
# by construction, because a killed run labels nothing.
|
|
255
324
|
tt="$GITHUB_WORKSPACE/test-temp"
|
|
256
325
|
[ -d "$tt" ] || exit 0
|
|
257
|
-
|
|
258
|
-
|
|
326
|
+
stuck=""
|
|
327
|
+
for c in $(docker ps -aq 2>/dev/null || true); do
|
|
328
|
+
if docker inspect -f '{{range .Mounts}}{{println .Source}}{{end}}' "$c" 2>/dev/null | grep -q "^$tt/"; then
|
|
329
|
+
stuck="$stuck $c"
|
|
330
|
+
fi
|
|
331
|
+
done
|
|
332
|
+
if [ -n "$stuck" ]; then
|
|
333
|
+
echo "reaping containers holding mounts under $tt:$stuck"
|
|
334
|
+
docker rm -f $stuck || true
|
|
335
|
+
fi
|
|
336
|
+
# The DIRECTORY, not just its contents: an empty but root-owned base
|
|
337
|
+
# still EACCESes a later mkdtemp. Mounting the PARENT is what lets a root
|
|
338
|
+
# container unlink the leaf.
|
|
339
|
+
docker run --rm --user 0:0 -v "$GITHUB_WORKSPACE":/w alpine \
|
|
340
|
+
sh -c 'rm -rf /w/test-temp' || rm -rf "$tt" || true
|
|
341
|
+
# Fail HERE if it survived. The checkout fails on it either way, but
|
|
342
|
+
# reports only an EACCES rmdir with no clue what was holding the path.
|
|
343
|
+
if [ -e "$tt" ]; then
|
|
344
|
+
echo "::error::$tt survived the pre-checkout clean"
|
|
345
|
+
ls -lan "$tt" || true
|
|
346
|
+
exit 1
|
|
347
|
+
fi
|
|
259
348
|
|
|
260
349
|
- uses: actions/checkout@v5
|
|
261
350
|
with:
|
|
@@ -41,23 +41,61 @@ jobs:
|
|
|
41
41
|
sync:
|
|
42
42
|
runs-on: x64
|
|
43
43
|
steps:
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
|
|
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)
|
|
44
|
+
# Runs BEFORE checkout, and does two things: points the harness's temp base
|
|
45
|
+
# out of the workspace for every later step in this job, and clears whatever
|
|
46
|
+
# the old repo-local default left behind on this runner. The body carries the
|
|
47
|
+
# why. It fails the job only when legacy litter survives -- which would take
|
|
48
|
+
# the checkout down a step later anyway, but report nothing useful.
|
|
49
|
+
- name: Route test-temp out of the workspace (pre-checkout)
|
|
55
50
|
run: |
|
|
56
51
|
set -uo pipefail
|
|
52
|
+
# These runners are docker-OUTSIDE-of-docker, so a bind-mount source the
|
|
53
|
+
# HOST daemon has not seen before is created by dockerd, as ROOT, on the
|
|
54
|
+
# host -- even when the launch passes --container-user (reproduced
|
|
55
|
+
# 2026-09-04). The harness's default base is repo-local for an OrbStack
|
|
56
|
+
# inotify constraint that binds macOS dev and nothing here, so every run
|
|
57
|
+
# seeded the CHECKOUT with root-owned dirs a non-root `git clean -ffdx`
|
|
58
|
+
# cannot remove. That took commentary's checks AND integration red on
|
|
59
|
+
# 2026-09-03. Use the sanctioned override, per-run and outside the
|
|
60
|
+
# checkout -- as 8ff17a8d did in June for one debug workflow, and stopped.
|
|
61
|
+
base="$RUNNER_TEMP/nctt-$GITHUB_RUN_ID-$GITHUB_RUN_ATTEMPT"
|
|
62
|
+
mkdir -p "$base"
|
|
63
|
+
echo "NORSK_CTL_TEST_TMP=$base" >> "$GITHUB_ENV"
|
|
64
|
+
# Yesterday's bases hold dockerd-created root dirs, so a non-root rm
|
|
65
|
+
# EACCESes. They cannot wedge a checkout, but they do fill the disk.
|
|
66
|
+
for old in $(find "$RUNNER_TEMP" -maxdepth 1 -name 'nctt-*' -mtime +0 -printf '%f\n' 2>/dev/null || true); do
|
|
67
|
+
docker run --rm --user 0:0 -v "$RUNNER_TEMP":/t alpine sh -c 'rm -rf -- "/t/$1"' sh "$old" >/dev/null 2>&1 || true
|
|
68
|
+
done
|
|
69
|
+
# LEGACY: a runner that ran the old default still carries a workspace
|
|
70
|
+
# test-temp, and a checkout with clean:true dies on it. Reap the holders
|
|
71
|
+
# first -- rm cannot unlink a live mountpoint even as root (EBUSY), and a
|
|
72
|
+
# RESTARTING leaked container re-creates the path as root seconds after
|
|
73
|
+
# any clean. Match on mount SOURCE, not a label: the holders are untracked
|
|
74
|
+
# by construction, because a killed run labels nothing.
|
|
57
75
|
tt="$GITHUB_WORKSPACE/test-temp"
|
|
58
76
|
[ -d "$tt" ] || exit 0
|
|
59
|
-
|
|
60
|
-
|
|
77
|
+
stuck=""
|
|
78
|
+
for c in $(docker ps -aq 2>/dev/null || true); do
|
|
79
|
+
if docker inspect -f '{{range .Mounts}}{{println .Source}}{{end}}' "$c" 2>/dev/null | grep -q "^$tt/"; then
|
|
80
|
+
stuck="$stuck $c"
|
|
81
|
+
fi
|
|
82
|
+
done
|
|
83
|
+
if [ -n "$stuck" ]; then
|
|
84
|
+
echo "reaping containers holding mounts under $tt:$stuck"
|
|
85
|
+
docker rm -f $stuck || true
|
|
86
|
+
fi
|
|
87
|
+
# The DIRECTORY, not just its contents: an empty but root-owned base
|
|
88
|
+
# still EACCESes a later mkdtemp. Mounting the PARENT is what lets a root
|
|
89
|
+
# container unlink the leaf.
|
|
90
|
+
docker run --rm --user 0:0 -v "$GITHUB_WORKSPACE":/w alpine \
|
|
91
|
+
sh -c 'rm -rf /w/test-temp' || rm -rf "$tt" || true
|
|
92
|
+
# Fail HERE if it survived. The checkout fails on it either way, but
|
|
93
|
+
# reports only an EACCES rmdir with no clue what was holding the path.
|
|
94
|
+
if [ -e "$tt" ]; then
|
|
95
|
+
echo "::error::$tt survived the pre-checkout clean"
|
|
96
|
+
ls -lan "$tt" || true
|
|
97
|
+
exit 1
|
|
98
|
+
fi
|
|
61
99
|
|
|
62
100
|
- uses: actions/checkout@v5
|
|
63
101
|
with:
|
|
@@ -43,23 +43,61 @@ jobs:
|
|
|
43
43
|
sync:
|
|
44
44
|
runs-on: x64
|
|
45
45
|
steps:
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
|
|
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)
|
|
46
|
+
# Runs BEFORE checkout, and does two things: points the harness's temp base
|
|
47
|
+
# out of the workspace for every later step in this job, and clears whatever
|
|
48
|
+
# the old repo-local default left behind on this runner. The body carries the
|
|
49
|
+
# why. It fails the job only when legacy litter survives -- which would take
|
|
50
|
+
# the checkout down a step later anyway, but report nothing useful.
|
|
51
|
+
- name: Route test-temp out of the workspace (pre-checkout)
|
|
57
52
|
run: |
|
|
58
53
|
set -uo pipefail
|
|
54
|
+
# These runners are docker-OUTSIDE-of-docker, so a bind-mount source the
|
|
55
|
+
# HOST daemon has not seen before is created by dockerd, as ROOT, on the
|
|
56
|
+
# host -- even when the launch passes --container-user (reproduced
|
|
57
|
+
# 2026-09-04). The harness's default base is repo-local for an OrbStack
|
|
58
|
+
# inotify constraint that binds macOS dev and nothing here, so every run
|
|
59
|
+
# seeded the CHECKOUT with root-owned dirs a non-root `git clean -ffdx`
|
|
60
|
+
# cannot remove. That took commentary's checks AND integration red on
|
|
61
|
+
# 2026-09-03. Use the sanctioned override, per-run and outside the
|
|
62
|
+
# checkout -- as 8ff17a8d did in June for one debug workflow, and stopped.
|
|
63
|
+
base="$RUNNER_TEMP/nctt-$GITHUB_RUN_ID-$GITHUB_RUN_ATTEMPT"
|
|
64
|
+
mkdir -p "$base"
|
|
65
|
+
echo "NORSK_CTL_TEST_TMP=$base" >> "$GITHUB_ENV"
|
|
66
|
+
# Yesterday's bases hold dockerd-created root dirs, so a non-root rm
|
|
67
|
+
# EACCESes. They cannot wedge a checkout, but they do fill the disk.
|
|
68
|
+
for old in $(find "$RUNNER_TEMP" -maxdepth 1 -name 'nctt-*' -mtime +0 -printf '%f\n' 2>/dev/null || true); do
|
|
69
|
+
docker run --rm --user 0:0 -v "$RUNNER_TEMP":/t alpine sh -c 'rm -rf -- "/t/$1"' sh "$old" >/dev/null 2>&1 || true
|
|
70
|
+
done
|
|
71
|
+
# LEGACY: a runner that ran the old default still carries a workspace
|
|
72
|
+
# test-temp, and a checkout with clean:true dies on it. Reap the holders
|
|
73
|
+
# first -- rm cannot unlink a live mountpoint even as root (EBUSY), and a
|
|
74
|
+
# RESTARTING leaked container re-creates the path as root seconds after
|
|
75
|
+
# any clean. Match on mount SOURCE, not a label: the holders are untracked
|
|
76
|
+
# by construction, because a killed run labels nothing.
|
|
59
77
|
tt="$GITHUB_WORKSPACE/test-temp"
|
|
60
78
|
[ -d "$tt" ] || exit 0
|
|
61
|
-
|
|
62
|
-
|
|
79
|
+
stuck=""
|
|
80
|
+
for c in $(docker ps -aq 2>/dev/null || true); do
|
|
81
|
+
if docker inspect -f '{{range .Mounts}}{{println .Source}}{{end}}' "$c" 2>/dev/null | grep -q "^$tt/"; then
|
|
82
|
+
stuck="$stuck $c"
|
|
83
|
+
fi
|
|
84
|
+
done
|
|
85
|
+
if [ -n "$stuck" ]; then
|
|
86
|
+
echo "reaping containers holding mounts under $tt:$stuck"
|
|
87
|
+
docker rm -f $stuck || true
|
|
88
|
+
fi
|
|
89
|
+
# The DIRECTORY, not just its contents: an empty but root-owned base
|
|
90
|
+
# still EACCESes a later mkdtemp. Mounting the PARENT is what lets a root
|
|
91
|
+
# container unlink the leaf.
|
|
92
|
+
docker run --rm --user 0:0 -v "$GITHUB_WORKSPACE":/w alpine \
|
|
93
|
+
sh -c 'rm -rf /w/test-temp' || rm -rf "$tt" || true
|
|
94
|
+
# Fail HERE if it survived. The checkout fails on it either way, but
|
|
95
|
+
# reports only an EACCES rmdir with no clue what was holding the path.
|
|
96
|
+
if [ -e "$tt" ]; then
|
|
97
|
+
echo "::error::$tt survived the pre-checkout clean"
|
|
98
|
+
ls -lan "$tt" || true
|
|
99
|
+
exit 1
|
|
100
|
+
fi
|
|
63
101
|
|
|
64
102
|
- uses: actions/checkout@v5
|
|
65
103
|
with:
|
|
@@ -201,23 +239,61 @@ jobs:
|
|
|
201
239
|
if: ${{ !cancelled() }}
|
|
202
240
|
runs-on: x64
|
|
203
241
|
steps:
|
|
204
|
-
#
|
|
205
|
-
#
|
|
206
|
-
#
|
|
207
|
-
#
|
|
208
|
-
#
|
|
209
|
-
|
|
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)
|
|
242
|
+
# Runs BEFORE checkout, and does two things: points the harness's temp base
|
|
243
|
+
# out of the workspace for every later step in this job, and clears whatever
|
|
244
|
+
# the old repo-local default left behind on this runner. The body carries the
|
|
245
|
+
# why. It fails the job only when legacy litter survives -- which would take
|
|
246
|
+
# the checkout down a step later anyway, but report nothing useful.
|
|
247
|
+
- name: Route test-temp out of the workspace (pre-checkout)
|
|
215
248
|
run: |
|
|
216
249
|
set -uo pipefail
|
|
250
|
+
# These runners are docker-OUTSIDE-of-docker, so a bind-mount source the
|
|
251
|
+
# HOST daemon has not seen before is created by dockerd, as ROOT, on the
|
|
252
|
+
# host -- even when the launch passes --container-user (reproduced
|
|
253
|
+
# 2026-09-04). The harness's default base is repo-local for an OrbStack
|
|
254
|
+
# inotify constraint that binds macOS dev and nothing here, so every run
|
|
255
|
+
# seeded the CHECKOUT with root-owned dirs a non-root `git clean -ffdx`
|
|
256
|
+
# cannot remove. That took commentary's checks AND integration red on
|
|
257
|
+
# 2026-09-03. Use the sanctioned override, per-run and outside the
|
|
258
|
+
# checkout -- as 8ff17a8d did in June for one debug workflow, and stopped.
|
|
259
|
+
base="$RUNNER_TEMP/nctt-$GITHUB_RUN_ID-$GITHUB_RUN_ATTEMPT"
|
|
260
|
+
mkdir -p "$base"
|
|
261
|
+
echo "NORSK_CTL_TEST_TMP=$base" >> "$GITHUB_ENV"
|
|
262
|
+
# Yesterday's bases hold dockerd-created root dirs, so a non-root rm
|
|
263
|
+
# EACCESes. They cannot wedge a checkout, but they do fill the disk.
|
|
264
|
+
for old in $(find "$RUNNER_TEMP" -maxdepth 1 -name 'nctt-*' -mtime +0 -printf '%f\n' 2>/dev/null || true); do
|
|
265
|
+
docker run --rm --user 0:0 -v "$RUNNER_TEMP":/t alpine sh -c 'rm -rf -- "/t/$1"' sh "$old" >/dev/null 2>&1 || true
|
|
266
|
+
done
|
|
267
|
+
# LEGACY: a runner that ran the old default still carries a workspace
|
|
268
|
+
# test-temp, and a checkout with clean:true dies on it. Reap the holders
|
|
269
|
+
# first -- rm cannot unlink a live mountpoint even as root (EBUSY), and a
|
|
270
|
+
# RESTARTING leaked container re-creates the path as root seconds after
|
|
271
|
+
# any clean. Match on mount SOURCE, not a label: the holders are untracked
|
|
272
|
+
# by construction, because a killed run labels nothing.
|
|
217
273
|
tt="$GITHUB_WORKSPACE/test-temp"
|
|
218
274
|
[ -d "$tt" ] || exit 0
|
|
219
|
-
|
|
220
|
-
|
|
275
|
+
stuck=""
|
|
276
|
+
for c in $(docker ps -aq 2>/dev/null || true); do
|
|
277
|
+
if docker inspect -f '{{range .Mounts}}{{println .Source}}{{end}}' "$c" 2>/dev/null | grep -q "^$tt/"; then
|
|
278
|
+
stuck="$stuck $c"
|
|
279
|
+
fi
|
|
280
|
+
done
|
|
281
|
+
if [ -n "$stuck" ]; then
|
|
282
|
+
echo "reaping containers holding mounts under $tt:$stuck"
|
|
283
|
+
docker rm -f $stuck || true
|
|
284
|
+
fi
|
|
285
|
+
# The DIRECTORY, not just its contents: an empty but root-owned base
|
|
286
|
+
# still EACCESes a later mkdtemp. Mounting the PARENT is what lets a root
|
|
287
|
+
# container unlink the leaf.
|
|
288
|
+
docker run --rm --user 0:0 -v "$GITHUB_WORKSPACE":/w alpine \
|
|
289
|
+
sh -c 'rm -rf /w/test-temp' || rm -rf "$tt" || true
|
|
290
|
+
# Fail HERE if it survived. The checkout fails on it either way, but
|
|
291
|
+
# reports only an EACCES rmdir with no clue what was holding the path.
|
|
292
|
+
if [ -e "$tt" ]; then
|
|
293
|
+
echo "::error::$tt survived the pre-checkout clean"
|
|
294
|
+
ls -lan "$tt" || true
|
|
295
|
+
exit 1
|
|
296
|
+
fi
|
|
221
297
|
|
|
222
298
|
- uses: actions/checkout@v5
|
|
223
299
|
with:
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
type CanonicalBytes,
|
|
26
26
|
CTL_HASH_LINE,
|
|
27
27
|
CTL_VERSION_LINE,
|
|
28
|
+
carriesIntegrationMarker,
|
|
28
29
|
END,
|
|
29
30
|
GITIGNORE_BEGIN,
|
|
30
31
|
GITIGNORE_END,
|
|
@@ -116,6 +117,33 @@ function syncSmoke(repoRoot: string, canonical: string, r: SyncReport): void {
|
|
|
116
117
|
writeIfChanged(path, out, rel, r.written);
|
|
117
118
|
}
|
|
118
119
|
|
|
120
|
+
// integration.yml: smoke.yml's twin, but only a copy that already carries the
|
|
121
|
+
// convention marker is rewritten — a hand-owned one is left untouched and named,
|
|
122
|
+
// because converging it is a deliberate per-repo change (hooks, a proving run),
|
|
123
|
+
// not something a nightly sync may do to a tier the release gate believes.
|
|
124
|
+
function syncIntegration(repoRoot: string, canonical: string, r: SyncReport): void {
|
|
125
|
+
const rel = ".github/workflows/integration.yml";
|
|
126
|
+
const path = join(repoRoot, rel);
|
|
127
|
+
if (!existsSync(path)) {
|
|
128
|
+
r.skipped.push(`${rel} (absent)`);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const existing = readFileSync(path, "utf8");
|
|
132
|
+
if (!carriesIntegrationMarker(existing)) {
|
|
133
|
+
r.skipped.push(`${rel} (hand-owned: no convention marker on line 1 — converge it deliberately)`);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const key = existing.match(/^\s*product:\s*(\S+)/m)?.[1];
|
|
137
|
+
if (!key || key === PRODUCT_SENTINEL) {
|
|
138
|
+
r.skipped.push(`${rel} (no product: key to preserve)`);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const runnerLine = existing.match(/^\s*-?\s*runner:.*$/m)?.[0];
|
|
142
|
+
let out = canonical.replace(PRODUCT_LINE, `$1${key}`);
|
|
143
|
+
if (runnerLine !== undefined) out = out.replace(RUNNER_LINE, () => runnerLine);
|
|
144
|
+
writeIfChanged(path, out, rel, r.written);
|
|
145
|
+
}
|
|
146
|
+
|
|
119
147
|
// flake.nix is verbatim except the dev-only ctl pin (ctlVersion + the four
|
|
120
148
|
// per-platform hashes), which floats per repo. Re-emit canonical STRUCTURE with
|
|
121
149
|
// the repo's pin restored in order — so a structural edit (e.g. a stray comment)
|
|
@@ -250,6 +278,7 @@ export function syncDrift(repoRoot: string, canonical: CanonicalBytes): SyncRepo
|
|
|
250
278
|
);
|
|
251
279
|
syncBuildImage(repoRoot, canonical.buildImage, r);
|
|
252
280
|
syncSmoke(repoRoot, canonical.smoke, r);
|
|
281
|
+
syncIntegration(repoRoot, canonical.integration, r);
|
|
253
282
|
|
|
254
283
|
// publish-docs.yml is optional and verbatim (no per-repo line) — only re-sync a
|
|
255
284
|
// repo that already carries it, unlike biome/tsconfig which every repo must have.
|
|
@@ -294,6 +323,7 @@ if (import.meta.main) {
|
|
|
294
323
|
gitignoreCore: readFileSync(join(dir, "gitignore.core"), "utf8"),
|
|
295
324
|
buildImage: readFileSync(join(dir, "build-image.yml"), "utf8"),
|
|
296
325
|
smoke: readFileSync(join(dir, "smoke.yml"), "utf8"),
|
|
326
|
+
integration: readFileSync(join(dir, "integration.yml"), "utf8"),
|
|
297
327
|
demoShim: readFileSync(join(dir, "demo.sh"), "utf8"),
|
|
298
328
|
};
|
|
299
329
|
const report = syncDrift(repoRoot, canonical);
|
|
@@ -45,23 +45,61 @@ jobs:
|
|
|
45
45
|
changed: ${{ steps.bump.outputs.changed }}
|
|
46
46
|
summary: ${{ steps.bump.outputs.summary }}
|
|
47
47
|
steps:
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
#
|
|
52
|
-
#
|
|
53
|
-
|
|
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)
|
|
48
|
+
# Runs BEFORE checkout, and does two things: points the harness's temp base
|
|
49
|
+
# out of the workspace for every later step in this job, and clears whatever
|
|
50
|
+
# the old repo-local default left behind on this runner. The body carries the
|
|
51
|
+
# why. It fails the job only when legacy litter survives -- which would take
|
|
52
|
+
# the checkout down a step later anyway, but report nothing useful.
|
|
53
|
+
- name: Route test-temp out of the workspace (pre-checkout)
|
|
59
54
|
run: |
|
|
60
55
|
set -uo pipefail
|
|
56
|
+
# These runners are docker-OUTSIDE-of-docker, so a bind-mount source the
|
|
57
|
+
# HOST daemon has not seen before is created by dockerd, as ROOT, on the
|
|
58
|
+
# host -- even when the launch passes --container-user (reproduced
|
|
59
|
+
# 2026-09-04). The harness's default base is repo-local for an OrbStack
|
|
60
|
+
# inotify constraint that binds macOS dev and nothing here, so every run
|
|
61
|
+
# seeded the CHECKOUT with root-owned dirs a non-root `git clean -ffdx`
|
|
62
|
+
# cannot remove. That took commentary's checks AND integration red on
|
|
63
|
+
# 2026-09-03. Use the sanctioned override, per-run and outside the
|
|
64
|
+
# checkout -- as 8ff17a8d did in June for one debug workflow, and stopped.
|
|
65
|
+
base="$RUNNER_TEMP/nctt-$GITHUB_RUN_ID-$GITHUB_RUN_ATTEMPT"
|
|
66
|
+
mkdir -p "$base"
|
|
67
|
+
echo "NORSK_CTL_TEST_TMP=$base" >> "$GITHUB_ENV"
|
|
68
|
+
# Yesterday's bases hold dockerd-created root dirs, so a non-root rm
|
|
69
|
+
# EACCESes. They cannot wedge a checkout, but they do fill the disk.
|
|
70
|
+
for old in $(find "$RUNNER_TEMP" -maxdepth 1 -name 'nctt-*' -mtime +0 -printf '%f\n' 2>/dev/null || true); do
|
|
71
|
+
docker run --rm --user 0:0 -v "$RUNNER_TEMP":/t alpine sh -c 'rm -rf -- "/t/$1"' sh "$old" >/dev/null 2>&1 || true
|
|
72
|
+
done
|
|
73
|
+
# LEGACY: a runner that ran the old default still carries a workspace
|
|
74
|
+
# test-temp, and a checkout with clean:true dies on it. Reap the holders
|
|
75
|
+
# first -- rm cannot unlink a live mountpoint even as root (EBUSY), and a
|
|
76
|
+
# RESTARTING leaked container re-creates the path as root seconds after
|
|
77
|
+
# any clean. Match on mount SOURCE, not a label: the holders are untracked
|
|
78
|
+
# by construction, because a killed run labels nothing.
|
|
61
79
|
tt="$GITHUB_WORKSPACE/test-temp"
|
|
62
80
|
[ -d "$tt" ] || exit 0
|
|
63
|
-
|
|
64
|
-
|
|
81
|
+
stuck=""
|
|
82
|
+
for c in $(docker ps -aq 2>/dev/null || true); do
|
|
83
|
+
if docker inspect -f '{{range .Mounts}}{{println .Source}}{{end}}' "$c" 2>/dev/null | grep -q "^$tt/"; then
|
|
84
|
+
stuck="$stuck $c"
|
|
85
|
+
fi
|
|
86
|
+
done
|
|
87
|
+
if [ -n "$stuck" ]; then
|
|
88
|
+
echo "reaping containers holding mounts under $tt:$stuck"
|
|
89
|
+
docker rm -f $stuck || true
|
|
90
|
+
fi
|
|
91
|
+
# The DIRECTORY, not just its contents: an empty but root-owned base
|
|
92
|
+
# still EACCESes a later mkdtemp. Mounting the PARENT is what lets a root
|
|
93
|
+
# container unlink the leaf.
|
|
94
|
+
docker run --rm --user 0:0 -v "$GITHUB_WORKSPACE":/w alpine \
|
|
95
|
+
sh -c 'rm -rf /w/test-temp' || rm -rf "$tt" || true
|
|
96
|
+
# Fail HERE if it survived. The checkout fails on it either way, but
|
|
97
|
+
# reports only an EACCES rmdir with no clue what was holding the path.
|
|
98
|
+
if [ -e "$tt" ]; then
|
|
99
|
+
echo "::error::$tt survived the pre-checkout clean"
|
|
100
|
+
ls -lan "$tt" || true
|
|
101
|
+
exit 1
|
|
102
|
+
fi
|
|
65
103
|
|
|
66
104
|
- uses: actions/checkout@v5
|
|
67
105
|
with:
|
|
@@ -323,23 +361,61 @@ jobs:
|
|
|
323
361
|
if: ${{ !cancelled() }}
|
|
324
362
|
runs-on: x64
|
|
325
363
|
steps:
|
|
326
|
-
#
|
|
327
|
-
#
|
|
328
|
-
#
|
|
329
|
-
#
|
|
330
|
-
#
|
|
331
|
-
|
|
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)
|
|
364
|
+
# Runs BEFORE checkout, and does two things: points the harness's temp base
|
|
365
|
+
# out of the workspace for every later step in this job, and clears whatever
|
|
366
|
+
# the old repo-local default left behind on this runner. The body carries the
|
|
367
|
+
# why. It fails the job only when legacy litter survives -- which would take
|
|
368
|
+
# the checkout down a step later anyway, but report nothing useful.
|
|
369
|
+
- name: Route test-temp out of the workspace (pre-checkout)
|
|
337
370
|
run: |
|
|
338
371
|
set -uo pipefail
|
|
372
|
+
# These runners are docker-OUTSIDE-of-docker, so a bind-mount source the
|
|
373
|
+
# HOST daemon has not seen before is created by dockerd, as ROOT, on the
|
|
374
|
+
# host -- even when the launch passes --container-user (reproduced
|
|
375
|
+
# 2026-09-04). The harness's default base is repo-local for an OrbStack
|
|
376
|
+
# inotify constraint that binds macOS dev and nothing here, so every run
|
|
377
|
+
# seeded the CHECKOUT with root-owned dirs a non-root `git clean -ffdx`
|
|
378
|
+
# cannot remove. That took commentary's checks AND integration red on
|
|
379
|
+
# 2026-09-03. Use the sanctioned override, per-run and outside the
|
|
380
|
+
# checkout -- as 8ff17a8d did in June for one debug workflow, and stopped.
|
|
381
|
+
base="$RUNNER_TEMP/nctt-$GITHUB_RUN_ID-$GITHUB_RUN_ATTEMPT"
|
|
382
|
+
mkdir -p "$base"
|
|
383
|
+
echo "NORSK_CTL_TEST_TMP=$base" >> "$GITHUB_ENV"
|
|
384
|
+
# Yesterday's bases hold dockerd-created root dirs, so a non-root rm
|
|
385
|
+
# EACCESes. They cannot wedge a checkout, but they do fill the disk.
|
|
386
|
+
for old in $(find "$RUNNER_TEMP" -maxdepth 1 -name 'nctt-*' -mtime +0 -printf '%f\n' 2>/dev/null || true); do
|
|
387
|
+
docker run --rm --user 0:0 -v "$RUNNER_TEMP":/t alpine sh -c 'rm -rf -- "/t/$1"' sh "$old" >/dev/null 2>&1 || true
|
|
388
|
+
done
|
|
389
|
+
# LEGACY: a runner that ran the old default still carries a workspace
|
|
390
|
+
# test-temp, and a checkout with clean:true dies on it. Reap the holders
|
|
391
|
+
# first -- rm cannot unlink a live mountpoint even as root (EBUSY), and a
|
|
392
|
+
# RESTARTING leaked container re-creates the path as root seconds after
|
|
393
|
+
# any clean. Match on mount SOURCE, not a label: the holders are untracked
|
|
394
|
+
# by construction, because a killed run labels nothing.
|
|
339
395
|
tt="$GITHUB_WORKSPACE/test-temp"
|
|
340
396
|
[ -d "$tt" ] || exit 0
|
|
341
|
-
|
|
342
|
-
|
|
397
|
+
stuck=""
|
|
398
|
+
for c in $(docker ps -aq 2>/dev/null || true); do
|
|
399
|
+
if docker inspect -f '{{range .Mounts}}{{println .Source}}{{end}}' "$c" 2>/dev/null | grep -q "^$tt/"; then
|
|
400
|
+
stuck="$stuck $c"
|
|
401
|
+
fi
|
|
402
|
+
done
|
|
403
|
+
if [ -n "$stuck" ]; then
|
|
404
|
+
echo "reaping containers holding mounts under $tt:$stuck"
|
|
405
|
+
docker rm -f $stuck || true
|
|
406
|
+
fi
|
|
407
|
+
# The DIRECTORY, not just its contents: an empty but root-owned base
|
|
408
|
+
# still EACCESes a later mkdtemp. Mounting the PARENT is what lets a root
|
|
409
|
+
# container unlink the leaf.
|
|
410
|
+
docker run --rm --user 0:0 -v "$GITHUB_WORKSPACE":/w alpine \
|
|
411
|
+
sh -c 'rm -rf /w/test-temp' || rm -rf "$tt" || true
|
|
412
|
+
# Fail HERE if it survived. The checkout fails on it either way, but
|
|
413
|
+
# reports only an EACCES rmdir with no clue what was holding the path.
|
|
414
|
+
if [ -e "$tt" ]; then
|
|
415
|
+
echo "::error::$tt survived the pre-checkout clean"
|
|
416
|
+
ls -lan "$tt" || true
|
|
417
|
+
exit 1
|
|
418
|
+
fi
|
|
343
419
|
|
|
344
420
|
- uses: actions/checkout@v5
|
|
345
421
|
with:
|