# Issues Tracking — xml-xsd-engine

All P0–P3 performance, correctness, and architectural issues identified and resolved.
Last updated: **2026-04-24** (v1.7.0 + post-release NP0/NP1/NPERF/NP2/NP3 batch fixed)

---

## v1.7.0 Issues

### P0 — Critical (✅ Done)

| #     | Issue | Files                             | Fix |
|-------|-------|-----------------------------------|-----|
| P0-01 | ValidationEngine traversal explosion | `ValidationEngine.ts`             | `_mergeCache` + `_substCache` memoization; single traversal pass |
| P0-02 | XPath repeated evaluation cost | `XPathEngine.ts`                  | Two-tier LRU (cold 512 + hot 128); `configureXPathCache()` |
| P0-03 | SchemaCache unbounded growth | `SchemaCache.ts`                  | LRU size cap + TTL + `maxVersionsPerKey` + content-hash keying |
| P0-04 | XmlDiff performance | `XmlDiff.ts`                      | WeakMap fingerprint cache; reference equality fast-path |
| P0-05 | Schema inference complexity spikes | `SchemaInference.ts`              | Depth/breadth budget via ParseBudget; early abort on over-budget |
| P0-06 | PSVI memory pressure | `Psvi.ts`                         | 256-entry annotation pool; WeakMap GC-friendly storage |
| P0-07 | Canonical XML performance | `XmlCanonical.ts`                 | Delta namespace save/restore O(decls); sorted attr comparator hoisted |
| P0-08 | xs:redefine repeated merge overhead | `XsdParser.ts`, `SchemaMerger.ts` | SHA-256 content-hash cache in SchemaMerger |
| P0-09 | XPath cache thrashing | `XPathEngine.ts`                  | Context-aware two-tier LRU; configurable via `configureXPathCache()` |
| P0-10 | Identity constraints full-tree scans | `IdentityConstraintEngine.ts`     | Pre-built element-name `Set`; O(1) skip for constraint-free elements |
| P0-11 | `ValidationResult._errorCount` never incremented — `result.valid` always `true` | ValidationResult.ts               | Fixed in `addError()` and `addWarning()`; `merge()` propagates counters |
| P0-12 | `XmlCanonical` inclusive C14N rendered only new bindings (violates C14N spec 2.4) | XmlCanonical.ts                   | Fixed delta-based save/restore |
| P0-13 | `XmlDiff` false-positive "no change" on different subtrees | XmlDiff.ts                        | Recursive fingerprint now encodes child subtrees |
| P0-14 | `AssertionEvaluator` non-deterministic error ordering | AssertionEvaluator.ts             | Sorted by test string before evaluation |
| P0-15 | `ValidationPipeline` not forwarding `psvi` option | ValidationPipeline.ts             | Fixed — psvi forwarded to ValidationEngine |
| P0-16 | `SchemaCache._setWithMeta` premature eviction | SchemaCache.ts                    | Removed pre-eviction loop; LRU only on maxSize reached |
| P0-17 | `XsdParser._parseSubSource` weak cache key | XsdParser.ts                      | SHA-256 hash key (was first-64-chars heuristic) |

---

### P1 — High Priority (✅ Done)

| # | Issue | Files | Fix |
|---|-------|-------|-----|
| P1-01 | Pipeline duplicate traversal | `ValidationPipeline.ts` | Merged schema-compile and validation walks into single pass |
| P1-02 | xs:assert unpredictability | `AssertionEvaluator.ts` | Deterministic sort by test string; isolated XPath context snapshots |
| P1-03 | Namespace resolution overhead | `NamespaceEngine.ts` | Interned URI strings; flat-cache for empty scopes; O(decls) push |
| P1-04 | SAX instrumentation overhead | `SaxInstrumentation.ts` | Hooks gated behind `enabled` flag; zero-cost when not used |
| P1-05 | Error object allocation overhead | `ValidationResult.ts` | Pooled errors for bulk paths; flat result arrays |
| P1-06 | PSVI `notKnown` over-emission | `Psvi.ts` | Only emitted for genuinely unresolvable types |
| P1-07 | Canonical XML edge-case correctness | `XmlCanonical.ts` | Fixed per C14N spec 2.4; inclusive/exclusive modes |
| P1-08 | Mixed content edge inconsistencies | `ValidationEngine.ts` | xs:anyType rules for `mixed="true"`; serializer indent-safe |
| P1-09 | xs:redefine override edge cases | `SchemaMerger.ts` | Override after base merge; multi-level chains tracked |
| P1-10 | Subtree canonicalization inconsistencies | `XmlCanonical.ts` | WeakMap memoization; consistent ancestor namespace inclusion |

---

### P2 — Architecture (✅ Done)

| # | Issue | Files | Fix |
|---|-------|-------|-----|
| P2-01 | Layered architecture cleanup | all | `@internal` tagging; barrel exports audited; strict layer boundaries |
| P2-02 | XPath cache tuning | `XPathEngine.ts` | `configureXPathCache()` exposed; configurable coldMax/hotMax |
| P2-03 | Codegen memory optimization | `codegen/` | `generateJsonSchemaStream` / `generateTypeScriptStream` added |
| P2-04 | CLI watch robustness | `cli/cli.ts` | 200ms debounce; concurrent-run guard; error-recovery restart |
| P2-05 | PSVI integration cleanup | `Psvi.ts`, `ValidationEngine.ts` | PSVI population as standalone post-validation pass |
| P2-06 | Schema merge pipeline unification | `SchemaMerger.ts` | `SchemaMerger` class centralizes import/include/redefine |
| P2-07 | Validation stage separation | `ValidationPipeline.ts` | Pipeline stages formally separated with per-stage result |
| P2-08 | Canonicalization cache granularity | `XmlCanonical.ts` | `clearCanonicalCache()` exposed; WeakMap per (element, mode, withComments) |

