Files
felhom.eu/.claude/rules/unprompted-work.md
T
admin a08bd3cbd5
gates / gates (push) Failing after 5m29s
architecture: the system poster committed, its facts given a home, and a rule to keep them together
PART A -- the poster. documentation/architecture/felhom-system-poster.html
(307 KB). Secret scan first: ZERO IPv4, zero PEM blocks, zero ssh keys, zero
Bearer. The one EAA... match is base64 inside an embedded "mime":"font/woff2"
blob, not a Facebook token. "token"/"secret"/"password" appear 11 times and
every one is a NAME ("6. ep0 read token", "the hub seal key"); the poster
itself says "Names only; no secret values". All five long base64 blobs are
declared assets: 1 image/png, 3 text/javascript, 1 font/woff2.

It renders with NO network: the source mentions cdn.jsdelivr.net and Google
Fonts, but the loaded requests are only the HTML plus blob:/data: URLs -- the
bundler inlined everything. Measured, not assumed, and it matters: this is a
disaster-recovery document, so needing the internet to draw would be a defect.
No console errors.

The operator's three Claude Design fixes are all present: (a) no "WG" badge,
WireGuard only for the tunnel, no badge on the ep0-copy tile; (b) the box ->
ep0 arrow reads "encrypted on the box, sent through WireGuard"; (c) "Known
gaps" holds two items and NOT the household-keys sentence, which is now a
neutral "By design" note under the ep0 household namespace.

ONE FACT ON IT WAS WRONG. The felhom.eu tile said "served from DooPlex through
Cloudflare". It is not: Cloudflare is DNS only and the traffic goes direct --
measured this morning for the privacy notice, which states exactly that. The
poster would have contradicted a published page. Fixed in place (a label):
"served from DooPlex, Cloudflare DNS only". The first wording overflowed the
fixed-size tile, so it was shortened to fit and the evidence lives in the
facts file instead -- checked by re-rendering, not by hoping.

PART B -- the facts and the rule. DESIGN-PROMPT-...md is renamed
felhom-system-poster.facts.md (one home per fact), with the three fixes folded
in as explicit instructions so a regeneration cannot undo them, plus a new
"Badges" section saying a "WG" chip must never come back.

New rule, section 6 "The system poster stays true", added IDENTICALLY to all
five copies of unprompted-work.md (the four repos and the workspace root on
DooPlex; verified identical by diff before and after) and to
PROMPT-TEMPLATE.md's end-of-session checklist as a FIFTH coupled artifact.

scripts/poster_facts_gate.py WARNS when the facts file has a newer commit than
the poster. It never fails a push, deliberately: a refresh needs Claude Design
and the operator, --no-verify is forbidden here, so a blocking gate would leave
deleting it as the only way out. It compares COMMIT times, not mtimes, because
a checkout rewrites mtimes and every fresh clone would shout.

RED-PROOF -- and it found a real bug in the gate. The first run warned
correctly but exited 1: a single non-ASCII character in its own warning raised
UnicodeEncodeError on this cp1250 console. A gate whose entire contract is
"never fails a push" was failing pushes. Fixed (ASCII output + an encode
guard), and the decoy now asserts BOTH the warning and exit 0. Three branches
proven: facts newer -> warns, rc 0; poster newer -> quiet, rc 0; poster
missing -> "could not tell", rc 2, not a false all-clear.

The decoy itself was seen to fail, twice, on Linux (the suite needs fcntl and
cannot run on Windows): breaking the warning gives STALE_WARNS=False, and
making it exit 1 gives RC_STALE=1. All 80 felhom.eu decoys behave.

