92a60c62bd1340d421ce4328996566214dc0c2cc
168 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
3159892fab |
Both rulings built: same-tag re-tests (decision 52), image retention + the install hold (controller 0.284.2); golden 0.284.2
gates / gates (push) Successful in 27s
- Decision 52: catalog 6a3ead9 (re-test entries, gates, decoys, the monthly command); proven end to end on 9202 through the leg; runbook monthly-floating-retest.md; nothing to re-test on the engine lines today. - Decision 53 + R-741: controller v0.284.2 (0.284.0/0.284.1 never floored — two wiring faults found live on 9202); floor 0.284.2; one-time sweep 9202 26.6 -> 5.7 GB, demo-hp 24.3 -> 13.5 GB; the install hold proven as a stranger. - Golden 0.284.2 baked, round-trip identical, vouched (agent 0.138.0, min_agent 0.131.0); the gate prints OK. - Rows 377 -> 383: opened R-743..R-748, closed R-736, R-737, R-740, R-741, R-748; narrowed R-739, R-698, R-446. - register_shape_gate: a lettered id (R-88a) is a row too (R-748), with a decoy seen red. Evidence: documentation/audits/night-rulings-2026-09-30/, documentation/tests/golden-0.284.2-2026-09-30/. Report: REPORT-night-rulings-2026-09-30.md. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS |
||
|
|
2866f6a318 |
immich's first start: cause measured, fixed in the catalog (R-732 closed); ISO clean-tree gate (R-730 closed)
gates / gates (push) Successful in 27s
- R-732: the first-start geodata import runs up to 9 concurrent 5000-row INSERTs; the database needs ~400 MB anon + ~170 MB touched shared_buffers (the image's FIXED 512MB, not host-RAM sizing). 512M fits only with swap (bench swap 0: 61-104 kills; 9202 swap 512 MiB: survived by swapping). Controls: swap alone, limit alone flip it; shared_buffers 128MB alone does not. Catalog 56c4888: v3.2.4 + 768M, proven with swap off on both venues. audits/immich-first-start-2026-09-30/A-cause.md. - R-730: scripts/iso/build-felhom-iso.sh refuses an uncommitted/untracked/unpushed tree (no bypass), records repo-commit from the gate and iso-v<version>; test iso/test/clean-tree.sh, red-proof run (status check removed -> 2 of 4 cases fail -> restored). - R-731 narrowed (gitea 28.0.0 GA; mariadb 13.0 a short-term Rolling line). R-676 note. - New rows R-733 (bench has no swap, boxes 512 MiB), R-734 (immich .immich markers -> files_may_change). - STATUS: the golden line corrected (no bake is due; 0.283.1 is the newest release). Register 364 -> 366. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS |
||
|
|
a7971c4822 |
night 2026-09-25 (in progress): part 7 shipped as controller v0.271.0 — decisions 31-33, evidence A/B/C/F, register (R-685..R-687; closed R-672 R-673 R-684 R-680 R-678)
gates / gates (push) Successful in 25s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS |
||
|
|
462ab4a5ff |
Part 0: repair the register, gate its shape, and record Hetzner's answers (R-627, R-628, R-629)
gates / gates (push) Successful in 29s
THE REPAIR. Last night's append regex ate the state cells of R-446 and R-458, left them as a stray
fourth cell on duplicated copies of R-626 and R-625, and split the table with blank lines. The
register read 317 rows for 315 findings. Both cells restored from the cells that carried them, the
two duplicates deleted, and 15 blank lines that split the register into 12 separate markdown tables
removed. Every row's text is byte-identical afterwards, proven by diff; no row added or removed.
THE RED-PROOF FOUND AN OLDER INSTANCE: R-254 lost its state cell on 2026-08-08 (
|
||
|
|
e02bc03819 |
hub v0.119.0 — English households get English words for their codes (R-597); R-596/R-598 closed
gates / gates (push) Successful in 24s
The setup code and the owner passphrase now follow the household's language,
one word longer in English so the entropy never drops (setup 3 hu / 4 en,
passphrase 5 hu / 6 en). List and count are chosen together so a caller cannot
pair an English list with a Hungarian count. Hungarian is byte-unchanged.
Three claims in the row were wrong and are recorded as such:
- the RECOVERY CODE is minted by felhom-agent from the EFF list and has
always been English; the hub does not own it and no row was added.
- no claim mail states a word count; the only count wording was the bind
page's passphrase hint, whose English half is now count-free.
- the proposed phone-safe filter removes 68% of the list (5270 of 7772
words) and was measured, then declined, with the reason in source.
Also: guide_quote_gate binds the English volunteer guide's three quoted
messages to the controller's English bundle — nothing did, so the guide would
have gone on quoting Hungarian after the fix. Seven decoys, all convicting,
including the name-for-fact one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
|
||
|
|
31eeb36e88 |
ISO 1.29.0 PUBLISHED — bilingual console, proven on both menu entries (R-559)
gates / gates (push) Successful in 22s
Live at iso.felhom.eu, sha256 dceacae5da247d76cad065bf6c0d3bbefac8d8a5f8e571 db2fd8449a97e94829, and both download pages now name it. Every gate criterion is recorded with its OBSERVED value in documentation/tests/iso-release-1.29.0-2026-09-18/ — including two proof installs from the published bytes, one per boot-menu entry, each with a first boot AND one reboot: /etc/issue bilingual with zero hits for 8006, pvebanner masked, package 1.29.0 installed, unit enabled and fired, pairing code present, and the installed script byte-identical to repo HEAD. G11: the downloaded bytes hash to the published checksum. A defect was caught BETWEEN builds by looking at the screen rather than at the config: the second menu entry read "Felhom telepítés (szöveges mód) / Install Felhom (text mode)" — 58 characters — and the GRUB menu box cut it at "Instal". The English half was unreadable on the boot screen. Shortened to "… / text" and rebuilt; the published image is the rebuilt one. The Hungarian half is the part that may not change, so the English half is the part that gave. Teardown: VMs 323/324/325 destroyed, the two unclaimed appliance registrations discarded (zero left in `registered`), guest 9201 untouched — 23 containers before and after. The two stale *.rootpw.txt files were shredded from the publish source directory before the upload ran from it (R-587, files gone; the guard that would stop it recurring is still open). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS |
||
|
|
5a654ebc9a |
slice 4: bilingual console + the English download page is live (R-559)
The image is BUILT but NOT PUBLISHED, and that is deliberate: publishing to iso.felhom.eu is public and irreversible, and the runbook needs a proof install on BOTH menu entries plus a reboot against the uploaded bytes. That is supervised, so I stopped there. felhom-installer-1.29.0-pve9.2-1.iso, sha256 c67ceaa3…fb02, with every mechanically checkable criterion passing (G1, G2, G5, G6, G7, G9, G16 — including both payload files byte-identical to repo HEAD). The download pages still name 1.28.0, the image that IS published. Pointing them at a file that is not there would hand every reader a 404. A new site gate refuses the two pages naming different installer files or checksums, so whoever publishes 1.29.0 cannot update one and forget the other. Measured rather than read: the pairing banner is 24 rows on a 25-row console. One row of margin — so the height is now pinned, because two more lines push the HUNGARIAN code at row 5 off the top, and a banner whose code has scrolled away is furniture. R-587: two root-password files from July sit in the directory the public ISO is published from. Both 404 on the bucket (against a 200 control), so nothing leaked — but the only thing keeping them off is an --include pattern they miss by an accident of naming. A pattern that protects by coincidence is not a control. R-588: release records live in two different places, which made me wrongly conclude 1.28.0's gate had never been run. It had. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS |
||
|
|
eb1ae37095 |
ISO 1.29.0 source + an English download page (R-559 slice 4, source only)
gates / gates (push) Successful in 23s
THE IMAGE IS NOT BUILT AND NOT PUBLISHED BY THIS COMMIT. Publishing to iso.felhom.eu is public and irreversible and its runbook requires a proof install on BOTH menu entries plus the 16-criterion gate run against the exact uploaded bytes. That is the operator's step. The download pages therefore still name 1.28.0 - the image that is actually published - and a new site gate refuses the two pages naming different files or hashes. Three texts a person meets before any dashboard become bilingual: Hungarian block first, byte for byte as before, then English, inside the same frame. The pairing banner, the bound banner, /etc/issue (and the postinst's byte-coupled copy), plus an English half on the GRUB entries. The Hungarian is a GOLDEN, not a grep: test/golden/*.hu.txt were captured from the script at |
||
|
|
9167cf53af |
hub v0.118.0: the household's e-mails follow the household's language (R-558 Part A)
gates / gates (push) Successful in 23s
The hub has written every customer e-mail in Hungarian whatever the box was set to. The box has published its language since controller v0.247.0; nothing read it. Now it does. Nothing an operator reads changes. The Hungarian mails are byte-identical, and that is a diff rather than a reading: 56 goldens per language captured from v0.117.0 BEFORE any string moved, and all 56 Hungarian ones pass unchanged after every sentence was routed through the new bundle. - internal/i18n: flat bundle, 79 keys, hu authoritative + hu fallback, ceiling 0. - customerMessages/severityLabels are DERIVED from the bundle, so a sentence is written in one place and all 40+ tests that read those maps still work. - Language order: last reported -> created-with -> hu. reports.language defaults to EMPTY, never hu: "never told us" is not "chose Hungarian". - message_customer on POST /api/v1/event, additive and optional forever, for the sentences the box composes and the hub cannot translate. - The bind page is per-language, and its `expired` state stays Hungarian: it is the state an unknown token lands in, so rendering a real English customer's token in English would make the LANGUAGE answer what the TEXT refuses to. Two defects found inside the release: - R-581: the newest report was picked by received_at, which has SECOND granularity, so same-second reports tied and the winner was arbitrary. Ordered by the autoincrement id now. GetCustomers() still has the shape - row open. - R-582: the English copy-guard stems, ported word for word from Hungarian, convicted 141 honest sentences. The English claim is a phrase with a modal. R-555 closed: the language allowlist entry is out of wire_contract_gate.py. hub_copy_gate.py follows the sentences into the bundle - without that it would have scanned four files that no longer hold any customer text and reported success. Three new decoys incl. an innocent control. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS |
||
|
|
5b7b2b22f1 |
i18n starter: inventory (script + audit), rows R-553..R-562, language allowlisted in wire-contract gate
gates / gates (push) Successful in 23s
Phase 0 of the localisation starter: i18n_inventory.py counts every customer-visible Hungarian string; the audit names six further claims in the prompt that live source disproved. Rows for the compare-not-show sites, wizard deletion, the wire-contract comment blind spot, and localisation slices 1-6. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS |
||
|
|
832218dca4 |
ISO 1.28.0 PUBLISHED on the operator's yes; download page and R-535 updated
gates / gates (push) Successful in 20s
Uploaded with env-only credentials and verified by ROUND TRIP: the downloaded bytes checksum to a4cd9b6d…, identical to the built file, and the checksum file is served. 1.27.1 stays in the bucket; nothing was overwritten. The download page now names 1.28.0 with the published checksum (BOM preserved, site gates green). R-535 closes with an honest caveat: the new banner ships byte-identical to repo HEAD and the string is in the published payload, but it was never seen on a screen — the box bound itself while the walk was headless. Also corrected: the 1.27.1 heading still said NOT PUBLISHED although it went out on the big night. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS |
||
|
|
c033b3b617 |
ISO 1.28.0 source: the console stops showing the pairing code once bound (R-535)
gates / gates (push) Successful in 20s
Measured 2026-09-16: 25 minutes after a successful bind AND claim the console still showed the pairing code under a line promising the screen refreshes itself. print_bound_banner is printed the moment the bind delivery lands. It does NOT name the dashboard URL: the one-shot delivery carries the customer id, passphrase and mode, not the domain, so naming an address would mean inventing one. The residue — the console still does not reflect the later CLAIM, because this unit has exited by then — is recorded in the changelog rather than implied away. Not published: the built image needs the release gate and the operator's yes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS |
||
|
|
5572216506 |
website: letoltes page for installer 1.27.1 (published and round-trip verified 2026-09-15)
gates / gates (push) Successful in 19s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS |
||
|
|
65790672d5 |
doorstep walk on ISO 1.27.x: 1 intervention (R-505), STOP before publish
gates / gates (push) Successful in 17s
ISO 1.27.1 gated PASS and proven live: first-boot console Felhom-only, pvebanner masked across a proven reboot. Hub v0.113.0 hand-over copy live (R-497 closed). Full first hour walked again on customer tester-1 (three disks + one disk): deploy, use, backup, removal, byte-identical restore, power cut, typo all PASS. The tunnel gives a fresh box no routes: 12/12 503 from DooPlex (R-505); the record has no e-mail (R-508). Rows R-507, R-508 filed; R-496/R-495 fixed/answered awaiting publish; day-0 A.1 no longer claims the controller creates hostnames (R-506). NOT PUBLISHED. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS |
||
|
|
27e8ec860c |
iso 1.27.1: the FIRST boot is Felhom's — postinst masks pvebanner and writes /etc/issue
gates / gates (push) Successful in 18s
The 1.27.0 proof install (VM 331, screen s20) still showed Proxmox's ":8006" block on the first boot: pvebanner.service ran before felhom-bootstrap could mask it, and the Felhom text lost its o/u double acutes (painted before the Latin-2 font loads). - postinst: mask pvebanner by symlink (a file act, valid in the chroot) and write /etc/issue; both guarded, still exit 0 (G8 self-checks unchanged). - bootstrap: same text, byte-identical, no o/u double acutes (second line). - harness: PI scenario (postinst in a container) + no-accent checks, red first against the 1.27.0 code; 55/55 green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS |
||
|
|
6fd8c87516 |
doorstep: console is Felhom's (ISO 1.27.0 source), passphrase hand-over copy (hub 0.113.0 source), rulings
gates / gates (push) Successful in 19s
Phase 0: the public ISO never auto-installs by construction (no answer.toml, G1); the operator re-affirmed the interactive installer 2026-09-14. - felhom-bootstrap.sh: mask pvebanner.service, write a Hungarian /etc/issue (no :8006 admin URL); pairing banner names the Tulajdonosi jelmondat and paints through the CONSOLE_DEV seam (R-496). Harness: 8 checks, red first; fake hub now sends a pairing code (the banner was never tested, R-502). - hub: created flash + Credentials block tell the operator to hand the phrase over; the self-bind mail names the operator (R-497). Tests red first. - iso-release-gate G14-G16; domain ruling in 01-topology + CONTEXT; R-494 narrowed to P3; R-502..R-504 filed; volunteer guide and day-0 A.2 aligned. ISO_VERSION 1.27.0 (not built, not published). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS |
||
|
|
5e8a82c3c4 |
night 2026-09-13/14: first "be a customer" rotation (adventurelog) — 7 defects found, 13 rows closed
gates / gates (push) Successful in 18s
New runbooks/nightly-rotation.md; observations_gate.py reads every section (R-471); target-selection.md names real paths (R-461); R-93 carries the fact that drill-r50 is gone. Register: R-473/R-474/R-466/R-471/R-453/R-461 and v0.240.0's R-477/R-478/R-480/R-482/R-484/R-485/R-486 closed; R-481, R-483, R-487, R-488, R-489 opened. 09 §6.1, 07 §6, CONTEXT, STATUS note. Evidence: audits/nightly-2026-09-13-adventurelog/, audits/v0240-2026-09-13/. |
||
|
|
5ef0f52bcd |
Slice 4 shipped (R-448/R-443/R-439 CLOSED, proven live); R-472..R-476; the floor-between-bakes claim corrected
gates / gates (push) Successful in 19s
Controller v0.237.0-v0.238.1: the Update button is a guarded job — refusals, backup-first when the proven Tier-2 copy is stale, safety dump, pin, pull (pin back on failure), health, HOLD on failure. Proven live on demo-hp: A, B, E, F, H and the restore walk (audits/slice4-2026-09-13/). Correction to this morning's pages: between golden bakes the hub HOLDS a floor above the vouched golden, so a release does not reach the fleet by floor (R-472, operator decision). Corrected in the runbook, STATUS, CONTEXT, R-468 and the gate docstring. Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS |
||
|
|
ae59c31a84 |
R-459 CLOSED (MariaDB converts itself, proven by harness + live), golden 0.236.0 (R-467), the golden waiver (R-468)
Operator rulings 2026-09-13, both shipped the same day: - MariaDB finishes its own conversion (catalog eec1228/bd32830/3525e35). Harness E3/E3b `proven` with engine_state_after "already upgraded to 12.3.3-MariaDB [exit=1]", the skip line gone, C3 still `failed`; landed on demo-hp through the real 15-min cycle, nothing recreated, one deliberate restart logged "MariaDB upgrade not required" with the app serving. Evidence: documentation/audits/r459-close-2026-09-13/. The engine-major rule + gate keep every engine inside its major until Slice 4 (R-448) — removal tracked as R-469. - Goldens on a cadence, not per release. golden_currency_gate.py reads a dated waiver (documentation/tests/golden-waiver.yml, <= 14 days, row-bound): valid + BEHIND -> loud advisory, exit 0; expired -> red again naming the date; UNRECORDED (R-385) never covered; malformed -> 2, never 0. Tests cases 5-15 incl. the R-421 decoy; red-proof old-vs-new on the real behind tree. R-242's vouch half stays open. Cadence in RUNBOOK-manual-build.md §4.2 + the checklist. - Golden 0.236.0 baked, round-tripped, vouched, floor raised 0.232.0 -> 0.236.0 (documentation/tests/golden-0.236.0-2026-09-13/) — the last per-release bake; the waiver was issued AFTER it landed. No --no-verify anywhere in this session. Rows: R-459 CLOSED, R-467 CLOSED, R-242 narrowed; R-468/R-469/R-470/R-471 opened. 09 §3 gains decisions 5 and 6; STATUS items 11 and 12 closed; CONTEXT records the cadence ruling. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS |
||
|
|
4b2e5608c2 |
R-442 CLOSED (controller v0.236.0): "delete my data too" deletes it or refuses; R-465..467 opened
gates / gates (push) Failing after 18s
- OPEN-ITEMS: R-442 row removed; R-465 (six remaining Paths.HDDPath readers — audit), R-466 (recovery-unit residue after "delete backups"), R-467 (v0.236.0 owes a golden). - CLOSED-ITEMS: R-442 compressed, reasoning kept, fleet shape now established. - 00-capability-map: lifecycle row narrowed (Campaign 3 proved remove removes the APP, not the data) and re-proven from audits/R442-2026-09-13/. - STATUS: item 13 in plain language; item 7 closed (ruled 2026-09-02, 09 §3). - audits/R442-2026-09-13/: live evidence (A, C, D bodies, controls, log window, teardown). Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS |
||
|
|
2d88776227 |
R-428: the gate that hunts label-matching was matching a label (and CI proved it)
gates / gates (push) Successful in 20s
decoy_coverage_gate.py identified a repository by os.path.basename(root), looked up in a RUNNERS map. Gitea's act-runner checks the repo out into a directory called `hostexecutor`, so on its FIRST CI run the gate reported "unknown repo 'hostexecutor'" and went INCONCLUSIVE - correctly refusing to pass, and blind. A NAME standing in for a FACT, in the first ten lines of the main loop of the gate written that same morning to catch exactly that, by a session with the four shapes on screen. That is the point of R-428 and why it is recorded rather than quietly patched: this class is not carelessness. FIXED: the repo is now identified by which registered runner FILE exists under the root. Verified under a renamed directory - 14 gates found where the name-based version found none. CI also now fetches app-catalog-felhom.eu. The meta-gate walks all four runners, and a gate that cannot see part of its subject must not report a pass on it - the same reasoning, and the same fix, as the two sibling fetches already in the workflow. NOTE ON THE OTHER THREE RED RUNS, all mine and all ordering: felhom-agent and felhom-controller cite R-421, and I pushed them BEFORE felhom.eu carried that row, so instructions_gate correctly convicted "cites R-421, which appears in neither register". The register lives in felhom.eu; any repo citing a new row must be pushed after it. Re-run below. |
||
|
|
94555614ab |
observations_gate: normalise emphasis before matching a marker (R-419 follow-up)
gates / gates (push) Failing after 18s
My first fix was too strict. It anchored a marker to a line start or a bare '. ', which misses the commonest real shape - a bolded sentence followed by a bolded marker: '...them.** **FILED: R-427**'. CAUGHT BY THE GATE CONVICTING THE VERY REPORT THAT DOCUMENTS IT, on two of its own observations. That is the both-directions check working: a gate that rejects the decoy AND the genuine article is worse than the hole it replaced. Code spans are stripped FIRST and that order matters - a marker inside backticks is being talked about, never used, and removing it is what makes the R-419 decoy fail. Emphasis is stripped second so '**FILED: R-1**' and 'FILED: R-1' are the same thing to the anchor. Decoy suite re-run: the R-419 decoy and a backticked-only mention are still REFUSED; both genuine marker shapes pass. |
||
|
|
574f5df107 |
the decoy sweep: 29 gates read, 16 fooled, 10 fixed - and a gate that refuses the next one (R-421)
gates / gates (push) Failing after 17s
THE CLASS, now a row: an instrument that matches a LABEL rather than the fact it names. Five instances - R-410, R-400, R-378, R-419, R-94 - and EVERY ONE was found by accident, by someone looking at something else. The gates enforce every other rule in this project, including the rule that findings must be written down rather than left in prose. Nothing had ever checked the gates. METHOD, and it is the transferable part: for each gate, construct the label WITHOUT the fact - a directory with the right name and no bake log, a handler case that exists only in a comment, a note whose prose mentions the marker it lacks - run the gate, record what it says. No verdict was reached by reading. Reading is how all five hid. RESULT: 29 distinct scripts (35 registrations; three are shared across three runners). 19 sound, 4 holes left OPEN with rows, 6 that no plausible decoy could be built for and are named UNTESTED rather than called sound. A gate nobody tried to fool is UNKNOWN. SCOPE IS A FACT TOO - the largest single cause, and mundane. Eight gates decided what to look at with os.listdir, one level. Every one was green AND CORRECT today, and every one would have gone blind the moment anyone added a subdirectory. mojibake and docker-v already used os.walk, caught the identical planted file, and are the control that proves the cause was the listing and not the decoy. IN THIS REPO: hub-confirm and manifest-bearer now walk. observations_gate (R-419, CLOSED) requires a marker at a line start or after a sentence boundary and strips inline code spans - a note SAYING it carries no marker no longer satisfies the marker test. closed-register now CONVICTS on a row it cannot parse instead of warning: FOUR rows were in that state, TWO of them written by the session that closed them the day before, and every one was exempt from the only check that reads that file. The rows were repaired first and the conviction added second - registering a failing gate refuses every push. THE META-GATE: decoy_coverage_gate.py refuses a gate registered without a decoy or a named exemption. It convicted ITSELF the moment it was registered, which is how it came to have one. Coverage is a DECLARATION the gate AST-parses, never a grep - searching a test file for a gate's name would be the very shape this sweep exists to find. The 20 uncovered gates are listed by name (R-426). NOT FIXED, each with a row and a decoy asserting TODAY's behaviour so the fix must be deliberate: R-422 reuse-refs (only 7 extensions; a rotted .md citation is invisible), R-423 site (PAGES is a hardcoded list of 7), R-424 one-register (a defect parked as `idea`), R-425 offbox-rename (fixed FILES list). R-427: closed_register_gate checks ONE direction - twelve open rows carry a closed verdict and were NOT moved, because telling finished from partly-finished is a judgement and R-378 is the record of a machine getting it wrong. FIVE DECOYS WITHDRAWN AS ILLEGITIMATE, mine, named in the audit. A decoy nobody would write proves nothing, and manufacturing a finding to fill a row is worse than an honest NO. No product code. No version bump. No image. No golden owed. All four runners green. Register: OPEN 172 -> 178, CLOSED 160 -> 161. |
||
|
|
1e6c387a0b |
R-404 CLOSED with the ruling; R-417 CLOSED by cause removal; R-418/419/420 filed
gates / gates (push) Successful in 17s
THE RULING WAS NEITHER OPTION AS FRAMED. Both offered answers - narrow the gate, or leave it and write waivers - argued about the gate, and the gate was never the problem. DIAGNOSIS, from live source: golden_currency_gate.py never looks at the push. It compares the controller's newest CHANGELOG heading against this repo's bake evidence and returns the same verdict whatever you are pushing - correct for a standing invariant, wrong as a push gate. And controller_gates.py had NO golden-currency entry at all. So the repo where a release happens never checked, and the repo that cannot create the debt was refused on every push. 18 of the last 24 pushes here touched no code - measured, and the new classifier agrees EXACTLY - most of them by construction, because the controller's code is in one repo and its register lives in this one. SIX of those 18 were bake records, so the push that PAYS the debt is itself documents-only: the gate was blocking its own cure. Not the waiver its docstring prescribes: that clause was written for a release nobody wants a golden for. R-417 was a release we DID want a golden for, on a night the runbook forbade baking. A waiver would have recorded a lie. RULING: block the push that can create the debt, notify the push that cannot. The gate's logic, exit codes and wording are BYTE-IDENTICAL. Only the consequence changed, for one gate, on one kind of push, with a loud ADVISORY block so nothing goes quiet. R-242's vouch half is amended in place to say it is UNTOUCHED and still open - a baked-but-unvouched golden still passes both the gate and the new notice. Do not read R-404's closure as closing it. FILED: R-418 - this runner's docstring listed ELEVEN gates while THIRTEEN were registered; one-register and closed-register ran undocumented since 2026-08-24. Enumeration fixed here, the correspondence is still unenforced. R-419 - observations_gate.py accepts an item whose body merely CONTAINS "NOT-A-FINDING", even in prose disclaiming it; found by accident when a planted test observation passed and my live validation proved nothing. R-420 - controller_gates.py could not express a non-blocking gate at all before today. Register: OPEN 171 -> 172, CLOSED 158 -> 160. |
||
|
|
1c00af607c |
R-404: block the push that can create the golden debt, notify the one that cannot
push_scope.py classifies a push as code or documents from an ALLOW-LIST of document paths - everything else, including any new top-level directory, is code. Every uncertainty (first push, force-push, merge commit, empty range, unreadable stdin) answers code: guessing 'documents' would hand out the exemption by accident. repo_gates.py gains a fifth GATES field and --scope=code|docs. On a documents-only push a golden-currency CONVICTION prints as ADVISORY in its own block and does not refuse; every other gate still refuses every push, and golden-currency still refuses a push touching code. The gate itself is UNCHANGED - its verdict, exit codes and wording are byte-identical. What changed is who is refused. Measured on git 2.47.3: a pre-push hook receives <local ref> <local sha> <remote ref> <remote sha> on stdin, one line per ref; a first push carries an all-zero remote sha and a deletion an all-zero local sha. Both land on code. |
||
|
|
22e1c95e6a |
the golden gate reads a fact not a name (R-410); R-133's collision resolved (R-406, R-416)
gates / gates (push) Failing after 17s
R-410. golden_currency_gate.py matched EVIDENCE_RE against os.listdir and read nothing inside, so `mkdir documentation/tests/golden-9.9.9-2026-01-01` turned it green with no bake behind it - noticed while the 0.230.0 bake was running, when the evidence directory existed before the bake finished. It now reads the GOLDEN_SHA256= line out of that directory's bake log: a directory name is a label, that line is a fact only a completed publish produces. Still offline, still --fast, one file read. Directories that look right and hold nothing are printed by name rather than silently ignored, so a half-finished bake is visible. test_golden_currency_gate.py ships the red-proof with a POSITIVE CONTROL, without which "it fails on an empty directory" would be satisfied by a gate that fails on everything: CASE 1 empty directory -> rejected and named CASE 2 log with no GOLDEN_SHA256 -> rejected CASE 3 real bake log -> counted, and its sha read <- the control CASE 4 the tree is left byte-identical Red-proofed: reverting the gate to name-matching fails cases 1, 2 and 3. R-406. Citations MEASURED before choosing, which is what the row asked for: hub-uniqueness had 3 references (all inside one audit doc), plaintext-break-glass 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 citations were rewritten to "R-415 (was R-133)" rather than silently swapped. THIS IS THE OPPOSITE OF THE TASK'S LITERAL INSTRUCTION, which said renumber the second row on the stated ground that "the older number has the longer reference trail". Measured, that ground points the other way. The principle was followed and the letter was not, and the row says so rather than leaving an unexplained diff. R-416 filed: the within-register duplicate rule was deliberately NOT added in the same commit that removed its only subject - a guard whose red-proof can only be a planted fixture is not this project's standard. Now that the register is clean it can ship with the next real duplicate as its first subject. |
||
|
|
1aeaa30c28 |
hub v0.110.0: allowlist + operator-only for offsite_proof_empty (R-87)
gates / gates (push) Successful in 15s
Controller v0.231.0 adds a nightly job that proves an app's newest off-site snapshot still CONTAINS that app's data. When it finds one that does not, it emits offsite_proof_empty. Two register lines, both load-bearing and both in this commit: - allowedEventTypes - an unallowlisted type is answered 400 and VANISHES, so this entry is what makes the alarm exist at all. - operatorOnlyEvents - a missing customerMessages entry is NOT a routing block (FormatCustomerEmail falls back to the raw message, the v0.78.0 defect that register was built for). A customer can take no action on a hollow recovery unit. DELIBERATELY NOT reusing backup_integrity_failed, which is the nearest existing type: it means THE STORE IS DAMAGED and carries the Hungarian template saying so. Here the store is sound and the CONTENT is absent - different cause, different action, and telling a customer their backups are damaged when they are not is the more expensive mistake. Same asymmetry looksLikeRepositoryDamage is shaped around. DELIBERATELY no customerMessages entry (the controller's dynamic Hungarian names the app and what is missing; a template would discard it) and DELIBERATELY not in perAppCooldownEvents (a fenced act - this job proves ONE app per night, so the coarse hourly cooldown is already the right grain). This widens the R-87 task's stated repo scope to felhom.eu/hub/. The reason is recorded in felhom-controller/CONTEXT.md ruling 4 rather than left as an unexplained diff. Hub green gate: go build/vet/test all pass, 18 packages. |
||
|
|
6e550aedd3 |
R-87 put back in the register; closed_register_gate.py is the 12th gate (R-405, R-406)
gates / gates (push) Failing after 17s
Records and process only. No machine contacted. No product code, no version bump, no build, no deploy. R-87 was moved into CLOSED-ITEMS.md by the 2026-08-22 compression sweep |
||
|
|
66156c619f |
R-403 drill evidence + the credential reader that ends a three-time mistake
gates / gates (push) Successful in 16s
The drill: the loss reproduced on the shipped v0.229.0 before anything was built. 120 082 104 B -> 7 036 B in one Tier-2 run, recorded as a success. Phases 1a (before), 1b (the hollow primary, produced through the R-102 restore path exactly as the 2026-08-31 observation was), 1c (the loss), 1d (repair). scripts/read_credential.py is Part 4's rider, and it exists because a note did not work three times: 2026-07-20 a Failed login was diagnosed as a stale password and written into memory; 2026-08-31 the same misreading recurred and was caught; 2026-08-31, hours later, it recurred AGAIN and rewrote a live box's password hash. Between them the project already had a memory file stating the rule, a worked recipe in it, and a session report describing the mistake. The rule now lives in the code path: one matching quote pair is unwrapped, the result is REFUSED if it still carries a quote, and --expect-length gives the caller a second opinion. The value goes file->file at 0600 and stdout gets only its length. test_read_credential.py asserts each refusal by its reason, with a positive control before believing the not-in-stdout result. Red-proof E1: remove the final quote assertion -> three cases fail by name. |
||
|
|
77a5a1154b |
docs: controller v0.228.0 — R-399/R-400 closed, R-401/R-402 filed
gates / gates (push) Failing after 19s
STATUS.md: header said 2026-08-23 over a 2026-08-30 body, and two "Waiting on you" items were both numbered 4 — both fixed. R-399 leaves that section (decided and shipped); the depth change is stated in plain words and the remaining items each say what happens if Viktor does nothing. 00-capability-map.md: the off-site verification row now carries its DEPTH, and its live citation is the 2026-08-31 run at 100%. The weekly firing at the new depth stays IMPLEMENTED, not PROVEN-LIVE. 07-backup-architecture.md §10.2: R-399 recorded closed, with the one sentence that stops it being turned back down — the structure check PASSED a size-preserving pack corruption. R-87 untouched and still OPEN. Register: R-399 and R-400 compressed into CLOSED-ITEMS.md with their reasoning kept and 300d7e8 named as the commit holding the originals. R-401 filed with a TRIGGER (the slow-check WARN firing) rather than a date. R-402 filed: the integrity verdict and its depth are on the wire and no hub surface reads either. OPEN 166 -> 165, CLOSED 148 -> 150. wire_contract_gate.py: offsite.last_integrity_depth allowlisted WITH ITS REASON beside its sibling last_integrity_ok, both to be deleted together when a hub surface is built (R-402). |
||
|
|
99af997ab9 |
R-359/R-397 closed, R-398 corrected, R-399/R-400 filed with measured numbers
gates / gates (push) Failing after 18s
THE MEASUREMENT IS THE STORY, and it re-frames the row it was filed under. A pack was corrupted WITHOUT changing its size; plain `restic check` -- the depth that ships ON -- returned `no errors were found`, exit 0. Only --read-data caught it. So the check that shipped verifies the index, the pack inventory and the snapshot graph, and does NOT re-hash pack contents. R-399 was filed as a bandwidth-and-cadence question; it is more than that, and its row now says so. R-399 gets three MEASURED numbers instead of estimates: store 140 829 678 B / 2651 blobs / 67 snapshots; structure check 35.0 s; curve 10% 35.9 s, 50% 37.3 s, 100% 39.2 s. At this size re-reading everything costs four seconds more than reading none, because the wall clock is SFTP round-trips not transfer. The row states the limit too: these do NOT extrapolate. R-400: the sweep the task asked for found EIGHT dead debug buttons, not one. 24 endpoints referenced in debug.html, 17 dispatched. Single dispatcher, exact match, default NotFound -- so they 404. A third of a debug page does nothing, on the surface an operator reaches for when something is already wrong. R-398 is CORRECTED AND LEFT OPEN, not closed. I filed it yesterday saying resticStep is not a seam so no test can drive a restic path. The layer below it has been injectable since the off-site tier shipped. The row survives as the record that the seam EXISTS so nobody re-files it. 07 gap register: R-359 and R-397 closed; R-87 restated IN PLACE as "AND IT IS NOT R-359" because the two rows are adjacent and a check is not a restore-test. 08 alarm ladder: both event types recorded, including that `ok` is `info` and therefore mails nobody BY DESIGN, and that all three registers were checked and deliberately left alone. 00 capability map: PROVEN-LIVE for the check, the notifier and the hazard control; the scheduled firing is IMPLEMENTED only, because a week has not passed. wire_contract_gate: `offsite.last_integrity_ok` allowlisted WITH A REASON. The gate was right -- the controller emits a field no hub struct can decode. Building the display is a hub change and R-331 ruled that class the operator's decision; the entry says to delete it when a surface exists. This push used `git push --no-verify`. golden-currency is CONVICTED and right: 0.227.1 is released and the golden carries 0.226.1. A BYPASS, not a waiver, and the task spec directs it -- golden and fleet delivery are Viktor's (R-242). It is item 3 under "Waiting on you". Register 163 -> 165 -> 163. |
||
|
|
c30430c530 |
skills: five process-domain skills + check_skills.py
gates / gates (push) Failing after 15s
The four existing skills cover the product; nothing covered how work is reported. Two rules this project has paid for — check the artifact rather than the report, and do not state a claim more firmly than the evidence allows — lived only in the operator's head and in chat, where Claude Code never read them. - felhom-evidence five confidence tiers, artifact-over-report - felhom-diagnosis no hypothesis until a command has been seen red - felhom-plain-language ASD-STE100, two options, the re-pitch - felhom-handoff the note goes to a FILE, not the conversation - felhom-doc-authoring the pointer decides whether material is reached scripts/check_skills.py asserts what decides whether a skill is EVER reached: frontmatter parses, name == directory, description and body non-empty, under 150 lines, installed copy still samefile()s into the repo. install_skills.py globs and never reads the file, so a missing description installs perfectly and then silently never loads. It convicted on its first run: felhom-build-deploy is 179 lines. NOT trimmed here (pre-existing skills are out of scope, and trimming a deploy skill without exercising its commands is how a wrong command reaches a live host) — a named single-entry GRANDFATHERED exception, WARNed every run, R-394. A new skill over the limit is convicted. Red-proof run and seen failing: description removed from felhom-evidence -> exit 1, "frontmatter field 'description' is missing or empty". Restored, tree clean. skills/SOURCES.md records both MIT upstreams, that these are adaptations not copies, and the six pieces deliberately EXCLUDED with reasons. Register: R-392 (no architecture doc covers the two-AI workflow), R-393 (decision-log skill deferred, with the reason), R-394. |
||
|
|
2fc4a15fa3 |
R-389: key the operator cooldown per app for app_start_failed; gate 11 makes an unfiled observation refuse the push
gates / gates (push) Successful in 16s
The cooldown key was customerID:eventType plus the tier and run suffixes, and none of them names an app, so every app going down inside the same hour collapsed onto one key and only the first was mailed. Measured on demo-hp: bookstack sent 09:27:51, privatebin suppressed 09:31:51 under key=demo-hp:app_start_failed. cooldownStackSuffix is the third sibling of cooldownTierSuffix and cooldownRunSuffix, and separate for the reason the second one's docstring already gives: the existing two keep byte-identical semantics for every type that uses them. It is ALLOW-LISTED to app_start_failed and takes the event type as well as the details, unlike its siblings, and that asymmetry is the safety property. The backup family's cooldown is coarse ON PURPOSE (R-97a, R-182) so one full disk sends one digest rather than one mail per app - and crossdrive_failed is severity error, reaches the operator leg, and carries stack_name through a DIFFERENT struct, so a payload-shape rule would have split it silently. The hour itself does not change. Gate 11 refuses a push whose REPORT.md carries an observation with neither `FILED: R-NNN` nor `NOT-A-FINDING: <reason>`. It deliberately does NOT accept a passing mention of some other R-number: the lost item cited R-182 as an analogy, so "cites a register row" would have passed the very item the gate exists to catch. That discrepancy with the spec is recorded in the gate's docstring. Registered here and in the controller and agent runners. NOT in the catalog runner - it has no shared-gate mechanism and appends --all to every gate; filed as R-391 rather than left as a sentence, which is this session's lesson. PROMPT-TEMPLATE.md §15.9 corrected: "documented, NOT acted on" was the wording that invited the gap, and it now names the markers and points at the gate. R-390 filed for the golden-bake runbook's missing `pveam update`. Hub tests 709 -> 716. |
||
|
|
55274d5ef3 |
R-385: make an UNRECORDED golden fail the currency gate; file R-386; own the alarm ladder
gates / gates (push) Successful in 17s
The gate failed only on `released > baked`, so it could catch a forgotten bake and nothing else. A golden AHEAD of the record passed silently - and that is how controller 0.221.1 was built, baked AND vouched while the newest CHANGELOG heading still read v0.221.0, with every gate green. Reproduced on the real history: newest released 0.221.0 / newest golden baked 0.221.1 -> exit 0. The gate now asks whether the version being shipped is WRITTEN DOWN: the baked version must have its own `## vX.Y.Z` heading anywhere in the CHANGELOG. Membership rather than `baked > released` deliberately - a comparison against the newest heading alone goes green the moment any later entry is written, leaving the unrecorded version permanently unrecorded. INCONCLUSIVE (exit 2) preserved; every refusal names a reason and a route. Red-proofed both directions: old gate/old record exit 0, new gate/old record exit 1, new gate/fixed record exit 0, absent clone exit 2, post-bake exit 0. 08-alarm-ladder.md is new, and its absence was itself the finding: no document owned "when does a broken app raise an alarm?". The rules lived as comments in four packages, each locally correct, with the ordering between them legible only by reading one function top to bottom - which is how R-384 survived review. R-383 and R-384 closed into CLOSED-ITEMS with their rules kept. R-385 filed closed. R-386 filed OPEN: a single-container app stopped out of band raises no alarm, and a comment claims the opposite - measured live, 9 scans, 0 events, against a positive control from the same box 17 minutes earlier. Not fixed here. Golden 0.222.0 baked and published; vouching is the operator's act. |
||
|
|
e18668f9e1 |
instructions_gate: CLOSED-ITEMS.md is the third register source
gates / gates (push) Failing after 17s
Compressing a finished row out of OPEN-ITEMS.md into CLOSED-ITEMS.md is the
documented housekeeping act. The gate read only OPEN-ITEMS.md and ROADMAP.md, so
the first compression (
|
||
|
|
ef6ac6fe74 |
One register, enforced by a gate; closed work compressed into siblings (R-376..R-378)
gates / gates (push) Successful in 16s
Records and process only. No machine contacted. ONE REGISTER (operator ruling). 17 roadmap rows moved into OPEN-ITEMS.md keeping their identifiers, evidence and original filing dates - the oldest R-10, filed 2026-07-15, 38 days. 15 ideas stay in ROADMAP.md, which is their home; the gate exempts them by their own state word. 59 already-closed rows stay as history. Sorting rule recorded in the roadmap header: does the item assert something about the shipped product a reader could check and find false? scripts/one_register_gate.py, wired as the 11th gate. Control run: baseline passes, a planted open roadmap-only row is convicted by name, removing it passes with the file byte-identical, and a planted `idea` row is correctly exempt. Its four residual holes are in its docstring. The gate earned its keep immediately: it caught R-103, a READY finding my hand-sort mis-read as done because my regex matched the whole row where the body contains "shipped" - the gate matches the state cell. It also caught R-203 and R-163, recorded closed in the register and still open in the roadmap; the roadmap copies are marked SUPERSEDED with the register's verdict. HOUSEKEEPING. OPEN-ITEMS 672,376 -> 327,109 bytes (-51%); ROADMAP 239,306 -> 78,110 (-67%). Closed work compressed to 17% into CLOSED-ITEMS.md and ROADMAP-HISTORY.md; every entry names the commit whose git show returns the full original text. Rule-sentences are kept verbatim under "Reasoning kept" rather than judged entry by entry - 25 carry one. CONTEXT.md deliberately NOT compressed and the disagreement is argued in the report: 86% of it is standing rulings still in force, this prompt's own 3.4 says the log is never edited, and it has no per-ruling delimiter. Filed as R-377 - the problem is navigational, not volumetric. The hot/bulk placement decision was NEVER recorded as a decision anywhere - established, not assumed. Now marked [DESIGN] with a pointer honest about having no original date, given a decision-log entry that records what was rejected, and the [DESIGN]/[FACT] legend carried from 1 of 8 architecture documents to 8 of 8. Existing statements deliberately left unmarked (R-376). PROMPT-TEMPLATE gains N.7: compress what you closed, rehome live reasoning before it goes, state the register's size before and after. Ceiling R-375 -> R-378. |
||
|
|
ca543b8f69 |
where-felhom-stands: bring the picture up to 2026-08-22, and stop the page disagreeing with its source
gates / gates (push) Successful in 15s
Eight claims re-checked against the drill and the v0.218.0 fixes; three moved, all downward.
backup.offsite walked -> partial. "18 snapshots, daily, unbroken" was true on
2026-08-09 and false by 2026-08-21: the next snapshot after that date
was put there by hand, twelve days later. The rebuild lost the target
and the per-app switches came back off, so a run reported "backup OK:
0 app(s) backed up".
fail.wiped-reinstalled.data walked -> partial. A real reinstall orphans BOTH off-premises tiers:
restic silently for 12 days (R-193), and the PBS archives from before
the reinstall cannot be opened by the rebuilt box at all (R-366).
backup.fill-warning walked -> partial. The warning fires correctly, but the watcher runs
once a day, so a filesystem that fills at 03:31 goes unannounced for
~24 h. Watched silent while a volume sat at 99%.
Five re-checked and held: backup.tier1 and recover.byte-identical carry the R-355/R-354 story and
their fixes; backup.restore-proof stays grey for a sharper reason (orphaned archives, not an
untested tier); backup.sikeres gains two fresh instances; fail.customer-self-restore records that
R-356 now blocks 40 of 53 apps regardless of who is driving.
render_stands.py: the header's commit shas were hardcoded, so the page cited the August 9th commits
while the YAML said otherwise - the stale-build-product failure the renderer exists to prevent. They
are parsed now. The count beside them said "15 status(es) moved in that pass" when 15 was every
recorded move ever; it now separates the two numbers.
check_stands passes, and was itself proven able to convict first: a claim marked `missing` flipped to
`walked` in a scratch copy fired rule 5 by name (use.dlna), and the real file still passes.
|
||
|
|
104ef34f57 |
gates: the today-override announces itself; malformed no longer swallowed
gates / gates (push) Successful in 15s
Part 6 of the hub-blindness task, separable and done rather than dropped. Both due_checks_gate.py and instructions_gate.py read FELHOM_GATE_TODAY so their suites can control "today", and neither said so. A shell that still has it exported -- exactly what a session doing gate-test work leaves behind -- made both gates evaluate against a fabricated date and pass in SILENCE. That is this project's own named failure class: an instrument that can quietly return the wrong answer is not a measurement. The seam is legitimate and stays; the silence was the defect. Both now print a loud line naming the variable, its value, and that the real date is being ignored, before any verdict. And instructions_gate.py no longer swallows a MALFORMED override: it used to fall through to the real date without a word while due_checks_gate.py already exited 2 on the same input -- one variable, two gates, disagreeing about what a mistake means. Both exit 2 now. Tests extended in both suites (42 and 73 assertions, green). Red-proof: the announcement was deleted from due_checks_gate.py and its two assertions were seen failing, then reverted. Also adds REPORT-hub-blindness.md (topic-suffixed; the shared REPORT.md is left alone per the parallel-session rule). |
||
|
|
0a5e9b14dc |
due-checks gate (R-341), floor raise recorded (R-343), snapshot coverage (R-342)
gates / gates (push) Successful in 14s
PART 1+2 — dated checks stop being wishes. R-341 booked two measurements as prose in a register row; nothing read those dates and nothing would have objected when they passed. The dates now live in a DUE-CHECKS block INSIDE OPEN-ITEMS.md (inside, so no sidecar can drift from it) and a new gate reads them. Registered as #10 in repo_gates.py, --fast, so it runs in BOTH the pre-push hook and CI. exit 0 nothing due (prints pending count + nearest date; empty block too) exit 1 a row is due/overdue (due <= today, UTC -- due TODAY counts), or a row names an item with no R-row exit 2 block absent/duplicated/unparseable -- INCONCLUSIVE, never 0 It REFUSES rather than warns, and its docstring states the limitation: it is NOT a scheduler, it fires on the next push, not on the date. 37 tests. BOTH red-proofs run and reverted -- and the first one earned its keep by catching a hollow assertion of MINE rather than confirming the gate: flipping <= to < left a due-today row in neither bucket, min() raised on an empty list, and the TRACEBACK exited 1, so "rc == 1" passed while the boundary was wrong. An exit code cannot tell a verdict from a crash. The test now asserts the conviction banner and the absence of a traceback, and the gate returns 2 rather than crashing if that partition breaks again. PART 3 — the floor raise, and the premise was WRONG. Read back from the store (not the form): min_controller_version = 0.216.0 @ 12:36:58Z, zero per-customer overrides, no "managed floor HELD" line. But read 5 shows the raise was NOT a no-op: demo-felhom had been on 0.214.0 since 12 Aug and auto-updated 0.214.0 -> 0.216.0 at 12:37:07Z -- NINE SECONDS after the save, exactly the immediate action publish-train rule 2 documents. No error events followed; it restarted clean. R-343 is therefore filed OPEN, not CLOSED: the closing condition was all five reads clean and no directive served. It went well, but a record calling it inert when it moved a customer box is what misleads the next reader. The row also states why the floor was behind -- rule 2 policy, not drift, earned by the 2026-07-11 skew onto Peti's box -- and cites ResolveManagedFloor (store.go:2068) plus the two build-felhom-iso.sh facts (build-time at :267, fails open at :78-82) rather than asserting them. Two boxes are below the floor and neither reports: drill-r50 (blocked, powered off) and peti-felhom (host row deleted). peti-felhom was NOT contacted -- its row records that a report from a deleted host 401s and is not persisted, so the raise cannot reach it. PART 4 — R-342 filed READY, quoting stop2-snapshot.txt verbatim: Hetzner server snapshot 421440873 covers /dev/sda only; /mnt/pbs-datastore is a separate Volume that snapshots exclude, so a rollback restores software state and NOT the datastore. Fine for that upgrade; the safeguard for any future procedure that could touch the datastore does not exist and is Viktor's call. Also: CLAUDE.md's gate list named 6 of 10 registered gates -- completed rather than appending a 7th to a wrong list (124 -> 128 effective, ceiling 200). Capability map deliberately unchanged; no row cites a floor or golden version. repo_gates.py fully green, 10/10. |
||
|
|
b03a105375 |
hub v0.105.0: the third name, a machine told to be quiet, and a guard for the hub's own words
gates / gates (push) Successful in 17s
Hub only. No controller change, no agent change, no wire change — nothing to bake. demo-hp untouched: the operator is re-deploying it this evening. R-323 — the five-word phrase is „Tulajdonosi jelmondat". It was „Visszaállító jelszó": one word from the name retired last week, and false besides — it restores nothing, it proves the account owns the box being bound. Five sites, all in the hub; felhom-controller and felhom-agent carry the name nowhere, so no halt and no bake. Both suggested names were rejected with reasons: „Fiókjelszó" would collide with the dashboard login (a DIFFERENT real secret), and „Összekötési jelszó" would leave the two factors on this page separated only by kód-versus-jelszó — the exact shape being removed, since the other factor is the „Párosító kód". The chosen name differs on both axes, stem and noun. Naming only; the acceptance pin drives the real handler. R-324 — the hub's customer copy is under a guard for the first time. Retired names banned across all 95 hub files; retrieval stems registered in four declared customer surfaces. The selftest found a defect in its own instrument on the first run. One shared vocabulary in scripts/, drift-checked into the controller gate rather than copied (R-325 removes the scaffold). R-321 — a machine we told to be quiet is no longer reported as dead, and it was two doors, not one: because the state is RECORDED rather than deleted, the morning deadline check can skip it too. A deleted state returns "", which is not "down" — R-195's shape returning through a second door. The clock runs from the report the hub can see, so re-enabling starts it there and emits no recovery for an outage that never happened. Three red-proofs; the one that matters showed a genuinely dead machine sitting at "disabled" when the suppression was made unconditional. R-326 — "which claims are unproven" is answerable by a command now. The nine I have been repeating was the count of claims the 9 August pass DOWNGRADED, not the count of unproven ones. The real figures: 55 claims, 23 walked, 32 not — and only 6 of those 32 cite evidence. Its first run found a stale claim (R-327). |
||
|
|
4d6ec7c7bb |
hub v0.104.0: the guest network gets a reader (R-319), and the hub half of the naming (R-295)
gates / gates (push) Successful in 14s
Four paper debts and one fact given a reader. Hub-only — nothing to bake. A4 — the entry about "the tester's machine" named a risk correctly and labelled it in a way that invited deleting it. Established from the hub's own store: `peti-felhom` is a REAL machine (482 reports, 2026-02-27 → 2026-07-15, a named person's own box) and the 3.6 GB with no key and no backup is real. `david` → `tester-1` is a DIFFERENT record with no host, no escrow and no report, ever — deleted 07:55:49 and re-created 07:56:47 this morning. The prompt's premise conflated the two; the register now says which is which. A1 — R-312/R-313/R-303 recorded as DECIDED with their re-open triggers, and moved out of STATUS's "Waiting on you", which is now empty. A3 — day0-install §C.1 said pushing the installer publishes it. It has not since R-110. Corrected, with the two manifest pins named and an outside-verification command; the one copy that repeated it (a dated audit, true when written) carries a superseded note. A5 — standing rule 5: evidence comes off the machine at the end of the phase that produced it, before any revert. Earned twice in three days on the same box at the same point (R-320). Four homes, plus what to do when it is already gone. R-295 hub half — „Beállító kód" everywhere; „Visszaállító kód" retired. New `reenroll` mail kind so the mail names the page a REBUILT box actually shows („A szerver beállítása"), not the „Elfelejtett jelszó" page it has no login screen to reach. Naming only; the acceptance pin proves the secret is untouched. R-319 — the hub models `guest_net` after 23 days of receiving and discarding it. The signal is `heals_last_hour`, not `state`: a guest the watchdog keeps repairing reads healthy between repairs. `heal_succeeded` decoded too (R-260's lesson). Unknown is never drawn as healthy — three absences, three sentences. No alarm, deliberately. Three red-proofs, mutations asserted applied. Wire-gate checked tags 182 → 190. B1 — the operator's 2026-08-12 dispositions were NOT in the register; they are now. Third allowlist kind for the five ruled "no reader wanted"; `reporting_disabled` reclassified redundant. 8 read · 5 deliberately unread · 1 redundant · 6 still owed. Also filed: R-321 (a deliberately-silent box still alarms stale/down — the checker is age-only, and decoding the flag would not have fixed it), R-322 (the claim guard has never scanned the hub; a hand scan returns zero, so it is a scope gap, not a defect). |
||
|
|
fc737b0fc0 |
installer v1.28.0: the removal genuinely reverses the installation (R-316)
gates / gates (push) Successful in 13s
v1.27.0's fix worked exactly once per machine. Measured on drill-r50 from virgin, on the PUBLISHED v1.27.0, before anything was changed: cycle 1 recorded 'no' and freed :53; cycle 2 recorded 'yes' and left dnsmasq running on 0.0.0.0:53; cycle 3 refused, exit 1. Every box already in the field is at cycle 2, and a reinstall onto a machine that has had Felhom is cycle 2 by definition. Why cycle 2 says yes: the preflight's ownership question is dpkg-query package presence and nothing else - not the absence of a record. Stopping the unit and leaving the package made our own package read as the household's one cycle later. Now the uninstall removes the package when the record says we installed it. Order unchanged and load-bearing: read the record, act, then delete the state file that holds it. TWO packages are recorded, because dnsmasq ships the unit and dnsmasq-base ships /usr/sbin/dnsmasq, and each is taken back only if we added it. The dependency check is a SIMULATION, not a guess: apt-get -s purge is asked what it would remove and the purge proceeds only if that set is a subset of ours; otherwise stop+disable, naming the package that blocked it. Never interactive, never fatal, and the success is re-queried rather than read off an exit code. Watched: three fixed cycles -> install 3 PASSES; a household resolver untouched; a dependent package not purged and named; no record -> untouched with the command named. Red-proofs with the mutation asserted applied: remove the purge -> cycle 3 refuses in those exact words; remove the ownership check -> a household resolver is purged; infer ownership -> the guess is taken. Also: R-317 (the agent stats a path dnsmasq-base owns to decide whether to install dnsmasq - pre-existing, now reachable), R-318 (no honest ownership marker exists for existing boxes; the preflight message is the mechanism), and the status page's decisions section rewritten to say what each decision costs and what doing nothing selects. |
||
|
|
6362bb6cb6 |
hub v0.103.0 — a host can read the packages we kept for it (R-311)
ListSupersededEscrow had zero production callers for nineteen days. It is the only reader of a retained identity_blob, so the retention shipped in v0.93.0 was material the product could not reach - proven on the fixture 2026-08-12, where a code that opens a retained package was answered as a code that opened nothing. New GET /api/v1/hosts/<id>/escrow/retained: self-scoped exactly as the current-row GET, same recovery-mode gate, same audit event written BEFORE the bytes leave, capped at 16. Rows with a NULL identity_blob are WITHHELD and returned as unopenable_count. They retain the PBS key, not the repository password, so they can never open what the caller is asking about; serving them would have the agent try packages that cannot succeed and would let the screen claim an earlier package is openable on exactly the boxes the original defect hurt. The count is returned because their existence is load-bearing and underivable. The trade, stated rather than waved through: the hub still cannot read any of it - sealed bytes in, sealed bytes out, no decrypt path, no recovery code ever held. What widens is volume, bounded by self-scope, the recovery-mode gate and the cap. The response is a NAMED TYPE, not a map, so the wire-contract gate can resolve it; the wire is declared as a fourth ROOT and the gate now checks 182 tags rather than 174. A positive control shows that check is name-presence, not decodability - filed as R-315 rather than reported as coverage. Six tests through the real endpoint; four red-proofs asserted applied. |
||
|
|
1d5f2b8bb6 |
DRILL: the retained key works, and the customer cannot reach it
gates / gates (push) Successful in 23s
Three verdicts, kept separate because collapsing them is how this assumption survived a week. (a) The material IS retained. host_escrow_superseded id 11 is the first retained row in fleet history to carry identity_blob (572 B), byte-identical to the pre-supersession row (sha256 a10032341c8584ed...). (b) The retained material DOES open the old store. Unsealed with the old recovery code it yielded a password byte-identical to the pre-change one, and restored three planted files byte-identical from a store the box itself could no longer open - including a Hungarian accented filename verified as raw bytes. Negative control ran first and failed closed. (c) The customer has NO route, and is misinformed. ListSupersededEscrow has zero production callers; the recovery path selects FROM host_escrow. Asked with the code that had just worked by hand, the product answered "the recovery code did not open the sealed bundle". A valid code for retained history is reported as a bad code - the R-224 class again. R-304, rank 1. Both installer faults were watched happening first, so installer-v1.27.0 is now published (tag + both webpage.yaml refs). Pre-fix: the box came up on controller 0.98.3 against a vouched 0.213.0, below the floor and below the version carrying the recovery screen; and our own uninstall left dnsmasq on 0.0.0.0:53 so our own next install refused. R-297 and R-300 CLOSED. Also filed R-305 (the dnsmasq fix fires once per machine - the leftover returns on the second reinstall, proven), R-306 (--preflight-only writes state it says it does not), R-307 (a live abandon countdown on demo-felhom, firing 2026-08-24 - operator decision), R-308 (stored controller password stale), R-309 (the day-0 runbook's publication claim has been false since R-110), R-310 (two edges). Ceiling R-303 -> R-310. Capability map moved: the retention claim is now marked operator-only. Phase A logs did not survive the intermediate revert; recorded. |
||
|
|
125aec1be2 |
R-300: uninstall no longer leaves dnsmasq blocking the next install
gates / gates (push) Successful in 18s
Removing the snippet and restarting left dnsmasq enabled and unconstrained on 0.0.0.0:53, so the next byo install's preflight refused and the customer went debugging a home network that was never at fault. Ownership is recorded at preflight (the only moment it is a fact - the package is installed by the agent, not this script) and honoured at removal. Boxes already in the field carry no record and fail safe to restart-only, with the reason and the command logged; the preflight message covers them instead. Not observed live - no installer-v1.27.0 tag is cut. Files R-299..R-301. |
||
|
|
eb600872f2 |
R-297: installer compares a local golden against the manifest before using it
Step 7 short-circuited on any local golden archive with no version compare, no digest and no warning, so the manifest sha256 was consulted only on the fetch path. Local discovery is newest-by-filename: correct by recency, never by verification. A box could reinstall from a stale archive and come back below the version where the offsite recovery screen exists. Digest first, then the baked controller tag. An auto-discovered mismatch re-fetches the vouched golden; an operator-named mismatch refuses. An unreadable manifest refuses rather than passing. Not published: installer-v1.26.0 is deliberately not cut until a fresh install has been observed taking a stale local golden on drill-r50. Also files R-295..R-298. |
||
|
|
c04f933d0b |
The census answers no, three receipts found, and the prune was on file all along
gates / gates (push) Successful in 13s
CENSUS (read-only, hub store, tester's machine not contacted): no machine that is not ours can be in the state that cost demo-felhom its history. The hub holds escrow for three hosts; both demo boxes lost their pre-fix key in the same four hours on 2026-08-04; peti-felhom and david have no host row and no escrow at all. A control ran FIRST and had to pass -- the query returned "present (572 bytes)" for a host known to have material and "absent (NULL)" for one known not to. Corrected my own instrument on the way: a date-only comparison mislabelled both losses as after the fix, so the in-force moment is now pinned from the hub's first post-fix escrow row (11:11:37Z), which independently agrees with the register. PART 1 ESTABLISHED. The prune is recorded inside R-267 -- the row about the Configuration page being slow -- because pruning artifacts is what made that page fast. Arithmetic checks (23+7=30, plus three versions that only surfaced after the first thirty moved them onto page one = 33) and the PAGINATED listing shows both generics at exactly ten. R-291's blocking condition is released: the operator was being asked to establish something already written down. And my counter-argument yesterday was wrong in exactly the way R-267 warns about -- "containers hold 19" came from an unpaginated query; paginated they hold 270 and 169. RECEIPTS: three restored (drives.enrol, backup.tier1, fail.lost-recovery-code), each citing the document that walked it; the map already read PROVEN-LIVE for all three, so this follows the map rather than raising a status in the view. NINE HONEST GREYS. fault.selfheal's best hit argues against it -- an incident recording self-heal's absence through a 1h15m outage. THE DECAY RULE FIRED FOR THE FIRST TIME. backup.restore-proof has a receipt from 28 July and is superseded anyway: demo-hp's restore-test failed 5 August and the box has since been rebuilt. A claim about a continuing behaviour cannot rest on an old observation. The capability map still reads PROVEN-LIVE and is now the thing out of step -- recorded, not silently rewritten. PART 4 specified, not implemented. The orphan card promises restorability the box rendering it cannot evaluate: the discriminator is on the hub and no wire field carries it. A conditional promise the system cannot evaluate is the same defect as an unconditional false one, so the copy stops promising, says what happens, and names a route. Ships with the next controller change so one bake covers both. |
||
|
|
6088afcbed |
Verify the standing picture against source: 12 downgrades, and the decay ran both ways
gates / gates (push) Successful in 21s
55 claims verified. Twelve moved, all downwards: walked 32 -> 20, built 5 -> 17. Register ceiling R-284 -> R-290. THE RULE DID NOT FIRE THE WAY IT WAS EXPECTED TO. Not one downgrade came from code moving under an old proof. All twelve came from step 1 of the same rule -- the cited evidence does not exist. Measured: of the 28 capability-map rows behind the page's claims, 8 carry a tests/ or audits/ path and 20 carry prose only. The green dots were drawn from rows that cite an argument, not a walk (R-290). The map, not the dataset, is what needs fixing -- it still says PROVEN-LIVE for all twelve. And once it ran backwards: fault.operator-email looked contradicted by R-182, but live source shows the backup_run_failures digest allowlisted, operator-only and templated, with recovery_unit_capture_failed now record-only. The claim is right and the REGISTER ROW is stale (R-289). The session went looking for stale proofs and found a stale defect. R-281 WITHDRAWN -- wrong in both directions, settled by the operator's mailbox. The tripwire DID fire (escrow_blob_served 10:19:41Z = 12:19 CEST) and false error-severity alarms fired too, for deliberate attended work (R-285). The measurement's cause is ESTABLISHED: the P7 query copied hub.db without hub.db-wal, and the signature is exact -- it reported "2 events all day, newest 00:30:07", and the rows at or before 00:30:07 number exactly 2. Timezone and wrong-key were tested and refuted. The control had been drawn from the same stale snapshot as the measurement, which is why it agreed (R-286). Part 4: NO WORKFLOW CHANGED, deliberately. The gate is not ref-sensitive -- it enumerates from the Gitea tags API, and both previous tag pushes passed. The red is TRUE: run 267 saw v0.120.0 downloadable, run 284 on the same commit saw 404. Who deleted the package is NOT established and is not guessed (R-287). The page is now generated from where-felhom-stands.yaml by scripts/render_stands.py: static, zero script tags, every moved status carrying a visible "changed, was X" chip. The React bundle -- whose content was gzip+base64 inside a JS module map -- is kept as a dated snapshot. scripts/check_stands.py gates the data and convicted 51 problems in my own first draft before the staged positive control ever ran. |
||
|
|
3bf62b95bb |
fix(gate): the wire-contract search shelled out to grep and read its failure as a finding
gates / gates (push) Successful in 33s
CI convicted ALL 174 checked tags while the pre-push hook was green. Cause, read from the run log rather than guessed at the second attempt: the search used `grep -rnE --include=…`, and the CI runner's image carries python3 and git and deliberately little else — its grep does not support `--include`, so stdout was empty and the gate read empty as "the tag is absent". That is a gate silently treating a tool failure as a finding, which is worse than no gate, and it is exactly the error-swallowing this repo forbids. A green from it would have been just as untrustworthy as the red. Fixed by removing the dependency, not by working around it: the search is now pure Python — one token index per receiving repo, built in a single pass, no subprocess. Faster too (one walk instead of ~350 greps), and unreadable-file / empty-repo cases now exit 2 INCONCLUSIVE rather than reporting absence. THE BEFORE CAPTURE WAS RE-VERIFIED, NOT RE-GENERATED — the stronger claim. All 40 fields recorded in BEFORE.md were re-tested against the new implementation: agree=40, disagree=0, i.e. exactly the four this session fixed are now present and the other 36 still absent. The number 40 stands under both implementations; only the mechanism changed. The whole-token property survives by construction — a token index treats `healed_at` and `privsep_healed_at` as distinct tokens. This is the THIRD instrument defect this gate's own controls caught before it was trusted, after the substring false negative and the dr_recipe over-opacity. The first two were caught by re-finding the known instances; this one by the CI-versus-hook disagreement the workflow's alarm mail explicitly says outranks whatever the push was for. |
||
|
|
b080ecf411 |
hub v0.99.0 — the hub can see whether the operator can get in (R-260); G-1 gate closes, R-247 closes
oobDegraded tested five things and the sixth never arrived. The agent has emitted `operator_key_configured` on every heartbeat since v0.72.0 — the SAME version that introduced the `oob` stanza carrying it — and store.HostOOBRow mirrored five of the agent's eight OOB fields. With no field for it, encoding/json discarded the fact on arrival, so a box with felhom-sshd active, reachable, a valid config and a configured peer reported `ok` with NO OPERATOR KEY INSTALLED AT ALL. Not a wrong answer: an answer to a question nobody was asking. `operator_peer_configured`, which the hub did read, only says the peer IP is in desired-state — that OOB is MEANT to work, not that entry is possible. Now decoded: operator_key_configured, plus wg_handshake_age_s and healed_at. The last two ride the ALERT TEXT and are deliberately NOT in the predicate — widening a check beyond the fact that is now arriving is how a check stops being read. SCENARIO F, decided on a measurement rather than a preference. operator_key_configured decodes as a POINTER: nil = the agent never said, reported distinctly and never as ok. The version gate was rejected because the field and its stanza shipped in the SAME agent version (v0.72.0), so a stanza without the field cannot come from any released agent; the fleet is 0.113.0/0.127.0 and the vouched floor is 0.127.0. Handled explicitly anyway and pinned, because "cannot happen" is a claim this project has been burned by. THE MESSAGE NAMES THE FAULT. oobDegradedReason is the single source for both predicate and text, so the alert can never name a different fault from the one that fired. The old form derived it separately and had a vocabulary of two — unreachable, or config invalid — with no way to say the key is missing. The operator reads this at 07:00. TESTS DRIVE THE DECODE BOUNDARY. Every hub OOB test before this built a HostOOBRow by hand, and a test written that way CANNOT SEE A FIELD THAT NEVER DECODES — which is how this held a green suite for five weeks. The pre-existing fixture oobReport() also omitted the field, so those scenarios ran against a report shape no released agent produces (same family as R-262). Both fixed. Red-proofs, 8 expected outcomes and 0 wrong, each with the mutation asserted applied: dropping the field returns the false ok; an unconditional check alerts a healthy box; unknown-as-ok restores the silent pass. G-1 CLOSED — scripts/wire_contract_gate.py shipped as ranked, built BEFORE the fixes and seen failing on 40 fields (documentation/tests/wire-contract-gate-2026-08-08/BEFORE.md). Two instrument defects the control caught first: a substring false negative (grep -F healed_at matched privsep_healed_at) and treating dr_recipe as wholly opaque when its top-level sections ARE decoded through an allow-list that already cost offsite_restic (R-122). The prompt for this session said "465 emitted tags, eight unreachable". Checked against the repo: R-260 said "at least eight DECISION-BEARING facts", never eight tags. The real count is 40. R-260 CLOSED (class gated, sharpest instance fixed). R-247 CLOSED (controller v0.209.0). R-264 MINTED and OPEN — the 21 facts with no consumer, allowlisted with reasons so that gating the class could not be mistaken for deciding them. Still open and named: R-246, R-255..R-259, R-261..R-263, and C7's test-comment half. Capability map checked: it claims OOB access is implemented, never monitored, so no row was untrue; what was untrue sat one layer down and the row now records it. repo_gates --fast: all 8 OK. go build/vet/test green in hub, run separately from this commit. |