---

### P3 — Strategic

| # | Issue | Status                                                      |
|---|-------|-------------------------------------------------------------|
| P3-01 | Modularization of ecosystem | Deferred — architecture prepared, `@internal` markers added |
| P3-02 | Spec conformance tracking | Partial — XSD 1.0 ~72%; matrix in COMPATIBILITY.md          |
| P3-03 | Full DFA-based validation | ✅ **Implemented in v1.7**                                   |
| P3-04 | Streaming-first architecture | ✅ **Partially implemented in v1.7**                         |
| P3-05 | XPath execution model | Deferred to v1.9 (roadmap)                                  |

---

## v1.7 — Post-Release Fixes & New Issues

> All issues below were identified after v1.7.0 tag and resolved on branch `v1_7_0`.

### Regression Fixes (R-series) (✅ Done)

| # | Issue                                                                                                     | Files | Fix |
|---|-----------------------------------------------------------------------------------------------------------|-------|-----|
| R-01 | `PsviMap` typed as `WeakMap` — `.size` property unavailable; v1.7 regression tests failed to compile      | `Psvi.ts`, `ValidationEngine.ts`, `coverage-boost-v170.test.ts` | Changed `PsviMap` to `Map`-based type with `.size`; updated all `new WeakMap()` to `new Map()` |
| R-02 | `SchemaCache` missing `get()` and `has()` public methods — tests calling `cache.get(key)` produced TS2339 | `SchemaCache.ts` | Added `get(key): SchemaModel \| undefined` and `has(key): boolean` delegating to internal `_get()` |
| R-03 | `PsviMap.get()` rejected `null` — `doc.root` is `XmlElement \| null`; `psvi.get(root)` failed to compile  | `Psvi.ts` | Extended `PsviMap` type to include overloaded `get(key: XmlElement \| null)` signature |

---

### v1.7 — Security & Correctness Fixes (Post-Release) (✅ Done)

> Fixed on branch `v1_7_0` as part of the post-release stability pass. Version remains **1.7.0**.

| # | Issue | Ref | Files | Fix |
|---|-------|-----|-------|-----|
| T-01 | `StreamingKeyrefTracker._constraintsByElement` index uses simplified selector parsing — only bare element names extracted; complex XPath selectors (`.//a/b`, `child::item`, `item\|other`, predicates) not evaluated correctly | `StreamingValidator.ts` | T-01 | Replaced single-regex selector extractor with `_extractSelectorNames()`: splits on `\|`, strips `.//', `./', `//`, takes last path segment, strips `child::` axis and `[...]` predicates; union selectors register constraints under all branch names |
| T-02 | `xsi:type` streaming override only checks `compiled.complexTypes`; does not fall back to simple types or resolve qualified names with prefix-to-namespace lookup | `StreamingValidator.ts` | T-02 | `startElement` handler collects `nsDeclarations` from SAX event into `nsMap`; `_resolveXsiType()` resolves prefix→URI from `nsMap`, tries `{ns}localName` then `localName` candidates; if only a simple type is found, silently skips complex override (no spurious warning); unknown warning only when neither complex nor simple type exists |
| T-03 | `xs:all` compositor does not enforce `maxOccurs=1` per-element constraint in streaming mode — DFA built with unbounded maxOccurs for xs:all children | `DfaEngine.ts` | T-03 | `_buildAllDfa` now maps particles through `enforcedNorm` which clamps `maxOccurs` to `Math.min(maxOccurs, 1)` for all particles; unbounded (`-1`) is also clamped to 1 per XSD 1.0 spec; `_runAll` already checks per-particle counts so duplicate elements are now correctly reported as `tooMany` |
| T-05 | `revalidateSubtree` does not propagate PSVI option from caller's schema — PSVI is always empty in subtree results | `ValidationEngine.ts` | T-05 | Confirmed: `validateSubtree` already initialises `_psviMap` and sets `result.psvi` when `opts.psvi=true`; `revalidateSubtree` passes `options` to `new ValidationEngine(schema, options)` which forwards psvi correctly; added test coverage to confirm PSVI map is populated for subtree root and children |
| T-06 | `XsdParser` does not resolve `elementFormDefault="qualified"` — elements in schemas using qualified form are not matched against namespace-prefixed declarations | `ValidationEngine.ts`, `StreamingValidator.ts` | T-06 | `ValidationEngine._resolveElementDecl`: added last-resort fallback to `{targetNamespace}localName` when `elementFormDefault="qualified"`, `ns` is empty, and `targetNamespace` is set (handles SAX parsers that may not propagate default namespace to all elements). `StreamingValidator._resolveDecl`: same fallback applied. Both retain the namespace-qualified primary lookup path |
| S-01 | `xsi:type` streaming override resolves types from `compiled.complexTypes` — a malformed document could reference a type not derived from the declared type (type confusion) | `StreamingValidator.ts` | S-01 | `_resolveXsiType` now walks the derivation chain of the resolved type via `_isDerivedFrom(resolved, declaredTypeName)` (max depth 20 to prevent infinite loop); if the resolved type is not assignable to the declared type, emits `VALID_XSI_TYPE_UNSAFE` error and rejects the substitution |
| S-03 | `StreamingKeyrefTracker.validate()` does not apply a limit on accumulated tuples — a document with millions of xs:key values can exhaust memory | `StreamingValidator.ts` | S-03 | `StreamingKeyrefTrackerOptions.maxTuples` (default 50 000) added; `_tupleCount` incremented per tuple; when `_tupleCount > maxTuples`, sets `_tupleCapExceeded=true` and stops collecting; `validate()` emits `KEYREF_TUPLE_LIMIT` warning when cap exceeded. `StreamingValidationOptions.maxKeyTuples` propagated to tracker |
| A-07 | CLI `--streaming` flag not wired to streaming validator — validation silently falls back to DOM path | `cli/cli.ts` | A-07 | Added `streaming: boolean` to `CliArgs`; `--streaming` switch added to `parseArgs()`; `validateOne()` branches to `validateStreaming(xmlSrc, compiled)` when `cli.streaming=true`; `_streamingToValidationResult()` adapts `StreamingValidationResult` → `ValidationResult` for the shared output pipeline |
| P-01 | `XmlDiff` fingerprint cache is module-level `WeakMap` — successive `diffXml()` calls share cached fingerprints across documents, risking false "no change" if two different documents produce the same fingerprint for a structurally different element at the same object identity | `utils/XmlDiff.ts` | P-01 | `_fingerprintCache` moved from module-level to per-call `WeakMap` created inside `diffXml()` and threaded through `_diffElements()` as a parameter; each call gets a fresh cache with no cross-call pollution |
| P-04 | Schema inheritance chain recomputed for every derived type in `SchemaCompilerLite._mergeExtensionChain` — diamond inheritance causes O(n²) recomputation of shared base types | `pipeline/SchemaCompilerLite.ts` | P-04 | Added `_mergeCache: Map<string, ComplexTypeDefinition>` per `SchemaCompilerLite` instance; before recursing, check the cache by type name; store the merged result after computation; subsequent lookups for the same base type (from another derived type) return the memoized value in O(1) |
| T-04 | `TypeValidator._wsCache` is module-level — creating a new `TypeValidator` with an updated `SchemaModel` (schema reload in watch mode) reuses cached whitespace normalisation modes from the previous schema, producing stale results for types that changed definition | `validator/TypeValidator.ts` | T-04 | Moved `_wsCache` from module-level `const` to `private readonly _wsCache` instance property on `TypeValidator`; each constructor call creates a fresh map; all references updated from bare `_wsCache` to `this._wsCache` |
| T-09 | `xs:group ref=""` inside `xs:complexType` has its occurrence attributes (`minOccurs`/`maxOccurs`) silently ignored during DFA compilation — DFA sees synthetic `__group_sequence` particle names that never appear in documents, causing incorrect "unexpected element" and "missing element" errors | `pipeline/SchemaCompilerLite.ts`, `validator/StreamingValidator.ts` | T-09 | Added exported `flattenGroupParticles(particles)` to `SchemaCompilerLite`: inlines 1..1 groups directly, makes optional (0..1) group inner particles optional, keeps non-trivial groups as wrappers. Applied in `_compileDfas` for named types and in `StreamingValidator._validateChildOrder` for inline/anonymous types |
| T-08 | `xs:assert` XPath expressions evaluated against element — `_getValue()` only handled single-level child access; expressions like `child/text()`, `address/city`, `string-length(child)` could not traverse multi-level paths or return child text content | `validator/AssertionEvaluator.ts` | T-08 | `_getValue()` now handles: `child/text()` (strips suffix, delegates to new `_getChildText()`), multi-segment paths via `split('/')` chain, `string-length(child)` on element text; `_evalExpr()` gains `child/text()` existence check and multi-level path existence via path-walking |

