Skip to content

Commit dc3a763

Browse files
wshallwshallclaude
andauthored
docs: add a role-keyed docs index, and record the shipped breaking change (#90)
* docs: add a role-keyed docs index, and record the shipped breaking change Two gaps, both of which make a reader draw a wrong conclusion. docs/README.md — there are 377 markdown files under docs/ and no index, so GitHub rendered no docs landing page and the root README pointed at five ad-hoc links, three of them maintainer-facing. There was no operator entry point at all; the inventory that opened this effort named that the highest-leverage gap. The index is organised by AUDIENCE rather than by directory, because this repo's layout does not track audience and its filenames actively mislead: CI.md and ADOPTER-CI.md describe different repositories for opposite readers; ASVS-L2-PHASE0-CHANGES.md is operator notes; testing/VERIFY.md is an operator tool, not a test plan. Where a name misleads, the index says so instead of repeating it. Three things it does deliberately: * Leads with a six-step start-here path for a new operator, in order, with one line of "why now" each. A table of 84 rows does not close a missing entry point no matter how good the rows are. * Disambiguates the two SECURITY.md files up front. .github/SECURITY.md is the vulnerability-disclosure policy; docs/SECURITY.md is the auth/RBAC reference. Same filename, different jobs, and getting it wrong sends a vulnerability report to the wrong place. * Quarantines the planning history rather than hiding it. ~160 of the 377 files are dated build plans, session handoffs and measurement records. Each directory gets one row with a file count and an explicit "do not follow as instructions" banner, so the omission is VISIBLE rather than silent. Retired and stale documents (the PySide6 console guides, the de-registered self-hosted runners, a June-2026 CI summary whose counts have moved) are labelled where they sit. Every one of the 72 links was checked to resolve. Two rows in the first draft pointed at files that do not exist — docs/threat-model.md and docs/TODO.md — and were replaced. The threat model turns out to be deliberately unpublished, which SECURITY-DOCS-POLICY.md explains, so the index now says that rather than dangling a link at it. CHANGELOG.md — PR #85 shipped a breaking change with no release note. An instance carrying an inert [[alerts.rules]] block that routes to an unconfigured transport now refuses to start. The entry states the exact triggering condition (including the half-filled [alerts] block missing email_to, which is the shape that looks configured but is not) and the mitigating fact that decides it: in that state the rule has never routed a single alert, so the refusal removes no working behaviour. The egress-gate widening and the DICOM refusal-message correction from the same PR are recorded alongside it; neither had a note either. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs: drop the frozen v0.1.0 .docx snapshot of the mental model docs/MessageFoundry-Mental-Model.docx was a Word snapshot of what is now docs/MENTAL-MODEL.md. The markdown is the living document — its masthead reads "v0.3.2 · prepared 2026-06-18, revised 2026-07-30". The .docx masthead still read "v0.1.0 (Early Access) · prepared 2026-06-18" and had not been touched since 2026-07-06, two minor versions behind. It is not a duplicate in the harmless sense. Of its 216 substantial paragraphs only 114 survive verbatim in the markdown; the rest is not extra information but SUPERSEDED phrasing — including a store description that still frames SQLite as the message store with Postgres as a parenthetical, which stopped being accurate when the three backends reached parity. That is the case for removing it rather than regenerating it. A binary in a public repository cannot be diffed or reviewed, so it drifts silently and no gate can catch it: nothing referenced it, and nothing would have noticed it going stale. The rendered PDF the website serves is generated from the markdown, so no reader loses anything. The blob remains in git history if the old wording is ever wanted. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 304a93b commit dc3a763

4 files changed

Lines changed: 209 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,60 @@ All notable changes to MessageFoundry are documented here. The format follows
66

77
## [Unreleased]
88

9+
### Changed
10+
- **BREAKING — an `[[alerts.rules]]` block that routes to an unconfigured transport now refuses at
11+
startup instead of being silently ignored.** `notifier_from_settings` returned early when **no**
12+
transport was configured, *before* the loop that cross-checks each rule's `transports` against the
13+
ones that exist. So the fail-loud guarantee held everywhere except the state an operator is most
14+
likely to be in while first setting alerts up: with one transport configured, a rule naming a
15+
different one was a hard `ValueError` at startup; with **zero** configured, the identical rule was
16+
accepted and then **never applied**, and nothing said so. Validation now runs first.
17+
**Who this bites:** an instance where `[[alerts.rules]]` exist AND at least one rule or escalation
18+
tier sets a non-empty `transports` AND no transport is actually configured (`webhook_url` unset, and
19+
**not all three** of `email_smtp_host` + `email_from` + `email_to` set — the email transport needs
20+
all three, which is what makes a half-filled `[alerts]` block look configured). Such an instance
21+
starts today and will refuse after upgrading.
22+
**Why this is safe to take:** in that state the rule has **never routed a single alert**. The
23+
refusal removes no working behaviour — it converts a permanent silent no-op into a startup error
24+
that names the exact keys to add. A rule that names **no** transport is unaffected and still starts
25+
(now with a warning when rules exist that cannot notify anyone), so the ordinary "write the rules
26+
first, wire the transport later" flow keeps working.
27+
The same cross-check now runs at **authoring** time: `messagefoundry alert add` (which the VS Code
28+
"New Alert" command shells) refuses a rule routing to an unconfigured transport rather than
29+
persisting a file that only fails at the next boot. It is scoped to the rule being added, so a file
30+
that already contains a bad rule can still be repaired with `alert remove`.
31+
- **A mail-only or Direct-only PHI instance can now satisfy the open-egress startup gate by declaring
32+
its destinations.** `[egress]` has eight `allowed_*` lists and all eight are enforced downstream,
33+
but the startup gate hand-enumerated six: `allowed_smtp` and `allowed_direct` were absent, so an
34+
instance whose only egress is `Email()` or `Direct()` exited 2 with *"outbound egress is
35+
UNRESTRICTED"* while holding a fully-enumerated allow-list, and nothing in the message named the two
36+
lists that did not count. The two are now counted — deliberately **only** when
37+
`[security].block_unlisted_outbound` is left unset, which is exactly the state the deny-by-default
38+
flip turns ON, so such an instance starts **fail-closed**. An instance that explicitly set
39+
`block_unlisted_outbound = false` is unchanged and still refused, because there the other six
40+
transports stay allow-any; the refusal now names that override as the reason. **No shipped refusal
41+
stops firing.**
42+
943
### Fixed
44+
- **The DICOM C-STORE SCP's fail-closed refusal named a settings key that does not exist.** It told
45+
the operator to set `[inbound].source_ip_allowlist`; `InboundSettings` has no such field and section
46+
models ignore unknown keys, so an operator following the engine's **own error message** wrote a key
47+
into `messagefoundry.toml` that was accepted and silently discarded — leaving a non-loopback SCP
48+
with no peer-IP gate while believing it had one. Aggravated by the construction gate *counting*
49+
controls: a `calling_ae_allowlist` (a caller-asserted AE Title with no cryptographic binding) plus
50+
the discarded key passed the check. The message now names the working surface — the `inbound(...)`
51+
keyword, which for a DICOM SCP is the **only** one, since `DICOM()` is not authorable in
52+
`connections.toml` — and distinguishes it from the `connections.toml` `[[inbound]]` key that *is*
53+
real, so a site running MLLP alongside DICOM cannot read it as licence to delete a working
54+
allowlist. The same wrong spelling is corrected in the module docstring, the gate comment,
55+
`config/wiring.py`, `config/settings.py`, `docs/SECURITY.md` and `docs/ASVS-L2-PHASE0-CHANGES.md`.
56+
Whether an AE-title list alone should keep satisfying that gate is tracked as **BACKLOG #252** — it
57+
is an ADR 0025 §9 contract change and is deliberately **not** decided here.
58+
- **Two startup gates described themselves against the deployment tier rather than the enforcement
59+
dial.** Comments on the managed-identity and security-notification gates read "refuse (production) /
60+
warn (non-production)" over branches that read `enforcing` — and `enforce` is the shipped default on
61+
`dev` and `staging` as much as on `prod`, so all three refuse. Comment-only, no behaviour change,
62+
but these are the comments two published documentation defects were copied from.
1063
- **The load harness's no-loss reconcile did not enforce the `read >= sent // 2` intake guarantee
1164
0.3.2 documented.** The unconfirmed-send excusal is capped at `max(connections, half the run)`, but
1265
that `max()` takes the connection count as a *floor*, and every call site passes a connection count

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,11 @@ Full documentation lives on **[messagefoundry.org](https://messagefoundry.org/)*
8282
[Guides](https://messagefoundry.org/guides/) ·
8383
[Documents](https://messagefoundry.org/documents.html)
8484

85+
**In this repository:** [`docs/README.md`](https://github.com/MEFORORG/MessageFoundry/blob/main/docs/README.md)
86+
is the index for the 377 documents under `docs/`. It opens with a six-step **start-here path for a new
87+
operator**, then splits the rest by audience — operators, developers, security reviewers, maintainers —
88+
and quarantines the dated planning artifacts so you do not mistake a superseded plan for instructions.
89+
8590
## Installing & rolling out
8691

8792
**The recommended way to deploy MessageFoundry is to install the published package from
-63.6 KB
Binary file not shown.

docs/README.md

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
# MessageFoundry documentation
2+
3+
There are **377 markdown files** under `docs/`. Most of them are maintainer planning history, not
4+
documentation. This page exists so you do not have to guess which is which.
5+
6+
**Filenames in this repository are unreliable signals.** `docs/CI.md` and `docs/ADOPTER-CI.md` describe
7+
different repositories for opposite audiences. `docs/ASVS-L2-PHASE0-CHANGES.md` is operator notes.
8+
`docs/testing/VERIFY.md` is an operator tool, not a test plan. Where a name misleads, this index says so
9+
rather than repeating it.
10+
11+
> **Reporting a security vulnerability?****[`.github/SECURITY.md`](../.github/SECURITY.md)**, the
12+
> disclosure policy. **Not** `docs/SECURITY.md`, which despite the identical filename is the
13+
> authentication and RBAC reference. Two files, same name, different jobs.
14+
15+
---
16+
17+
## Start here — a new operator, in order
18+
19+
Six documents, in this sequence. Each one is cheap to read and answers a question that makes the next
20+
one make sense.
21+
22+
1. **[EARLY-ADOPTER-GUIDE.md](EARLY-ADOPTER-GUIDE.md)** — the only doc that sequences the whole journey.
23+
It is an orchestration document: it ties the others together and adds the install-to-production
24+
rollout plan that nothing else carries. Read it first even if you skim it.
25+
2. **[SYSTEM-REQUIREMENTS.md](SYSTEM-REQUIREMENTS.md)** — the cheap disqualifier, before you install
26+
anything. Hardware, OS, the Python floor, the three store backends, ports, and sizing by volume.
27+
3. **[INSTALL-GUIDE.md](INSTALL-GUIDE.md)** — establishes the *deployment model*: a pinned, read-only
28+
engine wheel plus your own private git config repo (ADR 0017). This is the decision that is
29+
expensive to undo later, so understand it before you commit to a layout.
30+
4. **[USER-GUIDE.md](USER-GUIDE.md)** — the task-oriented path. Clean machine → running engine → first
31+
message end to end → authoring connections, routers and handlers → operating the console and the
32+
VS Code extension → reading dispositions and troubleshooting.
33+
5. **[DEPLOYMENT.md](DEPLOYMENT.md)****mandatory before any off-loopback bind.** Trust boundaries,
34+
the channel × TLS posture matrix, egress allow-lists, and the fail-closed bind guards that will
35+
otherwise refuse to start and leave you guessing why.
36+
6. **[testing/VERIFY.md](testing/VERIFY.md)** — the gate before real traffic. `messagefoundry verify`
37+
is a wheel-only, on-box acceptance check that answers "is *this* box set up right, and does a
38+
message actually flow?"
39+
40+
Read alongside these, not after: **[SUPPORT-POLICY.md](SUPPORT-POLICY.md)** — only the latest version is
41+
supported, and re-pinning a security release is mandatory and clocked.
42+
43+
Evaluating rather than installing? Start at **[FEATURE-MAP.md](FEATURE-MAP.md)**, the capability
44+
catalog, and **[../CHANGELOG.md](../CHANGELOG.md)**, which is authoritative for what is actually built.
45+
46+
---
47+
48+
## Operators and adopters
49+
50+
| Document | What it answers |
51+
|---|---|
52+
| [EARLY-ADOPTER-GUIDE.md](EARLY-ADOPTER-GUIDE.md) | The whole install-to-production journey, in order. |
53+
| [SYSTEM-REQUIREMENTS.md](SYSTEM-REQUIREMENTS.md) | Hardware, OS, Python floor, store backends, ports, sizing. |
54+
| [INSTALL-GUIDE.md](INSTALL-GUIDE.md) | Pinned wheel + your own config repo; the ADR 0017 deployment model. |
55+
| [USER-GUIDE.md](USER-GUIDE.md) | How do I actually do X — authoring, operating, troubleshooting. |
56+
| [CONFIGURATION.md](CONFIGURATION.md) | Every settings key, what each gate refuses, and why. |
57+
| [CONNECTIONS.md](CONNECTIONS.md) | Every transport and message type, and how to author each. |
58+
| [DEPLOYMENT.md](DEPLOYMENT.md) | Trust boundaries, TLS posture, egress, bind guards. Read before exposing anything. |
59+
| [testing/VERIFY.md](testing/VERIFY.md) | On-box acceptance check: is this machine set up correctly? |
60+
| [SUPPORT-POLICY.md](SUPPORT-POLICY.md) | What is supported, and the clock on re-pinning security releases. |
61+
| [ANTIVIRUS-FIREWALL.md](ANTIVIRUS-FIREWALL.md) | Exact paths, processes and ports to exclude or open (Windows/NSSM). |
62+
| [SERVICE.md](SERVICE.md) | Running the engine as a long-lived service. |
63+
| [CLUSTERING.md](CLUSTERING.md) | Active/passive HA for the engine itself. |
64+
| [CLOUD-DEPLOYMENT.md](CLOUD-DEPLOYMENT.md) | Multi-node HA on Kubernetes or cloud, with managed Postgres. |
65+
| [CLOUD-PHI-HIPAA.md](CLOUD-PHI-HIPAA.md) | The PHI/HIPAA considerations that pair with cloud deployment. |
66+
| [AOAG-DEPLOYMENT.md](AOAG-DEPLOYMENT.md) | Two-datacenter HA behind a SQL Server Always On AG. For DBAs. |
67+
| [DEPLOY-SERVER-DB.md](DEPLOY-SERVER-DB.md) | Store setup on a real database server. |
68+
| [ADOPTER-CI.md](ADOPTER-CI.md) | CI for **your config repo** — and honestly, what it does not prove. Not the engine's CI. |
69+
| [ASVS-L2-PHASE0-CHANGES.md](ASVS-L2-PHASE0-CHANGES.md) | Operator notes for one hardening phase; §4–§5 are the living crypto and comms inventories. |
70+
| [AI.md](AI.md) | How the shipped AI assistant is governed: modes, data scopes, RBAC, the PHI guarantee. |
71+
| [DICOM.md](DICOM.md) · [HL7-VALIDATION.md](HL7-VALIDATION.md) · [CODESETS.md](CODESETS.md) | Per-domain references. |
72+
73+
## Developers building on the engine
74+
75+
| Document | What it answers |
76+
|---|---|
77+
| [ARCHITECTURE.md](ARCHITECTURE.md) | How the engine decomposes: topology, store-as-queue, concurrency, module map. |
78+
| [architecture-diagram.md](architecture-diagram.md) | The same structure as a picture. |
79+
| [MENTAL-MODEL.md](MENTAL-MODEL.md) | The conceptual model behind the pipeline. |
80+
| [FEATURE-MAP.md](FEATURE-MAP.md) | The capability catalog — what exists, what is deferred, what was declined. |
81+
| [adr/](adr/) | **150 files.** Architecture decision records: *why* a thing is the way it is. Append-only history — a decision is superseded by a new ADR, never rewritten. Start at [adr/README.md](adr/README.md). |
82+
83+
## Security reviewers
84+
85+
| Document | What it answers |
86+
|---|---|
87+
| [../.github/SECURITY.md](../.github/SECURITY.md) | **Vulnerability disclosure policy.** Report here. |
88+
| [SECURITY.md](SECURITY.md) | Authentication and RBAC — *not* the disclosure policy, despite the name. |
89+
| [PHI.md](PHI.md) | Where PHI can and cannot go, and what the engine guarantees. |
90+
| [ASVS-L2-PHASE0-CHANGES.md](ASVS-L2-PHASE0-CHANGES.md) | §4 key/crypto inventory and §5 communications inventory, both CI-drift-guarded. §1–§3 are a historical phase changelog. |
91+
| [Secure_Development_Standards.md](Secure_Development_Standards.md) | The standards the build process holds itself to. |
92+
| [SECURITY-LOOSENING.md](SECURITY-LOOSENING.md) | The inverse of a hardening guide: every `[security]` switch defaults to the protective position, and this is what moving one off it costs. |
93+
| [SECURITY-DOCS-POLICY.md](SECURITY-DOCS-POLICY.md) | **Read this before hunting for a threat model.** The threat model, the ASVS assessments and the risk-acceptance register are deliberately *not* published; this explains what is public, what is withheld, and how to ask. |
94+
95+
## Maintainers and contributors
96+
97+
| Document | What it answers |
98+
|---|---|
99+
| [../CONTRIBUTING.md](../CONTRIBUTING.md) | How to contribute. |
100+
| [CI.md](CI.md) | What runs on a PR in **this** repo and which checks gate a merge. Not `ADOPTER-CI.md`. |
101+
| [Code_Quality_Standards.md](Code_Quality_Standards.md) | The quality rubric. Contains a dated (July 2026) graded self-assessment — treat the grade as a snapshot. |
102+
| [BACKLOG.md](BACKLOG.md) | **777 KB / 7,157 lines.** The maintainer work surface — ranked, deferred and declined items, including superseded sections. A required CI check keeps it current, but it is *not* a description of the product. For what the engine does, use `FEATURE-MAP.md`; for what is built, `CHANGELOG.md`. |
103+
| [SECURITY-REMEDIATION-LEDGER.md](SECURITY-REMEDIATION-LEDGER.md) | Single-writer coordination ledger for the 2026-06-26 audit-wave remediation. A dated work record, not a posture statement. |
104+
105+
---
106+
107+
## Historical and planning artifacts
108+
109+
These directories are **dated records kept for provenance. Do not follow them as instructions** — many
110+
describe plans that were superseded, and several state "next steps" that shipped long ago.
111+
112+
| Directory | Size | What it is |
113+
|---|---|---|
114+
| [releases/](releases/) | ~100 files | Multi-session build plans and session handoffs. History. |
115+
| [benchmarks/](benchmarks/) | ~42 files | Measurement records and inter-session review notes. The numbers are dated; the *method* is the reusable part. |
116+
| [research/](research/) · [design/](design/) · [archive/](archive/) | ~15 files | Exploratory and superseded material. |
117+
| [quality-gates/](quality-gates/) | 1 file | A single gate record. |
118+
119+
Individually dated documents worth knowing are historical rather than current:
120+
121+
- **[CI-SELFHOSTED-RUNNER.md](CI-SELFHOSTED-RUNNER.md)** — carries its own **RETIRED** banner. The
122+
runners are de-registered. It also still contains a "the repo is private" security rationale that the
123+
retirement itself invalidates.
124+
- **[CI-QUALITY.md](CI-QUALITY.md)** — a plain-language summary dated **June 20, 2026**, written for a
125+
non-technical evaluator. Its test counts have since moved; `CI.md` is the accurate technical version.
126+
- **[REMOTE-CONSOLE.md](REMOTE-CONSOLE.md)** and
127+
**[REMOTE-CONSOLE-CUSTOMER-GUIDE.md](REMOTE-CONSOLE-CUSTOMER-GUIDE.md)** — both describe the
128+
**retired** PySide6 desktop console (BACKLOG #103, 2026-07-13).
129+
- **[AI-OFF-MATRIX.md](AI-OFF-MATRIX.md)** — a coverage matrix from an internal plan. Its subject
130+
(working in the IDE with AI assist switched off) matters to adopters in PHI environments, but the
131+
document is keyed to internal item numbers.
132+
133+
---
134+
135+
## Repository-root documents
136+
137+
Not under `docs/`, and easy to miss:
138+
139+
| File | What it is |
140+
|---|---|
141+
| [../README.md](../README.md) | Project overview. |
142+
| [../CHANGELOG.md](../CHANGELOG.md) | **Authoritative for build state** — what actually shipped, per release. |
143+
| [../.github/SECURITY.md](../.github/SECURITY.md) | Vulnerability disclosure policy. |
144+
| [../CONTRIBUTING.md](../CONTRIBUTING.md) · [../GOVERNANCE.md](../GOVERNANCE.md) · [../MAINTAINERS.md](../MAINTAINERS.md) | Project process. |
145+
| [../CODE_OF_CONDUCT.md](../CODE_OF_CONDUCT.md) · [../CLA.md](../CLA.md) | Participation terms. |
146+
| [../LICENSE](../LICENSE) · [../NOTICE](../NOTICE) · [../COMMERCIAL-LICENSE.md](../COMMERCIAL-LICENSE.md) | Licensing. |
147+
148+
---
149+
150+
*This index is maintained by hand. If you add a document, add its row — and if it is a dated plan or a
151+
one-off evaluation, put it under Historical and planning artifacts, not in a reference table.*

0 commit comments

Comments
 (0)