Project records as evidence: plans, ledgers, versions, handoffs and re-audits

CNA snapshot 3b43bd86  ·  Deep Dives › Porting & project practice  ·  source links pinned to 3b43bd86

✓

Evidence basis: source-verified at the pinned commit; tests exist (not executed for this page). Claims on this page were checked by reading the CNA source at commit 3b43bd86; unless a sentence says otherwise, nothing here was built or executed. Records, gates and tests were read at c6dec91f and re-checked at 3b43bd86 (the records no longer in the tree at c1c316b9) and sharp-runtime's database at the two named revisions; nothing was executed. The recommended practices are not enforced by CNA's tooling.

CNA's repository is rich in plans, handoffs, audits, remediation ledgers and integration records. That abundance is valuable and dangerous at once: an old plan can explain why code exists while being wrong about whether it exists today. This page is about practice rather than a subsystem: which record answers which question at 3b43bd86, how CNA turns some records into executable checks, how the product and ABI versions sit beside task history, and how to keep a defect ledger and a documentation re-audit honest. The inventory of the artifacts themselves, with their counts and trust limits, is on reviewing large AI-generated areas; this page adds the rules for using and writing them.

Documents have roles, not equal authority

RecordRole at this snapshotRead it as
handoff.mdA 524-line statement of the repository as it is now, kept true: a dated campaign continuation point at the top (2026-10-24), the project summary and status, a table of where current information lives, and in its section 5 the single authoritative list of known defects and limitations. Until 2026-10-11 it was NEXT.md, which also carried the dated session log of July to October 2026; that log is in Git historyThe current-state record; still a claim to check against source and tests
CHECKLIST.mdThe per-file porting rules: surface from the XNA reference assemblies, behaviour from FNA, every non-XNA member marked CNAEXT, tests for each public member or a stated host-dependent exceptionPolicy; compliance still has to be checked against code and gates
plans/58 plan_*.md files (63 Markdown files in all; plans that were finished or superseded are deleted and kept in Git history, as plans/README.md records for each; indexed by plans/README.md); alpha.1 had 62 at the repository rootIntent, decisions and measurements with dates, not current status

