Skip to content

doc: replace doc/design with an architecture-only design guide - #529

Merged
Pedro Henrique Penna (ppenna) merged 3 commits into
devfrom
agents/refining-design-file-structure
Oct 14, 2026
Merged

Pedro Henrique Penna (ppenna) merged 3 commits into
devfrom
agents/refining-design-file-structure

Conversation

@ppenna

@ppenna Pedro Henrique Penna (ppenna) commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Summary

This PR replaces doc/design/ with an architecture-only design guide that describes the NVX microVM and its single-workload guest runtime by architectural concept rather than by implementation component. The guide was developed as doc/design-doc/ and now lives at doc/design/; the old component chapters and their index, doc/design.md, are removed. Start at doc/design/README.md.

Why

The old doc/design/ mixed the architecture with implementation detail: code paths, command-line options, tests and CI, measurements, status, current limits, and proposals. The guide keeps only the architecture, for readers who need the design without that detail. Keeping both would leave two diverging descriptions of the same system.

What's in it

The README indexes five chapters. Each chapter page introduces its chapter and links its sections, and each section is a separate file named by its number, such as 2.2-cpu-and-topology.md.

Chapter Sections
1. Architecture Overview Goals, non-goals, components and responsibilities, trust boundaries, compatibility and versioning
2. Virtual Machine Architecture Machine model and configuration, CPU and topology, memory layout, interrupt and I/O architecture, device topology and discovery
3. Virtual Devices Platform control device, clock and time, consoles, block storage, network, host filesystem
4. Guest Runtime Architecture Guest filesystem and image model, guest agents and workload, isolation and resources, host-guest control interface
5. VM Lifecycle and State Boot, readiness and runtime control, snapshot, snapshot artifacts and attachments, restore, shutdown and failure

The guide leaves out code paths, command-line options, internal component names, tests, CI and host qualification, performance measurements, implementation status and current limits, and proposals.

