Use Node 22+, npm ci, and npx playwright install chromium firefox webkit. Run npm test, npm run check:content, and npm run build before submitting a change. Commit regenerated reference.html, reference/, about.html, glossary.html, and sitemap.xml when content or presentation changes them. On a supported Linux distribution, Playwright's install --with-deps also installs required browser libraries.
A reference has label, url, and an optional kind: primary, research, community, taxonomy, database, or unclassified. Classify provenance explicitly, either on the reference or in the exact-URL publication records in reports/source-provenance.json; the software does not guess from the hostname. Database sources are curated records that may combine information from several contributors; label the exact record, not every page on the database operator's website. Taxonomy links classify an entry and tool homepages identify software. Neither establishes a particular claim.
Confirmed citation-destination problems are separate records in the same file's destinations array. Record the exact URL, observed status (chapter-missing, relocation-notice, or unrelated-destination), date, reviewer, observed title, final URL, supporting observation and capture SHA-256. Regenerate with node tools/source-provenance.mjs --write; the graph, reading pages and editorial inventory retain these notices independently of HTTP availability and source type. Remove or revise a notice only after observing that the specific problem has been resolved.
Publication records identify the reviewer, date, classification scope, supporting observation, publisher evidence and exact citation URLs. node tools/source-provenance.mjs --write validates those records and generates the compact public snapshot. npm run check:provenance checks that the snapshot agrees; staging requires the same check. Conflicting inline and publication classifications fail content validation. An explicit unclassified label is preserved and must be reconciled if it conflicts with a publication record. A changed URL needs its own record, including deliberate query/fragment variants. Keep these provenance decisions separate from graph claim reviews: they do not change review snapshots, add claims or establish applicability. The content report counts unresolved references after applying both explicit sources and publication records, and separately counts references classified through the registry.
Nodes and connections may carry a review record. Omission means unreviewed. A completed record must identify a reviewer and type, a real date, the limited scope, an exact content snapshot, and claims connected to declared source URLs and precise source sections. For example, for a generic teaching entry:
import { editorialSnapshot } from './src/data/editorial.js';
const entry = {
id: 'module', summary: 'A module is a collection of lessons.',
references: [{ label: 'Course handbook', url: 'https://example.org/handbook', kind: 'primary' }],
};
// Record only after the named reviewer has performed the comparison.
entry.review = {
status: 'source-reviewed',
reviewedAt: '2026-09-06',
reviewedBy: 'Name of the actual reviewer',
reviewerType: 'human',
reviewedContent: editorialSnapshot(entry),
scope: 'Definition in the introductory paragraph only',
claims: [{ text: 'The handbook defines a module as a collection of lessons.',
section: 'Terminology, “Module”', sources: ['https://example.org/handbook'] }],
};Use actual evidence and an actual review; do not copy the example's identity or date as a default. historical requires the same evidence record plus a scope explaining the historical context. A successful schema or link check cannot create a review record.
Changing any authored entry field makes that snapshot stale. Stale and malformed records cannot display completed claims or increase the coverage count. Recompare the affected material before recording a new snapshot. npm run build rejects invalid evidence before recreating .site/; CI runs the content audit explicitly as well.
Definitions and aliases live in src/data/glossary.js; original-source locators live in glossary-sources.js. Aliases must describe the same concept: AS-REP is a protocol reply, LDAPS names protected LDAP transport, and LOLBAS is a project, so each has its own entry.
glossary-reviews.js contains explicit snapshots of the wording, aliases and source records that a reviewer actually compared. It is never generated by a build or link check. After reading the supporting source sections and comparing the definition, record the exact snapshot, real date, reviewer identity, reviewerType (ai or human), and limited scope. Keep AI work labelled as AI work. Do not copy old snapshot text forward merely to make a check pass.
Changing wording, aliases, source URLs, source sections or provenance invalidates an existing comparison. The UI displays that stale status and check:content fails until the definition is compared again or the outdated record is removed. A missing record is explicitly unreviewed and is allowed; honest incompleteness is preferable to an unsupported record. Glossary counts are separate from node and connection reviews.
reports/glossary-source-capture.json records the original/final URLs, retrieval timestamps, HTTP status and SHA-256 of source responses used in this comparison. It supports provenance, not proof of correctness. Source sections remain the human-readable way to inspect each claim. Upstream changes are not monitored by the snapshot check; periodically reread sources, especially for product names and platform-specific definitions.
Shared relationship vocabulary lives in src/data/relationships.js. Each definition declares source-based or project-convention. A source-based definition needs named sources, provenance, and precise source sections. A project convention is explicitly authored terminology; it must not be counted as an independent source comparison.
After making an actual comparison, record its date, reviewer and type, scope, and exact reviewedContent in relationship-reviews.js. These records are explicit authoring artifacts, never build output. Changes to wording, origin, or citations invalidate the old record and fail content validation and staging. The three current source comparisons and one project convention are documented in reports/RELATIONSHIP_REVIEW.md.
The adapter shows the shared definition separately from the connection's authored description, conditions, references, and review. Shared-definition sources never become the connection's declared evidence. A generic untyped link is displayed as a connection. Do not backfill semantic types by guessing from endpoint kinds.
Preserve authored affects notes. Missing applicability is unknown. Structured versions tags describe the author's recorded scope. New catalog releases are catalogOnly until coverage is explicitly documented; existing range() calls exclude those releases so adding a catalog item does not change old data.
Generic graph checks reject duplicate or reserved identifiers, missing roots, invalid endpoints, unknown relationships, and duplicate source/target pairs. They also test visibility, layout, and search behavior. These checks validate software/data integrity, not domain-specific feasibility.
A map may contain cycles. The security domain owns its relationship vocabulary; HackGraph edges are not imported ATT&CK relationships. Explain whether a connection organizes topics or makes a substantive assertion, and document the evidence for that assertion. Endpoint kinds, layout order, or a taxonomy citation on either endpoint do not establish a connection's meaning. Do not infer that a category's technique link exhaustively describes the whole category.
Test layout or gesture changes with generic course fixtures. Keep the engine independent of domain terms. Maintain the single displayed camera state, common animation clock, measured card heights, readable expansion scale, and reduced-motion behavior. Avoid per-frame DOM/SVG geometry reads.
reports/content-coverage.json inventories evidence coverage. reports/reference-availability.json records request time, original/final URL, HTTP code, individual attempts, and error diagnostics. It contains no verification claim. ATT&CK snapshots record the upstream URL, collection version, content hash, date, used identifier statuses, and explicit replacement relationships. Review a publisher's replacement evidence before deliberately changing an identifier; a replacement does not establish the accuracy of an associated claim.
After a complete URL audit, npm run references:status explicitly updates the public access-exception snapshot. It preserves the original citations and records blocked requests separately from unreachable or not-found responses. Regenerate reading pages after updating the snapshot. The weekly audit remains read-only toward public metadata and uploads its report for inspection.
src/data/source-locations.js holds separately recorded AI publication-location observations. These identify the article found through the web reader, which may use indexed content. They never change an HTTP result or count as a completed claim review. Retain the earlier URL and observed publisher evidence when correcting a moved citation; see reports/DEFENSIVE_REVIEW.md for the completed examples.
npm run review:inventory generates JSON and CSV covering all graph entries and glossary definitions. These files are an inventory, not completed reviews; keep reviewer notes separately so regeneration cannot erase them. See reports/HUMAN_REVIEW.md for the handoff and remaining manual checks.
npm run build recreates the owned .site/ directory and verifies the result. tools/site-artifact.mjs defines its publication policy: named top-level assets, the three declared reference pages, runtime JavaScript/CSS under src/ excluding test/spec files and test/fixture directories, and WOFF2 files under engine/fonts/. Add new reading pages and fixed assets to STATIC_PUBLIC_FILES. Runtime files under src/ are public by this convention. Reports, screenshots, tools, dependencies, other source-file types, and this guide remain outside the site. Review generated changes before publishing; a successful build is not a publication action.
npm run verify:build checks an existing build without rebuilding or repairing it. It compares the actual and manifest file lists with the publication policy, recomputes the source digest, checks every staged file, and compares the injected build metadata with the manifest. A non-null commit must still match a clean checkout. Missing/extra files, stale source inputs, invalid metadata, and symbolic links fail the check. src/buildInfo.js is an intentional metadata substitution; its original source bytes participate in the input digest. Verification reads staged JavaScript as bytes and never imports it.
Use npm run layout:geometry for generic connector crossings and ordering comparisons, npm run layout:sequences for repeated disclosure and collapse, and node tools/caption-lab.mjs against the local development server for caption screenshots and rectangle checks. Timing measurements are separate: run node tools/render-lab.mjs without concurrent browser workloads. Run node tools/stability-lab.mjs for before/after screenshots and a desktop motion capture using the real expansion control. These diagnostics do not evaluate security-domain route viability.
Browser tests build and exercise .site/. The full npm test command ends with artifact verification. CI verifies the existing directory again after generated-page parity checks and before upload; the deployment job does not rebuild it.
The scheduled taxonomy checker writes reports/taxonomy-status.json even when identifiers need attention, and the workflow retains it alongside the HTTP audit. It collects structured mapping IDs and canonical HTTPS ATT&CK technique/tactic URLs from graph references, glossary sources and shared-definition references. The report retains each occurrence location and distinguishes citation-only IDs. Free-text mentions, other URL families and mapping suitability are outside this check. The deliberate public snapshot omits those development locations while retaining scope, counts and identifier results. --input uses a local publisher bundle and records that distinction; --report chooses an alternate report file. Workflow YAML was checked locally with actionlint 1.7.12; this does not establish that a hosted workflow or deployment has run.