Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 29 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@
# a pushed FILE is readable by anyone — including the agent repairing its
# own red build — via raw.githubusercontent.com/<repo>/diagnostics/
# ci-diagnostics.log. do not remove this step: it is the self-repair loop.
#
# it publishes ON FAILURE ONLY, and it can never fail this job. both were
# learned the hard way on 2026-08-26: the step ran `if: always()`, so a green
# run overwrote the failure log the next reader needed, and the force-push to
# a single branch RACED between the push- and pull_request-triggered runs of
# the same commit. the loser was rejected ("cannot lock ref
# 'refs/heads/diagnostics'") and took the whole verify job red with it — a
# diagnostic breaking the thing it exists to observe, with the gate itself
# green three steps above.
name: ci

on:
Expand Down Expand Up @@ -76,8 +85,11 @@ jobs:
# the self-repair loop, made concrete: whatever failed above, its real
# output lands on the diagnostics branch as a plain file. also mirrored
# into the job summary for humans reading the run page.
#
# `failure()`, not `always()`: a green run has nothing a reader wants,
# and publishing one CLOBBERS the failure log that is the whole point.
- name: publish diagnostics
if: always()
if: failure()
run: |
LOG=ci-diagnostics.log
{
Expand Down Expand Up @@ -111,8 +123,21 @@ jobs:
git rm -rfq . || true
git add "$LOG"
git commit -m "ci diagnostics for ${GITHUB_SHA}" --allow-empty
git push --force \
"https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" \
"HEAD:diagnostics"

# this push must never decide whether the build is green. two runs
# of the same commit (push + pull_request) can reach here together
# and one loses the ref lock; retry, then give up LOUDLY but with
# exit 0. a lost diagnostics log costs a reader one click into the
# job log below it. a red gate over one costs a landing.
REMOTE="https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git"
for attempt in 1 2 3; do
if git push --force "$REMOTE" "HEAD:diagnostics"; then
echo "diagnostics published on attempt ${attempt}"
exit 0
fi
echo "diagnostics push lost the ref lock (attempt ${attempt}); retrying"
sleep $((attempt * 5))
done
echo "::warning::could not publish ci-diagnostics.log after 3 attempts; read the job log above instead. the gate result is unaffected."
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
41 changes: 41 additions & 0 deletions memory/status.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,47 @@
> (agi/rsi gradient) and the constitution now governs every run. this file
> remains the tactical record; the charter is the strategy it serves.

## landed this run (ci: a diagnostic must not break what it observes — agent/ci-diagnostics-must-not-fail-the-gate)

found the honest way: a pr went red with a GREEN gate. `verify` on the
pull_request event failed at `publish diagnostics`, three steps after the
wasm check, the test gate and clippy had all passed.

cause: that step ran `if: always()` and force-pushed to a single
`diagnostics` branch, so the push- and pull_request-triggered runs of the
SAME commit reached it together, one lost the ref lock ("cannot lock ref
'refs/heads/diagnostics': is at X but expected Y"), and its non-zero exit
took the whole job with it.

two defects, both fixed:

- **it published on success.** the workflow's own header says "on any
failure this workflow publishes"; the code said `always()`. so every
green run OVERWROTE the failure log the next reader needed — the same
words-vs-code gap item 3 named as this project's recurring failure mode.
now `if: failure()`.
- **it could fail the job.** the push is retried three times with backoff,
then gives up with a `::warning::` and exit 0. a lost diagnostics log
costs a reader one click into the job log printed directly above it; a
red gate over one costs a landing.

D9's shape applied to ci: an escape hatch that only works when things are
fine is not an escape hatch, and a diagnostic that breaks the thing it
observes is not a diagnostic. pinned by
`the_diagnostics_publisher_cannot_turn_a_green_gate_red` in
tests/ci_gate.rs and verified in BOTH directions — flipping the step back
to `always()` reds the guard.

`workflow_source()` now normalizes \r\n: this checkout has
core.autocrlf=true, and every multi-line shape guard would otherwise go red
locally while ci passed — the trap boot_reconcile fell into in PR #16, now
recurring exactly as the board predicted it would.

LESSON for reading a red pr here: `ci` runs TWICE per pr (push and
pull_request). when one is green and the other is red on the same sha, the
difference is not the code — look at which STEP failed before assuming the
gate caught something.

## landed this run (item 8c: the agent rewrites its own reasoning policy — agent/cognitive-swap-tool)

eighth "keep going", stacked on 8b because github actions was in a major
Expand Down
59 changes: 58 additions & 1 deletion tests/ci_gate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,11 @@ fn the_shared_gate_uses_filesystem_discovery_not_a_list() {
/// these assertions are load-bearing.
fn workflow_source() -> Option<String> {
let path = Path::new(env!("CARGO_MANIFEST_DIR")).join(".github/workflows/ci.yml");
std::fs::read_to_string(path).ok()
// normalized: this repo is edited on a checkout with core.autocrlf=true,
// and every shape guard below would go red on \r\n while ci passed.
std::fs::read_to_string(path)
.ok()
.map(|t| t.replace("\r\n", "\n"))
}

#[test]
Expand Down Expand Up @@ -191,3 +195,56 @@ fn the_e2e_smoke_asserts_boot_not_mere_reachability() {
misleading 'worker never announced ready'."
);
}

/// the diagnostics publisher must not be able to fail the gate, and must
/// only run when there is something to publish.
///
/// both halves are scar tissue from 2026-08-26. the step ran `if: always()`
/// and force-pushed to one branch, so the push- and pull_request-triggered
/// runs of the SAME commit reached it together, one lost the ref lock
/// ("cannot lock ref 'refs/heads/diagnostics'"), and its non-zero exit took
/// the whole verify job red — with the gate itself green three steps above.
/// a green run also overwrote the failure log the next reader needed.
///
/// this is D9's shape applied to ci: a diagnostic that breaks the thing it
/// observes is not a diagnostic.
#[test]
fn the_diagnostics_publisher_cannot_turn_a_green_gate_red() {
let Some(wf) = workflow_source() else {
eprintln!("SKIP: .github/workflows/ci.yml not present in this checkout");
return;
};

let step = wf
.split("- name: publish diagnostics")
.nth(1)
.expect("ci.yml has no `publish diagnostics` step — it is the self-repair loop");

assert!(
step.contains("if: failure()"),
"the diagnostics step must run only on failure. `always()` publishes \
green runs, which CLOBBERS the failure log that is the entire point \
of the diagnostics branch."
);
assert!(
!step.contains("if: always()"),
"the diagnostics step is back on `always()`"
);

// the push is the racy part: it must be retried and it must not be the
// last word on the step's exit status.
assert!(
step.contains("for attempt in"),
"the diagnostics push is not retried; a lost ref lock between two \
concurrent runs of the same commit will fail the job again."
);
assert!(
step.contains("::warning::"),
"a diagnostics push that gives up must say so loudly (D4) rather than \
failing silently or fatally."
);
assert!(
!step.trim_end().ends_with("\"HEAD:diagnostics\""),
"the step ends on a bare push: its exit status decides the gate again"
);
}
Loading