[{"data":1,"prerenderedAt":4},["ShallowReactive",2],{"xEzwOeHgiv":3},"\u003Cp align=\"center\">\n  \u003Cimg src=\"docs/assets/lexpat.png\" alt=\"lexpat\" width=\"720\">\n\u003C/p>\n\n# lexpat\n\nA Lean 4 port of libexpat's XML 1.0 parsing semantics, aimed at untrusted\ninput. Compatibility is at the specification level, not byte-for-byte or\nC-internal: the parser reproduces XML 1.0 (Fifth Edition) document semantics\nand Expat's event and error categories, while normalizing details such as error\ntext, byte offsets and callback chunk boundaries.\n\nThe library is a pure, total, resumable parser core with a one-shot wrapper, a\ndemonstration executable with a versioned trace and chunked feeding modes, a\nchecked-in corpus compared against a local libexpat build, direct streaming\nassertions, and machine-checked properties of the UTF-8 decoder. The repository\nalso builds `include/expat.h` and `liblexpat.so.1` for a bounded `XML_*` call\nsurface; that artifact's C-boundary contract, differential, ABI and memory\nchecks are part of the check list below. The Lean library and the trace\nsemantics are unchanged by it.\n\nFor x86_64-linux with the pinned Lean runtime, `include/expat.h` and\n`liblexpat.so.1` provide the XML_* subset listed below as the verified C\nintegration route; unsupported calls fail closed as documented. This is not a\ndrop-in replacement for libexpat outside that subset. The tested compile recipe\nis `tests/c-abi-client.c`, built and run against both this library and the dev\nshell's libexpat by:\n\n```sh\nnix develop -c python3 tests/c-abi-differential.py\n```\n\n## Build and check\n\nThe toolchain is pinned (`lean-toolchain`, `flake.lock`); every command runs in\nthe Nix dev shell.\n\n```sh\nnix develop -c lake build\nnix develop -c lake build lexpatTrace\nnix develop -c lake env lean --run tests/OneShotPrecedence.lean\nnix develop -c lake env lean --run tests/StreamingCases.lean\nnix develop -c lake exe lexpatTrace tests/corpus/basic.xml\nnix develop -c lake exe lexpatTrace --chunk-size 1 tests/corpus/basic.xml\nnix develop -c bash tests/clean-build.sh\nnix develop -c bash tests/c-abi-contract.sh A1\nnix develop -c bash tests/c-abi-contract.sh A2\nnix develop -c python3 tests/c-abi-differential.py\nnix develop -c python3 tests/c-abi-verify.py\nnix develop -c bash tests/c-abi-memory.sh\nnix develop -c python3 tests/streaming.py\nnix develop -c python3 tests/differential.py\nnix develop -c python3 tests/differential.py --group limits\nnix develop -c bash tests/no-admits.sh\nnix develop -c lake env lean LexPat/Encoding/Proofs.lean\n```\n\n`lake build` builds the library, its proofs and the C ABI artifact; the\nexecutable is built on demand. A bare build needs nothing outside the Lean\ntoolchain, and the C artifact's runtime dependency on the pinned Lean runtime is\nrecorded as an `libleanshared.so` `NEEDED` entry. The differential needs a C\ncompiler and libexpat from the dev shell; the memory check needs Valgrind from\nthe same shell.\n\nThe C ABI contract, differential, artifact verification and memory checks cover\nonly the bounded `XML_*` surface below. `tests/c-abi-verify.py` also checks that\nthe header, the exported symbol set, the public layouts and the loader fields\nmatch what is documented here; `docs/compatibility.md` lists every unsupported\nfamily and the error mapping.\n\n## C ABI call surface\n\nSupported calls, all on a parser returned by `XML_ParserCreate(NULL)`:\n\n* lifecycle and data: `XML_ParserFree`, `XML_SetUserData`, the pinned\n  `XML_GetUserData` macro;\n* handlers: `XML_SetElementHandler`, `XML_SetStartElementHandler`,\n  `XML_SetEndElementHandler`, `XML_SetCharacterDataHandler`,\n  `XML_SetProcessingInstructionHandler`, `XML_SetCommentHandler`,\n  `XML_SetCdataSectionHandler`, `XML_SetStartCdataSectionHandler`,\n  `XML_SetEndCdataSectionHandler`, `XML_SetXmlDeclHandler`;\n* parse and buffer: `XML_Parse`, `XML_GetBuffer`, `XML_ParseBuffer`, with\n  `XML_STATUS_OK` or `XML_STATUS_ERROR` only;\n* error and location: `XML_GetErrorCode`, `XML_ErrorString`,\n  `XML_GetCurrentByteIndex`, `XML_GetCurrentLineNumber`,\n  `XML_GetCurrentColumnNumber`;\n* identity and status: `XML_ExpatVersion`, `XML_ExpatVersionInfo`,\n  `XML_GetFeatureList`, `XML_SetParamEntityParsing` with\n  `XML_PARAM_ENTITY_PARSING_NEVER` only, `XML_GetParsingStatus`,\n  `XML_GetSpecifiedAttributeCount`, `XML_GetIdAttributeIndex`.\n\nEverything else declared in `include/expat.h` refuses deterministically:\ncreators return `NULL`, setters return failure, enabling an unsupported handler\npoisons the parser with `XML_ERROR_UNSUPPORTED_FEATURE`, and no resolver, file\nor network path exists. Namespace processing, DTDs, external entities, unknown\nencodings and suspend/resume are not implemented. The version string and\nfeature list describe this library (`lexpat_0.1.0-cabi1`), not Expat.\n\nThe caller must pass a live handle from this library and accessible memory for\nevery non-NULL pointer and declared length. `XML_GetUserData` dereferences the\nhandle directly and is undefined after `XML_ParserFree`; an old pointer whose\naddress was reused for a different parser cannot be distinguished from that new\nparser. These limits and the exact error, buffer and location semantics are in\n`docs/compatibility.md`.\n\n## Library\n\n```lean\nLexPat.Stream.new    : LexPat.Limits → LexPat.Stream\nLexPat.Stream.feed   : LexPat.Stream → ByteArray → LexPat.Stream × Array LexPat.Event\nLexPat.Stream.finish : LexPat.Stream → LexPat.Stream × Array LexPat.Event\nLexPat.Stream.result : LexPat.Stream → Option (Except LexPat.ParseError Unit)\n\nLexPat.parseDocument : ByteArray → LexPat.Limits → Except LexPat.ParseError (Array LexPat.Event)\n```\n\n`Stream` is the resumable core. `feed` accepts any byte chunk, emits the events\nthat chunk determines, and returns the continuation state; `finish` resolves\nthe document. State holds the fixed budgets, cumulative byte and reference\ncounts, the encoding decision or its bounded undecided prefix, at most three\npending decoder bytes, the grammar continuation, the unfinished lexical value\nand the element stack. Returned events are the caller's; the state keeps no\noutput history and no earlier chunk.\n\nEncoding is selected once from byte evidence and, for an ASCII-compatible\ndocument, from the bounded declaration prefix. Incomplete BOM, UTF-16\nsignature, multibyte scalar and markup prefixes pause rather than guess. A\npermanent error is returned only when the bytes already consumed prove it for\nevery continuation. A fault completed earlier in the input wins over a later\ndecoding or input-budget fault; at `finish`, a pending decoder fault is resolved\nbefore EOF-only grammar faults. Events returned before a later rejection are a\nprovisional prefix, not a transactional guarantee.\n\n`parseDocument` is the one-shot wrapper: it feeds the whole byte array to a\nfresh stream, finishes it, and returns the ordered events only on success. A\nrejected document returns a `ParseError` carrying the public category, the\nconcrete rule and a diagnostic byte offset, with no event array. Because the\nwrapper shares the stream's rule, it reports the earliest certain fault in\ninput order. For an input with more than one independent fault this can differ\nfrom a decode-everything-first order; every corpus outcome is unchanged.\n\nThe Lean library API, `Stream` and `parseDocument` included, is not a supported\nintegration route. It is documented so its behavior can be inspected and so a\nlater design has something concrete to build on; no compatibility or stability\ncommitment attaches to it.\n\nEncoding validation and XML character legality are deliberately separate:\nUTF-8, UTF-16, ISO-8859-1 and US-ASCII decide which *scalars* exist, while\n`Char`, `NameStartChar` and `NameChar` decide which scalars an XML document may\nuse.\n\n## Demonstration executable\n\n`lexpatTrace` is a bounded demonstration and differential fixture. It is **not**\na production integration route and it is not a C API; the C ABI artifact\ndescribed above is the separate, bounded C route and this executable does not\ncall it. Its documented surface is its input syntax and its stable trace shape.\n\n```sh\nnix develop -c lake exe lexpatTrace tests/corpus/basic.xml\nnix develop -c lake exe lexpatTrace --chunk-size 1 tests/corpus/basic.xml\nnix develop -c lake exe lexpatTrace --chunk-size whole tests/corpus/basic.xml\nnix develop -c lake exe lexpatTrace --chunk-size 64 --chunk-seed 1 tests/corpus/basic.xml\nnix develop -c lake exe lexpatTrace --help\n```\n\nIt reads one binary XML file and writes JSON Lines to standard output. The\ndefault and `whole` modes read at most `maxInputBytes + 1` bytes; the numeric\nand seeded modes assemble 64 KiB IO blocks and stop feeding once the stream is\nterminal, so a block may extend past the byte that failed. Every object carries\n`\"format\":1`; the last object is a result.\n\nThe default invocation is the one-shot trace. `--chunk-size N` feeds the file\nin `N`-byte chunks, where `N` is a decimal integer in `[1,65536]`;\n`--chunk-size whole` feeds it in one chunk; and\n`--chunk-size N --chunk-seed SEED` selects variable per-feed lengths in `[1,N]`\nfor a decimal `SEED` in `[0,4294967295]`. The seeded rule is\n`s := (1664525*s + 1013904223) mod 4294967296` once per feed, then length\n`1 + s mod N`; a final shorter chunk may carry the tail. Invalid numbers,\n`--chunk-seed` without a numeric `--chunk-size`, and `--chunk-size whole` with\na seed are usage errors; no flag is silently ignored. The streaming modes print\neach event as it is returned and then one result. A rejected stream may already\nhave printed prefix events; the one-shot mode cannot.\n\n```json\n{\"format\":1,\"type\":\"xmlDecl\",\"version\":\"1.0\",\"encoding\":\"UTF-8\",\"standalone\":null}\n{\"format\":1,\"type\":\"comment\",\"data\":\" shipped example \"}\n{\"format\":1,\"type\":\"start\",\"name\":\"doc\",\"attributes\":{\"a\":\"first\",\"z\":\"last\"}}\n{\"format\":1,\"type\":\"end\",\"name\":\"doc\"}\n{\"format\":1,\"type\":\"result\",\"ok\":true}\n```\n\nEvent objects carry `type` and the applicable `name`, `attributes`, `data`,\n`target`, `version`, `encoding` and `standalone` fields. Attributes are rendered\nsorted by raw name; event order is document order, and a start event carries its\nattributes in document order. Accepted documents end with\n`{\"format\":1,\"type\":\"result\",\"ok\":true}`. Rejected documents end with\n`{\"format\":1,\"type\":\"result\",\"ok\":false,\"code\":\"TAG_MISMATCH\",\"reason\":\"tagMismatch\"}`.\n\nExit status is 0 for an accepted document, 1 for a rejected one (after its result\nobject), and 2 for a usage or I/O error, which is reported on standard error and\nnever presented as an XML result. Standard output carries no log chatter.\n\n`tests/corpus/basic.xml` and its expected trace `tests/corpus/basic.jsonl` are\nthe shipped example; `tests/corpus/malformed.xml` and `malformed.jsonl` show the\nrejection shape.\n\nStable means the field names, values and ordering above do not change without a\nformat-version bump. It does not make the executable an API.\n\n## Limits\n\nDefaults, per document:\n\n| budget | value | measured in |\n| --- | --- | --- |\n| raw input | 8 MiB (8388608 bytes) | bytes read |\n| one name, attribute value, text, comment, CDATA, PI accumulation or numeric-reference digit run | 1 MiB (1048576) | decoded characters |\n| open elements | 256 | element stack depth |\n| cumulative reference expansion | 1 MiB (1048576) | decoded characters contributed by predefined and numeric references |\n\nEach check happens before the corresponding append, and each failure is a local\ncode (`LIMIT_INPUT`, `LIMIT_TOKEN`, `LIMIT_DEPTH`, `LIMIT_ENTITY_EXPANSION`) so a\nrejection says which budget was crossed. `tests/corpus/cases.json` contains an\nexact-boundary and a boundary-plus-one row for each budget, constructed so the\nother three remain under their ceilings. The counters persist across `feed`\ncalls: a chunk boundary never resets a budget, and the input budget fails at the\nfirst excess byte. An undecided declaration prefix is bounded in storage by\n`min(maxInputBytes, maxTokenChars)` plus the fixed `\u003C?xml` discriminator; that\nstorage ceiling is not itself a public error, and the prefix is handed to the\ngrammar so its token budget is reported exactly as before. The prefix is also\nchecked as it arrives: a declaration token the grammar already rejects is\nreported as soon as its last byte is consumed, so a later input-budget failure\ncannot mask it. A non-ASCII byte inside an ASCII-constrained declaration\npseudo-attribute name or value is likewise a certain declaration fault at that\nbyte, decided before the encoding choice because no continuation or label can\nrestore an ASCII value; the same byte outside such a field still waits for its\nscalar. An incomplete value is never guessed.\n\nOnly attacker-controlled work within these budgets is bounded. A Lean runtime\nallocation failure is not claimed to be impossible; total parsing, the explicit\nstack and the absence of an entity resolver are what remove the usual\nunbounded-recursion and entity-amplification failure modes.\n\n## Security testing\n\n`docs/security.md` is the threat model: supported route, default budgets, the\nCVE-class disposition table, allocation-failure behavior, pointer and handle\nlifetime, and the limits of fuzzing and sanitizer evidence. The only verified\nintegration route is the bounded C ABI, and it is not a drop-in replacement for\nlibexpat outside that subset. `docs/security-hardening.md` is the work record:\nthe defects found and fixed, the evidence added, and the libexpat features that\nremain absent.\n\nA C-shim allocation failure during a callback now makes the same `XML_Parse`\ncall return `XML_STATUS_ERROR` with `XML_GetErrorCode` equal to\n`XML_ERROR_NO_MEMORY` (1); malformed internal callback metadata keeps the\nretained `XML_ERROR_MALFORMED_WIRE` (51) code and is refused before any handler\nruns. The public C scenario is `tests/c-abi-oom-contract.sh A3`, the\ndirect-linked direct-receiver and allocation checks are\n`tests/c-abi-verify.py --bridge`, and deterministic sanitizer-injected failures\nare `tests/security-oom-sanitized.sh`.\n\nThe security set uses the flake-locked Clang/LLVM 21.1.8, Lean 4.35.0 and the\npinned libexpat 2.8.5 oracle. `OUT` below is an absolute directory outside the\ncheckout, created first:\n\n```sh\nOUT=\"$(mktemp -d \"${TMPDIR:-/tmp}/lexpat-security.XXXXXX\")\"\nnix develop -c python3 tests/security-cve.py\nnix develop -c bash tests/security-differential-fixed.sh --out \"$OUT/diff\"\nnix develop -c bash tests/security-oom-sanitized.sh --out \"$OUT/oom\"\nnix develop -c bash tests/security-benchmark.sh --suite ci --out \"$OUT/bench\"\nbash tests/security-scaling-ci.sh --out \"$OUT/scaling\"\nnix develop -c bash tests/security-fuzz-smoke.sh --out \"$OUT/fuzz\"\npython3 scripts/run-bounded-security.py --seconds 300 --grace 5 \\\n  --log \"$OUT/bounded.log\" \\\n  -- nix develop -c bash tests/security-ci.sh --out \"$OUT/ci\"\n```\n\nThe `run-bounded-security.py` command runs `tests/security-ci.sh` under one\n300-second monotonic deadline enforced outside `nix develop`, so it covers Nix\nstartup and every child the step starts. A timeout exits 124 after the whole\nprocess group is terminated and reaped, a child that leaves a descendant behind\nexits 125, a failed sink or retained log exits 126, and the combined output is\nkept in `$OUT/bounded.log`. That step runs the CVE checks, the fixed\ndifferential replay, injected shim OOM and the fuzz smoke; the benchmark is not\npart of it.\n\n`tests/security-scaling-ci.sh` is the separate scaling step CI runs right after\nit. `--out` must not exist yet; each attempt gets its own directory, log, CSV,\nreport and drift signal. The comparator writes\n`drift-inconclusive.signal` with exactly `DRIFT_INCONCLUSIVE_V1\\n` and exits 3\nonly when the drift control moved more than 15%, and the helper retries once\nafter 30 seconds only for that status plus that marker. A build, driver, Nix or\ntimeout failure, a missing or mismatched marker, an incomplete report and an\nexisting output root all stop at the first attempt; two inconclusive attempts\nexit 3, which fails the step instead of calling an unmeasured session green.\n\n`tests/security-fuzz-smoke.sh` runs the bounded parser, C ABI, shipped-artifact\nand in-process differential libFuzzer targets. The artifact target instruments\nonly the harness, so its library coverage is explicitly absent; the parser and\nC ABI targets instrument the generated Lean C and the shim. The differential\ntarget links neither parser: it loads an instrumented candidate library and the\npinned, uninstrumented oracle with `dlopen(..., RTLD_NOW | RTLD_LOCAL)` and\ncalls each through its own `dlsym` table, requiring\n`pkg-config --modversion expat`, `XML_ExpatVersion()` and\n`XML_ExpatVersionInfo()` to be exactly `2.8.5` before comparing. The paired\nbenchmark interleaves base and candidate readings and one-shot/chunked trials,\nand measures each case in a forked child so its recorded RSS belongs to that\ncase; the CI sentinel above is a different reading and neither one establishes\nthe other's bound. Long campaigns run through `tests/security-fuzz-campaign.sh`\nwith an external output directory. Fuzzing is stochastic and bounded: a\ncompleted campaign is not a proof that the parser is free of bugs.\n\n## Supported XML subset\n\n* XML declaration at the start of the document: `version=\"1.0\"`, optional\n  `encoding` and optional `standalone=\"yes\"|\"no\"`, in that order.\n* UTF-8 with or without a BOM; UTF-16LE/BE with a BOM or a zero-byte initial\n  signature; ISO-8859-1 and US-ASCII selected by the declaration.\n* Elements including empty-element tags, attributes with XML CDATA attribute\n  value normalization, character data, CDATA sections, comments, processing\n  instructions, and the predefined entities `amp`, `lt`, `gt`, `apos`, `quot`\n  plus decimal and hexadecimal character references.\n* XML 1.0 `Char`, `NameStartChar` and `NameChar`, including supplementary-plane\n  names through `U+EFFFF`.\n* Document line-ending normalization and the CDATA attribute-value rules:\n  literal `CRLF`/`CR`/`LF`/`TAB` become spaces in attribute values, references to\n  those code points stay themselves, and literal document line endings become\n  `LF`.\n* Resumable parsing through `LexPat.Stream` and the `lexpatTrace` chunked\n  modes: the same grammar, events, budgets and errors under any chunking. For an\n  accepted document the ordered events equal the one-shot result; for a rejected\n  one the final `(code, reason)` is the same and any returned events are a\n  prefix of the input that was accepted. `finish` alone diagnoses an unfinished\n  document.\n\n## Not supported\n\nThese are rejected, not simulated or silently skipped:\n\n* every C call outside the bounded `XML_*` subset in `include/expat.h`:\n  namespace processing, DTDs, external entities, unknown encodings,\n  suspend/resume and all symbols the header does not declare fail closed, and\n  byte-identical output, exact error text or offsets and callback chunk\n  boundaries are not claimed;\n* every `DOCTYPE`, internal or external DTD subsets, parameter entities,\n  non-predefined general entities, external entity resolution, catalogs, and\n  system identifiers (no resolver exists and no identifier is read);\n* namespace URI processing and namespace-aware name splitting (raw qualified\n  names are reported instead);\n* XML 1.1, unknown encodings, and encodings other than the four above;\n* any production integration or stability promise for `Stream`,\n  `parseDocument` or `lexpatTrace`, which remain inspection and demonstration\n  surfaces; the trace's chunked modes are not an emulation of the C\n  `XML_Parse`/`XML_ParseBuffer` entry points. The bounded C subset is the\n  separately verified route described above.\n\n## Proved decoder properties\n\n`LexPat/Encoding/Proofs.lean` proves, about the real `decodeUtf8One`:\n\n* `decodeUtf8One_rejects_overlong_two_byte`: the leads `0xC0` and `0xC1` are\n  rejected with reason `overlong` for *every* possible tail.\n* `decodeUtf8One_ascii_roundtrip`: an ASCII scalar encoded by `encodeAscii`\n  decodes back to that scalar.\n\n`#print axioms` for both reports only `propext` and `Quot.sound`; `tests/no-admits.sh`\nrejects `sorry`, `partial` and `unsafe` in every Lean source. A build proves\nneither runtime behavior nor parser correctness: the corpus, the differential\nand the budget checks are the behavioral evidence, and they are not a proof of\nend-to-end correspondence.\n\n## Differential\n\n`tests/differential.py` compiles `tests/reference.c` against the dev shell's\nlibexpat and runs it and `lexpatTrace` over `tests/corpus/cases.json` on\nidentical bytes. Accepted documents are compared field by field: the events\n`lexpatTrace` emits, normalized by coalescing adjacent text and canonicalizing\nattribute sets by raw name, must equal the fixture's expected events, and the\nlibexpat oracle's events are checked against its own declared expectation\nindependently. Rejected documents are compared by accept/reject and semantic\nerror code, with the exact internal reason asserted for Lean. Fixture\nexpectations are asserted on both sides. Every deliberate difference is declared\nin the manifest with its reason, and an exception that stops differing is a\nfailure. Three negative controls run through the Lean comparison: an unmutated\ntrace must match, a mutated event field must be detected, and a flipped accept\ndecision must be detected in both directions. See `docs/compatibility.md` for\nthe mapping and the enumerated differences, and `AGENTS.md` for the test policy.\n\n`tests/c-abi-contract.sh` runs the C-boundary scenarios in\n`tests/c-abi-contract.md` against the shipped header and shared object.\n`tests/c-abi-differential.py` compiles `tests/c-abi-client.c` twice — once\nagainst `include/expat.h` and `liblexpat.so.1`, once against the dev shell's\nlibexpat — and runs both over `tests/corpus/cases.json` under whole, single-byte,\nfixed and seeded partitions and through the buffer entry points. It compares\nevent order, payloads, attribute order and common numeric error codes, asserts\neach side against its own fixture expectation, and declares every remaining\ndifference. `tests/c-abi-verify.py` checks source compatibility, the exact\nexported symbol set, SONAME/NEEDED/RUNPATH and a real loader launch, public layout and\ncallback-signature equality with the pinned header, truthful version and feature\nidentity, lifecycle, buffer, error, location,\nmalformed-bridge-payload, fail-closed and NULL/invalid-length behavior.\n`tests/c-abi-memory.sh` runs an ASan/UBSan-instrumented shim and client and a\nValgrind release client over those paths, and prints the detector limits.\n\n`tests/performance-benchmark.sh` is the throughput check: it parses the\nchecked-in eight-fixture corpus under `tests/performance/` through the candidate\nC ABI, a supplied base source tree and the pinned libexpat, each library loaded\nin its own process, and reports per-document base/candidate speedup,\ncandidate/libexpat gap and literal-text throughput. Every row states its unit\nand construction, and a run is conditioned on its recorded artifact and oracle\nhashes, CPU affinity and drift controls rather than published as a\nhost-independent constant. Hosted CI runs only its threshold-free smoke phase.\n\n`tests/streaming.py` is the separate streaming check. It runs\n`tests/StreamingCases.lean` for direct API assertions, then every corpus case\nunder fixed chunk sizes 1, 2, 3, 5, 7, 13 and 64, whole-input streaming and one\nseeded partition. Rejected documents must produce the same `(code, reason)` and\nthe same emitted prefix as whole-input streaming. The trace reader requires\nexactly one result object, last. Negative command-line combinations must exit 2,\nand controls show that a changed event, a changed result or an event moved after\nthe result is detected.\n\nFor accepted documents the harness builds an expected `Array Event` from the\nmanifest's explicit event recipes and the checked-in document order in\n`tests/corpus/raw-attribute-order.json`; a row that ships only a trace uses a\nrecipe with byte-identical input. `tests/StreamingEvents.lean`, built as\n`lexpatEvents`, compares all nine partitions and the one-shot result\npositionally against that array, so the expectation is independent of the parser\nand of the sorted trace rendering. An unmodified expected array and a reversed\nmulti-attribute array exercise the same typed comparator.\n\nThe executable printed at the top of a run is the libexpat build in the dev\nshell (recorded as `expat_2.8.5` for the current lock). Upstream libexpat\n`R_2_9_0` is the semantic reference for the subset and is not vendored or\nfetched by any check.\n\n## License\n\nCopyright 2026 Scidonia Limited.\n\nLicensed under the Apache License, Version 2.0. See [LICENSE](LICENSE) and\n[NOTICE](NOTICE).\n",1794686187903]