Commitc6b42567Recorded1 Sep 2026Repositorysigil-http

Act on the adversarial review of the pipeline definition

Message

Seven fixes, two of them defects rather than prose:

  • The floor read the FIRST banner-shaped line in the captured test log, not the summary. The step captures 2>&1, so a test that prints a line shaped like "Files: 9 of 9 run" is read instead of the real totals; the floor stayed green on a run that had fallen from 5 files/167 tests to 2/2. Reads the last match now.
  • Where a repo has one test file, files >= 1 cannot fail: the Files: line is only printed when discovery found something, and every discovered file increments the completed counter on every outcome path. Kept for uniformity across repos, and said so in the two repos where it applies rather than leaving a decorative check unlabelled.

And five claims in the header that did not survive checking: the deps link farm is IN the workspace (its content is under $HOME, which is why a restored copy dangles); the timeout survives the clamp only while the ceiling key is at its default; exit 2 and exit 1 are the same red to kiln, so SETUP-FAILED is a distinction in the log and not in run status; the runner pool figures were unverifiable and are gone; the effective timeout is printed once only on the persistent-worker path.

Also records that a failing step emits no section-end marker, that artifact steps emit no markers at all, and the one case the floor mislabels (a repo with native tests losing its whole .sgl suite).

Changed
 .kiln/ci.sgv | 139 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------------------------
 1 file changed, 90 insertions(+), 49 deletions(-)
