✨ Evaluate generated XMD text with constrained authority (#713) - #756
✨ Evaluate generated XMD text with constrained authority (#713)#756taras wants to merge 7 commits into
Conversation
`<Plan>` produces a program and never runs it. `<Evaluate>` is now the
composition site where one runs: paired content renders a producer into a
private program buffer, `program={value}` supplies complete-program source
directly, and both admit a complete root and evaluate it in the current
execution — its lifecycle, journal, cancellation scope, providers, working
directory and the authority in force where the element was written.
The forms are disjoint and ambiguity refuses before program content is produced
and before any program effect runs. The root deciding what happens is the
program: its frontmatter, props schema, `returns` and `<Output>` selection all
apply, root props are the explicit `props` object rather than the caller's, and
a value root requires `as`.
Complete programs record their own `evaluate_program` durable event; the
restricted `generated_xmd` admission #369 delivered is unchanged and
complete-program support is not reachable through `source` or `allow`. A
partial continuation expands the retained source and restores completed nested
effects; a changed program at the same occurrence is stale input and neither
source runs.
Canonical execution answers the new `Component.expandProgram()` from the frame
it already holds, which is what makes site authority a fact about the frame
rather than something a prop, binding or middleware return value could supply —
and what keeps a producer's private closure from crossing into the program.
| "Component.expandProgram() has no provider: not inside a function component invocation.", | ||
| ); | ||
| }, | ||
| // deno-lint-ignore require-yield |
There was a problem hiding this comment.
Redundant comment — restates what the code does.
| // deno-lint-ignore require-yield |
| yield* provideEnv(programEnvironment(site.callerValues, program.props)); | ||
| if (program.returns !== undefined) { | ||
| // A value root has no rendered result to fall back on, so an undecided | ||
| // error is the evaluation's failure rather than text in the document. |
There was a problem hiding this comment.
Redundant comment — restates what the code does.
| // error is the evaluation's failure rather than text in the document. |
| } | ||
| const mode = definition.returns === undefined ? "text" : "value"; | ||
| // Before the props are looked at, because a value root with nowhere to put | ||
| // its result is refused whatever its props would have validated to. |
There was a problem hiding this comment.
Redundant comment — restates what the code does.
| // its result is refused whatever its props would have validated to. |
| // Which grammar this element is written in, decided from the props alone. | ||
| // Neither half of it reads the authored form, so both are settled before | ||
| // the durable name is claimed: a combination this host does not have is not | ||
| // a fragment or a program being refused. |
There was a problem hiding this comment.
Redundant comment — restates what the code does.
| // a fragment or a program being refused. |
|
|
||
| // After the claim, so the shape read here is the one the claim proved this | ||
| // element was written in. A borrowed invocation reports somebody else's | ||
| // element, and refusing it is the claimant's answer rather than this one. |
There was a problem hiding this comment.
Redundant comment — restates what the code does.
| // element, and refusing it is the claimant's answer rather than this one. |
| origin: ORIGIN, | ||
| // The run profile's sentence plus the one form only this profile has. | ||
| // Stated here rather than in core because a catalog must not advertise a | ||
| // prop the profile it describes would refuse. |
There was a problem hiding this comment.
Redundant comment — restates what the code does.
| // prop the profile it describes would refuse. |
PR #756: ✨ Make
|
The `evaluate_program` record is hostile data and was read as though it were not. It is now two closed shapes — admitted and refused — with every nested record closed too, so a missing, additional or misspelled member at any depth is a record this evaluation did not write. Shape is not enough, because every member of the record is a claim about another one. Before anything expands, the retained source must hash to the retained digest, reparsing it must produce the retained root mode and a valid body structure, the retained elements must be the ones that source writes in that order and those forms, and the supplied props must validate to the retained validated props. A record failing any of them is refused as unreadable and neither the retained nor the current program performs an effect. The admission also retains what each name the program writes resolved to at this site, which restores the current-site compatibility contract: a continuation whose site now answers one of those names with a different identity, or admits it in a different form, refuses before either implementation runs, while an unchanged site resumes. Resolution stays behind canonical execution. The execution builds a resolver, carries it on the expansion authority, and settles the comparison itself before the first program effect. `Component.resolveProgramSite()` lets the admission retain what the site answered; nothing rests on that answer, because the canonical comparison is what decides — so short-circuiting it refuses the evaluation rather than widening it. `ExpansionAuthority` is still reachable from no component, and no prop, binding, middleware result or public context state supplies authority.
#713) The retained identity came from an independent `selectComponent()` call, which is not what runs. `Component.importComponent` middleware may answer without delegating or replace what came back, so a second resolution described a definition nobody invokes: two different middleware answers retained one identity, compared equal, and a continuation ran the other one. Resolution now goes through the ordinary chain and takes the identity from the final answer. A canonical tier keeps its canonical identity. An answer an identified provider supplied keeps that provider's own — a stable origin, its key for the name, and a revision that changes with the implementation — stated at the installation boundary through `useImportProvider()`, where canonical execution mints a claimant for that execution and binds the terms to the exact answer in execution-private state. Never on the definition, which an answer can copy; never through a replaceable Context answer. An answer nobody identified still works for ordinary expansion and cannot back a durable grant: a continuation would have nothing to compare. Two live providers under one origin refuse, and so does a second claim on one answer. Identities are closed tagged records now, so a missing, empty, additional or mis-tagged member is unreadable like every other corrupted member. The comparison and the invocation are one decision: canonical execution keeps its own copy of each witnessed answer and authorizes the program's imports against it, so an answer that changed between the check and the use is refused where it would be invoked rather than silently preferred.
…ts import (#713) Three corrections. **Closed authority was overwritten.** The program authority replaced the site's `imports` with one that forgot its tiers, and resolution asked the chain without applying them, so an identified provider could replace a bundled or declared component and be admitted as its identity. Resolution now applies the site's closed authority exactly as ordinary expansion does, and the program's expansion keeps the tiers it already had. A provider-supplied answer is witnessed rather than issued, so it authorizes nothing for a name a tier closed — the guarantee that sharing one witness table had quietly broken. **Resolution now journals nothing.** The selection half of an import and the definition it produces are separated from the durable operation, so a resolution-only path settles the site without recording anything. `evaluate_program` commits before the program's own ordinary import. **Expansion no longer asks again.** The answers that passed reconciliation reach expansion as settled answers: an element whose name is among them does not enter `Component.importComponent` at all. The chain is entered twice for one evaluation, never a third time. The authored element still records one ordinary import after the admission, restored from the already-authorized answer with identity-domain and form selection intact. Provider identity is read exactly once at registration and closed over as copied primitives, so a getter cannot answer the duplicate check and the claim differently and a later edit changes nothing. Structural syntax resolves directly to its structural identity: no lookup, no provider, no import record.
The settlement map was keyed by name, so a program writing `<Open /><Open />` collapsed two independently resolved answers into one and invoked the first twice. Each occurrence now carries its own settlement, keyed by where the element was written and paired with the admission's retained order, so two elements resolved to two implementations invoke those two in order. An unresolved occurrence is a settled outcome as well. It was absent from the map, so the element fell through to the ordinary open chain and could be answered by a lookup reconciliation never made. It now reports the ordinary unresolved failure without consulting anybody, and the chain is entered exactly twice for one evaluation whatever an occurrence settled to. Settlements belong to the program's own parsed body. A component the program invokes expands its own bytes at its own offsets, which nothing reconciled, so carrying them in would have made a settlement a name-wide override of somebody else's element. The nested durable record is a protocol rather than a marker. A settled occurrence records exactly `settled` and `name`, and a continuation parses that as the hostile replay data it is before invoking anything: a missing, additional, mistyped or unknown member, and a record naming another component, each refuse. A refusal this boundary raises during resolution — a second provider claiming one answer, an answer changed after it was claimed — now reaches the caller instead of being read as "this name resolves to nothing".
`readSettledImport()` cast the restored value and inspected it directly, so a value that answers differently on a second read decided what a continuation invoked. It now detaches the value into plain JSON under a failure boundary and inspects only that copy: a member read once cannot answer twice, and a value that will not detach is simply not the record. The admission's own restored result is detached under the same boundary for the same reason. The prose said one settled `import_component` per settled occurrence. It is one per *resolved* occurrence: an unresolved one loads no component and records no such event, which PE41 now asserts. A retained value that refuses to be read at all — a Proxy trapping `ownKeys` or a descriptor, a throwing accessor, a cycle — never reaches this parser. The run's own retention check reads every retained result before the document body starts and refuses there, in the journal's own words. That is a persistence boundary rather than this one, so the evidence proves what this boundary can promise: the component is never invoked.
…mand (#713) EP9 proves a workflow run is given one `<Evaluate>` and not two, and it proves it by starting a real run. `xmd workflow` exists on the Deno entrypoints alone, so under Node and Bun the command refused before a run existed and the case asserted nothing — which is why `test-node (2/7)` and `test-bun (3/4)` failed while the same file passed locally under Deno. The case moves to its own file and joins the Deno-only exclusions with its reason and issue, the way every other test of a workflow command does. Tier EP's portable half stays where it was and still runs on all three runtimes.
| "Component.expandProgram() has no provider: not inside a function component invocation.", | ||
| ); | ||
| }, | ||
| // deno-lint-ignore require-yield |
There was a problem hiding this comment.
Redundant comment — restates what the code does.
| // deno-lint-ignore require-yield |
| "Component.resolveProgramSite() has no provider: not inside a function component invocation.", | ||
| ); | ||
| }, | ||
| // deno-lint-ignore require-yield |
There was a problem hiding this comment.
Redundant comment — restates what the code does.
| // deno-lint-ignore require-yield |
| offered?.close(); | ||
| record = yield createDurableOperation<Json>( | ||
| { type: "import_component", name, ...sourceDescription(position) }, | ||
| // deno-lint-ignore require-yield |
There was a problem hiding this comment.
Redundant comment — restates what the code does.
| // deno-lint-ignore require-yield |
| case "workflow": | ||
| // The exact pinned source, already in hand: the bundle was read from | ||
| // the definition's own commit before this run existed, so recording it | ||
| // reads nothing and a replay reconstructs it without resolving a name. |
There was a problem hiding this comment.
Redundant comment — restates what the code does.
| // reads nothing and a replay reconstructs it without resolving a name. |
| case "declared-markdown": | ||
| // The exact declared bytes, already in hand: they were admitted | ||
| // before this run imported a root, so recording them reads nothing | ||
| // and a replay reconstructs the component without resolving a name. |
There was a problem hiding this comment.
Redundant comment — restates what the code does.
| // and a replay reconstructs the component without resolving a name. |
| digest: selected.digest, | ||
| content: selected.source, | ||
| // Recorded only when it holds, so an ordinary declaration's record | ||
| // is exactly what it always was. |
There was a problem hiding this comment.
Redundant comment — restates what the code does.
| // is exactly what it always was. |
|
|
||
| // Parsed rather than asserted: a replay hands back whatever the journal holds, | ||
| // and a history somebody else wrote is not a `DurableSelection` because it | ||
| // type-checked on the way in. |
There was a problem hiding this comment.
Redundant comment — restates what the code does.
| // type-checked on the way in. |
| } | ||
| // Canonical execution's own answer keeps its canonical identity. The | ||
| // selection that produced it is what names it, and asking for it here | ||
| // describes the definition this import actually settled on. |
There was a problem hiding this comment.
Redundant comment — restates what the code does.
| // describes the definition this import actually settled on. |
| // This import is canonical execution's own answer for a name this | ||
| // execution closed, which is the only provenance exact source is read | ||
| // from. An open import — one no tier claims — never sets it, however its | ||
| // answer describes itself. |
There was a problem hiding this comment.
Redundant comment — restates what the code does.
| // answer describes itself. |
<Evaluate> evaluate complete XMD programs (#713)|
The author contract for #713 has been superseded and the issue/PR handoff is now amended. Please do not continue or merge the current complete-program implementation. Correction is sequenced after #759: use its exact merged result as the base, retain the durable replay/isolation work named in this PR body, remove the complete-root machinery, and implement the unified fragment-text contract. The next review requires a new signed feedback commit and the focused evidence listed here; the current |
|
Architecture amendment: |
Why
Issue #713 makes generated XMD fragment text explicitly evaluable in ordinary and workflow documents while keeping the authority restriction trustworthy.
The current PR revision is not mergeable. It implements a superseded complete-program model on a pre-#759 base. Its replaceable
<Evaluate>name would also let a repository or middleware implementation ignoreallowand run with wider site authority.Architecture correction required
Restart the correction from a clean worktree based on the latest
maincontaining merged PR #759. Do not use an obsolete exact commit from this branch's complete-program implementation as the base, and do not preserve code merely because it exists on the old branch.<Plan>produces inert XMD fragment text. It does not execute that text and does not return a special Program value.<Evaluate>evaluates the text through two disjoint forms:The paired form privately renders its content to obtain the exact fragment. Validate input shape and
allowbefore producer work.allowapplies to evaluation of the resulting text, not to its producer. Omittedallowmeansread.The result remains
{ observations, output };ascaptures it, and otherwise it is discarded. Evaluation emits no fragment output into the enclosing document. Fragments introduce no root frontmatter, root props, rootreturns, or independent<Output>selection.Approved description:
Protected ownership
<Evaluate>is a public, engine-owned member of #758's canonical protected component tier. Canonical core owns its name, input grammar, admission,allowinterpretation, result shape, and invocation identity.A repository component, workflow bundle member, declared Markdown component, ordinary or reserved registration,
Component.importComponentmiddleware answer, document context value, or implementation from another loaded package copy cannot answer for or replace it. Middleware may observe, delegate, or refuse the protected import, but cannot provide, alter, redirect, retain for reuse, or invoke a replacement.Protection decides which policy interpreter runs. It grants no authority. Each trusted host supplies one private fragment-evaluation profile to canonical execution before document code. The profile states the maximum available authority through exact identities, forms, request limits, retained-root access where applicable, and provider operations.
allowseparately narrows that profile. A missing or duplicate profile refuses before producer or fragment work.Structural constructs remain protected through structural dispatch, not the protected component table. Ordinary defaults including
<File>,<Fetch>, and<Elicit>remain replaceable at ordinary authored sites. Generated evaluation runs only the exact identity and form the trusted profile admitted and the durable record retained.Authority and syntax
For both standard host profiles,
readselects exactly self-closing<File>.writeselects exactly paired<File>, paired<Dir>, and self-closing<File.Delete>and does not implyread;allow={["read", "write"]}selects both. Ordinary runs use their Files provider, while workflow runs use the retained Workspace's transaction-bound Files provider. Bounded Fetch and any further entry exist only as exact trusted-host extensions retained in policy.After form,
allow, and profile validation, Evaluate derives the selected vocabulary and installs it through merged #759's engine-owned lexical syntax reference while rendering paired content and evaluating the resulting fragment. Public<Syntax>and a directly nested Plan read it before admission; preflight enforces the same table before effects. Plan has no private Syntax implementation. A deferred Plan sees its own authoring site, and later Evaluate preflights its exact text before effects. Reading symbols grants no authority.Durability and compatibility
Use one generalized, versioned
generated_xmdprotocol shared by ordinary and workflow hosts. Keep old untagged #369 records readable as version 1 and write the closed successor as version 2. Retain exact text, policy, trusted limits, admitted identities, occurrence identity, and the committed result and effects required for replay. Hostile or stale records refuse before fragment effects.textis canonical. Preserve released workflowsourceonly as the silent deprecated alias in the workflow profile; ordinary evaluation rejects it. Use this approved Syntax documentation:Remove the unmerged
programspelling.Retain and adapt
Remove
programprop;returns, and independent Output behavior;evaluate_programjournal abstraction;<Evaluate>implementation.Review guide
Start with the protected-name selection and host-profile boundary. Then review the two input forms, generated-XMD admission, Syntax narrowing, durable protocol, middleware identity, and cancellation teardown.
The next feedback commit must freeze and pass the complete acceptance matrix in amended #713. In addition to the fragment, replay, compatibility, and lifecycle rows already required, evidence must discriminate:
allow={["read"]}and write, proving the replacement never runs and no provider or file effect occurs;Run the smallest explicit cross-boundary command covering the protected resolver, core fragment evaluator, ordinary CLI integration, workflow integration, Plan syntax, continuation, middleware substitution, and cancellation. Commit promptly when it passes using the configured signing identity
tarasm@gmail.com, then return the exact SHA and every focused command with its exact result. Do not wait for CI.Scope
Included
<Evaluate>for constrained XMD fragment text.sourcecompatibility alias.Intentionally unchanged
<Plan>only produces text.<Syntax>remains the public protected symbols component delivered by Add <Syntax> to list available XMD symbols (#758) #759.<Program>,<Source>, or<Execute>component.Closes #713.