Beyond reorganizing the old chapters, it:

  • Records the owner and lifetime of each kind of configuration.
  • Explains why the byte channel exists apart from virtio, and what the generation ID is for.
  • Describes end to end how a restored guest refreshes its entropy, and why an instance checkpoint keeps its random state.
  • Moves processor activation into the restore protocol, next to memory activation.
  • States that the guest TSC runs at its host's rate, and that a restore needs a host rate within 250 ppm of the snapshot's declared rate.
  • Describes snapshot memory sharing as OpenVMM implements it.
  • Incorporates private-network ingress (network: publish guest listeners to the host's private networks #495) and the virtio-fs device architecture (doc: describe the microVM virtio-fs device architecture #525).
  • Briefly describes x86 platform terms, such as XSAVE, MSR, and the e820 map, at first use.

Where the guide states the intended architecture ahead of the implementation, an issue tracks the gap:

Removing the old chapters

  • doc/design.md and the old doc/design/ chapters are deleted; they remain in the history.
  • doc/design/copilot-adversarial-testing.md moves to doc/adversarial-testing.md. It is the operator document for test-adversarial, not architecture, so it stays outside the guide.
  • Every reference to the removed chapters is retargeted. Documentation links point to the matching guide sections, and README.md now also lists the Ubuntu and Azure Linux guest chapters, which only doc/design.md indexed.
  • Material that the guide leaves out points to where it now lives: host qualification checks H1 to H7 and their schedules to the "Host qualification" section of doc/ci.md, and the CPU-time budgets to CHECK_CPU_BUDGET_US in scripts/nvx_tools/time_abi.py.
  • Code comments that cited doc/design/time-abi.md now cite the guide or doc/ci.md: the validate-runner action, guest/common/nvx-snapshot, guest/common/nvx-time.c, scripts/nvx_tools/doctor.py (including the doctor's parser description), scripts/nvx_tools/host_time_probe.rs, scripts/nvx_tools/time_abi.py, and scripts/test_guest_time.py.

Notes for reviewers

  • Apart from the doctor's --help description, there is no behavior change. Because comments in guest/common/ and scripts/ changed, CI runs the full matrix rather than the documentation-only path.
  • The OpenVMM submodule still cites doc/design/time-abi.md in three comments (vmm_core/virt/src/time_abi/mod.rs, vmm_core/virt_kvm/src/arch/x86_64/time_abi.rs, and vmm_core/virt_whp/src/time_abi.rs). They need a separate OpenVMM change.
  • Checked locally: every relative link and anchor in the repository's Markdown files resolves, and every doc/ path that code mentions exists; LF line endings; ruff check and ruff format --check; scripts/test_time_abi.py, scripts/test_guest_time.py, and scripts/test_nvx_tools.py; and nvx.py doctor --help.

cc Íñigo Goiri (@goiri) Enrique Saurez (@esaurez)

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The guide contains stale network and filesystem behavior and has not yet incorporated the current virtio-fs architecture.

4 open findings
What changed in this PR

Adds a standalone, architecture-focused NVX design guide organized by concepts rather than implementation components.

Changes:

  • Documents machine architecture, devices, guest runtime, and lifecycle.
  • Defines compatibility, trust boundaries, configuration ownership, and snapshot semantics.
  • Adds navigable chapter and section indexes.
File Description
doc/​design-doc/​README.md Indexes the architecture guide.
doc/​design-doc/​1-architecture-overview.md Introduces the architecture.
doc/​design-doc/​1.1-goals.md Defines architectural goals.
doc/​design-doc/​1.2-non-goals.md Defines excluded capabilities.
doc/​design-doc/​1.3-system-components-and-responsibilities.md Assigns component responsibilities.
doc/​design-doc/​1.4-trust-boundaries.md Documents trust and validation boundaries.
doc/​design-doc/​1.5-compatibility-and-versioning-model.md Defines compatibility contracts.
doc/​design-doc/​2-virtual-machine-architecture.md Indexes the machine architecture.
doc/​design-doc/​2.1-machine-model-and-configuration.md Defines machine configuration ownership.
doc/​design-doc/​2.2-cpu-and-topology.md Documents CPU profiles and topology.
doc/​design-doc/​2.3-memory-layout.md Defines guest memory layout.
doc/​design-doc/​2.4-interrupt-and-io-architecture.md Describes interrupt and I/O paths.
doc/​design-doc/​2.5-device-topology-transport-and-discovery.md Defines fixed device topology.
doc/​design-doc/​3-virtual-devices.md Indexes virtual devices.
doc/​design-doc/​3.1-platform-control-device.md Documents platform-control ports.
doc/​design-doc/​3.2-clock-and-time.md Defines the time ABI.
doc/​design-doc/​3.3-consoles.md Documents boot and control consoles.
doc/​design-doc/​3.4-block-storage.md Defines block roles and behavior.
doc/​design-doc/​3.5-network.md Documents networking and policy.
doc/​design-doc/​3.6-host-filesystem.md Documents host filesystem exports.
doc/​design-doc/​4-guest-runtime-architecture.md Indexes guest runtime architecture.
doc/​design-doc/​4.1-guest-filesystem-and-image-model.md Describes workload filesystem construction.
doc/​design-doc/​4.2-guest-agents-and-workload-model.md Defines workload supervision.
doc/​design-doc/​4.3-isolation-and-resource-model.md Documents guest isolation.
doc/​design-doc/​4.4-host-guest-control-interface.md Defines the control protocol.
doc/​design-doc/​5-vm-lifecycle-and-state.md Indexes lifecycle and state.
doc/​design-doc/​5.1-boot-protocol.md Defines boot behavior.
doc/​design-doc/​5.2-readiness-and-runtime-control.md Documents readiness and control.
doc/​design-doc/​5.3-snapshot-protocol.md Defines snapshot capture.
doc/​design-doc/​5.4-snapshot-artifact-and-attachment-model.md Defines snapshot artifacts and attachments.
doc/​design-doc/​5.5-restore-protocol.md Defines restore and guest repair.
doc/​design-doc/​5.6-shutdown-and-failure-semantics.md Defines shutdown and failures.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread doc/design-doc/3.5-network.md Outdated
Comment thread doc/design-doc/3.6-host-filesystem.md Outdated
Comment thread doc/design-doc/4.1-guest-filesystem-and-image-model.md Outdated
Comment thread doc/design-doc/5.4-snapshot-artifact-and-attachment-model.md Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread doc/design-doc/3.5-network.md Outdated
Comment thread doc/design/3.6-host-filesystem.md

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread doc/design-doc/3.6-host-filesystem.md Outdated
Comment thread doc/design-doc/5.5-restore-protocol.md Outdated
Comment thread doc/design-doc/5.6-shutdown-and-failure-semantics.md Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread doc/design-doc/2.4-interrupt-and-io-architecture.md Outdated
Comment thread doc/design-doc/3.2-clock-and-time.md Outdated
Comment thread doc/design-doc/4.3-isolation-and-resource-model.md Outdated
Comment thread doc/design-doc/4.3-isolation-and-resource-model.md Outdated
Copilot AI balanced review requested due to automatic review settings October 14, 2026 16:49

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread doc/design-doc/1.5-compatibility-and-versioning-model.md Outdated
Comment thread doc/design-doc/4-guest-runtime-architecture.md Outdated
Copilot AI balanced review requested due to automatic review settings October 14, 2026 17:29
@ppenna
Pedro Henrique Penna (ppenna) force-pushed the agents/refining-design-file-structure branch from 3194e86 to 4760118 Compare October 14, 2026 17:29

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The documentation-only changes leave two nonblocking wording clarifications and no identified approval blockers.

3 open findings
3 resolved since last review

🧠 Review effort: Balanced

Comment thread doc/design-doc/1.4-trust-boundaries.md Outdated
Comment thread doc/design/4.1-guest-filesystem-and-image-model.md
Copilot AI balanced review requested due to automatic review settings October 14, 2026 19:25
@ppenna
Pedro Henrique Penna (ppenna) force-pushed the agents/refining-design-file-structure branch from 4760118 to 1d898bf Compare October 14, 2026 19:25
@ppenna
Pedro Henrique Penna (ppenna) merged commit cc9960f into dev Oct 14, 2026
33 checks passed
@ppenna
Pedro Henrique Penna (ppenna) deleted the agents/refining-design-file-structure branch October 14, 2026 22:29
Pedro Henrique Penna (ppenna) added a commit that referenced this pull request Oct 15, 2026
#529 replaced doc/design with the architecture guide and removed
doc/design/time-abi.md, the time ABI specification. NVX code, tests, and
documentation, and OpenVMM's time ABI comments, still cited it: its
sections, its snapshot-agent and restore step numbers, and check IDs
that no remaining document defines. Retarget every NVX citation to the
guide, fold the specification's remaining architecture-level detail
into section 3.2, leave its implementation detail to the code that
defines it, and pin an OpenVMM revision whose documentation no longer
depends on NVX's documents.

Folded into 3.2.10 (Guest Obligations):

- Which checks capture and restore rerun, instead of "the relevant
  subset": capture reruns the clocksource, tick-device, and watcher
  checks; restore reruns them, checks the declared rate in the frequency
  MSR again, and checks the identity leaves, CPU time bits, and CPU flags
  of every processor that it brings online.
- What the state file reports: the outcome of each phase's checks, the
  declared rates, the generation, the last downtime and its source, and
  the wall clock's discontinuities, synchronization, offset, uncertainty,
  and frequency correction.
- Restore repair and the discipline step the clock through the kernel, so
  a workload observes every step through the kernel's clock-set
  notification, such as a timerfd armed with TFD_TIMER_CANCEL_ON_SET.

The rest of the specification's architecture-level content is already in
the guide: the guest's fail-fast statuses (5.6), generations (5.5.6), the
rejection of clock tokens in the command line (5.1), and capture rollback
(5.3). Its implementation detail stays out of the documentation: the E_*
and G_* codes, the check IDs C1 to C12, K1, and X1 to X6, the
NVX-TIME-ABI line formats, the state file's path and keys, the watcher's
kernel log patterns, report-only mode, and the warp probe's method.
guest/common/nvx-time.c now states that it defines its check IDs, guest
codes, output lines, and state file format.

Renumbered snapshot-agent steps:

- guest/common/nvx-time.c, guest/common/nvx-snapshot, and
  scripts/test_microvm_tests.py number the snapshot agent's steps as the
  guide does: guest preparation steps 1 to 4 (5.3.2) and guest repair
  steps 1 to 7 (5.5.7), instead of the removed specification's capture
  steps 1 to 5 and restore steps 6 to 13. Old steps 6 to 10 are repair
  steps 1 to 5, old step 12 is repair step 6, and old step 13, with old
  step 11's checks, is repair step 7.
- The comment on repair_restore names repair steps 2 and 3, because
  cmd_capture reads the status, repair step 1, itself.
- Rename step13_cpu_ns, fork_step13, and test_step13_cpu to
  deferred_restore_cpu_ns, fork_deferred_restore, and
  test_deferred_restore_cpu, and nvx-time's "step13-cpu" test hook to
  "deferred-restore-cpu", with its test in scripts/test_guest_time.py.

Retargeted documentation:

- doc/build.md: the RCU stall timeout stays at its production value
  because the conformance checks cover the stall-detector settings
  (3.2.10), rather than because of check C8.
- doc/ci.md: describe the exhaustive check's MSRs and the boot check of
  the debug detectors in words, rather than as checks C3 and C11.

Retargeted code comments and test names, with no behavior change:

- scripts/nvx_tools/doctor.py: the doctor runs its checks in ID order,
  as the --checks help text now says, and CHECK_CPU_BUDGET_US holds the
  CPU-time budgets.
- scripts/nvx_tools/microvm_tests.py: the debug-kernel restore cases and
  the long-downtime case cite doc/ci.md.
- scripts/nvx_tools/time_abi.py: the CPU profile catalog cites 2.2, and
  the warp probe check cites the 1 us skew bound of 3.2.
- scripts/test_guest_time.py: the restore packet (5.5.5) and time sample
  (3.2.9) fixtures follow the guide, and the exhaustive check's lines and
  the state file fixtures pin the formats that nvx-time.c defines. The
  exhaustive-report test becomes test_exhaustive_report_format.
- scripts/test_time_abi.py: the CPU-time budget comments no longer cite
  "the spec's" budgets.

OpenVMM pin:

Pin nanvix/openvmm#136 at c6d97c529986c0b3b1bf1fd7f00406654cdeee67, one
commit on 7f225e2c. OpenVMM's time ABI comments cited the removed
specification by path, by its "Backend obligations" table, by its step
numbers, and as "the specification". They now state what the code does
and refer only to OpenVMM's own items, and the three tests named after
the specification are renamed. There is no behavior change. Index the
new pin in doc/openvmm-upstream-roadmap.md.

Fixes #562

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna) added a commit that referenced this pull request Oct 15, 2026
Nothing in CI checked the repository's relative Markdown links. #529
moved the design documentation, numbered its sections, and retargeted
every inbound link by hand, while an older link had already broken: the
MXC integration table in aci_edge_sandboxes/README.md linked the anchor
native-proxy-and-forwarded-ports of a heading that #512 had renamed to
"Native proxy and host loopback". GitHub opens a page at its top when an
anchor is missing, so such links break silently, and so do comments
that cite a document by path, as the citations of the removed time ABI
specification did (#562).

Add scripts/check_markdown_links.py, a standard-library check, and run
it in check-quality, whose job runs for every pull request that is
ready for review, including the documentation-only ones that are most
likely to break a link:

- It resolves every relative link and image target of the tracked
  Markdown files, whether an inline link, a reference definition, or an
  HTML href or src attribute, against the files that Git tracks, so a
  target that is untracked or differs in case, which GitHub cannot
  serve, fails too. A directory resolves, and its anchors are those of
  its README. An empty target, such as that of [Self](), links to its
  own document.
- It resolves each anchor against the heading slugs that GitHub
  generates, including the numeric suffixes of repeated headings, and
  against HTML id and name attributes. Other files accept only line
  anchors, such as #L10, within their length.
- It scans the blocks of each document as CommonMark nests them in
  block quotes and list items, with lazy continuation lines, so that,
  as on GitHub, front matter, fenced and indented code, code spans, HTML
  comments and blocks, escaped brackets, and invalid reference
  definitions hold no links, while the definitions and headings inside
  block quotes and list items count.
- Links into the openvmm submodule resolve against its pinned commit
  when it is checked out and are skipped otherwise, as in the quality
  job, whose checkout does not fetch submodules.
- It also resolves, from the repository root, every doc/ path, with any
  anchor, that a tracked text file mentions. DOC_PATH_FIXTURES lists
  the test fixtures that name missing documents, such as doc/notes.md
  in scripts/test_nvx_tools.py, and an entry that its file no longer
  needs fails the check. A mention followed by a character that could
  extend the path, such as the * of a pattern, is ambiguous and is
  skipped.
- It reports each problem with its file and line, as an error
  annotation in GitHub Actions, and then exits with status 1.

scripts/test_markdown_links.py covers the slugs, the parsing, the
resolution in temporary repositories, including a submodule, the
fixtures, and the command line, and validate-nvx runs it on Linux and
Windows.

Retarget the README link to native-proxy-and-host-loopback, the only
broken link that the check found, and document the check in
doc/contribute.md, doc/setup.md, and doc/project-structure.md.

Fixes #563

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna) added a commit that referenced this pull request Oct 15, 2026
Nothing in CI checked the repository's relative Markdown links. #529
moved the design documentation, numbered its sections, and retargeted
every inbound link by hand, while an older link had already broken: the
MXC integration table in aci_edge_sandboxes/README.md linked the anchor
native-proxy-and-forwarded-ports of a heading that #512 had renamed to
"Native proxy and host loopback". GitHub opens a page at its top when an
anchor is missing, so such links break silently, and so do comments
that cite a document by path, as the citations of the removed time ABI
specification did (#562).

Add scripts/check_markdown_links.py, a standard-library check, and run
it in check-quality, whose job runs for every pull request that is
ready for review, including the documentation-only ones that are most
likely to break a link:

- It resolves every relative link and image target of the tracked
  Markdown files, whether an inline link, a reference definition, or an
  HTML href or src attribute, against the files that Git tracks, so a
  target that is untracked or differs in case, which GitHub cannot
  serve, fails too. A directory resolves, and its anchors are those of
  its README, whatever the README's case. An empty target, such as that
  of [Self](), links to its own document.
- It resolves each anchor against the heading slugs that GitHub
  generates, including the numeric suffixes of repeated headings, and
  against HTML id and name attributes. Other files accept only line
  anchors, such as #L10, within their length.
- It scans the blocks of each document as CommonMark nests them in
  block quotes and list items, with lazy continuation lines, and strips
  their markers before it parses inline content. As on GitHub, front
  matter, fenced and indented code, code spans, HTML comments and
  blocks, and escaped brackets and tags hold no links, while the
  definitions and headings inside block quotes and list items count.
- It parses each reference definition at the start of a paragraph
  whole, with its title, before the inline content that follows, and
  accepts any destination without spaces, as GitHub does. A reference
  link in a heading keeps its brackets in the anchor when no definition
  holds its label.
- Links into the openvmm submodule resolve against its pinned commit
  when it is checked out and are skipped otherwise, as in the quality
  job, whose checkout does not fetch submodules.
- It also resolves, from the repository root, every doc/ path, with any
  anchor, that a tracked text file mentions. DOC_PATH_FIXTURES lists
  the test fixtures that name missing documents, such as doc/notes.md
  in scripts/test_nvx_tools.py, and an entry that its file no longer
  needs fails the check. A mention followed by a character that could
  extend the path, such as the * of a pattern, is ambiguous and is
  skipped.
- It reports each problem with its file and line, as an error
  annotation in GitHub Actions, and then exits with status 1.

scripts/test_markdown_links.py covers the slugs, the parsing, the
resolution in temporary repositories, including a submodule, the
fixtures, and the command line, and validate-nvx runs it on Linux and
Windows.

Retarget the README link to native-proxy-and-host-loopback, the only
broken link that the check found, and document the check in
doc/contribute.md, doc/setup.md, and doc/project-structure.md.

Fixes #563

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna) added a commit that referenced this pull request Oct 15, 2026
Nothing in CI checked the repository's relative Markdown links. #529
moved the design documentation, numbered its sections, and retargeted
every inbound link by hand, while an older link had already broken: the
MXC integration table in aci_edge_sandboxes/README.md linked the anchor
native-proxy-and-forwarded-ports of a heading that #512 had renamed to
"Native proxy and host loopback". GitHub opens a page at its top when an
anchor is missing, so such links break silently, and so do comments
that cite a document by path, as the citations of the removed time ABI
specification did (#562).

Add scripts/check_markdown_links.py, a standard-library check, and run
it in check-quality, whose job runs for every pull request that is
ready for review, including the documentation-only ones that are most
likely to break a link:

- It resolves every relative link and image target of the tracked
  Markdown files, whether an inline link, a reference definition, or an
  HTML href or src attribute, against the files that Git tracks, so a
  target that is untracked or differs in case, which GitHub cannot
  serve, fails too. A directory resolves, and its anchors are those of
  its README, whatever the README's case. An empty target, such as that
  of [Self](), links to its own document.
- It resolves each anchor against the heading slugs that GitHub
  generates, including the numeric suffixes of repeated headings, and
  against HTML id and name attributes. Other files accept only line
  anchors, such as #L10, within their length.
- It scans the blocks of each document as CommonMark nests them in
  block quotes, list items, and GitHub's footnote definitions, with
  lazy continuation lines, and strips their markers before it parses
  inline content. As on GitHub, front matter, fenced and indented code,
  code spans, HTML comments and blocks, the attributes of raw HTML
  tags, link titles, and escaped brackets and tags hold no links, while
  the definitions and headings inside those containers count. A
  footnote renders, with its fn- and fnref- anchors, only when inline
  content cites it.
- It parses each reference definition at the start of a paragraph
  whole, within that paragraph, with its title, before the inline
  content that follows, and accepts any destination without spaces, as
  GitHub does. In heading text, a reference link or image resolves only
  when a definition holds its label.
- Links into the openvmm submodule resolve against its pinned commit
  when it is checked out and are skipped otherwise, as in the quality
  job, whose checkout does not fetch submodules.
- It also resolves, from the repository root, every doc/ path, with any
  anchor, that a tracked text file mentions. DOC_PATH_FIXTURES lists
  the test fixtures that name missing documents, such as doc/notes.md
  in scripts/test_nvx_tools.py, and an entry that its file no longer
  needs fails the check. A mention followed by a character that could
  extend the path, such as the * of a pattern, is ambiguous and is
  skipped.
- It reports each problem with its file and line, as an error
  annotation in GitHub Actions, and then exits with status 1.

scripts/test_markdown_links.py covers the slugs, the parsing, the
resolution in temporary repositories, including a submodule, the
fixtures, and the command line, and validate-nvx runs it on Linux and
Windows.

Retarget the README link to native-proxy-and-host-loopback, the only
broken link that the check found, and document the check in
doc/contribute.md, doc/setup.md, and doc/project-structure.md.

Fixes #563

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna) added a commit that referenced this pull request Oct 15, 2026
Nothing in CI checked the repository's relative Markdown links. #529
moved the design documentation, numbered its sections, and retargeted
every inbound link by hand, while an older link had already broken: the
MXC integration table in aci_edge_sandboxes/README.md linked the anchor
native-proxy-and-forwarded-ports of a heading that #512 had renamed to
"Native proxy and host loopback". GitHub opens a page at its top when an
anchor is missing, so such links break silently, and so do comments
that cite a document by path, as the citations of the removed time ABI
specification did (#562).

Add scripts/check_markdown_links.py, a standard-library check, and run
it in check-quality, whose job runs for every pull request that is
ready for review, including the documentation-only ones that are most
likely to break a link:

- It resolves every relative link and image target of the tracked
  Markdown files, whether an inline link, a reference definition, or an
  HTML href or src attribute, against the files that Git tracks, so a
  target that is untracked or differs in case, which GitHub cannot
  serve, fails too. A directory resolves, and its anchors are those of
  its README, whatever the README's case. An empty target, such as that
  of [Self](), links to its own document.
- It resolves each anchor against the heading slugs that GitHub
  generates, including the numeric suffixes of repeated headings, and
  against HTML id and name attributes. Other files accept only line
  anchors, such as #L10, within their length.
- It scans the blocks of each document as CommonMark nests them in
  block quotes, list items, and GitHub's footnote definitions, with
  lazy continuation lines, and strips their markers before it parses
  inline content. As on GitHub, front matter, fenced and indented code,
  code spans, HTML comments and blocks, the attributes of raw HTML
  tags, link titles, image descriptions, and escaped brackets and tags
  hold no links, while the definitions and headings inside those
  containers count. A footnote renders, with its fn- and fnref-
  anchors, only when inline content cites it.
- It matches brackets as CommonMark does, so links do not nest: an
  inner link leaves the brackets around it literal. Each cell of a
  table parses alone, and the cells past the header's count, which
  GitHub drops, hold no links.
- It parses each reference definition at the start of a paragraph
  whole, within that paragraph, with its title, before the inline
  content that follows, and accepts any destination without spaces, as
  GitHub does. In heading text, a reference link or image resolves only
  when a definition holds its label.
- Links into the openvmm submodule resolve against its pinned commit
  when it is checked out and are skipped otherwise, as in the quality
  job, whose checkout does not fetch submodules.
- It also resolves, from the repository root, every doc/ path, with any
  anchor, that a tracked text file mentions. DOC_PATH_FIXTURES lists
  the test fixtures that name missing documents, such as doc/notes.md
  in scripts/test_nvx_tools.py, and an entry that its file no longer
  needs fails the check. A mention followed by a character that could
  extend the path, such as the * of a pattern, is ambiguous and is
  skipped.
- It reports each problem with its file and line, as an error
  annotation in GitHub Actions, and then exits with status 1.

scripts/test_markdown_links.py covers the slugs, the parsing, the
resolution in temporary repositories, including a submodule, the
fixtures, and the command line, and validate-nvx runs it on Linux and
Windows.

Retarget the README link to native-proxy-and-host-loopback, the only
broken link that the check found, and document the check in
doc/contribute.md, doc/setup.md, and doc/project-structure.md.

Fixes #563

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna) added a commit that referenced this pull request Oct 15, 2026
Nothing in CI checked the repository's relative Markdown links. #529
moved the design documentation, numbered its sections, and retargeted
every inbound link by hand, while an older link had already broken: the
MXC integration table in aci_edge_sandboxes/README.md linked the anchor
native-proxy-and-forwarded-ports of a heading that #512 had renamed to
"Native proxy and host loopback". GitHub opens a page at its top when an
anchor is missing, so such links break silently, and so do comments
that cite a document by path, as the citations of the removed time ABI
specification did (#562).

Add scripts/check_markdown_links.py, a standard-library check, and run
it in check-quality, whose job runs for every pull request that is
ready for review, including the documentation-only ones that are most
likely to break a link:

- It resolves every relative link and image target of the tracked
  Markdown files, whether an inline link, a reference definition, or an
  HTML href or src attribute, against the files that Git tracks, so a
  target that is untracked or differs in case, which GitHub cannot
  serve, fails too. A directory resolves, and its anchors are those of
  its README, whatever the README's case. An empty target, such as that
  of [Self](), links to its own document.
- It resolves each anchor against the slugs that GitHub generates from
  the rendered text of the headings, including the numeric suffixes of
  repeated headings, and against HTML id and name attributes. Other
  files accept only line anchors, such as #L10, within their length.
- It scans the blocks of each document as CommonMark nests them in
  block quotes, list items, and GitHub's footnote definitions, with
  lazy continuation lines, and strips their markers before it parses
  inline content. As on GitHub, front matter, fenced and indented code,
  and HTML comments and blocks hold no links, while the definitions and
  headings inside those containers count.
- It parses inline content left to right as CommonMark does: code
  spans, autolinks, and raw HTML are atomic, each destination is
  consumed with its title, and brackets match so that links do not nest
  and an image's description holds no links. A footnote renders, with
  its fn- and fnref- anchors, only where inline content cites it. Each
  cell of a table parses alone, and the cells past the header's count,
  which GitHub drops, hold no links.
- It parses each reference definition at the start of a paragraph
  whole, within that paragraph, with its title, before the inline
  content that follows, and accepts any destination without spaces, as
  GitHub does. In heading text, a reference link or image resolves only
  when a definition holds its label.
- Links into the openvmm submodule resolve against its pinned commit
  when it is checked out and are skipped otherwise, as in the quality
  job, whose checkout does not fetch submodules.
- It also resolves, from the repository root, every doc/ path, with any
  anchor, that a tracked text file mentions. DOC_PATH_FIXTURES lists
  the test fixtures that name missing documents, such as doc/notes.md
  in scripts/test_nvx_tools.py, and an entry that its file no longer
  needs fails the check. A mention followed by a character that could
  extend the path, such as the * of a pattern, is ambiguous and is
  skipped.
- It reports each problem with its file and line, as an error
  annotation in GitHub Actions, and then exits with status 1.

scripts/test_markdown_links.py covers the slugs, the parsing, the
resolution in temporary repositories, including a submodule, the
fixtures, and the command line, and validate-nvx runs it on Linux and
Windows.

Retarget the README link to native-proxy-and-host-loopback, the only
broken link that the check found, and document the check in
doc/contribute.md, doc/setup.md, and doc/project-structure.md.

Fixes #563

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna) added a commit that referenced this pull request Oct 15, 2026
Nothing in CI checked the repository's relative Markdown links. #529
moved the design documentation, numbered its sections, and retargeted
every inbound link by hand, while an older link had already broken: the
MXC integration table in aci_edge_sandboxes/README.md linked the anchor
native-proxy-and-forwarded-ports of a heading that #512 had renamed to
"Native proxy and host loopback". GitHub opens a page at its top when an
anchor is missing, so such links break silently, and so do comments
that cite a document by path, as the citations of the removed time ABI
specification did (#562).

Add scripts/check_markdown_links.py, a standard-library check, and run
it in check-quality, whose job runs for every pull request that is
ready for review, including the documentation-only ones that are most
likely to break a link:

- It resolves every relative link and image target of the tracked
  Markdown files, whether an inline link, a reference definition, or an
  HTML href or src attribute, against the files that Git tracks, so a
  target that is untracked or differs in case, which GitHub cannot
  serve, fails too. A directory resolves, and its anchors are those of
  its README, whatever the README's case. An empty target, such as that
  of [Self](), links to its own document.
- It resolves each anchor against the slugs that GitHub generates from
  the rendered text of the headings, including the numeric suffixes of
  repeated headings, and against HTML id and name attributes. Other
  files accept only line anchors, such as #L10, within their length.
- It scans the blocks of each document as cmark nests them in block
  quotes, list items, and GitHub's footnote definitions, with lazy
  continuation lines and tabs that advance to the next multiple of four
  columns, and strips their markers before it parses inline content. As
  on GitHub, front matter, fenced and indented code, and the seven kinds
  of HTML blocks hold no Markdown links, while the definitions and
  headings inside those containers count.
- It parses inline content left to right as CommonMark does: code
  spans, autolinks, and raw HTML are atomic, each destination is
  consumed with its title, and brackets match so that links do not nest
  and an image's description holds no links. A footnote renders, with
  its fn- and fnref- anchors, only where inline content cites it. Each
  cell of a table parses alone, and the cells past the header's count,
  which GitHub drops, hold no links.
- It tokenizes the raw HTML, in the order that GitHub renders it, as an
  HTML parser does, to find the tags whose attributes hold targets and
  anchors. A comment hides everything that renders up to its end, even
  in later blocks, while a CDATA section, processing instruction, or
  declaration ends at its first >.
- It parses each reference definition at the start of a paragraph
  whole, within that paragraph, with its title, before the inline
  content that follows, and accepts any destination without spaces, as
  GitHub does. In heading text, a reference link or image resolves only
  when a definition holds its label.
- It decodes a destination as cmark does, its character references
  before its backslash escapes, and an HTML attribute as GitHub does,
  which keeps its backslashes, and its named references without
  semicolons, literal.
- Links into the openvmm submodule resolve against its pinned commit
  when it is checked out and are skipped otherwise, as in the quality
  job, whose checkout does not fetch submodules.
- It also resolves, from the repository root, every doc/ path, with any
  anchor, that a tracked text file mentions. DOC_PATH_FIXTURES lists
  the test fixtures that name missing documents, such as doc/notes.md
  in scripts/test_nvx_tools.py, and an entry that its file no longer
  needs fails the check. A mention followed by a character that could
  extend the path, such as the * of a pattern, is ambiguous and is
  skipped.
- It reports each problem with its file and line, as an error
  annotation in GitHub Actions, and then exits with status 1.

scripts/test_markdown_links.py covers the slugs, the parsing, the
resolution in temporary repositories, including a submodule, the
fixtures, and the command line, and validate-nvx runs it on Linux and
Windows.

Retarget the README link to native-proxy-and-host-loopback, the only
broken link that the check found, and document the check in
doc/contribute.md, doc/setup.md, and doc/project-structure.md.

Fixes #563

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna) added a commit that referenced this pull request Oct 16, 2026
Nothing in CI checked the repository's relative Markdown links. #529
moved the design documentation, numbered its sections, and retargeted
every inbound link by hand, while an older link had already broken: the
MXC integration table in aci_edge_sandboxes/README.md linked the anchor
native-proxy-and-forwarded-ports of a heading that #512 had renamed to
"Native proxy and host loopback". GitHub opens a page at its top when an
anchor is missing, so such links break silently, and so do comments
that cite a document by path, as the citations of the removed time ABI
specification did (#562).

Add scripts/check_markdown_links.py, a standard-library check, and run
it in check-quality, whose job runs for every pull request that is
ready for review, including the documentation-only ones that are most
likely to break a link:

- It resolves every relative link and image target of the tracked
  Markdown files, whether an inline link, a reference definition, or
  the href of an HTML link or src of an image, against the files that
  Git tracks, so a target that is untracked or differs in case, which
  GitHub cannot serve, fails too. A directory resolves, and its anchors
  are those of its README, whatever the README's case. An empty target,
  such as that of [Self](), links to its own document.
- It resolves each anchor against the slugs that GitHub generates from
  the rendered text of the Markdown and HTML headings, including the
  numeric suffixes of repeated headings, and against the id and name
  attributes of the HTML elements that GitHub keeps. Other files accept
  only line anchors, such as #L10, within their length.
- It scans the blocks of each document as cmark nests them in block
  quotes, list items, and GitHub's footnote definitions, with lazy
  continuation lines and tabs that advance to the next multiple of four
  columns, and strips their markers before it parses inline content. As
  on GitHub, front matter, fenced and indented code, and the seven kinds
  of HTML blocks hold no Markdown links, while the definitions and
  headings inside those containers count.
- It parses inline content left to right as CommonMark does: code
  spans, autolinks, and raw HTML are atomic, each destination is
  consumed with its title, and brackets match so that links do not nest
  and an image's description holds no links. A footnote renders, with
  its fn- and fnref- anchors, only where inline content cites it. Each
  cell of a table parses alone, and the cells past the header's count,
  which GitHub drops, hold no links.
- It tokenizes the raw HTML, in the order that GitHub renders it, as an
  HTML parser does, to find the tags whose attributes hold targets and
  anchors. A comment hides everything that renders up to its end, even
  in later blocks, while a CDATA section, processing instruction, or
  declaration ends at its first >. The tags that GitHub filters, such
  as <script> and <iframe>, render as text.
- It parses each reference definition at the start of a paragraph
  whole, within that paragraph, with its title, before the inline
  content that follows, and accepts any destination without spaces, as
  GitHub does. In heading text, a reference link or image resolves only
  when a definition holds its label.
- It decodes a destination as cmark does, its character references
  before its backslash escapes, and an HTML attribute as GitHub does,
  which keeps its backslashes, and its named references without
  semicolons, literal.
- Links into the openvmm submodule resolve against its pinned commit
  when it is checked out and are skipped otherwise, as in the quality
  job, whose checkout does not fetch submodules.
- It also resolves, from the repository root, every doc/ path, with any
  anchor, that a tracked text file mentions. DOC_PATH_FIXTURES lists
  the test fixtures that name missing documents, such as doc/notes.md
  in scripts/test_nvx_tools.py, and an entry that its file no longer
  needs fails the check. A mention followed by a character that could
  extend the path, such as the * of a pattern, is ambiguous and is
  skipped.
- It reports each problem with its file and line, as an error
  annotation in GitHub Actions, and then exits with status 1.

scripts/test_markdown_links.py covers the slugs, the parsing, the
resolution in temporary repositories, including a submodule, the
fixtures, and the command line, and validate-nvx runs it on Linux and
Windows.

Retarget the README link to native-proxy-and-host-loopback, the only
broken link that the check found, and document the check in
doc/contribute.md, doc/setup.md, and doc/project-structure.md.

Fixes #563

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna) added a commit that referenced this pull request Oct 16, 2026
Nothing in CI checked the repository's relative Markdown links. #529
moved the design documentation, numbered its sections, and retargeted
every inbound link by hand, while an older link had already broken: the
MXC integration table in aci_edge_sandboxes/README.md linked the anchor
native-proxy-and-forwarded-ports of a heading that #512 had renamed to
"Native proxy and host loopback". GitHub opens a page at its top when an
anchor is missing, so such links break silently, and so do comments
that cite a document by path, as the citations of the removed time ABI
specification did (#562).

Add scripts/check_markdown_links.py, a standard-library check, and run
it in check-quality, whose job runs for every pull request that is
ready for review, including the documentation-only ones that are most
likely to break a link:

- It resolves every relative link and image target of the tracked
  Markdown files, whether an inline link, a reference definition, or
  the href of an HTML link or src of an image, against the files that
  Git tracks, so a target that is untracked or differs in case, which
  GitHub cannot serve, fails too. A directory resolves, and its anchors
  are those of its README, whatever the README's case. An empty target,
  such as that of [Self](), links to its own document.
- It resolves each anchor against the slugs that GitHub generates from
  the rendered text of the Markdown and HTML headings, including the
  numeric suffixes of repeated headings, and against the id and name
  attributes of the HTML elements that GitHub keeps. Other files accept
  only line anchors, such as #L10, within their length.
- It scans the blocks of each document as cmark nests them in block
  quotes, list items, and GitHub's footnote definitions, with lazy
  continuation lines and tabs that advance to the next multiple of four
  columns, and strips their markers before it parses inline content. As
  on GitHub, front matter, fenced and indented code, and the seven kinds
  of HTML blocks hold no Markdown links, while the definitions and
  headings inside those containers count.
- It parses inline content left to right as CommonMark does: code
  spans, autolinks, and raw HTML are atomic, each destination is
  consumed with its title, and brackets match so that links do not nest
  and an image's description holds no links. A footnote renders, with
  its fn- and fnref- anchors, only where inline content cites it. Each
  cell of a table parses alone, and the cells past the header's count,
  which GitHub drops, hold no links.
- It tokenizes the raw HTML, in the order that GitHub renders it, as an
  HTML parser does, to find the tags whose attributes hold targets and
  anchors. A comment hides everything that renders up to its end, even
  in later blocks, while a CDATA section, processing instruction, or
  declaration ends at its first >. The tags that GitHub filters, such
  as <script> and <iframe>, render as text.
- It parses each reference definition at the start of a paragraph
  whole, within that paragraph, with its title, before the inline
  content that follows, and accepts any destination without spaces, as
  GitHub does. In heading text, a reference link or image resolves only
  when a definition holds its label, and the delimiters of emphasis and
  strikethrough match as cmark-gfm matches them, those of each link's
  text alone.
- It decodes a destination as cmark does, its character references
  before its backslash escapes, and an HTML attribute as GitHub does,
  which keeps its backslashes, and its named references without
  semicolons, literal.
- Links into the openvmm submodule resolve against its pinned commit
  when it is checked out and are skipped otherwise, as in the quality
  job, whose checkout does not fetch submodules.
- It also resolves, from the repository root, every doc/ path, with any
  anchor, that a tracked text file mentions. DOC_PATH_FIXTURES lists
  the test fixtures that name missing documents, such as doc/notes.md
  in scripts/test_nvx_tools.py, and an entry that its file no longer
  needs fails the check. A mention followed by a character that could
  extend the path, such as the * of a pattern, is ambiguous and is
  skipped.
- It reports each problem with its file and line, as an error
  annotation in GitHub Actions, and then exits with status 1.

scripts/test_markdown_links.py covers the slugs, the parsing, the
resolution in temporary repositories, including a submodule, the
fixtures, and the command line, and validate-nvx runs it on Linux and
Windows.

Retarget the README link to native-proxy-and-host-loopback, the only
broken link that the check found, and document the check in
doc/contribute.md, doc/setup.md, and doc/project-structure.md.

Fixes #563

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna) added a commit that referenced this pull request Oct 16, 2026
Nothing in CI checked the repository's relative Markdown links. #529
moved the design documentation, numbered its sections, and retargeted
every inbound link by hand, while an older link had already broken: the
MXC integration table in aci_edge_sandboxes/README.md linked the anchor
native-proxy-and-forwarded-ports of a heading that #512 had renamed to
"Native proxy and host loopback". GitHub opens a page at its top when an
anchor is missing, so such links break silently, and so do comments
that cite a document by path, as the citations of the removed time ABI
specification did (#562).

Add scripts/check_markdown_links.py, a standard-library check, and run
it in check-quality, whose job runs for every pull request that is
ready for review, including the documentation-only ones that are most
likely to break a link:

- It resolves every relative link and image target of the tracked
  Markdown files, whether an inline link, a reference definition, or
  the href of an HTML link or src of an image, against the files that
  Git tracks, so a target that is untracked or differs in case, which
  GitHub cannot serve, fails too. A directory resolves, and its anchors
  are those of its README, whatever the README's case. An empty target,
  such as that of [Self](), links to its own document, and an image's
  anchor, such as GitHub's #gh-dark-mode-only, goes unchecked.
- It resolves each anchor against the slugs that GitHub generates from
  the rendered text of the Markdown and HTML headings, including the
  numeric suffixes of repeated headings, and against the id and name
  attributes of the HTML elements that GitHub keeps. Other files accept
  only line anchors, such as #L10, within their length.
- It scans the blocks of each document as cmark nests them in block
  quotes, list items, and GitHub's footnote definitions, with lazy
  continuation lines and tabs that advance to the next multiple of four
  columns, and strips their markers before it parses inline content. As
  on GitHub, front matter, fenced and indented code, and the seven kinds
  of HTML blocks hold no Markdown links, while the definitions and
  headings inside those containers count.
- It parses inline content left to right as CommonMark does: code
  spans, autolinks, and raw HTML are atomic, each destination is
  consumed with its title, and brackets match so that links do not nest
  and an image's description holds no links. A footnote renders, with
  its fn- and fnref- anchors, only where inline content cites it. A
  table is a block of its own, which ends as GitHub's table extension
  ends it, and each of its cells parses alone, while the cells past the
  header's count, which GitHub drops, hold no links.
- It tokenizes the raw HTML, in the order that GitHub renders it, as an
  HTML parser does, to find the tags whose attributes hold targets and
  anchors. A comment hides everything that renders up to its end, even
  in later blocks, while a CDATA section, processing instruction, or
  declaration ends at its first >. The tags that GitHub filters, such
  as <script> and <iframe>, render as text.
- It parses each reference definition at the start of a paragraph
  whole, within that paragraph, with its title, before the inline
  content that follows, and accepts any destination without spaces, as
  GitHub does. In heading text, a reference link or image resolves only
  when a definition holds its label, and the delimiters of emphasis and
  strikethrough match as cmark-gfm matches them, those of each link's
  text alone.
- It decodes a destination as cmark does, its character references
  before its backslash escapes, and an HTML attribute as GitHub does,
  which keeps its backslashes, and its named references without
  semicolons, literal.
- Links into the openvmm submodule resolve against its pinned commit
  when it is checked out and are skipped otherwise, as in the quality
  job, whose checkout does not fetch submodules.
- It also resolves, from the repository root, every doc/ path, with any
  anchor, that a tracked text file mentions. DOC_PATH_FIXTURES lists
  the test fixtures that name missing documents, such as doc/notes.md
  in scripts/test_nvx_tools.py, and an entry that its file no longer
  needs fails the check. A mention followed by a character that could
  extend the path, such as the * of a pattern, is ambiguous and is
  skipped.
- It reports each problem with its file and line, as an error
  annotation in GitHub Actions, and then exits with status 1.

scripts/test_markdown_links.py covers the slugs, the parsing, the
resolution in temporary repositories, including a submodule, the
fixtures, and the command line, and validate-nvx runs it on Linux and
Windows.

Retarget the README link to native-proxy-and-host-loopback, the only
broken link that the check found, and document the check in
doc/contribute.md, doc/setup.md, and doc/project-structure.md.

Fixes #563

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna) added a commit that referenced this pull request Oct 16, 2026
Nothing in CI checked the repository's relative Markdown links. #529
moved the design documentation, numbered its sections, and retargeted
every inbound link by hand, while an older link had already broken: the
MXC integration table in aci_edge_sandboxes/README.md linked the anchor
native-proxy-and-forwarded-ports of a heading that #512 had renamed to
"Native proxy and host loopback". GitHub opens a page at its top when an
anchor is missing, so such links break silently, and so do comments
that cite a document by path, as the citations of the removed time ABI
specification did (#562).

Add scripts/check_markdown_links.py, a standard-library check, and run
it in check-quality, whose job runs for every pull request that is
ready for review, including the documentation-only ones that are most
likely to break a link:

- It resolves every relative link and image target of the tracked
  Markdown files, whether an inline link, a reference definition, or
  the href of an HTML link or src of an image, against the files that
  Git tracks, so a target that is untracked or differs in case, which
  GitHub cannot serve, fails too. A directory resolves, and its anchors
  are those of its README, whatever the README's case. An empty target,
  such as that of [Self](), links to its own document, and an image's
  anchor, such as GitHub's #gh-dark-mode-only, goes unchecked.
- It resolves each anchor against the slugs that GitHub generates from
  the rendered text of the Markdown and HTML headings, including the
  numeric suffixes of repeated headings, and against the id and name
  attributes of the HTML elements that GitHub keeps. Other files accept
  only line anchors, such as #L10, within their length.
- It scans the blocks of each document as cmark nests them in block
  quotes, list items, and GitHub's footnote definitions, with lazy
  continuation lines and tabs that advance to the next multiple of four
  columns, and strips their markers before it parses inline content. As
  on GitHub, front matter, fenced and indented code, and the seven kinds
  of HTML blocks hold no Markdown links, while the definitions and
  headings inside those containers count.
- It parses inline content left to right as CommonMark does: code
  spans, autolinks, and raw HTML are atomic, each destination is
  consumed with its title, and brackets match so that links do not nest
  and an image's description holds no links. A footnote renders, with
  its fn- and fnref- anchors, only where inline content cites it. A
  table is a block of its own, which ends as GitHub's table extension
  ends it, and each of its cells parses alone, while the cells past the
  header's count, which GitHub drops, hold no links.
- It tokenizes the raw HTML, in the order that GitHub renders it, as an
  HTML parser does, to find the tags whose attributes hold targets and
  anchors. A comment hides everything that renders up to its end, even
  in later blocks, while a CDATA section, processing instruction, or
  declaration ends at its first >. The tags that GitHub filters, such
  as <script> and <iframe>, render as text.
- It parses each reference definition at the start of a paragraph
  whole, within that paragraph, with its title, before the inline
  content that follows, and accepts any destination without spaces, as
  GitHub does. In heading text, a reference link or image resolves only
  when a definition holds its label, and the delimiters of emphasis and
  strikethrough match as cmark-gfm matches them, those of each link's
  text alone.
- It decodes a destination as cmark does, its character references
  before its backslash escapes, and an HTML attribute as GitHub does,
  which keeps its backslashes, and its named references without
  semicolons, literal.
- Links into the openvmm submodule resolve against its pinned commit
  when it is checked out and are skipped otherwise, as in the quality
  job, whose checkout does not fetch submodules.
- It also resolves, from the repository root, every doc/ path, with any
  anchor, that a tracked text file mentions. DOC_PATH_FIXTURES lists
  the test fixtures that name missing documents, such as doc/notes.md
  in scripts/test_nvx_tools.py, and an entry that its file no longer
  needs fails the check. A mention followed by a character that could
  extend the path, such as the * of a pattern, is ambiguous and is
  skipped.
- It reports each problem with its file and line, as an error
  annotation in GitHub Actions, and then exits with status 1.

scripts/test_markdown_links.py covers the slugs, the parsing, the
resolution in temporary repositories, including a submodule, the
fixtures, and the command line, and validate-nvx runs it on Linux and
Windows.

Retarget the README link to native-proxy-and-host-loopback, the only
broken link that the check found, and document the check in
doc/contribute.md, doc/setup.md, and doc/project-structure.md.

Fixes #563

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna) added a commit that referenced this pull request Oct 16, 2026
Nothing in CI checked the repository's relative Markdown links. #529
moved the design documentation, numbered its sections, and retargeted
every inbound link by hand, while an older link had already broken: the
MXC integration table in aci_edge_sandboxes/README.md linked the anchor
native-proxy-and-forwarded-ports of a heading that #512 had renamed to
"Native proxy and host loopback". GitHub opens a page at its top when an
anchor is missing, so such links break silently, and so do comments
that cite a document by path, as the citations of the removed time ABI
specification did (#562).

Add scripts/check_markdown_links.py, a standard-library check, and run
it in check-quality, whose job runs for every pull request that is
ready for review, including the documentation-only ones that are most
likely to break a link:

- It resolves every relative link and image target of the tracked
  Markdown files, whether an inline link, a reference definition, or
  the href of an HTML link or src of an image, against the files that
  Git tracks, so a target that is untracked or differs in case, which
  GitHub cannot serve, fails too. A directory resolves, and its anchors
  are those of its README, whatever the README's case. An empty target,
  such as that of [Self](), links to its own document, and an image's
  anchor, such as GitHub's #gh-dark-mode-only, goes unchecked.
- It resolves each anchor, in lowercase as GitHub's script looks it up,
  against the slugs that GitHub generates from the rendered text of the
  Markdown and HTML headings, including the numeric suffixes of repeated
  headings, and against the id and name attributes of the HTML elements
  that GitHub keeps. Other files accept only line anchors, such as #L10,
  within their length.
- It scans the blocks of each document as cmark nests them in block
  quotes, list items, and GitHub's footnote definitions, with lazy
  continuation lines and tabs that advance to the next multiple of four
  columns, and strips their markers before it parses inline content. As
  on GitHub, front matter, fenced and indented code, and the seven kinds
  of HTML blocks hold no Markdown links, while the definitions and
  headings inside those containers count.
- It parses inline content left to right as CommonMark does: code spans,
  autolinks, including GitHub's bare ones, and raw HTML are atomic, each
  destination is consumed with its title, and brackets match so that
  links do not nest and an image's description holds no links. A
  footnote renders, with its fn- and fnref- anchors, only where inline
  content cites it. A table is a block of its own, which ends as
  GitHub's table extension ends it, and each of its cells parses alone,
  while the cells past the header's count, which GitHub drops, hold no
  links.
- It tokenizes the raw HTML, in the order that GitHub renders it, as an
  HTML parser does, to find the tags whose attributes hold targets and
  anchors. A comment hides everything that renders up to its end, even
  in later blocks, while a CDATA section, processing instruction, or
  declaration ends at its first >. The tags that GitHub filters, such
  as <script> and <iframe>, render as text.
- It parses each reference definition at the start of a paragraph
  whole, within that paragraph, with its title, before the inline
  content that follows, and accepts any destination without spaces, as
  GitHub does. In heading text, a reference link or image resolves only
  when a definition holds its label, a citation renders as its
  footnote's number, and the delimiters of emphasis and strikethrough
  match as cmark-gfm matches them, those of each link's text alone.
- It decodes a destination as cmark does, its character references
  before its backslash escapes, and an HTML attribute as GitHub does,
  which keeps its backslashes, and its named references without
  semicolons, literal.
- Links into the openvmm submodule resolve against its pinned commit
  when it is checked out and are skipped otherwise, as in the quality
  job, whose checkout does not fetch submodules.
- It also resolves, from the repository root, every doc/ path, with any
  anchor, that a tracked text file mentions. DOC_PATH_FIXTURES lists
  the test fixtures that name missing documents, such as doc/notes.md
  in scripts/test_nvx_tools.py, and an entry that its file no longer
  needs fails the check. A mention followed by a character that could
  extend the path, such as the * of a pattern, is ambiguous and is
  skipped.
- It reports each problem with its file and line, as an error
  annotation in GitHub Actions, and then exits with status 1.

scripts/test_markdown_links.py covers the slugs, the parsing, the
resolution in temporary repositories, including a submodule, the
fixtures, and the command line, and validate-nvx runs it on Linux and
Windows.

Retarget the README link to native-proxy-and-host-loopback, the only
broken link that the check found, and document the check in
doc/contribute.md, doc/setup.md, and doc/project-structure.md.

Fixes #563

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna) added a commit that referenced this pull request Oct 16, 2026
Nothing in CI checked the repository's relative Markdown links. #529
moved the design documentation, numbered its sections, and retargeted
every inbound link by hand, while an older link had already broken: the
MXC integration table in aci_edge_sandboxes/README.md linked the anchor
native-proxy-and-forwarded-ports of a heading that #512 had renamed to
"Native proxy and host loopback". GitHub opens a page at its top when an
anchor is missing, so such links break silently, and so do comments
that cite a document by path, as the citations of the removed time ABI
specification did (#562).

Add scripts/check_markdown_links.py, a standard-library check, and run
it in check-quality, whose job runs for every pull request that is
ready for review, including the documentation-only ones that are most
likely to break a link:

- It resolves every relative link and image target of the tracked
  Markdown files, whether an inline link, a reference definition, or
  the href of an HTML link or src of an image, against the files that
  Git tracks, so a target that is untracked or differs in case, which
  GitHub cannot serve, fails too. A directory resolves, and its anchors
  are those of its README, whatever the README's case. An empty target,
  such as that of [Self](), links to its own document, and an image's
  anchor, such as GitHub's #gh-dark-mode-only, goes unchecked.
- It resolves each anchor, in lowercase as GitHub's script looks it up,
  against the slugs that GitHub generates from the rendered text of the
  Markdown and HTML headings, including the numeric suffixes of repeated
  headings, and against the id and name attributes of the HTML elements
  that GitHub keeps. Other files accept only line anchors, such as #L10,
  within their length.
- It scans the blocks of each document as cmark nests them in block
  quotes, list items, and GitHub's footnote definitions, with lazy
  continuation lines and tabs that advance to the next multiple of four
  columns, and strips their markers before it parses inline content. As
  on GitHub, front matter, fenced and indented code, and the seven kinds
  of HTML blocks hold no Markdown links, while the definitions and
  headings inside those containers count.
- It parses inline content left to right as CommonMark does: code spans,
  autolinks, including GitHub's bare ones, and raw HTML are atomic, each
  destination is consumed with its title, and brackets match so that
  links do not nest and an image's description holds no links. A
  footnote renders, with its fn- and fnref- anchors, only where inline
  content cites it. A table is a block of its own, which ends as
  GitHub's table extension ends it, and each of its cells parses alone,
  while the cells past the header's count, which GitHub drops, hold no
  links.
- It tokenizes the raw HTML, in the order that GitHub renders it, as an
  HTML parser does, to find the tags whose attributes hold targets and
  anchors. A comment hides everything that renders up to its end, even
  in later blocks, while a CDATA section, processing instruction, or
  declaration ends at its first >. The tags that GitHub filters, such
  as <script> and <iframe>, render as text.
- It parses each reference definition at the start of a paragraph
  whole, within that paragraph, with its title, before the inline
  content that follows, and accepts any destination without spaces, as
  GitHub does. In heading text, a reference link or image resolves only
  when a definition holds its label, a citation renders as its
  footnote's number, and the delimiters of emphasis and strikethrough
  match as cmark-gfm matches them, those of each link's text alone.
- It decodes a destination as cmark does, trimmed and with its
  character references before its backslash escapes, and an HTML
  attribute as GitHub does, which keeps its backslashes, and its named
  references without semicolons, literal, and drops only the leading
  whitespace of a URL. As in HTML, the first attribute of each name
  hides the others.
- Links into the openvmm submodule resolve against its pinned commit
  when it is checked out and are skipped otherwise, as in the quality
  job, whose checkout does not fetch submodules.
- It also resolves, from the repository root, every doc/ path, with any
  anchor, that a tracked text file mentions. DOC_PATH_FIXTURES lists
  the test fixtures that name missing documents, such as doc/notes.md
  in scripts/test_nvx_tools.py, and an entry that its file no longer
  needs fails the check. A mention followed by a character that could
  extend the path, such as the * of a pattern, is ambiguous and is
  skipped.
- It reports each problem with its file and line, as an error
  annotation in GitHub Actions, and then exits with status 1.

scripts/test_markdown_links.py covers the slugs, the parsing, the
resolution in temporary repositories, including a submodule, the
fixtures, and the command line, and validate-nvx runs it on Linux and
Windows.

Retarget the README link to native-proxy-and-host-loopback, the only
broken link that the check found, and document the check in
doc/contribute.md, doc/setup.md, and doc/project-structure.md.

Fixes #563

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna) added a commit that referenced this pull request Oct 16, 2026
Nothing in CI checked the repository's relative Markdown links. #529
moved the design documentation, numbered its sections, and retargeted
every inbound link by hand, while an older link had already broken: the
MXC integration table in aci_edge_sandboxes/README.md linked the anchor
native-proxy-and-forwarded-ports of a heading that #512 had renamed to
"Native proxy and host loopback". GitHub opens a page at its top when an
anchor is missing, so such links break silently, and so do comments
that cite a document by path, as the citations of the removed time ABI
specification did (#562).

Add scripts/check_markdown_links.py, a standard-library check, and run
it in check-quality, whose job runs for every pull request that is
ready for review, including the documentation-only ones that are most
likely to break a link:

- It resolves every relative link and image target of the tracked
  Markdown files, whether an inline link, a reference definition, or
  the href of an HTML link or src of an image, against the files that
  Git tracks, so a target that is untracked or differs in case, which
  GitHub cannot serve, fails too. A directory resolves, and its anchors
  are those of its README, whatever the README's case. An empty target,
  such as that of [Self](), links to its own document, and an image's
  anchor, such as GitHub's #gh-dark-mode-only, goes unchecked.
- It resolves each anchor as GitHub's script looks it up, in lowercase
  and with the user-content- prefix of the ids that GitHub renders,
  which the anchor may hold already, against the slugs that GitHub
  generates from the rendered text of the Markdown and HTML headings,
  including the numeric suffixes of repeated headings, and against the
  id and name attributes of the HTML elements that GitHub keeps. Other
  files accept only line anchors, such as #L10, within their length.
- It scans the blocks of each document as cmark nests them in block
  quotes, list items, and GitHub's footnote definitions, with lazy
  continuation lines and tabs that advance to the next multiple of four
  columns, and strips their markers before it parses inline content. As
  on GitHub, front matter, fenced and indented code, and the seven kinds
  of HTML blocks hold no Markdown links, while the definitions and
  headings inside those containers count.
- It parses inline content left to right as CommonMark does: code spans,
  autolinks, including GitHub's bare ones, and raw HTML are atomic, each
  destination is consumed with its title, and brackets match so that
  links do not nest and an image's description holds no links. A
  footnote renders, with its fn- and fnref- anchors, only where inline
  content cites it. A table is a block of its own, which ends as
  GitHub's table extension ends it, and each of its cells parses alone,
  while the cells past the header's count, which GitHub drops, hold no
  links.
- It tokenizes the raw HTML, in the order that GitHub renders it, as an
  HTML parser does, to find the tags whose attributes hold targets and
  anchors. A comment hides everything that renders up to its end, even
  in later blocks, while a CDATA section, processing instruction, or
  declaration ends at its first >. The tags that GitHub filters, such
  as <script> and <iframe>, render as text.
- It parses each reference definition at the start of a paragraph
  whole, within that paragraph, with its title, before the inline
  content that follows, and accepts any destination without spaces, as
  GitHub does. In heading text, a reference link or image resolves only
  when a definition holds its label, a citation renders as its
  footnote's number, and the delimiters of emphasis and strikethrough
  match as cmark-gfm matches them, those of each link's text alone.
- It decodes a destination as cmark does, trimmed and with its
  character references before its backslash escapes, and an HTML
  attribute as GitHub does, which keeps its backslashes, and its named
  references without semicolons, literal, and drops only the leading
  whitespace of a URL. As in HTML, the first attribute of each name
  hides the others.
- Links into the openvmm submodule resolve against its pinned commit
  when it is checked out and are skipped otherwise, as in the quality
  job, whose checkout does not fetch submodules.
- It also resolves, from the repository root, every doc/ path, with any
  anchor, that a tracked text file mentions. DOC_PATH_FIXTURES lists
  the test fixtures that name missing documents, such as doc/notes.md
  in scripts/test_nvx_tools.py, and an entry that its file no longer
  needs fails the check. A mention followed by a character that could
  extend the path, such as the * of a pattern, is ambiguous and is
  skipped.
- It reports each problem with its file and line, as an error
  annotation in GitHub Actions, and then exits with status 1.

scripts/test_markdown_links.py covers the slugs, the parsing, the
resolution in temporary repositories, including a submodule, the
fixtures, and the command line, and validate-nvx runs it on Linux and
Windows.

Retarget the README link to native-proxy-and-host-loopback, the only
broken link that the check found, and document the check in
doc/contribute.md, doc/setup.md, and doc/project-structure.md.

Fixes #563

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna) added a commit that referenced this pull request Oct 16, 2026
Nothing in CI checked the repository's relative Markdown links. #529
moved the design documentation, numbered its sections, and retargeted
every inbound link by hand, while an older link had already broken: the
MXC integration table in aci_edge_sandboxes/README.md linked the anchor
native-proxy-and-forwarded-ports of a heading that #512 had renamed to
"Native proxy and host loopback". GitHub opens a page at its top when an
anchor is missing, so such links break silently, and so do comments
that cite a document by path, as the citations of the removed time ABI
specification did (#562).

Add scripts/check_markdown_links.py, a standard-library check, and run
it in check-quality, whose job runs for every pull request that is
ready for review, including the documentation-only ones that are most
likely to break a link:

- It resolves every relative link and image target of the tracked
  Markdown files, whether an inline link, a reference definition, or
  the href of an HTML link or src of an image, against the files that
  Git tracks, so a target that is untracked or differs in case, which
  GitHub cannot serve, fails too. A directory resolves, and its anchors
  are those of its README, whatever the README's case. An empty target,
  such as that of [Self](), links to its own document, and an image's
  anchor, such as GitHub's #gh-dark-mode-only, goes unchecked.
- It resolves each anchor as GitHub's script looks it up, in lowercase
  and with the user-content- prefix of the ids that GitHub renders,
  which the anchor may hold already, against the slugs that GitHub
  generates from the rendered text of the Markdown and HTML headings,
  including the numeric suffixes of repeated headings, and against the
  id and name attributes of the HTML elements that GitHub keeps. Other
  files accept only line anchors, such as #L10, within their length.
- It scans the blocks of each document as cmark nests them in block
  quotes, list items, and GitHub's footnote definitions, with lazy
  continuation lines and tabs that advance to the next multiple of four
  columns, and strips their markers before it parses inline content. As
  on GitHub, front matter, fenced and indented code, and the seven kinds
  of HTML blocks hold no Markdown links, while the definitions and
  headings inside those containers count.
- It parses inline content left to right as CommonMark does: code spans,
  autolinks, including GitHub's bare ones, and raw HTML are atomic, each
  destination is consumed with its title, and brackets match so that
  links do not nest and an image's description holds no links. A
  footnote renders, with its fn- and fnref- anchors, only where inline
  content cites it. A table is a block of its own, which ends as
  GitHub's table extension ends it, and each of its cells parses alone,
  while the cells past the header's count, which GitHub drops, hold no
  links.
- It tokenizes the raw HTML, in the order that GitHub renders it, as an
  HTML parser does, to find the tags whose attributes hold targets and
  anchors. A comment hides everything that renders up to its end, even
  in later blocks, while a CDATA section, processing instruction, or
  declaration ends at its first >. The tags that GitHub filters, such
  as <script> and <iframe>, render as text.
- It parses each reference definition at the start of a paragraph whole,
  within that paragraph, with its title, before the inline content that
  follows, and accepts any destination without spaces, as GitHub does. A
  definition counts where it renders, and wherever a rendered reference
  uses it, even in a footnote that does not render. In heading text, a
  reference link or image resolves only when a definition holds its
  label, a citation renders as its footnote's number, and the delimiters
  of emphasis and strikethrough match as cmark-gfm matches them, those
  of each link's text alone.
- It decodes a destination as cmark does, trimmed and with its
  character references before its backslash escapes, and an HTML
  attribute as GitHub does, which keeps its backslashes, and its named
  references without semicolons, literal, and drops only the leading
  whitespace of a URL. As in HTML, the first attribute of each name
  hides the others.
- Links into the openvmm submodule resolve against its pinned commit
  when it is checked out and are skipped otherwise, as in the quality
  job, whose checkout does not fetch submodules.
- It also resolves, from the repository root, every doc/ path, with any
  anchor, that a tracked text file mentions. DOC_PATH_FIXTURES lists
  the test fixtures that name missing documents, such as doc/notes.md
  in scripts/test_nvx_tools.py, and an entry that its file no longer
  needs fails the check. A mention followed by a character that could
  extend the path, such as the * of a pattern, is ambiguous and is
  skipped.
- It reports each problem with its file and line, as an error
  annotation in GitHub Actions, and then exits with status 1.

scripts/test_markdown_links.py covers the slugs, the parsing, the
resolution in temporary repositories, including a submodule, the
fixtures, and the command line, and validate-nvx runs it on Linux and
Windows.

Retarget the README link to native-proxy-and-host-loopback, the only
broken link that the check found, and document the check in
doc/contribute.md, doc/setup.md, and doc/project-structure.md.

Fixes #563

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna) added a commit that referenced this pull request Oct 16, 2026
Nothing in CI checked the repository's relative Markdown links. #529
moved the design documentation, numbered its sections, and retargeted
every inbound link by hand, while an older link had already broken: the
MXC integration table in aci_edge_sandboxes/README.md linked the anchor
native-proxy-and-forwarded-ports of a heading that #512 had renamed to
"Native proxy and host loopback". GitHub opens a page at its top when an
anchor is missing, so such links break silently, and so do comments
that cite a document by path, as the citations of the removed time ABI
specification did (#562).

Add scripts/check_markdown_links.py, a standard-library check, and run
it in check-quality, whose job runs for every pull request that is
ready for review, including the documentation-only ones that are most
likely to break a link:

- It resolves every relative link and image target of the tracked
  Markdown files, whether an inline link, a reference definition, or
  the href of an HTML link or src of an image, against the files that
  Git tracks, so a target that is untracked or differs in case, which
  GitHub cannot serve, fails too. A directory resolves, and its anchors
  are those of its README, whatever the README's case. An empty target,
  such as that of [Self](), links to its own document, and an image's
  anchor, such as GitHub's #gh-dark-mode-only, goes unchecked.
- It resolves each anchor as GitHub's script looks it up, in lowercase
  and with the user-content- prefix of the ids that GitHub renders,
  which the anchor may hold already, against the slugs that GitHub
  generates from the rendered text of the Markdown and HTML headings,
  including the numeric suffixes of repeated headings, and against the
  id and name attributes of the HTML elements that GitHub keeps. Other
  files accept only line anchors, such as #L10, within their length.
- It scans the blocks of each document as cmark nests them in block
  quotes, list items, and GitHub's footnote definitions, with lazy
  continuation lines and tabs that advance to the next multiple of four
  columns, and strips their markers before it parses inline content. As
  on GitHub, front matter, fenced and indented code, and the seven kinds
  of HTML blocks hold no Markdown links, while the definitions and
  headings inside those containers count.
- It parses inline content left to right as CommonMark does: code spans,
  autolinks, including GitHub's bare ones, and raw HTML are atomic, each
  destination is consumed with its title, and brackets match so that
  links do not nest and an image's description holds no links. A
  footnote renders, with its fn- and fnref- anchors, only where inline
  content cites it. A table is a block of its own, which ends as
  GitHub's table extension ends it, and each of its cells parses alone,
  while the cells past the header's count, which GitHub drops, hold no
  links.
- It tokenizes the raw HTML, in the order that GitHub renders it, as an
  HTML parser does, to find the tags whose attributes hold targets and
  anchors. A comment hides everything that renders up to its end, even
  in later blocks, while a CDATA section, processing instruction, or
  declaration ends at its first >. The tags that GitHub filters, such
  as <script> and <iframe>, render as text.
- It parses each reference definition at the start of a paragraph whole,
  within that paragraph, with its title, before the inline content that
  follows, and accepts any destination without spaces, as GitHub does. A
  definition counts where it renders, and wherever a rendered reference
  uses it, even in a footnote that does not render. In heading text, a
  reference link or image resolves only when a definition holds its
  label, a citation renders as its footnote's number, and the delimiters
  of emphasis and strikethrough match as cmark-gfm matches them, those
  of each link's text alone.
- It decodes a destination as cmark does, trimmed and with its
  character references before its backslash escapes, and an HTML
  attribute as GitHub does, which keeps its backslashes, and its named
  references without semicolons, literal, and drops only the leading
  whitespace of a URL. As in HTML, the first attribute of each name
  hides the others.
- Links into the openvmm submodule resolve against its pinned commit
  when it is checked out and are skipped otherwise, as in the quality
  job, whose checkout does not fetch submodules.
- It also resolves, from the repository root, every doc/ path, with any
  anchor, that a tracked text file mentions. DOC_PATH_FIXTURES lists
  the test fixtures that name missing documents, such as doc/notes.md
  in scripts/test_nvx_tools.py, and an entry that its file no longer
  needs fails the check. A mention followed by a character that could
  extend the path, such as the * of a pattern, is ambiguous and is
  skipped.
- It reports each problem with its file and line, as an error
  annotation in GitHub Actions, and then exits with status 1.

scripts/test_markdown_links.py covers the slugs, the parsing, the
resolution in temporary repositories, including a submodule, the
fixtures, and the command line, and validate-nvx runs it on Linux and
Windows.

Retarget the README link to native-proxy-and-host-loopback, the only
broken link that the check found, and document the check in
doc/contribute.md, doc/setup.md, and doc/project-structure.md.

Fixes #563

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna) added a commit that referenced this pull request Oct 16, 2026
Nothing in CI checked the repository's relative Markdown links. #529
moved the design documentation, numbered its sections, and retargeted
every inbound link by hand, while an older link had already broken: the
MXC integration table in aci_edge_sandboxes/README.md linked the anchor
native-proxy-and-forwarded-ports of a heading that #512 had renamed to
"Native proxy and host loopback". GitHub opens a page at its top when an
anchor is missing, so such links break silently, and so do comments
that cite a document by path, as the citations of the removed time ABI
specification did (#562).

Add scripts/check_markdown_links.py, a standard-library check, and run
it in check-quality, whose job runs for every pull request that is
ready for review, including the documentation-only ones that are most
likely to break a link:

- It resolves every relative link and image target of the tracked
  Markdown files, whether an inline link, a reference definition, or
  the href of an HTML link or src of an image, against the files that
  Git tracks, so a target that is untracked or differs in case, which
  GitHub cannot serve, fails too. A directory resolves, and its anchors
  are those of its README, whatever the README's case. An empty target,
  such as that of [Self](), links to its own document, and an image's
  anchor, such as GitHub's #gh-dark-mode-only, goes unchecked.
- It resolves each anchor as GitHub's script looks it up, in lowercase
  and with the user-content- prefix of the ids that GitHub renders,
  which the anchor may hold already, against the slugs that GitHub
  generates from the rendered text of the Markdown and HTML headings,
  including the numeric suffixes of repeated headings, and against the
  id and name attributes of the HTML elements that GitHub keeps. Other
  files accept only line anchors, such as #L10, within their length.
- It scans the blocks of each document as cmark nests them in block
  quotes, list items, and GitHub's footnote definitions, with lazy
  continuation lines and tabs that advance to the next multiple of four
  columns, and strips their markers before it parses inline content. As
  on GitHub, front matter, fenced and indented code, and the seven kinds
  of HTML blocks hold no Markdown links, while the definitions and
  headings inside those containers count.
- It parses inline content left to right as CommonMark does: code spans,
  autolinks, including GitHub's bare ones, and raw HTML are atomic, each
  destination is consumed with its title, and brackets match so that
  links do not nest and an image's description holds no links. A
  footnote renders, with its fn- and fnref- anchors, only where inline
  content cites it. A table is a block of its own, which ends as
  GitHub's table extension ends it, and each of its cells parses alone,
  while the cells past the header's count, which GitHub drops, hold no
  links.
- It tokenizes the raw HTML, in the order that GitHub renders it, as an
  HTML parser does, to find the tags whose attributes hold targets and
  anchors. A comment hides everything that renders up to its end, even
  in later blocks, while a CDATA section, processing instruction, or
  declaration ends at its first >. The tags that GitHub filters, such
  as <script> and <iframe>, render as text.
- As GitHub does, it slugs the text that each heading holds in the tree
  that libxml2's HTML parser builds from the rendered HTML. A start tag
  closes only the elements that libxml2 lets it close, so a paragraph's
  start tag closes a heading while a code block's does not, an end tag
  closes no element of a higher priority than its own, and a heading
  closes the heading that it starts in, so the text of a heading may run
  on across blocks.
- It parses each reference definition at the start of a paragraph whole,
  within that paragraph, with its title, before the inline content that
  follows, and accepts any destination without spaces, as GitHub does. A
  definition counts where it renders, and wherever a rendered reference
  uses it, even in a footnote that does not render. In heading text, a
  reference link or image resolves only when a definition holds its
  label, a citation renders as its footnote's number, and the delimiters
  of emphasis and strikethrough match as cmark-gfm matches them, those
  of each link's text alone.
- It decodes a destination as cmark does, trimmed and with its
  character references before its backslash escapes, and an HTML
  attribute as GitHub does, which keeps its backslashes, and its named
  references without semicolons, literal, and drops only the leading
  whitespace of a URL. As in HTML, the first attribute of each name
  hides the others.
- Links into the openvmm submodule resolve against its pinned commit
  when it is checked out and are skipped otherwise, as in the quality
  job, whose checkout does not fetch submodules.
- It also resolves, from the repository root, every doc/ path, with any
  anchor, that a tracked text file mentions. DOC_PATH_FIXTURES lists
  the test fixtures that name missing documents, such as doc/notes.md
  in scripts/test_nvx_tools.py, and an entry that its file no longer
  needs fails the check. A mention followed by a character that could
  extend the path, such as the * of a pattern, is ambiguous and is
  skipped.
- It reports each problem with its file and line, as an error
  annotation in GitHub Actions, and then exits with status 1.

scripts/test_markdown_links.py covers the slugs, the parsing, the
resolution in temporary repositories, including a submodule, the
fixtures, and the command line, and validate-nvx runs it on Linux and
Windows.

Retarget the README link to native-proxy-and-host-loopback, the only
broken link that the check found, and document the check in
doc/contribute.md, doc/setup.md, and doc/project-structure.md.

Fixes #563

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna) added a commit that referenced this pull request Oct 16, 2026
Nothing in CI checked the repository's relative Markdown links. #529
moved the design documentation, numbered its sections, and retargeted
every inbound link by hand, while an older link had already broken: the
MXC integration table in aci_edge_sandboxes/README.md linked the anchor
native-proxy-and-forwarded-ports of a heading that #512 had renamed to
"Native proxy and host loopback". GitHub opens a page at its top when an
anchor is missing, so such links break silently, and so do comments
that cite a document by path, as the citations of the removed time ABI
specification did (#562).

Add scripts/check_markdown_links.py, a standard-library check, and run
it in check-quality, whose job runs for every pull request that is
ready for review, including the documentation-only ones that are most
likely to break a link:

- It resolves every relative link and image target of the tracked
  Markdown files, whether an inline link, a reference definition, or
  the href of an HTML link or src of an image, against the files that
  Git tracks, so a target that is untracked or differs in case, which
  GitHub cannot serve, fails too. A directory resolves, and its anchors
  are those of its README, whatever the README's case. An empty target,
  such as that of [Self](), links to its own document, and an image's
  anchor, such as GitHub's #gh-dark-mode-only, goes unchecked.
- It resolves each anchor as GitHub's script looks it up, in lowercase
  and with the user-content- prefix of the ids that GitHub renders,
  which the anchor may hold already, against the slugs that GitHub
  generates from the rendered text of the Markdown and HTML headings,
  including the numeric suffixes of repeated headings, and against the
  id and name attributes of the HTML elements that GitHub keeps. Other
  files accept only line anchors, such as #L10, within their length.
- It scans the blocks of each document as cmark nests them in block
  quotes, list items, and GitHub's footnote definitions, with lazy
  continuation lines and tabs that advance to the next multiple of four
  columns, and strips their markers before it parses inline content. As
  on GitHub, front matter, fenced and indented code, and the seven kinds
  of HTML blocks hold no Markdown links, while the definitions and
  headings inside those containers count.
- It parses inline content left to right as CommonMark does: code spans,
  autolinks, including GitHub's bare ones, and raw HTML are atomic, each
  destination is consumed with its title, and brackets match so that
  links do not nest and an image's description holds no links. A
  footnote renders, with its fn- and fnref- anchors, only where inline
  content cites it. A table is a block of its own, which ends as
  GitHub's table extension ends it, and each of its cells parses alone,
  while the cells past the header's count, which GitHub drops, hold no
  links.
- It tokenizes the raw HTML, in the order that GitHub renders it, as an
  HTML parser does, to find the tags whose attributes hold targets and
  anchors. A comment hides everything that renders up to its end, even
  in later blocks, while a CDATA section, processing instruction, or
  declaration ends at its first >. The tags that GitHub filters, such
  as <script> and <iframe>, render as text.
- As GitHub does, it slugs the text that each heading holds in the tree
  that libxml2's HTML parser builds from the whole rendered document. A
  start tag closes only the elements that libxml2 lets it close, so a
  paragraph's start tag closes a heading while a code block's does not,
  an end tag closes no element of a higher priority than its own, and a
  heading closes the heading that it starts in. The text of a heading
  may thus run on across blocks, and the end tag of an element that an
  earlier block leaves open, such as a div, may close it.
- It parses each reference definition at the start of a paragraph whole,
  within that paragraph, with its title, before the inline content that
  follows, and accepts any destination without spaces, as GitHub does. A
  definition counts where it renders, and wherever a rendered reference
  uses it, even in a footnote that does not render. In heading text, a
  reference link or image resolves only when a definition holds its
  label, a citation renders as its footnote's number, and the delimiters
  of emphasis and strikethrough match as cmark-gfm matches them, those
  of each link's text alone.
- It decodes a destination as cmark does, trimmed and with its
  character references before its backslash escapes, and an HTML
  attribute as GitHub does, which keeps its backslashes, and its named
  references without semicolons, literal, and drops only the leading
  whitespace of a URL. As in HTML, the first attribute of each name
  hides the others.
- Links into the openvmm submodule resolve against its pinned commit
  when it is checked out and are skipped otherwise, as in the quality
  job, whose checkout does not fetch submodules.
- It also resolves, from the repository root, every doc/ path, with any
  anchor, that a tracked text file mentions. DOC_PATH_FIXTURES lists
  the test fixtures that name missing documents, such as doc/notes.md
  in scripts/test_nvx_tools.py, and an entry that its file no longer
  needs fails the check. A mention followed by a character that could
  extend the path, such as the * of a pattern, is ambiguous and is
  skipped.
- It reports each problem with its file and line, as an error
  annotation in GitHub Actions, and then exits with status 1.

scripts/test_markdown_links.py covers the slugs, the parsing, the
resolution in temporary repositories, including a submodule, the
fixtures, and the command line, and validate-nvx runs it on Linux and
Windows.

Retarget the README link to native-proxy-and-host-loopback, the only
broken link that the check found, and document the check in
doc/contribute.md, doc/setup.md, and doc/project-structure.md.

Fixes #563

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna) added a commit that referenced this pull request Oct 16, 2026
Nothing in CI checked the repository's relative Markdown links. #529
moved the design documentation, numbered its sections, and retargeted
every inbound link by hand, while an older link had already broken: the
MXC integration table in aci_edge_sandboxes/README.md linked the anchor
native-proxy-and-forwarded-ports of a heading that #512 had renamed to
"Native proxy and host loopback". GitHub opens a page at its top when an
anchor is missing, so such links break silently, and so do comments
that cite a document by path, as the citations of the removed time ABI
specification did (#562).

Add scripts/check_markdown_links.py, a standard-library check, and run
it in check-quality, whose job runs for every pull request that is
ready for review, including the documentation-only ones that are most
likely to break a link:

- It resolves every relative link and image target of the tracked
  Markdown files, whether an inline link, a reference definition, or
  the href of an HTML link or src of an image, against the files that
  Git tracks, so a target that is untracked or differs in case, which
  GitHub cannot serve, fails too. A directory resolves, and its anchors
  are those of its README, whatever the README's case. An empty target,
  such as that of [Self](), links to its own document, and an image's
  anchor, such as GitHub's #gh-dark-mode-only, goes unchecked.
- It resolves each anchor as GitHub's script looks it up, in lowercase
  and with the user-content- prefix of the ids that GitHub renders,
  which the anchor may hold already, against the slugs that GitHub
  generates from the rendered text of the Markdown and HTML headings,
  including the numeric suffixes of repeated headings, and against the
  id and name attributes of the HTML elements that GitHub keeps. Other
  files accept only line anchors, such as #L10, within their length.
- It scans the blocks of each document as cmark nests them in block
  quotes, list items, and GitHub's footnote definitions, with lazy
  continuation lines and tabs that advance to the next multiple of four
  columns, and strips their markers before it parses inline content. As
  on GitHub, front matter, fenced and indented code, and the seven kinds
  of HTML blocks hold no Markdown links, while the definitions and
  headings inside those containers count.
- It parses inline content left to right as CommonMark does: code spans,
  autolinks, including GitHub's bare ones, and raw HTML are atomic, each
  destination is consumed with its title, and brackets match so that
  links do not nest and an image's description holds no links. A
  footnote renders, with its fn- and fnref- anchors, only where inline
  content cites it. A table is a block of its own, which ends as
  GitHub's table extension ends it, and each of its cells parses alone,
  while the cells past the header's count, which GitHub drops, hold no
  links.
- It tokenizes the raw HTML, in the order that GitHub renders it, as an
  HTML parser does, to find the tags whose attributes hold targets and
  anchors. A comment hides everything that renders up to its end, even
  in later blocks, while a CDATA section, processing instruction, or
  declaration ends at its first >. The tags that GitHub filters, such
  as <script> and <iframe>, render as text.
- As GitHub does, it slugs the text that each heading holds in the tree
  that libxml2's HTML parser builds from the whole rendered document. A
  start tag closes only the elements that libxml2 lets it close, so a
  paragraph's start tag closes a heading while a code block's does not,
  an end tag closes no element of a higher priority than its own, and a
  heading closes the heading that it starts in. The text of a heading
  may thus run on across blocks, and the end tag of an element that an
  earlier block leaves open, such as a div, may close it.
- It parses each reference definition at the start of a paragraph whole,
  within that paragraph, with its title, before the inline content that
  follows, and accepts any destination without spaces, as GitHub does. A
  definition counts where it renders, and wherever a rendered reference
  uses it, even in a footnote that does not render. In heading text, a
  reference link or image resolves only when a definition holds its
  label, a citation renders as its footnote's number, and the delimiters
  of emphasis and strikethrough match as cmark-gfm matches them, those
  of each link's text alone.
- It decodes a destination as cmark does, trimmed and with its
  character references before its backslash escapes, and an HTML
  attribute as GitHub does, which keeps its backslashes, and its named
  references without semicolons, literal, and drops only the leading
  whitespace of a URL. As in HTML, the first attribute of each name
  hides the others.
- Links into the openvmm submodule resolve against its pinned commit
  when it is checked out and are skipped otherwise, as in the quality
  job, whose checkout does not fetch submodules.
- It also resolves, from the repository root, every doc/ path, with any
  anchor, that a tracked text file mentions. DOC_PATH_FIXTURES lists
  the test fixtures that name missing documents, such as doc/notes.md
  in scripts/test_nvx_tools.py, and an entry that its file no longer
  needs fails the check. A mention followed by a character that could
  extend the path, such as the * of a pattern, is ambiguous and is
  skipped.
- It reports each problem with its file and line, as an error
  annotation in GitHub Actions, and then exits with status 1.

scripts/test_markdown_links.py covers the slugs, the parsing, the
resolution in temporary repositories, including a submodule, the
fixtures, and the command line, and validate-nvx runs it on Linux and
Windows.

Retarget the README link to native-proxy-and-host-loopback, the only
broken link that the check found, and document the check in
doc/contribute.md, doc/setup.md, and doc/project-structure.md.

Fixes #563

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna) added a commit that referenced this pull request Oct 16, 2026
Nothing in CI checked the repository's relative Markdown links. #529
moved the design documentation, numbered its sections, and retargeted
every inbound link by hand, while an older link had already broken: the
MXC integration table in aci_edge_sandboxes/README.md linked the anchor
native-proxy-and-forwarded-ports of a heading that #512 had renamed to
"Native proxy and host loopback". GitHub opens a page at its top when an
anchor is missing, so such links break silently, and so do comments
that cite a document by path, as the citations of the removed time ABI
specification did (#562).

Add scripts/check_markdown_links.py, a standard-library check, and run
it in check-quality, whose job runs for every pull request that is
ready for review, including the documentation-only ones that are most
likely to break a link:

- It resolves every relative link and image target of the tracked
  Markdown files, whether an inline link, a reference definition, or
  the href of an HTML link or src of an image, against the files that
  Git tracks, so a target that is untracked or differs in case, which
  GitHub cannot serve, fails too. A directory resolves, and its anchors
  are those of its README, whatever the README's case. An empty target,
  such as that of [Self](), links to its own document, and an image's
  anchor, such as GitHub's #gh-dark-mode-only, goes unchecked.
- It resolves each anchor as GitHub's script looks it up, in lowercase
  and with the user-content- prefix of the ids that GitHub renders,
  which the anchor may hold already, against the slugs that GitHub
  generates from the rendered text of the Markdown and HTML headings,
  including the numeric suffixes of repeated headings, and against the
  id and name attributes of the HTML elements that GitHub keeps. Other
  files accept only line anchors, such as #L10, within their length.
- It scans the blocks of each document as cmark nests them in block
  quotes, list items, and GitHub's footnote definitions, with lazy
  continuation lines and tabs that advance to the next multiple of four
  columns, and strips their markers before it parses inline content. As
  on GitHub, front matter, fenced and indented code, and the seven kinds
  of HTML blocks hold no Markdown links, while the definitions and
  headings inside those containers count.
- It parses inline content left to right as CommonMark does: code spans,
  autolinks, including GitHub's bare ones, and raw HTML are atomic, each
  destination is consumed with its title, and brackets match so that
  links do not nest and an image's description holds no links. A
  footnote renders, with its fn- and fnref- anchors, only where inline
  content cites it. A table is a block of its own, which ends as
  GitHub's table extension ends it, and each of its cells parses alone,
  while the cells past the header's count, which GitHub drops, hold no
  links.
- It tokenizes the raw HTML, in the order that GitHub renders it, as an
  HTML parser does, to find the tags whose attributes hold targets and
  anchors. A comment hides everything that renders up to its end, even
  in later blocks, while a CDATA section, processing instruction, or
  declaration ends at its first >. The tags that GitHub filters, such
  as <script> and <iframe>, render as text.
- As GitHub does, it slugs the text that each heading holds in the tree
  that libxml2's HTML parser builds from the whole rendered document,
  where the paragraphs of tight lists, as cmark finds them, have no
  tags. A start tag closes only the elements that libxml2 lets it close,
  so a paragraph's start tag closes a heading while a code block's does
  not, an end tag closes no element of a higher priority than its own,
  and a heading closes the heading that it starts in. The text of a
  heading may thus run on across blocks, and the end tag of an element
  that an earlier block leaves open, such as a div, may close it.
- It parses each reference definition at the start of a paragraph whole,
  within that paragraph, with its title, before the inline content that
  follows, and accepts any destination without spaces, as GitHub does. A
  definition counts where it renders, and wherever a rendered reference
  uses it, even in a footnote that does not render. In heading text, a
  reference link or image resolves only when a definition holds its
  label, a citation renders as its footnote's number, and the delimiters
  of emphasis and strikethrough match as cmark-gfm matches them, those
  of each link's text alone.
- It decodes a destination as cmark does, trimmed and with its
  character references before its backslash escapes, and an HTML
  attribute as GitHub does, which keeps its backslashes, and its named
  references without semicolons, literal, and drops only the leading
  whitespace of a URL. As in HTML, the first attribute of each name
  hides the others.
- Links into the openvmm submodule resolve against its pinned commit
  when it is checked out and are skipped otherwise, as in the quality
  job, whose checkout does not fetch submodules.
- It also resolves, from the repository root, every doc/ path, with any
  anchor, that a tracked text file mentions. DOC_PATH_FIXTURES lists
  the test fixtures that name missing documents, such as doc/notes.md
  in scripts/test_nvx_tools.py, and an entry that its file no longer
  needs fails the check. A mention followed by a character that could
  extend the path, such as the * of a pattern, is ambiguous and is
  skipped.
- It reports each problem with its file and line, as an error
  annotation in GitHub Actions, and then exits with status 1.

scripts/test_markdown_links.py covers the slugs, the parsing, the
resolution in temporary repositories, including a submodule, the
fixtures, and the command line, and validate-nvx runs it on Linux and
Windows.

Retarget the README link to native-proxy-and-host-loopback, the only
broken link that the check found, and document the check in
doc/contribute.md, doc/setup.md, and doc/project-structure.md.

Fixes #563

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna) added a commit that referenced this pull request Oct 16, 2026
Nothing in CI checked the repository's relative Markdown links. #529
moved the design documentation, numbered its sections, and retargeted
every inbound link by hand, while an older link had already broken: the
MXC integration table in aci_edge_sandboxes/README.md linked the anchor
native-proxy-and-forwarded-ports of a heading that #512 had renamed to
"Native proxy and host loopback". GitHub opens a page at its top when an
anchor is missing, so such links break silently, and so do comments
that cite a document by path, as the citations of the removed time ABI
specification did (#562).

Add scripts/check_markdown_links.py, a standard-library check, and run
it in check-quality, whose job runs for every pull request that is
ready for review, including the documentation-only ones that are most
likely to break a link:

- It resolves every relative link and image target of the tracked
  Markdown files, whether an inline link, a reference definition, or
  the href of an HTML link or src of an image, against the files that
  Git tracks, so a target that is untracked or differs in case, which
  GitHub cannot serve, fails too. A directory resolves, and its anchors
  are those of its README, whatever the README's case. An empty target,
  such as that of [Self](), links to its own document, and an image's
  anchor, such as GitHub's #gh-dark-mode-only, goes unchecked.
- It resolves each anchor as GitHub's script looks it up, in lowercase
  and with the user-content- prefix of the ids that GitHub renders,
  which the anchor may hold already, against the slugs that GitHub
  generates from the rendered text of the Markdown and HTML headings,
  including the numeric suffixes of repeated headings, and against the
  id and name attributes of the HTML elements that GitHub keeps. Other
  files accept only line anchors, such as #L10, within their length.
- It scans the blocks of each document as cmark nests them in block
  quotes, list items, and GitHub's footnote definitions, with lazy
  continuation lines and tabs that advance to the next multiple of four
  columns, and strips their markers before it parses inline content. As
  on GitHub, front matter, fenced and indented code, and the seven kinds
  of HTML blocks hold no Markdown links, while the definitions and
  headings inside those containers count.
- It parses inline content left to right as CommonMark does: code spans,
  autolinks, including GitHub's bare ones, and raw HTML are atomic, each
  destination is consumed with its title, and brackets match so that
  links do not nest and an image's description holds no links. A
  footnote renders, with its fn- and fnref- anchors, only where inline
  content cites it. A table is a block of its own, which ends as
  GitHub's table extension ends it, and each of its cells parses alone,
  while the cells past the header's count, which GitHub drops, hold no
  links.
- It tokenizes the raw HTML, in the order that GitHub renders it, as an
  HTML parser does, to find the tags whose attributes hold targets and
  anchors. A comment hides everything that renders up to its end, even
  in later blocks, while a CDATA section, processing instruction, or
  declaration ends at its first >. The tags that GitHub filters, such
  as <script> and <iframe>, render as text.
- As GitHub does, it slugs the text that each heading holds in the tree
  that libxml2's HTML parser builds from the whole rendered document,
  where the paragraphs of tight lists, as cmark finds them, have no
  tags. A start tag closes only the elements that libxml2 lets it close,
  so a paragraph's start tag closes a heading while a code block's does
  not, an end tag closes no element of a higher priority than its own,
  and a heading closes the heading that it starts in. The text of a
  heading may thus run on across blocks, and the end tag of an element
  that an earlier block leaves open, such as a div, may close it.
- It parses each reference definition at the start of a paragraph whole,
  within that paragraph, with its title, before the inline content that
  follows, and accepts any destination without spaces, as GitHub does. A
  definition counts where it renders, and wherever a rendered reference
  uses it, even in a footnote that does not render. In heading text, a
  reference link or image resolves only when a definition holds its
  label, a citation renders as its footnote's number, and the delimiters
  of emphasis and strikethrough match as cmark-gfm matches them, those
  of each link's text alone.
- It decodes a destination as cmark does, trimmed and with its
  character references before its backslash escapes, and an HTML
  attribute as GitHub does, which keeps its backslashes, and its named
  references without semicolons, literal, and drops only the leading
  whitespace of a URL. As in HTML, the first attribute of each name
  hides the others.
- Links into the openvmm submodule resolve against its pinned commit
  when it is checked out and are skipped otherwise, as in the quality
  job, whose checkout does not fetch submodules.
- It also resolves, from the repository root, every doc/ path, with any
  anchor, that a tracked text file mentions. DOC_PATH_FIXTURES lists
  the test fixtures that name missing documents, such as doc/notes.md
  in scripts/test_nvx_tools.py, and an entry that its file no longer
  needs fails the check. A mention followed by a character that could
  extend the path, such as the * of a pattern, is ambiguous and is
  skipped.
- It reports each problem with its file and line, as an error
  annotation in GitHub Actions, and then exits with status 1.

scripts/test_markdown_links.py covers the slugs, the parsing, the
resolution in temporary repositories, including a submodule, the
fixtures, and the command line, and validate-nvx runs it on Linux and
Windows.

Retarget the README link to native-proxy-and-host-loopback, the only
broken link that the check found, and document the check in
doc/contribute.md, doc/setup.md, and doc/project-structure.md.

Fixes #563

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants