slice 3 docs: the ruling, the shipped mechanism, and four rows closed
gates / gates (push) Successful in 17s

09-update-architecture.md gains the fourth dated operator ruling (2026-09-06,
Option 1) and its section 5 is rewritten from a proposed shape into the shipped
one: the pin, the stored definition, the render table, the four writers, the
startup ordering, and the trap this slice set for slice 2 - the live compose file
is now the frozen one, so a badge comparing against it would answer Naprakesz on
exactly the apps that are behind.

02-controller-module-map.md said 'copy compose + .felhom.yml'. That stopped being
true today, so it is corrected, and the two sections describing the old seam now
carry a banner saying they describe v0.234.0 and below - kept because every box
under v0.235.0 still behaves that way and because they are the measured account
of why it changed.

R-447, R-441, R-438 and R-455 closed and compressed into CLOSED-ITEMS; R-458
opened for the .felhom.yml asymmetry, with what would settle it by measurement.

Live evidence: two real catalog pushes travelling the real 15-minute cycle, both
reverted, the tree byte-identical afterwards. The restart that used to take 18.3
seconds and pull a new image now takes 0.1 seconds and pulls nothing.
This commit is contained in:
2026-09-06 10:37:33 +02:00
parent bc47dd4ef9
commit 417df06f35
9 changed files with 411 additions and 76 deletions
@@ -335,7 +335,7 @@ own; every caller that is not the customer must decide for itself whether the ap
### `sync/`
| File | Class | Reason | Risk |
|---|---|---|---|
| `sync/sync.go` | **KEEP** | Catalog git-sync (clone/fetch/reset, copy compose+`.felhom.yml`, never overwrite app.yaml). **It copies into EVERY stack folder, deployed or not — see "the app-definition seam" below.** | clean |
| `sync/sync.go` | **KEEP** | Catalog git-sync (clone/fetch/reset). **Since controller v0.235.0 it RENDERS `docker-compose.yml` rather than copying it** — verbatim while the catalog still offers the app's pinned version, from the app's stored `applied-compose.yml` once the catalog moves past it. `.felhom.yml` is still copied verbatim always, and `app.yaml` is still never touched. Reasoning: `09-update-architecture.md` §5. | clean |
### `system/` — split per-function (not per-file)
| File | Class | Reason | Risk |
@@ -518,6 +518,14 @@ own; every caller that is not the customer must decide for itself whether the ap
Until this was measured, no architecture document said what happens here, and the gap itself is
R-438. The three facts below are the ones a reader needs before touching any of it.
> **⚠ SECTIONS 1 AND 2 DESCRIBE THE BEHAVIOUR UP TO CONTROLLER v0.234.0. Controller v0.235.0
> (2026-09-06) CHANGED IT, on an operator ruling.** They are kept because they are the measured
> account of why it was changed, and because every box below v0.235.0 still behaves this way. **What
> ships now: `09-update-architecture.md` §5.** In one sentence — an app's VERSION is frozen to what the
> customer has and only a deliberate Update moves it, while template CORRECTIONS and the self-healing
> below still arrive on the 15-minute cycle. Nothing was added to the thirteen call sites in §2; they
> were made safe by removing the reason.
### 1. The catalog syncer rewrites the file under a running app, on a 15-minute cycle
`Syncer.copyTemplates` (`sync/sync.go:319`) walks every directory in the catalog cache and copies
@@ -526,6 +534,11 @@ the app is deployed.** The only guard is a sha256 content compare (`copyIfChange
exclusion is `app.yaml`. Interval is `git.sync_interval`, default `15m` (`config/config.go:351`), plus
one immediate sync at controller start (`sync.go:98`).
**SINCE v0.235.0** this walk still happens and `.felhom.yml` is still copied unconditionally, but the
compose file goes through `Syncer.renderSource`, which consults a per-app plan supplied by the stack
manager (`Manager.RenderPlanFor`) through a nil-safe seam. A nil seam is byte-for-byte the behaviour
described above.
**It restarts nothing.** The post-sync hook is `stackMgr.InjectMissingFields(updated)` and nothing
else. So from the moment it runs, a deployed app's *definition* and its *running containers* disagree,
and they stay that way until something else acts.