diff --git a/AGENTS.md b/AGENTS.md index 9af54e6..b33462d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,6 +2,10 @@ Guidance for anyone writing code in PureXML. +> Working in **Google Antigravity** (Gemini)? Start with `GEMINI.md` (always-on +> rules) and `docs/onboarding-antigravity.md` (the full, literal, step-by-step +> procedure for the schema-conformance work). + ## Rule Loading At the start of a session, read this file and the rules under `docs/rules/` that diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 0000000..46f2a4a --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,72 @@ +# GEMINI.md, always-on rules for Antigravity agents in PureXML + +These rules are always in effect. The full, step-by-step procedure is in +**`docs/onboarding-antigravity.md`**, read it before your first change. When a +rule here and a guideline elsewhere conflict, this file wins. + +## The prime directive + +PureXML is an XML/XSD validator. **Never reject a valid schema or a valid +document.** In the conformance counts this is `valid-schemas-rejected`; it is +**1** and must **never rise**. Under-rejecting (missing an invalid schema) is +acceptable; over-rejecting (rejecting a valid one) is a failure and must not be +merged. When unsure, do less. + +## Hard constraints (never violate) + +- No external SwiftPM dependencies. No C/C++/Objective-C/JavaScript/generated + runtime. Pure Swift only. +- Public API stays under the `PureXML` namespace tree. +- Must build on macOS, Linux, and WASI. +- Reference implementations are for **reading only, never copying** (clean-room). +- Tests import only `Foundation`, `Testing`, and `@testable import PureXML`. + +## Per-change procedure (do every step; details in the onboarding doc §6) + +1. **Measure first**: read `/tmp/xsts-failures.txt`, pick a family, **read the + actual `.xsd` files** before coding. Do one rule per change. +2. Implement conservatively in `Sources/Schema/`; return `[String]` errors; wire + into an aggregator in `SchemaIDAttribute.swift`. +3. Add unit tests (one rejected case + several accepted cases). Write the test + file **before** launching the release XSTS run. +4. Run all gates (build, `swiftformat --lint`, `swiftlint --strict`, + `check-style.sh`, `check-namespacing.sh`, `swift test`, `check-wasm.sh`). +5. Run the conformance differential: + `XSTS_ROOT=/private/tmp/xsts/xmlschema2006-11-06 swift test -c release --filter XSTS`. + `invalid-schemas-accepted` must go DOWN and **no other bucket may rise**. +6. Run an **adversarial critic** that tries to construct a valid schema your + change rejects. Fix any false positive before merging. (Mandatory.) +7. Update the pinned baseline in `Tests/XSTSSuiteTests.swift`, add a + `CHANGELOG.md` entry, update `docs/schema-validity-burndown.md`. +8. Branch off `main`, commit, `git merge --ff-only`, push to GitHub. + +## The traps that recur (onboarding doc §8) + +- **Namespace conflation:** resolve a name's namespace with + `XSDNode.referenceNamespace(ref, bindings)`; never just strip the prefix. A + built-in (`xs:*`) or imported component must not be confused with a + same-local-name user one. This has caused false positives 7+ times. +- **swiftformat vs swiftlint brace conflict:** extract a multi-condition `if` + into a `Bool` helper; turn a long `for ... where` into `guard ... continue`. +- **Limits:** file ≤ 400 lines, type body ≤ 250, cyclomatic ≤ 10, params ≤ 5, + line ≤ 180. Split into new files/helpers; bundle args into a labeled tuple. +- Read schema attributes **unprefixed** (`prefix == nil`); skip foreign-namespace + nodes and `xs:appinfo`/`xs:documentation`. + +## Commit discipline (never violate) + +- Commit format: `type(scope): summary`. **No em dashes** anywhere. +- **No AI attribution** of any kind in commits, PRs, branches, code, or docs (no + "Generated with", no "Co-Authored-By", no mention of Gemini/AI/agent). All work + reads as human-authored. (`docs/onboarding-antigravity.md` is the sole + exception, explicitly requested.) +- **Never push to a GitLab remote.** GitHub remotes only; check the URL first. +- Merge to `main` `--ff-only`. The pre-push hook runs `swift test` and a + commit-message check. + +## Definition of done + +All gates green; conformance differential shows invalid-accepted down and **no +other bucket up** (valid-rejected still 1); the critic found zero false +positives; baseline/CHANGELOG/burndown updated. If you rejected even one valid +schema, the change is wrong regardless of how many invalid ones it caught. diff --git a/docs/onboarding-antigravity.md b/docs/onboarding-antigravity.md new file mode 100644 index 0000000..ec5752c --- /dev/null +++ b/docs/onboarding-antigravity.md @@ -0,0 +1,471 @@ +# Onboarding: working on PureXML in Google Antigravity (Gemini) + +This document is written for an AI agent (Gemini) operating inside Google +Antigravity. It is deliberately literal and exhaustive. **Do exactly what it +says, in the order it says.** When a step says "run X", run X and read its +output before continuing. Do not skip the verification steps. Do not infer; when +this document gives a command or a rule, follow it verbatim. + +Antigravity auto-loads `GEMINI.md` (repo root) and `AGENTS.md` as always-on +rules. This file is the long-form companion: `GEMINI.md` is the short +constitution, this file is the full procedure. Read both before your first +change. + +--- + +## 0. The one thing that matters most + +PureXML is an XML/XSD validator. Its single most important quality is: + +> **Never reject a valid schema or a valid document.** + +In the conformance numbers (Section 5) this is the bucket +`valid-schemas-rejected`. It is currently **1** and must **never go up**. A +change that fixes ten bad schemas but wrongly rejects one good schema is a +**failure** and must not be merged. When in doubt, do less: it is always +acceptable to *miss* an invalid schema (under-reject); it is never acceptable to +reject a valid one (over-reject). This asymmetry drives every decision below. + +--- + +## 1. What PureXML is + +- A **pure-Swift** XML parser, emitter, and **XSD 1.0 schema validator**, with + XPath/XSLT support. It is a standalone Swift package (root `Package.swift`), + not part of a larger workspace. +- It compiles a `` document into an internal model and then validates + XML instances against it. +- The active work is **schema-validity conformance**: making the validator + correctly *reject* invalid schemas (and accept valid ones), measured against + the W3C XML Schema Test Suite (XSTS). + +### Non-negotiable constraints (these are hard rules, never violate them) + +1. **No external SwiftPM dependencies.** Do not add a package dependency. Ever. +2. **No C, C++, Objective-C, JavaScript, or any generated parser runtime** in the + package. Pure Swift only. +3. **Public API lives under the `PureXML` namespace tree.** Do not add public + symbols outside it. +4. **Must build on macOS, Linux, and WASI.** Do not use APIs unavailable on + Linux/WASI. Foundation is allowed only for file access in tests. +5. **Clean-room only.** There are reference implementations (Xerces, libxml2, + .NET, Python `xmlschema`) under a separate research repo. You may **read them + to understand a rule**, but you must **never copy their code**. Re-derive in + Swift. +6. **Tests may import only** `Foundation`, `Testing`, and `@testable import + PureXML`. Never `import PureXML` (use `@testable`). Never import anything else + in a test. + +--- + +## 2. Repository layout (where things are) + +- `Sources/Schema/`, the XSD schema engine. **Almost all your work is here.** +- `Sources/` (other folders), the XML parser, emitter, XPath, XSLT, regex. +- `Tests/`, Swift Testing test suites. One file per topic. +- `Tests/XSTSSuiteTests.swift`, the opt-in conformance runner (Section 5). +- `docs/`, rules and design docs. **Read these:** + - `docs/production-readiness.md`, the bar for shipping. Read it every time. + - `docs/schema-validity-burndown.md`, the conformance plan, priority order, + and the per-change protocol. **This is your playbook.** + - `docs/rules/code-style.md`, `namespacing.md`, `cross-platform.md`, + `testing.md`, `verification.md`, `research-first.md`, coding rules. + - `docs/xsts-deviations.md`, documented, intentional deviations (do not + "fix" these; they are spec ambiguities). +- `CHANGELOG.md`, every shippable change gets an entry here. +- `AGENTS.md`, `CLAUDE.md`, `GEMINI.md`, agent instruction files (read first). + +--- + +## 3. Environment and tools + +You need these on the machine (they are already installed in the normal dev +environment): + +- `swift` (Swift 6.1+ toolchain). +- `swiftformat` and `swiftlint` (formatting/lint gates). +- The XSTS corpus, unpacked at: **`/private/tmp/xsts/xmlschema2006-11-06`**. + This path is the value of the `XSTS_ROOT` environment variable used by the + conformance runner. If it is missing, the conformance runner silently does + nothing (it is opt-in), so confirm the directory exists before relying on it: + `ls /private/tmp/xsts/xmlschema2006-11-06/suite.xml`. + +--- + +## 4. The commands (run these, in this order, for every change) + +These are the project's gates. A change is not done until **all** pass. + +```sh +# 1. Build. +swift build + +# 2. Format check, then lint (strict). Fix any violation before continuing. +swiftformat . --config .swiftformat --lint +swiftlint --config .swiftlint.yml --strict + +# 3. Project style/namespacing checks. +bash scripts/check-style.sh +bash scripts/check-namespacing.sh + +# 4. Full unit test suite (fast; excludes the opt-in XSTS runner). +swift test + +# 5. WASI build check (cross-platform gate). +bash scripts/check-wasm.sh + +# 6. The conformance differential (opt-in; ~13 seconds in release). +XSTS_ROOT=/private/tmp/xsts/xmlschema2006-11-06 swift test -c release --filter XSTS +``` + +If `swiftformat --lint` reports a file needs formatting, run +`swiftformat . --config .swiftformat` (no `--lint`) to fix it, then re-run the +lint. **Always format new files before committing**, the pre-push hook will +otherwise reject the push. + +--- + +## 5. The conformance campaign (what success is measured by) + +The W3C XSTS runner (`Tests/XSTSSuiteTests.swift`) runs the entire suite and +counts four buckets. The expected counts are **pinned** as constants near the +top of that file: + +| Constant | Meaning | Current value | Direction | +|---|---|---|---| +| `knownSchemaValidRejected` | valid schemas we wrongly **reject** | **1** | must NEVER rise; drive toward 0 | +| `knownSchemaInvalidAccepted` | invalid schemas we wrongly **accept** | **394** | drive DOWN | +| `knownInstanceValidRejected` | valid instances we wrongly **reject** | **180** | must not rise; drive toward 0 | +| `knownInstanceInvalidAccepted`| invalid instances we wrongly accept | **160→158** (158) | drive down | + +The runner asserts these counts **exactly**. So: + +- When your change correctly rejects N more invalid schemas, the actual + `schemaInvalidAccepted` drops by N and the test "fails" reporting the new + lower number. That is success, **update the pinned constant to the new + number**. +- If **any** bucket goes UP, that is a regression. **Stop. Revert or fix.** A + rise in `schemaValidRejected` or `instanceValidRejected` (a false positive) is + the worst case and is never acceptable. + +The per-case failures are written to **`/tmp/xsts-failures.txt`** after each +run, one line per disagreement, e.g. `name00504m2: invalid schema accepted`. Use +this file to find and classify work (Section 7). + +> **Caution:** `/tmp/xsts-failures.txt` is written only *after* the run +> completes. If the run crashes, the file is **stale**. Always check the exit +> code and the printed bucket line, not just the file. + +The production bar (read `docs/production-readiness.md`): all four buckets to 0 +in both directions; false positives (valid rejected) are the worst stopper; +disclosed under-rejection is acceptable debt, hidden debt is a violation. + +--- + +## 6. The per-change procedure (the "PR-critic-loop"), follow every step + +Do **one** schema-validity rule per change. For each one: + +1. **Measure first.** Look at `/tmp/xsts-failures.txt`. Pick a family of related + failing cases (Section 7). **Read the actual `.xsd` files** of several cases + in that family before writing any code, do not assume what rule they need. + Confirm they all fail for the *same* reason. + +2. **State the rule in one sentence**, citing the XSD 1.0 clause it enforces. If + you are unsure of the spec, read `docs/rules/research-first.md` and consult + the reference sources (study only, never copy). + +3. **Implement at the compile-time consistency layer.** New schema-validity + checks live in `Sources/Schema/` and return `[String]` (a list of error + messages; empty = valid). They are wired into one of the aggregators (see + Section 9). Keep the check **conservative**: only reject when you are certain + the schema is invalid. + +4. **Add unit tests** in a new `Tests/Schema*Tests.swift` file: at least one + schema that must be **rejected** and several valid variants that must + **compile**. Write the test file **before** running the conformance + differential (see the race note in Section 8). + +5. **Run all gates** (Section 4). Build, format, lint, unit tests must be green. + +6. **Run the conformance differential** (Section 4, step 6). Confirm: + `schemaInvalidAccepted` went DOWN, and **no other bucket went up** (this is a + hard gate). If `schemaValidRejected` rose, you introduced a false positive , + find which valid schema you broke (diff `/tmp/xsts-failures.txt`), and fix or + revert. + +7. **Run an adversarial critic** (Section 6a). This is mandatory. + +8. **Update the pinned baseline** constant in `Tests/XSTSSuiteTests.swift` to the + new lower number. Re-run the conformance differential to confirm it now + passes. + +9. **Document.** Add a `CHANGELOG.md` entry (under `## [Unreleased]`, in the + `### Fixed` or `### Added` section) describing the rule, the clause, the XSTS + delta, and any disclosed under-rejection. Update the count in + `docs/schema-validity-burndown.md`. + +10. **Commit and merge** (Section 10). + +### 6a. The adversarial critic (mandatory, do not skip) + +Before merging, spawn a **separate** review pass whose *only job is to find a +valid schema your new check would wrongly reject.* Give it: + +- the exact files you changed, +- the rule and the XSD clause, +- an instruction to **construct concrete valid `` documents** that + the new code would reject, build them, and run them against the compiler to + confirm. + +The critic must specifically probe the **namespace-conflation trap** (Section +8): does the rule confuse a built-in or imported type/element/attribute with a +same-local-name user one? If the critic finds a real false positive, **fix it +before merging** (usually by namespace-gating, Section 8). The XSTS suite does +**not** contain every adversarial case, so the critic catches false positives +the suite misses. This has happened repeatedly; it is the single highest-value +safety step. + +--- + +## 7. How to pick the next task (measure-first) + +```sh +# Group the current failing invalid-schema cases by name family: +grep 'invalid schema accepted' /tmp/xsts-failures.txt \ + | sed -E 's/[0-9].*//; s/_.*//; s/\/.*//' | sed -E 's/[0-9]+$//' \ + | sort | uniq -c | sort -rn | head -20 +``` + +Then for a chosen family, list its cases and **read the schemas**: + +```sh +grep 'invalid schema accepted' /tmp/xsts-failures.txt | grep -iE '^familyName' +# find and read a case's .xsd: +find /private/tmp/xsts/xmlschema2006-11-06 -iname 'caseName.xsd' +``` + +**Estimate yield before investing.** Some families are large but each case is a +different rule (e.g. `addB` one-offs) or needs a subproject (regex-engine +fidelity, cross-document composition). Prefer a family where many cases share +one clean, well-defined rule. A clean rule with low over-rejection risk and a +yield of even 2 is fine; a risky rule (could reject valid schemas) is not worth +it regardless of yield. + +> **Watch out:** the same test-group name can appear in *different* testSets for +> *different* rules (the failures file does not disambiguate). If a case seems +> already handled, it may be a same-named case in another testSet. Verify by +> reading the actual schema and finding its testSet. + +--- + +## 8. Recurring traps and their exact fixes + +These have each bitten this project multiple times. Memorize them. + +### Trap A, the namespace-conflation trap (has occurred 7+ times) + +**Symptom:** a new rule resolves a type/element/attribute name by stripping the +prefix and matching the local name, and then wrongly rejects a valid schema +because a **built-in** (e.g. `xs:string`) or an **imported** component shares +that local name with a user component. + +**Rule:** any check that resolves a schema name must **resolve its namespace**, +not just strip the prefix. Use +`PureXML.Schema.XSDNode.referenceNamespace(reference, bindings)` (where +`bindings = XSDNode.namespaceBindings(of: schema)`) and only act when it resolves +to the namespace you intend (usually the schema's own `targetNamespace`). A +reference resolving to the XSD namespace is a built-in and must be skipped. + +When a rule depends on cross-document resolution and the schema has +`import`/`include`/`redefine`, the safest move is to **stand down entirely** for +that schema (gate on `!hasExternalReference(schema)`), which is a disclosed +under-rejection. Many existing checks do this. + +### Trap B, swiftformat vs swiftlint brace conflict + +**Symptom:** you write a multi-condition `if a, b, c {` (or `for ... where ... {`) +that is long; swiftformat moves the `{` to its own line; swiftlint then reports +an `opening_brace` violation. The two tools disagree and you cannot satisfy both +with that shape. + +**Fix:** extract the condition into a `Bool`-returning helper so the `if` is a +single short expression with the brace on the same line: + +```swift +if isNestedNamedDefinition(node, kind, parentIsTopLevel: parentIsTopLevel) { + errors.append(...) +} +``` + +For `for ... where`, convert a long `where` into a `guard ... else { continue }` +inside the loop body so the `for` line stays short. + +### Trap C, file/function size limits + +swiftlint enforces: **file ≤ 400 lines**, **type body ≤ 250 lines**, **function +cyclomatic complexity ≤ 10**, **function parameters ≤ 5**, **line ≤ 180 chars**. +When you hit one: + +- File too long → extract the new check into its **own new file** + (`Sources/Schema/SchemaYourRule.swift`), `extension PureXML.Schema.XSDParser`. +- Complexity > 10 → split the function into smaller helpers. +- Params > 5 → bundle related arguments into one labeled tuple (≤ 2 members). +- A long `[String]` concatenation can time out the type-checker → break it into + intermediate `let` bindings. + +### Trap D, the test-write-before-differential race + +If you launch the (release) conformance build/run and *then* create a new test +file, the release build may pick up a half-written file and fail with confusing +empty output. **Always write and save the new test file first**, run the fast +`swift test`, and only then launch the release XSTS differential. + +### Trap E, reading schema attributes by local name + +`XSDNode.attribute(node, "name")` matches by **local name only**. For a +schema-vocabulary attribute (`name`, `use`, `form`, `ref`, `default`, `fixed`, +…) read the **unprefixed** one (`attribute.name.prefix == nil`) so a foreign +`pre:name` is not mistaken for the real attribute. Skip foreign-namespace nodes +(`node.name?.namespaceURI == xsdNamespace`) and never descend into +`xs:appinfo` / `xs:documentation` (foreign content). + +--- + +## 9. Where the checks live (file map) + +Schema-validity checks are aggregated in two places, both in +`Sources/Schema/SchemaIDAttribute.swift`: + +- **`consistencyErrors(schema, context, containers)`**, checks that run before + the named types are compiled (structural / id / determinism / cycles / + placement). Structural checks are gathered by `structureErrors(schema)` in + `Sources/Schema/SchemaStructure.swift`. +- **`postNamedTypeErrors(schema, context, containers, derivation, typeMaps)`** , + checks that need the **compiled** types (`typeMaps.named` is the + `[String: ElementType]` map): references, attribute-use uniqueness, value + constraints against user types, substitution-member type derivation, all-group + in extension. + +To add a new check: write a `static func yourRuleErrors(...) -> [String]` in a +new file, then add `+ yourRuleErrors(...)` to the appropriate aggregator. Use +compiled types (`postNamedTypeErrors`) only if your rule needs resolved type +content; otherwise prefer the structural layer. + +Representative existing checks to imitate (read these as templates): +`SchemaAttributeApplicability.swift` (per-node + top-level scans), +`SchemaNestedDefinition.swift` (recursive top-level/nested walk), +`SchemaSimpleTypeFinal.swift` (namespace-gated name map), +`SchemaExtensionAllGroup.swift` (compiled-type lookup), +`SchemaFacetApplicability.swift` (value/facet checks), +`XSDSimpleParser.swift` (`facetDefinitionErrors`). + +--- + +## 10. Commit, branch, and release discipline (follow exactly) + +- **Branch off `main`** for each change: `git checkout -b schema/your-rule-name`. +- Commit message format: `type(scope): summary`, e.g. + `feat(schema): reject X (cos-rule-name)`. Body explains the rule, the clause, + and the XSTS delta. +- **Never use an em dash** anywhere in commit messages, code, comments, or docs. + Use a comma, period, colon, or rewrite the sentence. +- **Never add AI attribution** of any kind, no "Generated with", no + "Co-Authored-By", no mention of Gemini/Antigravity/AI/assistant/"agent" in + commit messages, PR descriptions, branch names, code comments, or any output + that lands in the repo. All work must read as human-authored. (This document + is the one exception, because it was explicitly requested as agent + onboarding.) +- **Never `git push` to a GitLab remote.** Only GitHub remotes are allowed. + Inspect the remote URL before pushing; if it contains `gitlab`, stop. +- Merge to `main` with a fast-forward only: `git checkout main && git merge + --ff-only your-branch`, then `git push origin main`. Delete the branch. +- A pre-push hook runs `swift test` and a commit-message style check; if the + push is rejected for formatting, format the offending file, amend, and push + again. +- **Tagged releases:** bump `CHANGELOG.md` (move `## [Unreleased]` content under + a new `## [x.y.z] - YYYY-MM-DD` header, add a fresh empty `[Unreleased]`), + commit, then `git tag -a vX.Y.Z -m "..."`, `git push origin vX.Y.Z`, and + `gh release create vX.Y.Z --title ... --notes ...`. The project is pre-1.0; + stay on `0.x` until `docs/production-readiness.md`'s gates are met. + +--- + +## 11. Current state (snapshot at v0.1.0) + +- Pinned baselines: **valid-schemas-rejected 1**, **invalid-schemas-accepted + 394**, **valid-instances-rejected 180**, **invalid-instances-accepted 158**. +- The clean structural / applicability rule families are largely **done**: + facet validity, structural content-model, UPA determinism, content order, + identity-constraint XPath subset, wildcard namespace, final/block value space, + simpleType content, circular derivation/references, attribute-use uniqueness, + type-excludes-inline, ID value-constraint, all-group placement, element-ref + namespaces, particle-restriction (MapAndSum etc.), substitution-member type, + default/fixed exclusion, default/use, value-against-user-type, top-level and + ref applicability, whiteSpace restriction direction, simpleType final + list/union, all-group-in-extension, nested-named-definition. +- **What remains (each is a deliberate subproject, not a one-line rule):** + - **Regex-engine fidelity** (`RegexTest` family, ~22): make the pattern + compiler correctly reject genuinely-invalid patterns *without* rejecting + valid ones. **High over-rejection risk**, be very careful. + - **Particle-restriction tail** (`particlesIk/L/Z/Hb/Ig`, ~40): needs the + particle model enriched with more type information (NameAndTypeOK clauses + nillable/fixed/block; Particle-Valid-Extension). + - **Cross-document composition** (`attg`/`schZ`/`schN`, ~50): load and merge + `import`/`include`/`redefine` so the existing rules run over the merged + schema. This unlocks the largest remaining cluster. + - **`addB`** (~19): adhoc one-offs, each its own investigation. + - **Located diagnostics** (`#169`): line/column positions on every error (the + IDE gate; a broad but mechanical refactor of the `[String]` error paths to + carry `SourceRange`). + +Pick from these only as deliberate, well-scoped efforts. Do not start one you +cannot finish cleanly. If you cannot find a clean, low-risk rule, say so rather +than forcing a risky change. + +--- + +## 12. A worked example (one complete iteration) + +The most recent change (`nested-named-definition`) is a model iteration: + +1. Measured `/tmp/xsts-failures.txt`; saw `stA*` and `attgB*` families. +2. Read `stA008.xsd` (a `` nested inside a + ``) and `attgB002.xsd` (a `` nested + inside another). Rule: a nested `simpleType`/`attributeGroup` may not be named + (schema-for-schemas `localSimpleType` / nested `attributeGroup` is a ref). +3. Wrote `Sources/Schema/SchemaNestedDefinition.swift`: a recursive walk passing + `parentIsTopLevel`, flagging a non-top-level XSD-namespace + `simpleType`/`attributeGroup` with an unprefixed `name`; skipping + appinfo/documentation. Wired into `structureErrors`. +4. Wrote `Tests/SchemaNestedDefinitionTests.swift` (reject nested-named; accept + nested-anonymous, attributeGroup ref, top-level named). +5. Ran gates → green. Hit Trap B (brace conflict) → extracted a `Bool` helper. +6. Ran the differential → `invalid-schemas-accepted` 403 → 394, all other + buckets held. Updated the pinned baseline to 394. +7. Ran the critic (17 constructed valid schemas) → zero false positives. +8. CHANGELOG entry, burndown count, commit on a branch, `--ff-only` merge, push. + +Total: one clean, verified, reversible improvement, with the false-positive line +held at zero. + +--- + +## 13. Definition of done (self-audit before you say "done") + +- [ ] `swift build` succeeds. +- [ ] `swiftformat --lint` and `swiftlint --strict` report zero violations. +- [ ] `bash scripts/check-style.sh`, `check-namespacing.sh`, `check-wasm.sh` pass. +- [ ] `swift test` is fully green (all unit tests). +- [ ] The XSTS differential shows `invalid-schemas-accepted` down and **no other + bucket up** (`valid-schemas-rejected` still 1). +- [ ] An adversarial critic found **zero** valid schemas your change rejects. +- [ ] New unit tests cover both a rejected case and several accepted cases. +- [ ] The pinned baseline in `Tests/XSTSSuiteTests.swift` matches the new count. +- [ ] `CHANGELOG.md` and `docs/schema-validity-burndown.md` updated. +- [ ] Commit message has the right format, no em dash, no AI attribution. +- [ ] Merged `--ff-only` to `main`; pushed to the GitHub remote (never GitLab). + +If any box is unchecked, you are not done. Ask: "did I take the optimal path, or +one I hope no one inspects?" If you wrongly rejected even one valid schema, the +change is wrong no matter how many invalid ones it caught.