Every decision is a record; the ledger is gone

papa-hq has no ledger. Its root is AGENTS.md, CLAUDE.md, README.md, every
decision is a numbered record, and its graduation playbook has no path for
an unrecorded decision. hal-hq now matches.

The ledger's 41 entries classified as: 10 restating a record, 11 restating
design docs, 15 describing how this repository works with the reasoning
sitting in a README rather than anywhere citable, 3 small rules with no
home, 2 superseded stubs. Mostly a copy — and a hand-maintained index, the
exact pattern ADR 0022 had just rejected for the decision index on the
grounds it drifted after one addition. Keeping one copy of that while
removing another is not a position. It also collided by name with
02-DECISIONS/ in any directory listing.

Nothing was dropped. Records 0019-0025 give the repository decisions the
reasoning they never had: HQ is its own repository and is public, design
has two layers, work moves through playbooks, status lives in frontmatter,
issues have a front door, the numbering is the flow, HQ is the source of
the constitution. 0026 records the ledger's own removal.

The three orphan rules went to how-we-build, where a rule is enforced and
keeps the incident that earned it — the package rule was genuinely
unwritten anywhere. Two lab decisions stated only in the ledger went into
the lab design. "Deliberately not decided" went to the research effort and
design document each question actually belongs to.

The chronological view the ledger provided is now generated from record
frontmatter, which is what it was for.

