Commit88f6b8faRecorded1 Sep 2026Repositorysigil-json

Drop the floor step; put `name:` before the command

Message

The floor read the test-count banner and failed the run if it dropped below what a green run reported. It was guarding a real hole -- sigil test silently skips files in test/ that do not match test-.sgl or -test.sgl, so a rename can shrink the suite while CI stays green -- but that hole belongs to sigil test, and fixing it there fixes it once for every repo instead of pasting ten lines of shell into each one. Filed separately; a --strict flag is the shape.

Removing it also removes its only consumer, so sigil test runs plainly again and the output capture and the artifact step go with it.

name: now precedes the command. Keyword arguments are order-free in the job language already, so this is the same step form and compiles to an identical script -- it just does not bury the step's name underneath the code it names.

Changed
 .kiln/ci.sgv | 175 +++++++++++++++++++++++++++++++++----------------------------------------------------------------------------------------------------------------------------------------------
 1 file changed, 33 insertions(+), 142 deletions(-)
Diff
.kiln/ci.sgvmodified
@@ -1,75 +1,40 @@
−1
;;; ci.sgv -- sigil-json CI, defined NATIVELY in kiln's job language.
+1
;;; ci.sgv -- sigil-json CI, defined natively in kiln's job language.
2
;;;
−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. Every claim below was measured on kiln
−7
;;; ad9b9c5 and sigil-json e667078, 2026-09-01, and re-checked by an
−8
;;; adversarial reviewer that found four of them wrong the first time.
+3
;;; No `.kiln/ci.sh`. The three steps below ARE the pipeline.
4
;;;
5
;;; `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
−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".
+6
;;; matches no event, produces no error and no run, and still lints OK --
+7
;;; so a missing `on:` reads exactly like a controller fault rather than
+8
;;; like this file.
9
;;;
−15
;;; NO `branch:` FILTER ON `push`, deliberately. A filter would narrow
−16
;;; coverage to one ref; this library has none to spare. `(pull-request)`
−17
;;; is NOT declared even though the `.woodpecker.yml` it replaces named
−18
;;; `pull_request`: kiln's matcher accepts the trigger, but no run of that
−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.
+10
;;; NO `branch:` FILTER ON `push`, deliberately: a filter would narrow
+11
;;; coverage to one ref. `(pull-request)` is not declared either -- kiln's
+12
;;; matcher accepts it, but no run of that kind has been observed on this
+13
;;; controller, and declaring coverage nobody has seen fire is how a green
+14
;;; comes to mean less than it looks.
15
;;;
−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 -- 2.3 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`, 2 entries here, all 2 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.
+16
;;; ONE NODE, NOT THREE JOBS. Every node gets its own worker and its own
+17
;;; delivered tree, and `sigil deps install` leaves a link farm in the
+18
;;; workspace pointing at content under $HOME -- so splitting the stages
+19
;;; across nodes means either shipping ~2.3 MB of `build/` between them or
+20
;;; depending on $HOME being shared between leases. Reasoning and
+21
;;; measurements: topics/kiln-native-pipeline-single-node-template.
22
;;;
−39
;;; WHAT A STEP NAME BUYS TODAY: readability, and nothing structural.
−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
−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.
+23
;;; STEP NAMES DO NOT FOLD IN THE UI TODAY, and that is a kiln bug, not a
+24
;;; mistake here: `nodeexec.sgl` emits `##[kiln:section-start:<name>]` and
+25
;;; the log viewer's parser wants the legacy `##[kiln:section-start <ms>
+26
;;; <name>]`. Keep the names -- they are the contract any future
+27
;;; comparison rests on -- but expect plain log lines until that is fixed.
+28
;;; There is also no check ledger here: that whole contract lives in each
+29
;;; repo's `.kiln/ci.sh`, and this pipeline has none.
+30
;;; See topics/kiln-native-pipeline-emits-no-checks.
31
;;;
−52
;;; THERE IS NO CHECK LEDGER HERE, AND THAT IS STRUCTURAL. The
−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
;;;
−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.
+32
;;; `timeout: 1800` is CLAMPED by `safety.max_job_timeout` with no
+33
;;; diagnostic -- a plain (min declared ceiling). 1800 is that key's
+34
;;; default, so it survives on a controller that has not lowered it and is
+35
;;; silently reduced on one that has. The effective value is printed once,
+36
;;; in `Persistent worker ready ... timeout=N`, and only on the
+37
;;; persistent-worker path.
38
39
(pipeline ci
40
on: #[ (push) (manual) ]
@@ -77,81 +42,7 @@
42
(job ci
43
worker: (worker timeout: 1800)
44
steps: #[
−80
−81
(shell "sigil deps install" name: deps)
−82
−83
(shell "sigil build" name: build)
−84
−85
;; `sigil test` is captured rather than piped: a pipeline's exit
−86
;; status is the LAST command's, so `sigil test | tee` reports tee's
−87
;; success and loses the failure. The `|| { rc=$?; ...; exit \"$rc\"; }`
−88
;; form keeps the real status while still printing the log.
−89
(shell "sigil test --no-color > kiln-test.out 2>&1 || { rc=$?; cat kiln-test.out; exit \"$rc\"; }
−90
cat kiln-test.out"
−91
name: test)
−92
−93
;; THE FLOOR. `sigil test` already exits 1 when it finds NO test
−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 e667078:
−98
;; `Files: 1 of 1 run`, `Tests: 57 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
−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
;; THIS REPO IS THAT CASE: it has ONE test file, so `files >= 1`
−125
;; here CANNOT FAIL and is decorative. The test-count floor is the
−126
;; only assertion in this step that can go red. The total-loss case
−127
;; is still covered, by `sigil test` itself, which exits 1 when it
−128
;; finds no test files at all.
−129
;;
−130
;; One case this floor mis-labels, worth knowing before a repo with
−131
;; NATIVE tests copies it: `sigil test` exits 1 on no tests only when
−132
;; BOTH the native and the .sgl sets are empty. A repo carrying a
−133
;; `build/*/bin/test-*` binary whose entire .sgl suite stops matching
−134
;; discovery gets exit 0, no `Files:` line, and a SETUP-FAILED here --
−135
;; a total loss of the .sgl suite reported as a tooling problem.
−136
;;
−137
;; NOTE FOR THE NEXT REPO: this step is the only part of the pipeline
−138
;; the job language cannot express. It is shell because there is
−139
;; nowhere else to put it, and that is how `ci.sh` starts.
−140
(shell "files=$(sed -n 's/^[[:space:]]*Files:[[:space:]]*\\([0-9][0-9]*\\) of .*/\\1/p' kiln-test.out | tail -1)
−141
tests=$(sed -n 's/^[[:space:]]*Tests:[[:space:]]*\\([0-9][0-9]*\\) passed.*/\\1/p' kiln-test.out | tail -1)
−142
for v in \"$files\" \"$tests\"; do
−143
case \"$v\" in '' | *[!0-9]*) echo \"SETUP-FAILED: floor could not read a count from kiln-test.out\"; exit 2 ;; esac
−144
done
−145
echo \"floor: files=$files (min 1), tests=$tests (min 57)\"
−146
[ \"$files\" -ge 1 ] || { echo \"FLOOR BREACHED: $files test files ran, expected at least 1\"; exit 1; }
−147
[ \"$tests\" -ge 57 ] || { echo \"FLOOR BREACHED: $tests tests ran, expected at least 57\"; exit 1; }
−148
echo 'floor: ok'"
−149
name: floor)
−150
−151
;; Staged last on purpose, and note what that costs: steps run under
−152
;; `set -e` and there is no post/always construct, so this does NOT
−153
;; run when an earlier step fails -- measured, $KILN_ARTIFACTS comes
−154
;; back empty on a red run. Exactly the run whose output you would
−155
;; most want kept.
−156
(artifact "kiln-test.out" as: "test-output.txt")
+45
(shell name: deps "sigil deps install")
+46
(shell name: build "sigil build")
+47
(shell name: test "sigil test")
48
]))