diff --git a/docs/validation/viewer-retirement-mounted-routes-20261004.rst b/docs/validation/viewer-retirement-mounted-routes-20261004.rst new file mode 100644 index 0000000000..bddc3cde75 --- /dev/null +++ b/docs/validation/viewer-retirement-mounted-routes-20261004.rst @@ -0,0 +1,112 @@ +Select mounted producer-bearing routes for viewer retirement +=========================================================== + +Source checkpoint: main01044edbc13c9a234a7ec52f772acca8015ccc99, +2026-10-04. Singer owns this narrow viewer-guide correction. Current open522 +retirement/selection and Planck's independent point-highlight investigation +are coordinated; open697 final summaries and693 analytical colour rendering +are disjoint. No production, dependency, runtime or scientific files change. + +Determining relation +------------------- + +Viewer state deliberately includes declared routes without a mounted layer. +NapariViewerProjectionABC.route_keys unions route titles and component groups; +layer_state_for projects mounted from the actual layer and producer_identities +from the original NapariComponentGroupStore.producer_identities_for. An +unmounted, zero-item route can correctly have an empty producer array. + +ViewerLayerRetirementControlOptions rejects empty producer tuples before +dispatch. Native NapariComponentAwareDisplayCoordinator.retire_layers also +requires every selected route to be mounted and its exact producer set to +match before any removal. This protects whole-set atomic admission, not a +ban on null invocation_key. Original PolyStore StreamProducerIdentity declares +invocation_key optional and its from_payload decoder preserves None; the +request annotation delegates to that decoder through BeforeValidator. + +The retained refused request selected eleven routes: eight populated producer +arrays plus three empty arrays. An earlier readback marks those three unmounted +and zero-item; the latest readback before retirement instead has them mounted, +with one item and populated producer identities. The request retained their +older empty arrays. The generic error is consistent with the empty-set guard, +not evidence that null invocation_key or a valid fresh mounted producer is +rejected. The first offline evidence assertion expected complete latest-state +parity and failed; that determining failure is preserved, not weakened or +misclassified as a product failure. Eight supplied populated identities match +the latest state exactly; the three stale empty entries do not. +The original request, error and subsequent scene remain untouched. No replay +or author feedback occurs. No unsupported retirement fallback is introduced. + +Owner and change +---------------- + +The declared manifest source for openhcs_viewer_qa is the existing packaged +references/viewer-qa.md; this guide has no RST backer. Its retirement procedure +now explicitly chooses mounted entries with nonempty returned producer arrays, +omits unmounted placeholders, and preserves null optional identity fields. +The declaration/decoder/coordinator remain the same owners; no copied identity +codec, route selector, store, registry, guard or alternate guide is added. + +Applicable review: BOUND-2 (use the existing producer decoder), IDEN-7 (a +declared route is not necessarily a mounted layer), and IDEN-6 (exact route and +producer incarnation, not title or port). Skill-creator limits this to the +missing operational distinction; Diataxis keeps it in the existing how-to +step, rather than creating a protocol reference or a new universal gate. + +Qualification checkpoint +------------------------ + +Offline SDK acceptance used the ORIGINAL installed receiving04 target and paired +interpreter, not a modified client or native process. build_server constructed +the actual registered retirement Tool's argument model. It decodes the original +eleven-entry request, after which the original request/control-options owner +rejects its three empty arrays with the exact retained error. Independently +constructing the eight populated, mounted entries preserves every returned +producer field through SDK -> original DTO -> as_tool_arguments, including +all null invocation_key values. No tool.run, transport send, native request, +client startup or viewer mutation was issued: dispatches0. + +sdk01 retained the first evidence assertion failure described above. sdk02 is +terminal0,7.36s,377876KiB maxRSS,Swap0. Both journal hashes remain unchanged; +their complete original paths/hashes, imported module paths/hashes and outcomes +are retained in the archive. Relevant owner ASTs are equal between current +source and this original installed target: retirement control-options, +request, result and native retire_layers. This is actual SDK/request admission, +not a new public TCP retirement or survivor remount. + +The existing audit Package/Repository AST owners parsed all701 production and +705 test modules at source766e2b9d3, zero omissions,32.83s/990724KiB/Swap0. +The selected direct consumers are capability declaration, request/result DTO, +original viewer-window service/gateway, CLI command and native coordinator; +two existing native/SDK test modules consume them. The original component-group +producer projection and PolyStore identity/optional-field decoding were read +semantically. This is complete source parse plus selected-family evidence, +not a full NRA/R1 detector or dynamic-resolution proof. No structural refactor +or production code change is made; no R0 improvement is claimed. + +The original project_knowledge_assets -> KnowledgeBaseService -> AgentSkillBundle +-> sync_skills owner chain passes1 focused control: source/package full bytes, +rendered whole viewer-QA/lifecycle content, full retirement subsection at the +original MAX_DOCUMENT_CHARS bound, search discovery, managed guide bytes and +unchanged second sync. Terminal0,9.69s whole invocation/7.21s pytest, +330708KiB/Swap0. The new assertion compares the COMPLETE source span with the +retrieved subsection; it does not match a phrase as behavioral proof. +Existing plugin-free asyncio-configuration warnings remain in raw output. +Skill-creator quick_validate reports Skill is valid, terminal0. The original +source bootstrap and source-byte-qualified tabular ABI backing were reused; +C++15acc82b8ab64268bd1ea4f83fa7a68f527bf317f002e9f39e15be321e03b28e +is unchanged. No new build/install, native execution or live managed-skill sync. + +Guide SHA256 bede1fa02bce04ed87ab31286327500df2771e8af3060db9f94cf43462acf4a7. +Byte-exact fifteen-member source/raw-log archive +viewer-retirement-mounted-routes-20261004.tar.gz is18762bytes, SHA256 +0fa36297a868602299f4fb6ca0e879d627f0eed9c7be278aa34f44b61e3fa32c; +tar --compare passed. First failed assertion, all original warnings and later +accepted outputs are preserved, not rewritten as clean evidence. Source guide, +test and verification caller are included; originals remain in the owned +validation/viewer-retirement-mounted-routes699 directory. Eight foreign +gitlinks and all earlier untracked ledgers are preserved, unstaged and unchanged. + +This documentation change does not claim a new native retirement, selected +geometry-survivor acceptance, autonomous gain or installed scientific update. +Existing active/frozen author packages remain unchanged. CI deferred. diff --git a/docs/validation/viewer-retirement-mounted-routes-20261004.tar.gz b/docs/validation/viewer-retirement-mounted-routes-20261004.tar.gz new file mode 100644 index 0000000000..25aa2c8af9 Binary files /dev/null and b/docs/validation/viewer-retirement-mounted-routes-20261004.tar.gz differ 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 0d624be258..28ca6bb7f3 100644 --- a/packaging/codex/openhcs/skills/use-openhcs/references/viewer-qa.md +++ b/packaging/codex/openhcs/skills/use-openhcs/references/viewer-qa.md @@ -190,9 +190,15 @@ contract is exposed: 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. + Select only entries with `mounted=true` and a nonempty `producer_identities` + array. State can also list declared, unmounted routes with empty arrays; + omit those placeholders from the retirement mapping, rather than sending + `[]` or inventing an identity for them. 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 + `invocation_key`; preserve `null` when that is the returned value. It is a + valid optional identity field, not a missing producer. 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. diff --git a/tests/unit/agent/test_viewer_retirement_guide_projection.py b/tests/unit/agent/test_viewer_retirement_guide_projection.py index 3bbad91f42..3598bdc6f8 100644 --- a/tests/unit/agent/test_viewer_retirement_guide_projection.py +++ b/tests/unit/agent/test_viewer_retirement_guide_projection.py @@ -7,7 +7,7 @@ from openhcs.agent.dto.knowledge import KnowledgeBaseDocumentRequest, KnowledgeBaseSearchRequest from openhcs.agent.services.knowledge_base_service import ( - KnowledgeBaseService, load_document_specs_from_manifest, + MAX_DOCUMENT_CHARS, 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 @@ -38,6 +38,17 @@ def test_real_viewer_guide_projection_retrieval_and_skill_delivery(tmp_path): # 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()) + if document_id == "openhcs_viewer_qa": + section = next(item for item in reply.sections + if item.section_id == "keep-iterative-review-within-its-resource-budget") + excerpt = service.get_document(KnowledgeBaseDocumentRequest.from_fields( + document_id=document_id, section_id=section.section_id, + max_chars=MAX_DOCUMENT_CHARS, + )) + assert not excerpt.errors and not excerpt.truncated + assert excerpt.content == "\n".join( + (root / path).read_text().splitlines()[section.start_line - 1:section.end_line] + ) 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)