PART C -- do box reports pass through Cloudflare? NO. Two channels. DNS from
PUBLIC resolvers (not DooPlex's own, which answers the LAN address):
hub.felhom.eu is a CNAME to dooplex.hopto.org -> 37.191.56.193, not a
Cloudflare address, and no cf-ray comes back. The manifest: an ordinary k3s
Ingress, Cloudflare named only in a DNS setup comment. THE CONTROL that makes
the negative mean something: iso.felhom.eu resolves to 172.67.x / 104.21.x,
real Cloudflare addresses -- so the method does detect proxying.

So nothing is added to the Cloudflare row: the hub path does not touch it.
06-offsite-connectivity.md section 1 claimed the public edge is a
Cloudflare-Tunnel and "DooPlex has no public IP" -- both untrue today. Kept
and marked STALE with the measurement rather than rewritten, because that
paragraph is the reason ep0 exists and the argument needs its premise visible.
total-loss-of-dooplex.md's "today a CNAME to dooplex.hopto.org" is confirmed
correct.

Register: 137 before, 137 after, 0 opened, 0 closed -- every finding here was
small and fixed in the session.
2026-10-09 18:09:50 +02:00

6.3 KiB

unconditional
unconditional
true

Unprompted work — rules for any session without a task file

Goal sessions, nightly sessions, "work the register" sessions. A session that starts from /goal or a standing brief inherits these rules exactly as it inherits the gates. They are the part of PROMPT-TEMPLATE.md that a task file used to carry and a goal does not. Same wording lives in felhom.eu, felhom-controller, felhom-agent and app-catalog-felhom.eu .claude/rules/, and in the workspace root's unversioned .claude/rules/; change all five or none.

1. What you may pick up on your own

  • A register row you or another CC session filed, with owner CC, at P3 or a bounded P2, that needs no operator decision, touches no customer data by design, and introduces no mechanism nobody has measured. Smallest first.
  • A defect you find while exercising the product, filed as a row before you fix it — unless it is small: a small finding is fixed in the session and never filed (the size rule, OPEN-ITEMS.md „How a row is filed").
  • Hygiene: register compression, stale citations, rows with no owner, documents that contradict live source.

Not yours, ever, without a task file or an operator word: money; anything that changes risk to customer data; anything that changes a promise the product makes to a customer; anything that reverses a documented design decision (documentation/architecture/ — a design decision is not a defect, R-370); anything on DooPlex or ep0; baking or vouching a golden; promoting a catalog version; a new external dependency; a hub image build or hub deploy in a session the operator does not attend (operator ruling 2026-10-07, 09 §3 decision 162).

2. When you may decide instead of ask (operator grant, 2026-09-14)

You may take a decision yourself when all of these hold: the architecture folder and the register give a clear direction; your choice follows that direction; it is reversible without customer-data risk; and you can write it in the 09-update-architecture.md §3 shape — one answerable sentence, the options, what each costs, why this one. Then record it as a dated decision in CONTEXT.md and the owning architecture document, tagged decided by CC unattended — operator may reverse, and put it first in the morning note. A decision you cannot write in that shape is one you do not take.

3. The discipline a task file used to carry

  1. Baselines first. Read each repo's main hash and version from live source before touching it.
  2. Read the architecture document for the area, and name it in the report, before any claim.
  3. Red-proof every correctness fix. A test never seen failing has not been shown to test anything.
  4. Live-validate on a Tier-0 box through the endpoints the UI invokes. demo-hp is ssh hp. Throwaway apps only; the standing apps and bentopdf stay.
  5. Evidence off the machine at the end of each phase, before any revert (R-320).
  6. One release per repo per session, with a CHANGELOG entry (controller: with its MinAgent line), REPORT overwritten, floor raised to deliver it. No golden unless a drill or fresh install needs one (the waiver, R-468). No --no-verify.
  7. An enumerated gap becomes a row in the same session — or, if it is small, is fixed in it (the size rule). Prose is not a record.
  8. Hungarian text is searched with ASCII fragments, with a positive and a negative control.
  9. Never leave a half-state. If time runs out, revert to clean and say what was reverted.
  10. Teardown, three layers, stated — machine, host, hub — or "provisioned nothing".
  11. Every helper prompt carries the brief's fences in full (operator ruling 2026-10-07). A helper session (a subagent, a fork, a workflow agent) gets the brief's fence list word for word — every protected machine, every „no", every delivery and Docker limit — not a summary and not „the usual fences". Earned on 2026-10-06 night: two helpers whose prompts carried only part of the fences ran docker volume prune on the bench and a Docker-using gate on DooPlex.

4. The morning note

One screen, plain language, in this order: decisions you took (§2) first; what you exercised; what broke and whether you fixed it; rows opened and closed with the register size before and after; what needs the operator, each with what happens if they do nothing. No file paths, no function names, no row numbers as the subject of a sentence.

5. Instruction files

Instruction files (CLAUDE.md, .claude/rules/*) are kept true by the session that finds them wrong (operator ruling 2026-10-06, 09 §3 decision 150). A session MAY, without asking: correct a stale fact (a command, a count, a version, a path, a description of what a gate does), add a fact it proved, and remove a reference to something that no longer exists. Each edit is named in the report (file, line, before, after, why). A session MAY NOT, without the operator's word: loosen a safety rule, a fence, a „never", a protected machine, a secret rule, or a review step; or remove a rule. When in doubt, it is a rule change, and it goes to the operator. If Claude Code's own permission check asks before such an edit, wait for the operator's click; if it refuses, record that and file the exact line.

6. The system poster stays true

A session that changes a fact listed in architecture/felhom-system-poster.facts.md (a machine, a role, a traffic path, a backup tier, a time, a retention, a key, a known gap) updates that file in the same commit. If the change is text only, it also edits the matching text in felhom-system-poster.html. If it needs a new drawing, it adds the line "System poster needs a refresh: " to STATUS.md's "waiting on the operator" list. The session report names which of the three it did.

Why the three-way split: the poster is a drawing made in Claude Design, and nothing in this repository renders it — unlike where-felhom-stands.html, which has render_stands.py. So the facts file is the source of truth and the drawing trails it. scripts/poster_facts_gate.py warns when the facts file has a newer commit than the poster; it never fails a push, because a refresh needs the operator and another tool, and a gate nobody can clear is a gate people learn to route around.