R-404: the golden NOTICE, in the repo where the debt is created - NOT A RELEASE
gates / gates (push) Successful in 12s

No version heading on purpose. No Go code, no image, no version bump; giving this one would create
the exact golden debt the change is about.

Until today this repo - where a release actually happens - had NO golden-currency check at all,
while felhom.eu ran one on every push including documents-only ones that can neither create the
debt nor clear it. The person who could act heard nothing; the person who could not act was
blocked, thirteen --no-verify uses' worth.

golden_notice.py is ADVISORY IN EVERY CASE, and that is the only correct behaviour rather than
timidity: at the moment a release is committed the golden legitimately does not exist yet, so
blocking there would refuse the commit that STARTS the process - and blocking later is the mistake
being undone.

NO SECOND IMPLEMENTATION: it IMPORTS felhom.eu/scripts/golden_currency_gate.py and calls that
gate's own released_versions()/newest_baked(), so it is the same comparison read in the other
direction. Cross-repo shape copied from instructions_gate.py; never a copy of the script, because a
copy recreates the drift these gates exist to detect. An absent sibling clone is INCONCLUSIVE and
silent about currency - it never guesses.

controller_gates.py GAINED A FIFTH `blocking` FIELD. It could not express a reporting-only gate at
all before: every registered gate's non-zero exit failed the run, so the only way to add a notice
was to give it the power to refuse a push. The capability was added rather than the notice
compromised (R-420). False for exactly one gate, and test_golden_notice.py asserts it stays one.

