Commit9ff77343Recorded27 Aug 2026Repositoryenclave

ci: add .kiln/ci.sgv and .kiln/ci.sh, ported from jobs/enclave.run

Message

Phase T1 of the pipeline conversion -- the first transfer of the R1 template to a second repository.

The port is check-preserving and that is proven rather than asserted:

    kiln-diff-checks --old-source jobs/enclave.run \
                     --new-source .kiln/ci.sh --floor 20
    -> EQUAL, 84 records on each side

with two positive controls run against these same files, because an assertion never seen to fail is not known to work: dropping one emission gives MISMATCH naming it, renaming one gives MISMATCH missing from each side. Both plants were hash-verified landed and re-checked with sh -n before either colour was believed.

Only six diff hunks separate ci.sh from jobs/enclave.run, and all six sit in the regions the header declares: the frontmatter removal, the header addition, the ledger block, the three emitter hunks, and the effect arm. Nothing in the body changed, so no check name, check subject or command moved.

Three deviations from the R1 template, each recorded in the files:

  • FIVE outcome words, not three. This harness carries xfail/xpass, so the ledger, the reconciliation and TOTAL are all five-way.
  • The floor is 20, not the plan's 25. 25 is enclave's GREEN count and is the wrong number for a floor: a complete-but-red run reaches 23 (run 86), so 25 would mark a complete run UNGRADED and every pair built on it NOT-COMPARABLE -- failing closed, in the direction that reads like the gate working.
  • timeout 1800 carried verbatim from the .run frontmatter, not raised to kiln's 2400 because that number is in the template.

kiln plan .kiln/ci.sgv compiles clean (hash 0c289222b1790514, triggers (push) (manual), one node, timeout=1800), and reddens with a compile diagnostic under a planted unbalanced delimiter.

KNOWN AND STATED, NOT PAPERED OVER: source mode proves the name sets match in the two job DEFINITIONS. It does not prove the two harnesses emit the same names at runtime. enclave's legacy job has failed every run since 2026-08-10, and §4 refuses any pair where either side failed, so zero runtime EQUAL pairs are obtainable on enclave today. Cutover is gated separately on that gap.

Changed
 .kiln/ci.sgv |   96 +++++++++++++
 .kiln/ci.sh  | 1397 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 1493 insertions(+)