The cost, stated in 0026 rather than glossed: a record is more work than a
table row, so the risk is a small decision going unrecorded because nobody
wanted to write a document. how-we-build takes rules cheaply, which is the
mitigation, not a solution.
This commit is contained in:
2026-08-23 18:17:59 +02:00
parent c0b35652d0
commit 3f6d939930
21 changed files with 599 additions and 134 deletions
-1
View File
@@ -27,7 +27,6 @@ and says they disagree.
`status: superseded` and `superseded-by:` — its text is never edited.
2. Edit the design; set `updated:` to today.
3. If this came from an issue, set that issue's `amended-design:`.
4. Add the ledger line to `DECISIONS.md`.
## Amending the as-is layer
-1
View File
@@ -21,7 +21,6 @@ it; this skill adds only the scaffolding.
empty `code:`, today's `updated:`, and `decisions:` pointing at the record.
4. **Close the effort** — the research `00-overview.md` gets `status: graduated` and `became:`
pointing at both.
5. **Add the ledger line** to `DECISIONS.md` under today's heading.
## Amending an existing design
@@ -27,7 +27,7 @@ the claim "HQ is the source" is checked.
3. Publish it to the knowledge base under the constitution slug, replacing the body.
4. **Read it back and verify the change is present.** A publish that reported success and did
nothing is exactly the failure class this repository exists to name — do not skip this.
5. Note the sync in the decision's ledger line.
5. Note the sync in the decision record's Consequences.
## Do not
+4
View File
@@ -70,6 +70,10 @@ reported failure, nothing stopped, and the damage happened somewhere nobody was
declare is invisible to the mesh: it will not be generated, injected, or audited.
- **Provisioned credentials arrive through declared requirements**, never hardcoded in code,
compose files or scripts. [ADR 0005](../02-DECISIONS/0005-capabilities-are-provisioned-on-declaration.md)
- **Never install a package by hand.** A package is declared in the manifest and arrives the
way every other package does. A hand-installed package is invisible to the mesh: it is not
declared, not reproduced on the next node, and not present after a rebuild — and the node
works until it doesn't.
### Placement
+5 -4
View File
@@ -19,7 +19,7 @@ idea ──► 01-RESEARCH ──► decision (02-DECISIONS/) ──► 03-DESIG
│ │ │
│ │ └─► 03-DESIGN/00-as-is once shipped
│ └────► abandoned (recorded, kept)
└─(small/obvious, decision recorded in DECISIONS.md)────► 03-DESIGN directly
└─(small/obvious, still recorded in 02-DECISIONS)──────────► 03-DESIGN directly
symptom ──► 04-ISSUES ──► diagnosis ──► code-repo fix and/or design amendment
@@ -54,6 +54,7 @@ the expensive half.
Research overviews, design docs, issue reports and decision records each carry their status as
YAML frontmatter (schemas in the section READMEs and playbooks). There are **no central status
files**. `DECISIONS.md` is a ledger of decisions as they were taken — an index and a home for
decisions too small to warrant a record — and is explicitly *not* a status board. Cross-cutting
views are generated on demand by the `hal-status` skill and never written to disk.
files** and no decision ledger. **Every decision is a record in
[`02-DECISIONS`](../../02-DECISIONS/)** — if it is worth recording it is worth a record, and if
it is not worth a record it is not recorded. Cross-cutting views, the decision index included,
are generated on demand by the `hal-status` skill and never written to disk.
-3
View File
@@ -27,8 +27,6 @@
4. **Close the effort.** Set the effort's `00-overview.md` frontmatter to `status: graduated` and
`became:` pointing at the design document and the decision record.
5. **Add a ledger line.** Append the decision to [`DECISIONS.md`](../../DECISIONS.md) under
today's heading, pointing at the record.
## Amending an existing design
@@ -39,7 +37,6 @@ A design changes only through a decision.
2. Edit the to-be design document and set `updated:` to today.
3. If the amendment came from an issue, set that issue's `amended-design:` to the document
path.
4. Add the ledger line.
## When something ships
+2 -2
View File
@@ -26,12 +26,12 @@ and, per the repository's own rule, it is how the rule "HQ is the source" is che
cite sections by number.
4. Verify the derived page reads back with the change present. A publish that reported success
and did nothing is exactly the failure class this repository exists to name.
5. Note the sync in the ledger line for the decision.
5. Note the sync in the decision record's Consequences.
## Rules
- **Never edit the derived page directly.** An edit there survives until the next sync and
then vanishes, taking its reasoning with it.
- The derived page may only be **tightened** by per-team override pages, never relaxed.
- If the sync cannot be performed, say so in the ledger line. An unsynced rule is a rule the
- If the sync cannot be performed, say so in the record. An unsynced rule is a rule the
mesh does not enforce, whatever `how-we-build.md` says.
@@ -50,3 +50,18 @@ boundary fault:
## Open questions
Tracked in [`analysis.md`](analysis.md) under "Open questions".
## Deliberately not decided
Recorded so they are not mistaken for oversights. Each is open, and each comes out of
[ADR 0015](../../02-DECISIONS/0015-mesh-brokers-nodes-host-agents-think.md); this effort stays
`active` until they are answered.
| Question | Status |
|---|---|
| `hal/scheduler` — infrastructure, or part of the work context. | Open. |
| Which context owns the executor. | Open. |
| Catalogue destination — one repository or many. | Open. Phase 4. |
| What the shared library keeps after extraction. | Open. Phase 3. |
| Where human agent modality is recorded — which user, on which node, a human agent acts as. | Open. Required by the model; not yet stored. |
| Which domains the modules outside the platform core group into. | Open, from [ADR 0017](../../02-DECISIONS/0017-modules-outside-the-core-are-grouped-by-domain.md), which settles the principle and deliberately not the list. |
@@ -0,0 +1,69 @@
---
status: accepted
date: 2026-08-22
deciders: jochen
reconstructed: false
supersedes: none
---
# 19. HQ is its own repository, and it is public
## Context
The mesh's reasoning — mission, research, design, decisions — began inside the code
repository, under a folder there. The objection to moving it out was specific and good: the
mesh already has an operational memory and a structured archive, and adding a third store
repeats the mistake that consolidation was meant to fix.
## Considered options
1. **Keep it in the code repository.** Rejected, but the objection it rests on is correct and
is answered rather than dismissed — see Consequences.
2. **Put it in the structured archive**, alongside the governed documents. Rejected: the
archive is not reviewable as a diff, and a design argument is exactly the thing that needs
line-by-line review and a branch.
3. **Its own repository.** Chosen.
## Decision
HQ is its own repository, and it is **public** — written for a reader who is not its author
and has no access to the mesh it describes.
Three reasons it is separate:
- **The cadence differs.** A decision changes when thinking changes, not when code changes.
Tying documents to a code branch merges them on the code's schedule.
- **The reviewers differ.** A design argument is not reviewed the way an implementation is,
and should not queue behind a build.
- **The scope is wider than one repository.**
[ADR 0015](0015-mesh-brokers-nodes-host-agents-think.md) sends most modules out of the
monorepo; documentation governing several repositories cannot live inside one of them.
Being public is not incidental. It is enforceable only because
[ADR 0003](0003-the-mesh-database-is-the-source-of-truth.md) made the code repository
node-agnostic: there is no per-node content to leak. Nothing here may carry routable
addresses, real domain names, hosting providers, node names, absolute paths, usernames,
credentials, or operational detail useful only to an attacker.
The test is whether a paragraph would still teach a stranger running an entirely different
mesh.
## Consequences
- A document and the code it describes can no longer land in one commit. Keeping them honest
is a discipline rather than a mechanism — which is why decisions are recorded as they are
taken, and why a document stating a rule must say how the rule is checked.
- Research must state evidence without identifying the mesh it observed. The shape of a
finding survives anonymisation; the instance does not travel.
- **The objection is answered by indexing, not by location** — the claim being that these
documents remain searchable beside everything else, one source with many surfaces.
**That indexing does not exist.** Checked 2026-08-23, it returns nothing. Until it does, the
objection stands unanswered and this repository is the third knowledge store it was argued
not to be. Recorded as
[`04-ISSUES/006`](../04-ISSUES/006-hq-is-not-indexed-into-the-knowledge-base/00-report.md).
## References
- Supersedes the earlier position that documentation lives inside the code repository under a
folder there. That position was never recorded separately and has no record of its own.
- [`README.md`](../README.md) — the public-repository rule in full.
@@ -0,0 +1,67 @@
---
status: accepted
date: 2026-08-23
deciders: jochen
reconstructed: false
---
# 20. Design is written in two layers: what is, and what is intended
## Context
HQ held only the intended mesh. Every reader had to already know the running system in order
to understand what the decisions were about, and a statement about current behaviour had
nowhere to live except inside a document describing an intention.
The consequence was invisible until looked for: an as-is claim inside a to-be document is
indistinguishable from the intention around it, so the document silently stops being true as
the system moves — and nobody can tell which half went stale.
The mesh also has a large body of shipped behaviour that nobody would choose again. It is not
design in the sense of "what we decided"; it is design in the sense of "what is there", and it
is exactly the part a person changing the system most needs.
## Considered options
1. **One layer, describing the target.** Rejected — the status quo. The running system goes
undocumented and the target document accumulates unmarked claims about it.
2. **One layer, describing what runs, with intentions only in decision records.** Rejected:
a decision record is an argument, not a specification, and a multi-part intention has
nowhere coherent to live.
3. **Two layers, declared per document, never mixed.** Chosen.
## Decision
`03-DESIGN` holds two layers, and every document declares which it is:
| Layer | Describes | Written from |
|---|---|---|
| `00-as-is/` | The mesh that exists | The implementation and the operational record |
| `01-to-be/` | The mesh being built toward | Decision records |
An as-is document **records what is, not what should be** — including behaviour nobody would
choose again. A layer that keeps only the good decisions is a brochure.
When a to-be design ships it **does not move**. Its as-is counterpart is written or updated,
the to-be document's status becomes `implemented`, and both stand: one describing what runs,
the other recording what was intended. Deleting the intention loses the reasoning.
Where implementation and intention disagree, the as-is document records the implementation and
says they disagree.
## Consequences
- A reader can tell, from the folder and from one frontmatter field, whether they are reading
a description or a plan. That distinction was previously unavailable at any price.
- Correcting an as-is document requires evidence from the implementation, not agreement — and
needs no decision record, because nothing was decided.
- Two documents must be kept current per subsystem instead of one. This is the cost, and it is
paid on every ship.
- Something that shipped differently from its design becomes a visible divergence rather than
a silently wrong document, and may deserve an issue.
## References
- [`03-DESIGN/README.md`](../03-DESIGN/README.md) — the layer contract and frontmatter schema.
- [`03-DESIGN/00-as-is/`](../03-DESIGN/00-as-is/) — the first eleven as-is documents, written
2026-08-23 from the monorepo and the operational memory.
@@ -0,0 +1,57 @@
---
status: accepted
date: 2026-08-23
deciders: jochen
reconstructed: false
---
# 21. Every workflow is a playbook, and agents operate through them
## Context
HQ stated a knowledge flow — research becomes design — and nowhere stated how anything moves
along it. What graduation required, who wrote the decision, what closed an effort, what
happened when something shipped: all of it was convention held in one person's head.
A large share of the work here is done by agents. An unwritten convention is not available to
an agent at all, so each one either invents a procedure or asks. Both produce a repository
whose shape depends on who last touched it.
## Considered options
1. **Convention, learned by reading existing documents.** Rejected — the status quo. It
transmits shape but not rules, and it transmits the mistakes along with the pattern.
2. **One long contributing document.** Rejected: it is read once, and the step someone needs
is never the step they are reading.
3. **A playbook per workflow, each with trigger, steps and outputs, wrapped by a thin skill.**
Chosen.
## Decision
Every workflow is a playbook in [`00-META/process/`](../00-META/process/): trigger, who runs
it, steps, outputs. Engineers and agents follow the same playbooks, and **agents must not act
outside them**.
Each playbook is wrapped by a thin skill that defers to it as authoritative and adds only
mechanical scaffolding — next free number, frontmatter block, where the file goes. The
playbook holds the reasoning; the skill holds the steps. When they disagree, the playbook
wins.
## Consequences
- An agent arriving with no context can act correctly, because the procedure is retrievable
rather than remembered.
- The playbooks are themselves reviewable. A bad rule can be found and changed, which is not
true of a convention.
- Duplication between playbook and skill is real, and is managed by making the skill thin and
naming the playbook as authoritative in the skill's first lines. Nothing prevents them
drifting; the constraint is that only one carries reasoning.
- A workflow with no playbook is a workflow agents will get wrong. Adding one is part of
adding the workflow.
## References
- [`00-META/process/00-overview.md`](../00-META/process/00-overview.md) — the five playbooks
and the flow they implement.
- Modelled on the process layer in the sibling HQ repository for the PAPA platform, which
arrived at the same shape and the same thin-skill split.
@@ -0,0 +1,54 @@
---
status: accepted
date: 2026-08-23
deciders: jochen
reconstructed: false
---
# 22. Status lives in frontmatter; cross-cutting views are generated
## Context
Status was carried in prose — a bold line near the top of a document saying what state it was
in — and indexes were maintained by hand. The decision-record index had already drifted from
the folder it described **after a single addition**, which is about as short a demonstration
as the failure mode offers.
A hand-maintained index is a copy of something the filesystem already knows. It is correct
only for as long as everyone remembers it exists, and its being wrong is silent.
## Considered options
1. **Prose status plus hand-maintained indexes.** Rejected — the status quo, already
demonstrably broken.
2. **A central status file.** Rejected. It centralises the drift rather than removing it: the
file and the documents disagree, and the file is the one people read.
3. **Machine-readable frontmatter per document; every cross-cutting view generated on
demand.** Chosen.
## Decision
Every document carries its state in YAML frontmatter — research overviews, design documents,
decision records, issue reports — with a schema stated in the section README.
**There are no central status files.** Every cross-cutting view — a status matrix, the
decision-record index, the open-issue list — is generated from frontmatter when asked for, and
never written to disk.
Prose does not restate status. One place, and two is one too many.
## Consequences
- A view cannot drift from what it describes, because it does not persist.
- Status becomes queryable. Inconsistencies — a closed effort with nothing in `became:`, an
`implemented` design with no owning repository — are findable mechanically, and the
generator reports them as flags rather than silently rendering around them.
- Frontmatter must be valid and paths in it must resolve, which is now something to check.
- A reader browsing the repository on a forge sees no index. That is the trade: the index is
correct and absent rather than present and wrong.
## References
- [`.claude/skills/hal-status/SKILL.md`](../.claude/skills/hal-status/SKILL.md) — the
generator, including the inconsistencies it flags.
- [`02-DECISIONS/README.md`](README.md) — the hand-written index that drifted, and its removal.
@@ -0,0 +1,60 @@
---
status: accepted
date: 2026-08-23
deciders: jochen
reconstructed: false
---
# 23. Issues have a front door, separate from the operational memory
## Context
Findings that were nobody's task accumulated in a table inside the decision ledger — a package
install reporting success while installing nothing, a manifest key read by no code, an
end-to-end harness dead for months. They were measured, true, and unowned: a table row cannot
be assigned, diagnosed or closed.
The mesh already has an operational memory holding roughly a hundred and thirty entries,
indexed on symptoms. The obvious move — put these there — is wrong, and the reason is the
distinction worth recording.
## Considered options
1. **Leave them in the ledger.** Rejected: a ledger records decisions taken, and these are
the opposite — questions nobody has answered.
2. **Put them in the operational memory.** Rejected. That store answers *how do I fix this
occurrence*; these are *why does the design allow this at all*. Filing them there makes
them findable by symptom and unfindable as open questions, and nothing there has a state
that can be closed.
3. **A numbered issue folder in HQ, deliberately narrow.** Chosen.
## Decision
`04-ISSUES` is the front door for something wrong at the level of **design or governance**:
a rule enforced by nothing, a stated invariant that is false, a failure the design permits to
be silent, or a symptom whose owner cannot be found without the whole mesh in view.
One numbered folder per issue: the report with the symptom as observed and the evidence, and
a diagnosis document carrying the trail, dated, including what was ruled out.
**This is not a second copy of the operational memory.** An issue here is a question HQ must
*answer*; an entry there is an incident someone must *clear*. An issue whose answer is a
general lesson belongs in both — and the operational memory is searched first, because if the
answer is already there this was never an issue.
## Consequences
- A finding gets a number, a state and an owner, and closing it is a visible act.
- The symptom-to-component trail accumulates in a place where the whole mesh is visible, which
is where cross-component diagnosis has to happen.
- The boundary needs judgement on every report, and will sometimes be got wrong. Filing too
narrowly loses a finding; filing too widely rebuilds the operational memory here, which is
the outcome HQ's separation was argued against
([ADR 0019](0019-hq-is-its-own-repository.md)).
- Six issues opened on creation, all previously unowned observations.
## References
- [`04-ISSUES/README.md`](../04-ISSUES/README.md) — the boundary table and the frontmatter
schema.
- [`00-META/process/03-issues.md`](../00-META/process/03-issues.md) — the playbook.
@@ -0,0 +1,76 @@
---
status: accepted
date: 2026-08-23
deciders: jochen
reconstructed: false
---
# 24. The folder numbering is the flow, and decision records run oldest first
## Context
Two orderings were wrong in ways that only show up when someone new reads the repository.
**The folders.** Decisions lived in an unnumbered folder that sorted after the numbered ones,
so the repository's most load-bearing content read as an annex.
The sibling HQ repository for the PAPA platform had already solved this and solved it
crookedly: its design folder existed from its initial commit, and when its decision folder was
finally promoted it took the **next free number** rather than its place in the sequence. That
repository now reads `01 research → 03 decision → 02 design`. A decision precedes the design
it authorises and is numbered after it. By the time this was visible, the design folder was too
settled to renumber.
**The records.** Fourteen decisions had been taken in implementation and never written down —
the broker, the module abstraction, the mesh database, the artifact, the three silos and the
rest. Meanwhile two records existed, holding numbers 0001 and 0002, for decisions taken last.
## Considered options
1. **Match the sibling repository exactly**, inheriting its ordering. Rejected: structural
parity is worth something, but not the cost of copying a scar the other repository would
not choose again.
2. **Leave the folder unnumbered.** Rejected — the annex problem, and it leaves an unexplained
gap for anyone arriving from the sibling repository.
3. **Number by position in the flow, and renumber the records chronologically.** Chosen, on
the grounds that this repository was four commits old and nothing outside it cited a
number. That is the only window in which either renumbering is free.
## Decision
**The numbering is the flow.** Research produces a decision; the decision authorises a design.
So `01-RESEARCH`, `02-DECISIONS`, `03-DESIGN`, `04-ISSUES`. Following the folder numbers walks
the process in the order it happens.
**Decision records are a chronological ledger.** They run oldest first. The fourteen decisions
already taken in implementation were back-filled as records 0001–0014, each dated from the
history, each carrying `reconstructed: true` and saying so in its first lines, and each citing
the commit, pull request or knowledge-base entry it was recovered from. The two existing
records moved to 0015 and 0016.
A reconstructed record is not a transcript. Where the deliberation is not recoverable it states
what the alternatives were and why the chosen one won on the evidence available — not a
discussion that did not happen. Where a date is not establishable it says so.
The foundational folder is `00-META`, matching the sibling repository.
## Consequences
- The repository reads in process order, and the gap at `03` that a reader coming from the
sibling repository would notice is explained by this record.
- The design documents can cite reasoning instead of asserting rules, because the reasoning now
exists.
- Structural divergence from the sibling repository, deliberately, in exactly one place. It is
recorded here so that the difference reads as a choice rather than an accident.
- **Record numbers are now stable and renumbering is over.** This decision spends the one
window that existed; a future record takes the next free number regardless of its date.
- Reconstructed records carry a standing risk: they are the most confident-sounding documents
in the repository and the least directly witnessed. The `reconstructed` flag exists so that
is never invisible.
## References
- The sibling repository's restructure of 2026-07-13 moved its decision folder in a single
commit of twelve renames with no content change, alongside the same status-into-frontmatter
and playbook changes made here.
- [`02-DECISIONS/README.md`](README.md) — the format, and the note on reconstructed records.
@@ -0,0 +1,67 @@
---
status: accepted
date: 2026-08-23
deciders: jochen
reconstructed: false
---
# 25. HQ is the source of the mesh constitution
## Context
[ADR 0009](0009-the-mesh-is-governed-by-a-constitution.md) established a canonical rule set,
injected into every eligible design session and checked before output is accepted. It lives in
the knowledge base, where the orchestrator reads it.
HQ separately carried a document stating overlapping rules with the reasoning that earned each
one. Two texts, one enforced and one not.
That arrangement has a predictable outcome and it is not a tie. The enforced copy wins by
default, because it is the one that blocks work. The reasoned copy quietly stops being true,
and the rules survive without the incidents that justify them — at which point a rule reads as
arbitrary, and an arbitrary rule is the kind people route around.
## Considered options
1. **The knowledge-base page is the source; HQ points at it.** Rejected, though it is the
honest description of what was already happening. It leaves the reasoning downstream of the
rule, and the reasoning is the part that makes a rule survive a challenge.
2. **Accept the overlap and let both stand.** Rejected: two authorities is no authority, and
the drift is silent.
3. **HQ is the source; the governed page is derived and published from it.** Chosen.
## Decision
[`00-META/how-we-build.md`](../00-META/how-we-build.md) is the source. The governed page the
mesh injects is **derived** from it — the rules without the reasoning — and is never edited
directly.
Publishing is a playbook step, not a manual act, and it ends with **reading the page back and
verifying the change is present**. A publish that reported success and did nothing is exactly
the failure class this mesh keeps producing
([ADR 0008](0008-a-failed-step-fails-the-job.md)).
Section numbering is stable, because the orchestrator and the review fragments cite sections by
number.
## Consequences
- One source, many surfaces — the same argument HQ's separation already rests on
([ADR 0019](0019-hq-is-its-own-repository.md)), applied to the rules themselves.
- Each rule keeps the incident that earned it, in a place that is reviewed as a diff.
- An edit to the derived page survives until the next sync and then vanishes. The playbook says
so, and nothing mechanically prevents it.
- **The sync is manual and is the weak point.** An unsynced rule is a rule the mesh does not
enforce, whatever the source says — so the playbook requires the failure to be stated rather
than passed over. This is the same class of gap as
[`04-ISSUES/006`](../04-ISSUES/006-hq-is-not-indexed-into-the-knowledge-base/00-report.md),
and it is worth watching for the same reason.
- The document grew from four rules to seven sections, because it now has to carry everything
the mesh enforces rather than only what someone thought to write down.
## References
- [`00-META/process/05-constitution-sync.md`](../00-META/process/05-constitution-sync.md) —
the sync, including the read-back.
- [ADR 0009](0009-the-mesh-is-governed-by-a-constitution.md) — the governed page and why it
exists.
@@ -0,0 +1,89 @@
---
status: accepted
date: 2026-08-23
deciders: jochen
reconstructed: false
---
# 26. Every decision is a record; there is no ledger
## Context
HQ carried a decision ledger at its root: a chronological table of forty-one numbered
decisions, each with who decided and a pointer to where the reasoning lived. It was created
deliberately, to make decisions findable and to give a home to decisions too small to warrant
a document.
By the time the decision records were back-filled
([ADR 0024](0024-the-numbering-is-the-flow.md)) the ledger had become three things at once,
and only one of them was still needed.
Classified, its forty-one entries were: ten restating a record, eleven restating design
documents, fifteen describing how this repository works — with the reasoning in a README rather
than anywhere citable — three small rules with no home at all, and two superseded stubs.
So the ledger was mostly a copy. Worse, it was a **hand-maintained index**, which
[ADR 0022](0022-status-lives-in-frontmatter.md) had just finished rejecting for the
decision-record index on the grounds that it had drifted after a single addition. Keeping one
copy of that pattern while removing another is not a position.
It had also produced a naming collision that a directory listing makes plain: `DECISIONS.md`
beside `02-DECISIONS/`, holding different things.
## Considered options
1. **Keep the ledger.** Rejected. It duplicates the records, restates status, and is the exact
hand-maintained index this repository decided against elsewhere.
2. **Keep it, renamed, for small decisions only.** Rejected, and this is the option worth
arguing with — it is genuinely useful to record a decision without writing a document. But a
decision small enough to be one table row is almost always a **rule** rather than a
decision, and a rule belongs in [`how-we-build.md`](../00-META/how-we-build.md) where it is
enforced and where its reasoning is kept. That is where the three orphans went.
3. **Every decision is a record; nothing else.** Chosen. This is how the sibling HQ repository
for the PAPA platform works, and it has no ledger of any kind.
## Decision
**If a decision is worth recording, it is worth a record. If it is not worth a record, it is
not recorded.**
`02-DECISIONS` holds every decision. There is no ledger, no index file, and no central status
of any kind. The chronological view — decisions in the order they were taken — is *generated*
from record frontmatter, which is what the ledger was actually for.
Content that was only in the ledger was rehomed rather than dropped:
| Was | Went to |
|---|---|
| Decisions about how this repository works | Records [0019](0019-hq-is-its-own-repository.md)–[0025](0025-hq-is-the-source-of-the-constitution.md) |
| Small rules with no record | [`how-we-build.md`](../00-META/how-we-build.md) — the package rule, and two already there |
| Lab decisions not stated in the design | [`03-DESIGN/01-to-be/01-end-to-end-testing.md`](../03-DESIGN/01-to-be/01-end-to-end-testing.md) |
| "Deliberately not decided" | The research effort and design document each question belongs to |
| Unowned observations | [`04-ISSUES`](../04-ISSUES/) ([ADR 0023](0023-issues-have-a-front-door.md)) |
## Consequences
- One place to look, and nothing to keep in sync. The collision between the ledger and the
record folder is gone.
- Structural parity with the sibling repository on decisions, which
[ADR 0024](0024-the-numbering-is-the-flow.md) deliberately broke on folder numbering. The
divergence is now exactly one thing, and it is the one thing that was argued for.
- **Writing a record is now the only way to record a decision, and a record is more work than
a table row.** The real risk is that a small decision goes unrecorded because nobody wanted
to write a document. The mitigation is that a small decision is usually a rule, and
`how-we-build.md` takes rules cheaply — but this is a cost, not a solved problem, and it is
the thing to watch.
- The chronological view now depends on the generator existing and being run. It did not
before.
- Two superseded ledger stubs had no record of their own. The position that documentation
lives inside the code repository is now recorded only as superseded context in
[ADR 0019](0019-hq-is-its-own-repository.md); the system-container position is explained in
[ADR 0016](0016-a-lab-node-is-a-virtual-machine.md). Neither is lost.
## References
- The sibling PAPA HQ repository: root holds only agent instructions and a README; every
decision is a numbered record, and its graduation playbook has no path for an unrecorded
decision.
- [ADR 0022](0022-status-lives-in-frontmatter.md) — the hand-maintained-index argument this
applies consistently.
+8 -1
View File
@@ -11,7 +11,14 @@ One file per decision, numbered, never deleted. A superseded record has its `sta
and gains a pointer to what replaced it — **its text is never edited**. The reasoning that was
rejected is the expensive half to rediscover.
The records are a **ledger**: they run in the order the decisions were taken, oldest first.
The records run in the order the decisions were taken, oldest first.
**Every decision is a record.** There is no ledger and no index file — if a decision is worth
recording it is worth a record, and if it is not worth a record it is not recorded
([ADR 0026](0026-every-decision-is-a-record.md)). A "decision" small enough to be one line is
almost always a **rule**, and a rule belongs in
[`00-META/how-we-build.md`](../00-META/how-we-build.md), where it is enforced and keeps the
incident that earned it.
## Frontmatter
+2 -2
View File
@@ -68,8 +68,8 @@ many surfaces.
**That indexing does not currently exist.** A search for this repository's content returns
nothing. The claim is load-bearing for the decision to separate the repository at all, and
until it is true, this repository is exactly the fourth knowledge system the objection
described. Recorded here rather than in the ledger, because it is a statement about how the
mesh's knowledge actually works today.
described. Recorded here because it is a statement about how the mesh's knowledge actually
works today, and as [`04-ISSUES/006`](../../04-ISSUES/006-hq-is-not-indexed-into-the-knowledge-base/00-report.md).
## The librarian
@@ -18,6 +18,18 @@ This is the Phase 0 prerequisite from [`00-work-breakdown.md`](00-work-breakdown
---
## Two boundaries this design sets
**Adoption is out of scope.** Bringing a node into being is the lab runner's job. Adopting a
machine that already exists, with its own configuration, is a legacy path and the lab does not
reproduce it — a scenario starts from nothing every time, which is what makes it a fixture
rather than a snapshot.
**The real topology is one test among many, not the baseline.** A scenario declares the mesh
its question needs. If something can only be tested against the shape the mesh happens to have
today, that is a gap in the vocabulary rather than a reason to privilege that shape.
## Where this sits in the way work happens
Work reaches the mesh along one path today:
@@ -361,3 +373,9 @@ is, a workstation stops being collateral.
[`01-RESEARCH/003-service-supervision`](../../01-RESEARCH/003-service-supervision/analysis.md)
remains open on its own merits — and once this exists, its options are cheap to try rather
than expensive to argue about.
## Deliberately not decided
**Whether the lab verdict is a workflow guard or an advisory check on the pull request.** Open,
and deliberately trivial — a policy detail, changeable in an afternoon, not an architectural
choice. Recorded so it is not mistaken for an oversight.
-114
View File
@@ -1,114 +0,0 @@
# Decision ledger
Every decision, in the order it was taken. Append-only — a decision that stops being true is
marked superseded and left in place, because the reasoning that was rejected is the expensive
half to rediscover.
An entry here is a **record**, not the reasoning. Anything architecturally significant carries
its full context, options and consequences in an [ADR](02-DECISIONS/); anything still being worked out
lives in [`01-RESEARCH`](01-RESEARCH/). This file is the index that makes both findable, and
the place small decisions live that never warrant a document of their own.
**Columns.** *Decided* is who made the call. *Where* points at the reasoning. A decision with
no pointer is one small enough that this line is the whole record.
---
## 2026-08-22 — decomposition
| # | Decision | Decided | Where |
|---|---|---|---|
| 1 | The mesh brokers capabilities; nodes host; agents think. Eight bounded contexts replace 33 platform modules. | jochen | [ADR 0015](02-DECISIONS/0015-mesh-brokers-nodes-host-agents-think.md) |
| 2 | Nodes and agents decouple — a node holds no licence; an agent holds credentials and delivery follows its bindings and modality. | jochen | [ADR 0015](02-DECISIONS/0015-mesh-brokers-nodes-host-agents-think.md) |
| 3 | noxflow dissolves; `hal/work` inherits tasks and workflows. | jochen | [ADR 0015](02-DECISIONS/0015-mesh-brokers-nodes-host-agents-think.md) |
| 4 | Third-party modules leave this repository — they run *on* the mesh, not *of* it. | jochen | [ADR 0015](02-DECISIONS/0015-mesh-brokers-nodes-host-agents-think.md) |
| 5 | ~~Documentation lives inside the code repository under `hq/`.~~ | jochen | **Superseded by #27** |
## 2026-08-22 — the lab
| # | Decision | Decided | Where |
|---|---|---|---|
| 6 | Phase 0 is a **development environment**, not a fixture rig — it is what the host-borrowing dev tooling becomes. | jochen | [002](01-RESEARCH/002-local-mesh/00-overview.md) |
| 7 | The delivery trigger is a **real forge** inside the lab, not a synthetic event — the webhook relay is part of what is under test. | jochen | [002](01-RESEARCH/002-local-mesh/00-overview.md) |
| 8 | ~~A lab node is a **system container**, promotable to a virtual machine.~~ | jochen | **Superseded by #10** |
| 9 | Adoption is a legacy path and is out of scope. Bringing nodes into being is the lab runner's job instead. | jochen | [design](03-DESIGN/01-to-be/01-end-to-end-testing.md) |
| 10 | A lab node is a **virtual machine** running the real install. Supersedes #8: the scale argument for system containers was invented rather than required, and a virtual machine dissolves the fidelity question instead of answering it. | jochen | [ADR 0016](02-DECISIONS/0016-a-lab-node-is-a-virtual-machine.md) |
| 11 | The environment is called **the lab**. | jochen | — |
| 12 | The lab is driven by `incus` — for virtual machines, snapshots and bridges through one interface, and because it also runs system containers if a scale run is ever needed. | jochen | [ADR 0016](02-DECISIONS/0016-a-lab-node-is-a-virtual-machine.md) |
| 13 | The simulated public segment uses **TEST-NET-3** (`203.0.113.0/24`). Not cosmetic: an RFC1918 public segment makes the hub test as unreachable and the mesh silently never forms. | — | [ADR 0016](02-DECISIONS/0016-a-lab-node-is-a-virtual-machine.md), [004](01-RESEARCH/004-lab-network/analysis.md) |
| 14 | The lab **issues its own certificates**, keeping production's two-authority split rather than collapsing it. | jochen | [ADR 0016](02-DECISIONS/0016-a-lab-node-is-a-virtual-machine.md) |
## 2026-08-22 — what the lab is for
| # | Decision | Decided | Where |
|---|---|---|---|
| 15 | **The module is what is under test; the mesh is the harness.** The loop is: change a module, run it end to end, get a verdict. | jochen | [design](03-DESIGN/01-to-be/01-end-to-end-testing.md) |
| 16 | **Nothing new drives delivery.** A lab mesh has its own coordinator; the pipeline that runs is the real one. A second delivery path would be blind to exactly the faults worth catching. | jochen | [design](03-DESIGN/01-to-be/01-end-to-end-testing.md) |
| 17 | A **test runner** is a legitimate component *used by* the coordinator — lab lifecycle and assertion execution. The line is the pipeline: a runner that decides what to build is a fork of the coordinator. | jochen | [design](03-DESIGN/01-to-be/01-end-to-end-testing.md) |
| 18 | The runner has **two callers** — the coordinator, and a person developing the mesh — so it needs both a run-to-verdict verb and a leave-it-standing verb. | jochen | [design](03-DESIGN/01-to-be/01-end-to-end-testing.md) |
| 19 | **A module carries its own assertions**, stated once, in the verification stage the coordinator already dispatches. Running them where failing is free is what makes writing them worth doing. | jochen | [design](03-DESIGN/01-to-be/01-end-to-end-testing.md) |
| 20 | A test declares the mesh it needs; **size ranges from one node upward**, chosen by the question rather than by what the mesh happens to have. | jochen | [design](03-DESIGN/01-to-be/01-end-to-end-testing.md) |
| 21 | The real topology is **one test among many**, not the baseline. Anything only testable there is a gap in the vocabulary. | jochen | [design](03-DESIGN/01-to-be/01-end-to-end-testing.md) |
## 2026-08-22 — working agreements
| # | Decision | Decided | Where |
|---|---|---|---|
| 22 | **Never install packages by hand.** A package is declared in the module manifest and arrives the way every other package does. | jochen | — |
| 23 | **Never open a pull request unprompted.** A permissions list saying it is allowed is not a request. | jochen | — |
| 24 | **Every merge is a human checkpoint**, without exception. | jochen | [`03-DESIGN/01-to-be/00-work-breakdown.md`](03-DESIGN/01-to-be/00-work-breakdown.md) |
| 25 | Work in an **isolated worktree**, never a shared checkout. | jochen | [`how-we-build`](00-META/how-we-build.md) §2 |
| 26 | Decisions are recorded **here**, thoroughly, as they are taken. | jochen | this file |
| 27 | HQ is **its own repository**, `hal-hq`. Supersedes #5: the original objection was to a fourth knowledge *system*, which indexing answers rather than location. Cadence, reviewers, and a scope wider than one repository all argue for separation. | jochen | [`README`](README.md) |
| 28 | **This repository is public.** Written for a reader who is not its author and has no access to the mesh it describes. No routable addresses, real domains, hosting providers, node names, absolute paths, or operational detail useful only to an attacker. Supersedes the previous rule that research may name instances. | jochen | [`README`](README.md) |
---
## 2026-08-23 — how this repository works
| # | Decision | Decided | Where |
|---|---|---|---|
| 29 | Design splits into two layers that are never mixed — **`00-as-is` describes the mesh that exists, `01-to-be` the one being built toward**. A design that ships does not move; its as-is counterpart is written and both stand. | jochen | [`03-DESIGN/README`](03-DESIGN/README.md) |
| 30 | The as-is layer is written **from the implementation and the operational record**, not from intent, and records shipped behaviour nobody would choose again. An as-is layer that keeps only the good decisions is a brochure. | jochen | [`03-DESIGN/00-as-is/README`](03-DESIGN/00-as-is/README.md) |
| 31 | **HQ is the source of the mesh constitution.** The governed page injected into design sessions is derived from `how-we-build.md` and never edited directly. Same one-source-many-surfaces argument as #27. | jochen | [playbook 05](00-META/process/05-constitution-sync.md) |
| 32 | Every workflow is a **playbook** in `00-META/process/`, wrapped by a thin skill that defers to it. Agents operate through the playbooks and not outside them. | jochen | [`process/00-overview`](00-META/process/00-overview.md) |
| 33 | Issues get a **front door**, `04-ISSUES` — design-level and governance faults only. Operational lessons stay in the knowledge base, which is indexed on symptoms. This repository is not a second copy of it. | jochen | [`04-ISSUES/README`](04-ISSUES/README.md) |
| 34 | Decision records are a **chronological ledger**. Fourteen decisions already taken in implementation were back-filled as records 0001–0014, each marked `reconstructed` and carrying its evidence; the two existing records renumbered to 0015 and 0016. | jochen | [`02-DECISIONS/README`](02-DECISIONS/README.md) |
| 35 | **Status lives in frontmatter.** No central status files; cross-cutting views are generated on demand. This file is a ledger of decisions as taken — an index, and the home of decisions too small to warrant a record — and explicitly not a status board. | jochen | [`process/00-overview`](00-META/process/00-overview.md) |
| 36 | The ADR index is **generated, not maintained**. The hand-written one had already drifted after a single addition. | jochen | [`02-DECISIONS/README`](02-DECISIONS/README.md) |
| 37 | Modules outside the platform core are grouped **by domain, not by single function**, extending #1. The principle is settled; the domain list deliberately is not. | jochen | [ADR 0017](02-DECISIONS/0017-modules-outside-the-core-are-grouped-by-domain.md) |
| 38 | **The mesh creates no symlinks at all** — not by hand, and not by the installer. Centralising who may link narrowed the incident class without closing it, and a derived file is a copy by nature. The position is settled; the staleness mechanism is not. | jochen | [ADR 0018](02-DECISIONS/0018-the-mesh-creates-no-symlinks.md) |
| 39 | Research efforts follow the **`00-overview.md`** convention, with `active` / `graduated` / `abandoned` in frontmatter — the same shape papa-hq uses, so the two repositories read alike. | jochen | [`01-RESEARCH/README`](01-RESEARCH/README.md) |
| 40 | **The numbering is the flow.** Decisions become `02-DECISIONS` and design becomes `03-DESIGN`, so a reader following the folder numbers walks the process in the order it happens — research, decision, design. papa-hq numbers these the other way round because `02-DESIGN` predated the promotion of `adr/`, which then took the next free number rather than its place in the sequence; that repository was too settled to renumber and this one was not. | jochen | [`02-DECISIONS/README`](02-DECISIONS/README.md) |
| 41 | `00-GENESIS` is renamed **`00-META`**, matching papa-hq. | jochen | [`00-META/README`](00-META/README.md) |
---
## Observations
Things established by measurement that no decision has yet answered now live in
[`04-ISSUES`](04-ISSUES/), one numbered folder each, so they can be diagnosed, owned and
closed rather than accumulating in a table nobody can resolve.
| Issue | What it means |
|---|---|
| [001](04-ISSUES/001-failed-package-install-reports-success/00-report.md) | A failed package install does not fail the job. The first thing the lab was asked to install demonstrated the exact fault the lab exists to catch. |
| [002](04-ISSUES/002-stale-package-index-fails-silently/00-report.md) | A declared package can fail purely because the node's index is old, and today that failure is silent. |
| [003](04-ISSUES/003-firewall-scope-is-read-by-no-code/00-report.md) | A manifest can appear to restrict a port and restrict nothing. |
| [004](04-ISSUES/004-certificate-issuance-targets-production/00-report.md) | Every certificate experiment on a real node consumes production issuance quota. |
| [005](04-ISSUES/005-pipeline-test-harness-unbuildable/00-report.md) | The repository's only end-to-end pipeline test has been silently dead since 2026-06-04. |
| [006](04-ISSUES/006-hq-is-not-indexed-into-the-knowledge-base/00-report.md) | This repository is not indexed into the knowledge base — the claim that holds up decision 27. |
## Deliberately not decided
Recorded so they are not mistaken for oversights.
| Question | Status |
|---|---|
| Which supervision model the mesh adopts — keep the host init system, drop the redundant per-module layer, containerise the daemons, or write a supervisor. | Open. Options costed in [003](01-RESEARCH/003-service-supervision/analysis.md). **No longer gates the lab.** |
| Whether the lab verdict is a workflow guard or an advisory check on the pull request. | Open, and deliberately trivial — a policy detail, changeable in an afternoon, not an architectural choice. |
| `hal/scheduler` — infrastructure or part of `hal/work`. | Open, from ADR 0015. |
| Which context owns the executor. | Open, from ADR 0015. |
| Catalogue destination — one repository or many. | Open, from ADR 0015. Phase 4. |
| What `hal/sdk` keeps after extraction. | Open, from ADR 0015. Phase 3. |
| Where human agent modality is recorded — which user, on which node, a human agent acts as. | Open, from ADR 0015. Required by the model; not yet stored. |
+5 -5
View File
@@ -12,7 +12,6 @@ why. Implementation lives in `modules/`; the reasoning behind it lives here.
| [`02-DECISIONS`](02-DECISIONS/) | Numbered decision records, in the order the decisions were taken — what was chosen, and what was rejected. |
| [`03-DESIGN`](03-DESIGN/) | The authoritative specification, in two layers: [`00-as-is`](03-DESIGN/00-as-is/) — the mesh that exists — and [`01-to-be`](03-DESIGN/01-to-be/) — the one being built toward. |
| [`04-ISSUES`](04-ISSUES/) | The front door for "something is wrong" at the level of design or governance. |
| [`DECISIONS.md`](DECISIONS.md) | The ledger: every decision as it was taken, indexing the records and holding the ones too small to warrant one. |
**The numbering is the flow.** Research produces a decision; the decision authorises a design.
That is why decisions are `02` and design is `03` — a reader following the numbers walks the
@@ -25,7 +24,7 @@ idea ──► 01-RESEARCH ──► decision (02-DECISIONS/) ──► 03-DESIG
│ │ │
│ │ └─► 03-DESIGN/00-as-is once shipped
│ └────► abandoned (recorded, kept)
└─(small/obvious, decision recorded in DECISIONS.md)────► 03-DESIGN directly
└─(small/obvious, still recorded in 02-DECISIONS)──────────► 03-DESIGN directly
symptom ──► 04-ISSUES ──► diagnosis ──► code-repo fix and/or design amendment
@@ -107,7 +106,8 @@ Answered separately, a repository of its own is the better home:
The trade is real and worth naming: a change to a document and the change to the code it
describes can no longer land in one commit. Keeping them honest is a discipline now rather
than a mechanism — which is why [`DECISIONS.md`](DECISIONS.md) records decisions as they are
taken, and why a document that states a rule should say how the rule is checked.
than a mechanism — which is why every decision is recorded in
[`02-DECISIONS`](02-DECISIONS/) as it is taken, and why a document that states a rule should
say how the rule is checked.
Recorded as decision 27 in [`DECISIONS.md`](DECISIONS.md).
Recorded as [ADR 0019](02-DECISIONS/0019-hq-is-its-own-repository.md).