Repository navigation
✨ One place for a run's setup, and a Syntax description anything may compose - #912
Conversation
Ordinary setup gets one execution-owned entry point, and live `<Syntax>` descriptions become ordinary composed state. `Execution.init(): Operation<void>` joins the existing owner. Canonical core invokes it in `invoke()` after the trusted host contributions are copied and their revocation armed, and before the installation callbacks, documentation capture and public `execute` policy — so a host installs its live dependencies once per execution, in the scope the invocation already owns, and nothing it installs can reach back and change what was already captured. The public default is no-op rather than a refusal: an execution with no host middleware has no live dependencies to establish and invents none. `Syntax` is a contextual Api with `Operation`-valued `symbols` and `text` properties and an Operation-valued `documentation(names)` method, exported from `/api` and the root. Its defaults are reusable lazy operations. The reference an execution derives is bound as the Api's *fallback*, not installed as middleware, so a host that stated its own profile with the new `useSyntax(source)` helper on `/host` still answers; a canonical evaluation boundary narrows symbols only, and keeps the enclosing named-documentation owner. `ExecutionInstallation.symbols` and its one-provider count rule are gone, and `xmd plan`, `xmd syntax` and the Plan fixtures bind their descriptions through the helper instead. A description now registers, resolves and authorizes nothing: it may name a component this host will refuse, or hide one it will run, and preflight decides either way. The protected component keeps its authenticated invocation, site and form checks, safe named refusal, publication and secret gate, and records the final middleware answer inside its existing durable live callback — completed replay restores that text without consulting any live provider. Intentionally unchanged: U4's metadata/identity work, U5's generated-selection and input removal, durable preparation's position after retained-history admission, Plan's structural validation and trusted reads, REPL's Agent attachment and journal lifetimes, and every other installation member.
|
Reviewed
Integration check: a scratch three-way merge with #913 applies cleanly. All existing anchors in the three affected documents survive, and #913's README reading routes and AGENTS guidance remain unchanged. Preserve that result when integrating the branch. The evidence above is a source/spec trace through SA2/SA6–SA9 and the initialization lifecycle; this comment does not reopen the accepted A2/A3 API decisions or the previous implementation verdict. |
Review feedback on #912. Prose and one test fixture; no behavior changes. Descriptions and execution permission are described as the separate things the SA rows prove. The spec's root sentence holds for the uncustomized default: descriptive middleware that filters the effective symbols separates the authoring and effective sets at a root as well, and the per-entry availability label is what says so. `SyntaxApi.symbols` is the effective structured description and `text` the effective text whose *default* renders it, rather than "what may be written" and "the same answer as Markdown". The captured catalog and the effective answer are separated where they were conflated: canonical collection owns the catalog a package ships, and live middleware composes in front of it — which SA9 shows is not necessarily a composition the host assembled. `SyntaxReference`'s rationale states the boundary that holds instead of the one that no longer explains it: component selection, invocation identity and durable recording stay canonical, and a description reaches none of them. The detailed contract stays in its owning chapter. `architecture.md`'s *Capability-backed execution* and *The syntax symbols boundary* keep the ordering, precedence, per-occurrence basis and scope-restoration invariants; the language spec keeps signatures, representative usage, observable behavior and the EI/SA rows, and links to those owners. Module headers keep the surprising constraints and lose the repeated introductions. `ExecutionApi`'s interface comment no longer promises every handler an `ExecutionRequest` — `init` is handed nothing, `document` a `DocumentRequest` — and the `Syntax` re-exports identify one descriptor and its owner instead of disagreeing about whether describing a site is a consumer boundary. The EI-child plugin fixture is written as `.mjs`. Its project is a bare directory with no `package.json`, so a `.ts` plugin there loads as CommonJS under Node, and the dependencies core reaches through publish an import condition only. The suite now passes under Deno, Node and Bun.
|
PASS — documentation alignment with #913 on
I reviewed the complete eight-file delta. The five changed production TypeScript files have identical parsed code with comments removed; the executable change is confined to the ESM test fixture. A scratch three-way merge with the accepted #913 tree applies cleanly, preserves every existing anchor in the three affected documents, and leaves its README reading routes and AGENTS guidance unchanged. No further changes are requested by this documentation review. A2/A3 remain settled. Keeping both commits preserves the reviewed revision and makes the correction traceable; no squash or force-push is needed for this review. |
U3 of the upstream-repository prerequisite stack. Stacked on landed U2
(#906); based on
main.Why
Two problems with one shape: something a run needs has no place to be
established, so it gets established somewhere that is not the run.
A command host had no seam for its own live dependencies. A Files provider, a
recorder or a service had to be arranged outside the execution and reach in,
which is why two REPL entries could not each own their own state, and why an
isolated
host="run"child inherited whatever a parent happened to install.And a
<Syntax />description travelled by value on the private expansionenvironment. One execution accepted exactly one
ExecutionInstallation.symbolsprovider, two were refused rather than ordered, and nothing could narrow,
transform or extend what a document was shown.
xmd planneeded a differentprofile than the execution writing the Plan, so it got a bespoke installation
member instead of composition.
What changes
Before:
ExecutionInstallation.symbolscarried one descriptive provider perexecution; two were refused. The description was a value on core's private
environment, and no middleware could reach it.
After:
Execution.init(): Operation<void>runs once in the scope the invocationalready owns. A host installs its live dependencies there and delegates.
Syntaxis a contextual Api anyone may compose on. A host states its ownprofile with
useSyntax(source); the execution's derived description is thefallback behind it; a trusted evaluation boundary narrows symbols for the
subtree it evaluates.
ExecutionInstallation.symbolsand its count rule aregone.
component this host will refuse, or hide one it will run; preflight decides
either way.
How it works
initis placed after the trusted copies and armed revocation, so nothing ahost installs can reach back and change what is already authoritative, and
before the installation callbacks and documentation capture, so what it
establishes is there for both. Its public default is no-op rather than a
refusal: an execution with no host middleware has no live dependencies to
establish and invents none.
The two ways a description gets supplied are deliberately different slots.
useRootSyntaxReferencebinds the execution's own reference as the value thedefault reads — a default is furthest from the caller, so an enclosing
useSyntax()profile still wins.useNarrowedSyntaxinstalls real middlewareat
minfor a boundary that admitted less than its enclosing site; that one ismeant to shadow, and it ends with the scope that installed it.
Review guide
Start with:
packages/core/src/syntax-api.tsThen review:
ExecutionApi.initand theExecutiondescriptor —packages/core/src/execute.tsinitdispatch ininvoke(), and the derived reference bound as thefallback in
executeDocument()packages/core/src/use-syntax.ts, andreferencing()inpackages/core/src/syntax-reference.ts— the one-basis-per-occurrence rulepackages/core/src/components/Syntax.ts— the dispatch moved inside thedurable live callback
packages/cli/src/syntax.tsuseCommandSyntax, and the Plan path(
cli.ts,plan.ts,plan-writer-profile.ts)Look carefully at:
syntax-reference.tsdocumentation(): it resolves the authoring basis,publishes it on a scoped context, then reads effective symbols through the
Api. That re-entry is what lets a host or boundary decide the availability
annotation, and the scoped basis is what stops one occurrence inspecting a
moving root twice. Two occurrences enter two scopes and resolve independently.
minslot inuse-syntax.tsanduseNarrowedSyntax. Getting thesebackwards means a host profile is shadowed by core's own fallback.
expand.tsnarrowedSyntax: set only where a boundary replaced thereference. The ordinary handle leaves it absent on purpose.
init(EI4/EI5) — settlement must wait foracquired teardown, and reach no policy, no root import and no body.
What must stay true
<Syntax>'s name, selected definition and invocation identity arecanonical core's, however freely the description is customized — enforced by
the protected tier and issued-invocation checks, checked by
syntax-loaded-copy,evaluate-loaded-copyand SA9.in
selectComponent()/preflight, checked by SA6 (a described-but-forbiddenname is still refused) and SA7 (a hidden admitted component still runs).
<Syntax>replay consults no live provider — enforced by queryingthe Api only inside the durable live callback, checked by SA5, which installs
symbols, text, catalog and source providers that throw if reached and
replays both a complete and a partial history.
initcannot alter what was already captured — enforced by its positionafter the trusted copies, checked by EI3.
invocation scope, checked by EI6 (concurrent), EI-REPL (two entries under one
session attachment) and EI-child (isolated child reassembles, inherits no
parent handler).
answer — enforced by the scoped basis in
referencing(), checked by SA2'ssource === 1 && effective === 1and SA4's two moving occurrences.provider failures stay terminal — checked by SA8, including a
provider-thrown
UnknownComponentError.How to verify it
Tier EI (
packages/core/tests/execution-init.test.ts) and tier SA(
packages/core/tests/syntax-api.test.ts) are new. Both are written to fail ona plausible wrong implementation rather than to confirm the right one:
init, install, documentation, execute, documentand fails if
initis moved intoexecutemiddleware after capture.the stream is empty; they fail if settlement merely signals cancellation
without joining teardown.
does, and that the provider is gone again outside the scope.
.texttwice over a file that changes in between and asserts twodistinct answers — it fails if a default is a stored consumable generator.
0; it failsif a non-delegating provider still triggers default inspection.
createApidescriptor with the same name andshows its middleware answers the protected component — the composition is
intentional, and harmless because descriptions grant nothing.
deno task test packages/core/tests/execution-init.test.ts packages/core/tests/syntax-api.test.tsResults at this revision are in Risks below.
Scope
Included
Execution.init, exported with the descriptor and request types from@executablemd/core/api.Syntax/SyntaxApiwithOperation-valuedsymbolsandtextand anOperation-valued
documentation(names), from/apiand the package root.useSyntax(source)from@executablemd/core/host.ExecutionInstallation.symbols, its provider-count refusal, itsargument through
executeDocument()/rootSyntaxReference(), and theSyntaxSymbolsProvideralias.xmd plan,xmd syntax, the REPL and the Plan fixtures.executable-mdx-spec§5.3.1 and §§8–9,plan-command-spec,and new spec tiers EI and SA.
Intentionally unchanged
validation, not ordinary initialization.
read grants and Scope observer stay removed.
agent.owning(key)attachment, lifecycle installation and per-entryjournal lifetimes.
initadds state below that owner.ExecutionInstallationmember. No blanket migration.New abstractions
SyntaxApi+ theSyntaxdescriptor exist because three consumers alreadyasked the same three questions in three places: the protected component,
xmd syntax, and a Plan's authorship. Accepted as architecture amendment A2.useSyntax(source)exists because a host has a lazily-inspectable profileand documentation contributions that only exist in its assembly scope;
binding one without the other is what sent named metadata to the wrong
catalog. Accepted as A3, and it is the only public setup helper added.
useNarrowedSyntax/useRootSyntaxReferenceare local to core and exist tokeep the shadowing slot and the fallback slot from being confused.
PlanDependencies.installSyntax/PlanWriterProfile.installSyntaxareprivate to the CLI and replace the removed
symbolsmember one-for-one.New dependencies
None. A2 uses the installed
@effectionx/context-api0.6.0 contract with noadapter, cast or runtime hook. No lockfile or manifest change.
Generated or mechanical changes
packages/core/src/syntax-markdown.ts/documentation-index.ts/mod.ts:NO_DOCUMENTATIONmoves to the renderer that emits it, keeping itspackage-root export. Not cosmetic — it cuts a real module cycle. Reverting it
fails
evaluation-profile.test.tswithReferenceError: Cannot access 'SYNTAX_PROTECTED' before initialization, becausecomponents/Syntax.ts → syntax-api.ts → syntax-markdown.ts → documentation-index.ts → components/registration.ts → components/protected.tscloses a cycle through
protected.ts's own module body.packages/core/tests/syntax-component.test.ts: ~14 call sites wrap directreference.documentation(...)reads in a helper that installs the referencefirst. Required, not stylistic — availability is now read through the Api, so
the reference is no longer usable standalone. No assertion changed.
packages/core/tests/protected-content.test.tsand thestating(...)fixtures in
syntax-component.test.ts/evaluate-component.test.tsfollowthe
SyntaxReference.symbols()return type and the removed installationmember. No assertion changed.
packages/cli/src/plan-writer-profile.tsis mostly re-indentation: the bodymoves into a nested
scoped(). The descriptive setup enters the target run'sdeclarative bootstraps, which re-register names the operational install above
already holds, and registration refuses
two registrations for "AgentProvider" at the same scopeunless the shadowing one nests.Risks and limitations
it. This follows from name-based contextual dispatch and is accepted: a
description grants nothing, and the protected definition, routing and
admission are unaffected. Reviewers who disagree should say so — it is a
property of the accepted A2 boundary, not an implementation accident.
site.syntaxremains a presence gate in the protected body even thoughthe answer now comes from the Api. Kept deliberately as the site validation
the contract preserves, so an expansion driven outside an execution refuses
while naming the element. It is reachable only in that case.
SyntaxSymbolsversion and schema areunchanged, and completed replay returns retained text.
or lockfile changes, so nothing migrates back.
Evidence at this revision
Base
a0320347. Every command below was re-run after rebasing onto it, andafter
deno task setup(the rebase brought #908'spackage.jsonlintscriptchange, which the build preflight refuses to build against — no dependency or
lock delta, and setup left both clean).
execution-init,execution-protocol,files-fatal,workflow-component-bundlesyntax-api,syntax-component,*-loaded-copy,evaluation-profile,generated-xmdtesting-execution-host,repl-execution,syntax-cli,plan-command-document,plugin-hostworkflow-run,workflow-run-journalplan-cli,plan,plan-host-acts,agent-adapterstsx --test)pnpm run lint(oxlint, prose oxlint,lint-architecture,oxfmt --check, knip)scripts/tests/oxlint-policy.test.ts(#908's banned-word regressions)deno task buildplan-component-compiled,plugin-compileddeno check --frozenover the four entrypointsThe compiled evidence was re-run against a binary rebuilt at this revision, not
the pre-rebase one.
Planner and Architect both returned PASS at
ac466780, the pre-rebase revisionwith identical behavior; the rebase resolved three prose-only conflicts from
#908 (
claim→registerin architecture, two reworded header hunks incomponents/Syntax.tswhere the landed vocabulary was adopted, and theExecutionInstallation.symbolsdoc this PR deletes). No code conflicted.Scope confirmation