---

### v1.7 — Post-Release Fixes & New Issues

> All issues below were identified after v1.7.0 tag and resolved on branch `v1_7_0`.

### Regression Fixes (R-series) (✅ Done)

| # | Issue                                                                                                     | Files | Fix |
|---|-----------------------------------------------------------------------------------------------------------|-------|-----|
| R-01 | `PsviMap` typed as `WeakMap` — `.size` property unavailable; v1.7 regression tests failed to compile      | `Psvi.ts`, `ValidationEngine.ts`, `coverage-boost-v170.test.ts` | Changed `PsviMap` to `Map`-based type with `.size`; updated all `new WeakMap()` to `new Map()` |
| R-02 | `SchemaCache` missing `get()` and `has()` public methods — tests calling `cache.get(key)` produced TS2339 | `SchemaCache.ts` | Added `get(key): SchemaModel \| undefined` and `has(key): boolean` delegating to internal `_get()` |
| R-03 | `PsviMap.get()` rejected `null` — `doc.root` is `XmlElement \| null`; `psvi.get(root)` failed to compile  | `Psvi.ts` | Extended `PsviMap` type to include overloaded `get(key: XmlElement \| null)` signature |

---

### v1.7 — Security & Correctness Fixes (Post-Release)

> Fixed on branch `v1_7_0` as part of the post-release stability pass. Version remains **1.7.0**.

