Model Re-Export Layer#
⚠️ HISTORICAL DOCUMENT: The Model Re-Export Layer was completely removed in PR #404. This page documents the historical architecture for reference only. External projects should now import directly from
pkg/model/,pkg/parser/, andpkg/schema/opnsense/.
Historical Context#
The Model Re-Export Layer was an architectural pattern in the opnDossier project that provided a stable public interface over internal XML schema types. Located in the internal/model/ package, this layer contained 92 type aliases following the pattern type X = schema.Y that masked the underlying internal/schema/ XML Data Transfer Objects (DTOs) from downstream consumers. The re-export layer served as the primary abstraction boundary between XML parsing concerns and application logic, enabling the project to support multiple firewall device types (OPNsense, pfSense) through a platform-agnostic CommonDevice domain model.
The layer served a dual purpose: it acted as both a type re-export mechanism and the home for critical parsing infrastructure including the ParserFactory (renamed to Factory in the migration) and DeviceParser interface. This design allowed 87+ consumer files across CLI commands, processors, converters, and audit plugins to operate on the CommonDevice domain model without coupling to OPNsense-specific XML structures. The re-export layer was designed as a temporary migration seam to facilitate backward compatibility during the multi-device architecture refactoring.
The layer was completely removed in PR #404, which moved all types to public pkg/ packages with SemVer stability guarantees. The migration eliminated the 13 re-export files containing 469 lines and 93 type aliases, replacing them with direct imports from well-structured public API packages.
Historical Architecture (Pre-PR #404)#
Layered Model Structure#
The opnDossier codebase implemented a multi-layered architecture that separated XML parsing from domain logic:
⚠️ Note: This diagram reflects the historical architecture. After PR #404, packages are located in pkg/schema/opnsense/, pkg/parser/opnsense/, and pkg/model/. See "Current Architecture" section below.
Historical Package Responsibilities#
internal/schema/ — XML Data Transfer Objects (DTOs) with xml:"" struct tags that mirror the OPNsense config.xml structure. These types were tightly coupled to XML unmarshaling and should not be imported by application logic.
internal/model/opnsense/ — Device-specific parser and converter implementations that transform schema DTOs into the CommonDevice domain model. This was the only package that imported internal/schema/.
internal/model/common/ — Platform-agnostic domain model with no XML tags. All application logic (processors, converters, markdown generators, audit plugins) operated on CommonDevice rather than XML-specific types.
internal/model/ — The re-export layer providing type aliases and constructor wrappers over internal/schema/, plus the ParserFactory implementation and DeviceParser interface for multi-device support.
Historical Type Aliases and Constructor Wrappers (Pre-PR #404)#
The re-export layer consisted of 92 type aliases organized across 15 domain-specific files. Each alias followed the pattern type X = schema.Y, providing a stable import path while delegating to the underlying schema implementation.
File Organization#
The 15 files in internal/model/ were organized by functional domain:
| File | Type Aliases | Constructor Functions | Domain |
|---|---|---|---|
security.go | 17 | 6 | Firewall rules, NAT, IPsec, IDS |
services.go | 16 | 4 | DNS, DHCP, NTP, Syslog, Monit |
interfaces.go | 14 | 0 | VLANs, bridges, GIF, GRE, LAGG |
system.go | 10 | 0 | System config, users, firmware |
vpn.go | 10 | 5 | OpenVPN, WireGuard |
network.go | 8 | 0 | Gateways, static routes |
dhcp.go | 5 | 0 | DHCP server configuration |
common.go | 3 | 0 | BoolFlag, ChangeMeta |
opnsense.go | 3 | 1 | Root document types |
certificates.go | 2 | 0 | Certificate authorities |
packages.go | 2 | 2 | Package metadata |
high_availability.go | 1 | 0 | HA sync configuration |
revision.go | 1 | 0 | Config revision tracking |
factory.go | 0 | 1 | ParserFactory for device detection |
factory_export.go | 2 | 0 | CommonDevice re-exports |
Note: All files listed above were removed in PR #404. These types now live directly in pkg/schema/opnsense/ or pkg/model/.
Type Alias Patterns (Historical Examples)#
Simple Type Alias — Minimal wrapper with documentation inheritance:
// BoolFlag provides custom XML marshaling for OPNsense boolean values.
// Type alias to schema.BoolFlag - all methods are inherited.
type BoolFlag = schema.BoolFlag
Type Alias with Constructor — Wraps both type and initialization logic:
// OpnSenseDocument is the root of the OPNsense configuration.
type OpnSenseDocument = schema.OpnSenseDocument
// NewOpnSenseDocument returns a new OpnSenseDocument with all slice
// and map fields initialized to prevent nil pointer dereferences.
func NewOpnSenseDocument() *OpnSenseDocument {
return schema.NewOpnSenseDocument()
}
Re-Exported Constants — factory_export.go provided constants alongside type aliases:
type CommonDevice = common.CommonDevice
type DeviceType = common.DeviceType
const (
DeviceTypeOPNsense = common.DeviceTypeOPNsense
DeviceTypePfSense = common.DeviceTypePfSense
DeviceTypeUnknown = common.DeviceTypeUnknown
)
Constructor Functions (Historical)#
The re-export layer provided 18+ constructor functions that delegated to the underlying schema package:
- Factory:
NewParserFactory()(renamed toNewFactory()with XML decoder injection) - Security:
NewFirewall(),NewIDS(),NewIPsec(),NewSwanctl() - VPN:
NewOpenVPN(),NewWireGuard(),NewClientExport() - Services:
NewDNSMasq(),NewSyslog(),NewMonit()
These constructors ensured proper initialization of nested slice and map fields, preventing nil pointer dereferences during configuration manipulation.
Current Architecture (Post-PR #404)#
Public API Structure#
PR #404 moved all types to pkg/ packages with SemVer stability guarantees:
pkg/
├── model/ # Platform-agnostic domain model
│ ├── device.go # CommonDevice, FirewallRule, Interface
│ ├── vpn.go # VPN configurations
│ ├── nat.go # NAT rules
│ ├── warning.go # ConversionWarning
│ └── ...
│
├── schema/
│ └── opnsense/ # OPNsense XML DTOs
│ ├── opnsense.go # OpnSenseDocument root
│ ├── security.go # Firewall, NAT structures
│ ├── common.go # BoolFlag, ChangeMeta
│ └── ...
│
└── parser/ # Parser interfaces + factory
├── parser.go # DeviceParser interface
├── factory.go # Factory (renamed from ParserFactory)
└── opnsense/ # OPNsense-specific implementation
├── parser.go # XML → schema DTO
└── converter.go # schema DTO → CommonDevice
Factory and Parser Usage#
The Factory (renamed from ParserFactory) now requires XML decoder injection to keep pkg/ free of internal/ dependencies:
import (
"github.com/EvilBit-Labs/opnDossier/internal/cfgparser"
common "github.com/EvilBit-Labs/opnDossier/pkg/model"
"github.com/EvilBit-Labs/opnDossier/pkg/parser"
)
// Create factory with injected XML decoder
factory := parser.NewFactory(cfgparser.NewXMLParser())
file, err := os.Open("config.xml")
if err != nil {
log.Fatal(err)
}
defer file.Close()
// Auto-detect device type and parse to CommonDevice
device, warnings, err := factory.CreateDevice(context.Background(), file, "", false)
if err != nil {
log.Fatal(err)
}
// Log conversion warnings
for _, w := range warnings {
log.Printf("Warning: %s (field=%s, severity=%s)", w.Message, w.Field, w.Severity)
}
// Operate on platform-agnostic CommonDevice
fmt.Printf("Device: %s, Version: %s\n", device.DeviceType, device.Version)
Interface Injection and Boundary Enforcement#
The XMLDecoder interface defined in pkg/parser/factory.go enables pkg/ to remain free of internal/ imports:
// pkg/parser/factory.go
type XMLDecoder interface {
Parse(ctx context.Context, r io.Reader) (*schema.OpnSenseDocument, error)
ParseAndValidate(ctx context.Context, r io.Reader) (*schema.OpnSenseDocument, error)
}
func NewFactory(decoder XMLDecoder) *Factory {
return &Factory{xmlDecoder: decoder}
}
Why interface injection is necessary: pkg/ packages must never import internal/ packages to maintain public API purity. Go enforces the internal/ access boundary at the module level—external consumers running go get would encounter build errors if pkg/ imported internal/cfgparser. The concrete cfgparser.NewXMLParser() implementation is wired at the cmd/ layer, allowing pkg/parser to remain dependency-free.
Go structural typing advantage: The Parser in pkg/parser/opnsense/ defines its own unexported xmlDecoder interface with identical method signatures. Go's structural typing automatically satisfies this interface without requiring an explicit import—any type that implements Parse() and ParseAndValidate() with matching signatures will work.
Detailed implementation: See docs/solutions/architecture-issues/pkg-internal-import-boundary.md for the complete explanation of boundary violations, interface injection patterns, and prevention strategies.
Import Pattern Changes#
Before PR #404:
import (
"github.com/EvilBit-Labs/opnDossier/internal/model"
"github.com/EvilBit-Labs/opnDossier/internal/model/common"
)
device, warnings, err := model.NewParserFactory().CreateDevice(ctx, r, "", false)
After PR #404:
import (
common "github.com/EvilBit-Labs/opnDossier/pkg/model"
"github.com/EvilBit-Labs/opnDossier/pkg/parser"
)
factory := parser.NewFactory(cfgparser.NewXMLParser())
device, warnings, err := factory.CreateDevice(ctx, r, "", false)
All 104 consumer files (CLI commands, processors, converters, audit plugins) were mechanically updated with these import path changes.
Historical Consumer Integration (Pre-PR #404)#
DeviceParser Interface#
The DeviceParser interface provides the contract for device-specific parsing implementations:
type DeviceParser interface {
Parse(ctx context.Context, r io.Reader) (*common.CommonDevice, error)
ParseAndValidate(ctx context.Context, r io.Reader) (*common.CommonDevice, error)
}
Both methods accept an XML configuration stream and return a *common.CommonDevice, abstracting away device-specific schema details from consumers.
ParserFactory and Device Type Detection (Historical)#
The historical ParserFactory performed automatic device type detection by inspecting the XML root element:
factory := model.NewParserFactory()
file, err := os.Open("config.xml")
if err != nil {
log.Fatal(err)
}
defer file.Close()
// Auto-detect device type and parse to CommonDevice
device, err := factory.CreateDevice(context.Background(), file, "", false)
if err != nil {
log.Fatal(err)
}
// Now operate on platform-agnostic CommonDevice
fmt.Printf("Device: %s, Version: %s\n", device.DeviceType, device.Version)
The factory method delegated to the appropriate DeviceParser implementation (internal/model/opnsense/parser.go for OPNsense), which internally used internal/cfgparser/xml.go for low-level XML parsing and internal/model/opnsense/converter.go to transform schema DTOs into the CommonDevice domain model.
Import Pattern Analysis (Historical)#
The architecture enforced clean separation between XML processing and domain logic:
Direct Schema Importers (5 files only):
internal/model/opnsense/parser.go— Unmarshals XML toschema.OpnSenseDocumentinternal/model/opnsense/converter.go— Converts schema DTOs to*common.CommonDeviceinternal/cfgparser/xml.go— Low-level XML parsing primitivesinternal/validator/opnsense.go— Structural validation of schema DTOstools/docgen/main.go— Documentation generator
CommonDevice Importers (87+ files):
- All CLI commands (
cmd/) - All converter implementations (
internal/converter/) - All processor logic (
internal/processor/) - All audit plugins (
internal/audit/)
This import analysis confirmed that only the XML parsing layer interacted with internal/schema/; all application logic operated on *common.CommonDevice. The re-export layer successfully isolated consumers from XML-specific implementation details.
Current State: After PR #404, consumers import directly from pkg/model/ and pkg/parser/. The pkg/schema/opnsense/ package is only imported by pkg/parser/opnsense/ (parser and converter).
Migration Execution (Completed in PR #404)#
Rationale for Removal#
GitHub issue #301 outlined the plan to eliminate the re-export layer as part of exposing opnDossier's types and parsing infrastructure as public packages under pkg/ with SemVer stability guarantees. The 13 re-export files containing 469 lines and 93 type aliases created maintenance overhead: every change to internal/schema/ or internal/model/common/ required corresponding updates to the re-export layer. By moving packages to a well-structured pkg/ hierarchy, external Go projects can directly import opnDossier's domain model and parsing infrastructure without depending on internal implementation details.
Status: ✅ Completed in PR #404. The re-export layer has been fully removed, with all types moved to pkg/ packages.
Executed Migration Plan#
PR #404 implemented a four-phase approach:
Phase 1: Create pkg/ Structure ✅#
Established the new public API hierarchy:
pkg/model/— Platform-agnostic domain model (moved frominternal/model/common/)pkg/schema/opnsense/— OPNsense XML DTOs (moved frominternal/schema/)pkg/parser/— Parser interfaces and factory (extracted frominternal/model/factory.go)pkg/parser/opnsense/— OPNsense-specific implementation (moved frominternal/model/opnsense/)
Phase 2: Update Internal Imports ✅#
Mechanical find-and-replace across 104 files:
internal/model/common → pkg/model
internal/schema → pkg/schema/opnsense
internal/model/opnsense → pkg/parser/opnsense
internal/model (factory) → pkg/parser
internal/model (aliases) → pkg/schema/opnsense or pkg/model
Phase 3: Remove Re-export Layer ✅#
Deleted the 13 re-export files: common.go, security.go, services.go, vpn.go, interfaces.go, system.go, network.go, dhcp.go, packages.go, opnsense.go, certificates.go, revision.go, high_availability.go. The entire internal/model/ directory was removed. Consumers now import directly from pkg/schema/opnsense or pkg/model.
Phase 4: Validation and Documentation ✅#
- Ran
just ci-checkto verify all tests pass (30 test packages) - Updated
.golangci.ymlrevive var-naming exclusion:internal/model/common/→pkg/model/ - Updated
AGENTS.md, project structure docs, and.github/copilot-instructions.mdwith new import patterns - External import validation:
go get github.com/EvilBit-Labs/opnDossier/pkg/model(available after merge + tag)
Final Public API Structure#
The completed structure provides SemVer stability:
pkg/
├── model/ # Platform-agnostic domain model
│ ├── device.go # CommonDevice, FirewallRule, Interface
│ ├── vpn.go # VPN configurations
│ ├── nat.go # NAT rules
│ ├── warning.go # ConversionWarning
│ └── ...
│
├── schema/
│ └── opnsense/ # OPNsense XML DTOs
│ ├── opnsense.go # OpnSenseDocument root
│ ├── security.go # Firewall, NAT structures
│ └── ...
│
└── parser/ # Parser interfaces + factory
├── parser.go # DeviceParser interface
├── factory.go # Factory
└── opnsense/ # OPNsense-specific implementation
├── parser.go # XML → schema DTO
└── converter.go # schema DTO → CommonDevice
Key Changes in PR #404#
ParserFactory→Factory: Renamed per Go naming conventions (avoid stuttering)- XML decoder injection:
Factorynow requiresXMLDecoderat construction (NewFactory(cfgparser.NewXMLParser())) to keeppkg/free ofinternal/dependencies ConversionWarningmoved: Frominternal/model/common/warning.gotopkg/model/warning.go(new file, content restructured)- Package name changes:
internal/model/common(packagecommon) becamepkg/model(packagemodel) - Import aliases preserved: All consumers continue using
common "..."andschema "..."aliases — no code changes beyond import paths
Timeline and Dependencies#
Completed: PR #404 merged, completing the broader multi-device architecture refactoring. This was the final implementation phase following:
- Issue #196: Multi-device model layer refactoring
- Issue #282: Re-export layer elimination planning
- Issue #301: Public package exposure design
Historical Gotchas and Best Practices#
Type Alias Removal Breaks All Consumers (Historical)#
Problem: Removing a type alias (type X = pkg.Y) is an immediate breaking change for all consumers that reference X.
Example: If internal/model/security.go contains type Firewall = schema.Firewall and this alias is removed, every file importing model.Firewall will fail to compile.
Mitigation:
- Grep exhaustively — Search for
model.Firewall(or the package name + type) across the entire codebase before removal - Atomic updates — Update all references in a single commit to prevent partial migration states
- CI validation — Run
just ci-checkto catch any missed references before merging
JSON vs YAML omitempty on Struct Fields#
Problem: Go's encoding/json ignores omitempty on struct-typed fields—empty structs are always serialized as {}. However, gopkg.in/yaml.v3 respects omitempty and can omit empty structs entirely.
// Correct: JSON omitempty removed, YAML omitempty retained
System System `json:"system" yaml:"system,omitempty"`
NAT NATConfig `json:"nat" yaml:"nat,omitempty"`
Enforcement: The modernize linter check in Go 1.26+ automatically flags omitempty on JSON struct fields and enforces removal.
Result:
- JSON output: Always includes struct fields (even if zero-valued)
- YAML output: Omits struct fields when zero-valued
Package Naming: "common" Triggers Linter Warnings (Historical)#
Problem: The revive linter flagged common as a meaningless package name, triggering var-naming: avoid meaningless package names warnings.
Justification: The name common was chosen to convey "platform-agnostic domain model" shared across multiple device types (OPNsense, pfSense).
Historical solution in .golangci.yml:
- path: internal/model/common/
linters: [revive]
text: "var-naming: avoid meaningless package names"
Current state: After PR #404, the package was renamed from internal/model/common to pkg/model, and the linter exclusion path was updated to pkg/model/.
Constants Must Be Re-Exported (Historical)#
Problem: Constants defined in source packages (internal/schema/, internal/model/common/) were not automatically re-exported through type aliases.
Example: factory_export.go had to explicitly re-export constants:
type DeviceType = common.DeviceType
const (
DeviceTypeOPNsense = common.DeviceTypeOPNsense
DeviceTypePfSense = common.DeviceTypePfSense
DeviceTypeUnknown = common.DeviceTypeUnknown
)
Implication: Any new constant added to common.DeviceType required a corresponding re-export in internal/model/factory_export.go.
Current state: After PR #404, constants are imported directly from pkg/model/ (e.g., common.DeviceTypeOPNsense). No re-export mechanism required.
CommonDevice Convenience Methods (Current)#
The CommonDevice struct (now in pkg/model/device.go) provides convenience methods for checking the presence of configuration sections:
// HasDHCP reports whether the device has any DHCP scope configuration.
func (d *CommonDevice) HasDHCP() bool
// HasInterfaces reports whether the device has any interface configuration.
func (d *CommonDevice) HasInterfaces() bool
// HasNATConfig reports whether the device has meaningful NAT configuration.
func (d *CommonDevice) HasNATConfig() bool
// HasRoutes reports whether the device has any static route configuration.
func (d *CommonDevice) HasRoutes() bool
// HasVLANs reports whether the device has any VLAN configuration.
func (d *CommonDevice) HasVLANs() bool
All methods return false if the device pointer is nil, making them safe for use without explicit nil checks. These methods are used by the diff engine for section-level change detection.
HasData() Value-Type Pattern#
For value-type structs (structs that are not pointers), presence detection requires checking whether the struct contains meaningful data. The HasData() method pattern provides this capability:
// NATConfig.HasData checks for meaningful NAT configuration
func (c NATConfig) HasData() bool {
return c.OutboundMode != "" ||
len(c.OutboundRules) > 0 ||
len(c.InboundRules) > 0 ||
c.ReflectionDisabled ||
c.PfShareForward ||
c.BiNATEnabled
}
Pattern Distinction:
- Pointer types — Nil checks suffice (e.g.,
if device.Firewall != nil) - Value types —
HasData()provides presence detection (e.g.,device.NAT.HasData())
This distinction is critical for the diff engine's section-level change detection. When a struct is embedded by value in CommonDevice (like NAT NATConfig), the field always exists but may be zero-valued. The HasData() method determines whether the zero-valued struct represents "no configuration" or simply "default configuration."
Usage: CommonDevice convenience methods delegate to HasData() where appropriate. For example, HasNATConfig() calls d.NAT.HasData() to determine NAT presence. The diff engine calls HasData() directly when comparing value-type sections.
Best Practices for Public Package Usage (Post-PR #404)#
- Import from
pkg/model/, not historical paths — Usecommon "github.com/EvilBit-Labs/opnDossier/pkg/model"for platform-agnostic domain model - Never import
internal/from external projects —pkg/packages are the stable public API boundary - Use
Factorywith XML decoder injection —parser.NewFactory(cfgparser.NewXMLParser())for CLI; external consumers provide their ownXMLDecoderimplementation - Use convenience methods for presence checks —
device.HasInterfaces()instead of manuallen(device.Interfaces) > 0checks - Handle conversion warnings — All parsing operations return
(*CommonDevice, []ConversionWarning, error)— log warnings without treating as fatal errors - Verify boundary violations before committing — Run
grep -rn 'internal/' --include='*.go' pkg/ | grep -v _test.goto ensurepkg/packages don't importinternal/ - Use interface injection for cross-boundary dependencies — Define interfaces in
pkg/and inject concrete implementations atcmd/. Seepkg/parser.XMLDecoderand docs/solutions/architecture-issues/pkg-internal-import-boundary.md for the canonical example - Leverage Go structural typing —
pkg/sub-packages can define their own unexported interfaces thatinternal/types satisfy without explicit imports
Current Code Files (Post-PR #404)#
| File Path | Purpose | Status |
|---|---|---|
| Public API (pkg/) | ||
pkg/model/device.go | CommonDevice platform-agnostic domain model | Active |
pkg/model/warning.go | ConversionWarning type | Active (new in PR #404) |
pkg/parser/factory.go | Factory implementation, device type detection | Active (renamed from ParserFactory) |
pkg/parser/opnsense/parser.go | OPNsense XML parser implementation | Active |
pkg/parser/opnsense/converter.go | Schema DTO to CommonDevice converter | Active |
pkg/schema/opnsense/opnsense.go | OpnSenseDocument root XML DTO | Active |
pkg/schema/opnsense/security.go | Firewall/NAT XML DTOs | Active |
pkg/schema/opnsense/common.go | BoolFlag, ChangeMeta, RuleLocation | Active |
| Internal Infrastructure | ||
internal/cfgparser/xml.go | Low-level XML parser and validator | Active |
internal/validator/opnsense.go | Schema DTO validation logic | Active |
| Configuration | ||
.golangci.yml | Linter configuration with pkg/model/ exclusion | Active |
AGENTS.md | Coding standards and linter documentation | Active |
Historical Code Files (Removed in PR #404)#
All files below were removed when the re-export layer was eliminated:
| File Path | Purpose | Status |
|---|---|---|
internal/model/common.go | Common type aliases (BoolFlag, ChangeMeta, RuleLocation) | ❌ Removed |
internal/model/security.go | Security/firewall type aliases and constructors | ❌ Removed |
internal/model/services.go | Service configuration type aliases | ❌ Removed |
internal/model/vpn.go | VPN type aliases (OpenVPN, WireGuard) | ❌ Removed |
internal/model/interfaces.go | Network interface type aliases | ❌ Removed |
internal/model/system.go | System configuration type aliases | ❌ Removed |
internal/model/network.go | Network/routing type aliases | ❌ Removed |
internal/model/dhcp.go | DHCP type aliases | ❌ Removed |
internal/model/packages.go | Package/service type aliases | ❌ Removed |
internal/model/opnsense.go | Root document type aliases | ❌ Removed |
internal/model/certificates.go | Certificate type aliases | ❌ Removed |
internal/model/revision.go | Configuration revision type alias | ❌ Removed |
internal/model/high_availability.go | HA sync type alias | ❌ Removed |
internal/model/factory.go | ParserFactory implementation (historical) | ❌ Removed (moved to pkg/parser/factory.go) |
internal/model/factory_export.go | CommonDevice and DeviceType re-exports | ❌ Removed |
internal/model/common/device.go | CommonDevice (historical location) | ❌ Removed (moved to pkg/model/device.go) |
internal/model/common/warning.go | ConversionWarning (historical location) | ❌ Removed (moved to pkg/model/warning.go) |
internal/schema/opnsense.go | OpnSenseDocument (historical location) | ❌ Removed (moved to pkg/schema/opnsense/opnsense.go) |
internal/schema/security.go | Firewall/NAT (historical location) | ❌ Removed (moved to pkg/schema/opnsense/security.go) |
internal/model/opnsense/parser.go | OPNsense parser (historical location) | ❌ Removed (moved to pkg/parser/opnsense/parser.go) |
internal/model/opnsense/converter.go | Schema converter (historical location) | ❌ Removed (moved to pkg/parser/opnsense/converter.go) |
Related Topics#
- CommonDevice Domain Model — Platform-agnostic firewall configuration representation (current location)
- Multi-Device Architecture Refactoring — Broader effort to support multiple firewall platforms (completed)
- XML Schema DTOs — OPNsense XML configuration data structures (current location)
- Parser Factory Pattern — Device type detection and parser delegation (current location)
- PR #404: Public Package Exposure — The pull request that removed the re-export layer
- Issue #301: Public Package Exposure Design — Planning issue for the migration
- Issue #282: Re-export Layer Elimination — Analysis of the 13 files and 93 type aliases removed