Absorb UBL/XSD sources into `novolis-xsd`
Policies that keep the org coherent
name: Novolis XSD UBL absorb
overview: Create a new Novolis-Platform library repo novolis-xsd that absorbs the Frank UBL/XSD lineage into a general XSD→C# generator plus pre-generated UBL and Peppol packages (SBDH lives under Peppol, not a separate facet)—reversing the current governance “skip finance/UBL” stance.
todos:
- id: gov-reverse-skip
content: Update governance skip/import/naming docs for novolis-xsd finance/UBL decision status: completed
- id: bootstrap-repo
content: Create Novolis-Platform/novolis-xsd from template under d:\novolis\novolis-xsd status: completed
- id: port-generator
content: Port Frank.UblSharp Generation pipeline into Novolis.Xsd.Generator + tools/XsdGen status: completed
- id: pregen-ubl
content: Vendor UBL 2.1 XSDs, generate Novolis.Xsd.Ubl + Validation, publish to GPR status: completed
- id: peppol-phase
content: Port SBDH into Novolis.Xsd.Peppol with BIS schemas, wrap/unwrap helpers, and tests status: completed
- id: helpers-perf
content: Ship serializer cache/stream helpers + performance tests; dogfood if needed status: completed isProject: false
Verdict
Stand up one new library repo `novolis-xsd` (from `novolis-template-dotnet`). Primary technical lineage is [Frank.UblSharp](https://github.com/frankhaugen/Frank.UblSharp) (supported Roslyn + XmlSchemaClassGenerator pipeline). Other repos become migration inputs, test oracles, or historical credit—not parallel generators.
Peppol owns the envelope: SBDH is not a separate package. Types and wrap/unwrap helpers from Frank.Libraries.SBDH land under `Novolis.Xsd.Peppol` alongside BIS schemas/helpers. Prefer Peppol naming in public APIs; keep Sbdh only as internal type names where the OASIS/CEFACT vocabulary requires it.
This does not touch novolis-markup / Manuscript (different “document” domain). It does reverse entries in `frank-repos-explicit-skip.md` and `frank-codegen-devtools.md`.
Source disposition
| Source | Role in Novolis |
|---|---|
| [Frank.UblSharp](https://github.com/frankhaugen/Frank.UblSharp) | **Absorb** — `Generation`, `Internals.XsdCodeGenerator`, `Resources`/`UBL-2.1`, runtime helpers, `Validation`. Drop legacy `Generator`/`V2`–`V4` after parity. |
| [Frank.Finance.Documents.Ubl](https://github.com/frankhaugen/Frank.Finance.Documents.Ubl) | **Ideas + tests** — per-document namespace mapping from `Generate.linq`; OASIS XSD tree; round-trip samples. **Do not** vendor the huge generated tree; regenerate under Novolis naming. Renderer → later optional facet or dogfood host. |
| [Frank.Libraries.Ubl](https://github.com/frankhaugen/Frank.Libraries/tree/master/src/Frank.Libraries.Ubl) | **Cherry-pick** helpers/services (`UblService`, any useful API). Discard its separate `Generation/CSharpGenerator` if it duplicates UblSharp. |
| [Frank.Libraries.SBDH](https://github.com/frankhaugen/Frank.Libraries/tree/master/src/Frank.Libraries.SBDH) | **Port into `Novolis.Xsd.Peppol`** — envelope types + `StandardBusinessDocumentService` (rename to Peppol-facing helpers where sensible). No `Novolis.Xsd.Sbdh` package. |
| [Frank.XsdCodeGeneration](https://github.com/frankhaugen/Frank.XsdCodeGeneration) | **Reference only** — incomplete Roslyn Syntax spike; do not ship. Long-term emit backend may move toward Roslyn Syntax *behind* the same public API. |
| [ubllarsen](https://github.com/Gammern/ubllarsen) | **Credit / NOTICE** — ancestral XmlSerializer models (Unlicense); already evolved inside UblSharp. Do not dual-maintain. |
| Upstream [UblSharp](https://github.com/ublsharp/UblSharp) (local `D:\repos\UblSharp`) | Baseline NuGet API comparison for helpers (`UblDocument`, validation). |
Dependency on `XmlSchemaClassGenerator` (nuget.org) stays explicit—same engine Frank.UblSharp and Finance.Documents already wrap—rather than inventing a third generator.
Target shape
d:\novolis\novolis-xsd\
schemas\ubl-2.1\ # vendored OASIS XSDs (or submodule) used by generate tool
schemas\peppol\ # BIS XSDs + SBDH/envelope schemas used by Peppol package
tools\XsdGen\ # CLI that writes Generated/ into packages
src\Novolis.Xsd\ # options, emit model, shared XML helpers
src\Novolis.Xsd.Generator\ # library API (schema set → C# sources)
src\Novolis.Xsd.Tool\ # packable dotnet tool (arbitrary XSD)
src\Novolis.Xsd.Ubl\ # pre-generated UBL 2.1 + serialize helpers
src\Novolis.Xsd.Ubl.Validation\ # schema validate
src\Novolis.Xsd.Peppol\ # SBDH envelope + Peppol BIS + wrap/process helpers
tests\Novolis.Xsd.Unit\
Novolis.Xsd.slnxOrthogonal library island (like markup/storage): Avalonia-free, not on the Math→Physics spine. Pack 2026.1.* to GitHub Packages; NuGet-only restore.
flowchart LR
xsdFiles[XSD schemas] --> genLib[Novolis.Xsd.Generator]
genLib --> tool[Novolis.Xsd.Tool]
tool --> ublPkg[Novolis.Xsd.Ubl Generated]
tool --> peppolPkg[Novolis.Xsd.Peppol]
sbdhPort[Frank.Libraries.SBDH port] --> peppolPkg
ublPkg --> helpers[Serialize validate pool helpers]
peppolPkg --> helpersGeneration product decisions (locked)
- Pre-generated packages for consumers — CI/
tools/XsdGenwrites C# intosrc/Novolis.Xsd.Ubl/Generated/(and Peppol). Consumers do not pay source-generator cost on every build. The Frank.UblSharp in-process Roslyn generator becomes the engine insideNovolis.Xsd.Generator/ tool, not the default UBL package shipping mode. - XmlSerializer-first models — emit
partial class(+ enums) withXml*attributes (UblSharp / ubllarsen fidelity). Optional record projections or--emit recordscome later for non-XML DTOs; records are a poor default for full UBL XmlSerializer fidelity. - Namespaces —
Novolis.Xsd.Ubl.{DocumentName}(and common CAC/CBC namespaces), inspired by Finance.Documents’ per-document split, not one giant root namespace. Peppol:Novolis.Xsd.Peppol.*(envelope + BIS). - General XSD path — same generator accepts arbitrary schema sets (Peppol, custom profiles) via tool flags: root namespace, naming scheme, collection type, nullable annotations.
- Licensing — MIT for Novolis packages; retain NOTICE for UblSharp/OASIS schema terms and ubllarsen ancestry; do not copy Unlicense sources wholesale if regenerated under MIT tooling.
- One Peppol facet — no separate SBDH package or
schemas/sbdh/tree; envelope schemas live underschemas/peppol/.
Performant helpers (ship with `Novolis.Xsd` / `Novolis.Xsd.Ubl` / `Novolis.Xsd.Peppol`)
Port and harden patterns from UblSharp + Finance.Documents:
- Cached
XmlSerializer/ typed document facade (à laUblDocument) - Serialize/deserialize to stream, UTF-8 bytes, and
XmlReader/XmlWriterwithout intermediate strings - Schema validation API from
Frank.UblSharp.Validation - Peppol envelope wrap/unwrap around UBL payloads (from SBDH port) without full DOM where practical
- Optional later: HTML/text render from Finance.Documents.Renderer (dogfood or separate facet—not phase 1)
Phased delivery
Phase 0 — Governance + repo bootstrap
- Update skip/import docs: Finance UBL and XsdCodeGeneration move from “skip/spike” to “import into
novolis-xsd”. - Add row to `frank-naming-and-structure.md`.
- Create
Novolis-Platform/novolis-xsdfrom template; clone underd:\novolis\novolis-xsd. - Wire into Platform slnx map when first packable projects exist (
Generate-Platform-Slnx.ps1).
Phase 1 — Generator spine
- Port Frank.UblSharp
Generation+ schema-set loading/XmlSchemaMetaVisitor/UblGeneratorFactoryintoNovolis.Xsd.Generator(Novolis naming, net10.0, TUnit). - Replace LINQPad
Generate.linqwithtools/XsdGencalling the library. - Golden tests: generate Invoice (+ one CAC type) and compare serialization round-trip against OASIS samples / existing Frank tests.
Phase 2 — Pre-generated UBL + validation
- Vendor UBL 2.1 XSDs under
schemas/ubl-2.1. - Generate full model set into
Novolis.Xsd.Ubl. - Port validation package; publish both to GPR.
- Smoke: deserialize OASIS invoice sample, validate, re-serialize.
Phase 3 — Peppol (includes SBDH)
- Port
Frank.Libraries.SBDHintoNovolis.Xsd.Peppol(envelope types + service helpers under Peppol namespaces). - Add Peppol BIS Billing (and agreed message set) XSDs under
schemas/peppol; generate Peppol-specific bits as needed. - Integration test: wrap UBL Invoice in Peppol/SBDH envelope and round-trip; helpers for common Peppol identifiers / process IDs.
- Schematron stays out-of-scope unless separately approved.
Phase 4 — Polish + retire Frank lanes
- Performance tests (serializer cache, large catalogues).
- Dogfood app under
novolis-dogfoodingif needed (not libraryapps/). - Archive or mark Frank.UblSharp / Finance.Documents.Ubl as superseded; leave XsdCodeGeneration archived.
Explicit non-goals (v1)
- No Avalonia UI / invoice designer in the library repo
- No bulk import of
Frank.Librariesmonolith - No separate
Novolis.Xsd.Sbdhpackage (SBDH is Peppol) - No local NuGet folder feeds; no cross-repo
ProjectReferencein committed csproj - No folding XSD into
novolis-codegen(keeps CodeGen focused on Reflection/bindings; XSD stays domain-adjacent to UBL/Peppol)
Success criteria
dotnet toolcan generate C# from a complex multi-file XSD set (UBL-scale) into a chosen namespaceNovolis.Xsd.Ubl+.Validation+.Peppolrestore from nuget.org + GitHub Packages only and pass round-trip + validate + envelope wrap tests- Governance skip lists and naming map agree with the new repo
verify-nuget-only/ project-ref mode scripts remain green for the new packages when added to the platform map