vaultwarden 1.36.0 -> 1.37.4: its first ladder step, proven on the bench and on 9202 (R-890); instruction files kept true (decision 150)
gates / gates (push) Successful in 8s
gates / gates (push) Successful in 8s
Written by upgrade-test.py --write-ladder from both verdicts. Bench 9401: harness v5, anon peak 16.3 %, seed read back. Box 9202: guarded Update done in 12.3 s, seed read back, seeded through the admin invite inside the box (decision 149). CLAUDE.md: stale gate counts, the CI/R-161 and catalog_since-gate sentences, the checklist count, steps/ in the layout, engine service counts, decoy gate path. Factual only. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -6,7 +6,8 @@ unconditional: true
|
||||
> 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 all three repos' `.claude/rules/`; change it in all three or in none.
|
||||
> in `felhom.eu`, `felhom-controller` and `app-catalog-felhom.eu` `.claude/rules/`, and in the workspace root's
|
||||
> unversioned `.claude/rules/`; change all four or none.
|
||||
|
||||
## 1. What you may pick up on your own
|
||||
|
||||
@@ -56,3 +57,13 @@ One screen, plain language, in this order: **decisions you took** (§2) first; w
|
||||
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.
|
||||
|
||||
+5
-2
@@ -1,6 +1,9 @@
|
||||
## 2026-10-06 (afternoon) — the test box may seed vaultwarden through its admin page (R-890)
|
||||
## 2026-10-06 (afternoon) — vaultwarden's first update step (1.36.0 → 1.37.4), proven on both venues; the test box may seed it through its admin page (R-890)
|
||||
|
||||
**What runs on a box changed:** vaultwarden's pin moves to `vaultwarden/server:1.37.4-alpine` with its first ladder entry, so a box offers the update and the night may take it. Written by `upgrade-test.py --write-ladder` from both verdicts: bench LXC 9401 (harness v5, 10-minute memory watch, anon peak 16.3 %, seed read back, no restart) and scratch 9202 (the product's guarded Update, done in 12.3 s, seed read back, badge „Naprakész" after). Evidence: `felhom.eu/documentation/audits/r890-instructions-2026-10-06/{bench,box/vaultwarden}`.
|
||||
|
||||
Instruction files (`09` §3 decision 150, factual only): `CLAUDE.md` — the gates runner and `--fast` described as they are (fourteen gates; `--fast` skips image-resolvable and volume-persistence; scoping applies to every gate that accepts it), the CI/R-161 sentence (CI exists; R-161 closed), the stale „no gate for catalog_since yet" paragraph removed (the gate exists, R-452), the onboarding checklist count (61), `steps/<StepKey>.yml` in the template layout, the MariaDB list (five, grimmory added) and the PostgreSQL count (twelve), the decoy gate's path; `.claude/rules/unprompted-work.md` §5 and its copies line.
|
||||
|
||||
**What runs on a box changed:** nothing. Test tools only.
|
||||
|
||||
- R-890 (`09` §3 decision 149): the box walk may now seed vaultwarden through its admin invite on a TEST box, as the bench does. A guard refuses unless all five hold: not the bench venue, FELHOM_BOX_ADMIN_SEED=1, the walk targets scratch guest 9202 on demo-hp (never 9201; the Tester 1 box is allowed by the ruling but the walk has no route to it), THIS run installed the app (box_walk now records its installs), and the box's own controller.yaml names the drill catalog. The invite runs INSIDE the box: the token is read from the container's environment into a shell variable and handed to curl on stdin, the admin cookie lives in a 0600 file that is shredded, and only HTTP codes come back — the token never leaves the box. Tests: BoxAdminSeedGuard (red-proved: a guard that always allows fails three tests).
|
||||
|
||||
|
||||
@@ -5,23 +5,24 @@
|
||||
|
||||
## What this repo is
|
||||
|
||||
The Felhom **app catalog**: one directory per app under `templates/<app>/`, each holding exactly
|
||||
The Felhom **app catalog**: one directory per app under `templates/<app>/`, each holding
|
||||
`docker-compose.yml` + `.felhom.yml` (deploy fields, resources, healthcheck probe, app_info — all
|
||||
customer-facing text in Hungarian). The felhom-controller git-syncs these to every customer box;
|
||||
`.felhom.yml` drives the deploy wizard. `templates.json` + `scripts/generate-customer.sh` are LEGACY
|
||||
(Portainer-era) — new apps don't touch them.
|
||||
(Portainer-era) — new apps don't touch them. An app with a superseded ladder step also holds
|
||||
`steps/<StepKey>.yml` (the box pins those, see below).
|
||||
|
||||
## Deploy contract
|
||||
|
||||
**Push to `main` = deploy.** The controller's sync picks changes up within 15 minutes (or trigger via
|
||||
the dashboard "Sablonok frissítése" button / `POST /api/sync`). Only the two template files sync;
|
||||
the dashboard "Sablonok frissítése" button / `POST /api/sync`). Only the template files sync;
|
||||
deployed `app.yaml` (customer secrets) is never overwritten. Full deploy details: the
|
||||
`felhom-build-deploy` skill.
|
||||
|
||||
## Conventions
|
||||
|
||||
- **Adding a NEW app starts with `cp onboarding/_TEMPLATE.md onboarding/<app>.md`** (operator request
|
||||
2026-10-01). `NEW-APP-CHECKLIST.md` is the list — 60 checks in 10 groups, each with how to test it and
|
||||
2026-10-01). `NEW-APP-CHECKLIST.md` is the list — 61 checks in 10 groups, each with how to test it and
|
||||
why it exists; the record answers every id `done` (with evidence that exists), `n/a` (with a reason) or
|
||||
`open`. The template and its complete record are published in ONE commit: `scripts/check-onboarding.py`
|
||||
(gate `onboarding`, in `--fast`, so the hook and CI) refuses a new template directory without one. The 53
|
||||
@@ -38,8 +39,8 @@ deployed `app.yaml` (customer secrets) is never overwritten. Full deploy details
|
||||
- No secrets in any committed file; secrets are generated at deploy time via `deploy_fields`
|
||||
`generate:` specs.
|
||||
- **Run `python3 scripts/catalog_gates.py <app>` after ANY template change** — it is the ONE entry
|
||||
point and runs all four gates below, exiting non-zero if any fails. Name the app(s) you touched
|
||||
and it scopes the two gates that accept scoping, which is fast; with no names the runtime gate
|
||||
point and runs every gate in its `GATES` table (that table is the list), exiting non-zero if any
|
||||
fails. Name the app(s) you touched and it scopes every gate that accepts an app scope, which is fast; with no names the runtime gate
|
||||
deploys **every** template, so that form belongs **on a scratch host, never a customer box**.
|
||||
Exit: 0 all clean · 1 convicted · 2 UNDETERMINED, which is never a pass.
|
||||
**Why a runner and not four separate invocations** (operator ruling 2026-08-02, R-161): of this
|
||||
@@ -47,12 +48,11 @@ deployed `app.yaml` (customer secrets) is never overwritten. Full deploy details
|
||||
CLAUDE.md — `felhom.eu/scripts/site_gates.py` is run, and R-29's three orphans are named nowhere and
|
||||
have stopped nothing. Controller-side enforcement was rejected because a check at template load can
|
||||
only read the file, and a static audit of all 53 templates reports the catalog clean **including
|
||||
papra** — it would pass on the exact defect it exists to catch. CI was rejected for now: neither
|
||||
repo has any, and there are no users yet. **R-161 stays open at reduced scope** — this is
|
||||
convention, run by a person; real automatic enforcement is owed when a second person touches
|
||||
templates. **Update 2026-08-02:** `.githooks/pre-push` now runs `catalog_gates.py --fast` on every
|
||||
push, which is gate 1 (`check-image-pins.py`) and, since 2026-09-13, the engine-major gate with the
|
||||
push range — the other two need network and a container
|
||||
papra** — it would pass on the exact defect it exists to catch. CI was rejected at the time (neither
|
||||
repo had any); it now exists and re-runs `--fast` on every push (below). R-161 was closed
|
||||
2026-10-05, accepted by the operator. **Update 2026-08-02:** `.githooks/pre-push` now runs `catalog_gates.py --fast` on every
|
||||
push, which is every gate except `image-resolvable` and `volume-persistence` (the range-reading ones,
|
||||
engine-major among them, get the push range) — those two need network and a container
|
||||
runtime and take minutes per app, and a push that pulls images and starts containers gets bypassed
|
||||
within a week, after which the bypass is the habit. They stay deliberate periodic runs. The hook is
|
||||
per-clone (`git config core.hooksPath .githooks`) and `git push --no-verify` bypasses it, which is
|
||||
@@ -71,9 +71,7 @@ deployed `app.yaml` (customer secrets) is never overwritten. Full deploy details
|
||||
A stale `catalog_since` under-reports how long a box has been behind, which is the one number the
|
||||
badge exists to give. Absent, empty, malformed or FUTURE-dated all degrade to a badge with no age
|
||||
and one WARN in the controller log — never a broken template.
|
||||
**There is no gate for this yet** and that is a known gap, filed as a register row: the gates
|
||||
runner fetches at `--depth 1` and has no parent commit to diff an `image:` line against, so a drift
|
||||
gate needs a deeper fetch. Backfilled for all 53 apps from git history on 2026-09-02.
|
||||
Backfilled for all 53 apps from git history on 2026-09-02.
|
||||
- **A pinned tag can still rot away upstream** — the pin gate is syntactic and cannot see that.
|
||||
Second gate: `python3 scripts/check-image-resolvable.py` (exit 0 resolve / 1 GONE / 2 inconclusive),
|
||||
run at the start of every catalog campaign and before any publish train that vouches the catalog.
|
||||
@@ -108,11 +106,11 @@ deployed `app.yaml` (customer secrets) is never overwritten. Full deploy details
|
||||
shipped. The original rule — *no database-engine image crosses a major until the Update button
|
||||
takes a verified backup as its precondition* — named its own expiry, and that condition is met:
|
||||
Slice 4 shipped 2026-09-13 (controller v0.237.0/v0.238.0; any backup tier since v0.239.0).
|
||||
**What is lifted.** The **four MariaDB** services (`bookstack-db`, `kimai-db`, `nextcloud-db`,
|
||||
`romm-db`) may now cross a major. They have both halves they need: a verified backup in front of
|
||||
**What is lifted.** The **MariaDB** services (`bookstack-db`, `grimmory-db`, `kimai-db`,
|
||||
`nextcloud-db`, `romm-db` on 2026-10-06; the gate judges by a glob) may now cross a major. They have both halves they need: a verified backup in front of
|
||||
the Update, and `MARIADB_AUTO_UPGRADE=1` on every sidecar (R-459), whose conversion the harness has
|
||||
WATCHED run on the E3/E3b edges with the seeded data read back after.
|
||||
**What is NOT lifted.** The **eleven PostgreSQL** services stay refused: the image performs no
|
||||
**What is NOT lifted.** The **PostgreSQL** services (twelve on 2026-10-06, postgis and immich's postgres image counted) stay refused: the image performs no
|
||||
`pg_upgrade` and REFUSES to start on an older major's datadir (R-463). A backup is a route back,
|
||||
not a conversion — the app simply would not come up. MySQL is refused too, with nothing measured
|
||||
at all. **ONE APP AT A TIME, since 2026-09-25 (`09` §3 decision 35):** the BOX converts a PostgreSQL
|
||||
@@ -158,8 +156,8 @@ deployed `app.yaml` (customer secrets) is never overwritten. Full deploy details
|
||||
|
||||
**A gate ships with a decoy test that has been seen to fail (R-421).** A decoy is the LABEL without
|
||||
the FACT — a directory with the right name and no bake log, a note whose prose mentions the marker it
|
||||
lacks. `scripts/decoy_coverage_gate.py` refuses a new gate that has neither a decoy nor a named
|
||||
lacks. `felhom.eu/scripts/decoy_coverage_gate.py` (run by felhom.eu's `repo_gates.py`, for all four repos) refuses a new gate that has neither a decoy nor a named
|
||||
exemption carrying its row. The four shapes, the 2026-09-01 sweep that fooled 16 of 29 gates, and the
|
||||
decoys withdrawn as illegitimate: `documentation/audits/AUDIT-gate-decoys-2026-09-01.md` and
|
||||
decoys withdrawn as illegitimate: `felhom.eu/documentation/audits/AUDIT-gate-decoys-2026-09-01.md` and
|
||||
`felhom-controller/.claude/rules/gates.md`. **Scope is a fact too** — prefer `os.walk` over
|
||||
`os.listdir`, and a glob over a hand-maintained list.
|
||||
|
||||
@@ -15,7 +15,7 @@ subdomain: "vault"
|
||||
slug: "vaultwarden"
|
||||
# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that
|
||||
# changes an image: line must set this to the same day (see CLAUDE.md).
|
||||
catalog_since: "2026-07-18"
|
||||
catalog_since: "2026-10-06"
|
||||
|
||||
# --- Resource hints (displayed on deploy screen) ---
|
||||
resources:
|
||||
@@ -150,3 +150,9 @@ i18n:
|
||||
label: 'No - by invitation only (recommended)'
|
||||
- value: 'true'
|
||||
label: 'Yes - anybody who knows the address can sign up'
|
||||
|
||||
# update_ladder — the test record: one tested step per line, oldest first (JSON flow mappings,
|
||||
# `09-update-architecture.md` §6.4 part 4). WRITTEN BY scripts/upgrade-test.py, never by hand;
|
||||
# gated by scripts/check-test-record.py. An image: move without a proven entry here is refused.
|
||||
update_ladder:
|
||||
- {"from": {"vaultwarden": "vaultwarden/server:1.36.0-alpine"}, "to": {"vaultwarden": "vaultwarden/server:1.37.4-alpine"}, "digest": {"vaultwarden": "sha256:19ea2b669945d842dadc3d500f2d6a795866a003330f59a47b9634658bae98bb"}, "verdict": "proven", "tested_at": "2026-10-06T11:49:21Z", "harness_version": 5, "evidence": "felhom.eu/documentation/audits/r890-instructions-2026-10-06/bench", "box_evidence": "felhom.eu/documentation/audits/r890-instructions-2026-10-06/box/vaultwarden", "memory_peak_pct": 16.3, "marks": {"files_may_change": false, "needs_person": null, "memory_tight": false}, "memory_basis": "anon", "memory_cgroup_peak_pct": 22.3}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
services:
|
||||
vaultwarden:
|
||||
image: vaultwarden/server:1.36.0-alpine
|
||||
image: vaultwarden/server:1.37.4-alpine
|
||||
container_name: vaultwarden
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
|
||||
Reference in New Issue
Block a user