36c - §10 and §11's decisions move to docs/decisions.md
This commit is contained in:
+226
-2
@@ -257,8 +257,8 @@ input reads `message`.
|
||||
inline alike, `\|` for every pipe row.
|
||||
- `not-an-adf-document` carries the document's own path throughout: seven of the guard's eight
|
||||
branches read the document's own shape, and threading a path to the eighth — a malformed node
|
||||
anywhere in the tree — wants the manual stack the no-recursion rule (`AGENTS.md` §11) forces,
|
||||
whose empty half no input reaches. The message names the violation instead.
|
||||
anywhere in the tree — wants the manual stack §Nothing recurses unbounded forces, whose empty
|
||||
half no input reaches. The message names the violation instead.
|
||||
|
||||
## Publish on a version bump
|
||||
|
||||
@@ -291,3 +291,227 @@ is saved to validates it.
|
||||
No ADF schema validation or exported validator. A refusal that keeps the round-trip is not schema
|
||||
validation, so the one a spelled node carrying the same mark type twice earns stays, and input
|
||||
nesting a spelling inside its own kind (`*(*a*)*`) names that mark once.
|
||||
|
||||
## The gate runs on Deno and Bun
|
||||
|
||||
2026-09-01, the maintainer. Goals 7 and 8. Valid while the suite, rather than a smoke import, is
|
||||
what proves an engine.
|
||||
|
||||
The gate runs the suite under Deno and Bun as well as Node, the three images pinned alike, and
|
||||
neither extra leg is Node's proof twice. Deno refuses an extensionless or directory specifier, so
|
||||
it holds the module graph to the fully-spelled form a browser can load; Bun runs JavaScriptCore,
|
||||
the one engine of the three that is not V8, where the Unicode property escapes emphasis matching
|
||||
leans on can disagree. Both refuse a run matching no test, so Node's is the only vacuous-green
|
||||
guard, and a test may reach only for what all three `node:` shims carry — the price of proving
|
||||
those engines over the corpus rather than over a smoke import.
|
||||
|
||||
## The gate installs the tarball
|
||||
|
||||
2026-09-03, the maintainer. Goal 7. Valid while consumers install the packed package.
|
||||
|
||||
The gate packs the build and installs the tarball under `package-tests/`, so `files`, `exports`
|
||||
and `types` are proved on the artifact that ships rather than on the source tree a self-reference
|
||||
would resolve against. `consumer.ts` typechecks the emitted `.d.ts` from outside
|
||||
`tsconfig.build.json` — declaration emit leaves the `.ts` specifiers
|
||||
`rewriteRelativeImportExtensions` rewrites in the JavaScript, and this is what says a consumer's
|
||||
resolver maps them, under `NodeNext` alone; a `.d.ts` reader that is not `tsc` stays unproven.
|
||||
`node-floor.js` round-trips the installed package under a Node pinned to `engines.node`'s floor.
|
||||
|
||||
## Firefox reads the build
|
||||
|
||||
2026-09-04, the maintainer. Goal 7. Valid while no other leg runs SpiderMonkey.
|
||||
|
||||
A headless Firefox loads `dist/index.js` over HTTP and converts the round-trip, normalization and
|
||||
error fixtures and the real payloads — the `commonmark-spec` sort is the Node suite's to check —
|
||||
which is the browser half of §Any ES2022 engine and the only SpiderMonkey there is — the gate's
|
||||
other engines are two V8s and a JavaScriptCore that is not Safari's. A WebDriver session is what
|
||||
carries a verdict back out, the driver and the page's server sharing one network namespace so each
|
||||
is the other's `127.0.0.1`; `--headless --screenshot` has no such channel, and loading
|
||||
`dist/index.js` in a globals-stripped realm buys one by not running a browser. The leg re-checks
|
||||
the conversions and nothing else — each fixture's emitted markdown, its parsed document, its error
|
||||
code — leaving the corpus's pairing, uniqueness, source positions and byte-level equality to the
|
||||
Node suite that owns them.
|
||||
|
||||
## The coverage floors
|
||||
|
||||
2026-08-24, the maintainer. Goal 1. Valid while `noUncheckedIndexedAccess` and ADF's optional keys
|
||||
force guards with a half no valid document reaches.
|
||||
|
||||
The floors live in the `test` script, so `npm test` and the gate are one path: 100% of lines and
|
||||
functions, and a branch floor that only ever moves upward. It sits below 100 because the guards
|
||||
`noUncheckedIndexedAccess` and ADF's optional keys force — `?? []`, `?? {}`, `?.`, an index
|
||||
compared against `undefined` — have a half no valid document reaches.
|
||||
|
||||
## The size ratchet
|
||||
|
||||
2026-09-20, the maintainer. Goal 10. Valid while no measure picks out what readers find hard
|
||||
better than a function's length.
|
||||
|
||||
`.oxlintrc.json`'s single rule, over the files `tsconfig.build.json` builds, is a per-function line
|
||||
ceiling, set at that set's worst and moving only downward. It covers the built files alone, since
|
||||
one ceiling over the tests too would have to be their worst, loosening the guard over the shipped
|
||||
code. It guards against drift and never drives a refactor, so no cyclomatic rule and no second lint
|
||||
rule join it: neither measure picked out what nine readers found hard (the comprehension panel,
|
||||
2026-09-20). `oxlint` measures it since TypeScript 7 is a native compiler publishing no in-process
|
||||
parser, only the `unstable/` AST surface an out-of-process handshake reaches. Three switches guard
|
||||
a silent green: `IIFEs: true`, since oxlint exempts an IIFE otherwise; an explicit `-c`, so a
|
||||
config gone missing fails the leg instead of falling back to oxlint's own defaults; and
|
||||
`--deny-warnings`, since a rule from a category this config never names arrives as a warning it
|
||||
exits 0 on.
|
||||
|
||||
## The corpus
|
||||
|
||||
2026-08-23, the maintainer. Goals 1 and 8. Valid while the round-trip is proved by example.
|
||||
|
||||
All checked in: hand-built fixtures per node and combination; real ADF Atlassian's editor wrote; the
|
||||
CommonMark spec suite against `markdownToAdf` and `markdownToHtml`.
|
||||
|
||||
## Properties on a fixed seed
|
||||
|
||||
2026-09-14, the maintainer. Goal 1. Valid while a red gate must reproduce.
|
||||
|
||||
Beside the corpus, properties run over documents generated from the node tables and over generated
|
||||
markdown, on a fixed seed in the gate; a counterexample found becomes a round-trip fixture.
|
||||
|
||||
## The flavour spec is read as a source
|
||||
|
||||
2026-09-01, the maintainer. Goals 1 and 6. Valid while `spec/flavour.md` restates the node tables
|
||||
in prose.
|
||||
|
||||
`spec/flavour.md` is read as a source, so the node tables cannot drift from the prose they copy:
|
||||
each `- ` bullet in `## Block nodes`, `## Inline nodes` and `## Marks` declares the nodes named
|
||||
before its first em dash, with the attributes following `Attributes: ` — a parenthesized value set
|
||||
reading `string` — and must equal the tables in `adf/`. Fenced examples are skipped. It guards the
|
||||
attributes alone: nodes that differ in content model share a bullet, and the argument attribute is
|
||||
spelled ahead of `Attributes: `, so both answer to the round-trip corpus and to nothing else where
|
||||
a node has no fixture.
|
||||
|
||||
## The node tables answer to Atlassian's schema
|
||||
|
||||
2026-09-13, the maintainer. Goal 1. Valid while a site's editor writes what Atlassian's schema
|
||||
holds.
|
||||
|
||||
For every node and mark the tables spell, the attribute names and kinds equal what `full.json` and
|
||||
`stage-0.json` (§Standards ship as data) hold between them. Value sets stay documentation, since
|
||||
any value round-trips. What the schema holds and the tables do not spell is pinned by name — an
|
||||
attribute as a gap, a type as carried — so a re-pin adding either goes red until someone spells it
|
||||
or pins it.
|
||||
|
||||
## Nothing recurses unbounded
|
||||
|
||||
2026-08-25, the directive form's count 2026-09-18, the maintainer. Goal 1. Valid while an engine's
|
||||
stack overflows near 2000 frames.
|
||||
|
||||
The guards walk iteratively, and blocks, marks and JSON values — an attribute's and a carried
|
||||
node's alike — are all held to 500 levels (`largestNesting`), so a deep document is a `Result`
|
||||
rather than the stack overflow that waits near 2000. An attribute is counted from its value; a
|
||||
spelling that nests it deeper — the block directive's `marks`, the carry — refuses in its own
|
||||
format, as its parser does. A list giving way to the directive form refuses at zero headroom rather
|
||||
than walking again; counting every list twice halved the list limit, counting the directive form
|
||||
once doubled the parser's frames per level.
|
||||
|
||||
## Nothing spreads an unbounded array
|
||||
|
||||
2026-09-18, the maintainer. Goal 1. Valid while engines cap a call's arguments.
|
||||
|
||||
Nothing spreads an unbounded array into a call — a node's siblings, a code block's held lines, a
|
||||
mark run's segments: the argument list caps near 125k and throws a `RangeError` where a `Result` is
|
||||
owed. A walk pushes one at a time. A literal spread (`[...value]`) is not the same thing and is
|
||||
fine (4c).
|
||||
|
||||
## A retry loop checks its own termination
|
||||
|
||||
2026-09-20, the maintainer. Goal 1. Valid while a line retries until a fallback spells it.
|
||||
|
||||
A loop retrying an input until a fallback spells it refuses the pass taking no fallback, so its
|
||||
termination is the loop's own check (28).
|
||||
|
||||
## Readers scan by index
|
||||
|
||||
2026-08-30, the kept scan 2026-09-18, the maintainer. Goal 9. Valid while the pipeline persona
|
||||
feeds documents nobody typed.
|
||||
|
||||
A reader takes the text and an index — a sticky regex whose `lastIndex` the caller sets on the line
|
||||
before it reads, `indexOf` — never a fresh slice per character, and a per-character walk hoists the
|
||||
scan that does not vary with the character: a megabyte through a quadratic walk is a minute rather
|
||||
than a millisecond. A scan may keep what it read for a later walk of the same text, and the
|
||||
fallback where it kept nothing must be the same reader over the same text at the same index, so the
|
||||
two cannot disagree — which is what makes the kept value a memo rather than a second spelling (4c).
|
||||
|
||||
## The spelling memo
|
||||
|
||||
2026-09-19, the maintainer. Goal 9. Valid while the `commonMarkSpelling` ask spells a node once
|
||||
per level above it otherwise.
|
||||
|
||||
The parse keeps each node's readable spelling in a memo, so the `commonMarkSpelling` ask stops
|
||||
spelling a node once per level above it (18). The node reference is the key, which holds because
|
||||
the parse builds one object per position; `adfToMarkdown` passes no memo, where a consumer's
|
||||
document may hold one node at two positions (4b). `text` and `spelling` carry no depth and
|
||||
`headroom` is affine in it, so a read at or above the depth that filled the entry rebases; a read
|
||||
below re-spells, because a hit skips the depth guards the walk it replaces runs and an ordered list
|
||||
past the marker cap gives way, spending two emitter levels where the parser spent one. Only what
|
||||
succeeded is kept, so no path minted at another position is ever read.
|
||||
|
||||
## Only the hard break holds a raw newline
|
||||
|
||||
2026-08-26, the maintainer. Goal 1. Valid while the whitespace carry finds a line edge by its raw
|
||||
newline.
|
||||
|
||||
Only the hard break's inline segment holds a raw newline — every other spelling escapes one or
|
||||
refuses it — which is how the whitespace carry finds a line edge.
|
||||
|
||||
## Emphasis follows CommonMark's matching
|
||||
|
||||
2026-08-27, the maintainer. Goals 1 and 8. Valid while CommonMark's emphasis rules are the
|
||||
reader's.
|
||||
|
||||
Emphasis is spelled against CommonMark's matching, never flanking alone: a delimiter run in text
|
||||
escapes wherever CommonMark could open or close with it, leaving the emitter's own delimiters the
|
||||
only ones in play, and a pair that matching hands to another delimiter rides the carry instead.
|
||||
|
||||
## Readable spellings take the `try` prefix
|
||||
|
||||
2026-09-21, the maintainer. Goals 1 and 4. Valid while a readable spelling is tried ahead of a
|
||||
general one.
|
||||
|
||||
A readable spelling tried ahead of a general one takes the `try` prefix and fails only where the
|
||||
general form fails on the same node (20): refusing there refuses a document the general form
|
||||
spells, so a refusal the general form does not share belongs in the general form or nowhere. A
|
||||
readable spelling that must spell its subtree before it can give way — the list, whose
|
||||
thematic-break first line and blank lines exist only spelled — hands that one walk to the general
|
||||
form instead: giving way after the walk walks again at every level, doubling per level (4b).
|
||||
|
||||
## The attribute vocabulary is ADF's
|
||||
|
||||
2026-08-27, the maintainer. Goal 2. Valid while every format spells the same ADF attributes.
|
||||
|
||||
`adf/` walks the attribute vocabulary and narrows each value to its kind, and a format spells the
|
||||
narrowed value. A spelling that re-checks the type is the check's second copy. Reading a spelling
|
||||
back is the format's own: the reader sits beside the spelling it inverts, so
|
||||
decode-respell-compare cannot drift, and each format writes its own — canonical JSON for a number
|
||||
is the markdown flavour's choice, not ADF's.
|
||||
|
||||
## The source parts by ADF and format
|
||||
|
||||
2026-08-27, `adf/`'s bar 2026-09-21, the maintainer. Goal 2. Valid while each format has a reader
|
||||
and a writer through ADF.
|
||||
|
||||
`src/adf/` holds ADF's own knowledge, imports no format, and is where a construct both formats read
|
||||
lives: the question is answered in ADF's vocabulary — a node type, an attribute kind, a content
|
||||
model — and no delimiter, element name or escape reaches it. A helper that cannot answer that way
|
||||
is two constructs, the ADF question there and the spelling in each format, the seam
|
||||
`markAttributes` and `markSpellings` already draw; one that cannot be split is a gap to ask.
|
||||
`markdown/` and `html/` are peers: neither imports the other, and no third directory sits between
|
||||
them. A primitive knowing neither ADF nor a format stays at `src/` root. A construct rises to
|
||||
`adf/` on its second consumer, not in anticipation of one.
|
||||
|
||||
Each format directory parts into `emit/` (ADF→format) and `parse/` (format→ADF), the rest of it
|
||||
holding what both directions read. A construct's reader lives there beside the regex the emitter
|
||||
escapes against, so the two cannot drift; a reader with no emit counterpart goes in `parse/`,
|
||||
unless it is part of a construct that side already holds — a grammar stays in one file rather than
|
||||
splitting across the seam. A rule both directions must answer alike — whether a list marker
|
||||
interrupts a paragraph — is one function there too, never a copy per direction, however
|
||||
conservative the copy would be. Where the rule is the emitter's own choice, input consults it
|
||||
rather than restating it, and that is the only import `parse/` takes from `emit/` —
|
||||
`commonMarkSpelling` and `openingLinkTakesDirective` — so no fixture the emitter writes can be
|
||||
refused, and a spelling the emitter refuses gives its own error rather than a second name for it.
|
||||
|
||||
Reference in New Issue
Block a user