Repository navigation
✨ Give installed Markdown components an explicit kind (#806) - #809
Conversation
A declared Markdown component now states `kind: "markdown"`, and canonical core reads that before it reads anything else about the value. Everything else a declaration carries is a statement about exact Markdown, so a value admitted on the strength of merely having a source and a digest would be one whose shape decided what it meant. `MarkdownDeclaration` is the canonical type and `DeclaredMarkdownComponent` stays as its alias, so every existing import keeps working while constructing a declaration states its kind. Capture copies the discriminant it was given rather than deciding one, so a declaration that says nothing, or says something this version does not know, reaches admission as it was written and is refused there — before the root document is imported. No other behavior changes: selection, closed imports, private closures, exact source, replay, inspection, validation and the syntax symbols are as they were.
`Markdown({…})` is now how a host states a declaration. It takes the
description — name, origin, source, digest, forms, schemas, privates — and
returns a fresh declaration carrying `kind: "markdown"`, written after the
input is spread so a value carrying a kind of its own is overwritten rather
than believed. There is no assembling a declaration member by member that ends
up claiming to be Markdown.
It is a constructor and nothing else: it validates nothing, computes no digest,
copies no nested schema, array or private declaration, freezes nothing and
admits nothing. Copying stays at capture, before any `install()` runs, and
checking stays at admission.
Admission now reads the discriminant before it reads any other member, because
reading a name, origin, source or digest from a value that never said it was
Markdown is already treating it as Markdown. Every later check, its order, its
error type and its diagnostic are unchanged.
Both Plan declarations and every declaration fixture in this slice are built
through the constructor, and the annotations the factory makes redundant are
gone.
The constructor documentation claimed more than the code does. `Markdown({…})`
is the canonical way to build a declaration and is what every declaration this
repository owns is built with, but `MarkdownDeclaration` is an ordinary
structural type: a caller can still write the object out by hand, and no
constructor can stop one. Saying otherwise described a defence that does not
exist.
What does defend the execution is unchanged and stays stated: admission reads
the discriminant before any other member and refuses a missing or unknown one,
before the root document is imported.
MDK1 now types its input as `MarkdownDeclarationInput` instead of asserting
`as const` on the forms it passes, which is the same claim without a cast.
No behavior changes, no new evidence rows, and the syntax symbols keep their
single version.
PR #809: ✨ Give installed Markdown components an explicit kind (#806)16 files, +455 / -75 Scope🟡 530 lines changed. PRs under 400 receive more thorough review. StructuralOxlint structural signals:
SlopOxlint slop signals:
Static AnalysisOxlint: 66 diagnostics across 9 files (16 rules) unbound-method (9): packages/cli/src/testing-host.ts, packages/cli/tests/support/plan-harness.ts, packages/cli/src/plan-component.ts (+2) CorrectnessFILE: packages/cli/src/plan-component.ts, packages/core/src/execute.ts, packages/core/src/inspect.ts FILE: packages/core/src/execute.ts FILE: packages/cli/src/plan-component.ts FILE: packages/core/src/execute.ts FILE: packages/core/src/execute.ts No extraneous code patterns detected. |
The type a host hands an execution is a component, not a declaration about one,
and the name now says so: `MarkdownComponent`, built from
`MarkdownComponentInput` by `Markdown({…})`.
`MarkdownDeclaration`, `MarkdownDeclarationInput` and the
`DeclaredMarkdownComponent` compatibility alias are gone rather than kept
beside the new names. Nothing outside this repository depends on them, and an
alias that exists for no caller is a second name for one thing that every later
reader has to reconcile.
Every repository-owned import, annotation, constructor input, documentation
reference and specification signature moves with it. The module keeps its
path — this renames the public types, not `components/declared-markdown.ts`.
No runtime behavior changes: the constructor still returns a fresh shallow
object and writes `kind` last, validating, hashing, copying, freezing and
admitting nothing; admission still reads the discriminant before any other
member; and the syntax symbols keep their single version.
Why
A declaration a trusted host hands an execution says everything about exact
Markdown — its name, origin, bytes and digest — but previously did not say what
kind of declaration it was. Admitting it from its shape would become ambiguous
once #806 adds structural declarations.
This prerequisite gives installed Markdown components an explicit kind and a
canonical constructor before the installation catalog gains another arm.
What changes
Before:
Any value with the expected Markdown fields was admitted without stating its
kind.
After:
Markdown({…})returns a freshMarkdownComponentcarryingkind: "markdown". Admission refuses a component whose kind is missing orunknown before the root document is imported.
How it works
The constructor writes
kindafter spreading the input, so an input carrying akind cannot override it. It performs no validation, hashing, admission, deep
copying or freezing. Capture and admission retain those existing
responsibilities.
Review guide
Start with:
packages/core/src/components/declared-markdown.tsThen review:
MarkdownComponent,MarkdownComponentInputandMarkdown()admitDeclaredMarkdown(), where the discriminant is read firstpackages/core/src/execute.ts, where the stated kind is captured once beforeinstallation code runs
Markdown({…})Look carefully at:
What must stay true
Markdown()is the canonical constructor, not an enforcement gate.MarkdownComponentremains an ordinary structural type; admission is thedefensive boundary. Tier MDK2 checks that boundary.
kindonce before anyinstall()call. Tier MDK3 checks thatlater mutation cannot change the admitted value.
inspection and validation remain unchanged. Tier DM and MDK1 retain those
paths.
How to verify it
kind, leaves its input unchanged, preserves supplied members and neither
hashes, clones nor freezes caller values.
DeclaredMarkdownErrorbefore root import or output, and before any othermember is read.
live inspection continues to emit symbols version 2.
deno task test packages/core/tests/declared-markdown-component.test.ts \ packages/core/tests/syntax-component.test.ts \ packages/cli/tests/plan-component.test.tsFocused evidence at
e3fa5027d05455be6915fa4252bd4168f8a746e4: 18 tests,166 steps and no failures.
deno task checkandgit diff --checkalso pass.Scope
Included
kind: "markdown"discriminant onMarkdownComponent.Markdown()andMarkdownComponentInput, exported from@executablemd/core/host.constructor.
Intentionally unchanged
ExecutionInstallation.declarationsremains Markdown-only. This PR adds noExecutionDeclarationunion, structural declaration, placement analysis,structural origin or expansion handler.
SyntaxSymbolsremains at version 2.their existing behavior.
New abstractions
MarkdownComponentInputomits the discriminant hosts should not have torepeat.
Markdown()constructs the installed Markdown components used by both Plansurfaces and the repository's declaration fixtures.
Risks and limitations
MarkdownComponentis intentionally structural, so callers can still write avalue by hand. Admission validates the required discriminant regardless of
how the value was constructed.
state.
Scope confirmation