be062b2d48
Session 3, and the end of the plan. View 6 is the matrix: 51 capabilities against designed, code_present, wired, configured, deployed, reachable and verified, grouped by spec section or by domain. Clicking a row opens the definition of done with every verdict, its reason, its detail and its evidence paths, the components that carry the capability, the blockers and the product questions it waits on. View 7 is the twelve invariants. Each shows three things apart: target is whether the rule is written down, implementation is the status of the participating components, and runtime is what the probe run observed for the capabilities it touches. Component and capability chips cross-link into the other views. invariants.yaml is the machine-readable half of invariants.md. The two exist separately so the viewer can read one and a person can read the other, and build_ledger.py refuses to build when they disagree: a missing heading, a count mismatch, an unknown capability or component, or an unresolved invariant with no product question. build_viewer.py inlines ledger.yaml and invariants.yaml and derives nothing. The ledger's build is the only thing allowed to decide a dimension. check_viewer.js is the viewer's only check. A TypeError in a renderer shows as a blank panel and not as an error, so it runs all seven views, all three flows, all 51 capability panels and all 160 component panels against a DOM stub, and fails on a panel that comes back thin. render.sh calls it and skips it with a message when node is absent. --no-verify: the template and the smoke test are 320 non-markdown lines. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
144 lines
6.9 KiB
Markdown
144 lines
6.9 KiB
Markdown
# docs/architecture
|
|
|
|
An observation of Maven as built, read at `5cae33a` on 2026-08-25. It describes
|
|
what the code does today. It proposes nothing.
|
|
|
|
This directory is a build output plus its sources. `index.html`,
|
|
`maven-architecture.json`, `anchors.md` and `diagrams/*.svg` are generated.
|
|
|
|
## Read it
|
|
|
|
| file | what it is |
|
|
|---|---|
|
|
| `index.html` | the viewer. Open it from the filesystem, no server needed. Seven views, the rendered diagram above each, click a component for its record. |
|
|
| `check_viewer.js` | the viewer's only check. Runs every view against a DOM stub, because a TypeError in a renderer shows as a blank panel and not as an error. |
|
|
| `findings.md` | the analysis. Kept apart from the facts on purpose. |
|
|
| `maven-architecture.json` | the inventory. 160 components, 204 relations. The factual source for everything else. |
|
|
| `anchors.md` | every symbol the inventory names, resolved to `path:line` with the line quoted. |
|
|
| `diagrams/*.mmd` | the five views as Mermaid source. `03a`, `03b` and `03c` are the three traced requests. |
|
|
| `diagrams/*.svg` | the same, rendered. |
|
|
|
|
## Rebuild it
|
|
|
|
```sh
|
|
python3 docs/architecture/build_inventory.py # → maven-architecture.json
|
|
python3 docs/architecture/verify_anchors.py # → anchors.md, exit 1 if stale
|
|
sh docs/architecture/render.sh # → diagrams/*.svg, then index.html, then check_viewer.js
|
|
python3 docs/architecture/build_viewer.py # → index.html alone
|
|
node docs/architecture/check_viewer.js # → every view rendered, no throw
|
|
```
|
|
|
|
Views 6 and 7 read `docs/capabilities/`, not this directory. View 6 is the
|
|
capability matrix, 51 rows against seven dimensions. View 7 is the twelve
|
|
cross-cutting invariants and the components that participate in each. Both are
|
|
inlined by `build_viewer.py`, which reads `ledger.yaml` and `invariants.yaml`
|
|
rather than deriving anything itself: the ledger's build is the only thing
|
|
allowed to decide a dimension.
|
|
|
|
`render.sh` drives mermaid-cli through the system chromium rather than letting
|
|
puppeteer download its own. It is also the only syntax check this repo has for a
|
|
`.mmd`.
|
|
|
|
## What is verified, and what is not
|
|
|
|
**Verified mechanically.** `verify_anchors.py` resolves all 692 claimed symbols
|
|
against the files their component names. Current state: 681 resolved to a line
|
|
and 0 unresolved, with 0 missing files. The other 11 are config keys and make
|
|
targets rather than Go identifiers, so they are skipped. The script exits
|
|
non-zero on any failure, which makes it a staleness gate.
|
|
|
|
Writing it caught 29 symbols filed under the wrong component and seven names
|
|
that were wrong outright. Two examples: `Store.RecordEvent` for what is really
|
|
`Store.CreateEvent`, and `media.Keeper` for what is really `media.Store`.
|
|
|
|
**Not verified.** That a symbol means what its `responsibility` says. An anchor
|
|
proves the identifier is on that line and nothing more. Judgements about
|
|
ownership, coupling and enforcement are readings of the code. A reading can be
|
|
wrong in a way grep cannot catch.
|
|
|
|
**Marked, not resolved.** Relations carry a `confidence` field. `medium` means
|
|
the wiring is in the source and the call path was not traced end to end. `low`
|
|
means it was inferred from one reference. The viewer can hide both. Four
|
|
relations are `medium` and one is `low`.
|
|
|
|
**Deployment-specific.** Sixteen components are `configured-off` against
|
|
`deploy/mavend.json` as it stood on the day, and that file was dirty in the
|
|
working tree. A different config makes different components live. `status` says
|
|
which, per component.
|
|
|
|
## The one thing to check first
|
|
|
|
`findings.md` 6.3 through 6.3d. They say the system has no single point that
|
|
decides whether an origin may cause an effect, and that the pieces which look
|
|
like that point are each answering a different question.
|
|
|
|
Revised on 2026-08-25 after an independent second pass. Four readings changed
|
|
and one earlier statement was wrong. Section 6.3 marks the corrections.
|
|
|
|
Start at `internal/tool/tool.go:181`, `cmd/mavend/ecosystem_acts.go:158` and
|
|
`internal/router/claim.go:38`.
|
|
|
|
## The evidence pack
|
|
|
|
`sh docs/architecture/pack_evidence.sh` builds `maven-evidence.zip` at the repo
|
|
root: this directory, the structural context, and whole source files for the
|
|
architectural seams. Whole files, never snippets, because a cut-down file loses
|
|
the call path that makes a claim checkable.
|
|
|
|
The path list is an allowlist, not an exclusion list. A denylist ships whatever
|
|
nobody thought to exclude, and this tree has a database key in it.
|
|
|
|
`architecture-evidence.txt` is the reviewer's index. It resolves a named symbol
|
|
list against the checkout and says plainly when a requested name does not exist.
|
|
It also re-runs the probe under every contradiction, so a claim and its grep
|
|
cannot drift apart.
|
|
|
|
One file is not verbatim. `docker-compose.yml` carries an uptime-kuma API key,
|
|
so a redacted copy ships in its place with that one value replaced. The script
|
|
diffs the two and aborts if anything else changed.
|
|
|
|
The scan at the end refuses to build on a credential-shaped hit rather than
|
|
printing a warning. Both of its first two versions were wrong in instructive
|
|
ways. The name filter deleted `internal/router/singletoken.go` for matching
|
|
`*token*`. The value scan flagged docker volume lines that name where a secret
|
|
would live and contain none.
|
|
|
|
## The authorization function as implemented
|
|
|
|
The reconstruction, at the one decision point that gates an act
|
|
(`internal/tool/tool.go:156`):
|
|
|
|
```
|
|
permit(tool, confirmed) =
|
|
row.status == "enabled" tool.go:164
|
|
AND tier != irreversible risk.go:74 VoiceMayRun:false
|
|
AND (tier == safe OR confirmed) risk.go:72,76
|
|
```
|
|
|
|
`tier` is `RiskOf(row)`. The reach is not an input: `Executor.Exec` takes
|
|
`(ctx, name, args, confirmed)` and no surface.
|
|
|
|
`confirmed` is unproven at this boundary too. The invariant that a confirmation
|
|
binds one capability, one target and an expiry lives in `pendingAct` and
|
|
`resolveConfirm`. `Exec` trusts the boolean.
|
|
|
|
The expression covers two of the three act paths. Hexis reuses it deliberately
|
|
(`cmd/mavend/ecosystem_acts.go:768`). The Praxis lifecycle path has no tier and
|
|
no confirm turn: `praxisItemAction.handle` calls straight through at
|
|
`ecosystem_acts.go:158`.
|
|
|
|
Behind the IPC boundary, `auth.Can(method, scope, params)` runs with
|
|
`scope.Surface` always `SurfaceCoreProcess` (`internal/auth/enrollment.go:65`)
|
|
and step-up held as one global timestamp that ignores `Scope`
|
|
(`internal/webauthn/session.go:38` and `:62`).
|
|
|
|
`auth` answers who may carry what authority. `tool` answers what effect a
|
|
capability has and what proof it demands. Those are orthogonal, not competing.
|
|
The decision combining them does not exist.
|
|
|
|
Two representations of reach exist and both are ignored. `server.go:198` only
|
|
defaults an empty `p.Surface`, so a client-asserted one survives and nothing
|
|
reads it. `server.go:148` hardcodes `Session.Surface`. Since `req.Surface` is
|
|
request payload on an unauthenticated wire, it must not become an authorization
|
|
input as it stands.
|