Files
lmxopcua/scripts/validation/opcua-lifecycle-memory/late-slope
Joseph Doherty aa60b3e34e
v2-ci / unit-tests (push) Has been cancelled
test(soak): #799 place the soak's UNS folder after #808; Windows x64 port of the lifecycle soak harness
Acceptance runs for #799 (owner scope 2026-11-12: Windows x64 + Linux) on current master.

Fixture: since #808 an UNS folder the authority snapshot does not place is denied under enforcement (fail
closed). The canonical soak creates its own "SoakEquipment" folder but never placed it, so on master the very
first cycle's UNS monitored item came back bad (retained failed Linux launch on b61f3e05, cycle 1). The rig
gains AdditionalFolderScopes (merged into FolderScopes by Enforce) and the soak places its folder there, as a
deployed generation does - the same fixture gap #888 fixed for the SDK matrix. No server authorization code,
cap, baseline or canonical operation changed; the soak test bodies are otherwise byte-identical to b18f9723.

Windows port (test/harness only, opt-in):
- DescriptorCountsAsync gains a Windows branch: total = every handle in the process handle table, PKI = disk
  file/directory handles whose GetFinalPathNameByHandle path lies under a canonicalised PKI root; dedicated
  thread under the same 5 s bound as the macOS lsof probe.
- Descriptor controls enabled on Windows (symlink alias; timeout control via cmd.exe + ping).
- LateSlopeRun.ValidateRequest accepts Linux ARM64 or Windows x64; duration/runtime/leak tracking unchanged.
- late-slope/analyze.py: expected architecture is a module constant (Linux default Arm64 unchanged).
- windows/: prepare.py (win-x64 self-contained 10.0.14 bundle + manifest), launch.ps1 (container.sh
  equivalent: bundle re-verification, 2/2 controls first, PID/apphost/coreclr verification, external
  sampler, 7440 s outer deadline), analyze.py (Linux row/window logic, X64), README with every adaptation.

Claude-Session: https://claude.ai/code/session_01GT67ACmpMR7b9LGRPLijMi
2026-11-12 12:42:16 -05:00
..
…

Linux late-slope diagnostic

The protocol fixes 7,200 seconds of canonical cycling from startup completion, with the existing active restart every fourth cycle. The cycle-three baseline and resource caps remain in force. A latched rejection keeps the original failing test and container exit. The result records acceptance=false even when the diagnostic completes its duration and shutdown.

The analyzer requires an ordered active, recovered-stall, resource-observation, quiescent and complete-cycle ledger. It checks the 25-second restart helper bound, 30-second complete cycle bound and 60-second shutdown bound. It recomputes the first cold rejection and verifies its assertion against the actual TRX. It checks each fixed time window and the entire 1,800–7,200 second range before reporting RSS and retained-managed slopes in bytes per hour. The report includes sample counts, quiescent peaks, final values and coverage. It identifies whether the earlier saturation workload had completed by 1,800 seconds, which bounds any interpretation relative to the previously observed restart step. The raw JSONL keeps every sample and the first assertion stack.

Certificate allocation stack tracking remains enabled and contributes to the measured process. The slope report is descriptive and carries no resource pass threshold. Quiescent peaks refer to the sampled points.

Offline controls and artifact preparation

The synthetic controls exercise the parser, receipts and bundle manifest with temporary files. Run them with Python 3.11 or later.

python3 scripts/validation/opcua-lifecycle-memory/late-slope/test_offline.py

Prepare from the reviewed clean commit containing the canonical test and tools. The image argument is the immutable ID of a locally cached Linux ARM64 SDK image. The explicitly selected Docker context must use a local Unix socket. The preparation command publishes the self-contained .NET 10.0.14 app and records the exact SDK 2.0.1 and HistorianGateway 1.0.0 graph, source hashes, file hashes and managed module MVIDs. It uses the repository's read-only PE metadata inspector. Preparation completes before any descriptor or lifecycle test runs.

python3 scripts/validation/opcua-lifecycle-memory/late-slope/prepare.py \
  --source-revision "$reviewedRevision" --docker-context "$localContext" \
  --image "$cachedImageId" --output "$bundle"
python3 "$bundle/tools/launch.py" --bundle "$bundle" \
  --receipt-sha256 "$reviewedBundleReceiptHash" --verify-only

Reviewed run and analysis

Root owns launch after review of the exact bundle receipt and controls. The bundled launcher uses the pinned Unix endpoint, immutable image, network-none container, read-only root filesystem and read-only application and tools mounts. A fresh private task directory holds results and fixture files. The launcher creates its temporary subdirectory with mode 0700 and binds the same storage at /tmp for NuGet's named mutex. TMPDIR continues to point at /task/tmp. The container checks both paths identify the same private directory before starting VSTest. The two descriptor controls must execute successfully before the canonical test starts. The launcher verifies the actual test process's apphost, mapped CLR and CLR hash. Its outer deadline is 7,440 seconds, with cleanup limited to the uniquely labeled container.

python3 "$bundle/tools/launch.py" --bundle "$bundle" \
  --receipt-sha256 "$reviewedBundleReceiptHash" --output "$run"
python3 "$bundle/tools/analyze.py" --run "$run" \
  --bundle-receipt "$bundle/receipt.json" --bundle-receipt-sha256 "$reviewedBundleReceiptHash"

A completed diagnostic with a cold-cap rejection has a nonzero launcher exit. Its receipt records diagnostic completion separately from the actual test and container status. The offline analyzer verifies the reviewed bundle receipt, raw evidence hashes, test identities and outcomes, fixed duration and verified shutdown before emitting a report. It can run from copies of the small bundle receipt and run evidence on a machine without Docker.