docs(security): reconcile five operator docs with the enforced posture - #83
Merged
Conversation
A documentation accuracy pass over the public set: 191 corrections across 81 files, each verified against the code rather than against the prior prose. Content corrections - The retired PySide6 desktop console is swept out of FEATURE-MAP, DEPLOYMENT, SYSTEM-REQUIREMENTS, SERVICE, docker/, and ide/. The web console at /ui is the sole operator surface; PySide6 is harness-only. FEATURE-MAP's cluster/leader row moved to shipped rather than being deleted with the desktop console -- the capability lives in the web console (monitoring.py renders it from GET /cluster/status). - CONNECTIONS.md documented 12 of 17 registered connectors. Settings sections added for the missing ones from the registry and config models, and the SFTP/SMTP self-contradiction resolved against transports/remotefile.py -- those rows were marked "planned" while REMOTEFILE registers both and pyproject ships an [sftp] extra. - CONFIGURATION.md's "(proposed)" framing dropped where settings.py implements the mechanism, and the implemented vs accepted-but-ignored key lists corrected against the model. That table is load-bearing: an adopter setting an ignored key currently gets silence. - DEPLOYMENT.md's TLS/exposure guidance aligned to ADR 0148/0153. - SYSTEM-REQUIREMENTS.md no longer claims no published baseline exists; TUNING-BASELINE.md is published. Three prose capacity ceilings that asserted capability as fact were corrected; the labelled projection table was left alone and its caveat hardened. - MENTAL-MODEL.md off its v0.1.0 banner; USER-GUIDE.md walkthroughs checked against the real CLI surface. - The four throughput plans ADR 0107 closed now carry superseded banners. The bodies stay -- they are the record of why. Cross-references - ADR slug links repaired. Where a renamed ADR meant the citing sentence was quoting the superseded position, the sentence is flagged for review rather than silently repointed -- a working link to a reversed conclusion is worse than a dead one. - Links into the deleted messagefoundry/console/ tree and into the withheld docs/security/ tree converted to plain-text references, so they stop reading as rot. No public path was invented and no policy document was created; publishing docs/Secure_Development_Standards.md remains an owner decision, so its 44 citations are likewise plain text. adr/TEMPLATE.md fixed first, since it propagated the dead citation into every new ADR. - All 26 internal links in ASVS-L2-PHASE0-CHANGES.md repaired. What did NOT change, and why it matters 60 leads from the inventory were rejected after checking the code. Among them: docker/ never had a [console] extra or console entrypoint; the Dockerfile's PySide6-in-runtime warning is accurate (requirements.lock pins pyside6); FHIR-IN really is planned (only a destination is registered); and SYSTEM-REQUIREMENTS.md never carried the 45M/day figure, so it was deliberately not introduced -- ADR 0052 is a target, never quotable as capability. Verification: leak gate exit 0 with the real roster (names=7, estate=13, site_prefixes=1); 967 passed / 0 failed across the doc, ledger, ADR-index, link and connector guards, with test_no_links_into_private_paths and test_the_real_backlog_satisfies_the_invariant confirmed executed, not skipped. BACKLOG.md changes are link-to-text only -- no numbered heading added or removed, banner glyphs untouched. 43 questions that need an owner decision are recorded separately rather than guessed at.
Owner decision. The document licenses itself as publishable and adopter-facing,
messagefoundry.org already served it as both PDF and Markdown, and citations
across the repo -- including adr/TEMPLATE.md, which propagated the dead
reference into every new ADR -- pointed at a file that did not exist here.
Source and de-branding
Three copies existed. The newest and richest lived in the private vault as a
customer-branded fork, and its own revision history records that it was
"re-versioned and re-branded from the prior generic Secure Development
Standards (which had reached v2.0)". So the generic lineage is the original and
de-branding restores it rather than inventing it. That copy is the base: it
carries ten sections the un-branded copies lack -- SSDF PW practices,
spec-driven development, NIST SP 800-115 testing tiers, ASVS 5.0 L3 scope,
NIST SP 800-66 Rev. 2 safeguards, and References.
Four transformations, each auditable in scratch publish_sds.py: de-brand the
title and Document row; carry the public version forward to 2.1 from the v2.0
the history names; rewrite the two revision rows to describe the content
changes without naming a customer fork; de-link references into docs/security/,
which stays withheld.
Checked before publishing, not after
- Leak gate exit 0 with the real roster. Note it also passed on the BRANDED
copy -- the initialism is not in the token roster -- so the de-brand was
verified by direct inspection, not by the gate. Zero occurrences remain.
- Every other customer-shaped term run down by hand: the one Corepoint mention
is a competitor list ("Corepoint, Mirth Connect, Rhapsody, Cloverleaf"),
which is public positioning; no hostnames, IPs, or internal identifiers.
- docs/ is absent from the sdist allowlist (only-include = messagefoundry,
README, CHANGELOG, LICENSE, NOTICE), so this does NOT undo the #1020
packaging remediation. test_release_pipeline passes.
Citations
8 restored across .github/SECURITY.md, three docs/ standards companions, three
ADRs and adr/TEMPLATE.md -- all verified to resolve. The inventory's "44 links"
was an overcount; the real figure is 23 mentions, most by title rather than
filename, and linking every occurrence would be noise, so the convention is the
first mention per file plus any section-qualified citation.
653 passed / 0 failed across the doc, link, ADR, backlog and release-pipeline
guards.
97 changes across 11 file groups, each verified against code rather than against the prior prose. 60 further leads were rejected after checking; they are the reason to trust the 97. Corrections that changed what the docs claim - SYSTEM-REQUIREMENTS said "No native transport TLS" and "no MLLP-over-TLS". Both are false: settings.py:694-712 is an in-process API/WebSocket TLS block (WP-13a, ADR 0002) and mllp.py:505-524 builds per-connection MLLP SSL contexts with opt-in mTLS (WP-13b). The doc was understating shipped capability and telling adopters to buy a reverse proxy they may not need. - CONNECTIONS' ASVS 5.1.1 block claimed the file surface accepts "HL7 v2 text only". The code sniffs against the DECLARED content_type (file.py:514) and a non-HL7 drop bypasses HL7 handling entirely (file.py:644,673, ADR 0004). It now describes declared-type conformance. An overstated control is the kind an assessor finds and then distrusts the rest of the document over. - The eight sizing tiers had no measurement behind them. Re-derived from the published baseline with the arithmetic shown, and daily figures now state their duty-cycle assumption instead of implying 86400s of peak. - ~40 implemented settings keys had no row in CONFIGURATION. Added from ServiceSettings.model_fields. - NOT_DEPLOYED, a seventh disposition genuinely set by the finalizer (store.py:319, 912, 926, 955), was documented nowhere. Added to MENTAL-MODEL, USER-GUIDE and ARCHITECTURE. - MENTAL-MODEL never mentioned engine sharding, though it is the built default scaling axis, while asserting active-active scale-out was dropped -- together reading as "this does not scale". Both corrected, and the two are now distinguished rather than conflated. - SERVICE now documents that a custom -Environment name also requires [ai].data_class and [ai].production (install-service.ps1:66-71). Following the old text produced a service that would not start. Classification and structure - New docs/SECURITY-DOCS-POLICY.md states what is kept private and why, using the operative rule: private only if it discloses an OPEN, un-remediated weakness in exploitable detail, or names a customer. A closed audit is transparency. The ~145 plain-text references into the withheld tree now have something to point at instead of naming documents a reader cannot find. - Four docs self-marked "internal" while sitting in a public repo. Re-sorted under that same rule; the labels now match reality. - The five throughput plans ADR 0107 closed moved to docs/archive/throughput/ with a README. Bodies intact -- they are the record of why. ADRs, treated as historical records - ADR 0101 gains the estimand reporting rule it was already being cited for. ADR 0074:146 cited it; ADR 0101 did not contain it. The rule is real: the intake-vs-delivery conflation caused the Arm 0 "sustains 26" retraction. Added with a dated amendment note, since 0101 is Accepted. - ADR 0090's PySide6 residual cannot be built. Annotated with a dated note rather than repointed -- repointing would rewrite what was decided. Verification Leak gate exit 0 with the real roster (names=7, estate=13, site_prefixes=1). 972 passed / 0 failed across the doc, link, ledger, ADR, feature-map, connector and release-pipeline guards, with test_feature_map_claims and test_backlog_status_check confirmed EXECUTED (23 PASSED), not skipped. The archive move was the risk here, because moving a file breaks inbound links without any test going red. All 4,593 relative links across 414 markdown files were resolved: zero real breakages. (Two `command:` VS Code URIs in an IDE ADR are legitimate, not file paths.) 57 security-posture passages are held back for owner review before the PR.
Ten commits landed on main during this documentation pass. Two files conflicted; both resolutions take the other session's content and keep this branch's link fix. docs/ASVS-L2-PHASE0-CHANGES.md — the sibling ASVS-TLS session corrected a real error: `SSLContext.set_groups` is a Python 3.15 API, not 3.13, so the key-exchange pin currently pins NOTHING and those contexts inherit OpenSSL's default group list. That correction wins over this branch's older "pins the approved groups" text, which was wrong. Kept this branch's relative-path fix (../messagefoundry, not ../../, which escapes the repo root). docs/Secure_AI_Development_Standards.md — kept their added honesty note that CODEOWNERS is documentation rather than enforcement here, and kept this branch's de-linking of RELEASE-GATE.md, since docs/security/ stays withheld from the public repo. Verified after integrating: leak gate exit 0 with the real roster; 976 passed / 0 failed; 4,605 relative links across 414 files resolve with zero breakages.
CI went red on a documentation-only branch. The cause was a precision bug in
the guard, not rot in the docs.
`_PROSE` alternated on the bare string `private repo`, so it matched INSIDE
longer words. Two lines counted as present-tense mirror prose while being
nothing of the kind:
.github/workflows/release.yml:271 a quoted GitHub error about
"user-owned private REPOsitories"
docs/Secure_Development_Standards.md:284
"a private REPOrting channel"
The second arrived with the newly published standards doc and took the count to
56 against a ceiling of 55.
The fix is a word boundary on that one alternative. Measured before changing it,
because the temptation here is to widen a guard until your own change passes:
the boundary suppresses exactly those two lines and keeps every genuine
"private repo" hit -- about twenty, across the workflows, INSTALL-GUIDE,
VERSION-CONTROL, SECURITY and the ADRs. No true positive is lost.
The ceiling therefore moves 55 -> 54. That is the ratchet working as documented
("lower it when you fix some; NEVER raise it") -- the honest count fell because
two false positives were removed, so the ceiling follows it down. It also keeps
test_the_ratchet_is_not_slack satisfied, which fails if the gap exceeds 8.
Proven, not assumed: appending "This repository is the public mirror of the
private repo." to a tracked doc makes the guard go RED at 55 vs 54, and removing
it makes it green again. A guard changed without demonstrating it can still fail
is a guard nobody should trust.
Also worth recording: the module's failure message labels its tail `hits[-12:]`
as "New or changed", which it is not -- it lists the last twelve hits in scan
order. That is what made the failure look like it came from test files nobody
had touched. The delta had to be computed against origin/main by hand.
Six documentation findings today were all the same shape, and none was a false statement: true about the mechanism, misleading about the posture. A sentence that survives every spot-check while pointing the reader somewhere they cannot go. A correctness review terminates at "yes, that is accurate" and finds none of them. Secure_Development_Standards.md §3 gains "Reviewing security prose: ask what a reader would DO with it" as the governing instruction, with three rules under it and the evidence for each: - State a load-bearing fact ONCE and link to it. A repo that states a fact twice will eventually state it two ways, and the stale copy is the one that gets cited, because a reader who finds A statement stops looking for the other. Three instances in one day: harden_kex_groups described as pinning key-exchange groups across FIVE live documents when SSLContext.set_groups is a Python 3.15 API that pins nothing; a superseded Transit/unkeyed-audit-chain claim left in PHI.md after the code closed it, from where it reached a public page AND a security review; and PHI.md §5 asserting "no PHI is placed in a URL" while §7 documented the query parameters that carry it. Each time the repo held both the right and the wrong version. - A completeness claim is a liability. "Two configurations do X, and a reviewer should hear both" invites the check and survives it. Twice this set shipped such a sentence wrong in BOTH directions -- naming a case that no longer existed while omitting one that did. - A compensating control must not rest on a false premise. A Referrer-Policy relaxation was justified by "console URLs carry opaque ids only (never PHI)", untrue of the console's own search route. The control was sound; the reason was not, and the next person reasons from the comment. CLAUDE.md §11 carries these as four bare one-line imperatives pointing at that section. The duplication is deliberate and is the rule applied to itself: the FACT lives once, because evidence, dates and line numbers drift; the INSTRUCTION is repeated because it is one line, cannot meaningfully drift, and a pointer nobody follows mid-task changes no behaviour. If the two ever disagree it will be about the wording of an instruction, not about what is true. CONFIGURATION.md's audit_days row is aligned with docs/PHI.md §8, which is now named in the row as the source of record. Both say keep-forever rests on the retention requirement -- 45 CFR 164.316(b)(2)(i), against framework floors far below it -- and NOT on chain-breakage. That argument was inverted: deleting the OLDEST rows does break the walk, but an attacker hides by truncating the NEWEST, and the surviving prefix still verifies (store/store.py:7385). The anchor that would catch it is not reachable from the shipped CLI, so audit-verify returns clean after a truncation; that gap is filed separately. Provenance is recorded in the standards section because the provenance is the evidence: the state-it-once rule came from the parallel ASVS session, which also supplied two of the three instances. Verified: leak gate exit 0 with the real roster; 72 passed across the ratchet, feature-map, backlog and security-doc-drift guards; 4,606 relative links across 414 files resolve. CI was GREEN on all 35 checks for the preceding commit.
Reported by the messagefoundry-website session, which hit it for real: a peer's
PR merged mid-session and the gate kept blocking 31 files, a third of that site.
The repo squash-merges everything ("title (#NN)"). A squashed commit never
becomes an ancestor of the branch it came from, so the merge-base never advances
and `diff origin/main...HEAD` keeps reporting the branch's files after its work
has landed. Its session then blocks that whole file set until someone prunes the
worktree.
Neither diff form is right alone, and each fails in the opposite direction:
three-dot credits a landed branch forever; two-dot blames a merely-behind branch
for every file main moved underneath it. The intersection -- authored AND not yet
landed -- is correct, and self-clears on squash, rebase and merge-commit alike.
A two-dot failure falls back to the authored set, so a git hiccup over-blocks
rather than silently under-blocking.
Measured here before and after, on real worktrees:
landed, claiming phantoms asvs-15-2-4-name-claimed 3dot=8 intersect=0
gate-version-stamp 3dot=4 intersect=0
pins 3dot=16 intersect=0
genuine outstanding work documentation-inventory 101 -> 101
master-test-plan 21 -> 21
asvs-14-2-7-retention 11 -> 11
ci-suite-wiring 4 -> 4
prunefix 7 -> 7
`pins` was live: it held zero uncommitted files and zero outstanding committed
files, and was still blocking 16. After the fix it drops off the map entirely
while every branch with real work is unchanged. Verified with -Refresh, since the
cache is TTL-bounded and served the old map on the first run.
Landing here rather than in its own PR because this branch's merge is exactly the
event that triggers the bug: 101 files would have stayed blocked for every other
session until the worktree was pruned.
Seven commits landed on main. One file conflicted, .github/SECURITY.md, and both sides had independently fixed the SAME defect: two markdown links into docs/security/, a directory absent from the public repo. main (#78) removed the references outright. This branch converted them to plain text and pointed at docs/SECURITY-DOCS-POLICY.md, a page that exists only on this branch and that the owner ratified precisely so those ~145 withheld-path mentions have somewhere to point instead of naming documents a reader cannot find. Resolution takes both: main's tighter sentence structure (em-dash rather than a parenthetical), and this branch's named document plus the policy pointer. A reader now learns the analysis exists, that it is maintainer-internal, and what they can request -- which is the whole point of the policy page. main's other change to the file, a working security@ address replacing the dead "email the maintainer at the address on the GitHub profile" fallback (#77), came through untouched and is strictly better than what either side had. Note the five docs/ files a review workflow is currently editing were NOT touched by main and remain uncommitted and intact; this merge deliberately stages only the conflicted file. Verified: 0 conflict markers; the new security@ address present; the policy pointer present twice; 4,639 relative links across 414 files resolve.
Four rounds of adversarial security review of the operator-facing docs, judging each claim by what a reader would DO with it, not merely whether it is accurate. Every fix targets a TRUE sentence that pointed the reader somewhere they could not go: an instruction contradicting the gate it configures, or a recipe that fails silently. Each was verified by executing the shipped recipe through load_settings()/load_config() in the venv, not by reading it. Files: - docs/CONFIGURATION.md - docs/CONNECTIONS.md - docs/DEPLOYMENT.md - docs/REMOTE-CONSOLE.md - docs/SYSTEM-REQUIREMENTS.md Highlights: - Browser-console walkthrough now names [security].serve_web_console = true as a non-optional Step 0; without it every posture it instructs trips the ADR 0143 auto-degrade to a silent /ui 404. Two complete, runnable postures added. - The refuse-vs-warn split for security_notifications_required, require_managed_identity and the cleartext-hop disposition is [security].enforcement, not the production tier. Corrected across the rows that told a staging operator they would be warned when the gate exits 2. - DICOM SCP peer control: source_ip_allowlist is an inbound(...) keyword, not an [inbound] TOML key (which is accepted and silently discarded). The construction gate counts controls, so the wrong spelling passes fail-closed. - The [egress] open-egress gate counts only six of eight lists; a mail-only or Direct-only PHI instance must set [security].block_unlisted_outbound = true. - Alert routing rules are silently inert with zero configured transports; the worked [cert_monitor]/[secret_rotation] recipes and the full §Example config now start clean against every gate they document. Filed as code defects (task chips), not fixed here: the stale "refuse (production)/warn" code comments that seeded the drift, the DICOM refusal message naming a discarded key, the egress gate omitting allowed_smtp/allowed_direct, and the alert-rule validation asymmetry. The round-4 fixer's empirical verification is complete and the doc-drift/ledger suite is green. Local leak scan uses the synthetic token set; CI runs the authoritative real-token forbidden-content check. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…o claude/documentation-inventory-c16092 # Conflicts: # docs/CONFIGURATION.md # docs/CONNECTIONS.md # docs/DEPLOYMENT.md # docs/REMOTE-CONSOLE.md # docs/SYSTEM-REQUIREMENTS.md
…o claude/documentation-inventory-c16092
…o claude/documentation-inventory-c16092
…o claude/documentation-inventory-c16092
wshallwshall
added a commit
that referenced
this pull request
Jul 30, 2026
) The documentation pass (#83) corrected five operator docs against the enforced posture. Four of those corrections were treating symptoms: the code itself named a settings key that does not exist, described its own gate against the wrong axis, silently discounted two allow-lists, and validated alert routing only in the state an operator is least likely to be in. Left alone, each re-seeds the drift the moment someone reads the source. 1. DICOM peer-control refusal named a key that is silently discarded transports/dicom.py told the operator to set `[inbound].source_ip_allowlist`. InboundSettings has no such field and section models ignore unknown keys, so an operator following the engine's OWN error message writes a key into messagefoundry.toml that is accepted and dropped — leaving a non-loopback SCP with no peer-IP gate while believing it has one. Aggravated by the gate COUNTING controls, so a spoofable AE-title list plus the discarded key passes. The message now names the working surface (an `inbound(...)` keyword, the ONLY one for a DICOM SCP since DICOM() is not authorable in connections.toml), disambiguates it from the connections.toml `[[inbound]]` key that IS real, and leads with mTLS. Same spelling corrected in the module docstring, the gate comment, wiring.py, docs/SECURITY.md and docs/ASVS-L2-PHASE0-CHANGES.md. 2. Open-egress gate counted six of eight allow-lists `egress_open` omitted allowed_smtp and allowed_direct, both of which ARE enforced downstream by _allowlist_for. A mail-only or Direct-only PHI instance could enumerate every destination it uses and still be refused as "UNRESTRICTED", with nothing naming the two lists that did not count — and the gate's own docstring already claimed otherwise. Fixed in the FAIL-CLOSED direction only: the two lists count only when [security].block_unlisted_outbound was left UNSET, which is exactly the state the deny-by-default flip turns ON. Measured, prod PHI: allowed_smtp-only and allowed_direct-only go exit 2 -> start (deny-by-default flipped ON); the explicit `block_unlisted_outbound = false` + smtp/direct-only case STILL refuses. No shipped refusal stops firing. The refusal now names that override when it is the reason a declared allowlist did not satisfy the gate. 3. Two gate comments described the deployment tier, not the enforcement dial __main__.py said "refuse (production) / warn (non-production)" over branches that read `enforcing` — which is `enforce` by default on dev and staging as much as prod, so all three REFUSE. These comments are what two BLOCKING doc defects were copied from. Comment-only, no behaviour change. 4. Alert rules were validated everywhere except where it matters notifier_from_settings returned on zero transports BEFORE cross-validating rules, so a [[alerts.rules]] block routing to a transport the instance does not configure was accepted and silently never applied — while the identical rule alongside one transport was a hard ValueError. Validation now runs first. A rule naming no transport is unaffected (still starts, now with a warning when rules exist that cannot page anyone). BREAKING: an instance that starts today with an inert rules block naming a transport will now refuse until the transport is configured. In that state the rule has never routed a single alert, so this removes no working behaviour — it converts a permanent silent no-op into a startup refusal that names the keys. Also gated at AUTHORING time: `alert add` cross-checks the rule being added, so the editor can no longer write a config that only fails at the next boot. Scoped to the added rule so a file already containing a bad rule can still be repaired with `alert remove` rather than being wedged shut. Filed, not fixed: BACKLOG #252 — the DICOM gate counting a spoofable AE-title list as a sufficient peer control. That is an ADR 0025 §9 + SECURITY.md contract change and deserves its own decision, not a rider on a message correction. Verified with the unfiltered suite (a narrow -k filter is what let a guard go red in CI last time). New regression tests pin the egress narrow form (including the loosening that deliberately did NOT happen), the alert authoring refusal, and the no-transport rule still being allowed. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…o claude/documentation-inventory-c16092
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this is
Four rounds of adversarial security review of the five operator-facing docs, landing as their own PR. (The prior documentation pass merged as #69 while this review was still in flight, so these fixes were never part of it.)
The review judged each claim by what a reader would do with it, not whether it is accurate. Every finding is a true sentence that misleads about the posture — accurate about the mechanism, but pointing the operator somewhere they cannot go: an instruction that contradicts the gate it configures, or a recipe that starts the engine in a state the doc says it prevents. Plain correctness review found almost nothing; adversarial review found 34 → 19 → 7 → 5 → 4 defeatable claims across the rounds, the blocking count falling as each round found a new class.
Files
docs/CONFIGURATION.md,docs/CONNECTIONS.md,docs/DEPLOYMENT.md,docs/REMOTE-CONSOLE.md,docs/SYSTEM-REQUIREMENTS.md— 969 insertions / 205 deletions total, plus a routine integration ofmain.The blocking fixes
Each was confirmed by executing the shipped recipe through
load_settings()/load_config()in the venv, not by reading it:[security].serve_web_console = true; every posture it instructed (off-loopback bind, declared terminator, public origin) independently trips the ADR 0143 auto-degrade — yielding a cleanly-started engine, one stderr warning, and a silent 404 at/ui. Added a non-optional Step 0 and two complete, runnable postures (declared-terminator and in-process-TLS), each verified to serve/uiand verified to silently degrade with the line removed.security_notifications_required,require_managed_identity, and the cleartext-hop disposition were described as "refuses in production / warns in non-production". The gates read[security].enforcement, which isenforceby default ondevandstagingtoo — so a team standing up staging is promised a warning and getsexit 2. (Measured: dev/staging/prod all resolveenforcing=True.) Rewrote every affected row.source_ip_allowlistis aninbound(...)keyword, not an[inbound]TOML key — and every section model isextra="ignore", so the TOML spelling is accepted and dropped at load. Because the construction gate counts controls, an operator who sets a (spoofable)calling_ae_allowlistplus the discarded key passes the fail-closed check believing they have an IP restriction they do not have. Rewrote the section, added the honest read-back disclosure (a code-first DICOM connection has no read-back surface), and fixed the "hardened SCP" example that bound loopback and configured no IP gate at all.allowed_smtpandallowed_directappear nowhere in the predicate, so a mail-only or Direct-only PHI instance that declares its destinations stillexit 2s with "outbound egress is UNRESTRICTED". Retitled the behaviour-table rows and told mail-only/Direct-only deployments to set[security].block_unlisted_outbound = true.Plus sweeps: the
§Exampleconfig now starts clean against all four gates it documents (it previously sat in three of them); alert routing rules were silently inert with zero configured transports;allowed_tcp/allowed_httpunder-described the connectors they gate; the/healthnetwork-gate exemption, the MFA-enrolment exemption, and a missingLoopbackimport were all corrected.Coverage evidence
Not just fixes — the reviewer listed the instructional passages checked and found already correct (36 verified alongside the changes), ran all 11 Python snippets + 4
connections.tomlblocks from CONNECTIONS.md through the real loader (15/15 load after one import fix), and re-ran the doc-drift / ledger test selection green after every edit.Deliberately not in this PR (owner call)
A rewrite had added a section describing the real
messagefoundry import corepointcommand using estate terminology the leak-gate flags (2 hits, source and PDF). It was absent frommain, so the rewrite would have introduced it. It is withheld here and preserved verbatim in the paused session's scratchpad. A real shipped feature is not automatically a publishable one — flagging for an owner decision on whether to reword + republish.Follow-up code defects (filed separately, not fixed here)
The doc fixes treat symptoms whose root causes are in code and will re-seed the drift:
transports/dicom.pyrefusal message + docstring name the non-existent[inbound].source_ip_allowlist.__main__.pyegress_openpredicate omitsallowed_smtp/allowed_direct— arguably a gate bug (a mail-only site is forced ontoblock_unlisted_outboundfor an invisible reason).__main__.py:1135-36and:2081-82comments still say "refuse (production)/warn (non-production)" — the stale comments the two blocking doc defects were copied from.pipeline/alert_sinks.pyreturns before rule/transport cross-validation, so a rule on a zero-transport instance is silently never applied.✅ RESOLVED — the inherited
mainbreakage is fixed(Kept for the record; no longer blocking.) When this PR was opened,
mainwas red and every open PR inherited it. PR #80 had added BACKLOG items #232–#239 todocs/BACKLOG.mdwithout the mandatory status banner, whichtests/test_backlog_status_check.pyenforces against the real file:Two independent proofs that this is inherited, not introduced:
origin/main(this branch not involved) reproduces the identical 8 errors.git diff --name-only origin/main...HEADlists only the five doc files — this branch never touchesdocs/BACKLOG.md.#82 ("docs(backlog): items 232-239 declared no status, turning main red",
docs/BACKLOG.mdonly, +16/−0) fixed it — authored by the session that owns those items, since the correct banner was a content call this PR should not guess. It merged asc786577e, and this branch has been re-synced onto it. The backlog checker now reportsOK — 237 backlog items, each declaring exactly one status, andtests/test_backlog_status_check.pyis 15 passed.Nothing here was ever blocked on this PR's own contents.
Verification
Local unfiltered
pytest— no-kfilter, because a narrow filter is exactly what let a guard go red in CI last time:That run predates the re-sync: the single failure was the inherited
mainbreakage described above, now fixed by docs(backlog): items 232-239 declared no status, turning main red #82 and confirmed green here. Everything else passed, which is the clean confirmation that nothing in these five documents is at fault.Branch integrated with
origin/main(contains docs(architecture): the stated Python floor was three minors behind pyproject #79, backlog: eight Steps-view items from the Windmill/Kestra evaluation #80); satisfies strict branch protection.Merge was verified lossless: the five docs are byte-identical to the pre-merge commit, and
ARCHITECTURE.md/BACKLOG.mdare byte-identical toorigin/main.No
.pyin the branch delta, so theruff …--fixrewriting hooks never fired (venv ruff is 0.15.22, matching theconstraints.lockpin).CI runs the authoritative real-token forbidden-content scan (the local pre-commit scan used the synthetic token set).
🤖 Generated with Claude Code