Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions ide/src/connectionEditorWebview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,9 +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. 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)};
Expand Down
17 changes: 13 additions & 4 deletions ide/src/connectionForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,10 +152,19 @@ 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 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. 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;
/** The referenced environment key, when `isEnvRef`. */
envKey?: string;
Expand Down
19 changes: 17 additions & 2 deletions ide/src/test/suite/webview-receivers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -376,12 +376,22 @@ 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",
}),
};

/** 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",
Expand All @@ -407,7 +417,7 @@ const connection: Receiver = {
<div id="error">${SENTINEL}</div>
<button id="save"></button><button id="cancel"></button><button id="delete"></button>`,
),
wellFormed: { error: [ERROR_OK], fields: [FIELDS_OK] },
wellFormed: { error: [ERROR_OK], fields: [FIELDS_OK, FIELDS_BARE] },
malformed: {
error: badMessages(ERROR_OK),
fields: [
Expand Down Expand Up @@ -814,6 +824,11 @@ 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(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)));
assert.ok(fields.some((f) => f.choices === null) && fields.some((f) => Array.isArray(f.choices)));
Expand Down
7 changes: 4 additions & 3 deletions messagefoundry/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 "
Expand Down
33 changes: 21 additions & 12 deletions tests/test_install_instruction_provenance.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,27 +41,36 @@
#: 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.
#: 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.
#:
#: 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.
#: 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/<name>/` 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`, `/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"}
)

#: 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
Expand Down
Loading