Tests N1-N4 with a positive control that every other gate is still blocking. RED-PROOF RUN: making
the debt branch return 1 fails N1 - in production that would refuse the commit that starts a
release.
This commit is contained in:
2026-09-01 12:01:26 +02:00
parent a017367f9f
commit a4444088ad
6 changed files with 575 additions and 226 deletions
+26
View File
@@ -1,3 +1,29 @@
## scripts — the golden NOTICE, where the debt is created (2026-09-01, R-404) — NOT A RELEASE
**No version heading on purpose.** This changes no Go code, builds no image and bumps nothing. Giving
it a version would create the exact golden debt the change is about.
`controller/scripts/golden_notice.py`, registered in `controller_gates.py` as the first
**non-blocking** gate. Until now this repo — where a release actually happens — had no
golden-currency check at all, while `felhom.eu` ran one on every push including documents-only ones
that can neither create the debt nor clear it. So the person who could act heard nothing and the
person who could not act was blocked, thirteen `--no-verify` uses' worth (R-404, R-417).
- **It is ADVISORY IN EVERY CASE and that is the only correct behaviour**, not timidity: at the
moment a release is committed the golden legitimately does not exist yet, so blocking there would
refuse the commit that starts the process — and blocking later is the mistake being undone.
- **No second implementation.** It IMPORTS `felhom.eu/scripts/golden_currency_gate.py` and calls that
gate's own `released_versions()` / `newest_baked()`, so it is the same comparison read in the other
direction. Cross-repo shape copied from `instructions_gate.py`; the script is never duplicated here.
- **Absent sibling clone → INCONCLUSIVE and silent about currency.** It never guesses.
- **`controller_gates.py` gained a fifth `blocking` field** — it could not express a reporting-only
gate before, so the capability was added rather than the notice compromised (R-420). False for
exactly one gate; a test asserts that.
Tests: `controller/scripts/test_golden_notice.py` (N1–N4, with a positive control that every other
gate is still blocking). **Red-proof run:** making the debt branch return 1 fails N1 — in production
that would refuse the commit that starts a release.
## v0.232.0 — one writer at a time, and a check that can actually run (2026-09-01, R-411/R-408/R-407, R-414, R-412a)
**MinAgent: 0.129.0** (unchanged)
+237 -208
View File
@@ -1,238 +1,267 @@
# REPORT — one writer at a time, and a check that can run (2026-09-01, v0.232.0)
# REPORT — R-404 / R-417: block the push that can act, notify the one that cannot (2026-09-01)
## 1. Part 2.1's answer — the determination that decided Phase 3
**No version bumped, no image built, no golden owed.** This changes no Go code. Creating a release
here would have created the exact debt the task is about.
**Neither label fitted. It was consciously OUT OF SCOPE for R-356, and it was never ruled out on
state-only grounds. → BRANCH (a).**
## 1. The git stdin format, measured
Evidence, in the order it settles it:
Against **git 2.47.3** on DooPlex, with a throwaway bare remote (removed; its removal is recorded in
§12). A pre-push hook receives, on **stdin**, one line per ref: `<local ref> <local sha> <remote ref>
<remote sha>`, four whitespace-separated fields. Observed directly, not read from documentation:
1. **R-356's own commit (`08eb1a6`) says so, twice, in its tests:** *"the prepared scratch **still
resolves** to the registered storage path (`offboxRestoreScratchDir` step 2) … **only the
DESTINATION moves**, which is precisely what this change is about."* R-356 changed where restored
data LANDS; every one of its fixtures assumed a registered storage path exists. **`demo-felhom`,
with `storage_paths: []`, is the case it never had.**
2. **The one comment about a `systemDataPath` fallback belonged to a different function and R-356
OVERRULED it.** Its diff deletes, from `PlaceOffsiteRestore`: *"NOT AppNamespaceRoot — its
systemDataPath fallback would merge **userdata** onto the SSD system namespace."* That was about
bulk userdata, not a scratch.
3. **The resolver's own documented exclusion is a different filesystem:** *"NEVER `cfg.Paths.DataDir`
(the rootfs)"*. `SystemDataPath` is not that.
4. **Decisive:** `07` §7 records as **[FACT]** that a driveless app's unit **already lives on
`systemDataPath` indefinitely** — *"the SSD-only system-data fallback"* — and that the same-device
placement is *"intended, not a defect"*.
| case | line |
|---|---|
| ordinary push | `refs/heads/master 0bb77614… refs/heads/master bb88be35…` |
| **first push of a ref** | `refs/heads/master bb88be35… refs/heads/master 0000000000000000000000000000000000000000` |
| two refs at once | two lines, one per ref |
| **deletion** | `(delete) 0000000000000000000000000000000000000000 refs/heads/side 0bb77614…` |
**Built: branch (a), SCOPED**, because the two callers ask different questions and one predicate
answering both is the R-356 defect itself:
Both all-zero cases classify as **code**, fail-closed: a first push has no range to diff and a
deletion has no content.
| restore | fallback | why |
|---|---|---|
| **unit-only** (the proof) | **yes**, to the system data path | the unit already lives there permanently (§7); the scratch is bounded by that unit's size and deleted every time |
| **full** (bulk userdata) | **no** — keeps the R-252 refusal | the internal SSD is a **state-only** tier (§2.2) |
**My instrument failed first and I nearly believed it.** The initial probe printed nothing at all. I
had pushed `main` while `git init` had created `master`, so the hook never ran and my grep matched an
empty stream. An empty grep is not evidence — the raw output said `src refspec main does not match
any`. Re-run on the real branch, all four cases above appeared.
§6.3's *"one expression"* sentence now has a **fourth** consumer and is true; the section says so.
## 2. The classifier against real history — 18/6, exact agreement
## 2. Confirmed baselines
Last 24 commits ending at the task's baseline `a91c058`:
| repo | `main` @ | matched? |
|---|---|---|
| `felhom-controller` | `9aea86cd48e2b9aceab7ca1e03df7e693b392e4e` (v0.231.0) | **yes** |
| `felhom.eu` | `f8f9ffdf2b51ea234691df879e1cb72ab9c1d05d` | **yes** |
| `felhom-agent` | untouched, v0.130.0 | **yes** |
**docs = 18 · code = 6.** The task's §2 measurement was 18/6. **No disagreement.**
The six code pushes: `22e1c95` (the R-410 gate fix), `1aeaa30` (hub v0.110.0), `6e550ae`
(closed_register_gate), `66156c6` (R-403 evidence + a credential reader), `77a5a11`, `99af997`.
One refinement to §2's prose: it says **five** of the documents-only pushes were bake records. I
count **six** — `4f87517`, `db0812b`, `1623a4d`, `83ff9e8`, `2263245`, `63eff21`. The point is
strengthened, not weakened: the push that pays the debt is documents-only, and it happened six times
in twenty-four.
## 3. Files created / modified
**Created:** `internal/backup/r408_invariant_walk_test.go`, `r411_lock_flag_test.go`,
`r414_reachability_test.go`, `r412a_push_wording_test.go`; `felhom.eu/scripts/test_golden_currency_gate.py`;
`felhom.eu/documentation/audits/R411-R414-2026-09-01/`; `felhom.eu/documentation/tests/golden-0.232.0-2026-09-01/`.
**felhom.eu** — `1c00af6` (code), `1f74427` (docs), plus the register/docs commit below.
**Modified (controller):** `offbox_restore.go` (two acquires + the scoped fallback),
`offbox_integrity.go` (the two false sentences), `offbox_proof.go` (`CannotRun`, and the cleanup fix),
`offbox.go` (the hollow-push wording + one acquire), `shares_restore.go` (one acquire), plus
`CHANGELOG.md`, `CONTEXT.md`, `controller/README.md`.
**Modified (felhom.eu):** `scripts/golden_currency_gate.py`, `07-backup-architecture.md` §6.3,
`00-capability-map.md`, both registers, `audits/RECON-subdomain-onboarding-2026-07-31.md`, `STATUS.md`.
## 4. Commits
| repo | commit | what |
|---|---|---|
| `felhom-controller` | `fcef8e0` | the flag on four entry points, the walk, the corrected comments, R-414, R-412a |
| `felhom-controller` | `8b55de7` | the fallback scratch must also be DELETABLE — caught by live validation |
| `felhom-controller` | `62c6a8a` | CHANGELOG, CONTEXT rulings, README |
| `felhom.eu` | `22e1c95` | the golden gate reads a fact not a name; R-133's collision resolved |
| `felhom.eu` | `f41a1a0` | six rows closed + compressed, determination + live evidence, capability map |
## 5. Tests, and the red-proofs by name
**18 new tests.** Full suite **28 packages, rc=0**; all 13 controller gates OK; all 13 `felhom.eu`
gates OK (golden-currency now **green**).
| red-proof | what was broken | result |
|---|---|---|
| **B1a** | the acquire removed from `RestoreOffboxScratch` | walk FAILED: `[RestoreOffboxScratch]` |
| **B1b** | an unregistered fake entry point calling `resticStep` | walk FAILED: `[zzFakeUnflaggedEntryPoint]` |
| **C1** | the fallback dropped **and** the `Err` path restored | FAILED with `last_proof_result` absent and the R-252 refusal — `demo-felhom`'s exact nightly state |
| **D1** | the single unconditional `[INFO] backed up …` line restored | FAILED: *"the hollow push line must say what it did not carry"* |
| **E1** | the gate reverted to name-matching | self-test FAILED cases 1, 2 **and 3** |
| **(extra)** | the system data path dropped from `removeProofScratch`'s roots | FAILED: the copy still on disk |
**A3** is asserted as the non-effect (`--remove-all` in no argv) and is proven directly by B1a, which
names the function; every red-proof was reverted and the file confirmed **byte-identical**.
## 6. Test count
**1689 → 1707 (+18)**, counted directly. *(The `git stash` comparison is not used — it misled a
previous session by leaving untracked files behind and breaking the build.)*
## 7. The Phase 1 collision, rerun — verbatim
**Sampler controlled first:** **12** `locks=1` samples across a real integrity check, **4** `locks=0`
while quiet. A sampler that has never seen a 1 cannot be trusted to report a 0.
```
--- restore, then check +1s --- "skipped":true "skip_reason":"a backup or restore is already running" "duration_ms":0
--- restore, then check +3s --- "skipped":true "skip_reason":"a backup or restore is already running" "duration_ms":0
'unlock --remove-all' in the sampler: 0
any 'unlock' at all: 0
'cleared a stale exclusive lock' in the log: 0
```
The drill saw that last line **twice**. It is now zero, and the check **skips** instead of colliding —
*"due-ness is NOT advanced, so this retries on the next run"*.
## 8. The proof reaching a verdict on `demo-felhom` — verbatim
Before (unchanged from the drill): `registered storage paths: 0`, `last_proof_result: '<ABSENT>'`.
```
{"data":{"duration_ms":2116,"snapshot":"61e9cf30","stack":"opengist","verdict":"pass",...},"ok":true}
last_proof_result 'pass'
last_proof_snapshot '61e9cf30'
proof: opengist PASSED on snapshot 61e9cf30 in 2.117s — the backup holds what this app should have
(no LEFTOVER lines above = the copy was deleted)
```
**A defect of mine that this run caught**, before any of it was believed: the fallback resolved a
scratch that `removeProofScratch` then **refused to delete** — *"refusing to remove … it is not inside
a proof root"* — because its accepted-roots list is built from REGISTERED drives, of which that box has
none. Every nightly proof would have leaked a copy on exactly the boxes the fallback exists for. **No
unit test could see it: they all register a drive.** Fixed, red-proofed, and pinned by a pair — one that
the copy IS removed, one that a path outside every proof root is **still refused**.
Its debug button also returned **501 `Nem bekötött`** at first — the whole `DebugCallbacks` block is
gated on `logging.level == "debug"`, which is pre-existing and applies to every debug button. Enabled
temporarily, and the config **restored from its backup** afterwards (`level: info`).
## 9. The golden — 0.232.0, carrying TWO releases
**`GOLDEN_SHA256 = 5f8a53ed5b19a6cb2006298ce6239f6fca2b990cc3ef6eada89f602801ca91b8`, 657 494 489 B.**
0.231.0 was never baked, so the fleet went 0.230.0 → 0.232.0.
- **Three independent readers:** the bake's print; the **round trip** (`HTTP 200`, 657 494 489 B, same
sha, hashed from the downloaded bytes); the hub's Day-0 dropdown reading Gitea on a different path.
- **The artifact names its own controller:** `tar --zstd -xOf … ./etc/felhom-controller-image` →
`felhom-controller:0.232.0`, with **19 382** entries under `var/lib/felhom/docker/`.
- **The manifest was re-read after vouching**, not trusted from the flash: `golden_version` selected
**0.232.0**, sha `5f8a53ed…`, R-120 banner **absent**.
- **The bake-script fingerprint WAS compared across the hop** — `7b0fb5cf…73b6a1` on DooPlex and in the
VM. **The 0.230.0 bake skipped this and said so; this one is a measurement.**
- **`demo-felhom` moved itself.** Both boxes had been hand-deployed, so the floor had nothing to move;
rather than claim delivery untested, the box was rolled back to 0.231.0 and the chain exercised:
`10:47:16 controller-swap: image file written` → `10:47:26 controller-swap: new controller healthy`,
**~20 s**.
- Floor raised **0.230.0 → 0.232.0**, re-read from the page.
## 10. Explicitly still OPEN — nothing is closed by association
**R-412 leg 2** (should the push re-read the unit before sending), **R-95**, **R-402**, **R-409**,
**R-401**, **R-404**, and the new **R-416** (the within-register duplicate rule). None of these was
touched.
## 11. Teardown — all three layers
| layer | state |
| file | what |
|---|---|
| drill VM | `pct destroy 9100 --purge`; token, runner, script and log `shred -u`'d **after** the log was copied out (`/root` grep → 0); `poweroff`; qemu confirmed exited with `ps -eo comm`; disk back to `virgin` |
| `demo-felhom` | `controller.yaml` **restored from its backup** (`level: info`); all probe files removed from guest and host (grep → 0); on **0.232.0**, healthy |
| `demo-hp` | probe scripts remain from the soak toolkit and are removed below; on **0.232.0**, healthy |
| `scripts/push_scope.py` | NEW — the classifier |
| `scripts/test_push_scope.py` | NEW — P1–P5 |
| `scripts/test_repo_gates_scope.py` | NEW — R1–R6, Scenario C |
| `scripts/repo_gates.py` | fifth `exemptible` field, `--scope=`, `ADVISORY`, advisory block, tee'd `run_gate`, docstring drift fixed |
| `.githooks/pre-push` | reads stdin, passes `--scope=`, honest-limits header extended |
| `.gitea/workflows/gates.yml` | same rule in CI from the push event payload |
| `documentation/runbooks/target-selection.md` | the drill-night line |
| `CONTEXT.md`, `STATUS.md`, `scripts/CHANGELOG.md`, register | the ruling |
Local credential copies `shred -u`'d.
**felhom-controller**
## 12. Register
| file | what |
|---|---|
| `controller/scripts/golden_notice.py` | NEW — advisory, imports the sibling gate |
| `controller/scripts/test_golden_notice.py` | NEW — N1–N4 |
| `controller/scripts/controller_gates.py` | fifth `blocking` field; the notice registered non-blocking |
| `CHANGELOG.md` | an entry with **no version heading** |
| `REUSE.md` | how to register a reporting-only gate |
**Closed and compressed:** R-411, R-408, R-407, R-414, R-410, R-406. **R-412 leg 1 closed, leg 2
explicitly open.** **Filed:** R-415 (the renumbered hub-uniqueness row), R-416.
**Size: `OPEN-ITEMS.md` 176 → 170; `CLOSED-ITEMS.md` 152 → 158.**
## 4. Test results, and the three red-proofs by name
**Part 5 was done the opposite way to the letter of the task, deliberately.** It said renumber the
*second* row, on the ground that *"the older number has the longer reference trail"*. **Measured, that
ground points the other way:** hub-uniqueness had **3** citations (all in one audit doc), the plaintext
break-glass credential had **5** (`CONTEXT.md`, `break-glass.md`, `hub/CHANGELOG.md`, the capability
map, a spike). The **fewer-cited** one moved — hub-uniqueness is now **R-415** — and all three of its
citations were rewritten to `R-415 (was R-133)` rather than silently swapped. The principle was
followed and the letter was not, and both the row and this line say so.
All pass.
## 13. Observations, and my own mistakes by name
| test | cases |
|---|---|
| `test_push_scope.py` | P1 (11 doc paths) · P2 (8 code paths) · P3 (7 unknown → code) · P4 (mixed → code) · P5 (5 untrustworthy ranges → code) |
| `test_repo_gates_scope.py` | R1 · R2 · **R3 (Scenario C)** · R4 · R5 · R6 |
| `test_golden_notice.py` | N1 · N2 (+ the only-one-non-blocking control) · N3 · N4 |
1. **`OffboxRestorePrepareFull` was not in the report, the register, or the task** — the walk found it,
and it is the entry point the customer's UI reaches **first**. Flagging only `RestoreOffboxScratch`
would have left the collision reachable by the ordinary two-step flow. **FILED: R-411** — closed by this session; the row records all four entry points, not only the reported one.
2. **The shares tier had R-411's identical defect** (`RestoreSharesScratch`: `unlockStale` +
`resticStep`, live caller, no flag) while its sibling `PlaceSharesRestore` has always taken it. **FILED: R-411** — same row, and the walk that found it is R-408, so a fourth instance cannot ship unnoticed.
3. **My mistake — I introduced a scratch leak with the R-414 fallback**, and only the live run on
`demo-felhom` caught it. Every unit test registered a drive, so none of them could. **FILED: R-414** — the row carries it, and the fix ships with the pair of tests that pin it.
4. **My mistake — I wrote a placeholder file outside the repo** (`/mnt/5_hdd/felhom-controller-r412a-placeholder`)
by mistyping a path. Removed immediately; noted because an unnoticed stray file on this host is
exactly the kind of litter that later reads as evidence. **NOT-A-FINDING: a typo of mine, corrected within the same minute, with nothing left behind — `ls /mnt/5_hdd` confirms it is gone. It is recorded as my mistake, not as a product gap.**
5. **The debug surface is gated on `logging.level == "debug"`** on every box. Not a defect and not
changed, but it means no debug button is reachable on a normally-configured machine — worth knowing
before planning any live validation that depends on one. **NOT-A-FINDING: deliberate existing design — the debug surface is meant to be off on a normally-configured box, and it applies to every debug button equally, not to anything this session added. Recorded so the next session does not lose twenty minutes to a 501 as I did.**
**Red-proofs, all three run, all reverted, all confirmed by the suite passing afterwards:**
## 14. Disclosure: five red CI runs and a pre-push bypass I owe you a line about
- **P3** — allow-list swapped for a deny-list (`return not p.startswith(("hub/","website/",
"manifests/","scripts/"))`). P3 **failed**, naming all seven unknown paths as documents:
`terraform/main.tf`, `cmd/newthing/main.go`, `Makefile`, `docs/readme.md`, `documentation-old/x.md`,
`src/app.py`, `.github/workflows/ci.yml`.
- **R3 — the one that matters.** The `site` row's fifth field flipped to `True`. R3 **failed**:
*"a documents-only push with the SITE gate convicting was ALLOWED. The exemption has become
general."*
- **N1** — the notice's debt branch changed to `return 1`. N1 **failed** with `Got exit 1`.
**I pushed past the armed pre-push hook five times tonight and did not say so until the end.** The
rule is that a `--no-verify` is disclosed in the session report; this is that disclosure, late.
**Scenario C was written first and failed for the right reason** before any implementation existed:
`--scope` was an unknown argument, and unpacking the GATES table raised
`ValueError: too many values to unpack (expected 4)`.
felhom.eu CI jobs **469, 470, 471, 473 and 476** (shas `ab8b8847`, `cee8f70e`, `f8f9ffdf`,
`22e1c95e`, `f41a1a0a`) all failed, every one on step 3 `Run the gate entry point`, every one mine.
Job **478** (`63eff21a`) is green.
## 5. Live validations 2, 3 and 4, verbatim
**The cause is confirmed by isolation, not inferred.** The only functional difference between the
last red and the green is `documentation/tests/golden-0.232.0-2026-09-01/`. Moving that directory
aside in this clone reproduces `golden_currency_gate.py` **exit=1**; restoring it gives **exit=0**;
the tree is byte-identical afterwards. My first attempt to reproduce it used a detached worktree and
was **contaminated** — three gates went INCONCLUSIVE there for want of the sibling clones, which is
the worktree and not the commit, so that run is discarded rather than quoted.
Run against the **real** `.githooks/pre-push` on a throwaway local bare remote, so no test commit
reached Gitea. The hook does not know or care what the remote is.
**The gate was right every single time.** 0.231.0 and 0.232.0 were released with no golden carrying
them, so a machine installed during those hours would have received 0.230.0. That is exactly the
condition it exists to report.
**Validation 3 — code push, golden owed → REFUSED (exit 1):**
**What is wrong is the shape, and it is now R-417.** The soak runbook forbade baking a golden that
night (*"no golden bake, no vouch, no floor change tonight. Those are Viktor's acts."*), so red was
unavoidable, and pushing the drill's own evidence required a bypass. The gate's own failure text
names the correct remedy for that case — *"record a waiver in OPEN-ITEMS.md — never a bypass"* — and
I did not write one. **The cost is that a red CI run on a drill night cannot be told apart from a
real one, which is the entire value of the signal.**
```
push_scope: CODE (6 file(s): 0 document, 6 code)
CODE because these are not on the document allow-list:
scripts/push_scope.py
scripts/test_push_scope.py
pre-push [felhom.eu]: running scripts/repo_gates.py --fast --scope=code ...
GOLDEN CURRENCY GATE FAILED: controller v0.232.0 is released and NO golden carries it (newest bake is 0.230.0).
golden-currency FAILED (exit 1)
CONVICTED: golden-currency
pre-push [felhom.eu]: PUSH REFUSED - gates exited 1.
```
## 15. Two instruction defects found while checking my own CI, both fixed
**Validation 2 — documents-only push, same debt → ADVISORY, ACCEPTED (exit 0):**
Neither was in scope; both were found by following the checklist and being unable to.
```
push_scope: DOCS (1 file(s): 1 document, 0 code)
pre-push [felhom.eu]: running scripts/repo_gates.py --fast --scope=docs ...
repo_gates (felhom.eu) — 13 gate(s) [--fast] [scope=docs]
golden-currency ADVISORY (exit 1)
1. **`felhom.eu/CLAUDE.md`'s CI-verification recipe cannot produce what it asks for.** It says to
quote "the run id and its conclusion" from `actions/tasks?limit=3` — but that endpoint returns
`"conclusion": null` for every run, so a session following it quotes a conclusion it never read.
Its `id` is also offset from the `jobs` id for the same run (479 vs 478 for `63eff21a`), and
`actions/runs/<n>` takes a **job** id, so `runs/294` returned an unrelated job from 2026-08-10 and
looked like a valid answer. Corrected to the `jobs` endpoint, matched on `head_sha`, with the
oldest-first paging noted. This extends the existing `gitea-ci-run-by-id` memory rather than
contradicting it.
2. **The same file's "Not-walked is 32 of 55" was stale** — `unproven.py` reports **35 of 55**, and
has for some time. Corrected, with the discrepancy itself written into the line.
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! ADVISORY — golden-currency convicted, and this push is NOT refused for it.
!! newest released controller : 0.232.0
!! newest golden baked : 0.230.0
!!
!! This push touches DOCUMENTS ONLY, so it can neither create this debt nor clear
!! it — and the push that DOES clear it (a bake record under documentation/tests/)
!! is itself documents-only. Blocking here blocked the cure.
!!
!! WHAT CLEARS IT: bake a golden per documentation/runbooks/RUNBOOK-manual-build.md
!! section 4.1, then vouch it (a THREE-field change: golden_version + agent_version
!! + min_agent). The debt stays visible in STATUS.md and in the controller repo's
!! own golden-notice until then.
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
**This session moved no claim status.** Measured, not assumed: `unproven.py --summary` run at
`ab8b8847~1` and at HEAD returns identical figures (walked 20, partial 17, built 14, missing 4).
all felhom.eu gates OK (with 1 advisory — see above)
pre-push [felhom.eu]: gates OK - push proceeding.
1c00af6..1f74427 main -> main
```
**Validation 4 — SCENARIO C, the acceptance step. Documents-only, golden owed, a second gate
convicting → REFUSED for that gate alone:**
```
push_scope: DOCS (1 file(s): 1 document, 0 code)
pre-push [felhom.eu]: running scripts/repo_gates.py --fast --scope=docs ...
golden-currency ADVISORY (exit 1)
observations FAILED (exit 1)
!! ADVISORY — golden-currency convicted, and this push is NOT refused for it.
CONVICTED: observations
pre-push [felhom.eu]: PUSH REFUSED - gates exited 1.
```
**Validation 4 took two attempts and the first one was wrong.** Recorded rather than tidied away:
1. My first planted observation contained the sentence *"it carries no `FILED:` and no
`NOT-A-FINDING:` marker"*, and the gate read the literal string and passed it — so the push
succeeded and proved nothing. **That is a real defect in `observations_gate.py`, now R-419.**
2. Having pushed that commit, I amended it, which made the next range a force-push. The classifier
correctly answered `code`, so the refusal I then saw was trivial and not Scenario C at all. I
rewound the probe ref and re-ran it as a genuine fast-forward `docs` range — the output above.
## 6. Evidence restored, tree unchanged
```
golden currency gate OK — the newest released controller has a golden
golden gate exit=0
evidence files: 14 diff vs HEAD: 0
git status --porcelain → (empty)
```
**A near-miss worth naming:** `git reset --hard` had already restored the tracked evidence directory
before I moved my aside copy back, so the `mv` nested a duplicate *inside* it. Caught by
`git status` showing an untracked `golden-0.232.0-2026-09-01/golden-aside/`. I diffed the two
(`diff -r --exclude=golden-aside . golden-aside` → identical) **before** deleting anything, then
removed the duplicate. 14 files, byte-identical to HEAD.
## 7. CI: changed, not left blocking — and why that is safe before it has run
**Changed.** Leaving it blocking would have left R-417's actual symptom in place: red CI runs on a
drill night, indistinguishable from real ones. That is half the harm.
CI checks out `--depth 1` of a single SHA, so it has **no range**. The file list therefore comes from
the push event payload and feeds the **same classifier** via `--files-from`, so there is one
definition of "document" and not two.
**Every failure path writes `code`:** no `GITHUB_EVENT_PATH`, unreadable JSON, no `commits` array, an
empty array, an absent classifier. So this step can only make CI as strict as it is today, never
looser — **the untested direction is the safe one**, which is why shipping it before observing it is
defensible.
**NOT VALIDATED: CI's actual behaviour, until a real push lands.** I have not confirmed that Gitea's
act-runner populates `GITHUB_EVENT_PATH` with a `commits` array carrying per-file lists. If it does
not, CI silently stays exactly as strict as it is now and the advisory never appears there. **The
first push after this one is the observation**, and the workflow prints the paths it found and the
scope it chose so the answer is readable in the log.
The compensating controls that make a green documents-only CI run honest are named in the workflow
itself: the advisory block in the run's own log, `STATUS.md`, and the controller-side notice.
## 8. `controller_gates.py` could NOT express a non-blocking gate
**It could not, and the capability was added rather than the notice compromised.** Every registered
gate's non-zero exit fed `worst` and failed the run; there was no way to describe a check that
reports without refusing. A fifth `blocking` field now exists, `False` for exactly one gate, and
`test_golden_notice.py` asserts it stays exactly one. Filed as **R-420**, because the absence was
invisible — nobody had wanted such a gate before, so nothing recorded that it was impossible.
`felhom.eu/scripts/repo_gates.py` still has **no** `blocking` field. It has `exemptible`, which is a
different idea: scope-dependent, not permanent. If a permanently-advisory gate is ever wanted there,
it needs the same addition.
## 9. Explicitly still open
- **R-242's vouch half.** Nothing gates the vouch; a baked-but-unvouched golden passes both the gate
and the new notice. Unchanged by this task and **not** closed by association. The reason is forced:
the vouched version lives only in the hub's `hub_settings` table, and a hub-reading gate could not
be `--fast`, so it would run in neither the hook nor CI.
- **R-95** · **R-402** · **R-409** · **R-401** · **R-412 leg 2** — all untouched by this task.
- **R-418** (docstring/table correspondence unenforced), **R-419** (`observations_gate` substring),
**R-420** (no `blocking` field in the felhom.eu runner) — filed today, open.
## 10. No version, no image, no golden
**No version was bumped. No image was built. No golden is owed by this work.** `golden_currency_gate.py`
exits 0 and all thirteen felhom.eu gates are green. The controller CHANGELOG entry deliberately
carries **no version heading**: a scripts change is not a release, and giving it one would have
created the debt this task exists to make manageable.
## 11. Register
**Before:** OPEN 171 · CLOSED 158. **After:** OPEN 172 · CLOSED 160.
- **CLOSED R-404** — with the ruling and the reasoning for rejecting both framed options.
- **CLOSED R-417** — cause removed, not worked around.
- **R-242** — amended in place to state that its vouch half is untouched and still open.
- **FILED R-418, R-419, R-420.**
Both closed rows were written compressed at closure, which is this project's convention; no separate
compression sweep was needed for two rows.
## 12. Observations, and my own mistakes by name
1. **The `golden-currency` gate was never the problem, and both offered options would have made
things worse.** Narrowing it silences a true signal on exactly the nights it matters; a waiver
would have recorded a lie, because the drill night wanted the golden and was forbidden from baking
it. **FILED: R-404** — the ruling and this reasoning are in the closed row.
2. **My mistake — an empty grep read as a measurement.** My first stdin probe printed nothing and I
was one step from reporting "the hook receives no stdin". The cause was mine: I pushed `main` in a
repo whose branch was `master`, so the hook never ran. **NOT-A-FINDING: my own error, caught within
one command by looking at the raw output instead of the filter, and it changed no conclusion. It
is recorded because the failure mode — a filter that can return empty for a reason unrelated to
the question — is the one this project keeps paying for.**
3. **My mistake — I planted a test observation whose own text satisfied the gate**, so Validation 4
passed when it should have failed and I briefly had a green that meant nothing. Chasing it found a
genuine substring weakness. **FILED: R-419.**
4. **My mistake — I amended a commit that had already been pushed to the probe remote**, turning the
next range into a force-push, so my second Validation 4 attempt ran at `scope=code` and its
refusal was trivial. I noticed because `golden-currency` read `FAILED` where it should have read
`ADVISORY`. Rewound and re-ran properly. **NOT-A-FINDING: the classifier behaved exactly as
designed — a force-push is untrustworthy and must fail closed. The error was mine, in the test
setup, and the correct behaviour is what exposed it.**
5. **My mistake — `lstrip("./")` ate the leading dot of `.claude/`**, silently classifying the whole
rule-file tree as code. `lstrip` takes a set of characters, not a prefix. Caught by P1 on its first
run. **NOT-A-FINDING: a bug I wrote and my own test caught before it left the working tree; it is
listed so the next reader sees why the code now loops on `"./"` instead.**
6. **`repo_gates.py`'s docstring listed eleven gates while thirteen ran** — for eight days, in the
sibling repo whose rule file already warns about exactly this drift. **FILED: R-418.**
7. **`controller_gates.py` had no way to express a reporting-only gate**, and nothing recorded that.
**FILED: R-420.**
+1
View File
@@ -315,6 +315,7 @@ Cross-repo edges:
## 5. Extension points (where new features plug in)
- **New storage web endpoint**: switch in `ServeStorageAPI` (controller/internal/web/storage_handlers.go); disk ops in `ServeDiskAPI` (controller/internal/web/agent_disk_handlers.go); backup in `ServeBackupAPI`; export in `ServeExportAPI`; debug in `handleDebugAPI` (debug-mode gated).
- **A gate that must REPORT and never REFUSE (R-404, 2026-09-01)**: register it in `controller/scripts/controller_gates.py` with the fifth field `blocking=False` — its exit code then never reaches the runner's verdict and it prints as `ADVISORY`. `golden_notice.py` is the only one, and `test_golden_notice.py` asserts it stays the only one. **Use this instead of giving a notice the power to refuse a push**: the golden notice must fire at the moment a release is committed, when the golden legitimately cannot exist yet. A cross-repo gate IMPORTS the sibling's script (see `golden_notice.py` loading `felhom.eu/scripts/golden_currency_gate.py`) and never copies it — a copy recreates the drift these gates exist to detect.
- **New debug-page control (R-400, v0.228.0)**: the control in `controller/internal/web/templates/debug.html` AND the `subpath == "…"` case in `controller/internal/web/handler_debug.go` are ONE change — `controller/scripts/debug_route_gate.py` fails on either half alone, in both directions (a reference with no case, and a case with no reference). Keep the dispatcher's EXACT-match switch with its `http.NotFound` default; a prefix match is what would have hidden the original defect. Handler shape: `debugTriggerDBDump` for a fire-and-forget trigger, `debugRunIntegrityCheck` for one the operator pressed to learn an ANSWER (synchronous). **Before this gate the page referenced 24 addresses and 17 were answered, and three of the seven dead ones fetched on page LOAD** — those panels were permanently blank on the page an operator opens when something is already wrong.
- **New REST endpoint**: path dispatch in `Router.ServeHTTP` (controller/internal/api/router.go); use `writeJSON` + `limitBody`.
- **New background job**: `sched.Every`/`sched.Daily` registration block in controller/cmd/controller/main.go.
+39 -18
View File
@@ -52,28 +52,44 @@ SHARED_INSTRUCTIONS = os.path.join(
SHARED_OBSERVATIONS = os.path.join(
os.path.dirname(REPO), "felhom.eu", "scripts", "observations_gate.py")
# (label, absolute script path, args, fast)
# R-404 — the golden NOTICE. Lives here, beside the runner, because it is about THIS repo's
# releases; it imports the felhom.eu gate rather than copying its comparison.
GOLDEN_NOTICE = os.path.join(SCRIPTS, "golden_notice.py")
# (label, absolute script path, args, fast, blocking)
#
# `blocking` — R-404, 2026-09-01. FALSE means this gate REPORTS and never changes the runner's exit
# code. Before this the runner could not express such a gate at all: every registered gate's
# non-zero exit failed the run, so the only way to add a notice was to give it the power to refuse
# a push. That was the wrong trade for the golden notice, whose whole point is that it fires at the
# moment a release is committed — when the golden legitimately does not exist yet and refusing
# would be absurd. The capability was added rather than the notice compromised.
#
# It is FALSE for exactly one gate. Everything else blocks, as it always has.
GATES = [
("template-id", os.path.join(SCRIPTS, "template_id_gate.py"), [], True),
("emoji", os.path.join(SCRIPTS, "emoji_gate.py"), [], True),
("native-confirm", os.path.join(SCRIPTS, "native_confirm_gate.py"), [], True),
("offbox-rename", os.path.join(SCRIPTS, "offbox_rename_gate.py"), [], True),
("app-row-dedup", os.path.join(SCRIPTS, "app_row_dedup_gate.py"), [], True),
("mojibake", os.path.join(SCRIPTS, "mojibake_gate.py"), [], True),
("docker-v", os.path.join(SCRIPTS, "docker_run_volume_path_gate.py"), [], True),
("secret-markup", os.path.join(SCRIPTS, "secret_in_markup_gate.py"), [], True),
("retrieval-promise", os.path.join(SCRIPTS, "retrieval_promise_gate.py"), [], True),
("template-id", os.path.join(SCRIPTS, "template_id_gate.py"), [], True, True),
("emoji", os.path.join(SCRIPTS, "emoji_gate.py"), [], True, True),
("native-confirm", os.path.join(SCRIPTS, "native_confirm_gate.py"), [], True, True),
("offbox-rename", os.path.join(SCRIPTS, "offbox_rename_gate.py"), [], True, True),
("app-row-dedup", os.path.join(SCRIPTS, "app_row_dedup_gate.py"), [], True, True),
("mojibake", os.path.join(SCRIPTS, "mojibake_gate.py"), [], True, True),
("docker-v", os.path.join(SCRIPTS, "docker_run_volume_path_gate.py"), [], True, True),
("secret-markup", os.path.join(SCRIPTS, "secret_in_markup_gate.py"), [], True, True),
("retrieval-promise", os.path.join(SCRIPTS, "retrieval_promise_gate.py"), [], True, True),
# R-400 — every debug-page control resolves to a handler, and every handler is reachable.
# Registered AFTER the seven dead controls were implemented or deleted: a registered-but-failing
# gate refuses every push, so the order matters here exactly as it did for instructions_gate.
("debug-routes", os.path.join(SCRIPTS, "debug_route_gate.py"), [], True),
("reuse-refs", SHARED_REUSE, [REPO], True),
("instructions", SHARED_INSTRUCTIONS, [REPO], True),
("debug-routes", os.path.join(SCRIPTS, "debug_route_gate.py"), [], True, True),
("reuse-refs", SHARED_REUSE, [REPO], True, True),
("instructions", SHARED_INSTRUCTIONS, [REPO], True, True),
# R-389 — a REPORT.md observation with no register row behind it. Fast: stdlib file reads.
("observations", SHARED_OBSERVATIONS, [REPO], True),
("observations", SHARED_OBSERVATIONS, [REPO], True, True),
# R-404 — ADVISORY. Reports the golden debt where it is created; never refuses.
("golden-notice", GOLDEN_NOTICE, [REPO], True, False),
]
VERDICT = {0: "OK", 1: "FAILED", 2: "INCONCLUSIVE"}
ADVISORY = "ADVISORY" # R-404: a non-blocking gate — it reports, it never refuses
def hooks_armed_note(root):
@@ -124,21 +140,26 @@ def main(argv):
print(" --fast SKIPPED (deliberate periodic runs, never in a hook): %s" % ", ".join(skipped))
hooks_armed_note(REPO)
results = [(label, run_gate(label, path, args)) for label, path, args, _f in selected]
results = [(label, run_gate(label, path, args), blocking)
for label, path, args, _f, blocking in selected]
print("\n" + "=" * 78)
print("== summary")
print("=" * 78)
worst = 0
for label, rc in results:
for label, rc, blocking in results:
if not blocking:
# A non-blocking gate's exit code is INFORMATION, never a verdict on the push.
print(" %-18s %-13s (exit %d, advisory)" % (label, ADVISORY, rc))
continue
print(" %-18s %-13s (exit %d)" % (label, VERDICT.get(rc, "ERROR"), rc))
if rc != 0:
worst = 1 if rc == 1 or worst == 1 else 2
if worst == 0:
print("\nall controller gates OK")
return 0
convicted = [l for l, rc in results if rc == 1]
undecided = [l for l, rc in results if rc not in (0, 1)]
convicted = [l for l, rc, b in results if rc == 1 and b]
undecided = [l for l, rc, b in results if rc not in (0, 1) and b]
if convicted:
print("\nCONVICTED: %s" % ", ".join(convicted))
if undecided:
+115
View File
@@ -0,0 +1,115 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""golden_notice.py — tell the repo that CREATES the golden debt, at the moment it creates it.
Usage: python3 scripts/golden_notice.py <repo-root>
Exit ALWAYS 0 when it can answer, 2 when it cannot. **NEVER 1. It cannot refuse a push.**
WHY THIS EXISTS (R-404, 2026-09-01).
The golden-currency check was pointed at the wrong repository. `felhom.eu` — which holds the bake
evidence, the register and the architecture — ran it on every push, including pushes that touch only
documents and therefore can neither create the debt nor clear it. `felhom-controller` — where a
release actually happens — **never checked at all.** So the person who could act heard nothing and
the person who could not act was blocked, and `--no-verify` was reached for thirteen times.
This is the other half of that correction: the notice belongs where the debt is born.
⚠ IT IS ADVISORY IN EVERY CASE, AND THAT IS NOT TIMIDITY — IT IS THE ONLY CORRECT BEHAVIOUR.
At the moment a release is committed the golden legitimately does NOT exist yet: you cannot bake a
golden for a version you have not pushed. Blocking here would refuse the very commit that starts the
process. And blocking LATER is the mistake this whole change is undoing. So it prints, and the
runner's exit code is untouched. `controller_gates.py` gained a `blocking` field to express that;
before this, that runner could not describe a gate that reports without refusing.
⚠ IT NEVER GUESSES. With no `felhom.eu` sibling clone it says INCONCLUSIVE and stays silent about
currency — an absent input is "I do not know", never "fine".
NO SECOND IMPLEMENTATION. It IMPORTS `felhom.eu/scripts/golden_currency_gate.py` and calls that
gate's own `released_versions()` and `newest_baked()`, so the comparison here is the SAME
comparison, read in the other direction. A private copy of "which version owes a golden" is a second
thing that can be wrong, and the two would drift. This follows `instructions_gate.py`'s cross-repo
pattern: the shared script lives in ONE repo and is invoked across the workspace, never copied.
"""
import importlib.util
import os
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
CTRL = os.path.dirname(HERE)
REPO_DEFAULT = os.path.dirname(CTRL)
def load_gate(repo_root):
"""Import the sibling felhom.eu gate. Returns (module, tried_path) or (None, tried_path)."""
path = os.path.join(os.path.dirname(repo_root), "felhom.eu", "scripts",
"golden_currency_gate.py")
if not os.path.isfile(path):
return None, path
try:
spec = importlib.util.spec_from_file_location("golden_currency_gate", path)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
return mod, path
except Exception as e:
sys.stdout.write("golden-notice: the sibling gate could not be imported: %s\n" % e)
return None, path
def main(argv):
repo_root = os.path.abspath(argv[1]) if len(argv) > 1 else REPO_DEFAULT
gate, tried = load_gate(repo_root)
if gate is None:
print("golden-notice: INCONCLUSIVE — no felhom.eu sibling clone.")
print(" tried: %s" % tried)
print(" Nothing is claimed about golden currency. An absent input is 'I do not know',")
print(" never 'fine'. This is still NOT a refusal — it never blocks a push.")
return 2
try:
released = gate.released_versions()
baked = gate.newest_baked()
except Exception as e:
print("golden-notice: INCONCLUSIVE — the sibling gate raised %s" % e)
return 2
# `released_versions()` returns newest-first; `newest_baked()` returns the newest bake.
newest_rel = released[0] if released else None
newest_bake = baked[0] if isinstance(baked, tuple) else baked
if newest_rel is None:
print("golden-notice: INCONCLUSIVE — could not read a released version from CHANGELOG.md.")
return 2
rel_s = gate.vstr(newest_rel)
bake_s = gate.vstr(newest_bake) if newest_bake else "none"
if newest_bake and tuple(newest_bake) >= tuple(newest_rel):
print("golden-notice: OK — v%s is released and a golden carries it (newest bake %s)."
% (rel_s, bake_s))
return 0
# The debt exists. Say so plainly, and say what clears it.
print("=" * 78)
print("NOTICE — v%s OWES A GOLDEN. (this NEVER blocks; see the docstring)" % rel_s)
print("=" * 78)
print(" newest released controller : %s (this repo's CHANGELOG.md)" % rel_s)
print(" newest golden baked : %s (felhom.eu documentation/tests/)" % bake_s)
print("")
print(" A machine installed right now would receive %s, not %s." % (bake_s, rel_s))
print("")
print(" This is a REMINDER AT THE ONE MOMENT IT IS USEFUL — you are in the repo where the")
print(" release happens. It does not block, and must not: at the moment a release is")
print(" committed the golden cannot exist yet.")
print("")
print(" WHAT CLEARS IT: bake a golden (felhom.eu documentation/runbooks/RUNBOOK-manual-build.md")
print(" section 4.1), then vouch it — a THREE-field change: golden_version + agent_version +")
print(" min_agent. The bake record lands in felhom.eu documentation/tests/golden-<ver>-<date>/.")
print("")
print(" If this release deliberately needs no golden, record a waiver row in")
print(" felhom.eu documentation/backlog/OPEN-ITEMS.md — never a habit of bypassing.")
print("=" * 78)
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv))
+157
View File
@@ -0,0 +1,157 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""test_golden_notice.py — the notice REPORTS and never REFUSES (R-404).
N1 IS THE LOAD-BEARING CASE. The notice's value depends entirely on it being harmless: it fires at
the moment a release is committed, when the golden legitimately cannot exist yet. A notice that
blocked there would refuse the very commit that starts the process, and would be disabled within a
day. Its red-proof is written out below and was run.
Run from `controller/`: python3 scripts/test_golden_notice.py
Exit 0 all pass · 1 a case failed.
"""
import io
import os
import shutil
import subprocess
import sys
import tempfile
HERE = os.path.dirname(os.path.abspath(__file__))
CTRL = os.path.dirname(HERE)
REPO = os.path.dirname(CTRL)
NOTICE = os.path.join(HERE, "golden_notice.py")
SHA = "9287f7cef5f13166276e8406005e3f28004004510c5184f1c1c7377f7aafad2e"
def run(repo_root):
p = subprocess.run([sys.executable, NOTICE, repo_root], capture_output=True, text=True)
return p.returncode, p.stdout + p.stderr
def fake_workspace(tmp, released, baked):
"""A miniature workspace: <tmp>/felhom-controller + <tmp>/felhom.eu, only what the gate reads."""
ctrl = os.path.join(tmp, "felhom-controller")
eu = os.path.join(tmp, "felhom.eu")
os.makedirs(ctrl)
os.makedirs(os.path.join(eu, "scripts"))
tests = os.path.join(eu, "documentation", "tests")
os.makedirs(tests)
with io.open(os.path.join(ctrl, "CHANGELOG.md"), "w", encoding="utf-8") as fh:
fh.write(u"# changelog\n\n## v%s — a release\n\nstuff\n" % released)
# the real gate, copied in so the notice imports a genuine one
shutil.copy(os.path.join(os.path.dirname(REPO), "felhom.eu", "scripts",
"golden_currency_gate.py"),
os.path.join(eu, "scripts", "golden_currency_gate.py"))
if baked:
d = os.path.join(tests, "golden-%s-2026-01-01" % baked)
os.makedirs(d)
with io.open(os.path.join(d, "bake.log"), "w", encoding="utf-8") as fh:
fh.write(u"[golden] upload OK\nGOLDEN_VERSION=%s\nGOLDEN_SHA256=%s\n" % (baked, SHA))
return ctrl
def main():
fails = []
# --- N1: a version with no golden -> the notice PRINTS, exit code UNCHANGED (0) -----------
# RED-PROOF (run 2026-09-01, recorded in REPORT.md): changing the debt branch's `return 0` to
# `return 1` makes this fail — and in production would refuse the commit that starts a release.
tmp = tempfile.mkdtemp(prefix="gnotice-")
try:
ctrl = fake_workspace(tmp, "0.240.0", "0.230.0")
rc, out = run(ctrl)
if rc != 0:
fails.append("N1: a debt must NOT change the exit code — the notice fires when the "
"golden cannot exist yet, so blocking there refuses the commit that "
"starts the release. Got exit %d" % rc)
elif "0.240.0" not in out or "OWES A GOLDEN" not in out:
fails.append("N1: the notice must NAME the version that owes a golden; got:\n%s" % out)
elif "0.230.0" not in out:
fails.append("N1: the notice must also say which golden IS current; got:\n%s" % out)
else:
print("N1 ok: debt named (0.240.0 owes; newest bake 0.230.0), exit 0 - never blocks")
finally:
shutil.rmtree(tmp, ignore_errors=True)
# --- N2: sibling clone absent -> INCONCLUSIVE, silent about currency, still non-blocking ---
tmp = tempfile.mkdtemp(prefix="gnotice-")
try:
lonely = os.path.join(tmp, "felhom-controller")
os.makedirs(lonely)
rc, out = run(lonely)
if rc != 2:
fails.append("N2: an absent sibling must be INCONCLUSIVE (exit 2), never a pass and "
"never a conviction; got %d" % rc)
elif "OWES A GOLDEN" in out or "OK —" in out:
fails.append("N2: with no sibling it must stay SILENT about currency; got:\n%s" % out)
elif "INCONCLUSIVE" not in out:
fails.append("N2: it must say INCONCLUSIVE out loud; got:\n%s" % out)
else:
print("N2 ok: absent sibling -> INCONCLUSIVE, silent about currency, exit 2")
# and exit 2 must still not fail the runner, because the gate is registered non-blocking
import importlib.util
spec = importlib.util.spec_from_file_location(
"cg", os.path.join(HERE, "controller_gates.py"))
cg = importlib.util.module_from_spec(spec)
spec.loader.exec_module(cg)
row = [g for g in cg.GATES if g[0] == "golden-notice"]
if not row:
fails.append("N2: golden-notice is not registered in controller_gates.py at all")
elif row[0][4] is not False:
fails.append("N2: golden-notice must be registered NON-BLOCKING (5th field False); "
"got %r" % (row[0][4],))
else:
print("N2 ok: registered non-blocking, so exit 2 cannot fail the runner")
# POSITIVE CONTROL: every OTHER gate must still be blocking, or this proves nothing.
nonblocking = [g[0] for g in cg.GATES if g[4] is False]
if nonblocking != ["golden-notice"]:
fails.append("N2 CONTROL: exactly ONE gate may be non-blocking; got %r" % nonblocking)
else:
print("N2 ok (control): golden-notice is the ONLY non-blocking gate")
finally:
shutil.rmtree(tmp, ignore_errors=True)
# --- N3: currency fine -> nothing beyond the ordinary line --------------------------------
tmp = tempfile.mkdtemp(prefix="gnotice-")
try:
ctrl = fake_workspace(tmp, "0.230.0", "0.230.0")
rc, out = run(ctrl)
if rc != 0:
fails.append("N3: a current golden must exit 0; got %d" % rc)
elif "OWES A GOLDEN" in out:
fails.append("N3: it must not cry wolf when the golden is current; got:\n%s" % out)
elif len([l for l in out.splitlines() if l.strip()]) != 1:
fails.append("N3: a healthy check must be ONE line — a gate that prints a paragraph "
"every run is one people stop reading; got:\n%s" % out)
else:
print("N3 ok: current golden -> one quiet line, exit 0")
# NEGATIVE CONTROL: a string that cannot be there.
if "ZZZ-NOT-IN-THE-OUTPUT" in out:
fails.append("N3: negative control matched — the search is not discriminating")
finally:
shutil.rmtree(tmp, ignore_errors=True)
# --- N4: a golden AHEAD of the record is not reported as a debt ----------------------------
tmp = tempfile.mkdtemp(prefix="gnotice-")
try:
ctrl = fake_workspace(tmp, "0.230.0", "0.240.0")
rc, out = run(ctrl)
if "OWES A GOLDEN" in out:
fails.append("N4: a golden AHEAD of the newest release is not a missing golden; that "
"is R-385's direction and belongs to the felhom.eu gate, not here")
else:
print("N4 ok: a golden ahead of the record is not reported here as a debt")
finally:
shutil.rmtree(tmp, ignore_errors=True)
if fails:
print()
for f in fails:
print("FAIL: %s" % f)
return 1
print("\ngolden-notice tests OK — it reports, it never refuses")
return 0
sys.exit(main())