120 lines
6.4 KiB
Markdown
120 lines
6.4 KiB
Markdown
---
|
|
status: resolved
|
|
opened: 2026-08-23
|
|
located-in: [hal (the instance), mesh-host internal/bootstrap (preflight asks the daemon), mesh-controller internal/catalogue (capabilities)]
|
|
fixed-by:
|
|
- "the instance only: PR #962 — incus hook. Merged, ran on one node in 6s of a 60s budget; pipeline #6832 green in 48s. The class remains open."
|
|
- "partly, on the mesh: mesh-host 73c010e, 9d8239a — preflight and the profile detectors ask the daemon, not the package (installed-but-broken reports absent)"
|
|
- "the class, on the mesh: a manifest declares capabilities, an action declares verify (ADR 0005), and a module's own assertions are the lab's verdict (design 01). The old arrangement is replaced module by module, so its count is never taken"
|
|
amended-design: 03-DESIGN/01-to-be/01-end-to-end-testing.md
|
|
---
|
|
|
|
# 007 — An installed package is not an available capability
|
|
|
|
## Symptom
|
|
|
|
A module declares the virtualisation package the lab needs. The package is installed —
|
|
version 7.3.0-1, recorded as explicitly installed. The client binary runs.
|
|
|
|
The capability does not exist:
|
|
|
|
| Checked | State |
|
|
|---|---|
|
|
| `incus.service` | disabled, inactive |
|
|
| `incus.socket` | disabled, inactive |
|
|
| `incus-user.socket` | disabled, inactive |
|
|
| the operator's group membership | not a member of any incus group |
|
|
| the client | reports **`Server version: unreachable`** |
|
|
|
|
Nothing failed. Nothing reported anything. The declaration was satisfied exactly as written,
|
|
and the thing it was declared for cannot be used.
|
|
|
|
## Why this is not issue 001 again
|
|
|
|
[Issue 001](../001-failed-package-install-reports-success/00-report.md) is *the install failed
|
|
and the job reported success*. This is the opposite and arguably worse: **the install
|
|
succeeded, and success was not the point.**
|
|
|
|
A package is a set of files. A capability is a running service, an enabled socket, and an
|
|
identity permitted to reach it. The module model declares the first and has no vocabulary for
|
|
the second, so the gap between them is invisible — there is no state in which the mesh believes
|
|
this node has virtualisation and is wrong, because the mesh was never asked to believe it.
|
|
|
|
The distance between the two is the same one the delivery layer already has a name for:
|
|
**transport versus effect.** A package install reports that files arrived, which is transport.
|
|
|
|
## Why it matters now
|
|
|
|
This is the first requirement of the lab
|
|
([ADR 0016](../../02-DECISIONS/0016-the-lab.md)), which is
|
|
phase 0 of the entire migration. The first capability the new work depends on is present,
|
|
declared, and unusable — and would have stayed unusable silently.
|
|
|
|
It also generalises. Every module that declares a package needing a unit enabled, a group
|
|
joined, a kernel module loaded, or a socket activated has this gap. Post-install work lives in
|
|
hooks, and hooks have their own recorded failure mode: thirteen were found that had never run.
|
|
|
|
## Evidence
|
|
|
|
- Package recorded as installed 2026-08-23 00:02, explicitly.
|
|
- Both units disabled and inactive; the operator in no incus group; client reports the server
|
|
unreachable.
|
|
|
|
## Open questions
|
|
|
|
- Should a module be able to declare a **capability** — a unit that must be enabled, a group
|
|
the operator must be in — rather than only the package that provides it?
|
|
- If that is what hooks are for, why is the gap invisible when a hook does not run? A hook that
|
|
never fires and a hook that fires and does nothing are indistinguishable today.
|
|
- Is this what the verify stage should be asserting? It exists, and a module's own assertions
|
|
are meant to test outcomes rather than steps — "the socket accepts a connection" is exactly
|
|
that shape.
|
|
- How many other declared packages are in this state? Nothing currently reports it, which means
|
|
the answer is unknown rather than zero.
|
|
|
|
## The instance is fixed; the class is what this issue is now about
|
|
|
|
*Updated 2026-08-23.* A hook now does the post-install work — group membership, subordinate id
|
|
ranges, enabling both units, creating the storage pool, the bridge, and the default profile.
|
|
Verified independently afterwards: the group exists with the operator in it, both id files
|
|
carry the range, the service is active, and the storage pool reports `CREATED`.
|
|
|
|
**So the mechanism was never missing.** Hooks are exactly the right place for this, and used
|
|
properly they work. The gap is narrower and worse than "there is no way to do it":
|
|
|
|
> The hook did six things. **Six checks were then performed by a human, by hand.** Nothing in
|
|
> the pipeline asserted any of them, and a pipeline that dispatched a hook which silently never
|
|
> fired would have been green in the same 48 seconds.
|
|
|
|
That is not hypothetical — a hook named for a feature its module does not carry is skipped
|
|
without complaint, and thirteen such hooks were found at once in the past. The distance between
|
|
*the hook ran and did six things* and *the hook was dispatched* is invisible from the outside,
|
|
and it is the whole of this issue.
|
|
|
|
### The verification that was done by hand is the assertion set
|
|
|
|
The six checks performed after the merge are, almost word for word, what the module's own
|
|
verification should assert — outcomes, not steps, exactly as the lab design requires:
|
|
|
|
| Asserted by hand | As a module assertion |
|
|
|---|---|
|
|
| operator in the admin group | the group exists and contains the operator |
|
|
| subordinate id range in both files | both files carry the range |
|
|
| both units enabled, service active | the service is active, and still active shortly after |
|
|
| storage pool present | the pool exists and reports created |
|
|
| bridge present with an address | the bridge exists and holds its address |
|
|
| default profile wired to both | the profile references the bridge and the pool |
|
|
|
|
They currently live in a chat message. Moved into the module, they would run on every delivery
|
|
to every node, and the difference between a hook that worked and a hook that was merely
|
|
dispatched would stop being something a person has to notice.
|
|
|
|
### What this issue now asks
|
|
|
|
- Does a module gain a way to declare a **capability** — the outcome — separately from the
|
|
package that provides it, or is "write assertions" the whole answer?
|
|
- The verify stage exists and asserts almost nothing. Is this simply the first module that
|
|
should use it properly, making the issue a coverage problem rather than a design gap?
|
|
- **How many other declared packages are in the state this one was in?** Still unknown, still
|
|
unreported by anything, and now demonstrably worth asking.
|