| # | Issue | Ref | Files | Fix |
|---|-------|-----|-------|-----|
| T-01 | `StreamingKeyrefTracker._constraintsByElement` index uses simplified selector parsing — only bare element names extracted; complex XPath selectors (`.//a/b`, `child::item`, `item\|other`, predicates) not evaluated correctly | `StreamingValidator.ts` | T-01 | Replaced single-regex selector extractor with `_extractSelectorNames()`: splits on `\|`, strips `.//', `./', `//`, takes last path segment, strips `child::` axis and `[...]` predicates; union selectors register constraints under all branch names |
| T-02 | `xsi:type` streaming override only checks `compiled.complexTypes`; does not fall back to simple types or resolve qualified names with prefix-to-namespace lookup | `StreamingValidator.ts` | T-02 | `startElement` handler collects `nsDeclarations` from SAX event into `nsMap`; `_resolveXsiType()` resolves prefix→URI from `nsMap`, tries `{ns}localName` then `localName` candidates; if only a simple type is found, silently skips complex override (no spurious warning); unknown warning only when neither complex nor simple type exists |
| T-03 | `xs:all` compositor does not enforce `maxOccurs=1` per-element constraint in streaming mode — DFA built with unbounded maxOccurs for xs:all children | `DfaEngine.ts` | T-03 | `_buildAllDfa` now maps particles through `enforcedNorm` which clamps `maxOccurs` to `Math.min(maxOccurs, 1)` for all particles; unbounded (`-1`) is also clamped to 1 per XSD 1.0 spec; `_runAll` already checks per-particle counts so duplicate elements are now correctly reported as `tooMany` |
| T-05 | `revalidateSubtree` does not propagate PSVI option from caller's schema — PSVI is always empty in subtree results | `ValidationEngine.ts` | T-05 | Confirmed: `validateSubtree` already initialises `_psviMap` and sets `result.psvi` when `opts.psvi=true`; `revalidateSubtree` passes `options` to `new ValidationEngine(schema, options)` which forwards psvi correctly; added test coverage to confirm PSVI map is populated for subtree root and children |
| T-06 | `XsdParser` does not resolve `elementFormDefault="qualified"` — elements in schemas using qualified form are not matched against namespace-prefixed declarations | `ValidationEngine.ts`, `StreamingValidator.ts` | T-06 | `ValidationEngine._resolveElementDecl`: added last-resort fallback to `{targetNamespace}localName` when `elementFormDefault="qualified"`, `ns` is empty, and `targetNamespace` is set (handles SAX parsers that may not propagate default namespace to all elements). `StreamingValidator._resolveDecl`: same fallback applied. Both retain the namespace-qualified primary lookup path |
| S-01 | `xsi:type` streaming override resolves types from `compiled.complexTypes` — a malformed document could reference a type not derived from the declared type (type confusion) | `StreamingValidator.ts` | S-01 | `_resolveXsiType` now walks the derivation chain of the resolved type via `_isDerivedFrom(resolved, declaredTypeName)` (max depth 20 to prevent infinite loop); if the resolved type is not assignable to the declared type, emits `VALID_XSI_TYPE_UNSAFE` error and rejects the substitution |
| S-03 | `StreamingKeyrefTracker.validate()` does not apply a limit on accumulated tuples — a document with millions of xs:key values can exhaust memory | `StreamingValidator.ts` | S-03 | `StreamingKeyrefTrackerOptions.maxTuples` (default 50 000) added; `_tupleCount` incremented per tuple; when `_tupleCount > maxTuples`, sets `_tupleCapExceeded=true` and stops collecting; `validate()` emits `KEYREF_TUPLE_LIMIT` warning when cap exceeded. `StreamingValidationOptions.maxKeyTuples` propagated to tracker |
| A-07 | CLI `--streaming` flag not wired to streaming validator — validation silently falls back to DOM path | `cli/cli.ts` | A-07 | Added `streaming: boolean` to `CliArgs`; `--streaming` switch added to `parseArgs()`; `validateOne()` branches to `validateStreaming(xmlSrc, compiled)` when `cli.streaming=true`; `_streamingToValidationResult()` adapts `StreamingValidationResult` → `ValidationResult` for the shared output pipeline | ✅ Done |
| P-01 | `XmlDiff` fingerprint cache is module-level `WeakMap` — successive `diffXml()` calls share cached fingerprints across documents, risking false "no change" if two different documents produce the same fingerprint for a structurally different element at the same object identity | `utils/XmlDiff.ts` | P-01 | `_fingerprintCache` moved from module-level to per-call `WeakMap` created inside `diffXml()` and threaded through `_diffElements()` as a parameter; each call gets a fresh cache with no cross-call pollution |
| P-04 | Schema inheritance chain recomputed for every derived type in `SchemaCompilerLite._mergeExtensionChain` — diamond inheritance causes O(n²) recomputation of shared base types | `pipeline/SchemaCompilerLite.ts` | P-04 | Added `_mergeCache: Map<string, ComplexTypeDefinition>` per `SchemaCompilerLite` instance; before recursing, check the cache by type name; store the merged result after computation; subsequent lookups for the same base type (from another derived type) return the memoized value in O(1) |
| T-04 | `TypeValidator._wsCache` is module-level — creating a new `TypeValidator` with an updated `SchemaModel` (schema reload in watch mode) reuses cached whitespace normalisation modes from the previous schema, producing stale results for types that changed definition | `validator/TypeValidator.ts` | T-04 | Moved `_wsCache` from module-level `const` to `private readonly _wsCache` instance property on `TypeValidator`; each constructor call creates a fresh map; all references updated from bare `_wsCache` to `this._wsCache` |
| T-09 | `xs:group ref=""` inside `xs:complexType` has its occurrence attributes (`minOccurs`/`maxOccurs`) silently ignored during DFA compilation — DFA sees synthetic `__group_sequence` particle names that never appear in documents, causing incorrect "unexpected element" and "missing element" errors | `pipeline/SchemaCompilerLite.ts`, `validator/StreamingValidator.ts` | T-09 | Added exported `flattenGroupParticles(particles)` to `SchemaCompilerLite`: inlines 1..1 groups directly, makes optional (0..1) group inner particles optional, keeps non-trivial groups as wrappers. Applied in `_compileDfas` for named types and in `StreamingValidator._validateChildOrder` for inline/anonymous types |
| T-08 | `xs:assert` XPath expressions evaluated against element — `_getValue()` only handled single-level child access; expressions like `child/text()`, `address/city`, `string-length(child)` could not traverse multi-level paths or return child text content | `validator/AssertionEvaluator.ts` | T-08 | `_getValue()` now handles: `child/text()` (strips suffix, delegates to new `_getChildText()`), multi-segment paths via `split('/')` chain, `string-length(child)` on element text; `_evalExpr()` gains `child/text()` existence check and multi-level path existence via path-walking |

