Commit 88009b5
fix(supply-chain): verify the web console's provenance at load, and retire the pre-claim assertions (BACKLOG #1193) (#812)
* docs(supply-chain): retire the pre-claim assertions about the console distribution (BACKLOG #1193)
The name messagefoundry-webconsole has been registered on PyPI since the first
webconsole-v* release on 2026-07-29, which is what the guard test's
_PUBLISHED_DISTRIBUTIONS already records. Five shipped artifacts still asserted
the opposite, so a reader who trusted them got the pre-claim world.
Corrected, located by content rather than by line number:
- .github/workflows/release.yml: the release-webconsole comment said a PENDING
Trusted Publisher "does NOT reserve the name" and that the first run of the
job would create the project. That run has happened.
- docs/WEBCONSOLE-PACKAGE.md: "Until the owner publishes, install by path".
The remaining true half is that the ENGINE declares no webconsole extra.
- docs/SERVICE.md: "The wheel is not published to an index yet".
- packaging/messagefoundry-webconsole/RELEASE.md: "not automated", "no
PyPI-publishing job for this wheel exists", "an unpublished dependency would
break uv lock", and the stale claim that release-harness is the precedent to
mirror. release-webconsole exists and fires on its own tag.
- messagefoundry/api/app.py: the serve_ui ImportError comment said to point at
the path install while the string beneath it already named the distribution.
The comment is corrected, not the string.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(serve): verify the web console's provenance before importing it (BACKLOG #1193)
The engine mounts the web console IN-PROCESS, and `serve` gated that on
`find_spec("messagefoundry_webconsole") is not None` -- PRESENCE, not
provenance. Whatever occupied the import name was executed with the engine's
own privileges, and a wheel's payload runs AT IMPORT.
The check now runs in the else-branch of that same presence test, before
create_managed_app reaches the guarded import in api/app.py. It reads
importlib.metadata and accepts three things and nothing else:
- the file about to be imported belongs to the installed
messagefoundry-webconsole distribution, and no other distribution claims
that import name;
- it sits in a source checkout of this repository, proved by two markers
(the packaging pyproject and the package itself). A checkout is the
STRONGEST case, not the weakest: no index resolved anything;
- it sits under the directory a PEP 610 editable install recorded, which is
a provenance statement the installer made rather than one inferred here.
Anything else FAILS CLOSED with a message naming what was found and what was
expected, plus the named opt-out MEFOR_ALLOW_UNVERIFIED_WEBCONSOLE for a
packaging layout the check does not recognise.
Honest scope, recorded at the call site: it covers the documented `serve` /
NSSM path, not an embedder calling create_app itself; it cannot reach a
squatted SDIST, whose build backend runs during install before any engine
process exists; and it identifies the distribution, not its contents.
Negative controls, because a guard never seen to fire is not evidence. Seven
refusal branches are driven, including the shadowing case that a
packages_distributions()-only check would call green. The end-to-end one
plants a package that writes a marker file from its top level:
find_spec reports it present (the old check would have passed), the new check
refuses and names it, and the marker does not exist -- so the refusal happened
BEFORE the payload ran, which is the whole claim.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* test(provenance): widen the install guard onto two blind trees and add the prose arm (BACKLOG #1193)
Two gaps let five artifacts assert an unclaimed name for weeks while every
assertion in this module stayed green.
FILE SET. The scan read zero files of two of the three distributions this repo
builds: messagefoundry_webconsole/ and harness/. Both ship code that raises
operator-facing errors, exactly like the api/app.py RuntimeError this module
exists because of. Both trees are now scanned, and
test_every_packaged_distribution_has_its_code_tree_scanned pins it per tree
rather than as a total, because a total stays healthy while one tree silently
drops to zero. The tree names are read from each packaging pyproject's
force-include target, so a list here cannot drift from the build.
PROSE ARM. Nothing read a factual CLAIM about publication state, only install
commands. test_no_tracked_text_asserts_a_published_distribution_is_unpublished
flags a blank-line block that asserts a distribution is unpublished while
_PUBLISHED_DISTRIBUTIONS classifies it published. Derived from that set, so it
inverts on its own if a name is ever reclassified.
Measured against the five sites at 46ea10a, all five fire. A single-block
rule found four and missed docs/SERVICE.md, whose claim and distribution name
sit one blank line apart, so the window is the claim's block and its two
neighbours. "unclaimed" was tried as a pattern and REMOVED on evidence: it
fired on the console README's sentence saying the opposite, and it caught none
of the five.
Also read: packaging/*/*.md and release.yml, which the install-command set
excludes on the stated ground that nobody pastes them. A factual claim is wrong
wherever it is written, and two of the five lived there.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* refactor(serve): simplify the provenance helpers (BACKLOG #1193)
Quality pass over the code this branch added.
- _normalized_distribution hand-rolled a PEP 503 normalizer to dodge an
`import re`. checks._normalize_dist already states the same rule in one
line, so this now matches it character for character and names it. It is
still a local copy rather than an import: checks.py is the
`messagefoundry check` gate, and importing it would put the whole gate on
the serve startup path for one line.
- _console_source_checkout returned its own argument or None, which the
caller then filtered for truth. It is a predicate, so it is now
_is_console_source_checkout returning bool and the caller is a plain
comprehension.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs(backlog): record what landed on #1193 and what stays open
Item stays OPEN. Its closing act is a scorecard re-score, which lives in the
vault and is the tracking seat's to perform.
Records the re-measurement at HEAD (the 2026-08-20 research held), two
corrections to its own proposal (the master-test-plan rows are unreachable from
an engine checkout under ADR 0160 D1; docs/SERVICE.md is a fifth site the list
did not name), what both limbs shipped, the negative-control evidence that the
load-path check fires before the payload runs, and the six subjects left open.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: wshallwshall <mefordev@messagefoundry.org>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>1 parent 840dc12 commit 88009b5
9 files changed
Lines changed: 812 additions & 29 deletions
File tree
- .github/workflows
- docs
- messagefoundry
- api
- packaging/messagefoundry-webconsole
- tests
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
493 | 493 | | |
494 | 494 | | |
495 | 495 | | |
496 | | - | |
497 | | - | |
498 | | - | |
499 | | - | |
500 | | - | |
| 496 | + | |
| 497 | + | |
| 498 | + | |
| 499 | + | |
| 500 | + | |
| 501 | + | |
501 | 502 | | |
502 | 503 | | |
503 | 504 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
10884 | 10884 | | |
10885 | 10885 | | |
10886 | 10886 | | |
| 10887 | + | |
| 10888 | + | |
| 10889 | + | |
| 10890 | + | |
| 10891 | + | |
| 10892 | + | |
| 10893 | + | |
| 10894 | + | |
10887 | 10895 | | |
10888 | 10896 | | |
10889 | 10897 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
394 | 394 | | |
395 | 395 | | |
396 | 396 | | |
397 | | - | |
| 397 | + | |
398 | 398 | | |
399 | 399 | | |
400 | | - | |
| 400 | + | |
| 401 | + | |
401 | 402 | | |
402 | 403 | | |
403 | 404 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
17 | | - | |
18 | | - | |
19 | | - | |
20 | | - | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
21 | 24 | | |
22 | 25 | | |
23 | 26 | | |
| |||
0 commit comments