diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index b2f4eb0..32a77c7 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -35,21 +35,22 @@ must carry XML documentation. Public API changes must also be nullable-correct
and covered by semantic surface tests. Runtime API changes must be reconciled
deliberately with `docs/1.0.0-PUBLIC-API-BASELINE.txt`; Source API changes must
be reconciled with `docs/1.1.0-SOURCE-PUBLIC-API-BASELINE.txt`. Do not regenerate
-either baseline merely to silence a mismatch. Beginning with C01, Compiler API
-changes must likewise be reconciled with
-`docs/1.2.0-COMPILER-PUBLIC-API-BASELINE.txt`.
+either baseline merely to silence a mismatch. Compiler API changes must likewise
+be reconciled with `docs/1.2.0-COMPILER-PUBLIC-API-BASELINE.txt`, and Inspection
+API changes with `docs/1.3.0-INSPECTION-PUBLIC-API-BASELINE.txt`.
## Version metadata
-`Icod.TermInfo.csproj`, `Icod.TermInfo.Source/Icod.TermInfo.Source.csproj`, and
-`Icod.TermInfo.Compiler/Icod.TermInfo.Compiler.csproj` contain ``,
+`Icod.TermInfo.csproj`, `Icod.TermInfo.Source/Icod.TermInfo.Source.csproj`,
+`Icod.TermInfo.Compiler/Icod.TermInfo.Compiler.csproj`, and
+`Icod.TermInfo.Inspection/Icod.TermInfo.Inspection.csproj` contain ``,
``, and the stable 1.x ``. Keep `Version`
-and `PackageVersion` identical within each project, and keep all three package
+and `PackageVersion` identical within each project, and keep all four package
versions synchronized.
-Runtime, Source, and Compiler use the same `1.2.0-Alpha-X` package version for
-each 1.2 development tranche. The 1.x assembly version remains `1.0.0.0` for
-all three assemblies, and all three remain unsigned.
+Beginning with 1.3, Runtime, Source, Compiler, and Inspection advance together.
+The 1.x assembly version remains `1.0.0.0` for all four assemblies, and all four
+remain unsigned.
Prerelease development should use the active version roadmap's alpha/beta/RC
sequence. A final release tag must be exactly `v`.
@@ -127,13 +128,14 @@ Package changes should preserve:
- unsigned assembly identity throughout 1.x;
- Source Link information supplied by the .NET SDK;
- portable PDBs and `.snupkg` generation for all three targets;
-- synchronized `Icod.TermInfo` / `Icod.TermInfo.Source` package versions;
-- synchronized `Icod.TermInfo.Compiler` package versions beginning with C01;
-- runtime and Source fresh-package smoke consumers on all three targets;
-- Compiler fresh-package smoke consumers on all three targets beginning with C01;
+- synchronized Runtime, Source, Compiler, and Inspection package versions;
+- Runtime, Source, Compiler, and Inspection fresh-package smoke consumers on all
+ three targets;
- each package README, icon metadata, and LGPL license expression;
-- a one-way Source -> runtime package dependency;
-- a one-way Compiler -> runtime/Source dependency;
+- a one-way Source -> Runtime package dependency;
+- a one-way Compiler -> Runtime/Source dependency;
+- a one-way Inspection -> Runtime/Source dependency with no production Compiler
+ dependency;
- identical validated release artifacts for NuGet.org and GitHub Packages.
See `docs/RELEASING.md` before modifying publication workflows.
@@ -151,6 +153,11 @@ in the optional `Icod.TermInfo.Compiler` package. The low-level writer remains
pure; filesystem/database-layout output is layered separately, and no compiler
dependency enters `Icod.TermInfo` or `Icod.TermInfo.Source`.
+Version 1.3 adds canonical rendering, structured semantic comparison, and
+provider-aware inspection in the optional `Icod.TermInfo.Inspection` package.
+Inspection depends on Runtime and Source but not on Compiler, and it does not
+enlarge the frozen Runtime, Source, or Compiler public contracts.
+
Live session ownership, input-event decoding, curses/UI behavior, PTY/ConPTY
lifecycle, terminal probing, command-line `tic`/`infocmp`/`toe` applications,
termcap interoperability, and terminal emulation remain outside the current
diff --git a/Icod.TermInfo-1.3.0-Inspection-and-Comparison-Roadmap.md b/Icod.TermInfo-1.3.0-Inspection-and-Comparison-Roadmap.md
index 09c6624..b09d3c3 100644
--- a/Icod.TermInfo-1.3.0-Inspection-and-Comparison-Roadmap.md
+++ b/Icod.TermInfo-1.3.0-Inspection-and-Comparison-Roadmap.md
@@ -10,10 +10,10 @@
**Source API contract:** frozen at 1.1
**Compiler API contract:** frozen at 1.2
**New package:** `Icod.TermInfo.Inspection`
-**Current development version:** `1.3.0-Alpha-5`
+**Current development version:** `1.3.0`
**Development sequence:** `1.3.0-Alpha-1` through `1.3.0-Alpha-7`
-**Status:** I05 implementation candidate — build/test/package validation required
-**Current tranche:** I05 — Source-aware comparison
+**Status:** Release closure — I01-I07 complete and candidate API/package contract frozen
+**Current tranche:** Final 1.3.0 release closure
**Release objective:** reusable managed inspection, canonical rendering, and semantic-comparison APIs underlying future `infocmp`-style tooling without destabilizing the existing Runtime, Source, or Compiler contracts.
---
@@ -584,6 +584,8 @@ TermInfoDifference
TermInfoDifferenceKind
TermInfoInspectionTarget
+TermInfoInspectionResult
+TermInfoInspectionComparison
TermInfoInspectionEngine
```
@@ -817,6 +819,8 @@ No console parsing/output policy belongs here.
inspection/comparison workflows without duplicating acquisition, rendering, or
comparison logic.
+**Implementation record:** [`docs/1.3.0-I06-PROVIDER-AWARE-INSPECTION.md`](docs/1.3.0-I06-PROVIDER-AWARE-INSPECTION.md)
+
---
# I07 — Differential validation, robustness, and API/package freeze
@@ -880,6 +884,8 @@ made part of the Icod contract.
**Gate I07:** the Inspection API is deterministic, cross-platform, corpus-backed,
package-valid, and ready to freeze for 1.3.0.
+**Implementation record:** [`docs/1.3.0-I07-DIFFERENTIAL-VALIDATION-AND-FREEZE.md`](docs/1.3.0-I07-DIFFERENTIAL-VALIDATION-AND-FREEZE.md)
+
---
# 8. 1.3.0 release closure
@@ -888,6 +894,8 @@ I01-I07 constitute the planned 1.3 implementation program.
Release closure is a separate finalization step.
+**Release audit:** [`docs/1.3.0-RELEASE-AUDIT.md`](docs/1.3.0-RELEASE-AUDIT.md)
+
Release closure SHALL:
- set Runtime, Source, Compiler, and Inspection versions to exactly `1.3.0`;
@@ -981,28 +989,14 @@ embedded into it.
---
-# 11. Recommended first implementation step
-
-Create a `1.3.0` working branch from the current `main` release commit and begin
-with I01 only.
-
-I01 should deliberately avoid implementing the canonical renderer yet.
-
-Its purpose is to freeze:
-
-1. package ownership;
-2. four-package coordinated versioning;
-3. dependency direction;
-4. public API baseline mechanics;
-5. solution/test/package layout;
-6. CI/release support for the fourth package;
-7. effective-versus-source-aware inspection semantics;
-8. the rule that Inspection has no production Compiler dependency.
+# 11. Recommended next implementation step
-Once I01 is green locally and in GitHub Actions, proceed to I02.
+Complete the 1.3.0 release closure and publish the immutable `v1.3.0` tag before
+beginning the 1.4 tool-suite line.
-That keeps 1.3 development incremental and gives every later feature tranche a
-stable package and release environment.
+The first 1.4 work should sit above the frozen Runtime, Source, Compiler, and
+Inspection engines. Command parsing, exit-code policy, and console formatting
+belong in the tool layer rather than enlarging those package contracts.
---
diff --git a/Icod.TermInfo-Post-1.0-Development-Roadmap.md b/Icod.TermInfo-Post-1.0-Development-Roadmap.md
index e7a1648..3826f93 100644
--- a/Icod.TermInfo-Post-1.0-Development-Roadmap.md
+++ b/Icod.TermInfo-Post-1.0-Development-Roadmap.md
@@ -4,14 +4,15 @@
**Stable runtime package:** `Icod.TermInfo`
**Optional source package:** `Icod.TermInfo.Source`
**Optional compiler package:** `Icod.TermInfo.Compiler`
+**Optional inspection package:** `Icod.TermInfo.Inspection`
**Language:** C# 13
**Target frameworks:** `net8.0`; `net9.0`; `net10.0`
**Frozen runtime contract:** `1.0.0`
-**Current release version:** `1.2.0`
-**Current development version:** `1.2.0`
-**Next development line:** `1.3.0`
-**Status:** 1.2.0 release closure
-**Current tranche:** Final 1.2.0 release closure
+**Current release version:** `1.3.0`
+**Current development version:** `1.3.0`
+**Next development line:** `1.4.0`
+**Status:** 1.3.0 release closure
+**Current tranche:** Final 1.3.0 release closure
**Primary objective:** Extend the terminfo ecosystem beyond runtime capability acquisition without destabilizing the frozen 1.0 runtime contract.
---
@@ -36,21 +37,21 @@ Post-1.0 development SHALL build outward from that foundation rather than repeat
The principal new family is **terminfo source and tooling**:
```text
- Icod.TermInfo.Tools
- |
- v
- Icod.TermInfo.Compiler
- / \
- v v
- Icod.TermInfo.Source ---> Icod.TermInfo
- stable runtime
+ Icod.TermInfo.Tools
+ / \
+ v v
+ Icod.TermInfo.Compiler Icod.TermInfo.Inspection
+ | \ / |
+ | \ / |
+ v v v v
+ Icod.TermInfo.Source ---> Icod.TermInfo
+ stable runtime
```
The arrows are dependency arrows. `Icod.TermInfo` remains dependency-free.
-`Icod.TermInfo.Source` depends on `Icod.TermInfo`. The compiler depends directly
-on `Icod.TermInfo`; its Source dependency is introduced only when source
-compilation enters the compiler in C05. No dependency may point from the runtime
-package back toward Source, Compiler, or Tools.
+`Icod.TermInfo.Source` depends on `Icod.TermInfo`. Compiler and Inspection each
+depend on matching Runtime and Source packages. No dependency may point from the
+runtime package back toward Source, Compiler, Inspection, or Tools.
Live-terminal state, terminal input, probing, PTYs, curses presentation, and terminal emulation remain outside this roadmap and belong to their respective packages.
@@ -1293,4 +1294,4 @@ TerminalDescription materialization + corpus
1.5 termcap interoperability
```
-The first implementation target should therefore be **1.1 S01 — `Icod.TermInfo.Source` package foundation**, followed by the lexer/source-location layer. This gives the project a clean new development line while leaving the stable runtime and the ongoing Terminal/DCurses work untouched.
+The 1.1 Source, 1.2 Compiler, and 1.3 Inspection lines are now complete. The next package-family development target is **1.4.0 — Tool Suite**, building the `tic`, `infocmp`, and `toe` command projects on the frozen managed engines without enlarging the Runtime contract.
diff --git a/Icod.TermInfo.Compiler/Icod.TermInfo.Compiler.csproj b/Icod.TermInfo.Compiler/Icod.TermInfo.Compiler.csproj
index fe08d09..fe503fc 100644
--- a/Icod.TermInfo.Compiler/Icod.TermInfo.Compiler.csproj
+++ b/Icod.TermInfo.Compiler/Icod.TermInfo.Compiler.csproj
@@ -12,17 +12,17 @@
Icod.TermInfo.Compiler
Icod.TermInfo.Compiler
Debug;Release;Staging
- 1.3.0-Alpha-5
+ 1.3.0
1.0.0.0
- 1.3.0-Alpha-5
+ 1.3.0
Icod.TermInfo.Compiler
Icod.TermInfo.Compiler
Timothy J. Bruce
Managed deterministic terminfo compiler for Icod.TermInfo. The 1.2 line compiles .ti source or immutable TerminalDescription values and can publish explicit conventional terminfo directory layouts without native ncurses dependencies.
- 1.3.0-Alpha-5 coordinates Compiler with the I05 source-aware comparison engine while preserving the frozen 1.2 compiler public API and completed C01-C07 behavior.
+ 1.3.0 coordinates Compiler with the Inspection release and its managed differential round-trip validation while preserving the frozen 1.2 compiler public API and production dependency graph.
README.md
icon.png
https://github.com/uniblab/Icod.TermInfo
diff --git a/Icod.TermInfo.Compiler/README.md b/Icod.TermInfo.Compiler/README.md
index 85d8ca9..17efe9b 100644
--- a/Icod.TermInfo.Compiler/README.md
+++ b/Icod.TermInfo.Compiler/README.md
@@ -13,15 +13,17 @@ ncurses/`tic` differential validation.
## Install
-For the 1.2.0 release:
+For the 1.3.0 release:
```text
-dotnet add package Icod.TermInfo.Compiler --version 1.2.0
+dotnet add package Icod.TermInfo.Compiler --version 1.3.0
```
The package targets `net8.0`, `net9.0`, and `net10.0` and depends on the matching
-`Icod.TermInfo` and `Icod.TermInfo.Source` packages. The dependency remains
-one-way; neither Source nor Runtime depends on Compiler.
+`Icod.TermInfo` and `Icod.TermInfo.Source` packages. Version 1.3.0 coordinates
+Compiler with the Inspection release without changing the frozen 1.2 Compiler
+public API. The dependency remains one-way; neither Source nor Runtime depends
+on Compiler.
C06 adds `CompiledTermInfoDatabaseWriter`. It never discovers a system database
or installs globally: callers must supply the output root explicitly. It can
diff --git a/Icod.TermInfo.Inspection/Icod.TermInfo.Inspection.csproj b/Icod.TermInfo.Inspection/Icod.TermInfo.Inspection.csproj
index 4502a47..66e6c3e 100644
--- a/Icod.TermInfo.Inspection/Icod.TermInfo.Inspection.csproj
+++ b/Icod.TermInfo.Inspection/Icod.TermInfo.Inspection.csproj
@@ -12,17 +12,17 @@
Icod.TermInfo.Inspection
Icod.TermInfo.Inspection
Debug;Release;Staging
- 1.3.0-Alpha-5
+ 1.3.0
1.0.0.0
- 1.3.0-Alpha-5
+ 1.3.0
Icod.TermInfo.Inspection
Icod.TermInfo.Inspection
Timothy J. Bruce
Managed inspection and semantic-comparison foundation for Icod.TermInfo. The 1.3 line provides canonical terminfo rendering and reusable infocmp-style comparison engines without enlarging the frozen Runtime, Source, or Compiler public contracts.
- 1.3.0-Alpha-5 implements I05: deterministic machine-readable comparison of unresolved Source entries/documents, including ordered fields, use= references, cancellation/disabled states, duplicates, source spans, and source-versus-effective semantic distinction.
+ 1.3.0 introduces the optional Inspection layer with canonical effective and unresolved-source rendering, structured semantic comparison, provider-aware inspection, deterministic cross-platform behavior, and no production Compiler dependency.
README.md
icon.png
https://github.com/uniblab/Icod.TermInfo
diff --git a/Icod.TermInfo.Inspection/README.md b/Icod.TermInfo.Inspection/README.md
index b0e3e83..e2a5680 100644
--- a/Icod.TermInfo.Inspection/README.md
+++ b/Icod.TermInfo.Inspection/README.md
@@ -5,7 +5,87 @@ comparison layer for the `Icod.TermInfo` package family.
The 1.3 line provides the reusable API engine underneath future
`infocmp`-style tooling while preserving the already-frozen Runtime 1.0, Source
-1.1, and Compiler 1.2 public contracts.
+1.1, and Compiler 1.2 public contracts. Version 1.3.0 is the first stable
+release of this optional package.
+
+## I07 differential validation, robustness, and API/package freeze
+
+`1.3.0-Alpha-7` closes the 1.3 implementation program without adding another
+production API surface. The reviewed I02-I06 Inspection API is now the candidate
+1.3 contract, and the existing public API baseline is treated as frozen for
+release closure.
+
+I07 adds cross-layer validation which deliberately uses
+`Icod.TermInfo.Compiler` only from the Inspection test project. Effective terminal
+descriptions are rendered through Inspection, compiled from the resulting Source,
+parsed back through Runtime, and compared semantically. The production package
+continues to depend only on Source and Runtime:
+
+```text
+Inspection -> Source -> Runtime
+Inspection ----------> Runtime
+```
+
+The validation corpus covers every built-in profile, the pinned T29 compiled
+fixtures, and the checked-in Source corpus. It also locks exact wrapping
+boundaries, culture-independent and insertion-order-independent comparison
+ordering, source cancellation/disabled/`use=`/duplicate sequencing, and the
+four-package release boundary.
+
+Ordinary CI remains independent of a host ncurses installation. Differential
+evidence is semantic: I07 does not claim byte-for-byte formatting identity with
+`infocmp`, and it does not change the existing Runtime, Source, or Compiler public
+contracts.
+
+## I06 provider-aware inspection and reusable `infocmp` engine
+
+`1.3.0-Alpha-6` composes the existing Runtime acquisition contract with the I02
+canonical renderer and I04 effective comparer. An inspection target contains an
+explicit `ITerminalDescriptionProvider`, the exact requested terminal name, and
+an optional caller-owned display label:
+
+```csharp
+TermInfoInspectionTarget target =
+ new(
+ provider,
+ "xterm",
+ "system xterm"
+ );
+
+TermInfoInspectionResult inspected =
+ TermInfoInspectionEngine.Inspect(
+ target
+ );
+```
+
+`TryInspect` preserves the Runtime provider contract's clean-miss semantics;
+provider exceptions continue to propagate. Successful results retain both the
+requested target identity and the provider-returned canonical
+`TerminalDescription`, so aliases do not erase what the caller actually asked
+for.
+
+The engine can render a target or an already acquired result and can compare two
+targets or two acquired results:
+
+```csharp
+TermInfoInspectionComparison comparison =
+ TermInfoInspectionEngine.Compare(
+ leftTarget,
+ rightTarget
+ );
+```
+
+The comparison retains both target/result identities together with the I04
+`TermInfoComparisonResult`. Already acquired results are never reacquired when
+rendered or compared.
+
+The optional display label is caller-owned diagnostic context only. I06 does not
+enumerate providers, expose private system-discovery internals, infer the exact
+compiled database path used by a provider, or add command-line/console-output
+policy. `SystemTerminalDescriptionProvider`, separate
+`DirectoryTerminalDescriptionProvider` roots, `TerminalDatabase.BuiltIn`, and
+caller-defined providers all participate through the same frozen Runtime
+interface.
## I05 source-aware comparison
@@ -146,10 +226,10 @@ There is no production dependency between Inspection and Compiler.
## Install
-During I05 development:
+For the 1.3.0 release:
```text
-dotnet add package Icod.TermInfo.Inspection --version 1.3.0-Alpha-5
+dotnet add package Icod.TermInfo.Inspection --version 1.3.0
```
The package targets `net8.0`, `net9.0`, and `net10.0`, uses C# 13, remains
diff --git a/Icod.TermInfo.Inspection/src/TermInfoInspectionComparison.cs b/Icod.TermInfo.Inspection/src/TermInfoInspectionComparison.cs
new file mode 100644
index 0000000..c71e67f
--- /dev/null
+++ b/Icod.TermInfo.Inspection/src/TermInfoInspectionComparison.cs
@@ -0,0 +1,42 @@
+namespace Icod.TermInfo.Inspection;
+
+///
+/// Couples an effective semantic comparison with the two explicitly acquired
+/// inspection results that produced it.
+///
+public sealed class TermInfoInspectionComparison {
+ internal TermInfoInspectionComparison(
+ TermInfoInspectionResult left,
+ TermInfoInspectionResult right,
+ TermInfoComparisonResult comparison
+ ) {
+ ArgumentNullException.ThrowIfNull( left );
+ ArgumentNullException.ThrowIfNull( right );
+ ArgumentNullException.ThrowIfNull( comparison );
+
+ Left = left;
+ Right = right;
+ Comparison = comparison;
+ }
+
+ ///
+ /// Gets the acquired left target and terminal.
+ ///
+ public TermInfoInspectionResult Left { get; }
+
+ ///
+ /// Gets the acquired right target and terminal.
+ ///
+ public TermInfoInspectionResult Right { get; }
+
+ ///
+ /// Gets the deterministic effective semantic comparison.
+ ///
+ public TermInfoComparisonResult Comparison { get; }
+
+ ///
+ /// Gets whether the acquired effective descriptions are semantically equal.
+ ///
+ public bool AreEqual =>
+ Comparison.AreEqual;
+}
diff --git a/Icod.TermInfo.Inspection/src/TermInfoInspectionEngine.cs b/Icod.TermInfo.Inspection/src/TermInfoInspectionEngine.cs
new file mode 100644
index 0000000..e169695
--- /dev/null
+++ b/Icod.TermInfo.Inspection/src/TermInfoInspectionEngine.cs
@@ -0,0 +1,174 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace Icod.TermInfo.Inspection;
+
+///
+/// Composes explicit provider acquisition, canonical effective rendering, and
+/// effective semantic comparison for reusable infocmp-style workflows.
+///
+///
+/// The engine consumes the frozen Runtime provider contract. Clean provider
+/// misses remain distinguishable through ; provider
+/// exceptions propagate. The engine does not enumerate providers or infer hidden
+/// system-database provenance.
+///
+public static class TermInfoInspectionEngine {
+ ///
+ /// Attempts to acquire one explicit inspection target.
+ ///
+ /// The provider/name target to acquire.
+ ///
+ /// The acquired target/result pair on success; otherwise
+ /// .
+ ///
+ ///
+ /// when the provider resolves the requested terminal;
+ /// otherwise for a clean provider miss.
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// The provider violates its contract by reporting success with a
+ /// terminal description.
+ ///
+ public static bool TryInspect(
+ TermInfoInspectionTarget target,
+ [NotNullWhen( true )] out TermInfoInspectionResult? result
+ ) {
+ ArgumentNullException.ThrowIfNull( target );
+
+ if ( !target.Provider.TryLoad(
+ target.RequestedName,
+ out TerminalDescription? terminal
+ ) ) {
+ result = null;
+ return false;
+ }
+
+ if ( terminal is null ) {
+ throw new InvalidOperationException(
+ $"Terminal provider '{target.Provider.GetType().FullName}' returned success without a terminal description."
+ );
+ }
+
+ result =
+ new TermInfoInspectionResult(
+ target,
+ terminal
+ );
+ return true;
+ }
+
+ ///
+ /// Acquires one explicit inspection target.
+ ///
+ /// The provider/name target to acquire.
+ /// The acquired target and effective terminal description.
+ ///
+ /// is .
+ ///
+ ///
+ /// The provider reports a clean miss for the requested terminal name.
+ ///
+ public static TermInfoInspectionResult Inspect(
+ TermInfoInspectionTarget target
+ ) {
+ ArgumentNullException.ThrowIfNull( target );
+
+ if ( TryInspect(
+ target,
+ out TermInfoInspectionResult? result
+ ) ) {
+ return result;
+ }
+
+ throw new KeyNotFoundException(
+ $"Terminal profile '{target.RequestedName}' is not available from inspection target '{target.DisplayName}'."
+ );
+ }
+
+ ///
+ /// Acquires and canonically renders one explicit inspection target.
+ ///
+ /// The provider/name target to acquire and render.
+ /// The canonical effective terminfo source representation.
+ public static string Render(
+ TermInfoInspectionTarget target
+ ) {
+ ArgumentNullException.ThrowIfNull( target );
+
+ return Render(
+ Inspect(
+ target
+ )
+ );
+ }
+
+ ///
+ /// Canonically renders an already acquired inspection result without
+ /// reacquiring it from its provider.
+ ///
+ /// The acquired result to render.
+ /// The canonical effective terminfo source representation.
+ public static string Render(
+ TermInfoInspectionResult result
+ ) {
+ ArgumentNullException.ThrowIfNull( result );
+
+ return TerminalDescriptionSourceRenderer.Render(
+ result.Terminal
+ );
+ }
+
+ ///
+ /// Acquires and compares two explicit inspection targets.
+ ///
+ /// The left provider/name target.
+ /// The right provider/name target.
+ ///
+ /// The acquired left/right identities and their effective semantic comparison.
+ ///
+ public static TermInfoInspectionComparison Compare(
+ TermInfoInspectionTarget left,
+ TermInfoInspectionTarget right
+ ) {
+ ArgumentNullException.ThrowIfNull( left );
+ ArgumentNullException.ThrowIfNull( right );
+
+ return Compare(
+ Inspect(
+ left
+ ),
+ Inspect(
+ right
+ )
+ );
+ }
+
+ ///
+ /// Compares two already acquired inspection results without reacquiring either
+ /// terminal from its provider.
+ ///
+ /// The acquired left result.
+ /// The acquired right result.
+ ///
+ /// The original left/right results and their effective semantic comparison.
+ ///
+ public static TermInfoInspectionComparison Compare(
+ TermInfoInspectionResult left,
+ TermInfoInspectionResult right
+ ) {
+ ArgumentNullException.ThrowIfNull( left );
+ ArgumentNullException.ThrowIfNull( right );
+
+ return new TermInfoInspectionComparison(
+ left,
+ right,
+ TerminalDescriptionComparer.Compare(
+ left.Terminal,
+ right.Terminal
+ )
+ );
+ }
+}
diff --git a/Icod.TermInfo.Inspection/src/TermInfoInspectionResult.cs b/Icod.TermInfo.Inspection/src/TermInfoInspectionResult.cs
new file mode 100644
index 0000000..4ac6e68
--- /dev/null
+++ b/Icod.TermInfo.Inspection/src/TermInfoInspectionResult.cs
@@ -0,0 +1,28 @@
+namespace Icod.TermInfo.Inspection;
+
+///
+/// Couples one successfully acquired terminal description with the explicit
+/// inspection target that produced it.
+///
+public sealed class TermInfoInspectionResult {
+ internal TermInfoInspectionResult(
+ TermInfoInspectionTarget target,
+ TerminalDescription terminal
+ ) {
+ ArgumentNullException.ThrowIfNull( target );
+ ArgumentNullException.ThrowIfNull( terminal );
+
+ Target = target;
+ Terminal = terminal;
+ }
+
+ ///
+ /// Gets the explicit provider/name target used for acquisition.
+ ///
+ public TermInfoInspectionTarget Target { get; }
+
+ ///
+ /// Gets the acquired immutable effective terminal description.
+ ///
+ public TerminalDescription Terminal { get; }
+}
diff --git a/Icod.TermInfo.Inspection/src/TermInfoInspectionTarget.cs b/Icod.TermInfo.Inspection/src/TermInfoInspectionTarget.cs
new file mode 100644
index 0000000..8baa1a8
--- /dev/null
+++ b/Icod.TermInfo.Inspection/src/TermInfoInspectionTarget.cs
@@ -0,0 +1,76 @@
+namespace Icod.TermInfo.Inspection;
+
+///
+/// Identifies one explicit provider/name pair to inspect.
+///
+///
+/// The optional display label is caller-owned diagnostic context. It does not
+/// assert provider provenance and is never inferred from provider internals.
+///
+public sealed class TermInfoInspectionTarget {
+ ///
+ /// Initializes an explicit inspection target.
+ ///
+ /// The provider used to acquire the terminal.
+ /// The exact terminal name requested from the provider.
+ ///
+ /// Optional caller-owned text used to identify the target in diagnostics.
+ ///
+ ///
+ /// or is
+ /// .
+ ///
+ ///
+ /// is empty or whitespace, or
+ /// is supplied as empty or whitespace.
+ ///
+ public TermInfoInspectionTarget(
+ ITerminalDescriptionProvider provider,
+ string requestedName,
+ string? displayLabel = null
+ ) {
+ ArgumentNullException.ThrowIfNull( provider );
+ ArgumentNullException.ThrowIfNull( requestedName );
+
+ if ( string.IsNullOrWhiteSpace( requestedName ) ) {
+ throw new ArgumentException(
+ "The requested terminal name cannot be empty or whitespace.",
+ nameof( requestedName )
+ );
+ }
+ if ( displayLabel is not null
+ && string.IsNullOrWhiteSpace( displayLabel ) ) {
+ throw new ArgumentException(
+ "The display label cannot be empty or whitespace when supplied.",
+ nameof( displayLabel )
+ );
+ }
+
+ Provider = provider;
+ RequestedName = requestedName;
+ DisplayLabel = displayLabel;
+ }
+
+ ///
+ /// Gets the explicit terminal-description provider.
+ ///
+ public ITerminalDescriptionProvider Provider { get; }
+
+ ///
+ /// Gets the exact name requested from .
+ ///
+ public string RequestedName { get; }
+
+ ///
+ /// Gets the optional caller-owned display/source label.
+ ///
+ public string? DisplayLabel { get; }
+
+ ///
+ /// Gets the caller-owned display label when present, otherwise the requested
+ /// terminal name.
+ ///
+ public string DisplayName =>
+ DisplayLabel
+ ?? RequestedName;
+}
diff --git a/Icod.TermInfo.Source/Icod.TermInfo.Source.csproj b/Icod.TermInfo.Source/Icod.TermInfo.Source.csproj
index 879f59c..f7823a1 100644
--- a/Icod.TermInfo.Source/Icod.TermInfo.Source.csproj
+++ b/Icod.TermInfo.Source/Icod.TermInfo.Source.csproj
@@ -12,17 +12,17 @@
Icod.TermInfo.Source
Icod.TermInfo.Source
Debug;Release;Staging
- 1.3.0-Alpha-5
+ 1.3.0
1.0.0.0
- 1.3.0-Alpha-5
+ 1.3.0
Icod.TermInfo.Source
Icod.TermInfo.Source
Timothy J. Bruce
Managed terminfo source-language support for Icod.TermInfo. The 1.1 line parses and resolves .ti source into the stable TerminalDescription model without enlarging the runtime Icod.TermInfo package.
- 1.3.0-Alpha-5 coordinates Source with the I05 source-aware comparison engine while preserving the frozen 1.1 source-language public API, semantics, and 1.0.0.0 assembly identity.
+ 1.3.0 coordinates Source with the Inspection release while preserving the frozen 1.1 source-language public API, semantics, and 1.0.0.0 assembly identity.
README.md
icon.png
https://github.com/uniblab/Icod.TermInfo
diff --git a/Icod.TermInfo.Source/README.md b/Icod.TermInfo.Source/README.md
index e45ed3b..625a0b5 100644
--- a/Icod.TermInfo.Source/README.md
+++ b/Icod.TermInfo.Source/README.md
@@ -9,16 +9,17 @@ values continue to reference `Icod.TermInfo` alone.
## Install
-For the 1.2.0 release:
+For the 1.3.0 release:
```text
-dotnet add package Icod.TermInfo.Source --version 1.2.0
+dotnet add package Icod.TermInfo.Source --version 1.3.0
```
The package depends on the matching `Icod.TermInfo` version and targets
`net8.0`, `net9.0`, and `net10.0`.
-Version 1.2.0 coordinates Source with the 1.2 compiler release and does not change the frozen 1.1 source-language public API or semantics.
+Version 1.3.0 coordinates Source with the Inspection release and does not change
+the frozen 1.1 source-language public API or semantics.
## What the 1.1 line provides
diff --git a/Icod.TermInfo.csproj b/Icod.TermInfo.csproj
index 00201e7..839d3de 100644
--- a/Icod.TermInfo.csproj
+++ b/Icod.TermInfo.csproj
@@ -12,16 +12,16 @@
Icod.TermInfo
Icod.TermInfo
Debug;Release;Staging
- 1.3.0-Alpha-5
+ 1.3.0
1.0.0.0
- 1.3.0-Alpha-5
+ 1.3.0
Icod.TermInfo
Icod.TermInfo
Timothy J. Bruce
Managed, dependency-free .NET terminfo library for terminal capability metadata, compiled terminfo parsing and database discovery, ANSI/DEC/xterm and Windows profiles, parameter expansion, color semantics, and padding-aware output.
- 1.3.0-Alpha-5 coordinates Runtime with the I05 source-aware comparison engine while preserving the frozen runtime public contract, dependency-free runtime package, and 1.0.0.0 assembly identity.
+ 1.3.0 coordinates Runtime with the new optional Icod.TermInfo.Inspection package while preserving the frozen 1.0 runtime public API, dependency-free runtime package, and 1.0.0.0 assembly identity.
README.md
icon.png
https://github.com/uniblab/Icod.TermInfo
diff --git a/README.md b/README.md
index 5538bc8..af92913 100644
--- a/README.md
+++ b/README.md
@@ -2,56 +2,64 @@
`Icod.TermInfo` is a managed, dependency-free .NET implementation of the low-level terminal-capability model traditionally supplied by `libtinfo`.
-Version 1.2.0 is the current coordinated release. It preserves the frozen 1.0
-runtime contract and frozen 1.1 Source contract while adding the optional
-`Icod.TermInfo.Compiler` package.
+Version 1.3.0 is the current coordinated release. It preserves the frozen 1.0
+Runtime, 1.1 Source, and 1.2 Compiler contracts while adding the optional
+`Icod.TermInfo.Inspection` package.
-The published 1.2.0 package family targets `net8.0`, `net9.0`, and `net10.0`;
+The published 1.3.0 package family targets `net8.0`, `net9.0`, and `net10.0`;
the packages use C# 13, contain no native ncurses/terminfo payload, and are
intended to run on Windows, Linux, and macOS.
## Install
-For the 1.2.0 release, runtime-only consumers use:
+For the 1.3.0 release, runtime-only consumers use:
```text
-dotnet add package Icod.TermInfo --version 1.2.0
+dotnet add package Icod.TermInfo --version 1.3.0
```
Applications which need terminfo source-language support use:
```text
-dotnet add package Icod.TermInfo.Source --version 1.2.0
+dotnet add package Icod.TermInfo.Source --version 1.3.0
```
Applications which compile terminfo source or write conventional compiled
terminfo databases use:
```text
-dotnet add package Icod.TermInfo.Compiler --version 1.2.0
+dotnet add package Icod.TermInfo.Compiler --version 1.3.0
+```
+
+Applications which need canonical rendering, semantic comparison, or
+provider-aware inspection use:
+
+```text
+dotnet add package Icod.TermInfo.Inspection --version 1.3.0
```
`Icod.TermInfo.Source` depends on the matching `Icod.TermInfo` package.
-`Icod.TermInfo.Compiler` depends on the matching Runtime and Source packages.
+`Icod.TermInfo.Compiler` and `Icod.TermInfo.Inspection` each depend on the
+matching Runtime and Source packages; Inspection does not depend on Compiler.
Applications which only load compiled terminfo or consume `TerminalDescription`
values continue to reference `Icod.TermInfo` alone.
The same validated package artifacts are published to NuGet.org and GitHub
Packages. Release closure and tag-publication requirements are recorded in
-`docs/1.2.0-RELEASE-AUDIT.md`.
+`docs/1.3.0-RELEASE-AUDIT.md`.
## 1.x stability contract
The 1.x line keeps runtime assembly identity `Icod.TermInfo, Version=1.0.0.0` and
remains unsigned. The frozen 1.0 and 1.1 releases support `net8.0` and
`net10.0`; beginning with 1.2, the supported consumer targets are `net8.0`,
-`net9.0`, and `net10.0`. `Icod.TermInfo.Source` and `Icod.TermInfo.Compiler`
-retain assembly version `1.0.0.0` throughout their 1.x lines. Public API,
-binary/package compatibility,
-deprecation, and target-framework policy are documented in `docs/VERSIONING.md`
-and `docs/COMPATIBILITY.md`.
+`net9.0`, and `net10.0`. `Icod.TermInfo.Source`, `Icod.TermInfo.Compiler`, and
+`Icod.TermInfo.Inspection` retain assembly version `1.0.0.0` throughout their
+1.x lines. Public API, binary/package compatibility, deprecation, and
+target-framework policy are documented in `docs/VERSIONING.md` and
+`docs/COMPATIBILITY.md`.
-The runtime 1.0 public API remains frozen. Version 1.1 adds source-language functionality in the separate `Icod.TermInfo.Source` package rather than making the runtime package depend on parser/front-end code. The 1.2 line adds deterministic compiled-entry writing in the separate `Icod.TermInfo.Compiler` package. Live terminal sessions, PTYs, curses/UI, terminal emulation, command-line `tic`/`infocmp`/`toe` tooling, termcap conversion, and active protocol negotiation remain later or sibling work.
+The runtime 1.0 public API remains frozen. Version 1.1 adds source-language functionality in the separate `Icod.TermInfo.Source` package rather than making the runtime package depend on parser/front-end code. The 1.2 line adds deterministic compiled-entry writing in the separate `Icod.TermInfo.Compiler` package. The 1.3 line adds canonical rendering and semantic comparison in the separate `Icod.TermInfo.Inspection` package. Live terminal sessions, PTYs, curses/UI, terminal emulation, command-line `tic`/`infocmp`/`toe` tooling, termcap conversion, and active protocol negotiation remain later or sibling work.
## What 1.0 provides
@@ -113,6 +121,28 @@ the existing compiled-term acquisition path:
Compiler remains opt-in. The Runtime package remains dependency-free, Source
depends only on Runtime, and Compiler depends on Runtime and Source.
+## What 1.3 adds
+
+The optional `Icod.TermInfo.Inspection` package adds reusable inspection and
+comparison engines without enlarging the already-frozen Runtime, Source, or
+Compiler public contracts:
+
+- canonical `.ti`-style rendering of effective `TerminalDescription` values;
+- normalized rendering of unresolved Source entries and documents while
+ preserving semantically significant field order;
+- deterministic structured comparison of effective terminal descriptions;
+- source-aware comparison which preserves cancellation, disabled fields,
+ duplicate declarations, `use=` references, and source ordering;
+- provider-aware inspection through explicit provider/name targets;
+- deterministic comparison ordering across cultures and extended-capability
+ insertion order;
+- corpus-backed managed render/compile/parse/compare validation across the
+ existing Runtime, Source, and Compiler layers.
+
+Inspection remains opt-in. It depends on matching Runtime and Source packages
+and deliberately has no production dependency on Compiler. Command-line
+`infocmp` policy remains future tooling work.
+
## Getting started
Terminal resolution remains explicit and conservative. A normal application can
@@ -639,7 +669,7 @@ purposes.
- redirection handling and explicit Windows VT enablement;
- a custom provider implementation.
-Both sample projects in the 1.2 development line target `net8.0`, `net9.0`,
+Both sample projects in the 1.3 package family target `net8.0`, `net9.0`,
and `net10.0`; `dotnet run` therefore needs an explicit framework. Run the
ordinary demonstration with:
@@ -697,13 +727,15 @@ See `samples/README.md`,
## Project-family boundary
-`Icod.TermInfo` owns immutable terminal-description data, acquisition of that data, and pure transformations required to interpret, expand, and output terminal capabilities. `Icod.TermInfo.Source` owns optional source-language parsing and inheritance resolution. Neither package owns a live terminal session, a child pseudo-terminal, or a virtual screen.
+`Icod.TermInfo` owns immutable terminal-description data, acquisition of that data, and pure transformations required to interpret, expand, and output terminal capabilities. `Icod.TermInfo.Source` owns optional source-language parsing and inheritance resolution, `Icod.TermInfo.Compiler` owns compiled output, and `Icod.TermInfo.Inspection` owns canonical rendering and semantic comparison. None of those packages owns a live terminal session, a child pseudo-terminal, or a virtual screen.
The intended family boundary is now explicit:
- **`Icod.TermInfo`** — descriptions, compiled-database acquisition, capability semantics, parameter expansion, and output transformation;
- **`Icod.TermInfo.Source`** — `.ti` lexical analysis, source diagnostics, unresolved entries, cancellation, `use=` inheritance, and materialization into `TerminalDescription`;
-- **future `Icod.TermInfo.Compiler` / tools** — compiled-entry writing, `tic`/`infocmp`/`toe` engines and commands, termcap conversion, and optional database-maintenance functionality;
+- **`Icod.TermInfo.Compiler`** — deterministic compiled-entry writing, source compilation, and explicit conventional database-layout publication;
+- **`Icod.TermInfo.Inspection`** — canonical effective/source rendering, structured semantic comparison, and provider-aware inspection;
+- **future tools** — `tic`, `infocmp`, and `toe` command applications plus later termcap conversion and optional database-maintenance functionality;
- **future `Icod.Terminal`** — raw/cooked session ownership, input decoding, keyboard/mouse/paste/focus events, active probing/negotiation, full-screen/cursor lifecycle, clipboard/hyperlink operations, and progress helpers;
- **future `Icod.Pty`** — Unix PTY and Windows ConPTY creation, resize propagation, and child-process plumbing;
- **future `Icod.Curses`** — Unicode cell/grid state, damage/refresh optimization, windows, pads, panels, menus, forms, and widgets.
@@ -756,11 +788,15 @@ dotnet build Icod.TermInfo.sln -c Staging
dotnet test Icod.TermInfo.sln -c Staging
dotnet pack Icod.TermInfo.csproj -c Staging --output artifacts
dotnet pack Icod.TermInfo.Source/Icod.TermInfo.Source.csproj -c Staging --output artifacts
+dotnet pack Icod.TermInfo.Compiler/Icod.TermInfo.Compiler.csproj -c Staging --output artifacts
+dotnet pack Icod.TermInfo.Inspection/Icod.TermInfo.Inspection.csproj -c Staging --output artifacts
dotnet build Icod.TermInfo.sln -c Release
dotnet test Icod.TermInfo.sln -c Release
dotnet pack Icod.TermInfo.csproj -c Release --output artifacts
dotnet pack Icod.TermInfo.Source/Icod.TermInfo.Source.csproj -c Release --output artifacts
+dotnet pack Icod.TermInfo.Compiler/Icod.TermInfo.Compiler.csproj -c Release --output artifacts
+dotnet pack Icod.TermInfo.Inspection/Icod.TermInfo.Inspection.csproj -c Release --output artifacts
```
Use the verifier with the same configuration used to build and pack.
@@ -779,35 +815,35 @@ For final Release validation:
bash .github/scripts/verify-release-package.sh artifacts Release
```
-Both wrappers run the same capability-metadata check; exact runtime and Source
-public-API baseline checks; net8/net9/net10 API-equivalence checks; runtime
-package/XML/symbol validation; isolated runtime and Source package-reference-only
-smoke consumers on all three target frameworks; and the sample's non-interactive
+Both wrappers run the coordinated four-package release verifier: generated
+capability metadata, all four public-API baselines, net8/net9/net10 API
+equivalence, package/XML/symbol/dependency validation, all four isolated
+package-reference-only smoke consumers, and the sample's non-interactive
`--describe-only` path. Windows package validation does not require Bash or
Python.
-Pull requests use Staging throughout, may upload the verified `.nupkg` and
-`.snupkg` artifacts for both packages, and never publish packages. Only pushes to
-`main` run the Release build/test/package-validation/publication workflow.
+Pull requests use Staging throughout and may upload verified package artifacts,
+but never publish. Pushes to `main` run the non-publishing Release validation
+matrix. Only an immutable `v*` tag matching the coordinated package version may
+start registry publication through `.github/workflows/release.yaml`.
See `docs/RELEASING.md` for the release procedure,
-`Icod.TermInfo-Development-Roadmap-1.0.0.md` for the frozen runtime contract,
-`Icod.TermInfo-Post-1.0-Development-Roadmap.md` for the 1.1 source-language
-program, and `docs/1.1.0-RELEASE-AUDIT.md` for the final release gate. Tag
-`v1.1.1` only on the exact `main` commit whose complete Release validation and
-publication succeeded; no source or package content may change between that
-validation and tagging.
+`Icod.TermInfo-1.3.0-Inspection-and-Comparison-Roadmap.md` for the completed
+I01-I07 contract, and `docs/1.3.0-RELEASE-AUDIT.md` for the final 1.3.0 release
+gate. Tag `v1.3.0` only on the exact validated `main` commit; no source or
+package content may change between that validation and tagging.
## Scope
-`Icod.TermInfo` is not curses, a terminal emulator, a PTY implementation, a termios session manager, an input-event parser, or a general terminal UI toolkit. It intentionally carries low-level descriptive data which those higher-level systems may consume. `Icod.TermInfo.Source` is an optional parser/resolver layer and does not change those runtime boundaries.
+`Icod.TermInfo` is not curses, a terminal emulator, a PTY implementation, a termios session manager, an input-event parser, or a general terminal UI toolkit. It intentionally carries low-level descriptive data which those higher-level systems may consume. Source, Compiler, and Inspection remain optional sibling layers and do not change those runtime boundaries.
See `Icod.TermInfo-Development-Roadmap-0.9.0.md` for the frozen acquisition
contract, `Icod.TermInfo-Development-Roadmap-1.0.0.md` for the 1.0 runtime
-stability contract, `Icod.TermInfo-Post-1.0-Development-Roadmap.md` for the 1.1
-source-language program, `docs/VERSIONING.md` and `docs/COMPATIBILITY.md` for the
-1.x promises, and `docs/FUTURE-WORK-INVENTORY.md` for the broader terminal-system
-dependency map. The 0.6.0 through 1.0.0 roadmaps remain historical frozen
+stability contract, `Icod.TermInfo-Post-1.0-Development-Roadmap.md` for the
+post-1.0 package-family sequence, `Icod.TermInfo-1.3.0-Inspection-and-Comparison-Roadmap.md`
+for the 1.3 Inspection contract, `docs/VERSIONING.md` and
+`docs/COMPATIBILITY.md` for the 1.x promises, and
+`docs/FUTURE-WORK-INVENTORY.md` for the broader terminal-system dependency map. The 0.6.0 through 1.0.0 roadmaps remain historical frozen
contracts.
## Authors
diff --git a/docs/1.3.0-I06-PROVIDER-AWARE-INSPECTION.md b/docs/1.3.0-I06-PROVIDER-AWARE-INSPECTION.md
new file mode 100644
index 0000000..7ed1c60
--- /dev/null
+++ b/docs/1.3.0-I06-PROVIDER-AWARE-INSPECTION.md
@@ -0,0 +1,153 @@
+# Icod.TermInfo 1.3.0 I06 — Provider-aware inspection
+
+**Development version:** `1.3.0-Alpha-6`
+
+I06 composes the frozen Runtime provider contract with the Inspection render and
+comparison engines so applications can implement common `infocmp`-style
+operations without duplicating acquisition glue.
+
+## Public surface
+
+I06 adds four public Inspection types:
+
+```text
+TermInfoInspectionTarget
+TermInfoInspectionResult
+TermInfoInspectionComparison
+TermInfoInspectionEngine
+```
+
+`TermInfoInspectionTarget` contains exactly the identity the caller knows before
+acquisition:
+
+- an explicit `ITerminalDescriptionProvider`;
+- the exact requested terminal name;
+- an optional caller-owned display label.
+
+The display label is diagnostic context, not provider provenance. When omitted,
+`DisplayName` falls back to the requested terminal name.
+
+`TermInfoInspectionResult` couples a successfully acquired
+`TerminalDescription` with its original target. This deliberately preserves a
+requested alias independently from the canonical name returned by the provider.
+
+`TermInfoInspectionComparison` retains both acquired results together with the
+I04 `TermInfoComparisonResult`, allowing a future CLI layer to explain which two
+explicit targets produced a semantic comparison without parsing renderer output.
+
+## Acquisition semantics
+
+`TermInfoInspectionEngine.TryInspect` preserves the existing Runtime provider
+contract:
+
+- provider success produces a `TermInfoInspectionResult`;
+- a clean provider miss returns `false` and a null result;
+- provider exceptions propagate unchanged.
+
+`Inspect` is the convenience form for callers that require a result. It converts
+a clean miss into `KeyNotFoundException` while leaving provider failures
+untouched.
+
+The engine does not catch malformed compiled-data exceptions, I/O errors,
+permission failures, or other provider-specific failures. Those remain distinct
+from a clean miss exactly as they are at the Runtime provider boundary.
+
+## Rendering
+
+The target overload:
+
+```csharp
+string source =
+ TermInfoInspectionEngine.Render(
+ target
+ );
+```
+
+acquires the target and delegates to the I02 canonical effective renderer.
+
+The acquired-result overload:
+
+```csharp
+string source =
+ TermInfoInspectionEngine.Render(
+ inspected
+ );
+```
+
+does not invoke the provider again.
+
+## Effective comparison
+
+Two explicit targets can be acquired and compared in one operation:
+
+```csharp
+TermInfoInspectionComparison comparison =
+ TermInfoInspectionEngine.Compare(
+ leftTarget,
+ rightTarget
+ );
+```
+
+The result contains:
+
+- the acquired left target and terminal;
+- the acquired right target and terminal;
+- the deterministic I04 effective semantic comparison.
+
+An overload accepts two already acquired `TermInfoInspectionResult` values and
+does not reacquire either provider.
+
+I06 does not alter I05 source-aware comparison. Callers continue to choose the
+comparison domain explicitly:
+
+```text
+TerminalDescriptionComparer / InspectionEngine.Compare
+ effective terminal semantics
+
+TermInfoSourceComparer
+ unresolved source-program structure
+```
+
+## Provider scenarios
+
+No provider-specific branch exists in the engine. The same API therefore covers:
+
+```text
+built-in xterm vs system xterm
+system xterm vs explicit directory xterm
+directory root A vs directory root B
+xterm-256color vs screen-256color
+caller provider A vs caller provider B
+```
+
+`SystemTerminalDescriptionProvider`, `DirectoryTerminalDescriptionProvider`,
+`TerminalDatabase.BuiltIn`, `InMemoryTerminalDescriptionProvider`, and custom
+providers all participate through `ITerminalDescriptionProvider`.
+
+## Deliberate non-goals
+
+I06 does not add:
+
+- provider enumeration;
+- provider search-path enumeration;
+- an API exposing which private system directory ultimately satisfied a lookup;
+- synthetic source-file/database provenance;
+- command-line parsing;
+- `infocmp` switch compatibility;
+- console formatting or exit-code policy;
+- `toe`-style enumeration.
+
+A caller that knows a meaningful label such as `system`, `database A`, or an
+explicit path may attach it to the target. Inspection preserves that label but
+does not manufacture one from hidden Runtime state.
+
+## Contract preservation
+
+I06 enlarges only `Icod.TermInfo.Inspection`.
+
+The frozen Runtime 1.0, Source 1.1, and Compiler 1.2 public API baselines remain
+unchanged. There is still no production dependency from Inspection to Compiler
+and no reverse dependency from Runtime, Source, or Compiler to Inspection.
+
+All four coordinated packages advance to `1.3.0-Alpha-6` while retaining
+`AssemblyVersion` `1.0.0.0`.
diff --git a/docs/1.3.0-I07-DIFFERENTIAL-VALIDATION-AND-FREEZE.md b/docs/1.3.0-I07-DIFFERENTIAL-VALIDATION-AND-FREEZE.md
new file mode 100644
index 0000000..57ac399
--- /dev/null
+++ b/docs/1.3.0-I07-DIFFERENTIAL-VALIDATION-AND-FREEZE.md
@@ -0,0 +1,121 @@
+# Icod.TermInfo 1.3.0 I07 — Differential Validation, Robustness, and API/Package Freeze
+
+**Tranche:** I07
+**Development version:** `1.3.0-Alpha-7`
+**Reference branch:** `1.3.0`
+**Reference commit:** `24e7da158bb7a2387631a9cd4a30ada001b9b8e3`
+**Assembly version:** `1.0.0.0`
+
+## Purpose
+
+I07 closes the 1.3 implementation program by validating the Inspection layer as
+one deterministic cross-package system and freezing the candidate 1.3 package
+and public API contract. It deliberately adds no new production Inspection API.
+
+The production dependency graph remains:
+
+```text
+Inspection -> Source -> Runtime
+Inspection ----------> Runtime
+
+Compiler -> Source -> Runtime
+Compiler ------------> Runtime
+```
+
+There is still no production dependency between Inspection and Compiler.
+`Icod.TermInfo.Inspection.Tests` acquires a test-only Compiler project reference
+so I07 can exercise a complete managed render/compile/parse/compare round trip.
+
+## Differential and corpus validation
+
+I07 adds end-to-end validation over both sides of the 1.3 inspection model.
+
+For effective terminal descriptions, every built-in Runtime profile and every
+checked-in T29 compiled fixture follows this path:
+
+```text
+TerminalDescription
+ -> TerminalDescriptionSourceRenderer
+ -> TermInfoSourceCompiler
+ -> compiled terminfo bytes
+ -> CompiledTermInfoParser
+ -> TerminalDescriptionComparer
+```
+
+The comparison is semantic. The test does not require byte-for-byte equality
+with an ncurses-generated file or textual identity with `infocmp` output.
+
+For unresolved source, every checked-in valid Source corpus file follows:
+
+```text
+TermInfoSourceParser
+ -> TermInfoSourceRenderer
+ -> TermInfoSourceParser
+ -> TermInfoSourceComparer
+```
+
+Each entry is also resolved before and after normalization and the resulting
+effective descriptions are compared. This checks that normalized source remains
+structurally equivalent and preserves resolution semantics.
+
+Ordinary CI therefore remains independent of a host `tic` or `infocmp`
+installation while retaining pinned corpus evidence originally derived from the
+ncurses compatibility work.
+
+## Determinism and hostile-boundary coverage
+
+I02-I06 already cover individual escaping, malformed-state, cancellation,
+disabled-field, duplicate-field, `use=`, provider, and comparison behaviors.
+I07 adds the cross-cutting freeze cases which were still missing:
+
+- exact 80-column effective-render wrapping boundary behavior;
+- identical comparison ordering under different cultures;
+- identical comparison ordering when extended capabilities are inserted in a
+ different dictionary/build order;
+- source-aware comparison with cancellation, disabled fields, repeated
+ capabilities, and reordered `use=` declarations in one stress case;
+- repeated semantic comparison after Compiler-backed round trips.
+
+The same tests run in the repository's Windows, Linux, and macOS CI matrices, so
+platform-specific line endings or culture defaults cannot become part of the
+Inspection contract.
+
+## API freeze
+
+`docs/1.3.0-INSPECTION-PUBLIC-API-BASELINE.txt` is unchanged by I07. That is
+intentional: the I02-I06 public surface is the candidate 1.3 contract. Existing
+package verification continues to compare that baseline against the built
+Inspection assembly and to require API equivalence across `net8.0`, `net9.0`,
+and `net10.0`.
+
+The Runtime 1.0, Source 1.1, and Compiler 1.2 baselines remain unchanged and
+continue to be verified independently.
+
+## Package and release freeze
+
+I07 adds explicit contract tests around the release boundary while retaining the
+existing verifier implementation. The repository already validates:
+
+- Runtime, Source, Compiler, and Inspection packages;
+- four `.nupkg` and four `.snupkg` artifacts;
+- package-reference-only Runtime, Source, Compiler, and Inspection consumers;
+- every consumer on `net8.0`, `net9.0`, and `net10.0`;
+- Inspection API equivalence across all three TFMs;
+- the four-package dependency direction;
+- non-publishing PR and `main` validation workflows;
+- tag-driven publication of the four package pairs;
+- nine GitHub Release assets total, including `SHA256SUMS.txt`.
+
+I07 does not widen the release workflow or add another package. It freezes and
+tests the four-package shape established in I01.
+
+## Completion gate
+
+I07 is complete when the full solution builds and tests on Windows, Linux, and
+macOS; all package-verification gates pass; the Inspection public API baseline is
+unchanged and exact; and all four projects report `1.3.0-Alpha-7` while retaining
+assembly version `1.0.0.0`.
+
+After that gate, the only planned 1.3 work is release closure: advance the four
+package versions to `1.3.0`, run the exact release matrix, merge to `main`, and
+publish from immutable tag `v1.3.0`.
diff --git a/docs/1.3.0-INSPECTION-PUBLIC-API-BASELINE.txt b/docs/1.3.0-INSPECTION-PUBLIC-API-BASELINE.txt
index be82e38..792df55 100644
--- a/docs/1.3.0-INSPECTION-PUBLIC-API-BASELINE.txt
+++ b/docs/1.3.0-INSPECTION-PUBLIC-API-BASELINE.txt
@@ -51,6 +51,35 @@ TYPE enum Icod.TermInfo.Inspection.TermInfoDifferenceKind [sealed]
ENUM SourceUseReference = 14
END
+TYPE class Icod.TermInfo.Inspection.TermInfoInspectionComparison [sealed]
+ PROPERTY Icod.TermInfo.Inspection.TermInfoComparisonResult Comparison { public get; } null=not-null/unknown
+ PROPERTY Icod.TermInfo.Inspection.TermInfoInspectionResult Left { public get; } null=not-null/unknown
+ PROPERTY Icod.TermInfo.Inspection.TermInfoInspectionResult Right { public get; } null=not-null/unknown
+ PROPERTY System.Boolean AreEqual { public get; } null=not-null/unknown
+END
+
+TYPE class Icod.TermInfo.Inspection.TermInfoInspectionEngine [static]
+ METHOD public static Icod.TermInfo.Inspection.TermInfoInspectionComparison Compare(Icod.TermInfo.Inspection.TermInfoInspectionResult left null=not-null/not-null, Icod.TermInfo.Inspection.TermInfoInspectionResult right null=not-null/not-null) return-null=not-null/not-null
+ METHOD public static Icod.TermInfo.Inspection.TermInfoInspectionComparison Compare(Icod.TermInfo.Inspection.TermInfoInspectionTarget left null=not-null/not-null, Icod.TermInfo.Inspection.TermInfoInspectionTarget right null=not-null/not-null) return-null=not-null/not-null
+ METHOD public static Icod.TermInfo.Inspection.TermInfoInspectionResult Inspect(Icod.TermInfo.Inspection.TermInfoInspectionTarget target null=not-null/not-null) return-null=not-null/not-null
+ METHOD public static System.Boolean TryInspect(Icod.TermInfo.Inspection.TermInfoInspectionTarget target null=not-null/not-null, out Icod.TermInfo.Inspection.TermInfoInspectionResult result null=nullable/nullable attrs=NotNullWhenAttribute(true)) return-null=not-null/not-null
+ METHOD public static System.String Render(Icod.TermInfo.Inspection.TermInfoInspectionResult result null=not-null/not-null) return-null=not-null/not-null
+ METHOD public static System.String Render(Icod.TermInfo.Inspection.TermInfoInspectionTarget target null=not-null/not-null) return-null=not-null/not-null
+END
+
+TYPE class Icod.TermInfo.Inspection.TermInfoInspectionResult [sealed]
+ PROPERTY Icod.TermInfo.Inspection.TermInfoInspectionTarget Target { public get; } null=not-null/unknown
+ PROPERTY Icod.TermInfo.TerminalDescription Terminal { public get; } null=not-null/unknown
+END
+
+TYPE class Icod.TermInfo.Inspection.TermInfoInspectionTarget [sealed]
+ CTOR public TermInfoInspectionTarget(Icod.TermInfo.ITerminalDescriptionProvider provider null=not-null/not-null, System.String requestedName null=not-null/not-null, System.String displayLabel null=nullable/nullable default=null)
+ PROPERTY Icod.TermInfo.ITerminalDescriptionProvider Provider { public get; } null=not-null/unknown
+ PROPERTY System.String DisplayLabel { public get; } null=nullable/unknown
+ PROPERTY System.String DisplayName { public get; } null=not-null/unknown
+ PROPERTY System.String RequestedName { public get; } null=not-null/unknown
+END
+
TYPE class Icod.TermInfo.Inspection.TermInfoSourceComparer [static]
METHOD public static Icod.TermInfo.Inspection.TermInfoComparisonResult Compare(Icod.TermInfo.Source.TermInfoSourceDocument left null=not-null/not-null, Icod.TermInfo.Source.TermInfoSourceDocument right null=not-null/not-null) return-null=not-null/not-null
METHOD public static Icod.TermInfo.Inspection.TermInfoComparisonResult Compare(Icod.TermInfo.Source.TermInfoSourceEntry left null=not-null/not-null, Icod.TermInfo.Source.TermInfoSourceEntry right null=not-null/not-null) return-null=not-null/not-null
diff --git a/docs/1.3.0-RELEASE-AUDIT.md b/docs/1.3.0-RELEASE-AUDIT.md
new file mode 100644
index 0000000..adcc041
--- /dev/null
+++ b/docs/1.3.0-RELEASE-AUDIT.md
@@ -0,0 +1,167 @@
+# Icod.TermInfo 1.3.0 Release Audit
+
+**Release version:** `1.3.0`
+**Runtime assembly version:** `1.0.0.0`
+**Source assembly version:** `1.0.0.0`
+**Compiler assembly version:** `1.0.0.0`
+**Inspection assembly version:** `1.0.0.0`
+**Status:** Final release candidate — main Release validation and tag publication are the remaining sign-off
+
+## Purpose
+
+This document is the final release gate for the coordinated `Icod.TermInfo`
+1.3.0 package family. I01-I07 are complete; release closure is not another
+Inspection feature tranche. It freezes the final package/API/documentation
+contract and records the checks which must pass before tag `v1.3.0` publishes
+the release.
+
+The publication boundary is the immutable release tag. Ordinary pushes to
+`main` validate release candidates but do not publish packages.
+
+## Release scope
+
+`Icod.TermInfo` remains the stable dependency-free Runtime package established
+in 1.0. Its public contract is not enlarged by the Inspection work.
+
+`Icod.TermInfo.Source` remains the optional 1.1 source-language layer and keeps
+its frozen 1.1 public API.
+
+`Icod.TermInfo.Compiler` remains the optional 1.2 compiled-output layer and
+keeps its frozen 1.2 public API.
+
+`Icod.TermInfo.Inspection` is the new optional 1.3 inspection/comparison layer.
+It provides:
+
+- canonical `.ti`-style rendering of effective `TerminalDescription` values;
+- normalized rendering of unresolved Source entries and documents;
+- deterministic structured comparison of effective terminal descriptions;
+- source-aware comparison which preserves ordered source semantics;
+- provider-aware inspection through explicit provider/name targets;
+- culture-independent and insertion-order-independent difference ordering;
+- corpus-backed managed differential round trips through Source, Compiler, and
+ Runtime.
+
+Command-line `tic`, `infocmp`, and `toe` applications remain later work.
+
+## API and assembly contract
+
+The final release SHALL preserve all of these conditions:
+
+- `docs/1.0.0-PUBLIC-API-BASELINE.txt` remains the exact Runtime public API
+ contract;
+- `docs/1.1.0-SOURCE-PUBLIC-API-BASELINE.txt` remains the exact Source public
+ API contract;
+- `docs/1.2.0-COMPILER-PUBLIC-API-BASELINE.txt` remains the exact Compiler
+ public API contract;
+- `docs/1.3.0-INSPECTION-PUBLIC-API-BASELINE.txt` is the frozen Inspection
+ public API contract;
+- Runtime, Source, Compiler, and Inspection public API manifests remain
+ identical across `net8.0`, `net9.0`, and `net10.0`;
+- all four assemblies remain unsigned;
+- all four assemblies retain assembly version `1.0.0.0`.
+
+The package version advances to `1.3.0`; the assembly versions do not. This is
+intentional and preserves the stable 1.x assembly-identity policy.
+
+No API baseline may be regenerated merely to silence a release check.
+
+## Package contract
+
+All four package projects SHALL publish version `1.3.0`.
+
+`Icod.TermInfo` SHALL remain dependency-free at the NuGet runtime level.
+`Icod.TermInfo.Source` SHALL depend one-way on matching Runtime version `1.3.0`.
+`Icod.TermInfo.Compiler` SHALL depend on matching Runtime and Source version
+`1.3.0`. `Icod.TermInfo.Inspection` SHALL depend on matching Runtime and Source
+version `1.3.0` and SHALL NOT acquire a production Compiler dependency.
+
+The dependency graph remains:
+
+```text
+Inspection -> Source -> Runtime
+Inspection ----------> Runtime
+
+Compiler -> Source -> Runtime
+Compiler ------------> Runtime
+```
+
+Each package SHALL retain its README, icon, LGPL license expression, repository
+metadata, portable symbols, Source Link, and managed/XML assets for all three
+supported TFMs.
+
+## Required final validation
+
+Before tag creation, the release-finalization commit SHALL pass the complete
+`main` validation workflow on Windows, Linux, and macOS.
+
+Equivalent local validation is:
+
+```text
+dotnet restore Icod.TermInfo.sln
+dotnet build Icod.TermInfo.sln -c Release
+dotnet test Icod.TermInfo.sln -c Release
+dotnet pack Icod.TermInfo.csproj -c Release --output artifacts
+dotnet pack Icod.TermInfo.Source/Icod.TermInfo.Source.csproj -c Release --output artifacts
+dotnet pack Icod.TermInfo.Compiler/Icod.TermInfo.Compiler.csproj -c Release --output artifacts
+dotnet pack Icod.TermInfo.Inspection/Icod.TermInfo.Inspection.csproj -c Release --output artifacts
+```
+
+Then run the platform-appropriate coordinated verifier:
+
+```text
+.github\scripts\verify-release-package.cmd artifacts Release
+bash .github/scripts/verify-release-package.sh artifacts Release
+```
+
+The verifier SHALL continue to cover generated capability metadata, all four
+frozen public API baselines, cross-target API equivalence, package structure,
+dependency closure, Source Link/symbol/XML metadata, all four isolated package
+consumers on all three TFMs, and the deterministic non-interactive repository
+sample path.
+
+Normal CI remains independent of a host ncurses installation.
+
+## Tag-based publication sign-off
+
+`.github/workflows/release.yaml` is the sole automated publication workflow.
+
+Tag `v1.3.0` SHALL:
+
+1. identify the exact release-finalization commit which already passed `main`
+ validation;
+2. identify a commit contained in `main`;
+3. match all four projects' `Version` and `PackageVersion` values exactly;
+4. trigger a fresh Windows/Linux/macOS Release validation matrix;
+5. publish only the canonical package artifacts produced by that successful tag
+ validation;
+6. publish all four `.nupkg` files to NuGet.org through trusted publishing;
+7. publish the same four `.nupkg` files to GitHub Packages;
+8. create a GitHub Release containing all four `.nupkg` files, all four
+ `.snupkg` files, and `SHA256SUMS.txt`.
+
+Before pushing the tag:
+
+- NuGet.org trusted publishing must authorize repository
+ `uniblab/Icod.TermInfo`, workflow `release.yaml`, environment `Release`, and
+ package IDs `Icod.TermInfo`, `Icod.TermInfo.Source`,
+ `Icod.TermInfo.Compiler`, and `Icod.TermInfo.Inspection`;
+- repository secret `NUGET_USER` must identify the intended NuGet.org account;
+- the `Release` environment must permit the intended `v*` tag;
+- GitHub Packages publication must retain `packages: write`.
+
+## Final sign-off
+
+The release is complete only when:
+
+- the tag workflow succeeds;
+- all four version `1.3.0` packages are visible on NuGet.org;
+- all four version `1.3.0` packages are visible in GitHub Packages;
+- the GitHub Release for `v1.3.0` exists with eight package artifacts and
+ `SHA256SUMS.txt`;
+- fresh Runtime, Source, Compiler, and Inspection consumers can restore the
+ final version;
+- `v1.3.0` still identifies the exact validated release commit.
+
+Do not move or recreate a published release tag. Any source, documentation,
+metadata, API-baseline, or package-content change after release requires a new
+version, a new validated commit, and a new tag.
diff --git a/docs/COMPATIBILITY.md b/docs/COMPATIBILITY.md
index d4fb293..3f7a838 100644
--- a/docs/COMPATIBILITY.md
+++ b/docs/COMPATIBILITY.md
@@ -60,10 +60,10 @@ The Source 1.1 public API is independently frozen by
The Compiler 1.2 public API is frozen through
`docs/1.2.0-COMPILER-PUBLIC-API-BASELINE.txt` and its compiler-contract tests.
-Beginning with I01, the Inspection 1.3 public API is developed independently
-through `docs/1.3.0-INSPECTION-PUBLIC-API-BASELINE.txt` and Inspection contract
-tests. I01 deliberately exports no Inspection public types; later I-tranches add
-reviewed API to that independent baseline.
+The Inspection 1.3 public API is independently frozen by
+`docs/1.3.0-INSPECTION-PUBLIC-API-BASELINE.txt` and Inspection contract tests.
+I01 started with an empty public surface, I02-I06 established the reviewed API,
+and I07 froze that contract for release.
Within 1.x:
@@ -169,9 +169,10 @@ tombstones, duplicate-source history, comments, or provenance that
field order where order is semantically significant and likewise shall not
invent source information the parsed model does not retain.
-I01 establishes only the package, dependency, baseline, and release-validation
-foundation. Canonical rendering and comparison behavior enter in later 1.3
-tranches and are frozen through the independent Inspection baseline.
+The released 1.3 contract includes canonical effective rendering, normalized
+unresolved-source rendering, structured effective and source-aware comparison,
+and provider-aware inspection orchestration. Those behaviors are frozen through
+the independent Inspection baseline and its semantic tests.
## Discovery and failure compatibility
diff --git a/docs/FUTURE-WORK-INVENTORY.md b/docs/FUTURE-WORK-INVENTORY.md
index 5e28e17..f0d5329 100644
--- a/docs/FUTURE-WORK-INVENTORY.md
+++ b/docs/FUTURE-WORK-INVENTORY.md
@@ -24,9 +24,9 @@ The governing distinction is:
## 1. Current foundation
-The published 1.2 line preserves the Runtime and Source foundations and adds the
-optional Compiler layer. The active 1.3 line adds the optional Inspection layer
-without moving those responsibilities into the older packages:
+The published 1.3 line preserves the Runtime, Source, and Compiler foundations
+and adds the optional Inspection layer without moving those responsibilities
+into the older packages:
- immutable terminal descriptions;
- complete standard capability metadata;
@@ -53,14 +53,17 @@ without moving those responsibilities into the older packages:
- source-to-compiled composition through the existing Source parser/resolver;
- controlled conventional database-layout output;
- deterministic compiler round-trip/differential validation;
+- canonical effective and unresolved-source rendering;
+- structured effective and source-aware semantic comparison;
+- provider-aware inspection orchestration;
- deterministic corpus/fuzz/resource-bound validation;
- no process-global current terminal;
- three-target `net8.0`/`net9.0`/`net10.0` package and compatibility gates.
The runtime public contract remains the frozen 1.0 contract. Source-language
functionality is isolated in `Icod.TermInfo.Source`; compiled writing is isolated
-in `Icod.TermInfo.Compiler`. I01 establishes `Icod.TermInfo.Inspection` as a
-fourth sibling layer before public inspection behavior is introduced.
+in `Icod.TermInfo.Compiler`; canonical rendering and semantic comparison are
+isolated in `Icod.TermInfo.Inspection`, whose 1.3 public contract is now frozen.
Beginning with 1.2, active package-family validation uses the three-target
`net8.0`/`net9.0`/`net10.0` matrix. This additive support change does not rewrite
@@ -79,7 +82,7 @@ the frozen 1.0/1.1 target-framework contracts.
| Historical vendor formats | HP-UX/AIX/OSF/1 divergent binary layouts | optional later | parser abstraction + fixtures |
| Terminfo source language | completed in 1.1: `.ti`, diagnostics, cancellation, `use=` inheritance, materialization | `Icod.TermInfo.Source` | runtime semantic model |
| Terminfo compiler | completed in 1.2: deterministic compiled-entry writer, source compiler engine, and safe database-layout output | `Icod.TermInfo.Compiler` | Runtime + Source |
-| Terminfo inspection/comparison | active 1.3 work: canonical effective/source-aware rendering, structured semantic comparison, provider-aware inspection | `Icod.TermInfo.Inspection` | Runtime + Source |
+| Terminfo inspection/comparison | completed in 1.3: canonical effective/source-aware rendering, structured semantic comparison, provider-aware inspection | `Icod.TermInfo.Inspection` | Runtime + Source |
| Terminfo command-line tooling | future `tic`, `infocmp`, `toe`, conversion applications | later tool projects | Source + Compiler + Inspection as appropriate |
| Termcap interoperability | termcap syntax, `TERMCAP`, `TERMPATH`, conversion | optional compatibility/tooling | source/conversion model |
| Live session | raw/cooked/cbreak, restore, tty ownership, full-screen/cursor lifecycle | `Icod.Terminal` | `Icod.TermInfo` + OS interop |
@@ -157,7 +160,7 @@ separate later layer so the core binary writer remains pure.
The complete pre-C01 representation and package contract is recorded in
`docs/1.2.0-PRE-C01-CONTRACT-AUDIT.md`.
-### 3.3 `infocmp`-class inspection — active in 1.3
+### 3.3 `infocmp`-class inspection — completed in 1.3
The 1.3 line establishes `Icod.TermInfo.Inspection` as the reusable engine for:
@@ -168,8 +171,9 @@ The 1.3 line establishes `Icod.TermInfo.Inspection` as the reusable engine for:
- provider-aware inspection orchestration;
- future `infocmp`-style applications without embedding console policy.
-I01 establishes the package, dependency, API-baseline, smoke, and release
-infrastructure before I02 introduces the first public rendering behavior.
+I01 established the package, dependency, API-baseline, smoke, and release
+infrastructure; I02-I06 added the reviewed rendering, comparison, and inspection
+surface; and I07 froze the API/package boundary with differential validation.
Inspection depends directly on Runtime and Source and has no production
dependency on Compiler.
diff --git a/docs/RELEASING.md b/docs/RELEASING.md
index de25313..c8ef169 100644
--- a/docs/RELEASING.md
+++ b/docs/RELEASING.md
@@ -19,9 +19,8 @@ This document describes the current validation and publication procedure for the
- Release validation must pass on Windows, Linux, and macOS on `main` before a
release tag is created. The tag workflow repeats the Release gate on the exact
tagged commit before publication.
-- Release validation must pass the frozen Runtime, Source, and Compiler API
- baselines, the developing Inspection API baseline, and net8/net9/net10
- API-equivalence gates.
+- Release validation must pass the frozen Runtime 1.0, Source 1.1, Compiler 1.2,
+ and Inspection 1.3 API baselines and the net8/net9/net10 API-equivalence gates.
- Release builds treat missing public XML documentation as an error.
- All four packages must pass the coordinated release verifier before publication.
Use `.github/scripts/verify-release-package.sh` on a Bash-capable host or
@@ -160,12 +159,13 @@ proves the packed package can:
- compose system lookup with `TerminalDatabase.BuiltIn` fallback.
The Source smoke consumer proves the separately packed source-language package
-can restore through its NuGet dependency on the matching runtime package and
+can restore through its NuGet dependency on the matching Runtime package and
execute on all three supported target frameworks. The Compiler smoke consumer
-likewise proves the Compiler package restores through its Runtime dependency and
-can write and reparse a C01 legacy entry on all three frameworks. The Inspection
-smoke consumer proves the fourth package restores with matching Runtime and Source
-dependencies while retaining the I01 empty-public-surface contract.
+likewise proves the Compiler package restores through its Runtime and Source
+dependencies and can write and reparse a C01 legacy entry on all three
+frameworks. The Inspection smoke consumer proves the fourth package restores
+with matching Runtime and Source dependencies and exercises the reviewed I02-I06
+public surface without a production Compiler dependency.
No checked-in runtime fixture is copied into the smoke project, so those checks
prove the public package surface rather than repository-only outputs.
@@ -290,11 +290,11 @@ frozen compiler architecture, `docs/1.2.0-C01-COMPILER-PACKAGE-FOUNDATION.md`
for the C01 implementation record, and
`docs/1.2.0-COMPILER-PUBLIC-API-BASELINE.txt` for the frozen Compiler API.
-For 1.3 development, use
+For the completed 1.3 line, use
`Icod.TermInfo-1.3.0-Inspection-and-Comparison-Roadmap.md` for the I01-I07
contract, `docs/1.3.0-PRE-I01-CONTRACT-AUDIT.md` for the package/layer freeze,
-and `docs/1.3.0-INSPECTION-PUBLIC-API-BASELINE.txt` for the developing Inspection
-API.
+`docs/1.3.0-INSPECTION-PUBLIC-API-BASELINE.txt` for the frozen Inspection API,
+and `docs/1.3.0-RELEASE-AUDIT.md` for final release sign-off requirements.
The final `v` tag must identify the exact validated and published `main`
commit. Do not edit the audit or any other source/package content after that
diff --git a/docs/VERSIONING.md b/docs/VERSIONING.md
index a453015..e43c45b 100644
--- a/docs/VERSIONING.md
+++ b/docs/VERSIONING.md
@@ -83,10 +83,9 @@ Beginning with C01, `docs/1.2.0-COMPILER-PUBLIC-API-BASELINE.txt` records the
developing public contract for `Icod.TermInfo.Compiler` and becomes the frozen
Compiler contract at 1.2 release closure.
-Beginning with I01, `docs/1.3.0-INSPECTION-PUBLIC-API-BASELINE.txt` records the
-deliberately developing public contract for `Icod.TermInfo.Inspection` and
-becomes the frozen Inspection contract at 1.3 release closure. I01 intentionally
-starts with no exported Inspection types.
+The approved `docs/1.3.0-INSPECTION-PUBLIC-API-BASELINE.txt` is the independent
+machine-readable public contract for `Icod.TermInfo.Inspection`, frozen at the
+1.3 release closure after the I02-I06 API additions and I07 validation gate.
The baselines record exported types, public/protected members, enum numeric
values, parameter names/order/defaults, ref/out/in/params shape, generic
diff --git a/tests/Icod.TermInfo.Compiler.Tests/src/C01ContractTests.cs b/tests/Icod.TermInfo.Compiler.Tests/src/C01ContractTests.cs
index 46f6db3..0964696 100644
--- a/tests/Icod.TermInfo.Compiler.Tests/src/C01ContractTests.cs
+++ b/tests/Icod.TermInfo.Compiler.Tests/src/C01ContractTests.cs
@@ -6,7 +6,7 @@
namespace Icod.TermInfo.Compiler.Tests;
public sealed class C01ContractTests {
- private const string DevelopmentVersion = "1.3.0-Alpha-5";
+ private const string DevelopmentVersion = "1.3.0";
private const string StableAssemblyVersion = "1.0.0.0";
[Fact]
diff --git a/tests/Icod.TermInfo.Inspection.Tests/Icod.TermInfo.Inspection.Tests.csproj b/tests/Icod.TermInfo.Inspection.Tests/Icod.TermInfo.Inspection.Tests.csproj
index fdf8495..2c7d099 100644
--- a/tests/Icod.TermInfo.Inspection.Tests/Icod.TermInfo.Inspection.Tests.csproj
+++ b/tests/Icod.TermInfo.Inspection.Tests/Icod.TermInfo.Inspection.Tests.csproj
@@ -25,6 +25,7 @@
+
diff --git a/tests/Icod.TermInfo.Inspection.Tests/src/I01ContractTests.cs b/tests/Icod.TermInfo.Inspection.Tests/src/I01ContractTests.cs
index a6b6ec8..f585d72 100644
--- a/tests/Icod.TermInfo.Inspection.Tests/src/I01ContractTests.cs
+++ b/tests/Icod.TermInfo.Inspection.Tests/src/I01ContractTests.cs
@@ -5,7 +5,7 @@
namespace Icod.TermInfo.Inspection.Tests;
public sealed class I01ContractTests {
- private const string DevelopmentVersion = "1.3.0-Alpha-5";
+ private const string DevelopmentVersion = "1.3.0";
private const string StableAssemblyVersion = "1.0.0.0";
[Fact]
@@ -75,6 +75,10 @@ in new[] {
"Icod.TermInfo.Inspection.TermInfoComparisonResult",
"Icod.TermInfo.Inspection.TermInfoDifference",
"Icod.TermInfo.Inspection.TermInfoDifferenceKind",
+ "Icod.TermInfo.Inspection.TermInfoInspectionComparison",
+ "Icod.TermInfo.Inspection.TermInfoInspectionEngine",
+ "Icod.TermInfo.Inspection.TermInfoInspectionResult",
+ "Icod.TermInfo.Inspection.TermInfoInspectionTarget",
"Icod.TermInfo.Inspection.TermInfoSourceComparer",
"Icod.TermInfo.Inspection.TermInfoSourceRenderer",
"Icod.TermInfo.Inspection.TerminalDescriptionComparer",
@@ -190,7 +194,7 @@ in new[] {
}
[Fact]
- public void InspectionPublicApiBaselineContainsReviewedI02ThroughI05Surface() {
+ public void InspectionPublicApiBaselineContainsReviewedI02ThroughI06Surface() {
string root =
FindRepositoryRoot();
string baseline =
@@ -216,6 +220,30 @@ public void InspectionPublicApiBaselineContainsReviewedI02ThroughI05Surface() {
"TYPE enum Icod.TermInfo.Inspection.TermInfoDifferenceKind [sealed]",
baseline
);
+ Assert.Contains(
+ "TYPE class Icod.TermInfo.Inspection.TermInfoInspectionComparison [sealed]",
+ baseline
+ );
+ Assert.Contains(
+ "TYPE class Icod.TermInfo.Inspection.TermInfoInspectionEngine [static]",
+ baseline
+ );
+ Assert.Contains(
+ "TYPE class Icod.TermInfo.Inspection.TermInfoInspectionResult [sealed]",
+ baseline
+ );
+ Assert.Contains(
+ "TYPE class Icod.TermInfo.Inspection.TermInfoInspectionTarget [sealed]",
+ baseline
+ );
+ Assert.Contains(
+ "METHOD public static System.Boolean TryInspect(Icod.TermInfo.Inspection.TermInfoInspectionTarget target",
+ baseline
+ );
+ Assert.Contains(
+ "PROPERTY Icod.TermInfo.ITerminalDescriptionProvider Provider",
+ baseline
+ );
Assert.Contains(
"TYPE class Icod.TermInfo.Inspection.TermInfoSourceComparer [static]",
baseline
diff --git a/tests/Icod.TermInfo.Inspection.Tests/src/I06InspectionEngineTests.cs b/tests/Icod.TermInfo.Inspection.Tests/src/I06InspectionEngineTests.cs
new file mode 100644
index 0000000..d9ef4ee
--- /dev/null
+++ b/tests/Icod.TermInfo.Inspection.Tests/src/I06InspectionEngineTests.cs
@@ -0,0 +1,536 @@
+using System.Diagnostics.CodeAnalysis;
+using Icod.TermInfo;
+using Icod.TermInfo.Inspection;
+using Xunit;
+
+namespace Icod.TermInfo.Inspection.Tests;
+
+public sealed class I06InspectionEngineTests {
+ [Fact]
+ public void InspectionTarget_RetainsExplicitProviderNameAndCallerLabel() {
+ ITerminalDescriptionProvider provider =
+ TerminalDatabase.BuiltIn;
+ TermInfoInspectionTarget target =
+ new(
+ provider,
+ "xterm",
+ "built-in xterm"
+ );
+
+ Assert.Same( provider, target.Provider );
+ Assert.Equal( "xterm", target.RequestedName );
+ Assert.Equal( "built-in xterm", target.DisplayLabel );
+ Assert.Equal( "built-in xterm", target.DisplayName );
+
+ TermInfoInspectionTarget unlabeled =
+ new(
+ provider,
+ "xterm-256color"
+ );
+ Assert.Null( unlabeled.DisplayLabel );
+ Assert.Equal( "xterm-256color", unlabeled.DisplayName );
+ }
+
+ [Fact]
+ public void InspectionTarget_RejectsInvalidArguments() {
+ ITerminalDescriptionProvider provider =
+ TerminalDatabase.BuiltIn;
+
+ Assert.Throws(
+ () =>
+ new TermInfoInspectionTarget(
+ null!,
+ "xterm"
+ )
+ );
+ Assert.Throws(
+ () =>
+ new TermInfoInspectionTarget(
+ provider,
+ null!
+ )
+ );
+ Assert.Throws(
+ () =>
+ new TermInfoInspectionTarget(
+ provider,
+ " "
+ )
+ );
+ Assert.Throws(
+ () =>
+ new TermInfoInspectionTarget(
+ provider,
+ "xterm",
+ "\t"
+ )
+ );
+ }
+
+ [Fact]
+ public void TryInspect_CleanMissRemainsDistinguishable() {
+ CountingProvider provider =
+ new(
+ acceptedName: "present",
+ terminal: null
+ );
+ TermInfoInspectionTarget target =
+ new(
+ provider,
+ "missing",
+ "custom provider"
+ );
+
+ bool found =
+ TermInfoInspectionEngine.TryInspect(
+ target,
+ out TermInfoInspectionResult? result
+ );
+
+ Assert.False( found );
+ Assert.Null( result );
+ Assert.Equal( 1, provider.LoadCount );
+ Assert.Equal( "missing", provider.LastRequestedName );
+ }
+
+ [Fact]
+ public void Inspect_CleanMissThrowsKeyNotFoundWithoutMaskingProviderContract() {
+ CountingProvider provider =
+ new(
+ acceptedName: "present",
+ terminal: null
+ );
+ TermInfoInspectionTarget target =
+ new(
+ provider,
+ "missing",
+ "explicit database"
+ );
+
+ KeyNotFoundException exception =
+ Assert.Throws(
+ () => TermInfoInspectionEngine.Inspect( target )
+ );
+
+ Assert.Contains( "missing", exception.Message );
+ Assert.Contains( "explicit database", exception.Message );
+ Assert.Equal( 1, provider.LoadCount );
+ }
+
+ [Fact]
+ public void Inspect_ProviderFailuresPropagateUnchanged() {
+ InvalidOperationException expected =
+ new(
+ "provider failure"
+ );
+ ThrowingProvider provider =
+ new(
+ expected
+ );
+ TermInfoInspectionTarget target =
+ new(
+ provider,
+ "xterm"
+ );
+
+ InvalidOperationException actual =
+ Assert.Throws(
+ () => TermInfoInspectionEngine.Inspect( target )
+ );
+
+ Assert.Same( expected, actual );
+ Assert.Equal( 1, provider.LoadCount );
+ }
+
+ [Fact]
+ public void Inspect_RetainsRequestedAliasSeparatelyFromCanonicalTerminalIdentity() {
+ TerminalDescription terminal =
+ new TerminalDescriptionBuilder( "canonical-terminal" )
+ .SetDescription( "I06 alias acquisition" )
+ .AddAlias( "requested-alias" )
+ .Build();
+ InMemoryTerminalDescriptionProvider provider =
+ new(
+ new[] {
+ terminal,
+ }
+ );
+ TermInfoInspectionTarget target =
+ new(
+ provider,
+ "requested-alias",
+ "caller label"
+ );
+
+ TermInfoInspectionResult result =
+ TermInfoInspectionEngine.Inspect(
+ target
+ );
+
+ Assert.Same( target, result.Target );
+ Assert.Same( terminal, result.Terminal );
+ Assert.Equal( "requested-alias", result.Target.RequestedName );
+ Assert.Equal( "canonical-terminal", result.Terminal.Name );
+ Assert.Equal( "caller label", result.Target.DisplayName );
+ }
+
+ [Fact]
+ public void Render_TargetAndAcquiredResultUseCanonicalEffectiveRenderer() {
+ TerminalDescription terminal =
+ TerminalProfiles.Xterm;
+ CountingProvider provider =
+ new(
+ "xterm",
+ terminal
+ );
+ TermInfoInspectionTarget target =
+ new(
+ provider,
+ "xterm"
+ );
+
+ string renderedFromTarget =
+ TermInfoInspectionEngine.Render(
+ target
+ );
+ Assert.Equal(
+ TerminalDescriptionSourceRenderer.Render( terminal ),
+ renderedFromTarget
+ );
+ Assert.Equal( 1, provider.LoadCount );
+
+ TermInfoInspectionResult acquired =
+ TermInfoInspectionEngine.Inspect(
+ target
+ );
+ Assert.Equal( 2, provider.LoadCount );
+ string renderedFromResult =
+ TermInfoInspectionEngine.Render(
+ acquired
+ );
+ Assert.Equal( renderedFromTarget, renderedFromResult );
+ Assert.Equal( 2, provider.LoadCount );
+ }
+
+ [Fact]
+ public void Compare_ExplicitTargetsRetainsBothIdentitiesAndLoadsEachOnce() {
+ TerminalDescription leftTerminal =
+ new TerminalDescriptionBuilder( "left-terminal" )
+ .SetDescription( "I06 left" )
+ .AddAlias( "left-request" )
+ .Build();
+ TerminalDescription rightTerminal =
+ new TerminalDescriptionBuilder( "right-terminal" )
+ .SetDescription( "I06 right" )
+ .AddAlias( "right-request" )
+ .Build();
+ CountingProvider leftProvider =
+ new(
+ "left-request",
+ leftTerminal
+ );
+ CountingProvider rightProvider =
+ new(
+ "right-request",
+ rightTerminal
+ );
+ TermInfoInspectionTarget left =
+ new(
+ leftProvider,
+ "left-request",
+ "left source"
+ );
+ TermInfoInspectionTarget right =
+ new(
+ rightProvider,
+ "right-request",
+ "right source"
+ );
+
+ TermInfoInspectionComparison comparison =
+ TermInfoInspectionEngine.Compare(
+ left,
+ right
+ );
+
+ Assert.Same( left, comparison.Left.Target );
+ Assert.Same( right, comparison.Right.Target );
+ Assert.Same( leftTerminal, comparison.Left.Terminal );
+ Assert.Same( rightTerminal, comparison.Right.Terminal );
+ Assert.False( comparison.AreEqual );
+ Assert.False( comparison.Comparison.AreEqual );
+ Assert.Equal( 1, leftProvider.LoadCount );
+ Assert.Equal( 1, rightProvider.LoadCount );
+ Assert.Equal( "left source", comparison.Left.Target.DisplayName );
+ Assert.Equal( "right source", comparison.Right.Target.DisplayName );
+ }
+
+ [Fact]
+ public void Compare_AcquiredResultsDoesNotReacquireProviders() {
+ TerminalDescription terminal =
+ TerminalProfiles.Xterm;
+ CountingProvider leftProvider =
+ new(
+ "xterm",
+ terminal
+ );
+ CountingProvider rightProvider =
+ new(
+ "xterm",
+ terminal
+ );
+ TermInfoInspectionResult left =
+ TermInfoInspectionEngine.Inspect(
+ new TermInfoInspectionTarget(
+ leftProvider,
+ "xterm"
+ )
+ );
+ TermInfoInspectionResult right =
+ TermInfoInspectionEngine.Inspect(
+ new TermInfoInspectionTarget(
+ rightProvider,
+ "xterm"
+ )
+ );
+
+ TermInfoInspectionComparison comparison =
+ TermInfoInspectionEngine.Compare(
+ left,
+ right
+ );
+
+ Assert.True( comparison.AreEqual );
+ Assert.Same( left, comparison.Left );
+ Assert.Same( right, comparison.Right );
+ Assert.Equal( 1, leftProvider.LoadCount );
+ Assert.Equal( 1, rightProvider.LoadCount );
+ }
+
+ [Fact]
+ public void Compare_BuiltInXtermAgainstCallerProviderRequiresNoApplicationGlue() {
+ ITerminalDescriptionProvider builtIn =
+ TerminalDatabase.BuiltIn;
+ InMemoryTerminalDescriptionProvider callerProvider =
+ new(
+ new[] {
+ TerminalProfiles.Xterm,
+ }
+ );
+ TermInfoInspectionTarget left =
+ new(
+ builtIn,
+ "xterm",
+ "built-in"
+ );
+ TermInfoInspectionTarget right =
+ new(
+ callerProvider,
+ "xterm",
+ "caller provider"
+ );
+
+ TermInfoInspectionComparison comparison =
+ TermInfoInspectionEngine.Compare(
+ left,
+ right
+ );
+
+ Assert.True( comparison.AreEqual );
+ Assert.Equal( "built-in", comparison.Left.Target.DisplayName );
+ Assert.Equal( "caller provider", comparison.Right.Target.DisplayName );
+ }
+
+ [Fact]
+ public void Compare_Xterm256ColorAgainstScreen256ColorIsARegularExplicitComparison() {
+ TerminalDescription screen =
+ new TerminalDescriptionBuilder( "screen-256color" )
+ .SetDescription( "I06 screen fixture" )
+ .Build();
+ InMemoryTerminalDescriptionProvider screenProvider =
+ new(
+ new[] {
+ screen,
+ }
+ );
+ TermInfoInspectionTarget left =
+ new(
+ TerminalDatabase.BuiltIn,
+ "xterm-256color",
+ "xterm-256color"
+ );
+ TermInfoInspectionTarget right =
+ new(
+ screenProvider,
+ "screen-256color",
+ "screen-256color"
+ );
+
+ TermInfoInspectionComparison comparison =
+ TermInfoInspectionEngine.Compare(
+ left,
+ right
+ );
+
+ Assert.False( comparison.AreEqual );
+ Assert.Equal( "xterm-256color", comparison.Left.Target.RequestedName );
+ Assert.Equal( "screen-256color", comparison.Right.Target.RequestedName );
+ Assert.NotEmpty( comparison.Comparison.Differences );
+ }
+
+ [Fact]
+ public void TargetsCanRepresentSystemAndSeparateDirectorySourcesWithoutInventedProvenance() {
+ SystemTerminalDescriptionProvider systemProvider =
+ new();
+ DirectoryTerminalDescriptionProvider firstDirectory =
+ new(
+ Path.Combine(
+ Path.GetTempPath(),
+ "icod-i06-first"
+ )
+ );
+ DirectoryTerminalDescriptionProvider secondDirectory =
+ new(
+ Path.Combine(
+ Path.GetTempPath(),
+ "icod-i06-second"
+ )
+ );
+
+ TermInfoInspectionTarget system =
+ new(
+ systemProvider,
+ "xterm",
+ "system"
+ );
+ TermInfoInspectionTarget explicitDirectory =
+ new(
+ firstDirectory,
+ "xterm",
+ "database A"
+ );
+ TermInfoInspectionTarget otherDirectory =
+ new(
+ secondDirectory,
+ "xterm",
+ "database B"
+ );
+
+ Assert.IsType( system.Provider );
+ Assert.IsType( explicitDirectory.Provider );
+ Assert.IsType( otherDirectory.Provider );
+ Assert.Equal( "system", system.DisplayName );
+ Assert.Equal( "database A", explicitDirectory.DisplayName );
+ Assert.Equal( "database B", otherDirectory.DisplayName );
+ Assert.NotEqual( firstDirectory.Root, secondDirectory.Root );
+ }
+
+ [Fact]
+ public void Engine_PublicEntryPointsRejectNullArguments() {
+ TermInfoInspectionTarget target =
+ new(
+ TerminalDatabase.BuiltIn,
+ "xterm"
+ );
+ TermInfoInspectionResult result =
+ TermInfoInspectionEngine.Inspect(
+ target
+ );
+
+ Assert.Throws(
+ () =>
+ TermInfoInspectionEngine.TryInspect(
+ null!,
+ out _
+ )
+ );
+ Assert.Throws(
+ () => TermInfoInspectionEngine.Inspect( null! )
+ );
+ Assert.Throws(
+ () => TermInfoInspectionEngine.Render( (TermInfoInspectionTarget)null! )
+ );
+ Assert.Throws(
+ () => TermInfoInspectionEngine.Render( (TermInfoInspectionResult)null! )
+ );
+ Assert.Throws(
+ () => TermInfoInspectionEngine.Compare( null!, target )
+ );
+ Assert.Throws(
+ () => TermInfoInspectionEngine.Compare( target, null! )
+ );
+ Assert.Throws(
+ () => TermInfoInspectionEngine.Compare( null!, result )
+ );
+ Assert.Throws(
+ () => TermInfoInspectionEngine.Compare( result, null! )
+ );
+ }
+
+ private sealed class CountingProvider : ITerminalDescriptionProvider {
+ private readonly string _acceptedName;
+ private readonly TerminalDescription? _terminal;
+
+ internal CountingProvider(
+ string acceptedName,
+ TerminalDescription? terminal
+ ) {
+ ArgumentException.ThrowIfNullOrWhiteSpace( acceptedName );
+
+ _acceptedName = acceptedName;
+ _terminal = terminal;
+ }
+
+ internal int LoadCount { get; private set; }
+
+ internal string? LastRequestedName { get; private set; }
+
+ public bool TryLoad(
+ string name,
+ [NotNullWhen( true )] out TerminalDescription? terminal
+ ) {
+ ArgumentNullException.ThrowIfNull( name );
+
+ LoadCount++;
+ LastRequestedName = name;
+
+ if ( _terminal is null
+ || !string.Equals(
+ name,
+ _acceptedName,
+ StringComparison.Ordinal
+ ) ) {
+ terminal = null;
+ return false;
+ }
+
+ terminal = _terminal;
+ return true;
+ }
+ }
+
+ private sealed class ThrowingProvider : ITerminalDescriptionProvider {
+ private readonly Exception _exception;
+
+ internal ThrowingProvider(
+ Exception exception
+ ) {
+ ArgumentNullException.ThrowIfNull( exception );
+
+ _exception = exception;
+ }
+
+ internal int LoadCount { get; private set; }
+
+ public bool TryLoad(
+ string name,
+ [NotNullWhen( true )] out TerminalDescription? terminal
+ ) {
+ ArgumentNullException.ThrowIfNull( name );
+
+ LoadCount++;
+ terminal = null;
+ throw _exception;
+ }
+ }
+}
diff --git a/tests/Icod.TermInfo.Inspection.Tests/src/I07ValidationTests.cs b/tests/Icod.TermInfo.Inspection.Tests/src/I07ValidationTests.cs
new file mode 100644
index 0000000..57c9ee5
--- /dev/null
+++ b/tests/Icod.TermInfo.Inspection.Tests/src/I07ValidationTests.cs
@@ -0,0 +1,784 @@
+using System.Globalization;
+using System.Text;
+using System.Xml.Linq;
+using Icod.TermInfo;
+using Icod.TermInfo.Compiler;
+using Icod.TermInfo.Inspection;
+using Icod.TermInfo.Source;
+using Xunit;
+
+namespace Icod.TermInfo.Inspection.Tests;
+
+public sealed class I07ValidationTests {
+ private static readonly TerminalDescription[] BuiltInProfiles = [
+ TerminalProfiles.Dumb,
+ TerminalProfiles.Ansi,
+ TerminalProfiles.Vt100,
+ TerminalProfiles.Vt102,
+ TerminalProfiles.Vt220,
+ TerminalProfiles.Xterm,
+ TerminalProfiles.Xterm16Color,
+ TerminalProfiles.Xterm88Color,
+ TerminalProfiles.Xterm256Color,
+ TerminalProfiles.XtermDirect,
+ TerminalProfiles.XtermDirect16,
+ TerminalProfiles.XtermDirect256,
+ TerminalProfiles.WinConsole,
+ TerminalProfiles.MsTerminal,
+ TerminalProfiles.MsTerminalDirect,
+ ];
+
+ private static readonly string[] T29CompiledFixtures = [
+ "t29-extended.bin",
+ "t29-extended32.bin",
+ "t29-legacy-alignment.bin",
+ "t29-legacy-edge.bin",
+ "t29-legacy-minimal.bin",
+ ];
+
+ [Fact]
+ public void EffectiveRenderer_CompilerRoundTrip_PreservesBuiltInsAndT29Corpus() {
+ foreach ( TerminalDescription description in BuiltInProfiles ) {
+ AssertCompilerRoundTrip(
+ description,
+ "built-in-" + description.Name
+ );
+ }
+
+ string repositoryRoot =
+ FindRepositoryRoot();
+ string fixtureRoot =
+ Path.Combine(
+ repositoryRoot,
+ "tests",
+ "Icod.TermInfo.Tests",
+ "fixtures",
+ "compiled-terminfo",
+ "compiled"
+ );
+
+ foreach ( string fixtureName in T29CompiledFixtures ) {
+ TerminalDescription description =
+ CompiledTermInfoParser.Parse(
+ File.ReadAllBytes(
+ Path.Combine(
+ fixtureRoot,
+ fixtureName
+ )
+ )
+ );
+ AssertCompilerRoundTrip(
+ description,
+ fixtureName
+ );
+ }
+ }
+
+ [Fact]
+ public void SourceCorpus_NormalizedRenderer_PreservesStructureAndResolution() {
+ string repositoryRoot =
+ FindRepositoryRoot();
+ string fixtureRoot =
+ Path.Combine(
+ repositoryRoot,
+ "tests",
+ "Icod.TermInfo.Source.Tests",
+ "fixtures",
+ "source-terminfo",
+ "valid"
+ );
+ string[] fixturePaths =
+ Directory.GetFiles(
+ fixtureRoot,
+ "*.ti",
+ SearchOption.TopDirectoryOnly
+ )
+ .OrderBy(
+ path => path,
+ StringComparer.Ordinal
+ )
+ .ToArray();
+
+ Assert.NotEmpty( fixturePaths );
+
+ foreach ( string fixturePath in fixturePaths ) {
+ TermInfoSourceParseResult parsed =
+ TermInfoSourceParser.Parse(
+ File.ReadAllText( fixturePath ),
+ fixturePath
+ );
+ Assert.False( parsed.HasErrors );
+
+ string rendered =
+ TermInfoSourceRenderer.Render(
+ parsed.Document
+ );
+ Assert.DoesNotContain( '\r', rendered );
+
+ TermInfoSourceParseResult reparsed =
+ TermInfoSourceParser.Parse(
+ rendered,
+ fixturePath + ".normalized"
+ );
+ Assert.False( reparsed.HasErrors );
+
+ TermInfoComparisonResult sourceComparison =
+ TermInfoSourceComparer.Compare(
+ parsed.Document,
+ reparsed.Document
+ );
+ Assert.True(
+ sourceComparison.AreEqual,
+ $"Normalized source differed structurally for '{Path.GetFileName( fixturePath )}'."
+ );
+
+ foreach ( TermInfoSourceEntry entry in parsed.Document.Entries ) {
+ TermInfoSourceResolveResult original =
+ TermInfoSourceResolver.Resolve(
+ parsed.Document,
+ entry.CanonicalName
+ );
+ TermInfoSourceResolveResult normalized =
+ TermInfoSourceResolver.Resolve(
+ reparsed.Document,
+ entry.CanonicalName
+ );
+
+ Assert.False( original.HasErrors );
+ Assert.False( normalized.HasErrors );
+ Assert.NotNull( original.Entry );
+ Assert.NotNull( normalized.Entry );
+
+ TermInfoComparisonResult effectiveComparison =
+ TerminalDescriptionComparer.Compare(
+ original.Entry!.ToTerminalDescription(),
+ normalized.Entry!.ToTerminalDescription()
+ );
+ Assert.True(
+ effectiveComparison.AreEqual,
+ $"Normalized source resolved differently for '{entry.CanonicalName}'."
+ );
+ }
+ }
+ }
+
+ [Fact]
+ public void EffectiveRenderer_WrapBoundary_IsStableAtEightyColumns() {
+ TerminalDescription fitsExactly =
+ new TerminalDescriptionBuilder( "i07-wrap-71" )
+ .SetDescription( "I07 wrap boundary" )
+ .SetString(
+ StringCapability.Bell,
+ new string( 'x', 71 )
+ )
+ .Build();
+ TerminalDescription wrapsNextCharacter =
+ new TerminalDescriptionBuilder( "i07-wrap-72" )
+ .SetDescription( "I07 wrap boundary" )
+ .SetString(
+ StringCapability.Bell,
+ new string( 'x', 72 )
+ )
+ .Build();
+
+ string[] exactLines =
+ TerminalDescriptionSourceRenderer.Render(
+ fitsExactly
+ )
+ .Split( '\n' );
+ string[] wrappedLines =
+ TerminalDescriptionSourceRenderer.Render(
+ wrapsNextCharacter
+ )
+ .Split( '\n' );
+
+ Assert.Equal( 80, exactLines[ 1 ].Length );
+ Assert.StartsWith( " bel=", exactLines[ 1 ] );
+ Assert.EndsWith( ",", exactLines[ 1 ] );
+ Assert.Equal( 3, exactLines.Length );
+
+ Assert.Equal( 79, wrappedLines[ 1 ].Length );
+ Assert.Equal( " x,", wrappedLines[ 2 ] );
+ Assert.Equal( 4, wrappedLines.Length );
+ Assert.All(
+ wrappedLines,
+ line => Assert.True( line.Length <= 80 )
+ );
+ }
+
+ [Fact]
+ public void Comparison_IsDeterministicAcrossCultureAndExtendedInsertionOrder() {
+ CultureInfo originalCulture =
+ CultureInfo.CurrentCulture;
+ CultureInfo originalUiCulture =
+ CultureInfo.CurrentUICulture;
+
+ try {
+ CultureInfo.CurrentCulture =
+ CultureInfo.GetCultureInfo( "tr-TR" );
+ CultureInfo.CurrentUICulture =
+ CultureInfo.GetCultureInfo( "tr-TR" );
+ string[] first =
+ DescribeDifferences(
+ TerminalDescriptionComparer.Compare(
+ CreateComparisonDescription(
+ left: true,
+ reverseExtendedInsertion: false
+ ),
+ CreateComparisonDescription(
+ left: false,
+ reverseExtendedInsertion: false
+ )
+ )
+ );
+
+ CultureInfo.CurrentCulture =
+ CultureInfo.GetCultureInfo( "fr-FR" );
+ CultureInfo.CurrentUICulture =
+ CultureInfo.GetCultureInfo( "fr-FR" );
+ string[] second =
+ DescribeDifferences(
+ TerminalDescriptionComparer.Compare(
+ CreateComparisonDescription(
+ left: true,
+ reverseExtendedInsertion: true
+ ),
+ CreateComparisonDescription(
+ left: false,
+ reverseExtendedInsertion: true
+ )
+ )
+ );
+
+ Assert.NotEmpty( first );
+ Assert.Equal( first, second );
+ Assert.Contains(
+ first,
+ item => item.Contains(
+ "DifferentValueKind|kindMismatch|",
+ StringComparison.Ordinal
+ )
+ );
+ Assert.Contains(
+ first,
+ item => item.Contains(
+ "|Feature|",
+ StringComparison.Ordinal
+ )
+ );
+ Assert.Contains(
+ first,
+ item => item.Contains(
+ "|feature|",
+ StringComparison.Ordinal
+ )
+ );
+ }
+ finally {
+ CultureInfo.CurrentCulture = originalCulture;
+ CultureInfo.CurrentUICulture = originalUiCulture;
+ }
+ }
+
+ [Fact]
+ public void SourceComparison_SequenceStress_IsDeterministicAndStateAware() {
+ const string leftSource =
+ "i07-parent|I07 parent,am,cols#80,\n"
+ + "i07-sequence|I07 sequence,\n"
+ + " clear=\\E[H,\n"
+ + " clear@,\n"
+ + " .cup=\\E[%i%p1%d;%p2%dH,\n"
+ + " Vendor=one,\n"
+ + " use=i07-parent,\n"
+ + " Vendor=two,\n"
+ + " use=i07-parent,\n";
+ const string rightSource =
+ "i07-parent|I07 parent,am,cols#80,\n"
+ + "i07-sequence|I07 sequence,\n"
+ + " clear@,\n"
+ + " clear=\\E[H,\n"
+ + " .cup=\\E[%i%p1%d;%p2%dH,\n"
+ + " Vendor=two,\n"
+ + " use=i07-parent,\n"
+ + " Vendor=one,\n"
+ + " use=i07-parent,\n";
+
+ TermInfoSourceParseResult left =
+ TermInfoSourceParser.Parse(
+ leftSource,
+ "i07-left-sequence.ti"
+ );
+ TermInfoSourceParseResult right =
+ TermInfoSourceParser.Parse(
+ rightSource,
+ "i07-right-sequence.ti"
+ );
+ Assert.False( left.HasErrors );
+ Assert.False( right.HasErrors );
+
+ TermInfoSourceEntry leftEntry = left.Document.Entries[ 1 ];
+ TermInfoSourceEntry rightEntry = right.Document.Entries[ 1 ];
+
+ Assert.Contains(
+ leftEntry.Fields,
+ field => field.Kind == TermInfoSourceFieldKind.CancelledCapability
+ );
+ Assert.Contains(
+ leftEntry.Fields,
+ field => field.Kind == TermInfoSourceFieldKind.DisabledCapability
+ );
+ Assert.Equal(
+ 2,
+ leftEntry.Fields.Count(
+ field => field.Kind == TermInfoSourceFieldKind.UseReference
+ )
+ );
+ Assert.Equal(
+ 2,
+ leftEntry.Fields.Count(
+ field => field.CapabilityName == "Vendor"
+ )
+ );
+
+ CultureInfo originalCulture =
+ CultureInfo.CurrentCulture;
+ try {
+ CultureInfo.CurrentCulture =
+ CultureInfo.GetCultureInfo( "tr-TR" );
+ string[] first =
+ DescribeSourceDifferences(
+ TermInfoSourceComparer.Compare(
+ leftEntry,
+ rightEntry
+ )
+ );
+
+ CultureInfo.CurrentCulture =
+ CultureInfo.GetCultureInfo( "fr-FR" );
+ string[] second =
+ DescribeSourceDifferences(
+ TermInfoSourceComparer.Compare(
+ leftEntry,
+ rightEntry
+ )
+ );
+
+ Assert.NotEmpty( first );
+ Assert.Equal( first, second );
+ Assert.Contains(
+ first,
+ item => item.Contains(
+ "SourceFieldKind",
+ StringComparison.Ordinal
+ )
+ || item.Contains(
+ "SourceFieldValue",
+ StringComparison.Ordinal
+ )
+ );
+ }
+ finally {
+ CultureInfo.CurrentCulture = originalCulture;
+ }
+ }
+
+ [Fact]
+ public void PackageFreeze_RetainsApisDependencyGraphAndPublishingBoundary() {
+ string root =
+ FindRepositoryRoot();
+ XDocument inspectionProject =
+ XDocument.Load(
+ Path.Combine(
+ root,
+ "Icod.TermInfo.Inspection",
+ "Icod.TermInfo.Inspection.csproj"
+ ),
+ LoadOptions.None
+ );
+ XDocument inspectionTests =
+ XDocument.Load(
+ Path.Combine(
+ root,
+ "tests",
+ "Icod.TermInfo.Inspection.Tests",
+ "Icod.TermInfo.Inspection.Tests.csproj"
+ ),
+ LoadOptions.None
+ );
+
+ Assert.Equal(
+ new[] {
+ @"..\Icod.TermInfo.Source\Icod.TermInfo.Source.csproj",
+ @"..\Icod.TermInfo.csproj",
+ },
+ ReadProjectReferences( inspectionProject )
+ );
+ Assert.Equal(
+ new[] {
+ @"..\..\Icod.TermInfo.Compiler\Icod.TermInfo.Compiler.csproj",
+ @"..\..\Icod.TermInfo.Inspection\Icod.TermInfo.Inspection.csproj",
+ },
+ ReadProjectReferences( inspectionTests )
+ );
+
+ string baseline =
+ File.ReadAllText(
+ Path.Combine(
+ root,
+ "docs",
+ "1.3.0-INSPECTION-PUBLIC-API-BASELINE.txt"
+ )
+ );
+ Assert.DoesNotContain(
+ "Icod.TermInfo.Compiler",
+ baseline
+ );
+
+ foreach (
+ string relativePath
+ in new[] {
+ ".github/scripts/verify-release-package.cmd",
+ ".github/scripts/verify-release-package.sh",
+ }
+ ) {
+ string verifier =
+ File.ReadAllText(
+ Path.Combine(
+ root,
+ relativePath.Replace(
+ '/',
+ Path.DirectorySeparatorChar
+ )
+ )
+ );
+
+ Assert.Contains(
+ "1.3.0-INSPECTION-PUBLIC-API-BASELINE.txt",
+ verifier
+ );
+ foreach ( string framework in new[] { "net8.0", "net9.0", "net10.0" } ) {
+ Assert.Contains( framework, verifier );
+ }
+ foreach (
+ string smoke
+ in new[] {
+ "package-smoke",
+ "source-package-smoke",
+ "compiler-package-smoke",
+ "inspection-package-smoke",
+ }
+ ) {
+ Assert.Contains( smoke, verifier );
+ }
+ }
+
+ string pullRequest =
+ ReadRepositoryFile(
+ root,
+ ".github/workflows/pr-build-and-test.yaml"
+ );
+ string pushMain =
+ ReadRepositoryFile(
+ root,
+ ".github/workflows/push-main.yaml"
+ );
+ string release =
+ ReadRepositoryFile(
+ root,
+ ".github/workflows/release.yaml"
+ );
+
+ Assert.DoesNotContain( "dotnet nuget push", pullRequest );
+ Assert.DoesNotContain( "dotnet nuget push", pushMain );
+ Assert.Contains( "dotnet nuget push", release );
+ Assert.Contains( "if (8 -ne $files.Count)", release );
+ Assert.Contains( "if (9 -ne $assets.Count)", release );
+ foreach (
+ string packageId
+ in new[] {
+ "Icod.TermInfo",
+ "Icod.TermInfo.Source",
+ "Icod.TermInfo.Compiler",
+ "Icod.TermInfo.Inspection",
+ }
+ ) {
+ Assert.Contains( packageId, release );
+ }
+
+ Assert.True(
+ File.Exists(
+ Path.Combine(
+ root,
+ "docs",
+ "1.3.0-I07-DIFFERENTIAL-VALIDATION-AND-FREEZE.md"
+ )
+ )
+ );
+ }
+
+ private static void AssertCompilerRoundTrip(
+ TerminalDescription expected,
+ string sourceName
+ ) {
+ ArgumentNullException.ThrowIfNull( expected );
+ ArgumentException.ThrowIfNullOrWhiteSpace( sourceName );
+
+ string rendered =
+ TerminalDescriptionSourceRenderer.Render(
+ expected
+ );
+ TermInfoSourceCompilationResult compiled =
+ TermInfoSourceCompiler.Compile(
+ rendered,
+ "i07-" + sourceName + ".ti"
+ );
+
+ Assert.False( compiled.HasErrors );
+ CompiledTermInfoSourceEntry entry =
+ Assert.Single( compiled.Entries );
+ TerminalDescription actual =
+ CompiledTermInfoParser.Parse(
+ entry.Data
+ );
+ TermInfoComparisonResult comparison =
+ TerminalDescriptionComparer.Compare(
+ expected,
+ actual
+ );
+
+ Assert.True(
+ comparison.AreEqual,
+ $"Compiler-backed Inspection round trip changed '{expected.Name}'."
+ );
+ }
+
+ private static TerminalDescription CreateComparisonDescription(
+ bool left,
+ bool reverseExtendedInsertion
+ ) {
+ TerminalDescriptionBuilder builder =
+ new TerminalDescriptionBuilder(
+ left
+ ? "i07-left"
+ : "i07-right"
+ )
+ .SetDescription(
+ left
+ ? "I07 left comparison"
+ : "I07 right comparison"
+ )
+ .AddAlias(
+ left
+ ? "i07-left-alias"
+ : "i07-right-alias"
+ )
+ .SetNumber(
+ NumericCapability.Columns,
+ left ? 80 : 132
+ );
+
+ if ( left ) {
+ builder.SetBoolean( BooleanCapability.AutoRightMargin );
+ }
+ else {
+ builder.SetString( StringCapability.Bell, "right" );
+ }
+
+ Action addFirst =
+ () => {
+ if ( left ) {
+ builder
+ .SetExtendedBoolean( "Feature" )
+ .SetExtendedNumber( "kindMismatch", 7 )
+ .SetExtendedNumber( "numberValue", 11 );
+ }
+ else {
+ builder
+ .SetExtendedBoolean( "feature" )
+ .SetExtendedString( "kindMismatch", "7" )
+ .SetExtendedNumber( "numberValue", 13 );
+ }
+ };
+ Action addSecond =
+ () => {
+ builder.SetExtendedString(
+ "stringValue",
+ left ? "left" : "right"
+ );
+ };
+
+ if ( reverseExtendedInsertion ) {
+ addSecond();
+ addFirst();
+ }
+ else {
+ addFirst();
+ addSecond();
+ }
+
+ return builder.Build();
+ }
+
+ private static string[] DescribeDifferences(
+ TermInfoComparisonResult result
+ ) {
+ ArgumentNullException.ThrowIfNull( result );
+
+ return result.Differences
+ .Select(
+ difference =>
+ string.Join(
+ "|",
+ difference.Kind,
+ difference.CapabilityName ?? string.Empty,
+ difference.IsExtendedCapability?.ToString() ?? string.Empty,
+ difference.LeftText ?? string.Empty,
+ difference.RightText ?? string.Empty,
+ DescribeAliases( difference.LeftAliases ),
+ DescribeAliases( difference.RightAliases ),
+ DescribeCapabilityValue( difference.LeftCapabilityValue ),
+ DescribeCapabilityValue( difference.RightCapabilityValue )
+ )
+ )
+ .ToArray();
+ }
+
+ private static string[] DescribeSourceDifferences(
+ TermInfoComparisonResult result
+ ) {
+ ArgumentNullException.ThrowIfNull( result );
+
+ return result.Differences
+ .Select(
+ difference =>
+ string.Join(
+ "|",
+ difference.Kind,
+ difference.CapabilityName ?? string.Empty,
+ FormatNullableInteger( difference.LeftSourceEntryIndex ),
+ FormatNullableInteger( difference.RightSourceEntryIndex ),
+ FormatNullableInteger( difference.LeftSourceFieldIndex ),
+ FormatNullableInteger( difference.RightSourceFieldIndex ),
+ difference.LeftSourceField?.Kind.ToString() ?? string.Empty,
+ difference.RightSourceField?.Kind.ToString() ?? string.Empty,
+ difference.LeftSourceField?.CapabilityName ?? string.Empty,
+ difference.RightSourceField?.CapabilityName ?? string.Empty,
+ difference.LeftSourceField?.ReferenceName ?? string.Empty,
+ difference.RightSourceField?.ReferenceName ?? string.Empty,
+ FormatNullableInteger( difference.LeftSourceField?.NumericValue ),
+ FormatNullableInteger( difference.RightSourceField?.NumericValue ),
+ difference.LeftSourceField?.StringValue ?? string.Empty,
+ difference.RightSourceField?.StringValue ?? string.Empty
+ )
+ )
+ .ToArray();
+ }
+
+ private static string DescribeAliases(
+ IReadOnlyList? aliases
+ ) {
+ return aliases is null
+ ? string.Empty
+ : string.Join( ",", aliases );
+ }
+
+ private static string DescribeCapabilityValue(
+ TermInfoCapabilityValue? value
+ ) {
+ if ( !value.HasValue ) {
+ return string.Empty;
+ }
+
+ TermInfoCapabilityValue actual =
+ value.Value;
+ return actual.Kind switch {
+ TermInfoCapabilityValueKind.Boolean =>
+ actual.BooleanValue ? "bool:1" : "bool:0",
+ TermInfoCapabilityValueKind.Number =>
+ "num:"
+ + actual.NumberValue.ToString(
+ CultureInfo.InvariantCulture
+ ),
+ TermInfoCapabilityValueKind.String =>
+ "str:"
+ + Convert.ToHexString(
+ Encoding.UTF8.GetBytes(
+ actual.StringValue
+ )
+ ),
+ _ => throw new InvalidOperationException(
+ $"Unsupported capability value kind '{actual.Kind}'."
+ ),
+ };
+ }
+
+ private static string FormatNullableInteger(
+ int? value
+ ) {
+ return value.HasValue
+ ? value.Value.ToString( CultureInfo.InvariantCulture )
+ : string.Empty;
+ }
+
+ private static string[] ReadProjectReferences(
+ XDocument project
+ ) {
+ ArgumentNullException.ThrowIfNull( project );
+
+ return project
+ .Descendants()
+ .Where(
+ element => element.Name.LocalName == "ProjectReference"
+ )
+ .Select(
+ element =>
+ element.Attribute( "Include" )?.Value
+ ?? string.Empty
+ )
+ .OrderBy(
+ value => value,
+ StringComparer.Ordinal
+ )
+ .ToArray();
+ }
+
+ private static string ReadRepositoryFile(
+ string root,
+ string relativePath
+ ) {
+ ArgumentException.ThrowIfNullOrWhiteSpace( root );
+ ArgumentException.ThrowIfNullOrWhiteSpace( relativePath );
+
+ return File.ReadAllText(
+ Path.Combine(
+ root,
+ relativePath.Replace(
+ '/',
+ Path.DirectorySeparatorChar
+ )
+ )
+ );
+ }
+
+ private static string FindRepositoryRoot() {
+ DirectoryInfo? current =
+ new( AppContext.BaseDirectory );
+
+ while ( current is not null ) {
+ if ( File.Exists(
+ Path.Combine(
+ current.FullName,
+ "Icod.TermInfo.sln"
+ )
+ ) ) {
+ return current.FullName;
+ }
+
+ current = current.Parent;
+ }
+
+ throw new InvalidOperationException(
+ "Unable to locate the Icod.TermInfo repository root."
+ );
+ }
+}
diff --git a/tests/Icod.TermInfo.Source.Tests/src/S01ContractTests.cs b/tests/Icod.TermInfo.Source.Tests/src/S01ContractTests.cs
index 5927802..ad04e25 100644
--- a/tests/Icod.TermInfo.Source.Tests/src/S01ContractTests.cs
+++ b/tests/Icod.TermInfo.Source.Tests/src/S01ContractTests.cs
@@ -4,7 +4,7 @@
namespace Icod.TermInfo.Source.Tests;
public sealed class S01ContractTests {
- private const string DevelopmentVersion = "1.3.0-Alpha-5";
+ private const string DevelopmentVersion = "1.3.0";
private const string StableAssemblyVersion = "1.0.0.0";
[Fact]
diff --git a/tests/Icod.TermInfo.Source.Tests/src/S02ContractTests.cs b/tests/Icod.TermInfo.Source.Tests/src/S02ContractTests.cs
index dd05414..c84b95f 100644
--- a/tests/Icod.TermInfo.Source.Tests/src/S02ContractTests.cs
+++ b/tests/Icod.TermInfo.Source.Tests/src/S02ContractTests.cs
@@ -7,7 +7,7 @@ namespace Icod.TermInfo.Source.Tests;
public sealed class S02ContractTests
{
- private const string DevelopmentVersion = "1.3.0-Alpha-5";
+ private const string DevelopmentVersion = "1.3.0";
private const string StableAssemblyVersion = "1.0.0.0";
[Fact]
diff --git a/tests/Icod.TermInfo.Source.Tests/src/S03ContractTests.cs b/tests/Icod.TermInfo.Source.Tests/src/S03ContractTests.cs
index c7801be..037a29d 100644
--- a/tests/Icod.TermInfo.Source.Tests/src/S03ContractTests.cs
+++ b/tests/Icod.TermInfo.Source.Tests/src/S03ContractTests.cs
@@ -7,7 +7,7 @@ namespace Icod.TermInfo.Source.Tests;
public sealed class S03ContractTests
{
- private const string DevelopmentVersion = "1.3.0-Alpha-5";
+ private const string DevelopmentVersion = "1.3.0";
private const string StableAssemblyVersion = "1.0.0.0";
[Fact]
diff --git a/tests/Icod.TermInfo.Source.Tests/src/S04ContractTests.cs b/tests/Icod.TermInfo.Source.Tests/src/S04ContractTests.cs
index 5fa7eea..08f4352 100644
--- a/tests/Icod.TermInfo.Source.Tests/src/S04ContractTests.cs
+++ b/tests/Icod.TermInfo.Source.Tests/src/S04ContractTests.cs
@@ -7,7 +7,7 @@
namespace Icod.TermInfo.Source.Tests;
public sealed class S04ContractTests {
- private const string DevelopmentVersion = "1.3.0-Alpha-5";
+ private const string DevelopmentVersion = "1.3.0";
private const string StableAssemblyVersion = "1.0.0.0";
[Fact] public void SourceAndRuntimePackagesAdvanceTogetherWithoutChangingAssemblyIdentity() {
diff --git a/tests/Icod.TermInfo.Source.Tests/src/S05ContractTests.cs b/tests/Icod.TermInfo.Source.Tests/src/S05ContractTests.cs
index 8ddeab1..b2189ea 100644
--- a/tests/Icod.TermInfo.Source.Tests/src/S05ContractTests.cs
+++ b/tests/Icod.TermInfo.Source.Tests/src/S05ContractTests.cs
@@ -7,7 +7,7 @@ namespace Icod.TermInfo.Source.Tests;
public sealed class S05ContractTests
{
- private const string DevelopmentVersion = "1.3.0-Alpha-5";
+ private const string DevelopmentVersion = "1.3.0";
private const string StableAssemblyVersion = "1.0.0.0";
[Fact] public void SourceAndRuntimePackagesAdvanceTogetherWithoutChangingAssemblyIdentity()
diff --git a/tests/Icod.TermInfo.Source.Tests/src/S06ContractTests.cs b/tests/Icod.TermInfo.Source.Tests/src/S06ContractTests.cs
index c88a3bc..6a07e4e 100644
--- a/tests/Icod.TermInfo.Source.Tests/src/S06ContractTests.cs
+++ b/tests/Icod.TermInfo.Source.Tests/src/S06ContractTests.cs
@@ -7,7 +7,7 @@
namespace Icod.TermInfo.Source.Tests;
public sealed class S06ContractTests {
- private const string DevelopmentVersion = "1.3.0-Alpha-5";
+ private const string DevelopmentVersion = "1.3.0";
private const string StableAssemblyVersion = "1.0.0.0";
[Fact] public void SourceAndRuntimePackagesAdvanceTogetherWithoutChangingAssemblyIdentity() {
diff --git a/tests/Icod.TermInfo.Source.Tests/src/S07ContractTests.cs b/tests/Icod.TermInfo.Source.Tests/src/S07ContractTests.cs
index d67bbc5..c779875 100644
--- a/tests/Icod.TermInfo.Source.Tests/src/S07ContractTests.cs
+++ b/tests/Icod.TermInfo.Source.Tests/src/S07ContractTests.cs
@@ -7,7 +7,7 @@ namespace Icod.TermInfo.Source.Tests;
public sealed class S07ContractTests
{
- private const string DevelopmentVersion = "1.3.0-Alpha-5";
+ private const string DevelopmentVersion = "1.3.0";
private const string StableAssemblyVersion = "1.0.0.0";
[Fact] public void SourceAndRuntimePackagesAdvanceTogetherWithoutChangingAssemblyIdentity()
diff --git a/tests/Icod.TermInfo.Source.Tests/src/S08ContractTests.cs b/tests/Icod.TermInfo.Source.Tests/src/S08ContractTests.cs
index 0f510cd..8eb42c3 100644
--- a/tests/Icod.TermInfo.Source.Tests/src/S08ContractTests.cs
+++ b/tests/Icod.TermInfo.Source.Tests/src/S08ContractTests.cs
@@ -7,7 +7,7 @@
namespace Icod.TermInfo.Source.Tests;
public sealed class S08ContractTests {
- private const string DevelopmentVersion = "1.3.0-Alpha-5";
+ private const string DevelopmentVersion = "1.3.0";
private const string StableAssemblyVersion = "1.0.0.0";
[Fact] public void SourceAndRuntimePackagesAdvanceTogetherWithoutChangingAssemblyIdentity() {
diff --git a/tests/Icod.TermInfo.Source.Tests/src/S09ContractTests.cs b/tests/Icod.TermInfo.Source.Tests/src/S09ContractTests.cs
index 46e295e..ef616b8 100644
--- a/tests/Icod.TermInfo.Source.Tests/src/S09ContractTests.cs
+++ b/tests/Icod.TermInfo.Source.Tests/src/S09ContractTests.cs
@@ -5,7 +5,7 @@
namespace Icod.TermInfo.Source.Tests;
public sealed class S09ContractTests {
- private const string DevelopmentVersion = "1.3.0-Alpha-5";
+ private const string DevelopmentVersion = "1.3.0";
private const string StableAssemblyVersion = "1.0.0.0";
[Fact] public void SourceAndRuntimePackagesAdvanceTogetherWithoutChangingAssemblyIdentity() {
diff --git a/tests/Icod.TermInfo.Tests/src/T45CompletionGateTests.cs b/tests/Icod.TermInfo.Tests/src/T45CompletionGateTests.cs
index 0324e2f..2f8024a 100644
--- a/tests/Icod.TermInfo.Tests/src/T45CompletionGateTests.cs
+++ b/tests/Icod.TermInfo.Tests/src/T45CompletionGateTests.cs
@@ -7,7 +7,7 @@ namespace Icod.TermInfo.Tests;
public sealed class T45CompletionGateTests {
[Fact]
- public void AssemblyRetainsStableIdentityForOneThreeDevelopment() {
+ public void AssemblyRetainsStableIdentityForOneThreeRelease() {
Assembly assembly =
typeof( TerminalDescription ).Assembly;
AssemblyName assemblyName =
@@ -30,12 +30,12 @@ public void AssemblyRetainsStableIdentityForOneThreeDevelopment() {
2 )[ 0 ];
Assert.Equal(
- "1.3.0-Alpha-5",
+ "1.3.0",
semanticVersion );
}
[Fact]
- public void ProjectMetadataIdentifiesOneThreeDevelopmentAndStableAssembly() {
+ public void ProjectMetadataIdentifiesOneThreeReleaseAndStableAssembly() {
string root =
FindRepositoryRoot();
XDocument project =
@@ -46,12 +46,12 @@ public void ProjectMetadataIdentifiesOneThreeDevelopmentAndStableAssembly() {
LoadOptions.None );
Assert.Equal(
- "1.3.0-Alpha-5",
+ "1.3.0",
ReadRequiredProperty(
project,
"Version" ) );
Assert.Equal(
- "1.3.0-Alpha-5",
+ "1.3.0",
ReadRequiredProperty(
project,
"PackageVersion" ) );
@@ -78,16 +78,19 @@ public void FinalReadmeUsesStablePackageVersionAndPolicies() {
"README.md" ) );
Assert.Contains(
- "dotnet add package Icod.TermInfo --version 1.2.0",
+ "dotnet add package Icod.TermInfo --version 1.3.0",
readme );
Assert.Contains(
- "dotnet add package Icod.TermInfo.Source --version 1.2.0",
+ "dotnet add package Icod.TermInfo.Source --version 1.3.0",
readme );
Assert.Contains(
- "dotnet add package Icod.TermInfo.Compiler --version 1.2.0",
+ "dotnet add package Icod.TermInfo.Compiler --version 1.3.0",
+ readme );
+ Assert.Contains(
+ "dotnet add package Icod.TermInfo.Inspection --version 1.3.0",
readme );
Assert.DoesNotContain(
- "1.2.0-Alpha-7",
+ "1.3.0-Alpha-7",
readme );
Assert.Contains(
"docs/VERSIONING.md",
@@ -96,7 +99,7 @@ public void FinalReadmeUsesStablePackageVersionAndPolicies() {
"docs/COMPATIBILITY.md",
readme );
Assert.Contains(
- "docs/1.2.0-RELEASE-AUDIT.md",
+ "docs/1.3.0-RELEASE-AUDIT.md",
readme );
}
@@ -145,6 +148,9 @@ in new[]
Assert.Contains(
"1.2.0-COMPILER-PUBLIC-API-BASELINE.txt",
verifier );
+ Assert.Contains(
+ "1.3.0-INSPECTION-PUBLIC-API-BASELINE.txt",
+ verifier );
Assert.True(
verifier.Contains(
"package-smoke",
@@ -157,11 +163,19 @@ in new[]
verifier.Contains(
"compiler-package-smoke",
StringComparison.OrdinalIgnoreCase ) );
+ Assert.True(
+ verifier.Contains(
+ "inspection-package-smoke",
+ StringComparison.OrdinalIgnoreCase ) );
+ Assert.True(
+ verifier.Contains(
+ "inspection-package-verifier",
+ StringComparison.OrdinalIgnoreCase ) );
}
}
[Fact]
- public void OneTwoReleaseAuditDefinesTagBasedReleaseSignOff() {
+ public void OneThreeReleaseAuditDefinesTagBasedReleaseSignOff() {
string root =
FindRepositoryRoot();
string audit =
@@ -169,7 +183,7 @@ public void OneTwoReleaseAuditDefinesTagBasedReleaseSignOff() {
Path.Combine(
root,
"docs",
- "1.2.0-RELEASE-AUDIT.md" ) );
+ "1.3.0-RELEASE-AUDIT.md" ) );
Assert.Contains(
"Final release candidate",
@@ -183,6 +197,9 @@ public void OneTwoReleaseAuditDefinesTagBasedReleaseSignOff() {
Assert.Contains(
"docs/1.2.0-COMPILER-PUBLIC-API-BASELINE.txt",
audit );
+ Assert.Contains(
+ "docs/1.3.0-INSPECTION-PUBLIC-API-BASELINE.txt",
+ audit );
Assert.Contains(
"verify-release-package",
audit );
@@ -190,7 +207,7 @@ public void OneTwoReleaseAuditDefinesTagBasedReleaseSignOff() {
"release.yaml",
audit );
Assert.Contains(
- "v1.2.0",
+ "v1.3.0",
audit );
}
diff --git a/tools/inspection-package-smoke/Program.cs b/tools/inspection-package-smoke/Program.cs
index 1957090..5a1b8de 100644
--- a/tools/inspection-package-smoke/Program.cs
+++ b/tools/inspection-package-smoke/Program.cs
@@ -31,15 +31,19 @@ string message
Type[] exportedTypes =
inspectionAssembly.GetExportedTypes();
Require(
- exportedTypes.Length == 7
+ exportedTypes.Length == 11
&& exportedTypes.Contains( typeof( TermInfoComparisonResult ) )
&& exportedTypes.Contains( typeof( TermInfoDifference ) )
&& exportedTypes.Contains( typeof( TermInfoDifferenceKind ) )
+ && exportedTypes.Contains( typeof( TermInfoInspectionComparison ) )
+ && exportedTypes.Contains( typeof( TermInfoInspectionEngine ) )
+ && exportedTypes.Contains( typeof( TermInfoInspectionResult ) )
+ && exportedTypes.Contains( typeof( TermInfoInspectionTarget ) )
&& exportedTypes.Contains( typeof( TermInfoSourceComparer ) )
&& exportedTypes.Contains( typeof( TermInfoSourceRenderer ) )
&& exportedTypes.Contains( typeof( TerminalDescriptionComparer ) )
&& exportedTypes.Contains( typeof( TerminalDescriptionSourceRenderer ) ),
- "The Inspection package did not expose exactly the reviewed I02-I05 surface."
+ "The Inspection package did not expose exactly the reviewed I02-I06 surface."
);
Require(
@@ -169,6 +173,45 @@ alteredParsed.Document.Entries[ 0 ]
"The I05 source-aware comparer did not report the local numeric difference."
);
+InMemoryTerminalDescriptionProvider provider =
+ new(
+ new[] {
+ terminal,
+ }
+ );
+TermInfoInspectionTarget inspectionTarget =
+ new(
+ provider,
+ "inspection-smoke",
+ "package smoke provider"
+ );
+TermInfoInspectionResult inspected =
+ TermInfoInspectionEngine.Inspect(
+ inspectionTarget
+ );
+Require(
+ ReferenceEquals( inspected.Target, inspectionTarget )
+ && ReferenceEquals( inspected.Terminal, terminal )
+ && inspected.Target.DisplayName == "package smoke provider",
+ "The I06 inspection engine did not retain target and terminal identity."
+);
+Require(
+ TermInfoInspectionEngine.Render( inspected ) == rendered,
+ "The I06 inspection engine did not delegate to canonical effective rendering."
+);
+TermInfoInspectionComparison inspectionComparison =
+ TermInfoInspectionEngine.Compare(
+ inspected,
+ inspected
+ );
+Require(
+ inspectionComparison.AreEqual
+ && inspectionComparison.Comparison.Differences.Count == 0
+ && ReferenceEquals( inspectionComparison.Left, inspected )
+ && ReferenceEquals( inspectionComparison.Right, inspected ),
+ "The I06 inspection engine did not preserve acquired results during comparison."
+);
+
Console.WriteLine(
"Icod.TermInfo.Inspection package smoke test passed."
);