The XNA API class table (AUDIT.md), the per-file remediation archive and the integration/ campaign records are no longer in the tree at this snapshot; they stay in Git history and are read there as dated evidence. The common failure is reading one of these for a question it cannot answer. Source audits generated before the August 2026 modularisation cite paths that later moved; a plan row marked done records that work was done, not that its test still describes the framework (CNA's own WebGPU plan records eight such failures); a checklist says what should be true. Each is evidence of intent or history. Current behaviour is answered by source, generated configuration and tests.

Stable identifiers preserve causality

Numbered tasks record rationale, experiments, owner decisions, defects discovered while doing the work, and the boundary of each follow-up. A stable identifier lets commits, tests, comments and later tasks point back to one decision: GLTF-245 is referred to from three plans (plan_gltf.md, plan_cnb.md, plan_cnj.md), and RRC-051, the task that gave SDL_GPU its Apple and native-Windows CI legs, is cited from the comments of apple-ci.yml and d3d-windows-ci.yml. When a named path, test or commit moves, update the forward pointer; never erase the old identifier.

A done marker is still a claim. CNA's C ABI release-gate record (RELEASE_GATE.md) is a worked history of this: one criterion was closed on 2026-08-16, reopened the same day by a merge, closed again, and then found to have been partly an illusion, because 73 mapping rules matched a whole header with .* so that 121 newly merged symbols inherited "implemented and tested" without any C route existing; the rules were then pinned to the stable identifiers they had been reviewed against. A later closure was recorded only after a separate task verified it "rather than reading the measurement". The committed gate record at this snapshot reads Not ready, with one unmet criterion: 633 planned public C++ declarations do not yet have C mappings. The record has also drifted from its own measurement before: while the inventory's scope table left three modules unclassified, running the gate script reported two unmet criteria (coverage and limitations, both stopped by that scope check) and its --check failed although the committed record listed one. CNA classified those modules in generate_coverage_inventory.py; the gate was not re-run for this page.

When a task grows across independent renderers or failure modes, split it. In the same record, the reopened rows were bound by five sibling tasks (CBIND-051A to 051E) and a later pair (052A, 052B), so a partial fix never turned a broad row green and each child carried its own acceptance evidence. The parent becomes a tracking pointer.

Integration records need immutable inputs

Merging independently developed campaigns is where prose summaries fail most. A useful checkpoint packet for work of that scale contains:

  • the base and input commit SHAs, the role of each branch, and evidence that no hidden working-tree state was consumed;
  • the path-overlap analysis and the reason for the merge order;
  • the exact configure, build and test commands, with renderer and host identity;
  • machine-readable results, skip counts, logs, screenshots and generated manifests;
  • newly found defects kept separate from the requested change;
  • a clean-tree and diff check, and a handoff that names the next bounded step.

A sentence such as "all lanes passed" cannot replace the packet, especially when one lane was only compile-checked or one renderer ran under a substitute runtime. Until October 2026 CNA kept one such record set in the tree: integration/ held the August 2026 renderer-integration campaign (a lane inventory, an order file that names the constraints which really ordered the lanes, a history policy with an archive tag before each adapted lane, a range-diff on every lane card and dropped commits recorded with their reason, 21 lane cards, seven batch stabilisation notes and a final reconciliation), and it is now read from Git history (here at c1c316b9). Read it as dated evidence, not a live policy: its own addendum of 2026-08-09 says the external MetaGL and EasyGL history was later rewritten and the external archive tags are now unsigned, so the old external SHAs and signature claims there are not current pins. A merge commit's summary numbers belong to that merge; the site's worked reading of a head merge shows what a packet lets a reviewer reconstruct (a worked head-merge reading).

Executable ledgers resist prose drift

If a record names a finite set that code can derive, derive it and compare. CNA does this in several places:

  • Known defects as tests. GltfKnownDefectTests.cpp is built to hold each proven glTF import defect as a test that is green while CNA is broken (at this snapshot none remains: its header says all eight audit defects are fixed, leaving only the ledger test and the GLTF-241 fix witness): each such test asserts that the spec-derived expectation is still not met and that the divergence is exactly the recorded one, so a partial fix fails with the fixture and task named. Converting a case to passing is a manifest change in tools/gltf_fixtures, never a change to the fixture's expected values, and the record stays in the corpus as the regression witness. A ledger test, EveryOpenDefectInTheCorpusLedgerHasAnExecutableTestHere, checks both directions: every open defect in the corpus ledger has a test, and no remediated defect keeps a "still broken" one. At this snapshot the open set is empty; D1 to D8 and GLTF-241 are recorded as remediated with the closing task of each.
  • Registries against registries. check_renderer_identities.py compares the public renderer enum, the CMake selection list and the runtime registry against one canonical table, and also checks the documents that state the count, because "a count written into prose is a fact with no owner". It follows each identity through to its descriptor accessor after an identity once passed both spelling lists while its runtime registry entry made configuration fail. RendererDescriptorGate.cmake compiles every registered family's descriptor in every configuration, taking its inventory from the registry itself, with one stated exception: the one descriptor that includes SDK headers (Direct3D 9's, which answers adapter queries from real D3DCAPS9 and D3DFORMAT values) is left to its own family target, so a tree that does not select DIRECTX9 compiles it nowhere.
  • Status derived from rows. count_webgpu_plan_status.sh computes the WebGPU plan's summary from its row table so the headline count is never hand-maintained.

