8.1 KiB
Original 1.5.378.182 canonical adapter — partial diagnostic proof
This folder compiles a standalone diagnostic against the unchanged original
83ed880e published host/client DLLs. It does not rebuild or patch product code,
restore a substitute SDK, edit a baseline oracle, install a replacement session
factory, or approve a performance denominator. Only short 1–4-cycle smoke runs are
implemented. No original warmed/full/two-hour run has been performed.
The immutable SDK reports 1.5.378.182+6cca32dd6d79c8b57d2e00e4d2071b991d2cf71b.
The source workload was ported from Sdk2LifecycleSoakTests at 75057d8c.
evidence.json records source/input hashes, exact SDK layout and short-run results.
Raw logs and task-owned PKI stay outside the repository; credentials are not logged.
Supported operation coverage
CanonicalOperations.cs preserves the candidate's operation ordering, status
assertions, timeouts, model, grants, event data, page size and typed-array shape.
The representation changes are explicit: SDK2 Variant/ArrayOf helpers become the
original Variant constructor and CLR values; ByteString becomes byte[]; and
QualifiedName.From becomes its original constructor. There are no collection
shims or private-state mutations.
| Canonical operation per normal cycle | Original short control observation |
|---|---|
| Two encrypted opc.tcp / SignAndEncrypt sessions, Raw/UNS URI binding | Completed |
| One subscription, three data items, notifications | Completed |
| One Write service with Int32/Int16[]/UNS Int32 values, three readbacks | Completed with original Good/type assertions |
| Two browse pages, one explicit release and one abandonment | Completed; active browse count 1 |
| History first page, foreign-session denial, resume, explicit release, six abandoned points | All 10 HistoryRead services returned; active history count 6 |
| Writable off/on, array removal/recreation/publication | Completed |
| Key revocation, denied read, authority restoration | Completed |
| Paused TCP read, one-second cancellation, resume and successful recovery read | Completed; original UA status 2156134400 on the canceled read |
| Subscription deletion/removal and address-space rebuild/reconfiguration | Completed |
| Both normal clients CloseAsync | Both returned; version-specific post-close list comparison differs |
| Every-fourth-cycle restart with an active session/subscription/item, browse/history points | Separate isolated one-restart control completed |
The failed normal control attempted one whole wire body. Its ledger records 2 connects, 1 subscription create/apply/delete/remove, 1 Write service (3 values), 5 ReadValue attempts (4 returned, 1 expected canceled fault), 1 revoked Read service, 2 Browse calls, 1 BrowseNext release, 10 HistoryRead calls, and 2 CloseAsync returns. Surgery, revocation, stalled-network recovery and rebuild all occurred before the post-close comparison. No normal cycle is reported as ownership-complete.
The independent active-restart control does not stand in for a successful four-cycle sequence: it starts one client/subscription/item, abandons browse and history points, stops/reboots the same URI/port/PKI server, and checks the captured old managers/sessions/subscription items and new manager counts. Those checks completed. It returned exit 2, explicitly unsupported ownership parity, not a green acceptance result.
Close and ownership differences — unresolved
The first normal smoke returned exit 1 at the copied zero-closed-list comparison: a retained server Session still had 1 browse / 6 history points. The server SessionManager already had zero active sessions. A separate fixed 250 ms read-only observation saw the same lists; the original failed comparison was still enforced. The first failure artifacts were left untouched.
Exact immutable-binary inspection explains the internal difference:
- SessionManager.CloseSession removes the session and calls Session.Close.
- Session.Close only deletes its diagnostics; it does not clear continuation lists.
- Session.Dispose clears both lists and disposes their retained values.
- SessionManager.Shutdown/Dispose disposes sessions still in its active dictionary.
These are version-specific diagnostics, not proof that the original violates the wire close contract, has an unbounded leak, or retains the same objects once all strong diagnostic references are dropped. The adapter deliberately holds the old Session references while checking these fields. The SDK2 zero-list invariant cannot silently become a shared cross-version acceptance contract.
The restart control follows the different active-session shutdown path: captured old session/point lists and subscription items became empty. The original validator's cached store references remained 1 before and after stop. A cached store reference is not a count of open handles or proof of a leak.
Original ApplicationInstance and CertificateValidator expose no Dispose API, and there is no SDK2 CertificateManager/m_disposed/core-store-clear equivalent. The adapter reports those observables as unsupported, never as zero. Actual PKI file descriptors can be measured with the same bounded/canonical-path observer as the candidate; those observations cannot invent the missing manager ownership proof. No manual Session.Dispose, store replacement or cache clearing was added to make an assertion pass.
The historical partial proof above did not test token/continuation rejection or
collection after dropping its diagnostic references. Later, separately reviewed
short controls established those semantics and observed the remaining server
graph retention after normal shutdown. Their direct point-resource inspection,
two weak-reference phases and separate terminal-disposal attribution are recorded
in OWNERSHIP.md and semantic-evidence.json. Both ordinary failure
results remain intact. Certificate-store/native ownership gaps remain explicit;
these findings do not approve a common lifecycle/acceptance protocol.
The standalone adapter also lacks the candidate's xUnit/VSTest host; equal process memory overhead is not proven. Earlier original-subset managed/timer growth is an observation, not an approved RSS denominator or a proven SDK1 defect. Resource and operation parity require Astra/root review before any long run; no cap, baseline or acceptance protocol changes are included here.
Build and short controls
build.py selects original DLLs from the provided immutable directories (host
wins duplicate names), plus Shouldly 4.3.0/assertion dependencies only. It emits
SHA-256 provenance and a new external project. No production project/package file
is touched. The inventory lists exact types, fields, lifecycle APIs and MVIDs
without creating product instances.
python3 scripts/validation/opcua-lifecycle-memory/baseline-canonical/build.py \
--host "$originalHost" --client "$originalClient" --assertions "$assertionDlls" \
--output /tmp/original-canonical-build
python3 scripts/validation/opcua-lifecycle-memory/baseline-canonical/run_smoke.py \
--adapter /tmp/original-canonical-build/bin/Release/net10.0/adapter.dll \
--output /tmp/original-canonical-close --mode normal
python3 scripts/validation/opcua-lifecycle-memory/baseline-canonical/run_smoke.py \
--adapter /tmp/original-canonical-build/bin/Release/net10.0/adapter.dll \
--output /tmp/original-canonical-active-restart --mode active-restart
Add --inventory to build the read-only metadata inventory instead. The runner
uses a fresh private directory and a temporary environment credential with the
same prefix/byte length as the canonical fixture; it preserves the original child
exit. Exit 1 is an observed control failure; exit 2 is operations completed with
unsupported ownership parity. Neither is a passing baseline. Normal/restart modes
have a hard 120-second bound and no long-run option.
The later semantic modes are --mode semantic-retention and the separately gated
--mode semantic-terminal-dispose attribution control. They use the 120-second
operation deadline plus bounded independent cleanup and a 180-second outer runner
limit. Neither changes normal/active-restart results or authorizes a long run.