From dd805941f6c410a7525354aa9cc4406f29f981b9 Mon Sep 17 00:00:00 2001 From: Peter van Tol Date: Sat, 17 Oct 2026 13:14:51 +0200 Subject: [PATCH 1/4] docs: finalize scoped R58 projectless installation readiness delivery --- ...rojectless-idle-finalization-20261017.json | 16 ++ ...S_INSTALLATION_IDLE_DELIVERY_2026_10_17.md | 170 ++++++++++++++++++ 2 files changed, 186 insertions(+) create mode 100644 .github/product-version-operations/forge-r58-projectless-idle-finalization-20261017.json create mode 100644 docs/qualification/R58_PROJECTLESS_INSTALLATION_IDLE_DELIVERY_2026_10_17.md diff --git a/.github/product-version-operations/forge-r58-projectless-idle-finalization-20261017.json b/.github/product-version-operations/forge-r58-projectless-idle-finalization-20261017.json new file mode 100644 index 0000000..ddb17cd --- /dev/null +++ b/.github/product-version-operations/forge-r58-projectless-idle-finalization-20261017.json @@ -0,0 +1,16 @@ +{ + "baseline_version": "2.22.1", + "classification_rationale": "L3-PROJECTLESS-INSTALLATION-IDLE-V1-20261017-FINALIZATION", + "component": "product", + "event_lineage": "L3-PROJECTLESS-INSTALLATION-IDLE-V1-20261017-FINALIZATION", + "expected_head": "44300fe7739c7c3ce7d1b4b7a7f7dca569316c04", + "operation_id": "forge-r58-projectless-idle-finalization-20261017", + "policy_revision": "forge-bootstrap-release-cadence-v2", + "product": "forge", + "projection_paths": "product-version.json", + "release_class": "NO_BUMP", + "requested_bump": "none", + "requested_version": null, + "schema_version": "1", + "target_version": "2.22.1" +} diff --git a/docs/qualification/R58_PROJECTLESS_INSTALLATION_IDLE_DELIVERY_2026_10_17.md b/docs/qualification/R58_PROJECTLESS_INSTALLATION_IDLE_DELIVERY_2026_10_17.md new file mode 100644 index 0000000..fead426 --- /dev/null +++ b/docs/qualification/R58_PROJECTLESS_INSTALLATION_IDLE_DELIVERY_2026_10_17.md @@ -0,0 +1,170 @@ +# R58 projectless installation scheduler readiness + +Assignment L3-PROJECTLESS-INSTALLATION-IDLE-V1-20261017, R58/plan1. +Same sole LANE_3_WORK writer/session. R57 branch, PR298, original native-host +failures, evidence and budgets remain preserved and separately blocked; its +recurring timer stays PAUSED. R56 and predecessors remain CLOSED. +Canonical PATCH2.22.0→2.22.1/schema46; this own separate Finalization is NO_BUMP. + +## Delivered boundary + +The original clean installer0.4.29 GUI test installed both products and paired +the installation, but Forge demanded project-bound EP host configuration even +with no Mission work. The fix is entirely Forge-side. No installer, VM, helper, +login, credential, existing installed instance, or target repository is changed. +No dummy project, project registration, Mission identity or execution approval. + +After the existing instance-owned provider readiness check, the scheduler uses +the existing RuntimeServiceLock and one validated read-only RuntimeBootstrap +snapshot. Only provable emptiness of all eleven canonical work/history tables +permits IDLE before full InstalledDynamicMissionRuntime composition. These +include immutable exchange audit and non-FK dispatcher/planning/bootstrap state. +Current schema, integrity, ownership, maintenance fence, lease and root identity +remain enforced. Missing, corrupt, invalid, uncertain, fenced or nonempty storage +retains the unchanged strict runtime path. There is no general exception-to-IDLE +fallback. Project choice remains a later Workspace action; installation readiness +never claims project authority or execution readiness. + +This bounded fresh-empty classifier does not assert that arbitrary historical +completed Missions can run without project configuration. Provider failure still +blocks empty installations. Existing Mission/project/EP-host/authority checks and +runtime execution behavior remain required whenever work or uncertainty exists. + +## Regression and installed evidence boundaries + +Original empty-runtime source regression was RED before correction. Twelve +linked scheduler regressions cover provider readiness, persistent Mission work, +allocation/intake/dispatch/planning/audit uncertainty, missing/corrupt/fenced +storage, read-only storage digest and canonical lease behavior. Five additional +concurrency qualification guards reject duplicates, two winners, unknown/untyped +errors and wrong canonical transitions. + +The ordinary noneditable installed HTTP qualifier compares346 product payload +files against source and wheel. It starts separate Forge Server OS processes +outside the checkout against the existing EP HTTP simulator. External Codex login, +OS-owner and Keychain resolution are explicitly synthetic test boundaries; no real +provider login, model call, live EP or physical VM acceptance is claimed. + +The four cases prove: fresh projectless HTTP readiness IDLE/CONNECTED and +service_ready=true; same-instance restart/new PID with the same result; +canonical persistent resumable Mission without project remains NOT_READY with +PeerConfigurationError; a separately initialized zero-Mission runtime containing +only canonical uncertain dispatch evidence also remains NOT_READY. The latter +has a separate instance ID and does not inherit the Mission from the prior case. +Project/execution authority stays false. Own scratch and processes are cleaned. +Existing normal installed Mission execution separately proves one original +submission, completion and original readback/replay without duplicate effects. + +## Failures and narrow qualification correction retained + +The first wheel probe rejected stale ignored R57 build payload before effects; +original wheel/build/log retained, clean rebuild qualified. One extended installed +probe lacked the synthetic OS-owner fixture in a child and failed before the +expected project error; original retained. Another PASS shared uncertain evidence +with a prior Mission and was not sufficient independent proof; a new distinct +runtime closed that evidence gap. These do not become historical PASS retroactively. + +CI39016840074 on f60f418 passed1652 canonical tests and strict changed-product +coverage789/917=86.04%, but failed the existing installed concurrent-start gate. +Original log/artifact retained; missing individual race outcomes were not +reconstructed. A controlled actual installed RED then GREEN demonstrated the +specific legitimate typed WAITING_FOR_EXECUTION→CREATED refusal after one winner. +The qualification classifier now recognizes only that exact observed canonical +refusal in addition to the prior exact refusal set. One winner, one rejection, +one planner/submission/POST, completion, replay and original readback remain +mandatory. Duplicate or unrelated outcomes still FAIL. Closed public diagnostics +expose only bounded enums/counts. The optional controlled fixture calls original +preflight and does not alter the normal matrix or product controller/deadlines. + +Prior actual TDE39016840030 is assessment FAIL/repository FAILED, not PASS: +703OPEN/65CRITICAL/638HIGH. One new own qualifier.run complexity26 was removed by +helper extraction using the same radon6.0.1 analyzer; run now7, helpers≤8. +Legacy qualifier _phase32/main64 remain unchanged, concurrent helper30→25. +No policy, threshold, suppression or waiver was introduced. Original findings +and all cancelled/failed attempts remain retained separately. + +## Qualified implementation and normal protected delivery + +[Implementation PR299](https://github.com/pcvantol/forge/pull/299) normally +protected-squash-merged at2026-10-17T11:14:01Z to +44300fe7739c7c3ce7d1b4b7a7f7dca569316c04. Exact reviewed head +dfc1f1dcb321088be0be137a566f1bf8d6a812ea and actual protected main share +treeaa8103bad189eb37ac9f426f02a332606e4fc166. Active strict ruleset21986307 +required the PR, resolved conversations and Test and static validation, with +zero bypass actors. Normal merge bound the exact expected head; source and +primary checkout history and all retained branches remain intact. + +[Complete current CI39023848383](https://github.com/pcvantol/forge/actions/runs/39023848383) +SUCCESS:1659 canonical tests in1842.956s, five existing Linux/profile skips; +all ordinary installed integration and preservation steps passed. Strict +changed-production executable coverage server_runtime.py789/917=86.04%, +strictly greater than80.2%. CodeQL and canonical product version checks passed. +Actual hosted checkout3bd04e1718d9b9ee45ff0b4e1cc2df3e57cf7814 contains the +reviewed head as parent and has the identical tree verified against main. + +[Projectless installed artifact12097409726](https://github.com/pcvantol/forge/actions/runs/39023848383/artifacts/12097409726) +contains four casesPASS and cleanuptrue; actual original ZIP SHA256 +178cb7a43dfa3c29f360bc65efdb8f7837fb4d0912a1efb64afc41ea4b8f7487 matches +registry and CRC. Receipt SHA256 +1a97382dc812b2472963a6f42919e394848017e50b2d32cdca3d4c3ee2ece878; +ordinary hosted wheel SHA256 +1ed0064f7befdd583b6e8756933a0622b2d8b938239467acaef979871064271d. +All346 source/wheel/installed payloads verified. Earlier actual Mac installed +proofs retain their own different wheel/receipt digests; they are not retagged. + +[Existing installed successor artifact12098571628](https://github.com/pcvantol/forge/actions/runs/39023848383/artifacts/12098571628) +proves102 scenariosPASS, including one original planner/submission/POST and +completion/replay for concurrent start. ZIP SHA256 +30783230138103f8c6a5639222409c8650954fb1326a18e30bbb8872333c6db5. +[Candidate artifact12099400050](https://github.com/pcvantol/forge/actions/runs/39023848383/artifacts/12099400050) +has11 positive casesPASS plus the explicitly expected revoked-grant +negative control and cleanuptrue; ZIP SHA256 +8d0d28f8f4cea5aaf1cdf9cea9110a979f5917867ac9e683c2a3876d533d154e. +[Preservation artifact12098593524](https://github.com/pcvantol/forge/actions/runs/39023848383/artifacts/12098593524) +is PRESERVATION_PRODUCER_PASS, seven cases/expected negative/cleanuptrue; +ZIP SHA256c13f4d23cb881defd75eb1941216ddab50275751fa2b0c35a969d895f566c3e2. +Original ZIPs, registry hashes and CRC independently checked. + +A separate TDE coverage run39020535592 failed before assessment:1657 tests, +one existing Candidate BUSINESS/crash-after-effect error reading an empty PID +signal file while its writer was open. Original log remains retained; no +assessment was reconstructed. A deterministic paused actual file writer made +that visibility race RED. The test-only correction creates exclusive private +mode0600 pending storage, writes and closes the PID, then atomically publishes +the ready marker. Failure leaves no advertised boundary or pending file. +Two linked regressions GREEN; unchanged canonical boundary predicate, exact +child PID check, crash/kill/recovery and15/20/5-second deadlines. New ordinary +installed Candidate11PASS independently qualifies that correction. Product and +workflow bytes are unchanged by this synchronization repair. + +[Current TDE39023848386](https://github.com/pcvantol/forge/actions/runs/39023848386) +ran1659 canonical testsPASS in2841.314s and all four capabilities VALID. +WorkflowSUCCESS is still actual assessmentFAIL/repositoryFAILED/exits2/2: +702OPEN complexity findings,65CRITICAL/637HIGH. The own new harness26 finding +was removed; retained existing debt is not relabelled PASS. No policy change, +suppression, waiver or acceptance/timeout relaxation. Both independent Quality +and Security audited all28 changed-path findings against source context and +found no concrete remaining scoped behavior/security blocker. Twenty-five +records concern unchanged source structures; three modified qualifier functions +remain real OPEN maintainability findings. Actual +[TDE artifact12097814707](https://github.com/pcvantol/forge/actions/runs/39023848386/artifacts/12097814707) +ZIP SHA256b2c8be9fd85e09e643a8d65980162b3140573e9d0098bddac516cf5090329781 +matches registry and CRC. Hosted checkout/source/tree binding was verified. + +Independent complete source/content and final actual-evidence PREMERGE +ASSURANCE PASS both bind exactdfc1f1d and the above original artifacts/logs, +strict coverage, source/tree/payload identity, negative controls and cleanup. +Own implementation consumed corrections6/installed candidatecycles14 and +Quality7/Security7 calls. All previous failed/superseded attempts, source, +proofs and budgets remain preserved; this Finalization adds its own review +and CI records without reset. Earlier complete CI39020535589 is separately +source-bound SUCCESS1657tests/2798.237s; it does not replace the current result. + +## Own finalization and terminal boundary + +This document records only actual scoped delivery evidence. Protected own +Finalization, NEW exact-final-main ordinary installed HTTP/restart evidence, +official protected-main release qualification/publication, immutable registry +artifact digests/readback and safe cleanup must all complete before terminal +CLOSED registration. Earlier candidate receipts are not a new final-main proof. +L1 controls its later installer product selection. No automatic successor. From e1bb7fc049664fa4d4aad3048336334a1bab10fe Mon Sep 17 00:00:00 2001 From: Peter van Tol Date: Sat, 17 Oct 2026 13:16:01 +0200 Subject: [PATCH 2/4] docs: distinguish executed R58 regressions from fail-closed source guards --- ...LESS_INSTALLATION_IDLE_DELIVERY_2026_10_17.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/docs/qualification/R58_PROJECTLESS_INSTALLATION_IDLE_DELIVERY_2026_10_17.md b/docs/qualification/R58_PROJECTLESS_INSTALLATION_IDLE_DELIVERY_2026_10_17.md index fead426..56a6103 100644 --- a/docs/qualification/R58_PROJECTLESS_INSTALLATION_IDLE_DELIVERY_2026_10_17.md +++ b/docs/qualification/R58_PROJECTLESS_INSTALLATION_IDLE_DELIVERY_2026_10_17.md @@ -20,9 +20,10 @@ snapshot. Only provable emptiness of all eleven canonical work/history tables permits IDLE before full InstalledDynamicMissionRuntime composition. These include immutable exchange audit and non-FK dispatcher/planning/bootstrap state. Current schema, integrity, ownership, maintenance fence, lease and root identity -remain enforced. Missing, corrupt, invalid, uncertain, fenced or nonempty storage -retains the unchanged strict runtime path. There is no general exception-to-IDLE -fallback. Project choice remains a later Workspace action; installation readiness +remain enforced. Missing, corrupt, invalid or fenced storage fails closed during +validation before runtime composition. Valid nonempty work/history takes the +unchanged strict runtime path. Uncertainty cannot justify the empty-store proof; +there is no general exception-to-IDLE fallback. Project choice remains a later Workspace action; installation readiness never claims project authority or execution readiness. This bounded fresh-empty classifier does not assert that arbitrary historical @@ -33,9 +34,12 @@ runtime execution behavior remain required whenever work or uncertainty exists. ## Regression and installed evidence boundaries Original empty-runtime source regression was RED before correction. Twelve -linked scheduler regressions cover provider readiness, persistent Mission work, -allocation/intake/dispatch/planning/audit uncertainty, missing/corrupt/fenced -storage, read-only storage digest and canonical lease behavior. Five additional +linked scheduler regressions exercise provider readiness, persistent Mission +work, orphan allocation/dispatch, hostbinding/audit and malformed-workset +uncertainty, missing/corrupt storage, read-only storage digest, canonical lease +contention and existing service-tick forwarding. Intake, planning and maintenance +fences are additional source-enforced guards; separate intake-only, planning-only +and active-maintenance cases are not claimed as executed regressions here. Five additional concurrency qualification guards reject duplicates, two winners, unknown/untyped errors and wrong canonical transitions. From e4182c0df1c92003603901a3bb8dfc34aa1c1751 Mon Sep 17 00:00:00 2001 From: Peter van Tol Date: Sat, 17 Oct 2026 14:07:27 +0200 Subject: [PATCH 3/4] fix(qualification): retain bounded concept recovery failures and atomic crash signals --- ...S_INSTALLATION_IDLE_DELIVERY_2026_10_17.md | 34 ++++++++ .../qualify_installed_mission_concepts.py | 86 ++++++++++++++++++- .../qualify_mission_concept_recovery.py | 12 ++- tests/test_candidate_boundary_publication.py | 20 +++-- .../test_mission_qualification_diagnostics.py | 64 ++++++++++++++ 5 files changed, 208 insertions(+), 8 deletions(-) create mode 100644 tests/test_mission_qualification_diagnostics.py diff --git a/docs/qualification/R58_PROJECTLESS_INSTALLATION_IDLE_DELIVERY_2026_10_17.md b/docs/qualification/R58_PROJECTLESS_INSTALLATION_IDLE_DELIVERY_2026_10_17.md index 56a6103..e372007 100644 --- a/docs/qualification/R58_PROJECTLESS_INSTALLATION_IDLE_DELIVERY_2026_10_17.md +++ b/docs/qualification/R58_PROJECTLESS_INSTALLATION_IDLE_DELIVERY_2026_10_17.md @@ -164,6 +164,40 @@ proofs and budgets remain preserved; this Finalization adds its own review and CI records without reset. Earlier complete CI39020535589 is separately source-bound SUCCESS1657tests/2798.237s; it does not replace the current result. +## Postimplementation main qualification repair + +Automatic implementation-mainCI39027908952 later failed in the existing +chat-first installed producer after16 passing cases. Its original public +receipt reports AssertionError at test_mission_concept_recovery without the +nested traceback. The missing private nested log is not reconstructed and +the exact original assertion remains UNKNOWN. Original +[artifact12100376974](https://github.com/pcvantol/forge/actions/runs/39027908952/artifacts/12100376974) +ZIP SHA25660740cfd1a90c7d225116b5de77b1b741d3da28df6f3604a0d9fd9a3f1e36c28 +and original failed-step log remain retained. + +Inspection and a deterministic actual paused writer independently demonstrated +the same empty-ready-PID visibility flaw in that concept recovery harness. +Its own atomic private pending-write/close/replace repair is linked to the +existing two publication regressions, now exercised for both owned crash +harnesses. This observed flaw is not asserted to reconstruct the earlier +hosted failure. Canonical boundary predicates, exact child PID check, +crash/kill/recovery assertions and all original deadlines remain unchanged. + +The installed concept qualifier now records bounded diagnostic facts for a +failed test: allowlisted method/stage/error types and source filename/function/ +line, with bounded arrays. Raw exception messages, absolute paths and arbitrary +subtest parameters remain in private logs. Secondary diagnostic errors preserve +the original failing unittest result. A private log write failure cannot +replace an existing test failure; a log failure after otherwise passing tests +still blocks the gate. Four linked regressions exercise those boundaries. + +This necessary qualification-only repair is part of this own Finalization. +Product/runtime, workflow gates, version, schema and acceptance criteria are +unchanged. Fresh installed positive/negative concept qualification, complete +current CI/TDE and separate source/Finalization assurance remain required; +the earlier docs-only candidate and its checks remain historical source-bound +facts. No prior failure or consumption is removed or relabelled PASS. + ## Own finalization and terminal boundary This document records only actual scoped delivery evidence. Protected own diff --git a/scripts/qualification/qualify_installed_mission_concepts.py b/scripts/qualification/qualify_installed_mission_concepts.py index 1fee24b..6afb390 100644 --- a/scripts/qualification/qualify_installed_mission_concepts.py +++ b/scripts/qualification/qualify_installed_mission_concepts.py @@ -17,6 +17,85 @@ 'test_mission_concept_readiness','test_mission_concept_continuity') + +_DIAGNOSTIC_METHODS = frozenset(( + 'test_real_os_crashes_and_lost_response_generation', + 'test_real_os_crashes_between_compound_effects_and_lost_response', + 'test_current_os_signer_drift_between_effects_stops_and_recovers_original_intent', + 'test_concurrent_confirmation_rejects_contender_without_duplicate_effects', +)) +_DIAGNOSTIC_STAGES = frozenset(( + 'crash-before-provider', 'crash-after-result', 'lost-response', + 'crash-before-registration', 'crash-after-registration', + 'crash-business-between-stores', 'crash-after-business', + 'crash-architecture-between-stores', 'crash-after-architecture', + 'crash-after-allocation', 'crash-after-intake', +)) +_DIAGNOSTIC_ERRORS = frozenset(( + 'AssertionError', 'ValueError', 'RuntimeError', 'TimeoutExpired', + 'HTTPError', 'URLError', 'FileNotFoundError', 'PermissionError', + 'OSError', 'TypeError', 'KeyError', 'JSONDecodeError', +)) + + +def _failure_facts(test, error, params): + import traceback + method = getattr(test, '_testMethodName', '') + kind = error[0].__name__ + stage = params.get('stage') + frames = [] + for frame in traceback.extract_tb(error[2]): + filename = Path(frame.filename).name + if filename in ('qualify_mission_concept_recovery.py', 'test_mission_concept_recovery.py'): + function = frame.name if frame.name in _DIAGNOSTIC_METHODS | {'phase', 'process_case', 'reopen', '_publish_boundary_pid'} else 'OTHER_FRAME' + frames.append({'file': filename, 'function': function, 'line': min(frame.lineno, 10000)}) + return { + 'test': method if method in _DIAGNOSTIC_METHODS else 'OTHER_TEST', + 'error': kind if kind in _DIAGNOSTIC_ERRORS else 'OTHER_ERROR', + 'stage': stage if stage in _DIAGNOSTIC_STAGES else 'OTHER_STAGE', + 'frames': frames[-4:], + } + + +class DiagnosticTestResult(unittest.TextTestResult): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.failure_diagnostics = [] + + def _remember(self, test, error, params=None): + try: + facts = _failure_facts(test, error, {} if params is None else params) + except Exception: + # Secondary diagnostics cannot replace the original test outcome. + facts = {'test': 'OTHER_TEST', 'error': 'DIAGNOSTIC_UNAVAILABLE', + 'stage': 'OTHER_STAGE', 'frames': []} + if facts not in self.failure_diagnostics and len(self.failure_diagnostics) < 16: + self.failure_diagnostics.append(facts) + + def addError(self, test, error): + self._remember(test, error) + super().addError(test, error) + + def addFailure(self, test, error): + self._remember(test, error) + super().addFailure(test, error) + + def addSubTest(self, test, subtest, error): + if error is not None: + self._remember(test, error, subtest.params) + super().addSubTest(test, subtest, error) + + +def _retain_test_log(path, stream, failed): + try: + path.write_text(stream.getvalue()) + except OSError: + if not failed: + raise + return False + return True + + def positive_control(root): spec=spec_from_file_location('concept_recovery_control',SOURCE/'scripts/qualification/qualify_mission_concept_recovery.py') driver=module_from_spec(spec);spec.loader.exec_module(driver) @@ -59,6 +138,7 @@ def run(args): 'source_revision':args.source_revision,'artifact':artifact, 'schema_sha256':'sha256:'+sha256(schema.read_bytes()).hexdigest(), 'result':'FAIL','case_count':0,'cases':[],'failure':None, + 'failure_diagnostics':[], 'diagnostic_log_write_failures':0, 'expected_failure_control':args.failure_control, 'external_fault':'REVOKED_POSITIVE_CONCEPT_GRANT' if args.failure_control else None, 'limitations':['Actual noneditable HTTP/auth/G001/context/provider adapter/validator/storage/approvals/Intake/worklist services.', @@ -76,8 +156,10 @@ def run(args): module=__import__(name) suite=unittest.defaultTestLoader.loadTestsFromModule(module) stage=name;stream=StringIO() - result=unittest.TextTestRunner(stream=stream,verbosity=2).run(suite) - (output/(name+'.private.log')).write_text(stream.getvalue()) + result=unittest.TextTestRunner(stream=stream,verbosity=2,resultclass=DiagnosticTestResult).run(suite) + receipt['failure_diagnostics'].extend(result.failure_diagnostics) + retained=_retain_test_log(output/(name+'.private.log'),stream,not result.wasSuccessful()) + receipt['diagnostic_log_write_failures']+=int(not retained) if not result.wasSuccessful():raise AssertionError('selected installed concept boundary failed') receipt['cases'].append({'case':name,'test_count':result.testsRun,'result':'PASS'}) receipt['case_count']+=result.testsRun diff --git a/scripts/qualification/qualify_mission_concept_recovery.py b/scripts/qualification/qualify_mission_concept_recovery.py index eacfe0c..42d9ecc 100644 --- a/scripts/qualification/qualify_mission_concept_recovery.py +++ b/scripts/qualification/qualify_mission_concept_recovery.py @@ -38,6 +38,16 @@ def counts(root): return result +def _publish_boundary_pid(root): + pending = root / 'boundary.pending.private' + pending.touch(mode=0o600, exist_ok=False) + try: + pending.write_text(str(os.getpid())) + os.replace(pending, root / 'boundary.ready.private') + finally: + pending.unlink(missing_ok=True) + + def phase(root, stage, mode): ready, _ = cases(); q = ready.qual command = json.loads((root/'request.private.json').read_text()) @@ -64,7 +74,7 @@ def identity(): 'governance_decisions','candidate_decision_receipts','mission_state')) == expected if stage=='crash-after-allocation':hit = hit and actual['allocations']==1 if hit: - (root/'boundary.ready.private').write_text(str(os.getpid())) + _publish_boundary_pid(root) time.sleep(20) return q.utils.fixture.IDENTITY with patch.object(q.utils.composition.MacOSGeneratedUIDIdentityAdapter,'resolve',side_effect=identity), ExitStack() as stack: diff --git a/tests/test_candidate_boundary_publication.py b/tests/test_candidate_boundary_publication.py index 1fd7b75..e264c7a 100644 --- a/tests/test_candidate_boundary_publication.py +++ b/tests/test_candidate_boundary_publication.py @@ -9,9 +9,9 @@ from unittest.mock import patch -def driver(): +def driver(filename='qualify_installed_candidate_decisions.py'): source = Path(__file__).resolve().parents[1] - spec = spec_from_file_location('candidate_boundary_probe', source / 'scripts/qualification/qualify_installed_candidate_decisions.py') + spec = spec_from_file_location('candidate_boundary_probe', source / 'scripts/qualification' / filename) module = module_from_spec(spec) spec.loader.exec_module(module) return module @@ -19,13 +19,18 @@ def driver(): class CandidateBoundaryPublicationTests(unittest.TestCase): def test_ready_path_is_not_visible_while_pid_write_is_incomplete(self): - module = driver() + for filename, marker in [('qualify_installed_candidate_decisions.py', 'decision-boundary.ready.private'), ('qualify_mission_concept_recovery.py', 'boundary.ready.private')]: + with self.subTest(qualifier=filename): + self.check_publication(filename, marker) + + def check_publication(self, filename, marker): + module = driver(filename) opened, release = Event(), Event() original = Path.open failures = [] with TemporaryDirectory() as tmp: root = Path(tmp) - ready = root / 'decision-boundary.ready.private' + ready = root / marker @contextmanager def delayed_open(path, mode='r', *args, **kwargs): @@ -58,7 +63,12 @@ def publish(): self.assertEqual(list(root.iterdir()), [ready]) def test_failed_publication_does_not_advertise_a_boundary(self): - module = driver() + for filename in ['qualify_installed_candidate_decisions.py', 'qualify_mission_concept_recovery.py']: + with self.subTest(qualifier=filename): + self.check_failed_publication(filename) + + def check_failed_publication(self, filename): + module = driver(filename) with TemporaryDirectory() as tmp: root = Path(tmp) with patch.object(module.os, 'replace', side_effect=OSError('synthetic publication failure')): diff --git a/tests/test_mission_qualification_diagnostics.py b/tests/test_mission_qualification_diagnostics.py new file mode 100644 index 0000000..59dc357 --- /dev/null +++ b/tests/test_mission_qualification_diagnostics.py @@ -0,0 +1,64 @@ +"""Bounded qualification failure evidence keeps the original acceptance result.""" +from importlib.util import module_from_spec, spec_from_file_location +from io import StringIO +import json +from pathlib import Path +import unittest +from unittest.mock import patch + + +def driver(): + path = Path(__file__).resolve().parents[1] / 'scripts/qualification/qualify_installed_mission_concepts.py' + spec = spec_from_file_location('mission_qualification_diagnostics', path) + module = module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +class MissionQualificationDiagnosticTests(unittest.TestCase): + def test_subtest_diagnostics_exclude_raw_messages_paths_and_parameters(self): + module = driver() + + class Failure(unittest.TestCase): + def runTest(self): + with self.subTest(stage='crash-after-intake', secret='private-value'): + raise ValueError('/private/secret-token private-value') + + result = unittest.TextTestRunner(stream=StringIO(), resultclass=module.DiagnosticTestResult).run(Failure()) + self.assertFalse(result.wasSuccessful()) + self.assertEqual(len(result.errors), 1) + facts = result.failure_diagnostics[0] + self.assertEqual(facts['error'], 'ValueError') + self.assertEqual(facts['stage'], 'crash-after-intake') + encoded = json.dumps(facts) + for private in ['secret-token', 'private-value', '/private/']: + self.assertNotIn(private, encoded) + + def test_secondary_diagnostic_failure_cannot_replace_original_error(self): + module = driver() + + class Failure(unittest.TestCase): + def runTest(self): + raise ValueError('original-test-error') + + with patch.object(module, '_failure_facts', side_effect=OSError('diagnostic fault')): + result = unittest.TextTestRunner(stream=StringIO(), resultclass=module.DiagnosticTestResult).run(Failure()) + self.assertFalse(result.wasSuccessful()) + self.assertEqual(len(result.errors), 1) + self.assertIn('original-test-error', result.errors[0][1]) + self.assertEqual(result.failure_diagnostics[0]['error'], 'DIAGNOSTIC_UNAVAILABLE') + + def test_test_failure_survives_secondary_log_write_failure(self): + module = driver() + with patch.object(Path, 'write_text', side_effect=OSError('log fault')): + self.assertFalse(module._retain_test_log(Path('unused'), StringIO('original failure'), True)) + + def test_log_failure_still_blocks_an_otherwise_successful_gate(self): + module = driver() + with patch.object(Path, 'write_text', side_effect=OSError('log fault')): + with self.assertRaises(OSError): + module._retain_test_log(Path('unused'), StringIO('test success'), False) + + +if __name__ == '__main__': + unittest.main() From fdde175c0686950e2f3b106bb937df4ae37d9655 Mon Sep 17 00:00:00 2001 From: Peter van Tol Date: Sat, 17 Oct 2026 14:21:18 +0200 Subject: [PATCH 4/4] fix(qualification): retain primary test failure across encoding and stream log errors --- ...LESS_INSTALLATION_IDLE_DELIVERY_2026_10_17.md | 5 ++++- .../qualify_installed_mission_concepts.py | 2 +- tests/test_mission_qualification_diagnostics.py | 16 +++++++++++----- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/docs/qualification/R58_PROJECTLESS_INSTALLATION_IDLE_DELIVERY_2026_10_17.md b/docs/qualification/R58_PROJECTLESS_INSTALLATION_IDLE_DELIVERY_2026_10_17.md index e372007..fba553f 100644 --- a/docs/qualification/R58_PROJECTLESS_INSTALLATION_IDLE_DELIVERY_2026_10_17.md +++ b/docs/qualification/R58_PROJECTLESS_INSTALLATION_IDLE_DELIVERY_2026_10_17.md @@ -189,7 +189,10 @@ line, with bounded arrays. Raw exception messages, absolute paths and arbitrary subtest parameters remain in private logs. Secondary diagnostic errors preserve the original failing unittest result. A private log write failure cannot replace an existing test failure; a log failure after otherwise passing tests -still blocks the gate. Four linked regressions exercise those boundaries. +still blocks the gate. Four linked regressions exercise those boundaries, +including OSError, closed-stream ValueError and UnicodeEncodeError on both the +already-failed and otherwise-passing paths. Actual narrow-catch RED retained; +the correction applies only to secondary qualification-log handling. This necessary qualification-only repair is part of this own Finalization. Product/runtime, workflow gates, version, schema and acceptance criteria are diff --git a/scripts/qualification/qualify_installed_mission_concepts.py b/scripts/qualification/qualify_installed_mission_concepts.py index 6afb390..75a0551 100644 --- a/scripts/qualification/qualify_installed_mission_concepts.py +++ b/scripts/qualification/qualify_installed_mission_concepts.py @@ -89,7 +89,7 @@ def addSubTest(self, test, subtest, error): def _retain_test_log(path, stream, failed): try: path.write_text(stream.getvalue()) - except OSError: + except Exception: if not failed: raise return False diff --git a/tests/test_mission_qualification_diagnostics.py b/tests/test_mission_qualification_diagnostics.py index 59dc357..8ee0b63 100644 --- a/tests/test_mission_qualification_diagnostics.py +++ b/tests/test_mission_qualification_diagnostics.py @@ -50,14 +50,20 @@ def runTest(self): def test_test_failure_survives_secondary_log_write_failure(self): module = driver() - with patch.object(Path, 'write_text', side_effect=OSError('log fault')): - self.assertFalse(module._retain_test_log(Path('unused'), StringIO('original failure'), True)) + for error in [OSError('log fault'), ValueError('closed log stream'), + UnicodeEncodeError('utf-8', chr(0xd800), 0, 1, 'secondary encoding fault')]: + with self.subTest(error_type=type(error).__name__): + with patch.object(Path, 'write_text', side_effect=error): + self.assertFalse(module._retain_test_log(Path('unused'), StringIO('original failure'), True)) def test_log_failure_still_blocks_an_otherwise_successful_gate(self): module = driver() - with patch.object(Path, 'write_text', side_effect=OSError('log fault')): - with self.assertRaises(OSError): - module._retain_test_log(Path('unused'), StringIO('test success'), False) + for error in [OSError('log fault'), ValueError('closed log stream'), + UnicodeEncodeError('utf-8', chr(0xd800), 0, 1, 'secondary encoding fault')]: + with self.subTest(error_type=type(error).__name__): + with patch.object(Path, 'write_text', side_effect=error): + with self.assertRaises(type(error)): + module._retain_test_log(Path('unused'), StringIO('test success'), False) if __name__ == '__main__':