The general rule: check row uniqueness, the accepted status vocabulary, set equality, path existence and non-empty parsing, and keep negative fixtures so the validator can be shown to go red. Executable documents rot too: prose counts inside build files outlive the registry they describe (the site's drift table lists several, such as a probe file still describing an earlier identity count), and a gate that cannot find its input is not current evidence. Self-validation must include its input mapping, and a physical source move should update implementation, tests, validators and cited documents together.

Two version coordinates beside the task history

The product version comes from the root CMakeLists.txt: project(CNA VERSION 0.1.0) plus CNA_VERSION_PRERELEASE (empty since the 0.1.0 release), joined into CNA_VERSION_STRING and generated into CNA/Version.hpp from Version.hpp.in, where CNA::getVersionString() and the CNA_VERSION_* macros expose it. The experimental C ABI has its own version in abi.h (0.50.0 at this snapshot), which moves with ABI changes and not with product tags, as releasing.md states. A release record is complete only when it names both where the C surface is in scope. The snapshot documented here reports 0.1.0, the version of the 0.1.0 release it follows by 948 commits, and is not itself a release; pre-1.0 SemVer identifies a state and signals prerelease status without promising a stable API. Task identifiers stay the causal ledger underneath: a task answers why and how, the tag answers which immutable product state. The release procedure is making a release.

A planning database makes scope measurable

sharp-runtime keeps its coarse classifications and concrete tickets in a tracked SQLite file, plan.sqlite3, with a task and a ticket table. The numbers only mean something with their revision and their query:

sharp-runtime revisiontask rowsby statusoutofscope=1ticket rows
f827a6c5 (2026-08-10)16,2011,082 ported, 14,979 ignored, 140 ignore1182,183 (2,073 done, 67 blocked, 29 todo, 10 needs_user, 4 wontfix)
41b918c9 (2026-09-20, next)16,2071,087 ported, 14,980 ignored, 140 ignore1182,417 (2,409 done, 3 blocked, 5 wontfix)
sqlite3 plan.sqlite3 'SELECT status, COUNT(*) FROM task GROUP BY status;'
sqlite3 plan.sqlite3 'SELECT COUNT(*) FROM ticket;'

The denominator matters more than the ratio. The database deliberately classifies vast areas of .NET that sharp-runtime will never implement, so "1,087 ported" counts rows with a working C++ counterpart under this schema; it is not a 6.7 % behavioural parity with the whole base class library. The status vocabulary itself is uneven (ignore and ignored coexist), which is one more reason to quote the query with the number. Doxygen @note Status: tags in headers are secondary human hints that can drift from the database. How sharp-runtime audits its own claims is on sharp-runtime parity and verification.

Separate observations, inferences and decisions

A robust task record keeps three kinds of statement apart. An observation is directly reproducible ("this generated gate reports 633 planned public C++ declarations without a C mapping"). An inference connects evidence ("the gate reopened because a merge added public headers faster than routes were bound"). A decision sets policy: on 2026-08-28 the project owner ruled that binding the CNB content headers was not that pass's work, and the gate record keeps every such row planned with an open task rather than marking it not applicable, because "deferring work and deciding it has no C form are different claims". Mixing the three lets plans outlive their premises: a decision can stay valid after the cited defect moves, an observation can become false after a fix, and an inference needs revisiting when a new control run appears. Marking the category makes the next re-audit cheaper.

Handoff is part of implementation

An effective handoff states what is complete, what was verified and how, what remains, which repositories are read-only, and which actions need external approval. It lists failed approaches whose repetition would waste time and names the documents known to be stale so they are not used as status sources. It does not paste an ever-growing transcript into one file: CNA cut its NEXT.md session log down to its current part on 2026-10-11 and renamed it handoff.md, which AGENTS.md names as the home of current defects and limitations and asks to be updated with each task; it was last changed on 2026-10-27, the day of this snapshot, and like any handoff it still needs a date check before use.

The practical authority order when records disagree:

  1. current source and generated configuration;
  2. executable tests and retained artifacts;
  3. accepted integration and remediation records;
  4. plans, audits, READMEs and comments, as context.

Comments often preserve the best explanation and the worst status. Durable practice keeps both visible by linking every explanation to something a later maintainer can rerun.

Keeping a defect ledger honest

The same discipline applies to any list of known defects, including the one this site maintains. An identifier stays open until current source plus a regression test or reproducer proves it fixed, or until the public contract it concerned is genuinely gone; "the code looks different now" is not closure. A partial fix narrows the entry to the surviving case instead of closing it. New confirmed defects get new identifiers above the current maximum, so numbering never hides a deletion. Unsupported features and missing evidence are not defects: they belong in separate categories (functional gap, platform limitation, verification gap), each stated as a scoped fact. CNA's glTF ledger above is this rule made executable: a remediated record is kept as a witness, and the test fails if the bookkeeping and the code disagree in either direction.

Re-auditing documentation against a later snapshot

Documentation written against one commit drifts as the code moves. A re-audit that stays trustworthy follows a fixed order:

  1. compute the delta from the pinned commit to the new one, read-only, without touching the working checkout;
  2. inventory the first-parent campaigns and changed paths, and treat every changed path a page cites as a mandatory re-read (an unchanged path only proves the file is identical, not that the old claim was right);
  3. re-derive every registry-backed fact from source (renderer identities, capabilities, platform and audio implementations, the ABI version, the API census) instead of editing numbers by hand;
  4. carry forward every open defect identifier and account for each one that closes;
  5. scan for content about removed functionality, and keep the previous, published state intact until the new one validates.

For this site the concrete procedure, with its scripts and validators, is updating libcna.com after CNA changes; the remaining work at this snapshot, re-derived from source, is on the detailed roadmap.

Evidence and limits

Checked by reading, at c6dec91f and again against the changes up to 3b43bd86: handoff.md, AGENTS.md, CHECKLIST.md, the plans directory, RELEASE_GATE.md, GltfKnownDefectTests.cpp, check_renderer_identities.py, RendererDescriptorGate.cmake, the root CMakeLists.txt and releasing.md. The sharp-runtime figures were computed with the two queries shown against plan.sqlite3 as committed at each named revision. Plan and commit counts are file and history counts made for this page; nothing was built or executed. The checkpoint packet, the three-way statement classification and the authority order are recommended practice, not rules CNA's tooling enforces.

The same subject is explained at several altitudes. These are the neighbouring pages at each one.