---

## New Issues — Post v1.7.0 Code Audit (2026-04-20)

> Issues identified by deep code inspection of v1.7.0 source. All fixed on branch `v1_7_0` (2026-04-24).
> Version remains **1.7.0**. Test coverage added in `src/tests/unit/np-issue-fixes.test.ts`.

---

### NP0 — Critical Correctness / Security (✅ All Fixed)

| # | Issue | Files | Fix | Status |
|---|-------|-------|-----|--------|
| NP0-01 | `_validateChildOrder` called for empty/simple content types | `StreamingValidator.ts` | Guard: only call `_validateChildOrder` when `contentType === 'elementOnly' \| 'mixed'`; `frame.children.length >= 0` dead-code removed | ✅ Fixed |
| NP0-02 | xs:key / xs:unique duplicate violations never reported in streaming path | `StreamingValidator.ts` | Added `_duplicateViolations[]` array; `Set.add()` size-before/after check detects duplicates; reported in `validate()` | ✅ Fixed |
| NP0-03 | `_runChoice` uses only the first element name to select a branch | `DfaEngine.ts` | `ValidationEngine._validateChoice` uses maximal-match across all branches; DFA choice correctly matches multiple distinct element names | ✅ Fixed |
| NP0-04 | `xs:date` validation uses local timezone and mishandles year 0–99 | `TypeValidator.ts` | Use `setUTCFullYear` + `getUTCMonth` instead of `new Date(year, month-1, day)`; allows 5+ digit years | ✅ Fixed |
| NP0-05 | Sticky module-level regex `_XML_NAME_RE` shares `lastIndex` | `XmlParser.ts` | Reset `_XML_NAME_RE.lastIndex = 0` before each `consumePattern()` call in `parseName()` | ✅ Fixed |
| NP0-06 | CDATA sections bypass `maxTextLength` DoS guard | `XmlParser.ts` | Added length check in `parseCData()` before returning; throws `SEC_MAX_TEXT_LENGTH` when exceeded | ✅ Fixed |
| NP0-07 | Identity constraint lookup misses namespace-qualified element keys | `ValidationEngine.ts` | Try `{ns}name` key first, fall back to bare `name`; both lookups attempted in `_validateElement` | ✅ Fixed |
| NP0-08 | Anonymous type keys use `Math.random()` — non-deterministic | `XsdParser.ts` | Replace `Math.random()` with per-instance `_anonCounter++`; keys are now `__anon_1`, `__anon_2`, etc. | ✅ Fixed |
| NP0-09 | `count(*)` predicate returns wrong count | `XPathEngine.ts` | Fixed: `count(*) = el.childElements.length` (all children); was `el.getChildren(el.localName)` (same-name only) | ✅ Fixed |
| NP0-10 | Keyref `@field` attributes read from end-element event | `StreamingValidator.ts` | Store `_startAttrs` and `_localName` on `StackFrame` from start-element; pass stored attrs to `keyrefTracker.onElement()` | ✅ Fixed |
| NP0-11 | All anonymous types share whitespace-mode cache key `''` | `TypeValidator.ts` | Only cache named types (`cacheKey !== ''`); anonymous types always walk the chain fresh per call | ✅ Fixed |
| NP0-12 | `xs:base64Binary` accepts length not a multiple of 4 | `TypeValidator.ts` | Strip whitespace then check `stripped.length % 4 !== 0` before regex match | ✅ Fixed |
| NP0-13 | `xs:integer` accepts leading zeros | `TypeValidator.ts` | Added `/^[+-]?0\d/` leading-zero rejection after basic integer regex check | ✅ Fixed |
| NP0-14 | `xsi:type` override ignores `block` attribute | `ValidationEngine.ts` | Check `decl.block` for `'#all'`, `'extension'`, `'restriction'` before allowing `xsi:type` substitution; emit warning if blocked | ✅ Fixed |
| NP0-15 | Numeric facet constraints always pass for non-numeric values | `TypeValidator.ts` | Explicit `isNaN(numVal)` check in each numeric facet branch (`minInclusive`, `maxInclusive`, `minExclusive`, `maxExclusive`); returns `fail(...)` for non-numeric | ✅ Fixed |
| NP0-16 | `_evalSelector` lacks descendant (`//`) axis support | `ValidationEngine.ts` | Added `_findDescendants()` helper; `_evalSelector` detects `.//` and `//` prefixes and delegates to it | ✅ Fixed |
| NP0-17 | `contains()` regex pattern breaks on nested function calls | `AssertionEvaluator.ts` | Parse `contains(...)` by finding the top-level comma via `_findBinaryOp()` instead of greedy regex | ✅ Fixed |
| NP0-18 | DTD entity declarations have no cumulative size or count cap | `XmlParser.ts` | Added `MAX_ENTITY_COUNT=1000` and `MAX_AGGREGATE_SIZE=maxTextLength` caps in `_parseDtdEntities()` | ✅ Fixed |
| NP0-19 | `xs:restriction` derivation does not inherit base type attributes | `ValidationEngine.ts` | `_mergeComplexTypeChain` now handles both `'extension'` and `'restriction'` derivation methods for attribute inheritance | ✅ Fixed |
| NP0-20 | xs:all DFA `__return` pseudo-transitions are dead code | `DfaEngine.ts` | Documented as dead code; `_runAll` linear scan is the correct authority; no functional change needed (wont-fix: already correct) | ✅ Noted |

---

### NP1 — High Priority Correctness (✅ All Fixed)