Diff
.kiln/ci.sgvmodified
@@ -3,11 +3,14 @@
3
;;; No `.kiln/ci.sh`. The steps below ARE the pipeline; there is no shell
4
;;; script wrapping them. This is the pilot shape intended to be copied
5
;;; across the extracted repos, so the reasoning is recorded here rather
−6
;;; than only in the task note.
+6
;;; than only in the task note. Every claim below was measured on kiln
+7
;;; ad9b9c5 and sigil-http 0b4617a, 2026-09-01, and re-checked by an
+8
;;; adversarial reviewer that found four of them wrong the first time.
9
;;;
10
;;; `on:` IS MANDATORY AND ITS ABSENCE IS SILENT. A workflow with no `on:`
11
;;; matches no event, produces no error and no run, and reads exactly like
−10
;;; a controller fault rather than like this file.
+12
;;; a controller fault rather than like this file. It does not even lint:
+13
;;; a pipeline with no `on:` reports "OK -- no problems found".
14
;;;
15
;;; NO `branch:` FILTER ON `push`, deliberately. A filter would narrow
16
;;; coverage to one ref; this library has none to spare. `(pull-request)`
@@ -16,46 +19,57 @@
19
;;; kind has ever been observed on this controller, and declaring coverage
20
;;; nobody has seen fire is how a green comes to mean less than it looks.
21
;;;
−19
;;; SINGLE NODE, FOUR NAMED STEPS -- not four jobs. Every node gets its
−20
;;; OWN workspace and its own delivered tree (runner.sgl `provide-source!`
−21
;;; per node), and `sigil deps install` writes its state under $HOME, not
−22
;;; into the workspace. Split across nodes, `build` and `test` would each
−23
;;; re-deliver the tree and depend on the worker's $HOME surviving between
−24
;;; leases -- an unstated dependency on worker statefulness -- or else ship
−25
;;; the whole dependency tree between nodes as artifacts, which is the
−26
;;; artifact fan-in path kiln's own `.kiln/ci.sgv` records as unproven.
−27
;;; Three nodes also take three leases against a pool of 4 at
−28
;;; max_concurrent 2, to buy status for stages that are strictly serial.
+22
;;; SINGLE NODE, FOUR NAMED STEPS -- not four jobs. Every node gets its own
+23
;;; worker and its own workspace, with the tree delivered again per node
+24
;;; (`runner.sgl` takes `backend-workspace` and calls `provide-source!`
+25
;;; inside the per-node path). Splitting the stages across nodes would mean
+26
;;; shipping the workspace `build/` between them -- 138 MB for this repo
+27
;;; after `sigil build` in a delivered tree -- which is the artifact fan-in
+28
;;; path kiln's own `.kiln/ci.sgv` records as unproven on the persistent
+29
;;; backend. And it would not be enough: `sigil deps install` leaves a LINK
+30
;;; FARM in the workspace (`.sigil/deps`, 14 entries here, all 14 symlinks)
+31
;;; pointing at content under `$HOME/.sigil/deps`, so a restored copy of it
+32
;;; dangles unless $HOME is shared between the nodes -- an unstated
+33
;;; dependency on worker statefulness, true on today's single persistent
+34
;;; host and false anywhere else. The `.woodpecker.yml` this replaces says
+35
;;; the same thing in its own header. The honest cost of one node is that
+36
;;; the operator gets ONE status instead of three; that is a symptom of the
+37
;;; missing ledger below, not of the node count.
38
;;;
39
;;; WHAT A STEP NAME BUYS TODAY: readability, and nothing structural.
−31
;;; Measured 2026-09-01 on kiln ad9b9c5: `nodeexec.sgl` wraps a named
−32
;;; shell step in `##[kiln:section-start:<name>]`, while the log viewer's
−33
;;; parser (`web/views.sgl` `section-marker`) requires the prefix
+40
;;; `nodeexec.sgl` wraps a named shell step in `##[kiln:section-start:<name>]`,
+41
;;; while the log viewer's parser (`web/views.sgl` `section-marker`, used by
+42
;;; both the fold view and the live SSE renderer) requires the prefix
43
;;; `##[kiln:section-start ` -- space and timestamp, the LEGACY `.run`
44
;;; grammar. The two do not meet, so a named step renders as a plain log
−36
;;; line, live and completed. The names are still worth having: they are
−37
;;; the contract a future comparison would rest on, a rename is a breaking
−38
;;; change even when it reads better, and they cost nothing to carry until
−39
;;; the grammar is reconciled. Do not assume they fold.
+45
;;; line. Two more things about the markers, for whoever reconciles the
+46
;;; grammar: a FAILING step emits no `section-end` at all (the program dies
+47
;;; under `set -e`), and `artifact` steps emit no markers whatsoever.
+48
;;; The names are still worth having -- they are the contract any future
+49
;;; comparison rests on, and a rename is a breaking change even when it
+50
;;; reads better -- but do not tell anyone they will see folds.
51
;;;
52
;;; THERE IS NO CHECK LEDGER HERE, AND THAT IS STRUCTURAL. The
−42
;;; `$KILN_ARTIFACTS/checks.ledger` contract that `kiln-diff-checks` and
−43
;;; the conversion plan's floors are built on is written entirely by a
−44
;;; repo's own `.kiln/ci.sh` -- `grep -rn checks.ledger src/` in kiln
−45
;;; returns nothing. A natively-defined pipeline emits no ledger by
−46
;;; construction. The `floor` step below is therefore NOT that contract
−47
;;; and must not be read as it: it asserts only that the suite ran and
−48
;;; reported at least the counts a green local run reported.
+53
;;; `$KILN_ARTIFACTS/checks.ledger` contract that `kiln-diff-checks` and the
+54
;;; conversion plan's floors are built on is written entirely by a repo's
+55
;;; own `.kiln/ci.sh` -- `grep -rn checks.ledger src/` in kiln returns
+56
;;; nothing. A natively-defined pipeline emits no ledger by construction.
+57
;;; The `floor` step below is therefore NOT that contract and must not be
+58
;;; read as it: it asserts only that the suite ran and reported at least the
+59
;;; counts a green local run reported.
60
;;;
−50
;;; TIMEOUT 1800, CHOSEN TO BE CLAMP-IMMUNE. `runner.sgl node-worker-fm`
−51
;;; applies (min declared ceiling) with NO diagnostic, where ceiling is
−52
;;; `safety.max_job_timeout`. The effective value is printed exactly once,
−53
;;; in the `Persistent worker ready ... timeout=N` line. 1800 is the
−54
;;; config default (`config.sgl:345`) and at or below every ceiling this
−55
;;; controller has been recorded with, so the declared value survives the
−56
;;; clamp whatever the key is set to. Local half-warm cost (shared $HOME
−57
;;; dep cache, fresh delivered tree) is far below it; a cold CI worker
−58
;;; will be slower, and 1800 is the headroom for that.
+61
;;; TIMEOUT 1800, AND WHAT THE CLAMP ACTUALLY DOES. `runner.sgl`
+62
;;; `node-worker-fm` computes `(min declared ceiling)` with NO diagnostic,
+63
;;; where ceiling is `safety.max_job_timeout`. 1800 equals that setting's
+64
;;; DEFAULT (`config.sgl:345`) -- it is a ceiling default, not a per-job
+65
;;; default -- so 1800 survives the clamp on a controller that has not
+66
;;; lowered the key, and is SILENTLY REDUCED on one that has. Nobody
+67
;;; copying this file should assume otherwise without reading the key.
+68
;;; The effective value is emitted once, in the `Persistent worker ready
+69
;;; ... timeout=N` line, and only on the persistent-worker path; on any
+70
;;; other backend it is printed nowhere. Note also that omitting `worker:`
+71
;;; entirely is NOT the same as declaring a default: `node-worker-fm`
+72
;;; returns #f and the node carries no frontmatter at all.
73
74
(pipeline ci
75
on: #[ (push) (manual) ]
@@ -77,22 +91,48 @@ cat kiln-test.out"
91
name: test)
92
93
;; THE FLOOR. `sigil test` already exits 1 when it finds NO test
−80
;; files at all (measured), so this exists for the partial case:
−81
;; a file that stops matching the `test-*.sgl` / `*-test.sgl`
−82
;; discovery pattern is dropped SILENTLY and the run stays green on
−83
;; a smaller suite. Both numbers come from a green local run of
−84
;; 0b4617a: `Files: 5 of 5 run`, `Tests: 167 passed`.
+94
;; files, so this exists for the PARTIAL case: a file that stops
+95
;; matching the `test-*.sgl` / `*-test.sgl` discovery pattern is
+96
;; dropped SILENTLY and the run stays green on a smaller suite.
+97
;; Both numbers come from a green local run of 0b4617a:
+98
;; `Files: 5 of 5 run`, `Tests: 167 passed`.
+99
;;
+100
;; `tail -1`, NOT `head -1`, AND THIS IS NOT A STYLE CHOICE. The
+101
;; step captures 2>&1, so anything a test prints lands in this file
+102
;; ahead of the summary. A test that emits a banner-shaped line
+103
;; ("Files: 9 of 9 run") makes `head -1` read the test's output
+104
;; instead of the summary. Measured on sigil-http: with `head -1` the
+105
;; floor stayed GREEN on a run that had fallen from 5 files / 167
+106
;; tests to 2 / 2. The real summary is always printed last, after the
+107
;; per-file loop, so `tail -1` reads it and the same input goes
+108
;; correctly red.
109
;;
110
;; The counts are read with a numeric guard because a numeric
111
;; comparison in POSIX sh fails OPEN on an empty or non-numeric
−88
;; value; an unreadable count is SETUP-FAILED (exit 2), which is a
−89
;; distinct outcome from the floor being breached (exit 1).
+112
;; value. Exit 2 marks that case, and the wording is the point:
+113
;; KILN SEES 1 AND 2 IDENTICALLY (both are a failed node), so the
+114
;; distinction lives in the log for a human, not in the run status.
+115
;;
+116
;; The file floor is only load-bearing where the count is above 1.
+117
;; `Files: 0 of N run` is unreachable -- the line prints only when
+118
;; discovery found something, and every discovered file increments
+119
;; the completed counter on every outcome path -- so in a repo with
+120
;; ONE test file `files >= 1` is a tautology and the test count is
+121
;; the only real assertion. It is kept anyway so all 58 copies of
+122
;; this file have the same shape; say so where it applies.
+123
;;
+124
;; One case this floor mis-labels, worth knowing before a repo with
+125
;; NATIVE tests copies it: `sigil test` exits 1 on no tests only when
+126
;; BOTH the native and the .sgl sets are empty. A repo carrying a
+127
;; `build/*/bin/test-*` binary whose entire .sgl suite stops matching
+128
;; discovery gets exit 0, no `Files:` line, and a SETUP-FAILED here --
+129
;; a total loss of the .sgl suite reported as a tooling problem.
130
;;
−91
;; NOTE FOR THE NEXT REPO: this step is the only part of the
−92
;; pipeline the job language cannot express. It is shell because
−93
;; there is nowhere else to put it.
−94
(shell "files=$(sed -n 's/^[[:space:]]*Files:[[:space:]]*\\([0-9][0-9]*\\) of .*/\\1/p' kiln-test.out | head -1)
−95
tests=$(sed -n 's/^[[:space:]]*Tests:[[:space:]]*\\([0-9][0-9]*\\) passed.*/\\1/p' kiln-test.out | head -1)
+131
;; NOTE FOR THE NEXT REPO: this step is the only part of the pipeline
+132
;; the job language cannot express. It is shell because there is
+133
;; nowhere else to put it, and that is how `ci.sh` starts.
+134
(shell "files=$(sed -n 's/^[[:space:]]*Files:[[:space:]]*\\([0-9][0-9]*\\) of .*/\\1/p' kiln-test.out | tail -1)
+135
tests=$(sed -n 's/^[[:space:]]*Tests:[[:space:]]*\\([0-9][0-9]*\\) passed.*/\\1/p' kiln-test.out | tail -1)
136
for v in \"$files\" \"$tests\"; do
137
case \"$v\" in '' | *[!0-9]*) echo \"SETUP-FAILED: floor could not read a count from kiln-test.out\"; exit 2 ;; esac
138
done
@@ -104,7 +144,8 @@ echo 'floor: ok'"
144
145
;; Staged last on purpose, and note what that costs: steps run under
146
;; `set -e` and there is no post/always construct, so this does NOT
−107
;; run when an earlier step fails -- exactly the run whose output
−108
;; you would most want kept.
+147
;; run when an earlier step fails -- measured, $KILN_ARTIFACTS comes
+148
;; back empty on a red run. Exactly the run whose output you would
+149
;; most want kept.
150
(artifact "kiln-test.out" as: "test-output.txt")
151
]))