✂️ Make xmd plan produce approved XMD source only (#724) - #749
Conversation
`xmd plan` mapped one request to a reviewed program and could then run it.
Two responsibilities in one command meant a caller who wanted the program
kept had to remember which flags configured which half, and a caller who
only wanted source still paid for a grammar describing an execution.
It now produces the artifact and stops. One invocation executes exactly one
root — the packaged plan command document — awaits its complete teardown,
structurally revalidates the returned bytes, and delivers them to stdout or
to an exclusively created `--output` path. Whether that program runs is the
caller's composition: `xmd plan … | xmd run -`, or a saved artifact a later
`xmd run` names.
The execution capability is gone structurally rather than left unselected:
`PlanCommand.run`, `PlanExecution`, `PlanExecutionConfig`, the
`PlanDependencies.execute` callback, `planExecutor`, the final
`retainedSource("<plan>")` run, and the command-side property binding,
signature freezing and reserved-token plumbing all had no consumer left.
The final host gate stays, and is now structural — the same contract
`<AdmitPlan>` applies — so a Plan declaring required root props is admitted
rather than refused for arguments nobody has offered it yet.
The grammar keeps one request, `--include`, `--agent-provider`,
`--default-agent`, `--session`, `--timeout`, `--output`, and ordinary help
and version. Every option that configured the former execution is refused by
name in fixed preflight, before the general parser can drop or coerce a
token: `--run` reports the migration naming both compositions, and each of
the rest reports that `xmd run` is where a program is configured. Authorship
now settles a provider and a default agent and no permission mode at all.
The same options are unchanged under `xmd run`.Four blockers from the #724 feedback review. The command validated approved bytes twice, but nothing could tell the two apart: `<AdmitPlan>` and the host's gate reached the same conclusion from separately built inputs, so the C10 that removed a component during teardown was answered by the first of them and deleting the second left every suite green. The invocation now settles one structural check — `validateDocumentStructure` under the ordinary run-profile registry, the `<plan>` identity, the caller's includes and the run profile's declarations — and hands that one to `<CheckDraft>`, to `<AdmitPlan>` and to itself. What the three can disagree about is when it was asked, which is the whole point of keeping the last one: C10 and PS9 now let both Component gates really validate and succeed, move the tree immediately after that admission, and watch the host refuse. Deleting the host's gate fails both. `AuthorshipProfile.installElicitation` and `.assess` had no consumer and are gone; the dependencies `planComponentDeclaration` captures are unchanged. `--help` is lifted out of argv before any command's own grammar runs, so `xmd plan --help --run` printed a page describing a command that would have refused the caller. A shared pure `removedPlanOption()` — the same classification the scan uses — answers first, in either order. Help that names no removed option is still the exact approved help. Generated-property recognition was a prefix test, so `--propspective`, `--no-propspective` and a bare `--no-props` were told to configure their program with `xmd run`. It is now exactly `--props`, `--props-*` and `--no-props-*`; everything else keeps the generic unknown-option refusal. The three session-directory diagnostics are restored to their base wording — they were never part of the approved source-only copy. Stale prose that still had an approved Plan running or receiving the ordinary run stack is corrected in `agent-stack.ts`, the repository-provider comment in `cli.ts`, and `plan.test.ts`.
| // leases on the checkouts this document selects, and the evidence of what it | ||
| // published. `xmd run` and an approved `xmd plan --run` supply the live one; | ||
| // published. `xmd run` supplies the live one; `xmd plan` executes no document | ||
| // of a caller's and reaches this line for none. |
There was a problem hiding this comment.
Redundant comment — restates what the code does.
| // of a caller's and reaches this line for none. |
| // is lifted out of argv early enough that a command's own grammar never sees | ||
| // the invocation it was written on, so a Plan command line naming a removed | ||
| // option would be answered with a page describing a command that would refuse | ||
| // it. It is refused here instead, in either order, having read nothing. |
There was a problem hiding this comment.
Redundant comment — restates what the code does.
| // it. It is refused here instead, in either order, having read nothing. |
| // The rest of what `xmd plan` decides on its own — cardinality, an unknown | ||
| // option, an empty session — answered before the shared checks below could | ||
| // report one of them as something else. The removed options were answered | ||
| // above, ahead of help. |
There was a problem hiding this comment.
Redundant comment — restates what the code does.
| // above, ahead of help. |
| }, | ||
| // Who writes, and nothing else. There is no permission mode to settle: | ||
| // this command starts no program, and the ceiling authorship runs under | ||
| // is the host's rather than the command line's. |
There was a problem hiding this comment.
Redundant comment — restates what the code does.
| // is the host's rather than the command line's. |
| // The two commands a `--timeout` bounds. `xmd plan`'s deadline encloses | ||
| // something different from a run's — the catalog, the assistant session, | ||
| // every repair, the human review, provider teardown, final validation and the | ||
| // artifact — and covers no later program, because it starts none. |
There was a problem hiding this comment.
Redundant comment — restates what the code does.
| // artifact — and covers no later program, because it starts none. |
| // Built before the declaration exists, and handed to it: the packaged `<Plan>` | ||
| // description is the declaration an ordinary run resolves, so what the draft | ||
| // check, the admission and the gate below all ask about is the profile the | ||
| // approved program would actually run in. |
There was a problem hiding this comment.
Redundant comment — restates what the code does.
| // approved program would actually run in. |
| // about a candidate — and however recently `<AdmitPlan>` concluded it — these | ||
| // are the bytes a later `xmd run` would execute, and the tree they resolve | ||
| // against has had a whole teardown to move since. So they are checked once | ||
| // more, as though nothing had ever validated them. |
There was a problem hiding this comment.
Redundant comment — restates what the code does.
| // more, as though nothing had ever validated them. |
| // wrote it — no fence, no heading, no trailing newline of this command's — | ||
| // so a caller can pipe it into `xmd run -`, a file, a diff or another | ||
| // program. A caller who named `--output` already has it, and gets a quiet | ||
| // command instead. |
There was a problem hiding this comment.
Redundant comment — restates what the code does.
| // command instead. |
| export const AGENT = "scripted-agent"; | ||
| /** The answer a case that is not about validation wants: this is a program. */ | ||
| // deno-lint-ignore require-yield |
There was a problem hiding this comment.
Redundant comment — restates what the code does.
| // deno-lint-ignore require-yield |
| // The deterministic seam standing where production's answer goes, recording | ||
| // every candidate it was asked about — the draft check's and the | ||
| // admission's alike, which is every time these bytes are decided on. | ||
| // deno-lint-ignore require-yield |
There was a problem hiding this comment.
Redundant comment — restates what the code does.
| // deno-lint-ignore require-yield |
PR #749: ✂️ Make |
Two CI-only failures, both from checks that do not run in `deno task check`. `tsconfig.node.json` targets ES2022 and Deno's own lib is newer, so `Object.keys(scan).toSorted()` passed locally and failed all seven `test-node` shards with TS2550. The assertion is about which fields the scan established, not about their order, so it compares the two directions of set membership instead of sorting — `unicorn(no-array-sort)` would only have suggested `toSorted` back. `site/` runs its own `deno task check`, which begins with `deno fmt --check`; `oxfmt` formats `packages/` and never saw the homepage paragraph. Reflowed by `deno fmt`, leaving the sentence PS12 pins on one line.
Uh oh!
There was an error while loading. Please reload this page.
Closes#724.
Why
xmd plandid two jobs. It turned a request into a reviewed program, and itcould then run that program. A caller who wanted the artifact kept had to
remember which half of the grammar configured which job, and a caller who only
wanted source still paid for a command line describing an execution — a journal,
a permission mode, exec and fetch deadlines, and the generated root properties of
a document that did not exist yet.
Splitting them makes the relationship explicit and lets the caller decide when a
planned program runs.
What changes
Before:
After:
xmd planexecutes exactly one root — the packaged plan command document — andstarts no program on any path. Without
--output, stdout carries the approvedsource and nothing else; with it, the path is created exclusively after approval,
teardown and validation, an existing path is left untouched, and stdout stays
empty.
The grammar keeps one request,
--include,--agent-provider,--default-agent,--session,--timeout,--output, and ordinary--helpand
--version. Every option that configured the former execution is refused byname before anything is read:
The same options are unchanged under
xmd run.How it works
One invocation settles one structural check —
validateDocumentStructureunder the ordinary run-profile registry, the<plan>identity, the caller's ordered includes and the run profile'sdeclarations — and hands that same function to three places:
<CheckDraft>answers a draft with it,
<AdmitPlan>admits the approved bytes with it, andthe host asks it once more after the command document has settled. What the
three can disagree about is when it was asked, not what: a component the
approved Plan names, removed after a successful admission, is caught by the last
one and nowhere else.
Validation is structural on purpose. A Plan whose root declares required
properties is a Plan; the values belong to whoever runs it later, and
xmd planhas no property source to resolve them from.
Review guide
Start with:
specs/plan-command-spec.md— rewritten around the singlesource artifact, in comprehension order.
Then review:
packages/cli/src/plan-args.ts— the complete fixed grammar, both approvedrefusals, and
removedPlanOption().packages/cli/src/plan-component.ts—StructuralValidation,structuralValidation(), and the two privates that now share it.packages/cli/src/plan.ts— one root, one gate, two sinks.packages/cli/src/cli.ts—planConfig, the help epilogue, the preflightahead of the help short-circuit, and the removal of
planExecutor.Look carefully at:
plan.test.tsC10: draft check → review → authorship teardown→ admission → host gate. That order is the reason the last check exists.
packages/cli/src/agent-stack.ts—AgentStacksplit intoAuthorshipStackplus the permission mode a run installs.
What must stay true
execution capability to reach — no
PlanExecution,PlanExecutionConfig,planExecutor,PlanDependencies.executeor second root — and checked byPS6/PS9/PS10, whose approved program writes a file and then fails.
exact: trueon the declarationand a single
process.stdout.write, checked by PS6 (one chunk, byte for byte)and PS7 (the same bytes in the file).
validateDocumentStructure, checked by PS8 andplan.test.tsC7.the one injected
StructuralValidation, checked by C10 and PS9 — deleting thegate fails both.
xmd runis untouched. Checked by PS5 and by the existing run suites.AuthorshipStackhas no field for one, checked byplan.test.tsC5.How to verify it
deno task test \ packages/cli/tests/plan-args.test.ts packages/cli/tests/plan-cli.test.ts \ packages/cli/tests/plan.test.ts packages/cli/tests/plan-command-document.test.ts \ packages/cli/tests/plan-component.test.ts packages/cli/tests/syntax-cli.test.ts \ packages/cli/tests/packaged-document.test.tsfrom a real subprocess whose agent name resolves to nothing and whose
HOMEisempty, and fail if a refusal arrives after a catalog, a provider or a session
directory — or if it never arrives because
--helpshort-circuited first.xmd plan --helpoutput and fail if a removedoption returns to it, or if splitting the grammar narrowed
xmd run.ran.txtand then exits 3; it provesnon-execution and fails if anything interprets the source — success plus the
exact bytes plus neither observation is the whole claim.
--outputpath from inside the authorship frame's ownteardown and fails if the file is created early or truncated.
and fails if full root-prop validation replaces the structural check.
plan.test.tsC10 let the draft check and<AdmitPlan>reallyvalidate and succeed, move the tree immediately after that admission, and watch
the host refuse. Both fail if the host's final validation is deleted —
independently confirmed.
--runother than to say it is gone.Also run locally, all green:
deno task check,deno task lint,git diff --check, anddeno task test --changed=origin/main(43 passed, 284 steps, 0 failed) — which includes
scripts/tests/cli-npm-bin.test.ts, where the emitted npm package answersxmd plan --runwith the migration before an Agent is needed, andscripts/tests/plan-component-compiled.test.tsafterdeno task build, wherethe compiled binary ships the same
<Plan>bytes and the source-only help.Scope
Included
xmd planproduces approved source only; the execution capability is removedstructurally.
gate.
specs/plan-command-spec.md,architecture.md,specs/executable-mdx-spec.md,specs/root-document-props-spec.md,specs/acp-client-spec.md,README.mdand the homepage.Intentionally unchanged
<Plan>'s public behaviour from Make<Plan>emit or capture approved XMD source #722 — bare emits,ascaptures, neitherevaluates — and its private closure, durable artifact, replay parsing and
session placement.
<Plan>review policy: the repair and presentation bounds, the automaticexplanation turn, and every authored ending's wording except the four
source-only sentences approved for this story.
xmd run.--verboseand--journalfor Plan authorship: Makexmd planauthorship observable during and after an invocation #676 introduces them withtheir behaviour, and nothing is reserved here. Complete-program
<Evaluate>is Make
<Evaluate>evaluate complete XMD programs #713's.New abstractions
StructuralValidationandstructuralValidation()(
packages/cli/src/plan-component.ts) exist because three call sites had toask one question of one environment:
<CheckDraft>,<AdmitPlan>and thecommand's own gate. Three consumers in production, and it is the seam the
final-gate evidence needs.
AuthorshipStack(packages/cli/src/agent-stack.ts) exists because planningsettles a provider, a default agent and adapters and no permission mode;
AgentStackextends it forxmd run. Two consumers.AuthorshipFlags(packages/cli/src/agent-config.ts) is the same split at theflag layer.
removedPlanOption()(packages/cli/src/plan-args.ts) exists because theremoval has to be decidable before
--helpshort-circuits the dispatch, andit shares its classification with
scanPlanArgsso a spelling cannot beremoved to one and unknown to the other.
Each new abstraction has multiple concrete uses or a clear justification.
No speculative functionality is included.
Generated or mechanical changes
packages/cli/tests/plan-cli.test.tsandpackages/cli/tests/plan-args.test.tsare largely rewritten around the PS rows rather than edited; read them as new
files.
packages/cli/src/plan.tsandplan-args.tsare the removedexecution and property-binding plumbing and carry no behaviour of their own.
Risks and limitations
--timeoutexpiry still reports "the run exceeded its--timeoutof Nms andwas cancelled". The wording is deliberately unchanged, but "the run" now names
something this command does not have.
xmd testandxmd workflowrefuse agent and timeout options with a sentencenaming where each belongs.
--agent-provider,--default-agentand--timeoutstill belong to both commands; the permission flags and theexec/fetch deadlines are now
xmd run's alone, so a smallbelongsTo()helperanswers per flag rather than one sentence that had become half untrue.
no artifact format changed.
Scope confirmation