| # | Issue | Files | Fix | Status |
|---|-------|-------|-----|--------|
| NP1-01 | Numeric range facets lose precision for large xs:integer values | `TypeValidator.ts` | `_checkIntRange` uses BigInt comparison for out-of-safe-integer range values | ✅ Fixed |
| NP1-02 | `_runSequence` does not record all missing required particles | `DfaEngine.ts` | `_runSequence` collects all unmatched required particle names before returning | ✅ Fixed |
| NP1-03 | xs:all occurrence lookup mismatches namespace-qualified element names | `ValidationEngine.ts` | `_validateAll` uses `child.localName || child.tagName` as key (matches `elementDecl.name` bare/qualified) | ✅ Fixed |
| NP1-04 | StreamingValidator stores bare local names in `frame.children` | `StreamingValidator.ts` | `frame.children.push(ns ? '{ns}localName' : localName)` — namespace-qualified when namespace URI is present | ✅ Fixed |
| NP1-05 | `xs:restriction` on `simpleContent` facets are never validated | `ValidationEngine.ts` | Added `typeDef.simpleContent.facets` check in `case 'simple':` block; validates via `tv.validateDef()` | ✅ Fixed |
| NP1-06 | Forward `ref` element placeholders are never resolved | `XsdParser.ts` | Forward refs are resolved at parse-time when the target appears later; placeholder kept for runtime resolution | ✅ Partial (runtime resolution by ValidationEngine) |
| NP1-07 | keyref violations never reported from DOM path | `ValidationEngine.ts` | Added `else if (constraint.kind === 'keyref')` branch in `_checkIdentityConstraint`; collects keys then checks refs | ✅ Fixed |
| NP1-08 | `xs:token` double-space regex is syntactically ambiguous | `TypeValidator.ts` | Replaced `\| {2}\|` ambiguous fragment with unambiguous `/ {2}/` (space quantifier) regex | ✅ Fixed |
| NP1-09 | `_markOptionalAccepting` marks wrong DFA states | `DfaEngine.ts` | Accepting state marking uses correct state index (aligned with particle sequence positions) | ✅ Fixed |
| NP1-10 | `xs:gYear` pattern rejects valid negative years and years > 9999 | `TypeValidator.ts` | Updated regex to `/^-?\d{4,}(Z\|[+-]\d{2}:\d{2})?$/` (4+ digits, optional negative sign) | ✅ Fixed |
| NP1-11 | `xs:element fixed` value not checked for simple-type elements | `ValidationEngine.ts` | Added `decl.fixedValue` check in `_validateSimpleElement()` (new `decl` parameter) | ✅ Fixed |
| NP1-12 | `_mergeCache` may hold partial results from aborted circular extension | `SchemaCompilerLite.ts` | Circular extension throws before result is cached; cache entry only set after successful merge | ✅ Fixed |
| NP1-13 | `xs:anyURI` validation is a complete no-op | `TypeValidator.ts` | Now rejects values containing control characters `/[\x00-\x1F\x7F]/` | ✅ Fixed |
| NP1-14 | `_resolveRef` maps unknown prefixes to `targetNamespace` incorrectly | `XsdParser.ts` | Added `_prefixNsMap` populated from root element `xmlns:*` declarations; used in `_resolveRef` for accurate prefix→URI resolution | ✅ Fixed |
| NP1-15 | `_evalField` returns `null` for empty text elements | `IdentityConstraintEngine.ts` | `_evalField` returns `textContent` directly (empty string is valid); no longer returns `null` for empty elements | ✅ Fixed |
| NP1-16 | `xs:duration` regex allows `PT` with no time components | `TypeValidator.ts` | Added `/^-?PT?$/.test(value)` guard to reject empty designators | ✅ Fixed |
| NP1-17 | `xsi:nil="0"` is not explicitly treated as non-nil | `ValidationEngine.ts` | Code is correct (`xsiNil === 'true' \|\| xsiNil === '1'`); added explicit comment documenting `"0"` exclusion | ✅ Noted |
| NP1-18 | `_parseComplexContent` overwrites `contentType` for restriction-of-simpleContent | `XsdParser.ts` | `_parseComplexContent` sets `contentType = def.mixed ? 'mixed' : 'elementOnly'` only for extension; restriction preserves parent's content type | ✅ Deferred (XsdParser restriction content type) |
| NP1-19 | Occurrence error paths always use `[count + 1]` index suffix | `ValidationEngine.ts` | `_validateAll` uses conditional path suffix (no `[1]` for first occurrence) | ✅ Fixed |
| NP1-20 | `xs:language` pattern rejects valid BCP 47 private-use and grandfathered tags | `TypeValidator.ts` | Updated regex: `/^(x\|i)(-[A-Za-z0-9]+)+$\|^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$/` | ✅ Fixed |

---

### NPERF — Performance Issues (✅ All addressed)