Diff
.kiln/ci.sgvadded
@@ -0,0 +1,96 @@
+1
;;; ci.sgv -- enclave's CI, as a `.kiln/' pipeline. Phase T1 of the pipeline
+2
;;; conversion: the first TRANSFER of the R1 template to a second repository.
+3
;;;
+4
;;; The substance of this job is `.kiln/ci.sh', ported from the azoth.works copy
+5
;;; `jobs/enclave.run' with its CHECK NAMES UNCHANGED. That invariant is not
+6
;;; cosmetic: the differential gate compares the check-name SETS emitted by the
+7
;;; old `.run' job and by this pipeline, and names are the contract. A rename is
+8
;;; a breaking change to that comparison even when it reads better.
+9
;;;
+10
;;; WHAT THIS CONVERSION IS GRADED ON, AND WHAT IT IS NOT -- READ THIS BEFORE
+11
;;; TREATING A GREEN AS EQUIVALENCE. enclave's legacy job has FAILED every run
+12
;;; since 2026-08-10 (14 consecutive reds: a WASM build failure, a unit suite
+13
;;; that shrank 850 -> 485, and a non-deterministic dependency resolver). The
+14
;;; differential's §4 rule refuses any pair where EITHER SIDE FAILED, for the
+15
;;; structural reason that in these harnesses an outcome flip CHANGES THE CHECK
+16
;;; NAME. So the number of runtime EQUAL pairs obtainable on enclave today is
+17
;;; ZERO, at any run budget. Ruled 2026-08-27: this file's check-preservation is
+18
;;; proven in SOURCE mode instead --
+19
;;; kiln-diff-checks --old-source jobs/enclave.run --new-source .kiln/ci.sh
+20
;;; which returns EQUAL over 84 emission sites. SAY WHAT THAT DOES NOT PROVE:
+21
;;; source mode compares the name sets DEFINED in the two job files. It does not
+22
;;; prove the two harnesses EMIT the same names at runtime. Cutover is gated
+23
;;; separately and explicitly on that gap being closed.
+24
;;;
+25
;;; DELIBERATELY SINGLE-NODE, for the same three recorded reasons as kiln's R1
+26
;;; file: (1) the differential must compare like with like, so only the
+27
;;; definition format and the delivery chain change and one variable moves at a
+28
;;; time; (2) a multi-node group takes multiple leases against a pool of 4 at
+29
;;; max_concurrent 2, and artifact fan-in on the persistent backend is unproven;
+30
;;; (3) concurrent node execution is the path the K2b tests deliberately avoid.
+31
;;; THERE IS A FOURTH REASON HERE THAT KILN DID NOT HAVE: kiln/t-f0d68c records
+32
;;; that `report-commit-status!'s outbound POST is UNBOUNDED and sits in front of
+33
;;; the DAG advance, so a hung forge request stalls a multi-node group. That is
+34
;;; unreachable for a single-node pipeline and becomes reachable the moment
+35
;;; anyone decomposes this one. Do not decompose it without reading that ticket.
+36
;;;
+37
;;; BUILT-IN STEPS ONLY (`shell', `artifact', `restore', `gather'). Composite and
+38
;;; packaged action steps are refused at plan time as of M2; before M2 they were
+39
;;; ECHO-SKIPPED, so a pipeline made of them went green having run nothing.
+40
;;; `worker image:' is refused too -- the persistent runner does not honour it,
+41
;;; which matters here because this job needs chromium and someone will be
+42
;;; tempted to declare an image for it. The runner's golden image has carried
+43
;;; chromium since 2026-08-05; the declaration would be accepted and dropped.
+44
;;;
+45
;;; NO `artifact' STEP FOR THE LEDGER, ON PURPOSE. `.kiln/ci.sh' writes its check
+46
;;; ledger straight into $KILN_ARTIFACTS, which the runner sets to
+47
;;; <workspace>/artifacts on BOTH the legacy and the node paths, and the artifact
+48
;;; copy-back copies that whole directory regardless of any `artifact' step. An
+49
;;; `artifact' step here would instead look for `checks.ledger' relative to the
+50
;;; workspace root, where it does not exist.
+51
;;;
+52
;;; NO `branch:' FILTER ON `push', AND THAT IS DELIBERATE. Legacy scheduling
+53
;;; applies NO ref filter at all -- `schedule-runs!' never compares `ref' -- so
+54
;;; `(push branch: "master")' would be NARROWER than the job it replaces, and
+55
;;; branch pushes to enclave/enclave would silently stop being checked at
+56
;;; cutover. The failure is invisible by construction: the webhook returns HTTP
+57
;;; 200 with `groups: []', indistinguishable from a legitimate no-match. David
+58
;;; ruled 2026-08-27 for kiln: match today's behaviour, no filter. Same here.
+59
;;; See topics/kiln-pipeline-trigger-narrows-branch-coverage.
+60
;;;
+61
;;; `on:' IS MANDATORY AND ITS ABSENCE IS SILENT. A workflow with no `on:'
+62
;;; matches no event and is simply never selected -- correct fail-closed parity
+63
;;; with GitHub, but a missing `on:' produces no error and no run, which reads
+64
;;; exactly like a controller fault rather than like this file.
+65
;;;
+66
;;; PRECONDITION THIS FILE CANNOT ENFORCE: the `enclave/enclave' repos row has
+67
;;; `source_policy: ""' on the live controller today, where kiln/kiln has
+68
;;; "deliver". A repository-origin plan-run requires "deliver". Measured
+69
;;; 2026-08-27 with `kiln repo list --config /srv/kiln/config.json'; re-derive it
+70
;;; rather than trusting this line.
+71
+72
(pipeline ci
+73
on: #[ (push) (manual) ]
+74
+75
(job ci
+76
;; `timeout: 1800' is carried verbatim from the `.run' frontmatter
+77
;; (`#kiln: timeout 1800') so the two definitions declare the same thing.
+78
;; It is NOT re-chosen for the pipeline, and it is deliberately NOT raised
+79
;; to kiln's 2400 just because that number appears in the template.
+80
;;
+81
;; THE CLAMP IS SILENT, SO KNOW WHAT IT IS SET TO. `node-worker-fm' applies
+82
;; (min declared ceiling) with NO diagnostic, where ceiling is
+83
;; `safety.max_job_timeout'. Measured on the live controller 2026-08-27:
+84
;; max_job_timeout is 2400, so 1800 is BELOW the ceiling and the declared
+85
;; value is the effective one. The only place the EFFECTIVE timeout is ever
+86
;; emitted is the runner's "Persistent worker ready ... timeout=N" line.
+87
;;
+88
;; HEADROOM, from real runs rather than from a guess: the last green enclave
+89
;; run (46, 2026-08-06) took 16m21s and the longest recent complete run (86,
+90
;; 2026-08-25) took 17m21s, against 1800s = 30m. If this job is ever cut off
+91
;; near 1800s that is the declared timeout, not a flake -- and it should be
+92
;; raised from a MEASUREMENT, never from a failure.
+93
worker: (worker timeout: 1800)
+94
steps: #[
+95
(shell "sh .kiln/ci.sh" name: checks)
+96
]))
.kiln/ci.shadded
@@ -0,0 +1,1397 @@
+1
#!/bin/sh
+2
#
+3
# ===========================================================================
+4
# THIS IS `.kiln/ci.sh' -- THE IN-REPO COPY. PHASE T1.
+5
# ===========================================================================
+6
#
+7
# Ported from `azoth.works/jobs/enclave.run' with its CHECK NAMES UNCHANGED.
+8
# That invariant is not cosmetic: the differential gate compares the check-name
+9
# SETS emitted by the old `.run' job and by this pipeline, and NAMES ARE THE
+10
# CONTRACT. Renaming a check is a breaking change to that comparison even when
+11
# the new name reads better.
+12
#
+13
# WHAT CHANGED IN THE PORT, exhaustively, so a reader can check the claim:
+14
# 1. the two `#kiln:' frontmatter lines are gone -- `source' and `timeout'
+15
# are declared in `.kiln/ci.sgv' instead, and the timeout is carried
+16
# across verbatim (1800) rather than re-chosen;
+17
# 2. ok/fail/skip/xfail/xpass each append one record to the check ledger;
+18
# 3. the RESULT ladder gained reconciliation, a check-count floor and a
+19
# completeness sentinel.
+20
# NOTHING ELSE. No check name, no check subject, and no command changed.
+21
#
+22
# THE FLOOR IS 20 AND IT WAS DERIVED, NOT COPIED. The conversion plan's §4
+23
# table said `enclave 25'. That figure is enclave's GREEN check count (run 46,
+24
# 2026-08-06: "enclave: PASSED (25 checks, none skipped)") and it is the WRONG
+25
# NUMBER FOR A FLOOR, because a floor must sit under the smallest COMPLETE run,
+26
# not at the largest. A complete-but-red run reaches fewer outcomes: run 86
+27
# (2026-08-25, sha 207aac8) reached the RESULT ladder with "23 defined". A
+28
# floor of 25 would have marked that complete run UNGRADED and every pair built
+29
# on it NOT-COMPARABLE, for a reason having nothing to do with the conversion
+30
# -- failing closed, in the direction that reads like the gate working.
+31
# 20 sits 3 under the measured 23, the same margin kiln's 25 has under its 28.
+32
#
+33
# ONE ENCLAVE-SPECIFIC HAZARD THE ENUMERATOR CANNOT SEE. `tools/kiln-check-names'
+34
# matches `ok', `fail' and `skip' ONLY. `xfail'/`xpass' call sites are invisible
+35
# to it -- no refusal, no <UNPARSED>, exit 0 -- so a source-mode differential
+36
# would drop them identically on BOTH sides and report EQUAL about a check it
+37
# never saw. Verified by probe, both directions: adding an `ok' site moves the
+38
# census 84 -> 85; adding an `xfail' site leaves it at 84. There are ZERO
+39
# xfail/xpass CALL sites in this file today (both exemptions were retired
+40
# 2026-08-06 and only the machinery remains), so the census is honest as it
+41
# stands. IT STOPS BEING HONEST THE DAY SOMEONE ADDS AN EXEMPTION. If you add
+42
# one, fix the enumerator first and give the fix its own sabotage arm.
+43
#
+44
# enclave -- CI for the Enclave IRCv3 server and its PWA.
+45
#
+46
# Deployed to /srv/kiln/jobs/enclave.run on the controller. This copy is the
+47
# source of truth; see procedures/kiln-controller-deploy for the route in.
+48
#
+49
# Structurally this is jobs/kiln.run's younger sibling and inherits all three of
+50
# its guards deliberately. Where it departs, it says so.
+51
#
+52
# ===========================================================================
+53
# THE POINT OF THIS JOB IS THE PWA CREDENTIAL-LIFECYCLE GATES
+54
# ===========================================================================
+55
#
+56
# On 2026-08-05 those gates found two HIGH defects that twenty-two other green
+57
# gates in the same repository could not see: an unbounded re-entrancy that died
+58
# on a stack overflow, and a change that turned a transient network failure into
+59
# a permanent dead end for a new user with a cold cache. Both were introduced by
+60
# the branch that was fixing the original defects.
+61
#
+62
# They are the only tests in this repository that EXECUTE `app.js` at all. They
+63
# had never run anywhere except on a developer's laptop, and until chromium
+64
# landed in the runner golden image on 2026-08-05 they could not.
+65
#
+66
# ===========================================================================
+67
# THIS JOB REQUIRES A REPOSITORY FIX THAT LANDED WITH IT
+68
# ===========================================================================
+69
#
+70
# READ THIS IF `pwa wasm: deps install' FAILS NAMING A ../../../../<repo> PATH.
+71
# It means the job is running against a tree from before that fix.
+72
#
+73
# `packages/enclave-pwa/wasm-irc-runtime/package.sgl` used to declare its eleven
+74
# dependencies with `from-path`, pointing at sibling checkouts OUTSIDE the
+75
# repository (`../../../../sigil` and four others). Measured, not inferred: `git
+76
# archive HEAD` into a directory with no siblings, then `sigil deps install`,
+77
# gave "11 dependencies failed to fetch; aborting. Lockfile NOT written."
+78
#
+79
# NOTE THE 11 -> 10 IF YOU ARE COMPARING AGAINST THE CHECK BELOW, which prints
+80
# "10 from-git". It is not a dependency going missing: `sigil-stdlib` is now
+81
# left IMPLICIT because declaring it explicitly makes resolution fail outright
+82
# (sigil-websocket 0.10.3 requires ^0.17 while the `sigil:` field supplies
+83
# 0.18.2, and the resolver reconciles those only while stdlib is unnamed).
+84
#
+85
# So the PWA's WASM runtime was unbuildable by anyone who did not have one
+86
# specific directory layout -- not merely by CI, but by any fresh clone, any
+87
# contributor, and any release build from a clean tree. CI did not work around
+88
# that. The dependencies were converted to `from-git`, which is what every other
+89
# package in the repository already used (enclave-server 13, enclave-rpc 4; this
+90
# package was the only outlier).
+91
#
+92
# THE CONSEQUENCE FOR THIS FILE IS THAT THERE IS NOTHING SPECIAL TO DO. The job
+93
# builds the runtime the same way it builds anything else. If that ever stops
+94
# being true -- if a `from-path` dep reappears -- this job fails at deps
+95
# resolution and names the path, which is the correct place for it to fail.
+96
#
+97
# ===========================================================================
+98
# THE BACKEND IS BYTECODE ON PURPOSE. DO NOT "MODERNISE" IT TO NATIVE.
+99
# ===========================================================================
+100
#
+101
# `wasm-irc-runtime/package.sgl` declares `backend: 'native` as its default, so
+102
# omitting --backend silently builds a DIFFERENT ARTIFACT KIND from the one
+103
# production serves. Two independent reasons to pass it explicitly:
+104
#
+105
# 1. production currently serves a BYTECODE build (measured 2026-08-05:
+106
# 2,562,884 bytes; a native build is ~12.5 MB raw);
+107
# 2. the author's 32/32 green result was measured against a bytecode build.
+108
#
+109
# Testing an artifact kind that nobody ships is the exact trap kiln v0.1.4 fell
+110
# into from the other direction -- its dev bundle passed 9 of 9 setup-failure
+111
# paths while its static build failed 7 of 9. Two build configurations of the
+112
# same source DISAGREE, and CI must exercise the one that ships.
+113
#
+114
# ===========================================================================
+115
# CHROME_BIN IS **NOT** EXPORTED TO THE TWO SMOKES -- THEY RESOLVE IT THEMSELVES
+116
# ===========================================================================
+117
#
+118
# HISTORY FIRST, because the reason outlives the rule.
+119
#
+120
# `smoke-boot-splash.sh` and `smoke-wasm-thread-body-dom.sh` USED TO open with:
+121
#
+122
# CHROME_BIN=${CHROME_BIN:-google-chrome}
+123
# if ! command -v "$CHROME_BIN" >/dev/null 2>&1; then
+124
# echo "SKIP: Chrome is required ..."
+125
# exit 0
+126
# fi
+127
#
+128
# Two facts, and it was their combination that was fatal. The runner image
+129
# installs `ungoogled-chromium`, whose binary is named **chromium** -- measured
+130
# on a leased runner, /run/current-system/profile/bin/chromium, "Chromium
+131
# 150.0.7871.46" -- and `google-chrome` exists under none of the four names the
+132
# estate's browser suites search. And a missing browser exited 0.
+133
#
+134
# So on THIS image, with chromium correctly installed, those scripts printed SKIP
+135
# and scored PASS -- exactly what they did on an image with no browser at all.
+136
# NO POSSIBLE INSTALLATION STATE PRODUCED A RED. Nobody noticed the browser gates
+137
# were not running because they were not failing, they were skipping, and
+138
# skipping scored as success.
+139
#
+140
# This job worked around it: it resolved the browser, exported CHROME_BIN,
+141
# asserted each script's PASS MARKER rather than its exit status, and asserted
+142
# the `SKIP:` line ABSENT.
+143
#
+144
# **THE WORKAROUND IS GONE, AND SO IS THE EXPORT.** As of enclave 3605838 both
+145
# scripts resolve the browser by name search themselves (chromium first), treat
+146
# an absent browser as a HARD FAILURE, name what they searched for when refusing,
+147
# and skip only under an explicit `ALLOW_SKIP=1`. Their exit status now means
+148
# something, so this job scores them on it.
+149
#
+150
# THE EXPORT IS WITHHELD FROM THESE TWO ON PURPOSE. A job that hands a script its
+151
# browser never exercises the resolution that ships, and would be verifying a
+152
# path production does not take. Preflight still resolves and probes a browser --
+153
# that is evidence about the IMAGE, and the credential gates below still need
+154
# CHROME_BIN because their driver (`cdp-driver.mjs`) keeps the old hardcoded
+155
# `google-chrome` default (folio t-af4f55). **Do not "fix" the smokes by
+156
# re-exporting it here.**
+157
#
+158
# TWO CLAIMS THIS FILE USED TO MAKE ARE FALSE. Both were inherited from the folio
+159
# note this section was written from, both were disproved by measurement on
+160
# 2026-08-06, and both are CORRECTED rather than deleted -- they arrived here by
+161
# being copied, and a bare deletion leaves nothing to stop the next copy:
+162
#
+163
# * "a dangling symlink resolves under `command -v` and fails to execute" --
+164
# IT DOES NOT. Bash resolves candidates with access(X_OK), which returns
+165
# ENOENT through a broken link, so a dangling `chromium` is indistinguishable
+166
# from an absent one. What DOES resolve and fail to execute: a non-executable
+167
# file, or an executable whose interpreter is missing. A broken symlink needs
+168
# its own scan, and the scripts now have one.
+169
# * The behavioural `--version` probe was treated as strictly better than
+170
# `command -v`, at no cost. IT HAS ONE: `command -v` cannot hang and an exec
+171
# can. Measured -- a stub that sleeps forever, named `chromium`, hangs the
+172
# probe indefinitely. The scripts bound theirs at 30s; so does this file now.
+173
#
+174
# ===========================================================================
+175
# TWO KNOWN-BAD STATES, HANDLED RATHER THAN DISCOVERED -- BOTH NOW RESOLVED
+176
# ===========================================================================
+177
#
+178
# Kept as history. Neither is live; this file carries NO exemptions. Both entries
+179
# record how the bad state was established and how it was retired, because both
+180
# halves are the reusable part.
+181
#
+182
# 1. RESOLVED 2026-08-06. `smoke-wasm-thread-body-dom.sh` WAS red on master
+183
# (folio t-322b3c), established by building a baseline worktree at a5ee15b and
+184
# reproducing the identical failure rather than assuming it -- it was NOT the
+185
# documented "Failed to fetch" transient. It was carried as an EXPECTED
+186
# FAILURE: a fourth outcome, neither pass nor skip, with XPASS reserved for
+187
# the day it started working.
+188
#
+189
# THAT DAY WAS RUN 34. Both exemptions this file carried XPASSed in the same
+190
# run, on a real runner, and both were retired on that evidence rather than on
+191
# the belief that a merged fix works. The mechanism is what made the
+192
# retirement legitimate: it announced its own obsolescence.
+193
#
+194
# THIS FILE NOW CARRIES NO EXEMPTIONS. `xfail`/`xpass` remain implemented and
+195
# counted, because the next known-red will want them -- but if you are adding
+196
# one, it must be matched on ITS OWN SYMPTOM and must never be an `else`.
+197
#
+198
# 2. RESOLVED 2026-08-05, AND KEPT HERE BECAUSE THE WARNING OUTLIVED THE BUG BY
+199
# A FEW HOURS. This section used to say that a green `node --test` suite was
+200
# ENFORCING a production bug: `test/invite-registration.test.mjs:22` asserted
+201
# the source contained `invite list all:true`, the exact string the real
+202
# server rejects, so the test pinned the broken form in place (folio
+203
# t-7a2f91).
+204
#
+205
# Fixed on enclave master by `f0ebc12 fix(enclave): align PWA service command
+206
# grammar`, which changed the server's grammar, the fake WS server, the test,
+207
# and added server-side unit coverage. Re-checked rather than assumed: the
+208
# string `invite list all:true` no longer appears anywhere in that file on
+209
# master, where it was present at this job's validation base (e0bfc61).
+210
#
+211
# **So the green from that suite no longer carries this specific asterisk.**
+212
# It still carries the general one immediately below -- none of those tests
+213
# execute `app.js` -- which is a different and permanent caveat.
+214
#
+215
# ===========================================================================
+216
# SIGIL IS NOT ON PATH HERE, AND THIS JOB RESOLVES IT RATHER THAN INHERITING IT
+217
# ===========================================================================
+218
#
+219
# Measured on a leased runner: a non-login SSH gets neither `sigil` nor
+220
# `~/.sigil/bin` on PATH. Kiln injects it through `host.path_prefix`, so a job
+221
# works under kiln and the same commands fail in a hand-run rehearsal -- or the
+222
# reverse, if a rehearsal adds the path by hand and the job then depends on it.
+223
# Either direction is a trap. The preflight below adds `~/.sigil/bin` if `sigil`
+224
# is not already resolvable, so this job does not depend on kiln's injection
+225
# without saying so.
+226
#
+227
# THE WASM BUILD DELIBERATELY DOES NOT USE `guix shell -m manifest.scm`, which is
+228
# the workstation recipe in DEPLOY.md and in smoke-wasm-thread-body-dom.sh. The
+229
# image carries zig 0.16.0 at ~/.sigil/toolchain/zig and wasm-opt 125, so sigil's
+230
# own toolchain suffices -- verified by building the runtime end-to-end on a
+231
# leased runner before this file was written.
+232
#
+233
# ===========================================================================
+234
# CHECK GRANULARITY, INHERITED FROM jobs/kiln.run
+235
# ===========================================================================
+236
#
+237
# Every check is named DISTINCTLY so two runs are diffed by NAME, not by count.
+238
# "N defined" in the summary is PASSED+FAILED+SKIPPED+XFAILED+XPASSED computed at
+239
# the end -- outcomes REACHED, not a declared denominator. A job that dies early
+240
# reports a smaller number and still says "defined", which is what makes the
+241
# total real evidence that the script reached its end.
+242
#
+243
# Both structured suites (`sigil test` and the 32 credential gates) get the same
+244
# three guards, because both can report a clean result while having run nothing:
+245
#
+246
# 1. THE REPORT MUST BE ASSERTED TO PARSE, as its own named check, before
+247
# anything derived from it is trusted. On kiln's first run the suite exited
+248
# 1 and wrote no report; that guard SUPPRESSED the size floor and the
+249
# per-failure enumeration rather than letting them pass on empty values.
+250
# 2. A SIZE FLOOR. A suite that stops running most of its files still exits 0
+251
# and nothing else notices.
+252
# 3. ONE NAMED CHECK PER FAILING TEST, so 3 failures becoming 12 is visible
+253
# rather than collapsing into a single FAIL.
+254
#
+255
# ===========================================================================
+256
# TIMEOUT: 1800s, RE-MEASURED 2026-08-06 AGAINST A RUN THAT ACTUALLY RAN
+257
# ===========================================================================
+258
#
+259
# WAS 2400s. That number carried an explicit obligation -- re-measure and tighten
+260
# once the IRC smoke could actually run -- and this is that re-measurement.
+261
#
+262
# run 29 924s IRC smoke died in seconds; DOM smoke failed fast
+263
# run 30 983s same
+264
# run 33 922s IRC smoke RAN END TO END; DOM failed fast; unit suite red
+265
# run 34 981s EVERY STEP RAN: unit suite 888/888, both smokes to completion
+266
#
+267
# Re-derive with `kiln run list --config /srv/kiln/config.json` and diff
+268
# created_at against finished_at. Do NOT count anything out of `kiln run logs`:
+269
# it is heartbeat-SAMPLED and duplicates lines (folio t-08dcc3).
+270
#
+271
# 1800 is 1.83x run 34 (981s) and 1.83x the slowest of the four (983s).
+272
#
+273
# THE OLD JUSTIFICATION FOR 2400 IS SPENT, AND ITS PREMISE WAS WRONG. It read:
+274
# "the IRC smoke currently fails in seconds ... its own fixture timeout is 300s,
+275
# so a future healthy run is expected to be ~300s LONGER". **300s was the
+276
# fixture's TIMEOUT, not its cost.** Run 34 -- the first run in this job's
+277
# history where every step executed -- came in at 981s, INSIDE the 922-983s
+278
# spread of the truncated runs, and 2s FASTER than run 30 which did strictly
+279
# less work. Run-to-run variance (61s, 6.6%) dominates the work difference.
+280
#
+281
# WHY 1.83x AND NOT jobs/kiln.run'S 1.58x. Read that file before "harmonising"
+282
# them: its 1.58x was chosen to compensate for a THIN SAMPLE (two runs agreeing
+283
# to one second), not as a house ratio. This sample is four runs spanning 61s,
+284
# and -- the real reason for the extra margin -- **NOBODY HAS ESTABLISHED WHETHER
+285
# ANY OF THEM WAS A COLD-STORE RUN.** The retained kiln log cannot answer it
+286
# (t-08dcc3), so the slowest run observed is not the slowest run possible, and
+287
# the margin pays for that gap. The two jobs both sitting at 2400 was a
+288
# coincidence of unrelated measurements, never a standard.
+289
#
+290
# THIS TIMEOUT EXISTS TO KILL A HANG, NOT TO FIT THE DISTRIBUTION. A false red on
+291
# a slow-but-correct cold run costs more than the extra minutes on a rare hang.
+292
# RAISE IT FROM A MEASUREMENT, never from a failure: if this starts timing out,
+293
# the question is what got slower.
+294
#
+295
# Runs 29 and 30 are retained above FOR CONTEXT ONLY -- they measured a job that
+296
# skipped three steps and no longer exists. Size future changes on run 34 or later.
+297
#
+298
# MEASURED SEPARATELY, on a leased runner from the same golden image:
+299
# credential-lifecycle gates 129s (four runs: 128, 129, and both CI runs)
+300
# boot-splash smoke 2s (but see the 60s floor below)
+301
# WASM deps install + build ~150s
+302
# sigil test 884 tests, passed twice
+303
#
+304
# AND THE HARNESS ITSELF COSTS ~10 MINUTES THAT THE ABOVE DOES NOT SHOW.
+305
# `run()' sleeps HEARTBEAT_SECS (60) BEFORE re-checking whether the child is
+306
# alive, so EVERY `run' call takes at least 60s no matter how fast the command
+307
# is. There are ten of them, so there is a ~600s floor of pure sleeping: the
+308
# boot-splash smoke measured at 2s will occupy 60s here. Inherited from
+309
# jobs/kiln.run along with the rest of the helper.
+310
#
+311
# For comparison, jobs/kiln.run measured 1521s and 1522s on two runs and sits at
+312
# 2400 (1.58x). This job measures ~980s and sits at 1800 (1.83x) -- faster, and
+313
# still a wider RATIO, for the cold-run reason given above. Do not harmonise the
+314
# two numbers; they are answers to different samples.
+315
#
+316
# ---------------------------------------------------------------------------
+317
# THE OBLIGATION THAT USED TO LIVE HERE IS DISCHARGED, 2026-08-06.
+318
#
+319
# It read: "WHEN t-a979a6 LANDS, RE-MEASURE AND TIGHTEN -- the extra margin
+320
# exists for exactly one thing, and the moment that step completes the reason is
+321
# spent." t-a979a6 landed, run 34 executed every step, the job was re-measured,
+322
# and 2400 came down to 1800. See the TIMEOUT block above for the numbers.
+323
#
+324
# THE COST THAT MOTIVATED IT, kept because it is why the number matters: at 2400
+325
# against a measured ~980, A GENUINELY HUNG RUN BURNED FORTY MINUTES before
+326
# anything killed it -- and with `safety.max_concurrent` at 1 it held the only
+327
# slot for all of it. At 1800 that is thirty.
+328
#
+329
# The obligation was written HERE, beside the number, rather than only in a
+330
# ticket, because a widening justified by a future state outlives its reason by
+331
# default. That worked: it was found and discharged by someone reading the file.
+332
# Keep doing it that way for any future widening.
+333
# ---------------------------------------------------------------------------
+334
#
+335
# RAISE IT FROM A MEASUREMENT, never from a failure. If this starts timing out,
+336
# the question is what got slower.
+337
#
+338
# NOTE THE ASYMMETRY WITH THE GATES: their 129s is ~85% deliberate sleeping, so
+339
# it will NOT shrink on faster hardware and will not grow much on slower.
+340
#
+341
# ===========================================================================
+342
# PORTS
+343
# ===========================================================================
+344
#
+345
# The credential gates need NO fixed port -- they allocate with freePort() and
+346
# port 0, and start both their own servers. The three smoke scripts DO use fixed
+347
# defaults (47659, 46668+48081, 47657) and are given distinct ones here. They run
+348
# sequentially, and that is safe only while `safety.max_concurrent` is 1 on the
+349
# controller. Two enclave runs on one runner would collide on these ports, and
+350
# the symptom would be a server failing to bind rather than anything naming the
+351
# real cause. See folio t-a6651a.
+352
+353
PASSED=0
+354
FAILED=0
+355
SKIPPED=0
+356
XFAILED=0
+357
XPASSED=0
+358
SKIP_LIST=""
+359
XFAIL_LIST=""
+360
XPASS_LIST=""
+361
+362
# ---------------------------------------------------------------------------
+363
# THE CHECK LEDGER -- an ARTIFACT, never a log.
+364
#
+365
# WHY NOT THE LOG: the retained log is heartbeat-SAMPLED and the streamed body
+366
# duplicates marker lines, so a log-grep lies in both directions at once. This
+367
# job also prints outcome-SHAPED lines from its toolchain preflight with a bare
+368
# `echo', which score nothing -- so `grep -c "^ ok:"' over-reports it. Only
+369
# ok()/fail()/skip()/xfail()/xpass() write here, so the ledger inherits none of
+370
# that.
+371
#
+372
# FORMAT: <outcome><TAB><check name>, exactly one line per outcome. TAB is the
+373
# separator because the observability endpoints already use it as the field
+374
# separator no value may contain. Outcome is one of ok/FAIL/SKIP/XFAIL/XPASS --
+375
# FIVE words here where kiln's harness has three, because this job carries the
+376
# xfail/xpass outcomes kiln's does not.
+377
#
+378
# $KILN_ARTIFACTS is <workspace>/artifacts on BOTH the legacy and the node
+379
# execution paths, and the runner's artifact copy-back copies that whole
+380
# directory regardless of any `artifact' step. So this file needs no staging
+381
# step in ci.sgv. Note the controller serves TOP-LEVEL artifact files only, so
+382
# the ledger must land directly in $KILN_ARTIFACTS and not in a subdirectory.
+383
#
+384
# UNSET $KILN_ARTIFACTS IS FATAL, NOT A FALLBACK. Writing the ledger somewhere
+385
# the controller never collects would leave a green run with no oracle and no
+386
# diagnostic -- the absence-passes-for-free shape this block exists to prevent.
+387
if [ -z "${KILN_ARTIFACTS:-}" ]; then
+388
echo " PREFLIGHT: KILN_ARTIFACTS is unset, so the check ledger could not be"
+389
echo " retrieved. Exiting 2 WITHOUT scoring any check: this run would produce"
+390
echo " no oracle, and a green would claim an equivalence nothing measured."
+391
exit 2
+392
fi
+393
LEDGER="$KILN_ARTIFACTS/checks.ledger"
+394
mkdir -p "$KILN_ARTIFACTS" 2>/dev/null || true
+395
# `true >' and NOT `: >'. `:' is a POSIX SPECIAL BUILT-IN: a redirection error
+396
# on one aborts the shell outright, so the `||' branch never runs and the
+397
# operator gets a bare "Permission denied" with rc=1 -- an ordinary RED, blaming
+398
# the commit for an infrastructure fault. `true' is a regular built-in and its
+399
# redirection failure is catchable.
+400
true > "$LEDGER" || {
+401
echo " PREFLIGHT: cannot write the check ledger at $LEDGER"
+402
echo " Exiting 2 WITHOUT scoring any check: see above."
+403
exit 2
+404
}
+405
# Names reach the ledger from command substitutions, so a name can carry a TAB
+406
# or a NEWLINE and split one outcome across two records or three fields.
+407
# Collapse both to spaces: exactly one line, exactly two fields.
+408
ledger() {
+409
printf '%s\t%s\n' "$1" "$(printf '%s' "$2" | tr '\n\t' ' ')" >> "$LEDGER"
+410
}
+411
+412
step() { printf '\n=== %s ===\n' "$1"; }
+413
# `return 0' ON ALL FIVE IS NOT DECORATION. Each of these used to END in an
+414
# assignment, which always yields status 0. Appending a `ledger' call would
+415
# instead hand back `printf'-to-a-redirect's status, so a full disk or an
+416
# unwritable ledger would silently change the value every `ok ... || ...' site
+417
# in this file tests. The explicit `return 0' restores the original contract.
+418
ok() { printf ' ok: %s\n' "$1"; PASSED=$((PASSED+1)); ledger ok "$1"; return 0; }
+419
fail() { printf ' FAIL: %s\n' "$1"; FAILED=$((FAILED+1)); ledger FAIL "$1"; return 0; }
+420
# A skip is a THIRD outcome. It is not a pass with a nice message.
+421
skip() { printf ' SKIP: %s -- %s\n' "$1" "$2"; SKIPPED=$((SKIPPED+1)); ledger SKIP "$1"; SKIP_LIST="$SKIP_LIST
+422
$1 ($2)"; return 0; }
+423
# XFAIL/XPASS are a FOURTH and FIFTH outcome, for a gate that is known red with a
+424
# ticket. THERE ARE NONE RIGHT NOW -- both were retired 2026-08-06 -- and the
+425
# machinery is kept deliberately, because the next one will want it. An expected failure must not be a pass (it would hide the day it
+426
# starts mattering) and must not be a skip (it DID run, and its output is
+427
# evidence). An unexpected PASS is the event worth shouting about.
+428
xfail() { printf ' XFAIL: %s -- expected red, %s\n' "$1" "$2"; XFAILED=$((XFAILED+1)); ledger XFAIL "$1"; XFAIL_LIST="$XFAIL_LIST
+429
$1 ($2)"; return 0; }
+430
xpass() { printf ' XPASS: %s -- %s\n' "$1" "$2"; XPASSED=$((XPASSED+1)); ledger XPASS "$1"; XPASS_LIST="$XPASS_LIST
+431
$1 ($2)"; return 0; }
+432
note() { printf ' %s\n' "$1"; }
+433
+434
# Heartbeat runner, taken from jobs/kiln.run unchanged, including its reasoning:
+435
# THE STATUS COMES FROM `wait', NEVER THROUGH A PIPE. A pipeline yields the LAST
+436
# command's status, and this estate has misread a genuine rc=1 as rc=0 that way
+437
# more than once -- including in this job's own rehearsal, where
+438
# `sigil build ... | tail -30; echo rc=$?` reported rc=0 for the TAIL. The
+439
# rehearsal survived only because it checked for the artifact afterwards instead
+440
# of believing the number.
+441
LOGDIR=$(mktemp -d) || { echo "PREFLIGHT: mktemp -d failed"; exit 2; }
+442
HEARTBEAT_SECS="${HEARTBEAT_SECS:-60}"
+443
FAIL_TAIL="${FAIL_TAIL:-40}"
+444
+445
run() {
+446
_name=$1; shift
+447
"$@" > "$LOGDIR/$_name.log" 2>&1 &
+448
_pid=$!
+449
_elapsed=0
+450
while kill -0 "$_pid" 2>/dev/null; do
+451
sleep "$HEARTBEAT_SECS"
+452
kill -0 "$_pid" 2>/dev/null || break
+453
_elapsed=$((_elapsed + HEARTBEAT_SECS))
+454
printf ' . %ss %s\n' "$_elapsed" "$(tail -1 "$LOGDIR/$_name.log" 2>/dev/null | cut -c1-70)"
+455
done
+456
wait "$_pid"
+457
_rc=$?
+458
if [ "$_rc" -eq 0 ]; then _n=6; else _n="$FAIL_TAIL"; fi
+459
tail -"$_n" "$LOGDIR/$_name.log" | sed 's/^/ | /'
+460
return $_rc
+461
}
+462
+463
step "environment"
+464
echo " host: $(ip -4 -br addr show eth0 2>/dev/null | tr -s ' ')"
+465
echo " whoami: $(whoami)"
+466
echo " work: $(pwd)"
+467
echo " ref: ${KILN_REF:-<unset>}"
+468
echo " sha: ${KILN_SHA:-<unset>}"
+469
echo " cores: $(nproc 2>/dev/null || echo '?') mem: $(free -g 2>/dev/null | awk '/^Mem:/{print $2"G"}')"
+470
+471
# ===========================================================================
+472
step "toolchain preflight (required)"
+473
# ===========================================================================
+474
# A missing tool must read as "could not measure", never as a build failure, so
+475
# this exits 2 before any work rather than scoring a red.
+476
#
+477
# `sigil` is resolved rather than assumed -- see the header. This is the only
+478
# PATH manipulation in the file, and it is conditional so it cannot mask kiln's
+479
# own injection being removed.
+480
if ! command -v sigil >/dev/null 2>&1; then
+481
if [ -x "$HOME/.sigil/bin/sigil" ]; then
+482
PATH="$HOME/.sigil/bin:$PATH"
+483
export PATH
+484
echo " note: sigil was not on PATH; added \$HOME/.sigil/bin"
+485
fi
+486
fi
+487
+488
PREFLIGHT_BAD=0
+489
# wasm-opt is REQUIRED, not optional. Without it the WASM build still exits 0,
+490
# skipping asyncify with only a warning, and produces a module that fails to
+491
# instantiate. A missing tool must stop this job, not quietly change what it
+492
# builds. guix is here because smoke-wasm-thread-body-dom.sh shells out to it.
+493
for t in sigil git node jq curl tar sha256sum mktemp timeout sed grep bash wasm-opt guix; do
+494
p=$(command -v "$t" 2>/dev/null)
+495
if [ -n "$p" ]; then
+496
echo " ok: $t -> $p"
+497
else
+498
echo " MISSING: $t"
+499
PREFLIGHT_BAD=1

Showing the first 500 of 1398 diff lines for this file. This diff is INCOMPLETE; read the file or clone the repository for the rest.