From 3134e2f3c186760e464a1128d0fcc89d39a995e1 Mon Sep 17 00:00:00 2001 From: Scott Hall Date: Tue, 27 Oct 2026 09:08:55 -0500 Subject: [PATCH 1/6] IDE: FieldDescriptor.value and defaultValue are optional, as the webview receives them describe() and describeUnknown() leave both undefined in the ordinary case (a key the record does not set, a secret, an env reference, a schema param with no default), and postMessage carries the payload as JSON, which drops an undefined member. The declared type promised two members the connection editor page often does not get. Both are now `?: unknown`. The comment above mfFieldDescriptor says why the page's shape check does not require them: a presence check would discard nearly every ordinary "fields" message. No check is added. A new webview-receivers test delivers a fields message whose every field lacks both members and requires it to be accepted. Mutation control: adding a presence check for defaultValue to the receiver fails it. BACKLOG #1123 Proposed PR title: IDE: FieldDescriptor.value and defaultValue are optional, matching what the connection editor webview receives Proposed ledger banner: #1123 engine half BUILT: the postMessage origin guard (mfTrusted plus per-render token) was already at all eight receivers (PRs 1755, 1791, 2196, 2203, 2212); this change makes FieldDescriptor.value/defaultValue optional to match the JSON the webview receives, with a positive-control receiver test. The ASVS 3.5.5 vault amendment and its re-read follow this merge. --- ide/src/connectionEditorWebview.ts | 8 ++++++-- ide/src/connectionForm.ts | 15 +++++++++++---- ide/src/test/suite/webview-receivers.test.ts | 20 ++++++++++++++++++++ 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/ide/src/connectionEditorWebview.ts b/ide/src/connectionEditorWebview.ts index d83cdca26c..67783d90de 100644 --- a/ide/src/connectionEditorWebview.ts +++ b/ide/src/connectionEditorWebview.ts @@ -297,8 +297,12 @@ export function connectionEditorScript(token: string, p: ConnectionEditorScriptI // this page. Both post "error"; only connectionEditor.ts answers with "fields". A field is a // FieldDescriptor from connectionForm.ts: its declared fields with their types. The two // unknown-typed values (value, defaultValue) and the carried-through envDefault and - // preserveValue are not typed there either. choices is required and may be null; envKey, cast - // and a group's description may be absent and are never null. + // preserveValue are not typed there either, and are not required to be present: value and + // defaultValue are optional there because describe() leaves them undefined whenever the record + // does not set the key, the key is a secret or an env reference, or the schema has no default, + // and postMessage serializes the payload as JSON, which drops an undefined member. A presence + // check on either would discard nearly every ordinary "fields" message. choices is required and + // may be null; envKey, cast and a group's description may be absent and are never null. // control and type are closed sets: the lists FieldControl and ParamType are derived from. const FIELD_CONTROLS = ${embedJson(FIELD_CONTROLS)}; const PARAM_TYPES = ${embedJson(PARAM_TYPES)}; diff --git a/ide/src/connectionForm.ts b/ide/src/connectionForm.ts index 613ec667a8..d50975c66a 100644 --- a/ide/src/connectionForm.ts +++ b/ide/src/connectionForm.ts @@ -152,10 +152,17 @@ export interface FieldDescriptor { choices: unknown[] | null; /** The default, as display text for a hint. "" when the setting has no default. */ placeholder: string; - /** The default, raw (a checkbox needs its unset position). RENDER-ONLY — never post it. */ - defaultValue: unknown; - /** The record's literal value, or undefined when the record does not set this key. */ - value: unknown; + /** + * The default, raw (a checkbox needs its unset position). RENDER-ONLY — never post it. Optional + * because it is often undefined, and posting the descriptor to the webview serializes it as JSON, + * which drops an undefined member: the page can receive a field with no `defaultValue` at all. + */ + defaultValue?: unknown; + /** + * The record's literal value, or undefined when the record does not set this key. Optional for the + * same reason as `defaultValue`: an undefined value is absent from what the webview receives. + */ + value?: unknown; isEnvRef: boolean; /** The referenced environment key, when `isEnvRef`. */ envKey?: string; diff --git a/ide/src/test/suite/webview-receivers.test.ts b/ide/src/test/suite/webview-receivers.test.ts index 2baa00927d..cfdbcb70dc 100644 --- a/ide/src/test/suite/webview-receivers.test.ts +++ b/ide/src/test/suite/webview-receivers.test.ts @@ -844,6 +844,26 @@ suite("webview receivers discard a malformed payload and render a well-formed on } }); + test("connection editor: a field with neither value nor defaultValue is accepted", () => { + // FieldDescriptor.value and .defaultValue are optional: describe() leaves them undefined in the + // ordinary case, and the JSON the host posts drops an undefined member. variant() is that JSON + // round trip; deleting both from every field is the extreme of it. Nothing may be discarded. + const bare = variant(FIELDS_OK, (c) => { + for (const f of c.groups.flatMap((g: Payload) => g.fields)) { + delete f.value; + delete f.defaultValue; + } + }); + const fields = bare.groups.flatMap((g: Payload) => g.fields); + assert.ok(fields.length > 0 && fields.every((f: Payload) => !("value" in f) && !("defaultValue" in f))); + const p = connection.load(); + const before = p.snapshot(); + p.deliver(bare); + assert.deepStrictEqual(p.errors.map(String), [], "the page threw"); + assert.notStrictEqual(p.snapshot(), before, "the message changed nothing"); + assert.deepStrictEqual(p.warnings, [], "discarded"); + }); + test("the engine prints only setting type tags this page's set holds", () => { // FieldDescriptor.type passes through from "connection schema --json". This reads the tags // _type_name() can return, so a tag added there fails here instead of the form going quiet. From f92bd905aa7fd2e65fe08e9e450c2fa8658c49f7 Mon Sep 17 00:00:00 2001 From: Scott Hall Date: Tue, 27 Oct 2026 09:14:52 -0500 Subject: [PATCH 2/6] IDE test: the bare-descriptor fixture joins the connection editor's well-formed list Simplify pass. The standalone test copied the ACCEPTS loop's body, so FIELDS_BARE (FIELDS_OK with value and defaultValue deleted from every field) is now a second well-formed "fields" fixture, and the fixture-shape test pins that FIELDS_OK carries both members and FIELDS_BARE carries neither. The webview comment points at FieldDescriptor for the reason rather than restating it. Mutation control: a presence check on value in the receiver fails the ACCEPTS test. BACKLOG #1123 Proposed PR title: IDE: FieldDescriptor.value and defaultValue are optional, matching what the connection editor webview receives Proposed ledger banner: #1123 engine half BUILT: the postMessage origin guard (mfTrusted plus per-render token) was already at all eight receivers (PRs 1755, 1791, 2196, 2203, 2212); this change makes FieldDescriptor.value/defaultValue optional to match the JSON the webview receives, with a positive-control receiver fixture. The ASVS 3.5.5 vault amendment and its re-read follow this merge. --- ide/src/connectionEditorWebview.ts | 10 +++--- ide/src/test/suite/webview-receivers.test.ts | 33 +++++++------------- 2 files changed, 16 insertions(+), 27 deletions(-) diff --git a/ide/src/connectionEditorWebview.ts b/ide/src/connectionEditorWebview.ts index 67783d90de..374d175606 100644 --- a/ide/src/connectionEditorWebview.ts +++ b/ide/src/connectionEditorWebview.ts @@ -297,12 +297,10 @@ export function connectionEditorScript(token: string, p: ConnectionEditorScriptI // this page. Both post "error"; only connectionEditor.ts answers with "fields". A field is a // FieldDescriptor from connectionForm.ts: its declared fields with their types. The two // unknown-typed values (value, defaultValue) and the carried-through envDefault and - // preserveValue are not typed there either, and are not required to be present: value and - // defaultValue are optional there because describe() leaves them undefined whenever the record - // does not set the key, the key is a secret or an env reference, or the schema has no default, - // and postMessage serializes the payload as JSON, which drops an undefined member. A presence - // check on either would discard nearly every ordinary "fields" message. choices is required and - // may be null; envKey, cast and a group's description may be absent and are never null. + // preserveValue are not typed there either. Nor is their presence checked: all four are + // optional there, and FieldDescriptor says why value and defaultValue are often absent, so a + // presence check would discard ordinary "fields" messages. choices is required and may be null; + // envKey, cast and a group's description may be absent and are never null. // control and type are closed sets: the lists FieldControl and ParamType are derived from. const FIELD_CONTROLS = ${embedJson(FIELD_CONTROLS)}; const PARAM_TYPES = ${embedJson(PARAM_TYPES)}; diff --git a/ide/src/test/suite/webview-receivers.test.ts b/ide/src/test/suite/webview-receivers.test.ts index cfdbcb70dc..c2082ccc31 100644 --- a/ide/src/test/suite/webview-receivers.test.ts +++ b/ide/src/test/suite/webview-receivers.test.ts @@ -382,6 +382,14 @@ const FIELDS_OK = { }), }; +/** FIELDS_OK with no field carrying `value` or `defaultValue`: both are optional (FieldDescriptor). */ +const FIELDS_BARE = variant(FIELDS_OK, (c) => { + for (const f of c.groups.flatMap((g: Payload) => g.fields)) { + delete f.value; + delete f.defaultValue; + } +}); + const connection: Receiver = { panel: "connection editor", key: "command", @@ -407,7 +415,7 @@ const connection: Receiver = {
${SENTINEL}
`, ), - wellFormed: { error: [ERROR_OK], fields: [FIELDS_OK] }, + wellFormed: { error: [ERROR_OK], fields: [FIELDS_OK, FIELDS_BARE] }, malformed: { error: badMessages(ERROR_OK), fields: [ @@ -814,6 +822,9 @@ suite("webview receivers discard a malformed payload and render a well-formed on const fields = FIELDS_OK.groups.flatMap((g) => g.fields); assert.ok(fields.some((f) => typeof f.envKey === "string" && typeof f.cast === "string")); assert.ok(fields.some((f) => !("envKey" in f) && !("cast" in f))); + const bare = FIELDS_BARE.groups.flatMap((g: Payload) => g.fields); + assert.ok(fields.some((f) => f.value !== undefined) && fields.some((f) => f.defaultValue !== undefined)); + assert.ok(bare.length > 0 && bare.every((f: Payload) => !("value" in f) && !("defaultValue" in f))); assert.ok(FIELDS_OK.groups.some((g) => typeof g.description === "string"), "no group has a description"); assert.ok(FIELDS_OK.groups.some((g) => !("description" in g))); assert.ok(fields.some((f) => f.choices === null) && fields.some((f) => Array.isArray(f.choices))); @@ -844,26 +855,6 @@ suite("webview receivers discard a malformed payload and render a well-formed on } }); - test("connection editor: a field with neither value nor defaultValue is accepted", () => { - // FieldDescriptor.value and .defaultValue are optional: describe() leaves them undefined in the - // ordinary case, and the JSON the host posts drops an undefined member. variant() is that JSON - // round trip; deleting both from every field is the extreme of it. Nothing may be discarded. - const bare = variant(FIELDS_OK, (c) => { - for (const f of c.groups.flatMap((g: Payload) => g.fields)) { - delete f.value; - delete f.defaultValue; - } - }); - const fields = bare.groups.flatMap((g: Payload) => g.fields); - assert.ok(fields.length > 0 && fields.every((f: Payload) => !("value" in f) && !("defaultValue" in f))); - const p = connection.load(); - const before = p.snapshot(); - p.deliver(bare); - assert.deepStrictEqual(p.errors.map(String), [], "the page threw"); - assert.notStrictEqual(p.snapshot(), before, "the message changed nothing"); - assert.deepStrictEqual(p.warnings, [], "discarded"); - }); - test("the engine prints only setting type tags this page's set holds", () => { // FieldDescriptor.type passes through from "connection schema --json". This reads the tags // _type_name() can return, so a tag added there fails here instead of the form going quiet. From 96e35b0d9f01adbb4e34869784ea24bb3b809923 Mon Sep 17 00:00:00 2001 From: Scott Hall Date: Tue, 27 Oct 2026 09:26:19 -0500 Subject: [PATCH 3/6] IDE: FieldDescriptor docs name when value and defaultValue are undefined; fixtures carry envDefault and preserveValue Round one of the diff check. `connection schema --json` always emits `default` (null when the factory has none), so defaultValue is undefined at least for a key the schema does not describe and for a schema param with no `default` member; the first commit's message said "a schema param with no default", which yields null, not undefined. value is undefined for a secret and an env reference too, not only an unset key; the doc now says so. The webview comment no longer calls envDefault and preserveValue "carried-through": the page never reads either. FIELDS_OK now carries an env default and an inline secret literal, and the fixture-shape test pins both members present and absent, so a shape clause refusing either would fail. BACKLOG #1123 Proposed PR title: IDE: FieldDescriptor.value and defaultValue are optional, matching what the connection editor webview receives Proposed ledger banner: #1123 engine half BUILT: the postMessage origin guard (mfTrusted plus per-render token) was already at all eight receivers (PRs 1755, 1791, 2196, 2203, 2212); this change makes FieldDescriptor.value/defaultValue optional to match the JSON the webview receives, with a positive-control receiver fixture. The ASVS 3.5.5 vault amendment and its re-read follow this merge. --- ide/src/connectionEditorWebview.ts | 11 ++++++----- ide/src/connectionForm.ts | 10 ++++++---- ide/src/test/suite/webview-receivers.test.ts | 6 +++++- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/ide/src/connectionEditorWebview.ts b/ide/src/connectionEditorWebview.ts index 374d175606..235d9c9393 100644 --- a/ide/src/connectionEditorWebview.ts +++ b/ide/src/connectionEditorWebview.ts @@ -296,11 +296,12 @@ export function connectionEditorScript(token: string, p: ConnectionEditorScriptI // One entry per message its hosts post: at least connectionEditor.ts and configEditors.ts use // this page. Both post "error"; only connectionEditor.ts answers with "fields". A field is a // FieldDescriptor from connectionForm.ts: its declared fields with their types. The two - // unknown-typed values (value, defaultValue) and the carried-through envDefault and - // preserveValue are not typed there either. Nor is their presence checked: all four are - // optional there, and FieldDescriptor says why value and defaultValue are often absent, so a - // presence check would discard ordinary "fields" messages. choices is required and may be null; - // envKey, cast and a group's description may be absent and are never null. + // unknown-typed values (value, defaultValue, envDefault, preserveValue) are not typed there + // either, and their presence is not checked: all four are optional there, and FieldDescriptor + // says why value and defaultValue can be absent, so a presence check would discard ordinary + // "fields" messages. This page does not read envDefault or preserveValue at all. choices is + // required and may be null; envKey, cast and a group's description may be absent and are never + // null. // control and type are closed sets: the lists FieldControl and ParamType are derived from. const FIELD_CONTROLS = ${embedJson(FIELD_CONTROLS)}; const PARAM_TYPES = ${embedJson(PARAM_TYPES)}; diff --git a/ide/src/connectionForm.ts b/ide/src/connectionForm.ts index d50975c66a..a2da84edd4 100644 --- a/ide/src/connectionForm.ts +++ b/ide/src/connectionForm.ts @@ -154,13 +154,15 @@ export interface FieldDescriptor { placeholder: string; /** * The default, raw (a checkbox needs its unset position). RENDER-ONLY — never post it. Optional - * because it is often undefined, and posting the descriptor to the webview serializes it as JSON, - * which drops an undefined member: the page can receive a field with no `defaultValue` at all. + * because it can be undefined (at least for a key the schema does not describe, and for a schema + * param that carries no `default`), and posting the descriptor to the webview serializes it as + * JSON, which drops an undefined member: the page can receive a field with no `defaultValue`. */ defaultValue?: unknown; /** - * The record's literal value, or undefined when the record does not set this key. Optional for the - * same reason as `defaultValue`: an undefined value is absent from what the webview receives. + * The record's literal value. Undefined at least when the record does not set this key, when the + * key is a secret, and when it holds an env reference -- so undefined does not mean "unset". Optional + * for the same reason as `defaultValue`: an undefined value is absent from what the webview receives. */ value?: unknown; isEnvRef: boolean; diff --git a/ide/src/test/suite/webview-receivers.test.ts b/ide/src/test/suite/webview-receivers.test.ts index c2082ccc31..7e4b5b4895 100644 --- a/ide/src/test/suite/webview-receivers.test.ts +++ b/ide/src/test/suite/webview-receivers.test.ts @@ -376,8 +376,10 @@ const SCHEMA: ConnectionSchema = { const FIELDS_OK = { command: "fields", groups: buildForm(SCHEMA, "mllp", "outbound", { - host: { env: "PEER_HOST", cast: "str" }, + host: { env: "PEER_HOST", cast: "str", default: "localhost" }, port: 6661, + // An inline secret literal, so one field carries preserveValue. + tls_key_password: "x", legacy_key: "kept", }), }; @@ -824,6 +826,8 @@ suite("webview receivers discard a malformed payload and render a well-formed on assert.ok(fields.some((f) => !("envKey" in f) && !("cast" in f))); const bare = FIELDS_BARE.groups.flatMap((g: Payload) => g.fields); assert.ok(fields.some((f) => f.value !== undefined) && fields.some((f) => f.defaultValue !== undefined)); + assert.ok(fields.some((f) => "envDefault" in f) && fields.some((f) => !("envDefault" in f))); + assert.ok(fields.some((f) => "preserveValue" in f) && fields.some((f) => !("preserveValue" in f))); assert.ok(bare.length > 0 && bare.every((f: Payload) => !("value" in f) && !("defaultValue" in f))); assert.ok(FIELDS_OK.groups.some((g) => typeof g.description === "string"), "no group has a description"); assert.ok(FIELDS_OK.groups.some((g) => !("description" in g))); From 619655dd87b513b11c5757b0dbac942eaabb78a9 Mon Sep 17 00:00:00 2001 From: Scott Hall Date: Tue, 27 Oct 2026 09:09:19 -0500 Subject: [PATCH 4/6] ADR 0201 slice 3: messagefoundry-toolkit is a published distribution The toolkit name is claimed on PyPI, so the install-provenance guard moves it from _UNPUBLISHED_DISTRIBUTIONS to _PUBLISHED_DISTRIBUTIONS. The unpublished set is empty again, the goal state. Probe read 2026-10-27 against PyPI's JSON API: 200 for /pypi/messagefoundry-toolkit/json, /0.5.0/json and /0.5.1/json; 404 for the control /0.0.0.dev999/json and for an unregistered project name. The 0.5.0 wheel's provenance names publisher MEFORORG/MessageFoundry, workflow release.yml. The harness at 0.5.1 returned 200 as the ADR's control. Moves no CLI subcommand or module; that is slice 4. Touches only the one distribution in the two sets that BACKLOG #1193 shares. BACKLOG #1192 --- tests/test_install_instruction_provenance.py | 28 +++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/tests/test_install_instruction_provenance.py b/tests/test_install_instruction_provenance.py index 1f9074cf24..b51bcc2a1f 100644 --- a/tests/test_install_instruction_provenance.py +++ b/tests/test_install_instruction_provenance.py @@ -41,27 +41,29 @@ #: Distributions this repository builds that are **not yet published to any index**. Until a name is #: claimed, no shipped text may tell a user to install it by bare name. #: -#: **`messagefoundry-toolkit` is here from ADR 0201 slice 2 until its first upload claims the name.** -#: The other three distributions this repo builds are registered on PyPI (`messagefoundry`, -#: `messagefoundry-harness`, `messagefoundry-webconsole` -- the last claimed 2026-07-29 by the first -#: `webconsole-v*` release, closing the ASVS 15.2.4 exposure where README.md instructed an index -#: install of an unclaimed name). ADR 0201 slice 3 moves the toolkit to `_PUBLISHED_DISTRIBUTIONS` -#: after a probe of the first tag's JSON API URL, with the harness as a control, shows the upload -#: landed. An empty set is the goal state, not a disabled guard. -#: -#: This matches an install VERB only, so it cannot see text that names the toolkit without one. ADR -#: 0201 section 1 does not rely on it to close the window: the release job uploads the toolkit before -#: the engine, so no engine naming the toolkit reaches PyPI while the name is unclaimed. +#: All four distributions this repo builds are registered on PyPI, so the set is empty. An empty set +#: is the goal state, not a disabled guard. `messagefoundry-webconsole` was claimed 2026-07-29 by the +#: first `webconsole-v*` release, closing the ASVS 15.2.4 exposure where README.md instructed an index +#: install of an unclaimed name. #: #: The set exists for the NEXT distribution: add its name here the moment `packaging//` lands and #: before any doc references it, then remove it once the first release claims the name. #: `test_a_new_distribution_must_be_classified` fails if a packaged distribution is neither listed here #: nor demonstrably published, so this cannot silently rot back to vacuous. -_UNPUBLISHED_DISTRIBUTIONS: frozenset[str] = frozenset({"messagefoundry-toolkit"}) +_UNPUBLISHED_DISTRIBUTIONS: frozenset[str] = frozenset() #: Distribution names registered on PyPI and published by this repo's release workflow. Listing one #: here asserts the name is CLAIMED -- the property that makes a bare-name install instruction safe. -_PUBLISHED_DISTRIBUTIONS = frozenset({"messagefoundry-harness", "messagefoundry-webconsole"}) +#: +#: **`messagefoundry-toolkit` moved here in ADR 0201 slice 3**, from `_UNPUBLISHED_DISTRIBUTIONS`, +#: where slice 2 put it. The probe, read 2026-10-27: PyPI's JSON API returned 200 for +#: `/pypi/messagefoundry-toolkit/json`, `/0.5.0/json` and `/0.5.1/json`, and 404 for the control +#: `/0.0.0.dev999/json`. The 0.5.0 wheel's provenance names publisher `MEFORORG/MessageFoundry`, +#: workflow `release.yml`, so the claim is this repo's first upload. That is a reading on that date, +#: not something this test re-checks: no test here reaches the network. +_PUBLISHED_DISTRIBUTIONS = frozenset( + {"messagefoundry-harness", "messagefoundry-toolkit", "messagefoundry-webconsole"} +) #: Files whose install commands are shipped to, or executed by, someone other than a maintainer. #: CI workflows and internal handoffs are excluded deliberately: they install from the source tree by From 2e7ad5fadb7e59346dbdb6ebeacbf31aa2a3b1f5 Mon Sep 17 00:00:00 2001 From: Scott Hall Date: Tue, 27 Oct 2026 09:20:04 -0500 Subject: [PATCH 5/6] ADR 0201 slice 3: the unpublished-set comment names no count The comment said "All four distributions"; a pinned count goes stale at the next distribution. /simplify finding. BACKLOG #1192 --- tests/test_install_instruction_provenance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_install_instruction_provenance.py b/tests/test_install_instruction_provenance.py index b51bcc2a1f..45d3ed3620 100644 --- a/tests/test_install_instruction_provenance.py +++ b/tests/test_install_instruction_provenance.py @@ -41,7 +41,7 @@ #: Distributions this repository builds that are **not yet published to any index**. Until a name is #: claimed, no shipped text may tell a user to install it by bare name. #: -#: All four distributions this repo builds are registered on PyPI, so the set is empty. An empty set +#: Every distribution this repo builds is registered on PyPI, so the set is empty. An empty set #: is the goal state, not a disabled guard. `messagefoundry-webconsole` was claimed 2026-07-29 by the #: first `webconsole-v*` release, closing the ASVS 15.2.4 exposure where README.md instructed an index #: install of an unclaimed name. From 7c0eb57e94f54aea83564b505fb04b8651f0ea0d Mon Sep 17 00:00:00 2001 From: Scott Hall Date: Tue, 27 Oct 2026 09:49:15 -0500 Subject: [PATCH 6/6] ADR 0201 slice 3: code-review round 1 repairs The _refuse_toolkit_command docstring said the toolkit was not yet on PyPI; it now says the line was written while the name was unclaimed and slice 3 recorded the claim. The provenance test restores the note that its detector matches an install verb only, and its probe record names the harness control, scopes each control path, and rests "this repo's upload" on the release list plus every wheel's provenance, not on 0.5.0 alone. BACKLOG #1192 Proposed PR title: ADR 0201 slice 3: messagefoundry-toolkit is a published distribution (BACKLOG #1192) Proposed ledger banner: #1192: ADR 0201 slice 3 built (the install-provenance guard lists messagefoundry-toolkit as published after a PyPI probe with controls). Slice 5 is on its own PR; slices 4, 6, 7 and the release-gated 15.2.3 re-score (slice 8) remain. --- messagefoundry/__main__.py | 7 ++++--- tests/test_install_instruction_provenance.py | 15 +++++++++++---- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/messagefoundry/__main__.py b/messagefoundry/__main__.py index 9e9ba60159..d77a5db8bb 100644 --- a/messagefoundry/__main__.py +++ b/messagefoundry/__main__.py @@ -206,9 +206,10 @@ def _refuse_toolkit_command(command: str, argv: Sequence[str]) -> int: ``_emit_error`` writes JSON to stdout or one line to stderr, never both, and its own return value is 1, so it is not returned. ``argv_wants_json`` is the one pre-parse JSON rule both commands use. - The line names no install command. The toolkit distribution is not yet on PyPI, and - tests/test_install_instruction_provenance.py refuses shipped text that tells a user to install - an unclaimed name. + The line names no install command. It was written while the toolkit name was unclaimed on PyPI, + when tests/test_install_instruction_provenance.py refused shipped text telling a user to + install it by name. ADR 0201 slice 3 recorded the name as claimed; the docs that give a + bare-name install arrive with slice 4. """ _emit_error( f"`{command}` is not a messagefoundry command. It is an authoring command: run it as " diff --git a/tests/test_install_instruction_provenance.py b/tests/test_install_instruction_provenance.py index 45d3ed3620..636e11cc62 100644 --- a/tests/test_install_instruction_provenance.py +++ b/tests/test_install_instruction_provenance.py @@ -46,6 +46,9 @@ #: first `webconsole-v*` release, closing the ASVS 15.2.4 exposure where README.md instructed an index #: install of an unclaimed name. #: +#: This matches an install VERB only, so it cannot see text that names a listed distribution without +#: one, such as a `Requires-Dist` line or prose saying a package depends on it. +#: #: The set exists for the NEXT distribution: add its name here the moment `packaging//` lands and #: before any doc references it, then remove it once the first release claims the name. #: `test_a_new_distribution_must_be_classified` fails if a packaged distribution is neither listed here @@ -57,10 +60,14 @@ #: #: **`messagefoundry-toolkit` moved here in ADR 0201 slice 3**, from `_UNPUBLISHED_DISTRIBUTIONS`, #: where slice 2 put it. The probe, read 2026-10-27: PyPI's JSON API returned 200 for -#: `/pypi/messagefoundry-toolkit/json`, `/0.5.0/json` and `/0.5.1/json`, and 404 for the control -#: `/0.0.0.dev999/json`. The 0.5.0 wheel's provenance names publisher `MEFORORG/MessageFoundry`, -#: workflow `release.yml`, so the claim is this repo's first upload. That is a reading on that date, -#: not something this test re-checks: no test here reaches the network. +#: `/pypi/messagefoundry-toolkit/json`, `/pypi/messagefoundry-toolkit/0.5.0/json` and +#: `/pypi/messagefoundry-toolkit/0.5.1/json`, and for the harness control +#: `/pypi/messagefoundry-harness/0.5.1/json`; it returned 404 for the controls +#: `/pypi/messagefoundry-toolkit/0.0.0.dev999/json` and an unregistered project name. The project +#: lists releases 0.5.0, 0.5.1 and 0.5.2, and each wheel's provenance names publisher +#: `MEFORORG/MessageFoundry`, workflow `release.yml`. So every release on the project is this repo's +#: upload. That is a reading on that date, not something this test re-checks: no test here reaches +#: the network. _PUBLISHED_DISTRIBUTIONS = frozenset( {"messagefoundry-harness", "messagefoundry-toolkit", "messagefoundry-webconsole"} )