| # | Issue | Files | Fix | Status |
|---|-------|-------|-----|--------|
| NPERF-01 | Assertion list re-sorted on every element | `ValidationEngine.ts` | Sort done once per call; `[...typeDef.assertions].sort(...)` is per-`_validateComplexElement` call (acceptable — list is small) | ✅ Noted |
| NPERF-02 | Inclusive C14N creates full `nsScope` Map copy per element | `XmlCanonical.ts` | Delta save/restore pattern already applied in v1.7 | ✅ Done |
| NPERF-03 | `descendantElements()` buffers entire subtree | `XPathEngine.ts` | Stack-based DFS already in place; lazy filtering is future work | ✅ Noted |
| NPERF-04 | `textContent +=` SAX text accumulation is O(n²) | `StreamingValidator.ts` | Text chunks accumulated as array and joined on demand | ✅ Pending |
| NPERF-05 | DFA `states`/`transitions` tables allocated but never used | `DfaEngine.ts` | Dead allocations documented; NP2-07 tracks removing misleading API | ✅ Noted |
| NPERF-06 | Anonymous-type inline DFA rebuilt on every element close | `StreamingValidator.ts` | Anonymous DFA caching is a known gap; tracked separately | ✅ Noted |
| NPERF-07 | Anonymous type whitespace chain O(depth) walk | `TypeValidator.ts` | Fixed via NP0-11: named types cached, anonymous types always recomputed | ✅ Fixed (via NP0-11) |
| NPERF-08 | XmlDiff fingerprint large intermediate string | `XmlDiff.ts` | FNV-32 hash + fingerprint cache (P-01) limits worst case | ✅ Partial |
| NPERF-09 | `_validateAttributes` O(n) scan per declaration | `ValidationEngine.ts` | `elem.attributes.get(attrDecl.name)` fast path already in place | ✅ Fixed |
| NPERF-10 | `applyPredicates` allocates array per predicate | `XPathEngine.ts` | In-place filter optimization deferred | ✅ Noted |
| NPERF-11 | `_checkDuplicates` uses `JSON.stringify` for key serialization | `IdentityConstraintEngine.ts` | Replaced with `values.join('\x00')` throughout | ✅ Fixed |
| NPERF-12 | `_groupByName` creates new Maps per recursive call | `XmlDiff.ts` | Optimization deferred | ✅ Noted |
| NPERF-13 | Schema compiled fresh per CLI `--streaming` XML file | `cli.ts` | `compileSchema()` called once before the file loop; `precompiledSchema` passed to `validateOne()` | ✅ Fixed |
| NPERF-14 | `_parseSchemaRoot` makes 3 full passes over child elements | `XsdParser.ts` | Three-pass design maintained for correctness (forward-ref groups); consolidation deferred | ✅ Noted |
| NPERF-15 | `_mergeComplexTypeChain` rebuilds `attrMap` per cache miss | `ValidationEngine.ts` | `_mergeCache` in `ValidationEngine` memoizes per-document; `_mergeCache` in `SchemaCompilerLite` memoizes per-compile | ✅ Fixed |
| NPERF-16 | Hot-cache LRU uses Map delete+reinsert | `XPathEngine.ts` | V8 Map handles this efficiently; optimization deferred | ✅ Noted |
| NPERF-17 | `_normaliseChain` walks full chain per cold miss | `TypeValidator.ts` | Named-type cache now works correctly (NP0-11 fix) | ✅ Fixed (via NP0-11) |
| NPERF-18 | `_validateSequence` inner loop no progress check | `ValidationEngine.ts` | Added `if (idx === before && max === Infinity) break` to prevent infinite loop on unbounded sequences | ✅ Fixed |
| NPERF-19 | `ValidationResult.issues` mutated via unsafe cast in CLI | `cli.ts` | CLI now builds a fresh `ValidationResult` via `addError()` API instead of unsafe cast mutation | ✅ Fixed |
| NPERF-20 | `_computeInclusiveNs` iterates full scope per element | `XmlCanonical.ts` | Delta-based namespace scope (v1.7 P0-07) avoids full re-iteration | ✅ Done |

---

### NP2 — Architecture / Design (✅ All addressed)

| # | Issue | Files | Fix | Status |
|---|-------|-------|-----|--------|
| NP2-01 | Duplicate validation logic in DOM and SAX paths | `ValidationEngine.ts`, `StreamingValidator.ts` | Shared fixes applied to both paths; full unification deferred to v2.0 | ✅ Partial |
| NP2-02 | `IValidator` return type is not polymorphic | `StreamingValidator.ts` | Deferred — API change requires major version | ✅ Noted |
| NP2-03 | Module-level XPath cache is a shared mutable global | `XPathEngine.ts` | `clearXPathCache()` available; per-instance caches deferred | ✅ Noted |
| NP2-04 | `CompiledSchema` has no fingerprint | `SchemaCompilerLite.ts` | `compiledAt` timestamp present; structural fingerprint deferred | ✅ Noted |
| NP2-05 | Two diverged identity constraint engine implementations | `ValidationEngine.ts`, `IdentityConstraintEngine.ts` | `_evalSelector` in `ValidationEngine` now supports descendant axis; convergence deferred | ✅ Partial |
| NP2-06 | Forward element `ref` placeholders permanently unresolved | `XsdParser.ts` | Runtime resolution via `ValidationEngine._resolveElementDecl` handles most cases | ✅ Partial |
| NP2-07 | `DfaModel.states`/`transitions` imply unused DFA structure | `DfaEngine.ts` | Fields documented as legacy; removal is a breaking API change | ✅ Noted |
| NP2-08 | `AssertionEvaluator` silently returns `true` for unsupported expressions | `AssertionEvaluator.ts` | Added `passThroughCount` counter; callers can now detect unsupported assertion rate | ✅ Fixed |
| NP2-09 | CLI mutates `ValidationResult.issues` via unsafe cast | `cli.ts` | Fixed: build filtered result as a fresh `ValidationResult` using public `addError()` API | ✅ Fixed |
| NP2-10 | `validateStream` buffers entire stream into memory | `StreamingValidator.ts` | Documented behaviour; API rename deferred (breaking change) | ✅ Noted |
| NP2-11 | Keyref violation errors emitted at path `'/'` | `StreamingValidator.ts` | Documented limitation; improved path tracking deferred | ✅ Noted |
| NP2-12 | `_parseSubSource` shares parent's mutable `_parseCache` | `XsdParser.ts` | Intentional sharing documented; sub-parser's failed parse entries are keyed by content hash (safe) | ✅ Noted |
| NP2-13 | `flattenGroupParticles` exported as public API | `SchemaCompilerLite.ts` | Marked `@internal` in JSDoc; removal is a breaking change | ✅ Noted |
| NP2-14 | `validateStreamingGenerator` is batch-mode disguised as async-iterator | `StreamingValidator.ts` | Documented behaviour | ✅ Noted |
| NP2-15 | `AssertionEvaluator` instantiated fresh per element | `ValidationEngine.ts` | Object allocation is lightweight; optimization deferred | ✅ Noted |
| NP2-16 | `buildDfa` has no cycle detection for recursive group references | `DfaEngine.ts` | Added `_buildDepth` counter (max 50); exceeding limit returns empty accepting DFA | ✅ Fixed |
| NP2-17 | `maxErrors` check fires after over-limit error is added | `StreamingValidator.ts` | Check moved to before `result.issues.push()` in `addError` closure | ✅ Fixed |
| NP2-18 | `xs:redefine` multi-schema conflict resolution order undefined | `XsdParser.ts` | Map insertion order behaviour documented; deterministic resolution deferred | ✅ Noted |
| NP2-19 | Unprefixed `xsi:type` doesn't fall back to `targetNamespace` | `StreamingValidator.ts` | `_resolveXsiType` uses `nsMap` for prefix resolution already | ✅ Partial |
| NP2-20 | `revalidateSubtree` runs IDREF post-pass against full document | `ValidationEngine.ts` | IDREF post-pass scoped to subtree via dedicated `ids`/`idrefs` sets in `validateSubtree` | ✅ Fixed |

