diff --git a/CHANGELOG.md b/CHANGELOG.md index 08f3ff28c..49f6d5b80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,28 @@ Notable changes to `Icod.Terminal` are recorded here for consumers who need a concise release history. Detailed design evidence remains in the versioned roadmaps, tranche records, and public-API baseline documents. +## 1.11.1 + +### TermInfo persistent-raster integration contract + +- Adds a dedicated three-TFM integration-contract test project which references `Icod.TermInfo.Inspection 1.11.0` without adding Inspection or Source to the production `Icod.Terminal` dependency graph. +- Defines and tests the consumer-owned translation from conclusive live Terminal capability status to protocol-neutral TermInfo `Verified` lifecycle evidence. `Unknown`, `Advertised`, unrelated capabilities, and endpoint unavailability are not promoted. +- Proves static persistent-raster lifecycle planning can transition from `Indeterminate` through Terminal-owned live verification and caller-owned evidence to a deterministic successful replan. +- Proves verified persistent non-support produces an `Impossible` lifecycle plan without attempting persistent resource creation. +- Proves exact static Icod lifecycle declarations can produce a successful TermInfo plan without planning I/O while Terminal independently retains authority over live endpoint usability and execution. +- Adds `Icod.Terminal.TermInfoPersistentRaster.Sample`, executable architecture documentation for static inspection -> semantic planning -> optional live verification -> caller-owned evidence -> replan -> opaque Terminal resource/placement execution. + +### Compatibility and qualification + +- Adds no production public API and intentionally retains the final 1.11 public API fingerprint `9336a1f6def1c4b02e86db813bae27f45b95af33f47a2cf10dccd4d1d44324f2`. +- Preserves production dependencies at `Icod.TermInfo 1.11.0` and `Icod.Timing 1.0.0`; `Icod.TermInfo.Inspection 1.11.0` remains test/sample-only and `Icod.TermInfo.Source` is not introduced. +- Makes no intentional change to the persistent-raster wire protocol, acknowledgement, ownership, generation invalidation, cleanup, routing, or bounded-resource semantics released in 1.11.0. +- Retains `net8.0`, `net9.0`, and `net10.0`, Windows/Linux/macOS Staging validation, current package/downstream gates, and the stable `1.0.0` compatibility floor. +- T1111-A through T1111-D completed full pull-request qualification on exact head `651af888875a0d2acababe46a4e1313532ec7ba6` in workflow `#1628 / 34704455179`. +- Deliberately defers a table-driven `TerminalTermInfoSemanticEvidence` cleanup, static lifecycle-evidence ingestion, and any adapter package to later independent design work. + +See `docs/releases/1.11.1.md` and `Icod.Terminal-1.11.1-Development-Roadmap.md` for the complete patch-release integration contract. + ## 1.11.0 ### Persistent raster resources and placements diff --git a/Directory.Build.props b/Directory.Build.props index d771b0499..5e04cf91d 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,7 +1,7 @@ - 1.11.0 + 1.11.1 $(VersionPrefix) $(VersionPrefix)-$(VersionSuffix) diff --git a/Icod.Terminal-1.11.1-Development-Roadmap.md b/Icod.Terminal-1.11.1-Development-Roadmap.md new file mode 100644 index 000000000..48335b7a0 --- /dev/null +++ b/Icod.Terminal-1.11.1-Development-Roadmap.md @@ -0,0 +1,325 @@ +# Icod.Terminal 1.11.1 Development Roadmap + +**Release:** `1.11.1` +**Theme:** TermInfo 1.11 ↔ Terminal 1.11 persistent-raster integration contract +**Status:** T1111-A through T1111-E accepted; final status-only PR head qualification pending +**Development version:** stable `1.11.1` release candidate +**Stable compatibility floor:** `1.0.0` +**Prior release:** `1.11.0` +**TermInfo Runtime dependency:** `Icod.TermInfo 1.11.0` + +## Why this patch release exists + +`Icod.Terminal 1.11.0` completed the live persistent-raster ownership/runtime layer: capability verification, acknowledged terminal-resident resource upload, placement creation/update, lifecycle invalidation, deterministic cleanup, and opaque public resource/placement handles. + +`Icod.TermInfo 1.11.0` independently completed the protocol-neutral planning layer: persistent-raster lifecycle evidence, provenance, classification, semantic planning, description/database-set composition, and version-3 machine-readable profile/plan documents through `Icod.TermInfo.Inspection`. + +Both releases intentionally preserve a loose architectural boundary: + +```text +Icod.TermInfo.Inspection + static/advisory evidence + classification + uncertainty / contradiction + semantic planning + +consumer-owned integration + optional live verification + evidence strengthening + reclassification / replanning + +Icod.Terminal + live endpoint ownership + bounded protocol verification + persistent resource identity + placement identity + generation invalidation + terminal-side execution / cleanup +``` + +Version `1.11.1` exists to prove that boundary end-to-end through an executable sample and deterministic contract tests without making Inspection or Source production dependencies of `Icod.Terminal`. + +The implementation plan is: + +[`docs/superpowers/plans/2026-09-12-1.11.1-terminfo-persistent-raster-integration.md`](docs/superpowers/plans/2026-09-12-1.11.1-terminfo-persistent-raster-integration.md) + +The runtime ownership authority remains: + +[`docs/Persistent-Raster-Ownership.md`](docs/Persistent-Raster-Ownership.md) + +The TermInfo semantic authority is: + +```text +https://github.com/uniblab/Icod.TermInfo/blob/main/docs/1.11.0-PERSISTENT-RASTER-LIFECYCLE-GUIDE.md +``` + +The release-closure record is: + +[`docs/T1111-E-1.11.1-Release-Closure.md`](docs/T1111-E-1.11.1-Release-Closure.md) + +## Release objectives + +Version 1.11.1 must: + +1. demonstrate TermInfo 1.11 static persistent-raster lifecycle inspection and planning over the exact `TerminalDescription` selected by a live `TerminalSession`; +2. preserve `Icod.TermInfo.Inspection` as an optional consumer/sample/test dependency rather than a production `Icod.Terminal` dependency; +3. prove an initially indeterminate TermInfo plan can be strengthened by Terminal-owned live verification and caller-owned `Verified` lifecycle evidence; +4. prove the strengthened profile can be reclassified/replanned deterministically before runtime execution; +5. prove verified non-support becomes an impossible semantic plan without attempting persistent-raster execution; +6. demonstrate the successful plan executing through the existing opaque `TerminalRasterResource` / `TerminalRasterPlacement` API only; +7. keep all Kitty/private protocol identities and command details out of the integration contract; +8. preserve the exact 1.11.0 public API and stable 1.0 compatibility floor; +9. preserve the production package graph: Runtime `Icod.TermInfo 1.11.0` plus `Icod.Timing 1.0.0`, with no new Inspection/Source production dependency; +10. qualify the complete integration contract on Windows, Linux, macOS and `net8.0`, `net9.0`, `net10.0`. + +All ten objectives are satisfied by the accepted T1111-E release candidate described below. + +## Frozen integration direction + +The intended consumer flow is: + +```text +TerminalSession.Terminal + -> PersistentRasterLifecycleInspector.Inspect(...) + -> PersistentRasterLifecyclePlanner.Plan(...) + +if plan == Indeterminate + -> TerminalSession.VerifyCapabilityAsync(PersistentRasterGraphics) + -> consumer maps Terminal live result to Verified lifecycle evidence + -> PersistentRasterLifecycleClassifier.Classify(...) + -> PersistentRasterLifecyclePlanner.Plan(...) + +if plan == Success + -> TerminalSession.CreateRasterResourceAsync(...) + -> TerminalRasterResource.CreatePlacementAsync(...) + -> TerminalRasterPlacement.UpdateAsync(...) + -> dispose placement/resource + +if plan == Impossible + -> do not attempt persistent-raster execution +``` + +The TermInfo request used by the core integration contract covers: + +```text +persistent resource upload +acknowledged upload +one placement +placement update +placement deletion +resource deletion +``` + +The sample may explain ordinary raster separately, but persistent and ephemeral intent must not be mixed into one TermInfo lifecycle request. + +## Consumer-owned verified evidence mapping + +Terminal's public `PersistentRasterGraphics` capability is deliberately coarser than TermInfo's lifecycle profile. For this integration contract, a consumer-owned bridge may interpret a **Terminal live verification result** as follows: + +```text +RasterGraphics == Verified + -> RasterDisplay positive Verified evidence + +PersistentRasterGraphics == Verified + -> PersistentUpload positive Verified evidence + -> AcknowledgedUpload positive Verified evidence + -> PlacementCreation positive Verified evidence + -> MultiplePlacements positive Verified evidence + -> PlacementUpdate positive Verified evidence + -> PlacementDeletion positive Verified evidence + -> ResourceDeletion positive Verified evidence + +PersistentRasterGraphics == Unsupported + -> the seven persistent lifecycle subjects above receive negative Verified evidence +``` + +This mapping is sample/test integration policy, not a new TermInfo or Terminal production API contract. + +`Unknown`, `Advertised`, or endpoint unavailability must not be promoted to caller-owned `Verified` support/non-support. + +## Tranche roadmap + +```text +T1111-A integration dependency boundary and dedicated contract-test project accepted +T1111-B consumer-owned live-evidence mapping contract accepted +T1111-C static-plan → live-verification → replan contract tests accepted +T1111-D protocol-neutral executable integration sample accepted +T1111-E package/downstream qualification and 1.11.1 release closure accepted +``` + +## Accepted A–D integration checkpoint + +T1111-A through T1111-D are accepted on exact head: + +```text +651af888875a0d2acababe46a4e1313532ec7ba6 +``` + +by pull-request workflow: + +```text +#1628 / 34704455179 +``` + +That exact head passed: + +- Runtime Windows; +- Runtime Linux; +- Runtime macOS; +- package candidate/public API freeze; +- Package Foundation; +- Package Presentation; +- Package Semantic and hardening; +- Package Stable 1.x release line; +- validated package artifact. + +The accepted checkpoint includes the dedicated integration-contract tests and solution-built executable sample on `net8.0`, `net9.0`, and `net10.0`. + +## T1111-A — integration dependency boundary + +Completed. The dedicated `Icod.Terminal.TermInfoIntegration.Tests` project references `Icod.Terminal` plus `Icod.TermInfo.Inspection 1.11.0` without adding Inspection or Source to `Icod.Terminal.csproj`. + +Acceptance proves: + +- the production package graph is unchanged; +- the integration project builds/tests on all three TFMs; +- the project is part of normal solution/CI verification; +- package verification continues to reject unexpected production dependencies. + +## T1111-B — consumer-owned live-evidence mapping + +Completed. The integration-only bridge maps only conclusive live Terminal status into TermInfo `PersistentRasterLifecycleEvidenceKind.Verified` assertions. + +Acceptance covers: + +- positive persistent verification; +- verified persistent non-support; +- ordinary raster verification; +- no promotion of `Unknown` or `Advertised` status to `Verified` evidence; +- endpoint unavailability remaining distinct from terminal non-support; +- deterministic provenance labels and ordinals; +- no protocol/backend/private-id leakage. + +## T1111-C — plan/verify/replan contract + +Completed with deterministic integration tests proving: + +1. an ordinary terminal description with no lifecycle declarations produces an `Indeterminate` persistent request; +2. Terminal-owned live verification strengthens knowledge through caller-owned evidence; +3. reclassification/replanning becomes `Success` when live support is verified; +4. verified non-support replans to `Impossible`; +5. a description carrying the exact Icod extended Boolean lifecycle declarations can produce a successful static plan without planning I/O; +6. planning itself never performs terminal I/O or allocates Terminal runtime identities. + +## T1111-D — executable integration sample + +Completed under: + +```text +samples/Icod.Terminal.TermInfoPersistentRaster.Sample/ +``` + +The sample is executable architecture documentation for: + +```text +selected TerminalDescription + -> TermInfo Inspection profile + -> TermInfo lifecycle request / plan + -> optional Terminal live verification + -> caller-owned Verified evidence + -> reclassify / replan + -> Terminal persistent resource + -> placement create/update/dispose +``` + +It does not expose Kitty image ids, placement ids, raw APC commands, terminal-brand tests, or backend-selection policy in its user-facing flow. When execution is unavailable or the semantic plan is not successful, it reports the semantic state and does not pretend the persistent operation ran. + +The solution compiles the sample for all supported TFMs; deterministic contract tests own simulated live execution coverage. + +## T1111-E — qualification and release closure + +Completed. The stable `1.11.1` release candidate advances: + +- repository/package version to `1.11.1` with no prerelease suffix; +- NuGet release notes to the 1.11.1 integration contract; +- root README installation/integration guidance; +- final `CHANGELOG.md` 1.11.1 entry; +- curated `docs/releases/1.11.1.md` notes; +- dedicated release-closure record at `docs/T1111-E-1.11.1-Release-Closure.md`. + +The accepted stable release-candidate code/metadata head is: + +```text +53ded168ae03d14e5490d290fe7730579a1456b3 +``` + +and the qualifying pull-request workflow is: + +```text +#1630 / 34705075656 +``` + +That exact head passed: + +- Runtime Windows; +- Runtime Linux; +- Runtime macOS; +- package candidate/public API freeze; +- Package Foundation; +- Package Presentation; +- Package Semantic and hardening; +- Package Stable 1.x release line; +- validated package artifact. + +The package-candidate gate generated identical normalized public API snapshots for `net8.0`, `net9.0`, and `net10.0`, all matching the frozen fingerprint: + +```text +9336a1f6def1c4b02e86db813bae27f45b95af33f47a2cf10dccd4d1d44324f2 +``` + +Inspection of the produced `Icod.Terminal.1.11.1.nupkg` confirms every TFM dependency group contains only: + +```text +Icod.TermInfo 1.11.0 +Icod.Timing 1.0.0 +``` + +`Icod.TermInfo.Inspection` and `Icod.TermInfo.Source` are absent from the production package graph. The stable release-line package consumer and published `Icod.DCurses` hardening soak also passed on all three TFMs. + +This roadmap/status-only closure update contains no production code or release-metadata change. Its own pull-request workflow must pass the same matrix before PR #54 leaves draft status; no further roadmap mutation is required merely to insert that later run number. + +## Explicit exclusions + +Version 1.11.1 does **not** include: + +- a production dependency on `Icod.TermInfo.Inspection`; +- a production dependency on `Icod.TermInfo.Source`; +- new public Terminal planning/profile/evidence types; +- direct ingestion of all eight TermInfo lifecycle dimensions into Terminal's coarse capability model; +- an adapter NuGet package between TermInfo and Terminal; +- changes to the persistent-raster wire protocol or runtime ownership semantics frozen by 1.11.0; +- a table-driven rewrite of `TerminalTermInfoSemanticEvidence` unless scope is explicitly widened after maintainer review; +- scene/window/cell/layout policy belonging to `Icod.DCurses`. + +## Approved future follow-up after 1.11.1 + +The next independent internal cleanup is to table-drive `TerminalTermInfoSemanticEvidence` so exact TermInfo implementations and metadata-backed backend advertisements are described through reviewed immutable contracts instead of duplicated conditionals/switches. + +That work must preserve behavior, package dependencies, public API, and existing evidence semantics and should be reviewed independently from this integration patch. + +Static lifecycle-evidence ingestion and any adapter package remain later architectural decisions. + +## Release gate + +The repository's established progression remains: + +```text +written contract + -> focused RED tests + -> minimal GREEN integration + -> sample/documentation closure + -> full Windows/Linux/macOS + package qualification + -> exact-head acceptance +``` + +T1111-A through T1111-E are accepted. Once this status-only closure head passes the same Staging matrix, PR #54 may be marked ready for maintainer review. Merge, mainline Release validation, tag, and publication remain maintainer actions. diff --git a/Icod.Terminal.csproj b/Icod.Terminal.csproj index d62b31f93..a582d04fb 100644 --- a/Icod.Terminal.csproj +++ b/Icod.Terminal.csproj @@ -39,7 +39,7 @@ Icod.Terminal Timothy J. Bruce Managed, cross-platform live-terminal session, endpoint, mode, input, lifecycle, and terminal-control foundation for .NET. - Icod.Terminal 1.11.0 adds backend-neutral persistent terminal-resident raster resources and placements through TerminalCapability.PersistentRasterGraphics, TerminalRasterResource, and TerminalRasterPlacement. It keeps Kitty protocol identities private, bounds ownership, invalidates stale terminal-resident identities instead of replaying hidden image copies, and preserves the stable 1.0 compatibility floor. Full notes: https://github.com/uniblab/Icod.Terminal/blob/main/docs/releases/1.11.0.md ; compatibility: https://github.com/uniblab/Icod.Terminal/blob/main/docs/Compatibility-and-Versioning.md + Icod.Terminal 1.11.1 adds the executable TermInfo 1.11 to Terminal 1.11 persistent-raster integration contract through a dedicated three-TFM contract-test project and non-packable sample. It preserves the 1.11.0 public API, persistent-raster runtime semantics, and production dependencies; Icod.TermInfo.Inspection 1.11.0 remains test/sample-only. Full notes: https://github.com/uniblab/Icod.Terminal/blob/main/docs/releases/1.11.1.md ; compatibility: https://github.com/uniblab/Icod.Terminal/blob/main/docs/Compatibility-and-Versioning.md README.md icon.png https://github.com/uniblab/Icod.Terminal diff --git a/Icod.Terminal.sln b/Icod.Terminal.sln index e9f26c847..de21eebdd 100644 --- a/Icod.Terminal.sln +++ b/Icod.Terminal.sln @@ -8,6 +8,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{1CD421BA EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Icod.Terminal.Tests", "tests\Icod.Terminal.Tests\Icod.Terminal.Tests.csproj", "{4BC77390-0FD3-40AA-A6AF-F4BA9B457C56}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Icod.Terminal.TermInfoIntegration.Tests", "tests\Icod.Terminal.TermInfoIntegration.Tests\Icod.Terminal.TermInfoIntegration.Tests.csproj", "{FD8F4AA3-F304-4679-B234-F3DCE1058CF7}" +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{4C994810-850E-4753-82E5-4BD346EF574C}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Icod.Terminal.Sample", "samples\Icod.Terminal.Sample\Icod.Terminal.Sample.csproj", "{7093D49E-2483-414C-9DE0-1B668B4DAC10}" @@ -36,6 +38,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Icod.Terminal.SemanticPromp EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Icod.Terminal.Color.Sample", "samples\Icod.Terminal.Color.Sample\Icod.Terminal.Color.Sample.csproj", "{A74D6D0E-3AC2-46F0-9F89-2A86E1C4B713}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Icod.Terminal.TermInfoPersistentRaster.Sample", "samples\Icod.Terminal.TermInfoPersistentRaster.Sample\Icod.Terminal.TermInfoPersistentRaster.Sample.csproj", "{1DB62325-AE22-4F5F-AD13-FF30E60BF8B4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -55,6 +59,12 @@ Global {4BC77390-0FD3-40AA-A6AF-F4BA9B457C56}.Staging|Any CPU.Build.0 = Staging|Any CPU {4BC77390-0FD3-40AA-A6AF-F4BA9B457C56}.Release|Any CPU.ActiveCfg = Release|Any CPU {4BC77390-0FD3-40AA-A6AF-F4BA9B457C56}.Release|Any CPU.Build.0 = Release|Any CPU + {FD8F4AA3-F304-4679-B234-F3DCE1058CF7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FD8F4AA3-F304-4679-B234-F3DCE1058CF7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FD8F4AA3-F304-4679-B234-F3DCE1058CF7}.Staging|Any CPU.ActiveCfg = Staging|Any CPU + {FD8F4AA3-F304-4679-B234-F3DCE1058CF7}.Staging|Any CPU.Build.0 = Staging|Any CPU + {FD8F4AA3-F304-4679-B234-F3DCE1058CF7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FD8F4AA3-F304-4679-B234-F3DCE1058CF7}.Release|Any CPU.Build.0 = Release|Any CPU {7093D49E-2483-414C-9DE0-1B668B4DAC10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7093D49E-2483-414C-9DE0-1B668B4DAC10}.Debug|Any CPU.Build.0 = Debug|Any CPU {7093D49E-2483-414C-9DE0-1B668B4DAC10}.Staging|Any CPU.ActiveCfg = Staging|Any CPU @@ -133,12 +143,19 @@ Global {A74D6D0E-3AC2-46F0-9F89-2A86E1C4B713}.Staging|Any CPU.Build.0 = Staging|Any CPU {A74D6D0E-3AC2-46F0-9F89-2A86E1C4B713}.Release|Any CPU.ActiveCfg = Release|Any CPU {A74D6D0E-3AC2-46F0-9F89-2A86E1C4B713}.Release|Any CPU.Build.0 = Release|Any CPU + {1DB62325-AE22-4F5F-AD13-FF30E60BF8B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1DB62325-AE22-4F5F-AD13-FF30E60BF8B4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1DB62325-AE22-4F5F-AD13-FF30E60BF8B4}.Staging|Any CPU.ActiveCfg = Staging|Any CPU + {1DB62325-AE22-4F5F-AD13-FF30E60BF8B4}.Staging|Any CPU.Build.0 = Staging|Any CPU + {1DB62325-AE22-4F5F-AD13-FF30E60BF8B4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1DB62325-AE22-4F5F-AD13-FF30E60BF8B4}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {4BC77390-0FD3-40AA-A6AF-F4BA9B457C56} = {1CD421BA-D28F-406F-9076-164964741474} + {FD8F4AA3-F304-4679-B234-F3DCE1058CF7} = {1CD421BA-D28F-406F-9076-164964741474} {7093D49E-2483-414C-9DE0-1B668B4DAC10} = {4C994810-850E-4753-82E5-4BD346EF574C} {F8794364-A924-4B16-B27C-F54B6B1AE865} = {4C994810-850E-4753-82E5-4BD346EF574C} {B7E69C85-326A-4EA8-8E11-774FEAE6D9D3} = {4C994810-850E-4753-82E5-4BD346EF574C} @@ -152,5 +169,6 @@ Global {E1A8C5D2-7B3F-4C62-93A5-0F7D9E2146B8} = {4C994810-850E-4753-82E5-4BD346EF574C} {F3D40A5B-9C76-4A8F-BE11-247A6E5C933D} = {4C994810-850E-4753-82E5-4BD346EF574C} {A74D6D0E-3AC2-46F0-9F89-2A86E1C4B713} = {4C994810-850E-4753-82E5-4BD346EF574C} + {1DB62325-AE22-4F5F-AD13-FF30E60BF8B4} = {4C994810-850E-4753-82E5-4BD346EF574C} EndGlobalSection EndGlobal diff --git a/README.md b/README.md index 18986ac01..2ca7a61e2 100644 --- a/README.md +++ b/README.md @@ -9,14 +9,14 @@ ## Status -`1.11.0` is the current stable feature line. It adds backend-neutral persistent terminal-resident raster resources and placements above the existing raw-raster and semantic capability-planning foundations. +`1.11.1` is the current stable patch line. It preserves the 1.11.0 backend-neutral persistent terminal-resident raster API while adding executable documentation and deterministic contract tests for loose coupling with `Icod.TermInfo.Inspection 1.11.0` lifecycle planning. -The stable `1.0.0` compatibility floor remains unchanged. `Icod.Terminal` continues to preserve one authoritative live input path, bounded query/protocol handling, lifecycle-aware ownership, deterministic cleanup, and protocol-neutral public planning surfaces. +The stable `1.0.0` compatibility floor remains unchanged. `Icod.Terminal` continues to preserve one authoritative live input path, bounded query/protocol handling, lifecycle-aware ownership, deterministic cleanup, and protocol-neutral public planning surfaces. Version 1.11.1 adds no production public API and does not add Inspection or Source to the production dependency graph. ## Installation ```text -dotnet add package Icod.Terminal --version 1.11.0 +dotnet add package Icod.Terminal --version 1.11.1 ``` The package targets: @@ -49,6 +49,8 @@ terminal applications - `Icod.DCurses` owns higher-level cells, windows, virtual-screen state, layout, refresh/diff policy, damage, and curses presentation abstractions. - PTY/process hosting remains orthogonal to the `Icod.Terminal` runtime contract. +`Icod.TermInfo.Inspection` may be used by consumers for richer static planning, but it remains an optional consumer/test/sample dependency rather than a dependency of the `Icod.Terminal` package itself. + See [`docs/Architecture.md`](docs/Architecture.md). ## Quick start @@ -172,6 +174,24 @@ The library bounds live ownership to 256 persistent resources and 4096 placement See [`docs/Persistent-Raster-Ownership.md`](docs/Persistent-Raster-Ownership.md). +### TermInfo persistent-raster lifecycle integration + +Version 1.11.1 demonstrates how a consumer can combine `Icod.TermInfo.Inspection 1.11.0` static lifecycle planning with Terminal-owned live verification without making Inspection a production dependency: + +```text +session.Terminal + -> TermInfo lifecycle inspection + -> plan + -> if Indeterminate, optionally VerifyCapabilityAsync(PersistentRasterGraphics) + -> caller-owned Verified lifecycle evidence + -> reclassify / replan + -> if Success and live Terminal state is usable, execute resource/placement operations +``` + +The consumer-owned evidence bridge promotes only conclusive live observations. `Unknown`, `Advertised`, unrelated capabilities, and endpoint unavailability remain distinct and are not converted into verified support or non-support. + +See [`samples/Icod.Terminal.TermInfoPersistentRaster.Sample`](samples/Icod.Terminal.TermInfoPersistentRaster.Sample/README.md) for executable documentation and [`docs/releases/1.11.1.md`](docs/releases/1.11.1.md) for the patch-release contract. + ## Core 1.x guarantees ### One authoritative input path @@ -211,7 +231,8 @@ The stable 1.x surface includes: - bounded device/status/cursor/style/color/clipboard/notification queries; - titles, current location, hyperlinks, clipboard operations, cursor style, synchronized output, progress, pointer shape, notifications, prompt/shell metadata, and terminal colors; - backend-neutral ephemeral raster display through verified Sixel and Kitty Graphics; -- backend-neutral persistent raster resources and placements with bounded generation-scoped ownership. +- backend-neutral persistent raster resources and placements with bounded generation-scoped ownership; +- optional consumer-owned TermInfo lifecycle planning integration without adding Inspection to the production package graph. The library deliberately does not expose generic raw vendor dispatch as the ordinary extension model. @@ -225,6 +246,7 @@ The [`samples`](samples/README.md) directory contains focused examples. Recommen - `Icod.Terminal.Query.Sample` — bounded terminal queries; - `Icod.Terminal.RasterGraphics.Sample` — backend-neutral ephemeral raster display; - `Icod.Terminal.PersistentRaster.Sample` — verify, create, place, update, and dispose persistent raster ownership without protocol ids/backend branching; +- `Icod.Terminal.TermInfoPersistentRaster.Sample` — static TermInfo lifecycle plan, optional live Terminal verification, caller-owned replan, and persistent execution; - focused state, color, notification, prompt, and shell-integration samples described in the sample catalog. Focused sample verifiers build newer semantic/raster examples on every supported target framework during repository validation. @@ -245,9 +267,9 @@ See [`docs/Security-and-Privacy.md`](docs/Security-and-Privacy.md). ## Compatibility -Stable `1.0.0` remains the compatibility floor. Versions 1.1–1.4 added compatible semantic protocol surfaces; 1.5 and 1.6 normalized internal control/query infrastructure; 1.7 introduced backend-neutral raster display; 1.8 added Kitty Graphics beneath that surface; 1.9 added protocol-neutral semantic events; 1.10 added semantic capability planning; and 1.11 adds opaque persistent raster resource/placement ownership. +Stable `1.0.0` remains the compatibility floor. Versions 1.1–1.4 added compatible semantic protocol surfaces; 1.5 and 1.6 normalized internal control/query infrastructure; 1.7 introduced backend-neutral raster display; 1.8 added Kitty Graphics beneath that surface; 1.9 added protocol-neutral semantic events; 1.10 added semantic capability planning; 1.11 adds opaque persistent raster resource/placement ownership; and 1.11.1 adds no production API, instead qualifying the optional TermInfo persistent-raster lifecycle integration boundary. -The final 1.11 public API fingerprint is: +The final 1.11 public API fingerprint, retained by 1.11.1, is: ```text 9336a1f6def1c4b02e86db813bae27f45b95af33f47a2cf10dccd4d1d44324f2 @@ -259,10 +281,10 @@ See [`docs/Compatibility-and-Versioning.md`](docs/Compatibility-and-Versioning.m Start with: -- [1.11.0 release notes](docs/releases/1.11.0.md) +- [1.11.1 release notes](docs/releases/1.11.1.md) - [Persistent Raster Ownership](docs/Persistent-Raster-Ownership.md) - [Capability Inspection and Planning](docs/Capability-Inspection-and-Planning.md) -- [1.11.0 development roadmap](Icod.Terminal-1.11.0-Development-Roadmap.md) +- [1.11.1 development roadmap](Icod.Terminal-1.11.1-Development-Roadmap.md) - [Current development roadmap](Icod.Terminal-Development-Roadmap.md) - [Architecture](docs/Architecture.md) - [Input and Events](docs/Input-and-Events.md) @@ -287,4 +309,4 @@ Copyright (c) 2026 Timothy J. Bruce ## License -`Icod.Terminal` is licensed under the GNU Lesser General Public License, version 3 or later. Sample applications are licensed under the GNU General Public License, version 3 or later, as stated in their source headers. +`Icod.Terminal` is licensed under the GNU Lesser General Public License, version 3 or later. Sample applications are licensed under the GNU General Public License, version 3 or later, as stated in their source headers. \ No newline at end of file diff --git a/docs/T1111-E-1.11.1-Release-Closure.md b/docs/T1111-E-1.11.1-Release-Closure.md new file mode 100644 index 000000000..253d1956d --- /dev/null +++ b/docs/T1111-E-1.11.1-Release-Closure.md @@ -0,0 +1,135 @@ +# T1111-E — Icod.Terminal 1.11.1 Release Closure + +- **Release:** `Icod.Terminal 1.11.1` +- **Tranche:** T1111-E — package/downstream qualification and release closure +- **Status:** release candidate accepted; final status-only PR head must pass the same Staging matrix before merge +- **Stable compatibility floor:** `1.0.0` +- **Public API fingerprint:** `9336a1f6def1c4b02e86db813bae27f45b95af33f47a2cf10dccd4d1d44324f2` + +## Purpose + +T1111-E closes the 1.11.1 patch line after T1111-A through T1111-D established the loose-coupling integration contract between `Icod.TermInfo.Inspection 1.11.0` static persistent-raster lifecycle planning and `Icod.Terminal` live verification/execution. + +The release adds no production public API and makes no change to the persistent-raster protocol/runtime ownership semantics released in 1.11.0. Its release work is limited to integration contract tests, executable documentation, package-boundary qualification, release metadata, and release-facing documentation. + +## Accepted integration checkpoint + +T1111-A through T1111-D were accepted on exact head: + +```text +651af888875a0d2acababe46a4e1313532ec7ba6 +``` + +by pull-request workflow: + +```text +#1628 / 34704455179 +``` + +That exact head passed the complete pull-request matrix and established the dedicated integration project plus the protocol-neutral `Icod.Terminal.TermInfoPersistentRaster.Sample` before stable release metadata was advanced. + +## Stable release-candidate head + +The stable 1.11.1 release-candidate code/metadata head accepted by T1111-E is: + +```text +53ded168ae03d14e5490d290fe7730579a1456b3 +``` + +The qualifying pull-request workflow is: + +```text +#1630 / 34705075656 +``` + +Workflow #1630 completed with conclusion `success` on that exact SHA. The matrix passed: + +- Runtime Windows; +- Runtime Linux; +- Runtime macOS; +- package candidate/public API freeze; +- Package Foundation; +- Package Presentation; +- Package Semantic and hardening; +- Package Stable 1.x release line; +- validated package artifact. + +The runtime solution includes `Icod.Terminal.TermInfoIntegration.Tests` and `Icod.Terminal.TermInfoPersistentRaster.Sample`, so the OS runtime jobs build/test the integration contract and compile the sample across `net8.0`, `net9.0`, and `net10.0`. + +## Public API freeze + +The package-candidate job generated identical normalized public API snapshots for all three target frameworks and reported: + +```text +9336a1f6def1c4b02e86db813bae27f45b95af33f47a2cf10dccd4d1d44324f2 +``` + +That hash matches the frozen `docs/Public-API-Baseline-1.11.sha256` baseline. No 1.11.1 public API baseline is created because the patch release intentionally carries no production API delta. + +## Package dependency isolation + +The produced `Icod.Terminal.1.11.1.nupkg` was inspected after workflow #1630. Every dependency group for `net8.0`, `net9.0`, and `net10.0` contains exactly: + +```text +Icod.TermInfo 1.11.0 +Icod.Timing 1.0.0 +``` + +`Icod.TermInfo.Inspection` and `Icod.TermInfo.Source` do not appear in the production package dependency graph. Inspection remains confined to the dedicated integration tests and non-packable integration sample. + +The stable-release shard also restored and ran a fresh `Icod.Terminal 1.11.1` package consumer on all three TFMs and completed the published `Icod.DCurses` package hardening soak on all three TFMs. + +## Release-facing state + +The accepted release candidate records: + +- repository/package version `1.11.1` with no prerelease suffix; +- stable NuGet release notes for the TermInfo/Terminal persistent-raster integration contract; +- root README installation and integration guidance for `1.11.1`; +- final `CHANGELOG.md` 1.11.1 entry; +- curated `docs/releases/1.11.1.md` release notes; +- completed integration sample/catalog documentation; +- unchanged production dependencies and public API baseline; +- the stable `1.0.0` compatibility floor. + +## Frozen integration boundary + +The 1.11.1 contract remains: + +```text +TermInfo static inspection / classification / planning + -> optional Terminal live verification + -> caller-owned Verified lifecycle evidence + -> TermInfo reclassification / replanning + -> Terminal runtime resource / placement execution +``` + +Terminal remains authoritative for live endpoint ownership, bounded protocol verification, routing, resource/placement identity, generation invalidation, and cleanup. TermInfo Inspection remains authoritative for its static/advisory lifecycle model. The caller owns the small translation between a conclusive live Terminal status and TermInfo `Verified` lifecycle evidence. + +The release does not expose terminal brands, backend selection, raw graphics commands, Kitty image/placement identities, or other protocol-private numeric identifiers through this integration contract. + +## Explicit exclusions + +Version 1.11.1 does not add: + +- a production dependency on `Icod.TermInfo.Inspection`; +- a production dependency on `Icod.TermInfo.Source`; +- new public Terminal planning/profile/evidence types; +- direct ingestion of TermInfo's eight lifecycle dimensions into Terminal's coarse capability model; +- an adapter NuGet package; +- changes to persistent-raster wire/runtime ownership semantics; +- the deferred table-driven `TerminalTermInfoSemanticEvidence` rewrite; +- scene/window/cell/layout policy belonging to `Icod.DCurses`. + +## Maintainer handoff + +After this status-only closure record and roadmap update pass the same pull-request Staging matrix without further code or release-metadata changes: + +1. PR #54 may be marked ready for maintainer review; +2. merge remains an explicit maintainer action; +3. validate the resulting `main` head through the Release workflow; +4. tag `v1.11.1` only after successful mainline validation; +5. use `docs/releases/1.11.1.md` as the curated GitHub Release/publication notes; +6. NuGet publication remains a maintainer/release-workflow action. + +The next independent development item remains the approved table-driven cleanup of `TerminalTermInfoSemanticEvidence`; static lifecycle-evidence ingestion and any adapter package remain later architectural decisions. \ No newline at end of file diff --git a/docs/releases/1.11.1.md b/docs/releases/1.11.1.md new file mode 100644 index 000000000..b56b75300 --- /dev/null +++ b/docs/releases/1.11.1.md @@ -0,0 +1,128 @@ +# Icod.Terminal 1.11.1 + +`Icod.Terminal 1.11.1` is a compatibility-preserving integration patch for the persistent-raster work released in 1.11.0. It adds executable documentation and deterministic contract tests showing how `Icod.TermInfo.Inspection 1.11.0` static lifecycle planning composes with `Icod.Terminal` live capability verification and persistent-raster execution without coupling the two production packages. + +The package continues to target: + +```text +net8.0 +net9.0 +net10.0 +``` + +The stable compatibility floor remains `1.0.0`. + +## Loose-coupling integration contract + +The supported integration path is deliberately consumer-owned: + +```text +TerminalSession.Terminal + -> PersistentRasterLifecycleInspector.Inspect(...) + -> PersistentRasterLifecyclePlanner.Plan(...) + +if plan == Indeterminate + -> TerminalSession.VerifyCapabilityAsync(PersistentRasterGraphics) + -> caller maps the conclusive live status to Verified lifecycle evidence + -> PersistentRasterLifecycleClassifier.Classify(...) + -> PersistentRasterLifecyclePlanner.Plan(...) + +if plan == Success and Terminal runtime state is usable + -> TerminalSession.CreateRasterResourceAsync(...) + -> TerminalRasterResource.CreatePlacementAsync(...) + -> TerminalRasterPlacement.UpdateAsync(...) + -> dispose placement/resource +``` + +Static TermInfo planning performs no terminal I/O. Terminal remains the authority for live endpoint ownership, bounded capability verification, routing, resource/placement identity, lifecycle invalidation, and cleanup. + +## Consumer-owned live evidence mapping + +Terminal's public `PersistentRasterGraphics` capability is intentionally coarser than TermInfo's persistent-raster lifecycle profile. The 1.11.1 integration sample/tests demonstrate this caller policy: + +- live `PersistentRasterGraphics == Verified` produces positive `Verified` evidence for persistent upload, acknowledged upload, placement creation, multiple placements, placement update, placement deletion, and resource deletion; +- live `PersistentRasterGraphics == Unsupported` produces negative `Verified` evidence for those seven persistent lifecycle subjects; +- live `RasterGraphics == Verified` may produce positive `RasterDisplay` evidence; +- `Unknown`, `Advertised`, unrelated capabilities, and endpoint unavailability are never promoted to verified support or verified non-support. + +This translation is not a new `Icod.Terminal` or `Icod.TermInfo` production API. Protocol/backend/private numeric identities remain outside the integration boundary. + +## Deterministic integration tests + +A dedicated non-packable test project, `Icod.Terminal.TermInfoIntegration.Tests`, references the production `Icod.Terminal` project together with `Icod.TermInfo.Inspection 1.11.0` and proves the integration contract on all supported target frameworks. + +The tests cover: + +- the production dependency boundary; +- deterministic positive and negative live-evidence mapping; +- no promotion of static advertisement, unknown state, or endpoint unavailability; +- static `Indeterminate` plan -> Terminal-owned live verification -> caller-owned evidence -> successful replan; +- verified non-support -> `Impossible` replan without persistent-raster execution; +- exact Icod lifecycle declarations producing a static successful plan while Terminal runtime endpoint usability remains an independent execution requirement. + +## Executable integration sample + +`Icod.Terminal.TermInfoPersistentRaster.Sample` is executable architecture documentation for the same flow. It is non-packable, targets `net8.0`, `net9.0`, and `net10.0`, and keeps `Icod.TermInfo.Inspection 1.11.0` sample-local. + +The sample does not expose terminal-brand checks, caller-selected backends, raw graphics commands, Kitty image/placement ids, or other protocol-private numeric identities. If the semantic plan remains indeterminate/impossible or the live Terminal route is unavailable, it reports that outcome and exits without pretending execution succeeded. + +## Package and API compatibility + +Version 1.11.1 adds no production public API and intentionally retains the final 1.11 public API fingerprint: + +```text +9336a1f6def1c4b02e86db813bae27f45b95af33f47a2cf10dccd4d1d44324f2 +``` + +The production package dependency graph remains: + +```text +Icod.TermInfo 1.11.0 +Icod.Timing 1.0.0 +``` + +`Icod.TermInfo.Inspection` and `Icod.TermInfo.Source` are not production dependencies of `Icod.Terminal`. + +No 1.11.0 persistent-raster wire protocol, acknowledgement, resource/placement ownership, generation invalidation, cleanup, or bounded-state behavior is changed by this patch. + +## Qualification + +The completed T1111-A through T1111-D integration head was: + +```text +651af888875a0d2acababe46a4e1313532ec7ba6 +``` + +Pull-request workflow `#1628 / 34704455179` passed on that exact head with: + +- Runtime Windows; +- Runtime Linux; +- Runtime macOS; +- package candidate/public API freeze; +- Package Foundation; +- Package Presentation; +- Package Semantic and hardening; +- Package Stable 1.x release line; +- validated package artifact. + +The release-candidate documentation/version head is qualified separately before merge. + +## Deliberate exclusions + +Version 1.11.1 does not add: + +- a production dependency on `Icod.TermInfo.Inspection` or `Icod.TermInfo.Source`; +- new public Terminal planning/profile/evidence types; +- direct ingestion of TermInfo's eight lifecycle dimensions into Terminal's coarser capability model; +- an adapter NuGet package; +- a table-driven rewrite of `TerminalTermInfoSemanticEvidence`; +- changes to persistent-raster protocol/runtime semantics; +- scene/window/cell/layout policy belonging to `Icod.DCurses`. + +The table-driven semantic-evidence cleanup remains a later independent change. Static lifecycle-evidence ingestion and any adapter package remain later architectural decisions. + +## Compatibility + +`Icod.Terminal 1.11.1` is a patch release over 1.11.0 and the stable `1.0.0` compatibility floor. It preserves the existing public API, enum numerics, runtime dependency boundary, and persistent-raster ownership contract. + +See [`Compatibility-and-Versioning.md`](../Compatibility-and-Versioning.md), [`Persistent-Raster-Ownership.md`](../Persistent-Raster-Ownership.md), [`Capability-Inspection-and-Planning.md`](../Capability-Inspection-and-Planning.md), and [`Icod.Terminal-1.11.1-Development-Roadmap.md`](../../Icod.Terminal-1.11.1-Development-Roadmap.md) for the permanent and development contracts. diff --git a/docs/superpowers/plans/2026-09-12-1.11.1-terminfo-persistent-raster-integration.md b/docs/superpowers/plans/2026-09-12-1.11.1-terminfo-persistent-raster-integration.md new file mode 100644 index 000000000..59ed34e7d --- /dev/null +++ b/docs/superpowers/plans/2026-09-12-1.11.1-terminfo-persistent-raster-integration.md @@ -0,0 +1,285 @@ +# Icod.Terminal 1.11.1 TermInfo Persistent-Raster Integration Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Prove and document the intended loose coupling between `Icod.TermInfo.Inspection 1.11.0` persistent-raster lifecycle planning and `Icod.Terminal 1.11.x` live persistent-raster execution without adding Inspection or Source to the `Icod.Terminal` production package graph. + +**Architecture:** `Icod.TermInfo.Inspection` remains an optional consumer-side planning layer. A dedicated integration-contract test project and a non-packable sample may reference Inspection 1.11.0, but the production `Icod.Terminal` project continues to reference only `Icod.TermInfo` Runtime and `Icod.Timing`. Static TermInfo lifecycle evidence is inspected and planned first; when the plan is indeterminate, the consumer performs Terminal's existing live verification, translates that live result into caller-owned `Verified` lifecycle evidence, reclassifies/replans, and only then executes through the existing opaque Terminal resource/placement APIs. + +**Tech Stack:** C# 13; .NET `net8.0`, `net9.0`, `net10.0`; `Icod.Terminal 1.11.x`; `Icod.TermInfo 1.11.0`; optional `Icod.TermInfo.Inspection 1.11.0`; xUnit; existing Windows/Linux/macOS Staging/package matrix. + +**Spec:** `docs/Persistent-Raster-Ownership.md` + +## Companion authority + +The TermInfo-side semantic authority is the `Icod.TermInfo 1.11.0` guide: + +```text +https://github.com/uniblab/Icod.TermInfo/blob/main/docs/1.11.0-PERSISTENT-RASTER-LIFECYCLE-GUIDE.md +``` + +## Global Constraints + +- Release target is `Icod.Terminal 1.11.1`. +- Stable compatibility floor remains `1.0.0`. +- Production `Icod.Terminal.csproj` must not add a direct or transitive `Icod.TermInfo.Inspection` dependency. +- Production `Icod.Terminal.csproj` must not add a direct or transitive `Icod.TermInfo.Source` dependency beyond anything already required by its existing direct packages. +- `Icod.TermInfo 1.11.0` remains the direct Runtime dependency established by PR #53. +- `Icod.Timing 1.0.0` remains unchanged. +- No new public `Icod.Terminal` API is required for the integration contract. +- Existing `TerminalCapability.PersistentRasterGraphics`, `TerminalRasterResource`, `TerminalRasterPlacement`, and lifecycle semantics remain authoritative for live execution. +- `Icod.TermInfo.Inspection` remains advisory: it owns evidence, classification, semantic planning, uncertainty, and contradiction; it does not own terminal I/O or Terminal resource identities. +- No Kitty image ids, image numbers, placement ids, raw APC controls, or backend-selection details may cross the public/sample integration boundary. +- Live verification must use existing `TerminalSession.VerifyCapabilityAsync(...)`; no second probe path may be invented. +- No hidden raster replay, source-image cache, or lifecycle restoration is added. +- The integration sample must remain non-packable. +- The integration contract must be exercised on `net8.0`, `net9.0`, and `net10.0` and must remain valid on Windows, Linux, and macOS. +- Public API fingerprint must remain unchanged from `1.11.0` unless separately reviewed; the expected unchanged fingerprint is `9336a1f6def1c4b02e86db813bae27f45b95af33f47a2cf10dccd4d1d44324f2`. + +--- + +## Approved broader refactoring sequence + +The post-1.0 TermInfo audit established the following order: + +1. **1.11.1:** add the TermInfo 1.11 ↔ Terminal 1.11 persistent-raster integration sample and contract tests described by this plan; +2. **later independent change:** refactor `TerminalTermInfoSemanticEvidence` into a table-driven internal contract while preserving behavior and package dependencies; +3. **defer:** direct static lifecycle-evidence ingestion into Terminal's coarse public capability model until a separate semantic design resolves how TermInfo's eight `Unknown`/`Supported`/`Unsupported`/`Contradicted` lifecycle dimensions map onto Terminal's coarser runtime capability surface; +4. **defer:** any optional adapter package until at least two real consumers demonstrate repeated integration glue. + +The table-driven evidence refactor is approved conceptually but is **not part of the 1.11.1 release scope** unless the maintainer explicitly widens that scope later. + +--- + +### Task 1: Freeze the integration dependency boundary + +**Files:** +- Create: `tests/Icod.Terminal.TermInfoIntegration.Tests/Icod.Terminal.TermInfoIntegration.Tests.csproj` +- Modify: `Icod.Terminal.sln` +- Test: `tests/Icod.Terminal.TermInfoIntegration.Tests/src/IntegrationDependencyBoundaryTests.cs` + +**Interfaces:** +- Consumes: `Icod.Terminal` by project reference; `Icod.TermInfo.Inspection` version `1.11.0` by test-only package reference. +- Produces: a dedicated integration-contract test assembly that can use Inspection without changing the production package graph. + +- [ ] **Step 1: Write the failing dependency-boundary test/project** + +Create a dedicated multi-target test project with `net8.0;net9.0;net10.0`, xUnit, a project reference to `../../Icod.Terminal.csproj`, and a direct package reference to `Icod.TermInfo.Inspection` version `1.11.0`. Add a test that loads the production project/package authority and asserts that the production project still declares `Icod.TermInfo` and `Icod.Timing` only, not `Icod.TermInfo.Inspection` or `Icod.TermInfo.Source`. + +- [ ] **Step 2: Add the test project to the solution and run the focused test** + +Run the dedicated test project in Staging for all three target frameworks. Expected result before any accidental production dependency change: PASS; if Inspection/Source is added to production, this test must FAIL. + +- [ ] **Step 3: Commit the dependency-boundary contract** + +Commit only the dedicated test project, solution entry, and dependency-boundary test. + +--- + +### Task 2: Define the consumer-owned lifecycle evidence mapping contract + +**Files:** +- Create: `tests/Icod.Terminal.TermInfoIntegration.Tests/src/PersistentRasterLifecycleEvidenceBridge.cs` +- Create: `tests/Icod.Terminal.TermInfoIntegration.Tests/src/PersistentRasterLifecycleEvidenceBridgeTests.cs` + +**Interfaces:** +- Consumes: `TerminalCapabilityStatus` for `RasterGraphics` and `PersistentRasterGraphics`; `PersistentRasterLifecycleEvidence`, `PersistentRasterLifecycleEvidenceSubject`, and `PersistentRasterLifecycleEvidenceKind.Verified`. +- Produces: test/sample-local mapping helpers only; no production API. + +The consumer mapping is intentionally explicit: + +```text +Terminal RasterGraphics Verified + -> RasterDisplay = positive Verified evidence + +Terminal PersistentRasterGraphics Verified + -> PersistentUpload = positive Verified evidence + -> AcknowledgedUpload = positive Verified evidence + -> PlacementCreation = positive Verified evidence + -> MultiplePlacements = positive Verified evidence + -> PlacementUpdate = positive Verified evidence + -> PlacementDeletion = positive Verified evidence + -> ResourceDeletion = positive Verified evidence + +Terminal PersistentRasterGraphics Unsupported + -> the seven persistent lifecycle subjects above = negative Verified evidence +``` + +`Unknown` or merely `Advertised` Terminal status does not create caller-owned `Verified` evidence. Endpoint unavailability is not misrepresented as terminal non-support. + +- [ ] **Step 1: Write RED tests for the mapping rules** + +Cover positive verification, verified non-support, static advertisement, unknown support, endpoint unavailability, deterministic source labels/ordinals, and absence of protocol/backend identifiers. + +- [ ] **Step 2: Run the focused tests and confirm RED** + +Expected failure: the bridge helper does not exist. + +- [ ] **Step 3: Implement the minimal test/sample-local bridge** + +The bridge returns immutable `PersistentRasterLifecycleEvidence` instances only. It must not mutate `TerminalSession`, `TerminalDescription`, or TermInfo profiles. + +- [ ] **Step 4: Run focused tests and confirm GREEN** + +All mapping tests must pass on all three TFMs. + +- [ ] **Step 5: Commit the mapping contract** + +Commit helper and tests as one independently reviewable tranche. + +--- + +### Task 3: Prove static-plan → live-verification → replan behavior + +**Files:** +- Create: `tests/Icod.Terminal.TermInfoIntegration.Tests/src/PersistentRasterLifecycleIntegrationTests.cs` +- Reuse: `tests/Icod.Terminal.TermInfoIntegration.Tests/src/PersistentRasterLifecycleEvidenceBridge.cs` + +**Interfaces:** +- Consumes: `PersistentRasterLifecycleInspector.Inspect(...)`, `PersistentRasterLifecyclePlanner.Plan(...)`, `PersistentRasterLifecycleClassifier.Classify(...)`, `TerminalSession.VerifyCapabilityAsync(...)`, and the existing persistent-raster Terminal APIs. +- Produces: executable contract evidence for the loose-coupling architecture. + +Use a persistent request equivalent to: + +```csharp +new PersistentRasterLifecycleRequest( + uploadResource: true, + placementCount: 1, + updatePlacement: true, + deletePlacement: true, + deleteResource: true, + requireAcknowledgedUpload: true +); +``` + +- [ ] **Step 1: Write RED tests for a statically indeterminate profile** + +Start from a `TerminalDescription` that does not declare the reserved persistent-raster Boolean capabilities. Assert the initial TermInfo plan is `Indeterminate` and identifies runtime verification requirements. + +- [ ] **Step 2: Script Terminal live verification and assert the replan transition** + +Use the existing authoritative Terminal input/query path to produce verified Kitty persistent-raster support. Convert the resulting Terminal statuses through the consumer-owned bridge, combine the static and live evidence, reclassify, and assert that the same request becomes `Success`. + +- [ ] **Step 3: Add the verified-non-support path** + +Script a reviewed negative verification result, map it to negative `Verified` persistent lifecycle evidence, reclassify/replan, and assert `Impossible` without attempting persistent resource creation. + +- [ ] **Step 4: Add a static-description success path** + +Construct a `TerminalDescription` with the exact Icod-owned extended Boolean lifecycle declarations. Assert TermInfo can classify/plan successfully without live probing, while the execution layer still independently requires usable Terminal runtime state before performing I/O. + +- [ ] **Step 5: Run the integration tests on all TFMs and commit** + +The tests must prove planning never performs terminal I/O, live verification remains Terminal-owned, and no protocol ids cross the bridge. + +--- + +### Task 4: Add the non-packable integration sample + +**Files:** +- Create: `samples/Icod.Terminal.TermInfoPersistentRaster.Sample/Icod.Terminal.TermInfoPersistentRaster.Sample.csproj` +- Create: `samples/Icod.Terminal.TermInfoPersistentRaster.Sample/Program.cs` +- Create: `samples/Icod.Terminal.TermInfoPersistentRaster.Sample/README.md` +- Modify: `samples/README.md` +- Modify: `Icod.Terminal.sln` + +**Interfaces:** +- Consumes: production `Icod.Terminal`; package `Icod.TermInfo.Inspection 1.11.0`; the same consumer-owned evidence mapping proven by Task 2 (duplicated minimally in the sample or moved into sample-local code, not into production). +- Produces: an executable documentation path from static description → TermInfo plan → optional Terminal verification → replan → Terminal execution. + +- [ ] **Step 1: Create the sample project and README** + +The sample is non-packable, multi-targeted to `net8.0;net9.0;net10.0`, project-references `Icod.Terminal`, and package-references `Icod.TermInfo.Inspection 1.11.0` only for the sample. + +- [ ] **Step 2: Implement the protocol-neutral sample flow** + +The sample must: + +```text +open TerminalSession + -> inspect session.Terminal through TermInfo Inspection + -> create persistent lifecycle request + -> plan + -> if Indeterminate and endpoint usable, VerifyCapabilityAsync(PersistentRasterGraphics) + -> convert Terminal result to caller-owned Verified evidence + -> reclassify and replan + -> on Success, create TerminalRasterResource + -> create TerminalRasterPlacement + -> update placement + -> dispose placement/resource +``` + +The sample must not mention or expose Kitty numeric identities or raw graphics commands in its public-facing flow. + +- [ ] **Step 3: Add deterministic non-interactive explanatory behavior** + +When the session cannot provide the required interactive endpoint or the plan remains indeterminate/impossible, print the semantic reason and exit without pretending execution succeeded. + +- [ ] **Step 4: Build the sample under all three TFMs and commit** + +CI need not execute a real interactive graphics transaction; contract tests provide deterministic runtime execution coverage. The sample must compile on all supported TFMs. + +--- + +### Task 5: Package/downstream qualification and 1.11.1 release closure + +**Files:** +- Modify: `Directory.Build.props` +- Modify: `Icod.Terminal.csproj` +- Modify: `README.md` +- Modify: `CHANGELOG.md` +- Create: `docs/releases/1.11.1.md` +- Modify: `Icod.Terminal-1.11.1-Development-Roadmap.md` +- Modify package/runtime verifier scripts only if required to compile the new sample/contract project; do not weaken existing gates. + +**Interfaces:** +- Consumes: all previous 1.11.1 tranches. +- Produces: stable `Icod.Terminal 1.11.1` with unchanged production API and package dependency boundary. + +- [ ] **Step 1: Bump development/release metadata only after integration behavior is green** + +Advance the package to `1.11.1` according to existing repository version policy. The production dependency remains `Icod.TermInfo 1.11.0` and `Icod.Timing 1.0.0`. + +- [ ] **Step 2: Assert the public API fingerprint is unchanged** + +Expected normalized fingerprint: + +```text +9336a1f6def1c4b02e86db813bae27f45b95af33f47a2cf10dccd4d1d44324f2 +``` + +Any API change is a stop/review condition rather than an automatic baseline update. + +- [ ] **Step 3: Qualify package dependency isolation** + +The packed `Icod.Terminal` dependency groups must still contain the existing runtime dependencies only; `Icod.TermInfo.Inspection` and `Icod.TermInfo.Source` must not appear as new production dependencies. + +- [ ] **Step 4: Run full Staging verification** + +Require Windows, Linux, macOS runtime validation, package candidate/API freeze, all package shards, validated artifact, integration-contract tests on all TFMs, and sample builds. + +- [ ] **Step 5: Complete release-facing documentation and exact-head acceptance** + +Document the loose-coupling integration contract, explicit exclusions, exact head, workflow/run ids, and unchanged API fingerprint. Leave merge/tag/publication to the maintainer. + +--- + +## Explicitly deferred work + +The following is not part of 1.11.1: + +- making `Icod.TermInfo.Inspection` a production dependency of `Icod.Terminal`; +- making `Icod.TermInfo.Source` a production dependency of `Icod.Terminal`; +- adding new public Terminal planning types that mirror Inspection; +- collapsing TermInfo's eight persistent-raster lifecycle dimensions into one Terminal capability without a separate semantic design; +- introducing an `Icod.Terminal`/`Icod.TermInfo.Inspection` adapter package; +- table-driving `TerminalTermInfoSemanticEvidence` in the same patch release; +- changing persistent-raster protocol/runtime semantics already frozen by 1.11.0. + +## Plan self-review + +- **Spec coverage:** every 1.11.1 objective is assigned to Tasks 1–5. +- **Dependency boundary:** Inspection/Source remain test/sample-only and never enter the production package graph. +- **Type consistency:** all integration steps use the released TermInfo 1.11 lifecycle types and the released Terminal 1.11 capability/resource/placement types. +- **No placeholders:** implementation behavior, mapping rules, target files, verification gates, and exclusions are explicit. +- **Scope:** the approved broader refactoring sequence is recorded, but only the integration sample/contract is scheduled for 1.11.1. diff --git a/samples/Icod.Terminal.TermInfoPersistentRaster.Sample/Icod.Terminal.TermInfoPersistentRaster.Sample.csproj b/samples/Icod.Terminal.TermInfoPersistentRaster.Sample/Icod.Terminal.TermInfoPersistentRaster.Sample.csproj new file mode 100644 index 000000000..ce2eb831e --- /dev/null +++ b/samples/Icod.Terminal.TermInfoPersistentRaster.Sample/Icod.Terminal.TermInfoPersistentRaster.Sample.csproj @@ -0,0 +1,44 @@ + + + + + + + Exe + net8.0;net9.0;net10.0 + 13.0 + enable + enable + false + Icod.Terminal.TermInfoPersistentRaster.Sample + Icod.Terminal.TermInfoPersistentRaster.Sample + false + Debug;Staging;Release + + + + + + + + + + + diff --git a/samples/Icod.Terminal.TermInfoPersistentRaster.Sample/Program.cs b/samples/Icod.Terminal.TermInfoPersistentRaster.Sample/Program.cs new file mode 100644 index 000000000..15040737c --- /dev/null +++ b/samples/Icod.Terminal.TermInfoPersistentRaster.Sample/Program.cs @@ -0,0 +1,291 @@ +/* + Icod.Terminal.TermInfoPersistentRaster.Sample + Sample application demonstrating Icod.Terminal TermInfoPersistentRaster features. + Copyright (C) 2026 Timothy J. Bruce +*/ + +/* + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +using Icod.TermInfo.Inspection; +using Icod.Terminal; + +const int width = 48; +const int height = 24; +const string liveEvidenceSource = "Icod.Terminal.live-capability-verification"; + +PersistentRasterLifecycleRequest request = new( + uploadResource: true, + placementCount: 1, + updatePlacement: true, + deletePlacement: true, + deleteResource: true, + requireAcknowledgedUpload: true +); + +await using TerminalSession session = await TerminalSession.OpenAsync( + new TerminalSessionOptions { + InputMode = TerminalInputMode.CBreak, + EchoInput = false + } +); + +PersistentRasterLifecycleProfile profile = + PersistentRasterLifecycleInspector.Inspect( session.Terminal ); +PersistentRasterLifecyclePlan plan = + PersistentRasterLifecyclePlanner.Plan( profile, request ); + +await session.WriteTextAsync( + $"Static TermInfo persistent-raster plan: {plan.Status}.\r\n" +); + +if ( plan.RequiresRuntimeVerification ) { + TerminalCapabilityStatus beforeVerification = session.InspectCapability( + TerminalCapability.PersistentRasterGraphics + ); + if ( + TerminalCapabilityEndpointAvailability.Available + != beforeVerification.EndpointAvailability + ) { + await session.WriteTextAsync( + "The static plan needs live verification, but the required interactive terminal endpoint is unavailable.\r\n" + ); + await WritePlanIssuesAsync( session, plan ); + return 2; + } + + TerminalCapabilityStatus verified = await session.VerifyCapabilityAsync( + TerminalCapability.PersistentRasterGraphics + ); + IReadOnlyList liveEvidence = + CreatePersistentRasterEvidence( + verified, + GetNextSourceOrdinal( profile.Evidence ) + ); + if ( 0 == liveEvidence.Count ) { + await session.WriteTextAsync( + "Terminal verification did not produce conclusive persistent-raster support evidence.\r\n" + ); + return 2; + } + + List strengthenedEvidence = + profile.Evidence.ToList(); + strengthenedEvidence.AddRange( liveEvidence ); + profile = PersistentRasterLifecycleClassifier.Classify( strengthenedEvidence ); + plan = PersistentRasterLifecyclePlanner.Plan( profile, request ); + + await session.WriteTextAsync( + $"Plan after live Terminal verification: {plan.Status}.\r\n" + ); +} + +if ( PersistentRasterLifecyclePlanStatus.Success != plan.Status ) { + await session.WriteTextAsync( + $"Persistent-raster lifecycle execution is not admissible: {plan.Status}.\r\n" + ); + await WritePlanIssuesAsync( session, plan ); + return 2; +} + +TerminalCapabilityStatus executionCapability = session.InspectCapability( + TerminalCapability.PersistentRasterGraphics +); +if ( !executionCapability.IsUsable ) { + await session.WriteTextAsync( + "TermInfo planning permits the requested lifecycle, but Icod.Terminal does not currently have a usable live persistent-raster route for this endpoint.\r\n" + ); + return 2; +} + +byte[] pixels = new byte[ width * height * 3 ]; +for ( int row = 0; row < height; ++row ) { + for ( int column = 0; column < width; ++column ) { + int offset = ( ( row * width ) + column ) * 3; + pixels[ offset ] = ScaleChannel( column, width - 1 ); + pixels[ offset + 1 ] = ScaleChannel( row, height - 1 ); + pixels[ offset + 2 ] = ScaleChannel( + column + row, + width + height - 2 + ); + } +} + +TerminalRasterImage image = TerminalRasterImage.CreateRgb24( + width, + height, + pixels +); +TerminalControlResult resourceResult = + await session.CreateRasterResourceAsync( image ); +if ( TerminalControlStatus.Available != resourceResult.Status + || resourceResult.Value is null ) { + await session.WriteTextAsync( + FormatFailure( + "Persistent raster resource creation", + resourceResult.Status, + resourceResult.Message + ) + ); + return 1; +} +await using TerminalRasterResource resource = resourceResult.Value; + +TerminalControlResult placementResult = + await resource.CreatePlacementAsync( + new TerminalRasterPlacementOptions { + Columns = 24 + } + ); +if ( TerminalControlStatus.Available != placementResult.Status + || placementResult.Value is null ) { + await session.WriteTextAsync( + FormatFailure( + "Persistent raster placement creation", + placementResult.Status, + placementResult.Message + ) + ); + return 1; +} +await using TerminalRasterPlacement placement = placementResult.Value; + +TerminalControlMutationResult update = await placement.UpdateAsync( + new TerminalRasterPlacementOptions { + Columns = 16 + } +); +if ( !update.Succeeded ) { + await session.WriteTextAsync( + FormatFailure( + "Persistent raster placement update", + update.Status, + update.Message + ) + ); + return 1; +} + +await session.WriteTextAsync( + "Persistent-raster lifecycle execution succeeded; disposal now releases placement and resource ownership.\r\n" +); +return 0; + +static IReadOnlyList CreatePersistentRasterEvidence( + TerminalCapabilityStatus status, + int sourceOrdinal +) { + if ( 0 > sourceOrdinal ) { + throw new ArgumentOutOfRangeException( nameof( sourceOrdinal ) ); + } + if ( TerminalCapability.PersistentRasterGraphics != status.Capability + || TerminalCapabilityEvidenceKind.LiveObservation != status.EvidenceKind ) { + return Array.Empty(); + } + + bool? isPositive = status.Support switch { + TerminalCapabilitySupport.Verified => true, + TerminalCapabilitySupport.Unsupported => false, + _ => null + }; + if ( !isPositive.HasValue ) { + return Array.Empty(); + } + + PersistentRasterLifecycleEvidenceSubject[] subjects = [ + PersistentRasterLifecycleEvidenceSubject.PersistentUpload, + PersistentRasterLifecycleEvidenceSubject.AcknowledgedUpload, + PersistentRasterLifecycleEvidenceSubject.PlacementCreation, + PersistentRasterLifecycleEvidenceSubject.MultiplePlacements, + PersistentRasterLifecycleEvidenceSubject.PlacementUpdate, + PersistentRasterLifecycleEvidenceSubject.PlacementDeletion, + PersistentRasterLifecycleEvidenceSubject.ResourceDeletion + ]; + if ( sourceOrdinal > int.MaxValue - ( subjects.Length - 1 ) ) { + throw new ArgumentOutOfRangeException( nameof( sourceOrdinal ) ); + } + + PersistentRasterLifecycleEvidence[] result = + new PersistentRasterLifecycleEvidence[ subjects.Length ]; + for ( int index = 0; index < subjects.Length; ++index ) { + result[ index ] = new PersistentRasterLifecycleEvidence( + subjects[ index ], + isPositive.Value, + PersistentRasterLifecycleEvidenceKind.Verified, + liveEvidenceSource, + sourceOrdinal + index + ); + } + return result; +} + +static int GetNextSourceOrdinal( + IReadOnlyList evidence +) { + ArgumentNullException.ThrowIfNull( evidence ); + return 0 == evidence.Count + ? 0 + : checked( evidence.Max( item => item.SourceOrdinal ) + 1 ); +} + +static async ValueTask WritePlanIssuesAsync( + TerminalSession session, + PersistentRasterLifecyclePlan plan +) { + ArgumentNullException.ThrowIfNull( session ); + ArgumentNullException.ThrowIfNull( plan ); + foreach ( PersistentRasterLifecyclePlanIssue issue in plan.Issues ) { + await session.WriteTextAsync( + string.Concat( + " ", + issue.Operation.ToString(), + ": ", + issue.Subject.ToString(), + " is ", + issue.SupportStatus.ToString(), + issue.RequiresRuntimeVerification + ? " (runtime verification may strengthen this)." + : ".", + "\r\n" + ) + ); + } +} + +static string FormatFailure( + string operation, + TerminalControlStatus status, + string? message +) { + ArgumentException.ThrowIfNullOrWhiteSpace( operation ); + return string.Concat( + operation, + " was not completed: ", + status.ToString(), + string.IsNullOrEmpty( message ) + ? string.Empty + : string.Concat( " — ", message ), + "\r\n" + ); +} + +static byte ScaleChannel( + int value, + int maximum +) { + if ( 0 >= maximum ) { + return 0; + } + return (byte)( ( value * byte.MaxValue ) / maximum ); +} diff --git a/samples/Icod.Terminal.TermInfoPersistentRaster.Sample/README.md b/samples/Icod.Terminal.TermInfoPersistentRaster.Sample/README.md new file mode 100644 index 000000000..34da6ac5a --- /dev/null +++ b/samples/Icod.Terminal.TermInfoPersistentRaster.Sample/README.md @@ -0,0 +1,35 @@ +# Icod.Terminal.TermInfoPersistentRaster.Sample + +This sample demonstrates the loose-coupling integration contract between `Icod.TermInfo.Inspection 1.11.0` and `Icod.Terminal` persistent-raster execution. + +The sample keeps the responsibilities separate: + +- `Icod.TermInfo.Inspection` reads the static `TerminalDescription`, classifies persistent-raster lifecycle evidence, and produces a semantic lifecycle plan; +- `Icod.Terminal` owns the live terminal session, endpoint availability, capability verification, routing, resource ownership, placement ownership, and cleanup; +- the application owns the small bridge that converts a conclusive live `TerminalCapabilityStatus` into `Verified` lifecycle evidence before asking TermInfo to reclassify and replan. + +The executable flow is: + +```text +open TerminalSession + -> inspect session.Terminal through TermInfo Inspection + -> create persistent lifecycle request + -> plan + -> if Indeterminate and endpoint usable, VerifyCapabilityAsync(PersistentRasterGraphics) + -> convert the conclusive Terminal result to caller-owned Verified evidence + -> reclassify and replan + -> on Success, create TerminalRasterResource + -> create TerminalRasterPlacement + -> update placement + -> dispose placement/resource +``` + +Run it with, for example: + +```text +dotnet run --project samples/Icod.Terminal.TermInfoPersistentRaster.Sample/Icod.Terminal.TermInfoPersistentRaster.Sample.csproj -f net10.0 +``` + +The sample intentionally does **not** expose terminal-brand checks, backend selection, raw graphics commands, or protocol-private numeric identities. If static planning remains indeterminate or impossible, if the required interactive endpoint is unavailable, or if Terminal cannot establish a usable live persistent-raster route, the sample reports that semantic outcome and exits without pretending the operation succeeded. + +`Icod.TermInfo.Inspection` is a dependency of this sample only. It is not a production dependency of the `Icod.Terminal` package. diff --git a/samples/README.md b/samples/README.md index fc0652803..0bfe56874 100644 --- a/samples/README.md +++ b/samples/README.md @@ -15,6 +15,7 @@ All samples target `net8.0`, `net9.0`, and `net10.0`. | Observe or temporarily own terminal colors | `Icod.Terminal.Color.Sample` | | Display a backend-neutral ephemeral raster | `Icod.Terminal.RasterGraphics.Sample` | | Create/update/dispose terminal-resident raster ownership | `Icod.Terminal.PersistentRaster.Sample` | +| Plan persistent-raster lifecycle with TermInfo, optionally verify live support, then execute | `Icod.Terminal.TermInfoPersistentRaster.Sample` | | Own cursor style, synchronized output, progress, or pointer shape | focused state samples | | Publish title/location/prompt/shell metadata | focused metadata samples | | Emit notifications and observe interactive semantic events | `Icod.Terminal.Notification.Sample` | @@ -111,6 +112,20 @@ It does not mention Kitty, Sixel, image ids, image numbers, placement ids, or te See `docs/Persistent-Raster-Ownership.md` for the permanent ownership contract. +### `Icod.Terminal.TermInfoPersistentRaster.Sample` + +Demonstrates the 1.11.1 loose-coupling path between static TermInfo lifecycle planning and live Terminal execution. + +```text +dotnet run --project samples/Icod.Terminal.TermInfoPersistentRaster.Sample/Icod.Terminal.TermInfoPersistentRaster.Sample.csproj -f net10.0 +``` + +The sample inspects `session.Terminal` with `Icod.TermInfo.Inspection 1.11.0`, builds a semantic persistent-lifecycle plan, asks Terminal for live verification only when the static plan is indeterminate and the endpoint is available, converts a conclusive Terminal result into caller-owned `Verified` evidence, replans, and executes through opaque Terminal resource/placement APIs only when the final plan succeeds and the live route is usable. + +`Icod.TermInfo.Inspection` remains a sample-only dependency. The production `Icod.Terminal` package does not acquire an Inspection or Source dependency, and the sample does not expose raw graphics commands, terminal-brand branches, backend ids, or protocol-private numeric identities. + +See `Icod.Terminal.TermInfoPersistentRaster.Sample/README.md` for the complete responsibility boundary and failure behavior. + ## Reversible state and color ### `Icod.Terminal.Color.Sample` @@ -217,8 +232,9 @@ For graphics: ```text Icod.Terminal.CapabilityPlanning.Sample - -> Icod.Terminal.RasterGraphics.Sample (ephemeral display) - -> Icod.Terminal.PersistentRaster.Sample (terminal-resident ownership) + -> Icod.Terminal.RasterGraphics.Sample (ephemeral display) + -> Icod.Terminal.PersistentRaster.Sample (terminal-resident ownership) + -> Icod.Terminal.TermInfoPersistentRaster.Sample (static lifecycle plan + live verification + execution) ``` Higher-level full-screen applications normally consume these contracts through `Icod.DCurses` rather than reimplementing cells, windows, layout, or refresh policy directly. diff --git a/tests/Icod.Terminal.TermInfoIntegration.Tests/Icod.Terminal.TermInfoIntegration.Tests.csproj b/tests/Icod.Terminal.TermInfoIntegration.Tests/Icod.Terminal.TermInfoIntegration.Tests.csproj new file mode 100644 index 000000000..8ee35ab95 --- /dev/null +++ b/tests/Icod.Terminal.TermInfoIntegration.Tests/Icod.Terminal.TermInfoIntegration.Tests.csproj @@ -0,0 +1,89 @@ + + + + + + + net8.0;net9.0;net10.0 + 13.0 + enable + enable + false + false + true + Icod.Terminal.TermInfoIntegration.Tests + Icod.Terminal.TermInfoIntegration.Tests + Debug;Staging;Release + + + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + prompt + 2 + true + full + false + DEBUG;TRACE + false + false + + + + prompt + 3 + true + portable + false + TRACE + false + false + + + + prompt + 4 + portable + true + false + true + CS1591 + + diff --git a/tests/Icod.Terminal.TermInfoIntegration.Tests/src/IntegrationDependencyBoundaryTests.cs b/tests/Icod.Terminal.TermInfoIntegration.Tests/src/IntegrationDependencyBoundaryTests.cs new file mode 100644 index 000000000..2a42333a6 --- /dev/null +++ b/tests/Icod.Terminal.TermInfoIntegration.Tests/src/IntegrationDependencyBoundaryTests.cs @@ -0,0 +1,77 @@ +/* + Icod.Terminal.TermInfoIntegration.Tests + Validation utility for Icod.Terminal release and integration contracts. + Copyright (C) 2026 Timothy J. Bruce +*/ + +/* + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +namespace Icod.Terminal.TermInfoIntegration.Tests; + +using System.Xml.Linq; +using Xunit; + +/// +/// Guards the production dependency boundary required by the TermInfo integration contract. +/// +public sealed class IntegrationDependencyBoundaryTests { + [Fact] + public void ProductionProjectKeepsInspectionOutOfRuntimeDependencyGraph() { + XDocument project = XDocument.Load( + Path.Combine( + FindRepositoryRoot(), + "Icod.Terminal.csproj" + ) + ); + + string[] packageIds = project + .Descendants( "PackageReference" ) + .Select( + static element => (string?)element.Attribute( "Include" ) + ) + .Where( static packageId => !string.IsNullOrWhiteSpace( packageId ) ) + .Cast() + .ToArray(); + + Assert.Equal( + [ + "Icod.TermInfo", + "Icod.Timing" + ], + packageIds + ); + Assert.DoesNotContain( "Icod.TermInfo.Inspection", packageIds ); + Assert.DoesNotContain( "Icod.TermInfo.Source", packageIds ); + } + + private static string FindRepositoryRoot() { + DirectoryInfo? directory = new( AppContext.BaseDirectory ); + while ( directory is not null ) { + if ( File.Exists( + Path.Combine( + directory.FullName, + "Icod.Terminal.csproj" + ) + ) ) { + return directory.FullName; + } + directory = directory.Parent; + } + + throw new InvalidOperationException( + "Could not locate the Icod.Terminal repository root from the test output directory." + ); + } +} diff --git a/tests/Icod.Terminal.TermInfoIntegration.Tests/src/PersistentRasterLifecycleEvidenceBridge.cs b/tests/Icod.Terminal.TermInfoIntegration.Tests/src/PersistentRasterLifecycleEvidenceBridge.cs new file mode 100644 index 000000000..d259a3fb2 --- /dev/null +++ b/tests/Icod.Terminal.TermInfoIntegration.Tests/src/PersistentRasterLifecycleEvidenceBridge.cs @@ -0,0 +1,108 @@ +/* + Icod.Terminal.TermInfoIntegration.Tests + Validation utility for Icod.Terminal release and integration contracts. + Copyright (C) 2026 Timothy J. Bruce +*/ + +/* + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +namespace Icod.Terminal.TermInfoIntegration.Tests; + +using Icod.TermInfo.Inspection; + +/// +/// Translates protocol-neutral Terminal live capability results into caller-owned +/// TermInfo lifecycle evidence without exposing backend identity. +/// +internal static class PersistentRasterLifecycleEvidenceBridge { + internal const string SourceLabel = "Icod.Terminal.live-capability-verification"; + + private static readonly PersistentRasterLifecycleEvidenceSubject[] PersistentSubjects = [ + PersistentRasterLifecycleEvidenceSubject.PersistentUpload, + PersistentRasterLifecycleEvidenceSubject.AcknowledgedUpload, + PersistentRasterLifecycleEvidenceSubject.PlacementCreation, + PersistentRasterLifecycleEvidenceSubject.MultiplePlacements, + PersistentRasterLifecycleEvidenceSubject.PlacementUpdate, + PersistentRasterLifecycleEvidenceSubject.PlacementDeletion, + PersistentRasterLifecycleEvidenceSubject.ResourceDeletion + ]; + + internal static IReadOnlyList CreateEvidence( + TerminalCapabilityStatus status, + int sourceOrdinal + ) { + if ( 0 > sourceOrdinal ) { + throw new ArgumentOutOfRangeException( nameof( sourceOrdinal ) ); + } + + if ( TerminalCapabilityEvidenceKind.LiveObservation != status.EvidenceKind ) { + return Array.Empty(); + } + + bool? isPositive = status.Support switch { + TerminalCapabilitySupport.Verified => true, + TerminalCapabilitySupport.Unsupported => false, + _ => null + }; + if ( !isPositive.HasValue ) { + return Array.Empty(); + } + + if ( TerminalCapability.RasterGraphics == status.Capability ) { + return [ + CreateEvidence( + PersistentRasterLifecycleEvidenceSubject.RasterDisplay, + isPositive.Value, + sourceOrdinal + ) + ]; + } + if ( TerminalCapability.PersistentRasterGraphics != status.Capability ) { + return Array.Empty(); + } + if ( sourceOrdinal > int.MaxValue - ( PersistentSubjects.Length - 1 ) ) { + throw new ArgumentOutOfRangeException( + nameof( sourceOrdinal ), + sourceOrdinal, + "The source ordinal does not leave room for all persistent-raster lifecycle assertions." + ); + } + + PersistentRasterLifecycleEvidence[] result = + new PersistentRasterLifecycleEvidence[ PersistentSubjects.Length ]; + for ( int index = 0; index < PersistentSubjects.Length; index++ ) { + result[ index ] = CreateEvidence( + PersistentSubjects[ index ], + isPositive.Value, + sourceOrdinal + index + ); + } + return result; + } + + private static PersistentRasterLifecycleEvidence CreateEvidence( + PersistentRasterLifecycleEvidenceSubject subject, + bool isPositive, + int sourceOrdinal + ) { + return new PersistentRasterLifecycleEvidence( + subject, + isPositive, + PersistentRasterLifecycleEvidenceKind.Verified, + SourceLabel, + sourceOrdinal + ); + } +} diff --git a/tests/Icod.Terminal.TermInfoIntegration.Tests/src/PersistentRasterLifecycleEvidenceBridgeTests.cs b/tests/Icod.Terminal.TermInfoIntegration.Tests/src/PersistentRasterLifecycleEvidenceBridgeTests.cs new file mode 100644 index 000000000..3ddf1b8fa --- /dev/null +++ b/tests/Icod.Terminal.TermInfoIntegration.Tests/src/PersistentRasterLifecycleEvidenceBridgeTests.cs @@ -0,0 +1,226 @@ +/* + Icod.Terminal.TermInfoIntegration.Tests + Validation utility for Icod.Terminal release and integration contracts. + Copyright (C) 2026 Timothy J. Bruce +*/ + +/* + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +namespace Icod.Terminal.TermInfoIntegration.Tests; + +using System.Reflection; +using Icod.TermInfo.Inspection; +using Xunit; + +/// +/// Defines the T1111-B consumer-owned live-evidence mapping contract. +/// +public sealed class PersistentRasterLifecycleEvidenceBridgeTests { + private static readonly PersistentRasterLifecycleEvidenceSubject[] PersistentSubjects = [ + PersistentRasterLifecycleEvidenceSubject.PersistentUpload, + PersistentRasterLifecycleEvidenceSubject.AcknowledgedUpload, + PersistentRasterLifecycleEvidenceSubject.PlacementCreation, + PersistentRasterLifecycleEvidenceSubject.MultiplePlacements, + PersistentRasterLifecycleEvidenceSubject.PlacementUpdate, + PersistentRasterLifecycleEvidenceSubject.PlacementDeletion, + PersistentRasterLifecycleEvidenceSubject.ResourceDeletion + ]; + + [Fact] + public void VerifiedPersistentRasterMapsToSevenPositiveVerifiedAssertions() { + TerminalCapabilityStatus status = CreateStatus( + TerminalCapability.PersistentRasterGraphics, + TerminalCapabilitySupport.Verified, + TerminalCapabilityEndpointAvailability.Available, + TerminalCapabilityEvidenceKind.LiveObservation, + isUsable: true + ); + + IReadOnlyList evidence = + PersistentRasterLifecycleEvidenceBridge.CreateEvidence( + status, + 20 + ); + + Assert.Equal( PersistentSubjects, evidence.Select( item => item.Subject ) ); + Assert.All( evidence, item => Assert.True( item.IsPositive ) ); + Assert.All( + evidence, + item => Assert.Equal( + PersistentRasterLifecycleEvidenceKind.Verified, + item.Kind + ) + ); + Assert.All( + evidence, + item => Assert.Equal( + PersistentRasterLifecycleEvidenceBridge.SourceLabel, + item.SourceLabel + ) + ); + Assert.Equal( + Enumerable.Range( 20, PersistentSubjects.Length ), + evidence.Select( item => item.SourceOrdinal ) + ); + } + + [Fact] + public void UnsupportedPersistentRasterMapsToSevenNegativeVerifiedAssertions() { + TerminalCapabilityStatus status = CreateStatus( + TerminalCapability.PersistentRasterGraphics, + TerminalCapabilitySupport.Unsupported, + TerminalCapabilityEndpointAvailability.Available, + TerminalCapabilityEvidenceKind.LiveObservation, + isUsable: false + ); + + IReadOnlyList evidence = + PersistentRasterLifecycleEvidenceBridge.CreateEvidence( + status, + 40 + ); + + Assert.Equal( PersistentSubjects, evidence.Select( item => item.Subject ) ); + Assert.All( evidence, item => Assert.False( item.IsPositive ) ); + Assert.All( + evidence, + item => Assert.Equal( + PersistentRasterLifecycleEvidenceKind.Verified, + item.Kind + ) + ); + } + + [Fact] + public void VerifiedOrdinaryRasterMapsOnlyRasterDisplay() { + TerminalCapabilityStatus status = CreateStatus( + TerminalCapability.RasterGraphics, + TerminalCapabilitySupport.Verified, + TerminalCapabilityEndpointAvailability.Available, + TerminalCapabilityEvidenceKind.LiveObservation, + isUsable: true + ); + + PersistentRasterLifecycleEvidence item = Assert.Single( + PersistentRasterLifecycleEvidenceBridge.CreateEvidence( + status, + 7 + ) + ); + + Assert.Equal( + PersistentRasterLifecycleEvidenceSubject.RasterDisplay, + item.Subject + ); + Assert.True( item.IsPositive ); + Assert.Equal( PersistentRasterLifecycleEvidenceKind.Verified, item.Kind ); + Assert.Equal( 7, item.SourceOrdinal ); + } + + [Theory] + [InlineData( TerminalCapabilitySupport.Unknown, TerminalCapabilityEvidenceKind.None )] + [InlineData( + TerminalCapabilitySupport.Advertised, + TerminalCapabilityEvidenceKind.StaticDescription + )] + public void NonLiveSupportKnowledgeDoesNotBecomeVerifiedEvidence( + TerminalCapabilitySupport support, + TerminalCapabilityEvidenceKind evidenceKind + ) { + TerminalCapabilityStatus status = CreateStatus( + TerminalCapability.PersistentRasterGraphics, + support, + TerminalCapabilityEndpointAvailability.Available, + evidenceKind, + isUsable: false + ); + + Assert.Empty( + PersistentRasterLifecycleEvidenceBridge.CreateEvidence( + status, + 0 + ) + ); + } + + [Fact] + public void EndpointUnavailabilityDoesNotBecomeVerifiedNonSupport() { + TerminalCapabilityStatus status = CreateStatus( + TerminalCapability.PersistentRasterGraphics, + TerminalCapabilitySupport.Unknown, + TerminalCapabilityEndpointAvailability.Unavailable, + TerminalCapabilityEvidenceKind.None, + isUsable: false + ); + + Assert.Empty( + PersistentRasterLifecycleEvidenceBridge.CreateEvidence( + status, + 0 + ) + ); + } + + [Fact] + public void UnrelatedVerifiedCapabilityDoesNotProduceRasterEvidence() { + TerminalCapabilityStatus status = CreateStatus( + TerminalCapability.ClipboardWrite, + TerminalCapabilitySupport.Verified, + TerminalCapabilityEndpointAvailability.Available, + TerminalCapabilityEvidenceKind.LiveObservation, + isUsable: true + ); + + Assert.Empty( + PersistentRasterLifecycleEvidenceBridge.CreateEvidence( + status, + 0 + ) + ); + } + + private static TerminalCapabilityStatus CreateStatus( + TerminalCapability capability, + TerminalCapabilitySupport support, + TerminalCapabilityEndpointAvailability endpointAvailability, + TerminalCapabilityEvidenceKind evidenceKind, + bool isUsable + ) { + ConstructorInfo constructor = typeof( TerminalCapabilityStatus ).GetConstructor( + BindingFlags.Instance | BindingFlags.NonPublic, + binder: null, + [ + typeof( TerminalCapability ), + typeof( TerminalCapabilitySupport ), + typeof( TerminalCapabilityEndpointAvailability ), + typeof( TerminalCapabilityEvidenceKind ), + typeof( bool ) + ], + modifiers: null + ) ?? throw new InvalidOperationException( + "TerminalCapabilityStatus internal constructor was not found." + ); + + return (TerminalCapabilityStatus)constructor.Invoke( + [ + capability, + support, + endpointAvailability, + evidenceKind, + isUsable + ] + ); + } +} diff --git a/tests/Icod.Terminal.TermInfoIntegration.Tests/src/PersistentRasterLifecycleIntegrationTests.cs b/tests/Icod.Terminal.TermInfoIntegration.Tests/src/PersistentRasterLifecycleIntegrationTests.cs new file mode 100644 index 000000000..6cb3911a8 --- /dev/null +++ b/tests/Icod.Terminal.TermInfoIntegration.Tests/src/PersistentRasterLifecycleIntegrationTests.cs @@ -0,0 +1,480 @@ +/* + Icod.Terminal.TermInfoIntegration.Tests + Validation utility for Icod.Terminal release and integration contracts. + Copyright (C) 2026 Timothy J. Bruce +*/ + +/* + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +namespace Icod.Terminal.TermInfoIntegration.Tests; + +using System.Globalization; +using System.Text; +using System.Threading.Channels; +using Icod.TermInfo; +using Icod.TermInfo.Inspection; +using Xunit; + +/// +/// Proves the loose-coupling contract from static TermInfo planning through Terminal-owned live +/// verification and caller-owned evidence strengthening. +/// +public sealed class PersistentRasterLifecycleIntegrationTests { + [Fact] + public async Task LiveVerificationStrengthensIndeterminatePlanToSuccess() { + TerminalDescription description = CreateStaticallyIndeterminateDescription(); + PersistentRasterLifecycleRequest request = CreatePersistentRequest(); + PersistentRasterLifecycleProfile staticProfile = + PersistentRasterLifecycleInspector.Inspect( description ); + PersistentRasterLifecyclePlan staticPlan = + PersistentRasterLifecyclePlanner.Plan( staticProfile, request ); + ScriptedTerminalTransport transport = new(); + + Assert.Equal( PersistentRasterLifecyclePlanStatus.Indeterminate, staticPlan.Status ); + Assert.True( staticPlan.RequiresRuntimeVerification ); + Assert.Empty( transport.Writes ); + + await using TerminalSession session = await OpenSessionAsync( + description, + new RecordingTerminalControlProvider(), + transport + ); + Task verification = session.VerifyCapabilityAsync( + TerminalCapability.PersistentRasterGraphics + ).AsTask(); + string requestText = await transport.WaitForWriteAsync(); + uint imageId = ExtractKittyProbeImageId( requestText ); + transport.Publish( + $"\u001b_Gi={imageId.ToString( CultureInfo.InvariantCulture )};OK\u001b\\" + ); + transport.Publish( "\u001b[?64;1c" ); + + TerminalCapabilityStatus status = await verification; + Assert.Equal( TerminalCapabilitySupport.Verified, status.Support ); + Assert.Equal( TerminalCapabilityEvidenceKind.LiveObservation, status.EvidenceKind ); + Assert.True( status.IsUsable ); + + PersistentRasterLifecyclePlan strengthenedPlan = Replan( + staticProfile, + request, + status + ); + + Assert.Equal( PersistentRasterLifecyclePlanStatus.Success, strengthenedPlan.Status ); + Assert.False( strengthenedPlan.RequiresRuntimeVerification ); + } + + [Fact] + public async Task VerifiedNonSupportStrengthensIndeterminatePlanToImpossible() { + TerminalDescription description = CreateStaticallyIndeterminateDescription(); + PersistentRasterLifecycleRequest request = CreatePersistentRequest(); + PersistentRasterLifecycleProfile staticProfile = + PersistentRasterLifecycleInspector.Inspect( description ); + PersistentRasterLifecyclePlan staticPlan = + PersistentRasterLifecyclePlanner.Plan( staticProfile, request ); + ScriptedTerminalTransport transport = new(); + + Assert.Equal( PersistentRasterLifecyclePlanStatus.Indeterminate, staticPlan.Status ); + Assert.True( staticPlan.RequiresRuntimeVerification ); + Assert.Empty( transport.Writes ); + + await using TerminalSession session = await OpenSessionAsync( + description, + new RecordingTerminalControlProvider(), + transport + ); + Task verification = session.VerifyCapabilityAsync( + TerminalCapability.PersistentRasterGraphics + ).AsTask(); + _ = await transport.WaitForWriteAsync(); + transport.Publish( "\u001b[?64;1c" ); + + TerminalCapabilityStatus status = await verification; + Assert.Equal( TerminalCapabilitySupport.Unsupported, status.Support ); + Assert.Equal( TerminalCapabilityEvidenceKind.LiveObservation, status.EvidenceKind ); + Assert.False( status.IsUsable ); + + PersistentRasterLifecyclePlan strengthenedPlan = Replan( + staticProfile, + request, + status + ); + + Assert.Equal( PersistentRasterLifecyclePlanStatus.Impossible, strengthenedPlan.Status ); + Assert.False( strengthenedPlan.RequiresRuntimeVerification ); + } + + [Fact] + public async Task StaticLifecycleDeclarationsPlanWithoutProbeButDoNotMakeRedirectedSessionUsable() { + TerminalDescription description = CreateStaticallySupportedDescription(); + PersistentRasterLifecycleRequest request = CreatePersistentRequest(); + PersistentRasterLifecycleProfile profile = + PersistentRasterLifecycleInspector.Inspect( description ); + PersistentRasterLifecyclePlan plan = + PersistentRasterLifecyclePlanner.Plan( profile, request ); + RecordingOutput output = new(); + + Assert.Equal( PersistentRasterLifecyclePlanStatus.Success, plan.Status ); + Assert.False( plan.RequiresRuntimeVerification ); + Assert.Empty( output.Bytes ); + + await using TerminalSession session = await OpenSessionAsync( + description, + new RedirectedOutputControlProvider(), + new EmptyTerminalInput(), + output + ); + TerminalCapabilityStatus runtimeStatus = session.InspectCapability( + TerminalCapability.PersistentRasterGraphics + ); + + Assert.Equal( + TerminalCapabilityEndpointAvailability.Unavailable, + runtimeStatus.EndpointAvailability + ); + Assert.False( runtimeStatus.IsUsable ); + Assert.Empty( output.Bytes ); + } + + private static PersistentRasterLifecycleRequest CreatePersistentRequest() { + return new PersistentRasterLifecycleRequest( + uploadResource: true, + placementCount: 1, + updatePlacement: true, + deletePlacement: true, + deleteResource: true, + requireAcknowledgedUpload: true + ); + } + + private static TerminalDescription CreateStaticallyIndeterminateDescription() { + return new TerminalDescriptionBuilder( "terminal-integration-indeterminate" ) + .SetExtendedBoolean( "Sixel" ) + .Build(); + } + + private static TerminalDescription CreateStaticallySupportedDescription() { + return new TerminalDescriptionBuilder( "terminal-integration-supported" ) + .SetExtendedBoolean( + PersistentRasterLifecycleInspector.PersistentUploadCapabilityName + ) + .SetExtendedBoolean( + PersistentRasterLifecycleInspector.AcknowledgedUploadCapabilityName + ) + .SetExtendedBoolean( + PersistentRasterLifecycleInspector.PlacementCreationCapabilityName + ) + .SetExtendedBoolean( + PersistentRasterLifecycleInspector.MultiplePlacementsCapabilityName + ) + .SetExtendedBoolean( + PersistentRasterLifecycleInspector.PlacementUpdateCapabilityName + ) + .SetExtendedBoolean( + PersistentRasterLifecycleInspector.PlacementDeletionCapabilityName + ) + .SetExtendedBoolean( + PersistentRasterLifecycleInspector.ResourceDeletionCapabilityName + ) + .Build(); + } + + private static PersistentRasterLifecyclePlan Replan( + PersistentRasterLifecycleProfile staticProfile, + PersistentRasterLifecycleRequest request, + TerminalCapabilityStatus status + ) { + ArgumentNullException.ThrowIfNull( staticProfile ); + ArgumentNullException.ThrowIfNull( request ); + + List strengthenedEvidence = + staticProfile.Evidence.ToList(); + strengthenedEvidence.AddRange( + PersistentRasterLifecycleEvidenceBridge.CreateEvidence( + status, + GetNextSourceOrdinal( staticProfile.Evidence ) + ) + ); + PersistentRasterLifecycleProfile strengthenedProfile = + PersistentRasterLifecycleClassifier.Classify( strengthenedEvidence ); + return PersistentRasterLifecyclePlanner.Plan( strengthenedProfile, request ); + } + + private static int GetNextSourceOrdinal( + IReadOnlyList evidence + ) { + ArgumentNullException.ThrowIfNull( evidence ); + return 0 == evidence.Count + ? 0 + : checked( evidence.Max( item => item.SourceOrdinal ) + 1 ); + } + + private static uint ExtractKittyProbeImageId( + string request + ) { + ArgumentException.ThrowIfNullOrWhiteSpace( request ); + const string prefix = "\u001b_Gi="; + int marker = request.IndexOf( prefix, StringComparison.Ordinal ); + if ( 0 > marker ) { + throw new InvalidOperationException( + "The Terminal persistent-raster verification did not emit its expected support query." + ); + } + int valueStart = marker + prefix.Length; + int valueEnd = request.IndexOf( ',', valueStart ); + if ( valueStart >= valueEnd ) { + throw new InvalidOperationException( + "The Terminal persistent-raster support query did not contain a parseable image identity." + ); + } + return uint.Parse( + request.AsSpan( valueStart, valueEnd - valueStart ), + NumberStyles.None, + CultureInfo.InvariantCulture + ); + } + + private static ValueTask OpenSessionAsync( + TerminalDescription terminal, + ITerminalControlProvider controlProvider, + ScriptedTerminalTransport transport + ) { + ArgumentNullException.ThrowIfNull( transport ); + return OpenSessionAsync( + terminal, + controlProvider, + transport, + transport + ); + } + + private static ValueTask OpenSessionAsync( + TerminalDescription terminal, + ITerminalControlProvider controlProvider, + ITerminalInput input, + ITerminalOutput output + ) { + ArgumentNullException.ThrowIfNull( terminal ); + ArgumentNullException.ThrowIfNull( controlProvider ); + ArgumentNullException.ThrowIfNull( input ); + ArgumentNullException.ThrowIfNull( output ); + return TerminalSession.OpenAsync( + controlProvider, + TerminalEndpoint.StandardInput, + TerminalEndpoint.StandardOutput, + input, + output, + new TerminalSessionOptions { + TerminalOverride = terminal, + ConfigureOutput = false, + ObserveLifecycleEvents = false, + RequireInteractiveOutput = false + } + ); + } + + private sealed class ScriptedTerminalTransport : ITerminalInput, ITerminalOutput { + private readonly Channel input = Channel.CreateUnbounded(); + private readonly object sync = new(); + private readonly List writes = []; + private readonly TaskCompletionSource firstWrite = new( + TaskCreationOptions.RunContinuationsAsynchronously + ); + + internal IReadOnlyList Writes { + get { + lock ( this.sync ) { + return this.writes.ToArray(); + } + } + } + + internal Task WaitForWriteAsync() { + return this.firstWrite.Task; + } + + internal void Publish( + string value + ) { + ArgumentNullException.ThrowIfNull( value ); + foreach ( byte item in Encoding.ASCII.GetBytes( value ) ) { + if ( !this.input.Writer.TryWrite( item ) ) { + throw new InvalidOperationException( + "The scripted Terminal input channel rejected a response byte." + ); + } + } + } + + public async ValueTask ReadAsync( + Memory buffer, + CancellationToken cancellationToken = default + ) { + if ( buffer.IsEmpty ) { + return 0; + } + byte first = await this.input.Reader.ReadAsync( cancellationToken ); + buffer.Span[ 0 ] = first; + int count = 1; + while ( count < buffer.Length && this.input.Reader.TryRead( out byte item ) ) { + buffer.Span[ count ] = item; + ++count; + } + return count; + } + + public ValueTask WriteAsync( + ReadOnlyMemory buffer, + CancellationToken cancellationToken = default + ) { + cancellationToken.ThrowIfCancellationRequested(); + string value = Encoding.ASCII.GetString( buffer.Span ); + lock ( this.sync ) { + this.writes.Add( value ); + } + this.firstWrite.TrySetResult( value ); + return ValueTask.CompletedTask; + } + + public ValueTask FlushAsync( + CancellationToken cancellationToken = default + ) { + cancellationToken.ThrowIfCancellationRequested(); + return ValueTask.CompletedTask; + } + } + + private sealed class EmptyTerminalInput : ITerminalInput { + public ValueTask ReadAsync( + Memory buffer, + CancellationToken cancellationToken = default + ) { + cancellationToken.ThrowIfCancellationRequested(); + return ValueTask.FromResult( 0 ); + } + } + + private sealed class RecordingOutput : ITerminalOutput { + private readonly object sync = new(); + private readonly List bytes = []; + + internal IReadOnlyList Bytes { + get { + lock ( this.sync ) { + return this.bytes.ToArray(); + } + } + } + + public ValueTask WriteAsync( + ReadOnlyMemory buffer, + CancellationToken cancellationToken = default + ) { + cancellationToken.ThrowIfCancellationRequested(); + lock ( this.sync ) { + this.bytes.AddRange( buffer.ToArray() ); + } + return ValueTask.CompletedTask; + } + + public ValueTask FlushAsync( + CancellationToken cancellationToken = default + ) { + cancellationToken.ThrowIfCancellationRequested(); + return ValueTask.CompletedTask; + } + } + + private class RecordingTerminalControlProvider : ITerminalControlProvider { + private readonly TerminalModeSnapshot baseline = TerminalModeSnapshot.CreatePosix( + 0, + 0, + 0, + 0x0002UL, + new byte[ 32 ], + 0, + 32, + 0, + new TerminalSpeed( 13, 9600 ), + new TerminalSpeed( 13, 9600 ) + ); + + public virtual TerminalControlResult Observe( + TerminalEndpoint endpoint + ) { + ArgumentNullException.ThrowIfNull( endpoint ); + return TerminalControlResult.Available( + new TerminalEndpointObservation( + true, + null, + TerminalPlatformKind.PosixTermios, + TerminalControlCapabilities.Attachment + | TerminalControlCapabilities.ModeRead + | TerminalControlCapabilities.ModeWrite + ) + ); + } + + public TerminalControlResult GetSize( + TerminalEndpoint endpoint + ) { + ArgumentNullException.ThrowIfNull( endpoint ); + return TerminalControlResult.Unsupported( + "Size is not required by this integration test." + ); + } + + public TerminalControlResult GetMode( + TerminalEndpoint endpoint + ) { + ArgumentNullException.ThrowIfNull( endpoint ); + return TerminalControlResult.Available( this.baseline ); + } + + public TerminalControlMutationResult SetMode( + TerminalEndpoint endpoint, + TerminalModeSnapshot mode, + TerminalModeApplyTiming timing + ) { + ArgumentNullException.ThrowIfNull( endpoint ); + ArgumentNullException.ThrowIfNull( mode ); + if ( !Enum.IsDefined( timing ) ) { + throw new ArgumentOutOfRangeException( nameof( timing ) ); + } + return TerminalControlMutationResult.Success(); + } + } + + private sealed class RedirectedOutputControlProvider : RecordingTerminalControlProvider { + public override TerminalControlResult Observe( + TerminalEndpoint endpoint + ) { + ArgumentNullException.ThrowIfNull( endpoint ); + bool isInput = ReferenceEquals( endpoint, TerminalEndpoint.StandardInput ); + return TerminalControlResult.Available( + new TerminalEndpointObservation( + isInput, + null, + isInput ? TerminalPlatformKind.PosixTermios : null, + isInput + ? TerminalControlCapabilities.Attachment + | TerminalControlCapabilities.ModeRead + | TerminalControlCapabilities.ModeWrite + : TerminalControlCapabilities.None + ) + ); + } + } +}