From 16c203e206f63717a80bf36be6451006bad0e574 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Sat, 3 Oct 2026 04:42:06 -0400 Subject: [PATCH 1/2] Document explicit producer-incarnation retirement in canonical viewer guide --- .../mcp_knowledge_base_manifest.json | 4 +- docs/source/guides/viewer_management.rst | 7 +++ ...cit-producer-retirement-guide-20261003.rst | 57 +++++++++++++++++++ .../use-openhcs/references/viewer-qa.md | 46 ++++++++++++--- 4 files changed, 105 insertions(+), 9 deletions(-) create mode 100644 docs/validation/explicit-producer-retirement-guide-20261003.rst diff --git a/docs/source/development/mcp_knowledge_base_manifest.json b/docs/source/development/mcp_knowledge_base_manifest.json index f6fb9b0e67..33f0c3d5cc 100644 --- a/docs/source/development/mcp_knowledge_base_manifest.json +++ b/docs/source/development/mcp_knowledge_base_manifest.json @@ -806,8 +806,8 @@ "document_id": "openhcs_viewer_qa", "section_count": 0, "source_path": "packaging/codex/openhcs/skills/use-openhcs/references/viewer-qa.md", - "summary": "Distributed raw-only faint-structure and nuisance scans with progressively lower numeric upper display limits; diagnostic soma saturation versus publication intent. Matched raw-only/result-only/combined QA across channels, positions and scales; verify live state and recapture after canvas changes.", - "tags": ["viewer QA", "raw only result only combined", "overlay visibility", "canvas resize recapture", "multiscale channels contrast", "per image contrast check", "channel switch contrast window", "remote desktop compression native capture", "segmentation visual debugging", "faint neurite lower upper display limit", "diagnostic soma saturation", "noise background illumination scan"], + "summary": "Distributed raw-only faint-structure and nuisance scans with progressively lower numeric upper display limits; diagnostic soma saturation versus publication intent. Matched raw-only/result-only/combined QA across channels, positions and scales. Settled explicit producer-incarnation retirement preserves current routes and saved evidence; measure actual memory rather than layer visibility.", + "tags": ["viewer QA", "raw only result only combined", "overlay visibility", "canvas resize recapture", "multiscale channels contrast", "per image contrast check", "channel switch contrast window", "remote desktop compression native capture", "segmentation visual debugging", "faint neurite lower upper display limit", "diagnostic soma saturation", "noise background illumination scan", "viewer layer retirement", "producer incarnation expected_producers", "settled candidate memory RSS"], "title": "Review matched microscopy views" }, { diff --git a/docs/source/guides/viewer_management.rst b/docs/source/guides/viewer_management.rst index 0b59b07430..fb485f7151 100644 --- a/docs/source/guides/viewer_management.rst +++ b/docs/source/guides/viewer_management.rst @@ -26,6 +26,13 @@ stopped through its typed viewer-control endpoint or by the live manager that retains its exact process handle. Non-persistent viewers belong to the execution session's exact process group and are stopped when that session is cleaned up. +For resource-bound candidate review, retrieve the ``openhcs_viewer_qa`` knowledge +document, also bundled in the ``use-openhcs`` skill. Its conditional selective- +retirement procedure follows the installed typed capability and distinguishes +scene/payload custody from visibility, durable evidence and measured memory. +That is the operating procedure; this lifecycle overview does not maintain a +second retirement recipe or assume that every installed viewer supports it. + Display-axis ownership ---------------------- diff --git a/docs/validation/explicit-producer-retirement-guide-20261003.rst b/docs/validation/explicit-producer-retirement-guide-20261003.rst new file mode 100644 index 0000000000..d0e8f55fc5 --- /dev/null +++ b/docs/validation/explicit-producer-retirement-guide-20261003.rst @@ -0,0 +1,57 @@ +Explicit producer retirement: canonical guide checkpoint +======================================================== + +Parent assigns this disjoint source-only guide change; Copernicus released +viewer-qa/canonical viewer.rst on2026-10-03 and owns separate measurement prose. +Existing checkout reused on docs/explicit-producer-retirement-guide-20261003 +from current mainf7de; published522b2fa/source6c49 checkpoints remain unchanged. +Foreign gitlinks and untracked519/503 evidence are preserved. No runtime/client/ +native/provider/SCI contact, package/build/install, target04 or live skill edit. + +Actual owner and gap +------------------- + +Knowledge manifest declares openhcs_viewer_qa.source_path directly as +packaging/codex/openhcs/skills/use-openhcs/references/viewer-qa.md. There is no +RST backer for that specific guide. docs/source/guides/viewer_management.rst +is a separate lifecycle overview; it now links the original guide, not a copied +procedure. Manifest discovery metadata changes only the viewer-QA entry. +SKILL.md already routes viewer review to this reference and stays unchanged. + +The previous520 text exposed generic conditional retirement but omitted522's +actual expected_producers mapping and full invocation identity. Parent/Copernicus +read target04's actual capability/request and confirmed the gap. The new guide +reflects installed capability before use, requires known terminal/settled custody, +preserves current matched/raw/reference routes and durable evidence, submits one +explicit complete producer mapping, checks typed reply/actual surviving state +and records real memory telemetry. A known terminal failed candidate is eligible; +UNKNOWN/in-flight is not. No scalar cap/limit change, default disposal rule, +dataset identity/answer, new registry, codec or runtime implementation. + +This is a conditional how-to under Diataxis; skill-creator keeps the recipe in +one existing reference with existing routing. NRA identity/boundary guidance +distinguishes a route/title from producer incarnation and layer/style from row +selection; declaration-owned runtime behavior is not implemented in this docs PR. +Packaging/knowledge proof follows; no autonomous scientific gain is claimed. + +Separate522 row-selection query +------------------------------ + +The current52233 controls prove native model/payload release, survivor image +style/domain/calibration and active-layer preservation, NOT untargeted feature +row selection. Source query shows both Points and Shapes handlers mount new +native layers when shared-domain rematerialization is required. Original +NapariLayerSelectionAuthority.capture/restore carries only active_layer; +NapariLayerDisplayHandler.preserve_native_presentation carries visibility/ +opacity/blending. Neither captures/restores selected_data. Native Points starts +with empty Selection; Shapes starts with an empty selected-data set. Therefore +general selected-row preservation is absent across that remount boundary. + +Original NapariResultElementSelectionAuthority owns state/select_indices; +NapariResultSelectionController owns linked-subject groups, observer caches and +its synchronization guard. Any production correction belongs to those owners +and the original cooperative presentation/mount hooks, not Point metadata, +Root publication, a new selection mirror or this guide. No production fix or +red/green native acceptance is claimed by this source query. The updated guide +requires explicit row readback rather than implying that active layer/style +preserves it. Target04 remains immutable and original failures remain retained. diff --git a/packaging/codex/openhcs/skills/use-openhcs/references/viewer-qa.md b/packaging/codex/openhcs/skills/use-openhcs/references/viewer-qa.md index 76650efe27..b68c211ecf 100644 --- a/packaging/codex/openhcs/skills/use-openhcs/references/viewer-qa.md +++ b/packaging/codex/openhcs/skills/use-openhcs/references/viewer-qa.md @@ -171,13 +171,45 @@ only chosen routes with `openhcs_isolate_viewer_window_layers` changes visibilit not buffer lifetime. A new execution's stream reset is not selective retirement of earlier mounted results. Do not budget hidden layers as released memory. -Discover a supported selective layer-retirement capability before attempting -it. If exposed, retire only explicitly identified superseded candidate routes -at a settled boundary, then read back surviving payload/source identities, -axes and camera and verify the current matched comparison still works. Keep -the needed raw source mounted, including its domain for point-only review. -Do not use blanket scene clearing, history deletion or viewer restarts as an -iteration policy. +Discover the installed selective-retirement capability and reflect its request +before using it. When the compatible `openhcs_retire_viewer_window_layers` +contract is exposed: + +1. Preserve durable candidate files, source/declarations, captures and decisions. + Resolve execution/stream mutations to a known terminal disposition and let + receiver work settle before retirement. UNKNOWN, pending or in-flight work + is not permission to discard a route. A known failed terminal candidate can + be explicitly retired once no mutation is outstanding and its evidence is + retained; failure does not require keeping its buffers forever. +2. Read fresh, unfiltered viewer state on the same viewer incarnation. Choose + only explicitly superseded routes; retain the current matched candidate, + raw/source domain and reference routes still needed for the next comparison. + Build `expected_producers` by mapping each chosen **exact `route_key`** to + its **complete `producer_identities` array** from that readback, including + `invocation_key`. Do not shorten, fabricate or substitute identities from a + previous candidate, title or port. The receiving owner checks the whole set + before removal; a changed producer requires a fresh disposition/readback, + not a weaker identity check. +3. Submit that explicit mapping once. Retain the typed acknowledgement and + check `observed`, `applied`, errors, exact `retired_route_keys` and untargeted + `remaining_route_keys`. After a timeout or error, preserve the original + reply/disposition and inspect actual state read-only; do not assume rollback + or replay the mutation. This operation releases the selected scene/cache + entries, not persisted files or scientific history. +4. Read back survivor payload/source identities, domain, native calibration, + axes, camera and presentation, then verify the current matched comparison. + Check selected feature-row indices too: preserving the active layer/style + does not establish preserved Points/Shapes `selected_data`. Record a lost + or changed row as a receiving discrepancy, not another object's evidence. + +Compare permitted **actual memory telemetry** for the same native process or +scope before and after settled retirement. A removed/hidden layer count is not +an RSS/PSS measurement. Native cache/scene release and lower process RSS are +different observations: allocators may retain released memory, and a small +retirement need not show a measurable RSS drop. Record the measured change or +missing attribution and keep resource accounting conservative; do not assume +reclaimed headroom from the UI. Do not use blanket scene clearing, history +deletion or viewer restarts as an iteration policy. If no compatible retirement operation is exposed, record that contract gap and request its original runtime owner; do not invent a remove-layer command, From cb6c5ede503a7525cfe555485c96ba6d65b0af6f Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Sat, 3 Oct 2026 04:56:58 -0400 Subject: [PATCH 2/2] Qualify canonical retirement guide delivery and record receiving gaps --- ...cit-producer-retirement-guide-20261003.rst | 83 +++++++++++++++++- ...-producer-retirement-guide-20261003.tar.gz | Bin 0 -> 21615 bytes ...test_viewer_retirement_guide_projection.py | 51 +++++++++++ 3 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 docs/validation/explicit-producer-retirement-guide-20261003.tar.gz create mode 100644 tests/unit/agent/test_viewer_retirement_guide_projection.py diff --git a/docs/validation/explicit-producer-retirement-guide-20261003.rst b/docs/validation/explicit-producer-retirement-guide-20261003.rst index d0e8f55fc5..67d5304a03 100644 --- a/docs/validation/explicit-producer-retirement-guide-20261003.rst +++ b/docs/validation/explicit-producer-retirement-guide-20261003.rst @@ -32,7 +32,8 @@ This is a conditional how-to under Diataxis; skill-creator keeps the recipe in one existing reference with existing routing. NRA identity/boundary guidance distinguishes a route/title from producer incarnation and layer/style from row selection; declaration-owned runtime behavior is not implemented in this docs PR. -Packaging/knowledge proof follows; no autonomous scientific gain is claimed. +Packaging/knowledge delivery controls below pass; no autonomous scientific gain +is claimed. Separate522 row-selection query ------------------------------ @@ -55,3 +56,83 @@ Root publication, a new selection mirror or this guide. No production fix or red/green native acceptance is claimed by this source query. The updated guide requires explicit row readback rather than implying that active layer/style preserves it. Target04 remains immutable and original failures remain retained. + +Canonical delivery qualification +-------------------------------- + +At source16c203e206f63717a80bf36be6451006bad0e574, the original +project_knowledge_assets owner projects the actual declared package assets. +Original KnowledgeBaseService retrieves both openhcs_viewer_qa and +openhcs_viewer_management without truncation: complete source/projected bytes +and complete rendered reply content match. Actual knowledge search discovers +the canonical viewer guide for ``viewer layer retirement``. AgentSkillBundle +declares that same guide; original sync_skills copies identical bytes into an +isolated pytest destination and its next invocation reports unchanged. The live +managed skill, scientific installation and target04 are not synchronized. + +One new delivery control plus two unchanged projection/sync controls: 3PASS, +terminal0, elapsed6.16s, maxRSS257164KiB, original cgroup memory peak170.4M, +Swap0. Original skill-creator quick_validate reports ``Skill is valid!``: +terminal0, elapsed0.05s, maxRSS14920KiB, memory peak6.7M, Swap0. These establish +source/package/retrieval/sync delivery and skill schema validity, not native +retirement, agent decision quality, measured memory release or installed MCP +construction. All original warnings and raw logs are preserved byte-exact. + +Both checks used the existing paired interpreter +/home/ts/wt/basicpy-live-candidate-20260930/.venv/bin/python through the original +systemd-run owner: CPUQuota100%, AllowedCPUs3, MemoryMax536870912, +MemorySwapMax0, RuntimeMaxSec60. The pytest invocation used -B, --noconftest, +--import-mode=importlib, -p no:cacheprovider, -o addopts=, plugin autoload disabled +and existing single-thread/JIT-disabled source controls. Selected cases: + +* tests/unit/agent/test_viewer_retirement_guide_projection.py +* tests/unit/test_build_mcp_knowledge_assets.py::test_projection_includes_complete_declared_plugin_skill +* tests/unit/agent/test_skill_sync.py::test_dry_run_has_no_filesystem_side_effect + +Resource check returned critical disk/swap advisory (home4.3GiB, root7.5GiB, +swap14.6GiB, availableRAM19.4GiB). This tiny bounded text/projection check reused +dependencies; it is not admission evidence for another native/scientific lane. +No repeated qualification, full suite, build, install, launch or cleanup. + +Qualified source SHA256 values: + +* viewer-qa.md: cd45b329ea1c5826db298707c8a2224025c5417b91dd935c43f3f4788a03a75f +* viewer_management.rst: 9677f711607aaf8b3667ed02f29bf23103b33cc2518c5fb7d14b7feae3b85a14 +* mcp_knowledge_base_manifest.json: a9d1cffaf18c326e731e7af9180ef375aca5ecc7cc19ac7ea868de3db5b8582e + +Byte-exact archive explicit-producer-retirement-guide-20261003.tar.gz has eight +members (four original raw logs, the new delivery test and three qualified +canonical assets),21615bytes, SHA256 +b33b84c63f1597cf8c5e34a43cc2d5dbdd2227e24096b9b100110e208482903c. +Original tar --compare passed for every member. Raw warning whitespace remains +inside the archive rather than rewritten as clean evidence. + +Separate actual MCP construction failure +--------------------------------------- + +Parent's later frozen target04 run failed build_server with RecursionError; +only bootstrap diagnostics were exposed, no native/viewer was launched. Original +failure/freeze remains at next-metaxpress-p001-independent95-revision02-20261003/ +METAXPRESS_P001_INDEPENDENT95/author-workspace/output/runtime/mcp.stdout under +the parent issue-batch root. Neither the source lifecycle suite nor this guide +delivery suite proves actual server construction. + +Planck is the one combined integration owner. Singer read the original +engineering494/server-construction17.log and source boundary: request factory +expected_producers annotation passed unchanged through get_type_hints and +GeneratedMcpViewerRequestToolBinding to FastMCP create_model, which expanded +the implicit recursive JsonValue union. Planck's published91d449a71 corrects +the request declaration to original StreamProducerIdentity, whose nine fields +already own the complete identity; original from_payload already accepts that +type or original StreamProducerPayloadMapping. Generic server registration, +codec, invocation/whole-set guards and timeout remain unchanged. No JSON schema +alias fork, local Any escape, recursion-limit increase or competing patch here. +BOUND-2/8 apply: carry the existing owned identity across its schema boundary. + +Original full named-family census engineering494/SCHEMA-FAMILY17-BEFORE.json +reports704 production modules,12 dependency modules,1090 sites, zero parse +omissions; dynamic relationships are read semantically, not equivalent-behavior +proof. This is received owner evidence, not a newly run or broader dependency +audit. Singer recorded the exact determining trace/ownership on PR522 comment +5967387805. New-target actual MCP construction/health and later selective native +retirement remain that owner's receiving qualification; frozen04 stays intact. diff --git a/docs/validation/explicit-producer-retirement-guide-20261003.tar.gz b/docs/validation/explicit-producer-retirement-guide-20261003.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..ce8205021d26bd90c42be9bff2cd7cce68250cbc GIT binary patch literal 21615 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 literal 0 HcmV?d00001 diff --git a/tests/unit/agent/test_viewer_retirement_guide_projection.py b/tests/unit/agent/test_viewer_retirement_guide_projection.py new file mode 100644 index 0000000000..3bbad91f42 --- /dev/null +++ b/tests/unit/agent/test_viewer_retirement_guide_projection.py @@ -0,0 +1,51 @@ +"""Actual canonical guide -> package knowledge -> retrieval/managed skill bytes. + +This is documentation discovery/delivery proof, not an agent decision or native +retirement test. Only the original projection/retrieval/sync owners do work. +""" +from pathlib import Path + +from openhcs.agent.dto.knowledge import KnowledgeBaseDocumentRequest, KnowledgeBaseSearchRequest +from openhcs.agent.services.knowledge_base_service import ( + KnowledgeBaseService, load_document_specs_from_manifest, +) +from openhcs.agent.skill_bundle import AGENT_PLUGIN_MANIFEST_PATH, AgentSkillBundle +from openhcs.agent.skill_sync import sync_skills +from scripts.build_mcp_knowledge_assets import ( + KNOWLEDGE_MANIFEST_RELATIVE_PATH, project_knowledge_assets, +) + + +def test_real_viewer_guide_projection_retrieval_and_skill_delivery(tmp_path): + root = Path(__file__).resolve().parents[3] + destination = tmp_path / "package-knowledge" + projected = project_knowledge_assets(root, destination) + service = KnowledgeBaseService( + repo_root=destination, + document_specs=load_document_specs_from_manifest( + destination / KNOWLEDGE_MANIFEST_RELATIVE_PATH, + ), + ) + paths = [] + for document_id in ("openhcs_viewer_qa", "openhcs_viewer_management"): + reply = service.get_document(KnowledgeBaseDocumentRequest.from_fields( + document_id=document_id, max_chars=50000, + )) + assert not reply.errors and not reply.truncated + path = Path(reply.document.source_path) + assert destination / path in projected + assert (destination / path).read_bytes() == (root / path).read_bytes() + # The original renderer joins source lines; compare all content, not a + # wording substring that would pretend to prove the guide's decisions. + assert reply.content == "\n".join((root / path).read_text().splitlines()) + paths.append(path) + found = service.search(KnowledgeBaseSearchRequest(query="viewer layer retirement", limit=5)) + assert any(hit.document.document_id == "openhcs_viewer_qa" for hit in found.hits) + bundle = AgentSkillBundle.from_manifest(destination / AGENT_PLUGIN_MANIFEST_PATH) + guide = destination / paths[0] + assert guide in bundle.source_paths() + managed = tmp_path / "isolated-managed-skills" + (receipt,) = sync_skills(managed, bundle=bundle) + assert receipt.status == "installed" + assert (Path(receipt.path) / "references/viewer-qa.md").read_bytes() == guide.read_bytes() + assert sync_skills(managed, bundle=bundle)[0].status == "unchanged"