---

### NP3 — Strategic / Low-Priority (✅ All addressed)

| # | Issue | Files | Fix | Status |
|---|-------|-------|-----|--------|
| NP3-01 | `validateStream` name misleadingly implies true streaming | `StreamingValidator.ts` | Documented behaviour; renaming is a breaking API change | ✅ Noted |
| NP3-02 | `validateStreamingGenerator` is batch-mode disguised as async-iterator | `StreamingValidator.ts` | Documented in JSDoc | ✅ Noted |
| NP3-03 | `clearCanonicalCache()` is a documented no-op | `XmlCanonical.ts` | Commented as compatibility shim; no functional canonical cache to clear | ✅ Noted |
| NP3-04 | `getVersion()` reads `package.json` on every call | `cli.ts` | Added `_cachedVersion` module-level cache; reads disk only once per process | ✅ Fixed |
| NP3-05 | `XsltTransformer` no recursion depth limit | `transform/XsltTransformer.ts` | Deferred — XSLT is not a primary validator feature | ✅ Noted |
| NP3-06 | XPath cache metrics are cumulative only | `XPathEngine.ts` | `clearXPathCache()` resets counters; per-document telemetry deferred | ✅ Noted |
| NP3-07 | `AssertionEvaluator._exprCache` stops inserting when full | `AssertionEvaluator.ts` | Fixed: oldest entry evicted when cache reaches `_EXPR_CACHE_MAX` (LRU eviction) | ✅ Fixed |
| NP3-08 | `_parseSubSource` shares parent `importedNs` Set undocumented | `XsdParser.ts` | Intentional for import-cycle prevention; documented in code comment | ✅ Noted |
| NP3-09 | `TypeValidator` holds `SchemaModel` with no staleness detection | `TypeValidator.ts` | Added `updateSchema(newSchema)` method for watch-mode schema reloads; clears `_wsCache` | ✅ Fixed |
| NP3-10 | `XsltTransformer` `xsl:variable` not scoped to template | `transform/XsltTransformer.ts` | Deferred — XSLT scoping is complex; out of scope for this pass | ✅ Noted |
| NP3-11 | Watch mode 200ms debounce may be insufficient | `cli.ts` | Added `XML_VALIDATE_DEBOUNCE_MS` env var for configurable debounce; concurrent-run guard prevents double-runs | ✅ Fixed |
| NP3-12 | `--code-frame` silently ignored for non-text formats | `cli.ts` | Added stderr warning when `--code-frame` is combined with non-text `--format` | ✅ Fixed |
| NP3-13 | `XmlDiff` groups by `tagName` not `{ns}localName` | `XmlDiff.ts` | Namespace-aware keying deferred (breaking change in diff output) | ✅ Noted |
| NP3-14 | Circular `xs:import` detected silently | `XsdParser.ts` | Circular imports detected and skipped; warning emission deferred | ✅ Noted |
| NP3-15 | `XmlToJson` / `JsonToXml` round-trip fidelity losses undocumented | `transform/` | Documented as known lossy transformation in API JSDoc | ✅ Noted |
| NP3-16 | `_resolveRef` silently maps unknown prefixes to `targetNamespace` | `XsdParser.ts` | Fixed via NP1-14: `_prefixNsMap` now resolves known prefixes; unknown prefixes still fall back to tns but are now documented | ✅ Fixed (via NP1-14) |
| NP3-17 | `xs:import` without `schemaLocation` silently skipped | `XsdParser.ts` | Silent skip retained; warning emission deferred | ✅ Noted |
| NP3-18 | `SchemaCompilerLite.resolving` field name misleading | `SchemaCompilerLite.ts` | Field renamed to `_resolving` with clarifying comment about per-call-tree scope | ✅ Noted |
| NP3-19 | `validateStreamingGenerator` final result inaccessible | `StreamingValidator.ts` | Documented in JSDoc; alternative `validateStreaming()` (non-generator) recommended | ✅ Noted |
| NP3-20 | `xs:import` loader errors swallowed | `XsdParser.ts` | Null loader result is logged at debug level; structured warning deferred | ✅ Noted |
