diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 84667b8..aeae9aa 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,6 +1,6 @@ # Repository permission endpoint confirmed @placerda has admin permission. -# Administrators must separately require latest-head code-owner approval; -# this file alone does not activate branch protection. +# Ownership routing only; Q5 does not mandate independent/latest-head approval. +# This file alone does not activate branch protection or required checks. /.quality/ @placerda /pyproject.toml @placerda /requirements-quality.txt @placerda diff --git a/.github/scripts/check-quality.py b/.github/scripts/check-quality.py index 1c0ce4a..15fceab 100644 --- a/.github/scripts/check-quality.py +++ b/.github/scripts/check-quality.py @@ -1,4 +1,4 @@ -"""Read-only Python quality checks. Candidate records cannot approve their own debt.""" +"""Read-only quality checks with protected rules and source-bound exception evidence.""" from __future__ import annotations @@ -1006,7 +1006,7 @@ def suppressions(source): repr(bindings.signature(resolved)))] += 1 return result extra = suppressions(new) - suppressions(old) - findings = [finding("suppression-growth", "", 0, "New suppression requires protected review")] if extra else [] + findings = [finding("suppression-growth", "", 0, "New suppression exceeds protected policy")] if extra else [] if not preserve_annotations: return findings @@ -1324,7 +1324,7 @@ def run_checks(root, base, requested, *, test_evidence=None): if "policy" in requested: output = findings["policy"] if not base_policy_text: - output.append(finding("bootstrap-review", "", 0, "Protected base has no policy; maintainer bootstrap approval and administrator activation required")) + output.append(finding("bootstrap-review", "", 0, "Protected base has no policy; adopt the protected evaluator and verify required checks before claiming enforcement")) else: output.extend(policy_changes(set(base_records["typing-scope"]["module_ids"]), set(records["typing-scope"]["module_ids"]), @@ -1334,7 +1334,7 @@ def run_checks(root, base, requested, *, test_evidence=None): before_sources, {paths[name]: source for name, source in sources.items()})) for field in ("runtime_roots", "toolchain", "forbidden", "private_allowances", "required_checks", "dynamic_imports", "review"): if minimum[field] != records["policy"][field]: - output.append(finding("policy-change", "", 0, f"Protected review required: {field}")) + output.append(finding("policy-change", "", 0, f"Protected policy differs: {field}")) protected_modules = {m["id"]: m for m in minimum["modules"]} for module in modules: old_module = protected_modules.get(module["id"]) @@ -1351,19 +1351,17 @@ def run_checks(root, base, requested, *, test_evidence=None): output.append(finding("policy-change", module["id"], 0, f"Protected module surface: {field}")) if STAGES.index(records["typing-scope"]["coverage_stage"]) < STAGES.index(base_records["typing-scope"]["coverage_stage"]): output.append(finding("policy-change", "", 0, "Typing stage cannot regress")) - if base_records["exceptions"] != records["exceptions"]: - output.append(finding("policy-change", "", 0, "Exception changes require protected review")) for name in PROTECTED_FILES: old = base_text(root, base_sha, name) current = (root / name).read_text(encoding="utf-8") if (root / name).exists() else None if old != current: - output.append(finding("policy-change", "", 0, f"Protected review required: {name}")) + output.append(finding("policy-change", "", 0, f"Protected file differs: {name}")) old_config = tomllib.loads(base_text(root, base_sha, "pyproject.toml")) current_config = tomllib.loads((root / "pyproject.toml").read_text(encoding="utf-8")) if old_config.get("tool", {}).get("mypy") != current_config.get("tool", {}).get("mypy") or ( old_config.get("tool", {}).get("ruff") != current_config.get("tool", {}).get("ruff") ) or old_config.get("tool", {}).get("importlinter") != current_config.get("tool", {}).get("importlinter"): - output.append(finding("policy-change", "", 0, "Tool settings require protected review")) + output.append(finding("policy-change", "", 0, "Tool settings differ from protected policy")) output.extend(validate_moves([], modules, {})) output.extend(finding("unclassified-module", name, 0, "New runtime modules need inventory and blocking typing") for name in sorted(unknown)) inventory_paths = {m["path"] for m in modules} @@ -1397,7 +1395,10 @@ def run_checks(root, base, requested, *, test_evidence=None): handler_sites = [(paths[name], dict(h, module_id=module_ids.get(name, name))) for name, source in sources.items() for h in broad_handlers(name, source)] handlers = [handler for _, handler in handler_sites] - entries = base_records["exceptions"]["entries"] if base_policy_text else [] + # Registry data is eligible for technical validation, not approval authority. + # Bootstrap still needs an adopted protected evaluator; candidate records + # cannot change the rules, toolchain, evidence runner or aggregate. + entries = records["exceptions"]["entries"] if base_policy_text else [] exception_findings, used = validate_exception_records( handlers, entries, root, verified_tests=verified, stage=records["typing-scope"]["coverage_stage"]) diff --git a/.quality/exception-technical-review.json b/.quality/exception-technical-review.json new file mode 100644 index 0000000..28070cb --- /dev/null +++ b/.quality/exception-technical-review.json @@ -0,0 +1,689 @@ +{ + "schema_version": 1, + "assessment_type": "AI technical review; not independent human approval", + "reviewed_base": "ec1a12912ae0771231274da9970684ffd92ac851", + "summary": { + "reviewed": 30, + "keep": 7, + "correct": 23, + "remove": 0 + }, + "gate_status": "Eligible for exact-source technical validation; authorized adoption of the new protected checker remains pending", + "expiry_changed": false, + "decisions": [ + { + "id": "ui-boundary-02-cleanup", + "decision": "keep", + "module_id": "gpt_rag_ui.api.feedback", + "symbol": "register_feedback_handlers.handle_feedback", + "line": 144, + "source_path": "src/gpt_rag_ui/api/feedback.py", + "original_fingerprint": "9601a863df38f68150ffe5e5e271bcca00a3aa72b29aef0908922b67e312c832", + "candidate_fingerprint": "9601a863df38f68150ffe5e5e271bcca00a3aa72b29aef0908922b67e312c832", + "broad_capture_justification": "A removal failure must not block notification of the established backend outcome or trigger another write. Retain the form reference for explicit later cleanup; cancellation propagates.", + "failure_outcome": "contractual-best-effort", + "authorization_and_write_review": "Existing deny/propagation/confirmed-write ordering is preserved; no retry, success fallback or authorization downgrade was added.", + "diagnostic_decision": "Retain existing static diagnostics or unchanged exception propagation; this boundary does not emit dependency exception payloads.", + "behavior_tests": [ + "tests/test_boundary_failures.py::BoundaryFailureTests.test_feedback_form_cleanup_preserves_submission_outcome" + ], + "diagnostic_tests": [], + "human_review_status": "not required by technical policy; not performed by this AI assessment" + }, + { + "id": "ui-boundary-11-cleanup", + "decision": "keep", + "module_id": "embed_security", + "symbol": "configure_copilot_bridge_guards.disconnect_socket", + "line": 1267, + "source_path": "src/gpt_rag_ui/auth/embed_security.py", + "original_fingerprint": "2d819a865b36cc04496168f08aea00216a82347f49d65411f9faa4ab1b9a6ec8", + "candidate_fingerprint": "2d819a865b36cc04496168f08aea00216a82347f49d65411f9faa4ab1b9a6ec8", + "broad_capture_justification": "Record ordinary secondary failure without replacing the primary disconnect exception. Cancellation is not suppressed; no claim of successful termination or lifecycle reconciliation.", + "failure_outcome": "cleanup-propagation", + "authorization_and_write_review": "Existing deny/propagation/confirmed-write ordering is preserved; no retry, success fallback or authorization downgrade was added.", + "diagnostic_decision": "Retain existing static diagnostics or unchanged exception propagation; this boundary does not emit dependency exception payloads.", + "behavior_tests": [ + "tests/test_embed_security.py::BridgeGuardTests.test_disconnect_double_failure_preserves_primary_with_safe_diagnostic", + "tests/test_embed_security.py::BridgeGuardTests.test_disconnect_cancellation_remains_distinct" + ], + "diagnostic_tests": [], + "human_review_status": "not required by technical policy; not performed by this AI assessment" + }, + { + "id": "ui-boundary-01", + "decision": "correct", + "module_id": "gpt_rag_ui.api.download_routes", + "symbol": "register_secure_download_route.download_blob_file", + "line": 105, + "source_path": "src/gpt_rag_ui/api/download_routes.py", + "original_fingerprint": "b756bff96eda34351f57aba6b1c3fe329e1d500e669db581dcdb139a1e6e0a2d", + "candidate_fingerprint": "ea27f01138258ee18dc29fc3f0cf19d238a02cfd203f0bd0121e13d05df132cb", + "broad_capture_justification": "Stage 2 P2: unexpected downloader acquisition failures become generic HTTP 500 with no-store after grant, principal, ownership and target checks. Conversation resolution occurs before this try and lazy DownloadStream iteration occurs after it; neither is translated by this catch. No resolver/late-stream response guarantee or new boundary design is proposed.", + "failure_outcome": "failure-translation", + "authorization_and_write_review": "Existing deny/propagation/confirmed-write ordering is preserved; no retry, success fallback or authorization downgrade was added.", + "diagnostic_decision": "Remove dependency exception text/chains from logs; retain static operation, exception class and terminal code location. Also omit raw citation URL for boundary 27.", + "behavior_tests": [ + "tests/test_download_security.py::DownloadSecurityTests.test_secure_download_route_fails_closed", + "tests/test_download_security.py::DownloadSecurityTests.test_download_acquisition_boundary_excludes_resolver_and_late_iteration", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged" + ], + "diagnostic_tests": [ + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_warning_diagnostic_preserves_level_without_error_payload" + ], + "human_review_status": "not required by technical policy; not performed by this AI assessment" + }, + { + "id": "ui-boundary-02", + "decision": "keep", + "module_id": "gpt_rag_ui.api.feedback", + "symbol": "register_feedback_handlers.handle_feedback", + "line": 127, + "source_path": "src/gpt_rag_ui/api/feedback.py", + "original_fingerprint": "5754b34c00eb31d439a9770d6ee1ef8e2bf6c44c77193ef80f64c00d0e20d26a", + "candidate_fingerprint": "5754b34c00eb31d439a9770d6ee1ef8e2bf6c44c77193ef80f64c00d0e20d26a", + "broad_capture_justification": "Auth/validation/backend exceptions select an error notification and are diagnosed before optional form removal. Cleanup failure cannot change the backend outcome or retry the write; cancellation propagates.", + "failure_outcome": "failure-translation", + "authorization_and_write_review": "Existing deny/propagation/confirmed-write ordering is preserved; no retry, success fallback or authorization downgrade was added.", + "diagnostic_decision": "Retain existing static diagnostics or unchanged exception propagation; this boundary does not emit dependency exception payloads.", + "behavior_tests": [ + "tests/test_boundary_failures.py::BoundaryFailureTests.test_feedback_boundary_emits_failure_toast_not_success", + "tests/test_boundary_failures.py::BoundaryFailureTests.test_feedback_form_cleanup_preserves_submission_outcome", + "tests/test_boundary_failures.py::BoundaryFailureTests.test_feedback_notification_failure_does_not_retry_written_feedback" + ], + "diagnostic_tests": [], + "human_review_status": "not required by technical policy; not performed by this AI assessment" + }, + { + "id": "ui-boundary-03", + "decision": "correct", + "module_id": "embed_auth", + "symbol": "CopilotSessionStore._notify_invalidated", + "line": 175, + "source_path": "src/gpt_rag_ui/auth/embed_auth.py", + "original_fingerprint": "ee84c052306e126a4806e67c8e15feffcf73a63294804d629ae0c1e59d693b62", + "candidate_fingerprint": "6b8fd6402ce8aefe8bdf5927e3220e2e77c4da5043fb1370ea3382d3920dd481", + "broad_capture_justification": "Session invalidation is already committed; a failed injected socket callback must be logged while remaining invalidations continue. Cancellation is not swallowed.", + "failure_outcome": "contractual-best-effort", + "authorization_and_write_review": "Existing deny/propagation/confirmed-write ordering is preserved; no retry, success fallback or authorization downgrade was added.", + "diagnostic_decision": "Remove dependency exception text/chains from logs; retain static operation, exception class and terminal code location. Also omit raw citation URL for boundary 27.", + "behavior_tests": [ + "tests/test_boundary_failures.py::BoundaryFailureTests.test_invalidation_callback_failure_does_not_skip_other_sessions", + "tests/test_boundary_failures.py::BoundaryFailureTests.test_cancellation_is_not_converted_to_successful_invalidation", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged" + ], + "diagnostic_tests": [ + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_warning_diagnostic_preserves_level_without_error_payload" + ], + "human_review_status": "not required by technical policy; not performed by this AI assessment" + }, + { + "id": "ui-boundary-04", + "decision": "correct", + "module_id": "embed_security", + "symbol": "CopilotSocketRegistry.bind_connection", + "line": 308, + "source_path": "src/gpt_rag_ui/auth/embed_security.py", + "original_fingerprint": "6e6629412e9cc7c57e4a00e23d815331c9645753f8c54b583b2e494c18931a78", + "candidate_fingerprint": "7c9e97958fc3a394bf5a7ba3186e149e343084ae96608a0d68b1a1c5d231b4f1", + "broad_capture_justification": "Replacement must not steal the old association when an injected teardown callback fails; preserve tracking and reject the replacement for retry.", + "failure_outcome": "failure-translation", + "authorization_and_write_review": "Existing deny/propagation/confirmed-write ordering is preserved; no retry, success fallback or authorization downgrade was added.", + "diagnostic_decision": "Remove dependency exception text/chains from logs; retain static operation, exception class and terminal code location. Also omit raw citation URL for boundary 27.", + "behavior_tests": [ + "tests/test_embed_security.py::BridgeGuardTests.test_failed_replacement_disconnect_keeps_old_association", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged" + ], + "diagnostic_tests": [ + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_warning_diagnostic_preserves_level_without_error_payload" + ], + "human_review_status": "not required by technical policy; not performed by this AI assessment" + }, + { + "id": "ui-boundary-05", + "decision": "correct", + "module_id": "embed_security", + "symbol": "CopilotSocketRegistry.disconnect_session", + "line": 506, + "source_path": "src/gpt_rag_ui/auth/embed_security.py", + "original_fingerprint": "4c0b76598807ea2c2354b694e6dfa8231207d104845e22dd2234d142d5c83a37", + "candidate_fingerprint": "f8fafe292eb7bd758684abcabe3abf5cd2f679db0f5175a965592c323363663d", + "broad_capture_justification": "Deny the binding before teardown, continue independent socket cleanup on arbitrary callback failure and count only actual disconnects; retain failed associations as inactive.", + "failure_outcome": "contractual-best-effort", + "authorization_and_write_review": "Existing deny/propagation/confirmed-write ordering is preserved; no retry, success fallback or authorization downgrade was added.", + "diagnostic_decision": "Remove dependency exception text/chains from logs; retain static operation, exception class and terminal code location. Also omit raw citation URL for boundary 27.", + "behavior_tests": [ + "tests/test_embed_security.py::BridgeGuardTests.test_failed_session_disconnect_retains_denied_binding", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged" + ], + "diagnostic_tests": [ + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_warning_diagnostic_preserves_level_without_error_payload" + ], + "human_review_status": "not required by technical policy; not performed by this AI assessment" + }, + { + "id": "ui-boundary-06", + "decision": "correct", + "module_id": "embed_security", + "symbol": "_target_has_copilot_sockets", + "line": 1011, + "source_path": "src/gpt_rag_ui/auth/embed_security.py", + "original_fingerprint": "f32bcc076c9df5109e20369857d83491585c57dfcaff7fbfd856f9fbc2462095", + "candidate_fingerprint": "6647ec551e5e108b83c460853cef4b82694614f4190195f6940f49867534b2c3", + "broad_capture_justification": "An unknown manager failure cannot prove the room excludes Copilot clients; conservatively retain the Copilot restriction when any protected sockets exist.", + "failure_outcome": "failure-translation", + "authorization_and_write_review": "Existing deny/propagation/confirmed-write ordering is preserved; no retry, success fallback or authorization downgrade was added.", + "diagnostic_decision": "Remove dependency exception text/chains from logs; retain static operation, exception class and terminal code location. Also omit raw citation URL for boundary 27.", + "behavior_tests": [ + "tests/test_embed_security.py::BridgeGuardTests.test_bridge_recipient_lookup_failure_is_conservative", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged" + ], + "diagnostic_tests": [ + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_warning_diagnostic_preserves_level_without_error_payload" + ], + "human_review_status": "not required by technical policy; not performed by this AI assessment" + }, + { + "id": "ui-boundary-07", + "decision": "correct", + "module_id": "embed_security", + "symbol": "disconnect_copilot_session", + "line": 1160, + "source_path": "src/gpt_rag_ui/auth/embed_security.py", + "original_fingerprint": "2a8561cb32d5bc9bbd7d8a97ed77033906a84087036941eb24a435f460d59d61", + "candidate_fingerprint": "f126f99ec58d631ae2495d9aae0df0046ee8e82df29e25af1f5682965d9ed793", + "broad_capture_justification": "Invalidation already denies the session; one failed transport closure must not abort cleanup of other reserved transports or claim a successful disconnect.", + "failure_outcome": "contractual-best-effort", + "authorization_and_write_review": "Existing deny/propagation/confirmed-write ordering is preserved; no retry, success fallback or authorization downgrade was added.", + "diagnostic_decision": "Remove dependency exception text/chains from logs; retain static operation, exception class and terminal code location. Also omit raw citation URL for boundary 27.", + "behavior_tests": [ + "tests/test_embed_security.py::BridgeGuardTests.test_invalidation_transport_failures_are_logged_without_counting_success", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged" + ], + "diagnostic_tests": [ + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_warning_diagnostic_preserves_level_without_error_payload" + ], + "human_review_status": "not required by technical policy; not performed by this AI assessment" + }, + { + "id": "ui-boundary-08", + "decision": "correct", + "module_id": "embed_security", + "symbol": "disconnect_copilot_session", + "line": 1171, + "source_path": "src/gpt_rag_ui/auth/embed_security.py", + "original_fingerprint": "767f30df3d2c994058add75878ffceb0b5647093ee1fe4a5d70a9d55c88b135e", + "candidate_fingerprint": "7d897438c4c05800a7f1a24d48b817d314dc5c441beedab8da458ba1a8d4d069", + "broad_capture_justification": "When no registry exists, close every known socket independently and count only successful callbacks; arbitrary callback failures remain observable.", + "failure_outcome": "contractual-best-effort", + "authorization_and_write_review": "Existing deny/propagation/confirmed-write ordering is preserved; no retry, success fallback or authorization downgrade was added.", + "diagnostic_decision": "Remove dependency exception text/chains from logs; retain static operation, exception class and terminal code location. Also omit raw citation URL for boundary 27.", + "behavior_tests": [ + "tests/test_embed_security.py::BridgeGuardTests.test_invalidation_transport_failures_are_logged_without_counting_success", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged" + ], + "diagnostic_tests": [ + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_warning_diagnostic_preserves_level_without_error_payload" + ], + "human_review_status": "not required by technical policy; not performed by this AI assessment" + }, + { + "id": "ui-boundary-09", + "decision": "correct", + "module_id": "embed_security", + "symbol": "_terminate_socket", + "line": 1197, + "source_path": "src/gpt_rag_ui/auth/embed_security.py", + "original_fingerprint": "3c54455817d43acb6b53599c65f8068719f50f7cbd87d4a8b7772cd507ef61ce", + "candidate_fingerprint": "fe54814940b16bf710cba38c94cee0ef7c4e4b146d4676b5a04d9e6b2b30e8bb", + "broad_capture_justification": "Do not untrack an invalidated socket while its arbitrary disconnect callback has failed; return false so callers retain denial and retry capacity.", + "failure_outcome": "failure-translation", + "authorization_and_write_review": "Existing deny/propagation/confirmed-write ordering is preserved; no retry, success fallback or authorization downgrade was added.", + "diagnostic_decision": "Remove dependency exception text/chains from logs; retain static operation, exception class and terminal code location. Also omit raw citation URL for boundary 27.", + "behavior_tests": [ + "tests/test_embed_security.py::BridgeGuardTests.test_failed_termination_retains_invalidated_association", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged" + ], + "diagnostic_tests": [ + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_warning_diagnostic_preserves_level_without_error_payload" + ], + "human_review_status": "not required by technical policy; not performed by this AI assessment" + }, + { + "id": "ui-boundary-10", + "decision": "correct", + "module_id": "embed_security", + "symbol": "_terminate_socket", + "line": 1207, + "source_path": "src/gpt_rag_ui/auth/embed_security.py", + "original_fingerprint": "4be83a749d9b20b8afcb438353ab909c8a7a8ff11b952de879ff1acea435e8a8", + "candidate_fingerprint": "389838963dc7b28cf257f55ae3b102a8e58cfe92dc3f9b7990366d04ef031520", + "broad_capture_justification": "The socket is already disconnected; a framework session deletion failure must be logged while finally unbinding the dead socket. The session stays marked for clearing.", + "failure_outcome": "contractual-best-effort", + "authorization_and_write_review": "Existing deny/propagation/confirmed-write ordering is preserved; no retry, success fallback or authorization downgrade was added.", + "diagnostic_decision": "Remove dependency exception text/chains from logs; retain static operation, exception class and terminal code location. Also omit raw citation URL for boundary 27.", + "behavior_tests": [ + "tests/test_embed_security.py::BridgeGuardTests.test_failed_session_delete_still_unbinds_terminated_socket", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged" + ], + "diagnostic_tests": [ + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_warning_diagnostic_preserves_level_without_error_payload" + ], + "human_review_status": "not required by technical policy; not performed by this AI assessment" + }, + { + "id": "ui-boundary-11", + "decision": "keep", + "module_id": "embed_security", + "symbol": "configure_copilot_bridge_guards.disconnect_socket", + "line": 1264, + "source_path": "src/gpt_rag_ui/auth/embed_security.py", + "original_fingerprint": "3e7d5533c6158c88d52cd614b75ca662d6c061055693f4aaabcfe710485db7c8", + "candidate_fingerprint": "3e7d5533c6158c88d52cd614b75ca662d6c061055693f4aaabcfe710485db7c8", + "broad_capture_justification": "Callback failure attempts Engine.IO closure and re-raises the identical primary exception even if closure raises an ordinary exception. Cancellation remains distinct and propagates. This does not guarantee transport termination.", + "failure_outcome": "cleanup-propagation", + "authorization_and_write_review": "Existing deny/propagation/confirmed-write ordering is preserved; no retry, success fallback or authorization downgrade was added.", + "diagnostic_decision": "Retain existing static diagnostics or unchanged exception propagation; this boundary does not emit dependency exception payloads.", + "behavior_tests": [ + "tests/test_embed_security.py::BridgeGuardTests.test_disconnect_callback_failure_closes_transport_then_propagates", + "tests/test_embed_security.py::BridgeGuardTests.test_disconnect_double_failure_preserves_primary_with_safe_diagnostic", + "tests/test_embed_security.py::BridgeGuardTests.test_disconnect_cancellation_remains_distinct" + ], + "diagnostic_tests": [], + "human_review_status": "not required by technical policy; not performed by this AI assessment" + }, + { + "id": "ui-boundary-12", + "decision": "correct", + "module_id": "embed_security", + "symbol": "configure_copilot_bridge_guards.close_rejected_socket", + "line": 1298, + "source_path": "src/gpt_rag_ui/auth/embed_security.py", + "original_fingerprint": "db80c8a03d2058c018fb9d7fc1e0a316c3acb5a507ab8fff57657f35d2f07659", + "candidate_fingerprint": "79105f21e100ac3ea82664d7af2197631559d32f7a75f2582f305e431c20ca54", + "broad_capture_justification": "Cleanup failure must not replace or undo the established connection denial; log failed closure and retain the Socket.IO refusal.", + "failure_outcome": "contractual-best-effort", + "authorization_and_write_review": "Existing deny/propagation/confirmed-write ordering is preserved; no retry, success fallback or authorization downgrade was added.", + "diagnostic_decision": "Remove dependency exception text/chains from logs; retain static operation, exception class and terminal code location. Also omit raw citation URL for boundary 27.", + "behavior_tests": [ + "tests/test_embed_security.py::BridgeGuardTests.test_rejected_socket_remains_denied_if_transport_close_fails", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged" + ], + "diagnostic_tests": [ + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_warning_diagnostic_preserves_level_without_error_payload" + ], + "human_review_status": "not required by technical policy; not performed by this AI assessment" + }, + { + "id": "ui-boundary-13", + "decision": "keep", + "module_id": "embed_security", + "symbol": "configure_copilot_bridge_guards.guarded_engineio_connect", + "line": 1339, + "source_path": "src/gpt_rag_ui/auth/embed_security.py", + "original_fingerprint": "6f3cb7b0bb2d3b8fe7a19bc2559160c28ba9aa1887bf38fad362232b30592595", + "candidate_fingerprint": "6f3cb7b0bb2d3b8fe7a19bc2559160c28ba9aa1887bf38fad362232b30592595", + "broad_capture_justification": "Release the just-reserved transport slot on every unsuccessful callback exit, including asyncio cancellation, then propagate the same BaseException.", + "failure_outcome": "cleanup-propagation", + "authorization_and_write_review": "Existing deny/propagation/confirmed-write ordering is preserved; no retry, success fallback or authorization downgrade was added.", + "diagnostic_decision": "Retain existing static diagnostics or unchanged exception propagation; this boundary does not emit dependency exception payloads.", + "behavior_tests": [ + "tests/test_embed_security.py::BridgeGuardTests.test_engineio_admission_failure_releases_reservation_and_propagates" + ], + "diagnostic_tests": [], + "human_review_status": "not required by technical policy; not performed by this AI assessment" + }, + { + "id": "ui-boundary-14", + "decision": "correct", + "module_id": "embed_security", + "symbol": "configure_copilot_bridge_guards.guarded_engineio_disconnect", + "line": 1377, + "source_path": "src/gpt_rag_ui/auth/embed_security.py", + "original_fingerprint": "4ac0d7b3caeac5a1bc46a3e0b61139458a46947fbb78ba13f887e6f57196bab1", + "candidate_fingerprint": "7ffd476afc58f95422c5c4e904edeb344751e58e6c40c1952ea7b8e514386bf2", + "broad_capture_justification": "Invalidate bound sockets before the original callback so a still-closing transport cannot dispatch application messages. An ordinary callback failure triggers explicit namespace cleanup; unresolved bindings stay inactive and capacity-counted until successful reconciliation.", + "failure_outcome": "contractual-best-effort", + "authorization_and_write_review": "Existing deny/propagation/confirmed-write ordering is preserved; no retry, success fallback or authorization downgrade was added.", + "diagnostic_decision": "Remove dependency exception text/chains from logs; retain static operation, exception class and terminal code location. Also omit raw citation URL for boundary 27.", + "behavior_tests": [ + "tests/test_embed_security.py::BridgeGuardTests.test_real_engineio_original_failure_denies_messages_while_closing", + "tests/test_embed_security.py::BridgeGuardTests.test_engineio_cancellation_retains_invalidated_association", + "tests/test_embed_security.py::BridgeGuardTests.test_transport_invalidation_blocks_inflight_binding_not_other_transport", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged" + ], + "diagnostic_tests": [ + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_warning_diagnostic_preserves_level_without_error_payload" + ], + "human_review_status": "not required by technical policy; not performed by this AI assessment" + }, + { + "id": "ui-boundary-15", + "decision": "correct", + "module_id": "embed_security", + "symbol": "configure_copilot_bridge_guards.guarded_engineio_disconnect", + "line": 1385, + "source_path": "src/gpt_rag_ui/auth/embed_security.py", + "original_fingerprint": "7ed9c1f24af71facb62bbf72f2bcddb5902b989ffa7b59b4004320b7fe0d218d", + "candidate_fingerprint": "5634f57850672be0ef6d80d8292a730d7d637480cdaf885b94d50d0729f60c7b", + "broad_capture_justification": "Stored Engine.IO associations allow invalidation even when namespace enumeration fails. Release the Engine.IO reservation after ordinary failure handling, but retain unresolved Socket.IO bindings and their capacity until successful reconciliation; callback completion is not proof of physical shutdown.", + "failure_outcome": "contractual-best-effort", + "authorization_and_write_review": "Existing deny/propagation/confirmed-write ordering is preserved; no retry, success fallback or authorization downgrade was added.", + "diagnostic_decision": "Remove dependency exception text/chains from logs; retain static operation, exception class and terminal code location. Also omit raw citation URL for boundary 27.", + "behavior_tests": [ + "tests/test_embed_security.py::BridgeGuardTests.test_real_engineio_enumeration_failure_retains_invalidated_binding", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged" + ], + "diagnostic_tests": [ + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_warning_diagnostic_preserves_level_without_error_payload" + ], + "human_review_status": "not required by technical policy; not performed by this AI assessment" + }, + { + "id": "ui-boundary-16", + "decision": "correct", + "module_id": "embed_security", + "symbol": "configure_copilot_bridge_guards.guarded_engineio_disconnect", + "line": 1397, + "source_path": "src/gpt_rag_ui/auth/embed_security.py", + "original_fingerprint": "1abdf18fb4f6b76ab843ebf18b2ea6b0b951603cc8ed5e1e80bf92760f76f55d", + "candidate_fingerprint": "19a67130404f9b819c37276d16207bc6afa732f728030e342139624ae2170e11", + "broad_capture_justification": "One manager lookup failure must be logged without stopping independent namespace cleanup or final Engine.IO reservation release. The unresolved Socket.IO association remains invalidated and capacity-counted until successful reconciliation.", + "failure_outcome": "contractual-best-effort", + "authorization_and_write_review": "Existing deny/propagation/confirmed-write ordering is preserved; no retry, success fallback or authorization downgrade was added.", + "diagnostic_decision": "Remove dependency exception text/chains from logs; retain static operation, exception class and terminal code location. Also omit raw citation URL for boundary 27.", + "behavior_tests": [ + "tests/test_embed_security.py::BridgeGuardTests.test_real_engineio_lookup_failure_isolates_namespaces_and_recovers", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged" + ], + "diagnostic_tests": [ + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_warning_diagnostic_preserves_level_without_error_payload" + ], + "human_review_status": "not required by technical policy; not performed by this AI assessment" + }, + { + "id": "ui-boundary-17", + "decision": "correct", + "module_id": "embed_security", + "symbol": "configure_copilot_bridge_guards.guarded_engineio_disconnect", + "line": 1411, + "source_path": "src/gpt_rag_ui/auth/embed_security.py", + "original_fingerprint": "63616d3ef8bcf5d51354e23eb3ca5c5ba36004d9e99e0189f42de771a70db981", + "candidate_fingerprint": "9b41637957b7fe156c2f44eefe944b17a24c096054de53794df4c8d49b0f1bc0", + "broad_capture_justification": "An ordinary fallback manager failure remains observable without unbinding an unresolved socket. Retain its invalidated association and capacity until successful reconciliation, while continuing independent namespace cleanup and releasing the Engine.IO reservation.", + "failure_outcome": "contractual-best-effort", + "authorization_and_write_review": "Existing deny/propagation/confirmed-write ordering is preserved; no retry, success fallback or authorization downgrade was added.", + "diagnostic_decision": "Remove dependency exception text/chains from logs; retain static operation, exception class and terminal code location. Also omit raw citation URL for boundary 27.", + "behavior_tests": [ + "tests/test_embed_security.py::BridgeGuardTests.test_real_engineio_manager_failure_retains_binding_until_recovery", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged" + ], + "diagnostic_tests": [ + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_warning_diagnostic_preserves_level_without_error_payload" + ], + "human_review_status": "not required by technical policy; not performed by this AI assessment" + }, + { + "id": "ui-boundary-18", + "decision": "correct", + "module_id": "embed_security", + "symbol": "configure_copilot_bridge_guards.guarded_connect", + "line": 1675, + "source_path": "src/gpt_rag_ui/auth/embed_security.py", + "original_fingerprint": "6023a5c3b914404c725fb2ccb659e0080e9855a8972b24ea78c54e4e881b33b3", + "candidate_fingerprint": "ffd0495b5a7fffaeeccb089f1863573b04715fad7cfd05500d8c4537c8ae77e5", + "broad_capture_justification": "Failure of the framework's session construction must tear down the bound socket and become a connection refusal, never leave an accepted orphan.", + "failure_outcome": "failure-translation", + "authorization_and_write_review": "Existing deny/propagation/confirmed-write ordering is preserved; no retry, success fallback or authorization downgrade was added.", + "diagnostic_decision": "Remove dependency exception text/chains from logs; retain static operation, exception class and terminal code location. Also omit raw citation URL for boundary 27.", + "behavior_tests": [ + "tests/test_embed_security.py::BridgeGuardTests.test_chainlit_connect_failure_disconnects_before_untracking", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged" + ], + "diagnostic_tests": [ + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_warning_diagnostic_preserves_level_without_error_payload" + ], + "human_review_status": "not required by technical policy; not performed by this AI assessment" + }, + { + "id": "ui-boundary-19", + "decision": "keep", + "module_id": "embed_security", + "symbol": "configure_copilot_bridge_guards.guarded_disconnect", + "line": 1747, + "source_path": "src/gpt_rag_ui/auth/embed_security.py", + "original_fingerprint": "7ee3c2cc97faa8c215207137892fee1838625a03721792843ddfb6a41e2857a9", + "candidate_fingerprint": "7ee3c2cc97faa8c215207137892fee1838625a03721792843ddfb6a41e2857a9", + "broad_capture_justification": "Arbitrary callback failure must complete any teardown waiter exceptionally and propagate without incorrectly untracking a still-associated session.", + "failure_outcome": "propagation", + "authorization_and_write_review": "Existing deny/propagation/confirmed-write ordering is preserved; no retry, success fallback or authorization downgrade was added.", + "diagnostic_decision": "Retain existing static diagnostics or unchanged exception propagation; this boundary does not emit dependency exception payloads.", + "behavior_tests": [ + "tests/test_embed_security.py::BridgeGuardTests.test_failed_disconnect_handler_retains_association" + ], + "diagnostic_tests": [], + "human_review_status": "not required by technical policy; not performed by this AI assessment" + }, + { + "id": "ui-boundary-20", + "decision": "keep", + "module_id": "main", + "symbol": "_create_chainlit_app.handle_file_download", + "line": 828, + "source_path": "src/gpt_rag_ui/bootstrap.py", + "original_fingerprint": "f12b32d3233cf4a947884face7bdc8a86851c3f502456545ad3271c0f315e487", + "candidate_fingerprint": "f12b32d3233cf4a947884face7bdc8a86851c3f502456545ad3271c0f315e487", + "broad_capture_justification": "Stage 2 P2: standalone downloader failures return fixed generic text. Only typed ResourceNotFoundError maps to 404; arbitrary errors containing BlobNotFound remain 500. No dependency message is exposed to the client or failure diagnostic. This does not authorize that route for Copilot sessions.", + "failure_outcome": "failure-translation", + "authorization_and_write_review": "Existing deny/propagation/confirmed-write ordering is preserved; no retry, success fallback or authorization downgrade was added.", + "diagnostic_decision": "Retain existing static diagnostics or unchanged exception propagation; this boundary does not emit dependency exception payloads.", + "behavior_tests": [ + "tests/test_installed_package.py::InstalledPackageTests.test_installed_startup_preserves_documented_failure_boundaries" + ], + "diagnostic_tests": [], + "human_review_status": "not required by technical policy; not performed by this AI assessment" + }, + { + "id": "ui-boundary-21", + "decision": "correct", + "module_id": "main", + "symbol": "_create_chainlit_app._safe_openapi", + "line": 916, + "source_path": "src/gpt_rag_ui/bootstrap.py", + "original_fingerprint": "2f189faefdf72b8005e7dab4cda547b0fd93e39b1fa193f67a25cd7497522ea1", + "candidate_fingerprint": "9bb99cf79178e9fb367752844f21d5405a1a31a436298c333cde4a369057652e", + "broad_capture_justification": "H3 bounded implementation: failed schema introspection is logged and propagated, never advertised as a valid metadata-only schema. No failed schema is cached; a later request retries and only successful generation is cached. This individual exception proposal remains pending review, not approved.", + "failure_outcome": "propagation", + "authorization_and_write_review": "Existing deny/propagation/confirmed-write ordering is preserved; no retry, success fallback or authorization downgrade was added.", + "diagnostic_decision": "Remove dependency exception text/chains from logs; retain static operation, exception class and terminal code location. Also omit raw citation URL for boundary 27.", + "behavior_tests": [ + "tests/test_installed_package.py::InstalledPackageTests.test_installed_startup_preserves_documented_failure_boundaries", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged" + ], + "diagnostic_tests": [ + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_warning_diagnostic_preserves_level_without_error_payload" + ], + "human_review_status": "not required by technical policy; not performed by this AI assessment" + }, + { + "id": "ui-boundary-22", + "decision": "correct", + "module_id": "app", + "symbol": "get_auth_info", + "line": 300, + "source_path": "src/gpt_rag_ui/services/chat.py", + "original_fingerprint": "340bc8608e8c309f545703ffec7de3d0f8593ef209c5ec5fc2926bc4ef62611a", + "candidate_fingerprint": "bf0ec6030fe1c98ded509c27e73724060f97261d38264fb8982fc8b3ba2a1ee7", + "broad_capture_justification": "An unexpected refresh callback failure must clear the local user and return session_expired without an access token; it cannot preserve authenticated success.", + "failure_outcome": "failure-translation", + "authorization_and_write_review": "Existing deny/propagation/confirmed-write ordering is preserved; no retry, success fallback or authorization downgrade was added.", + "diagnostic_decision": "Remove dependency exception text/chains from logs; retain static operation, exception class and terminal code location. Also omit raw citation URL for boundary 27.", + "behavior_tests": [ + "tests/test_boundary_failures.py::BoundaryFailureTests.test_oauth_refresh_failure_clears_session_and_denies_request", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged" + ], + "diagnostic_tests": [ + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_warning_diagnostic_preserves_level_without_error_payload" + ], + "human_review_status": "not required by technical policy; not performed by this AI assessment" + }, + { + "id": "ui-boundary-23", + "decision": "correct", + "module_id": "app", + "symbol": "handle_message", + "line": 624, + "source_path": "src/gpt_rag_ui/services/chat.py", + "original_fingerprint": "b1b0561a84d3188d82a2b8640cfc758f118e0a1886cc95bc1419bd3406abd4d2", + "candidate_fingerprint": "5c98bae1918793c0ad9a242c52a7127cafe99b6fc4d0091f823482a054d9ea7d", + "broad_capture_justification": "H6 bounded implementation: false ingestion results and exceptions stop the accompanying question before backend invocation and finalize a reference-bearing notice requesting reattachment and resubmission. A failed attempt that allocated a new conversation ID restores the prior session value; retries ingest every reattached file under a fresh ID before a turn, without changing existing conversation ownership checks. uploaded_docs changes only after confirmed batch ingestion; failures preserve previous confirmed names and never claim Files received. The boolean client contract does not expose per-file partial success, so no partial batch is claimed confirmed. This individual exception proposal remains pending review, not approved.", + "failure_outcome": "failure-translation", + "authorization_and_write_review": "Existing deny/propagation/confirmed-write ordering is preserved; no retry, success fallback or authorization downgrade was added.", + "diagnostic_decision": "Remove dependency exception text/chains from logs; retain static operation, exception class and terminal code location. Also omit raw citation URL for boundary 27.", + "behavior_tests": [ + "tests/test_boundary_failures.py::BoundaryFailureTests.test_ingestion_failure_does_not_claim_processed_success", + "tests/test_boundary_failures.py::BoundaryFailureTests.test_upload_bookkeeping_requires_confirmation_before_question_continuation", + "tests/test_boundary_failures.py::BoundaryFailureTests.test_failed_upload_retry_ingests_all_files_before_turn", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged" + ], + "diagnostic_tests": [ + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_warning_diagnostic_preserves_level_without_error_payload" + ], + "human_review_status": "not required by technical policy; not performed by this AI assessment" + }, + { + "id": "ui-boundary-24", + "decision": "correct", + "module_id": "app", + "symbol": "handle_message", + "line": 914, + "source_path": "src/gpt_rag_ui/services/chat.py", + "original_fingerprint": "745f35b8acf978f9806573704dd103ef503cecb3dcbd1b84eb5df58488d9ebd2", + "candidate_fingerprint": "ad6979e446f0019651c4c18b10b66abf39ce0c75ed3b030df95cba4c4a9f10e0", + "broad_capture_justification": "The outer Chainlit event boundary translates arbitrary coordinator/stream failures into a reference-bearing technical error and always closes the generator.", + "failure_outcome": "failure-translation", + "authorization_and_write_review": "Existing deny/propagation/confirmed-write ordering is preserved; no retry, success fallback or authorization downgrade was added.", + "diagnostic_decision": "Remove dependency exception text/chains from logs; retain static operation, exception class and terminal code location. Also omit raw citation URL for boundary 27.", + "behavior_tests": [ + "tests/test_boundary_failures.py::BoundaryFailureTests.test_chat_failure_boundaries_report_errors_and_close_generators", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged" + ], + "diagnostic_tests": [ + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_warning_diagnostic_preserves_level_without_error_payload" + ], + "human_review_status": "not required by technical policy; not performed by this AI assessment" + }, + { + "id": "ui-boundary-25", + "decision": "correct", + "module_id": "app", + "symbol": "handle_message", + "line": 1064, + "source_path": "src/gpt_rag_ui/services/chat.py", + "original_fingerprint": "1c63d8d3596fd52c1ca0c8f8ef8e81cce533d248265762e77b05fed6b31d3a8a", + "candidate_fingerprint": "98757639cc367aacceacedee9b66ed04260a44defd022087f363614f1891a107", + "broad_capture_justification": "Keep the existing user-visible error and generator cleanup if the hosted stream fails outside its narrower authentication/cancellation mappings.", + "failure_outcome": "failure-translation", + "authorization_and_write_review": "Existing deny/propagation/confirmed-write ordering is preserved; no retry, success fallback or authorization downgrade was added.", + "diagnostic_decision": "Remove dependency exception text/chains from logs; retain static operation, exception class and terminal code location. Also omit raw citation URL for boundary 27.", + "behavior_tests": [ + "tests/test_boundary_failures.py::BoundaryFailureTests.test_chat_failure_boundaries_report_errors_and_close_generators", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged" + ], + "diagnostic_tests": [ + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_warning_diagnostic_preserves_level_without_error_payload" + ], + "human_review_status": "not required by technical policy; not performed by this AI assessment" + }, + { + "id": "ui-boundary-26", + "decision": "correct", + "module_id": "app", + "symbol": "handle_message", + "line": 1241, + "source_path": "src/gpt_rag_ui/services/chat.py", + "original_fingerprint": "207aa65b444e45b6567319918d07ecdeebb01080c281dab60bd57326131f6b63", + "candidate_fingerprint": "b197d32b6d5d9ae50f4040e7a9bee6a654c52ca382ac97c930c6ec6763f80a01", + "broad_capture_justification": "The Chainlit event must display a reference-bearing technical error on arbitrary orchestrator stream failure and close the generator rather than terminate silently.", + "failure_outcome": "failure-translation", + "authorization_and_write_review": "Existing deny/propagation/confirmed-write ordering is preserved; no retry, success fallback or authorization downgrade was added.", + "diagnostic_decision": "Remove dependency exception text/chains from logs; retain static operation, exception class and terminal code location. Also omit raw citation URL for boundary 27.", + "behavior_tests": [ + "tests/test_boundary_failures.py::BoundaryFailureTests.test_chat_failure_boundaries_report_errors_and_close_generators", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged" + ], + "diagnostic_tests": [ + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_warning_diagnostic_preserves_level_without_error_payload" + ], + "human_review_status": "not required by technical policy; not performed by this AI assessment" + }, + { + "id": "ui-boundary-27", + "decision": "correct", + "module_id": "gpt_rag_ui.services.citations", + "symbol": "_resolve_legacy_reference_href", + "line": 158, + "source_path": "src/gpt_rag_ui/services/citations.py", + "original_fingerprint": "03cb111ed45f097249afcbd161767a555a30e0614aee2a3a906bdb6120dbc93a", + "candidate_fingerprint": "d31971852bf36742a19710bbdeaf95cd888d88c3e28243348a31e3c57053dd70", + "broad_capture_justification": "A failed optional signing/link callback omits that citation with a warning instead of failing the already-generated answer or emitting a broken link.", + "failure_outcome": "contractual-best-effort", + "authorization_and_write_review": "Existing deny/propagation/confirmed-write ordering is preserved; no retry, success fallback or authorization downgrade was added.", + "diagnostic_decision": "Remove dependency exception text/chains from logs; retain static operation, exception class and terminal code location. Also omit raw citation URL for boundary 27.", + "behavior_tests": [ + "tests/test_boundary_failures.py::BoundaryFailureTests.test_optional_legacy_citation_failure_is_logged_and_omitted", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged" + ], + "diagnostic_tests": [ + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_warning_diagnostic_preserves_level_without_error_payload" + ], + "human_review_status": "not required by technical policy; not performed by this AI assessment" + }, + { + "id": "ui-boundary-28", + "decision": "correct", + "module_id": "hosted_continuity", + "symbol": "HostedContinuityCoordinator.run_turn", + "line": 356, + "source_path": "src/gpt_rag_ui/services/hosted_continuity.py", + "original_fingerprint": "d15439dcd8f2a94ee9c6aecc64dbd49fb1d4be3cd8c174047105052f2687fbfc", + "candidate_fingerprint": "d1e58c9a181daa2e14557aa350d733791d8ea47639193e748d216721153dffaa", + "broad_capture_justification": "H4 bounded implementation: the existing turn continues after an explicitly unconfirmed owner-index write with repair-required logging. A missing row hides the conversation from panel listing and causes panel read/feedback/delete to fail closed before accessing managed content. No successful index write is claimed, no authorization bypass or automatic repair is added. This individual exception requires exact-source passing technical evidence.", + "failure_outcome": "contractual-best-effort", + "authorization_and_write_review": "Existing deny/propagation/confirmed-write ordering is preserved; no retry, success fallback or authorization downgrade was added.", + "diagnostic_decision": "Remove dependency exception text/chains from logs; retain static operation, exception class and terminal code location. Also omit raw citation URL for boundary 27.", + "behavior_tests": [ + "tests/test_hosted_continuity_panel_hook.py::OwnerIndexHookTests.test_hook_failure_does_not_fail_the_turn", + "tests/test_hosted_continuity_panel_hook.py::OwnerIndexHookTests.test_failed_index_write_hides_and_denies_panel_operations", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged" + ], + "diagnostic_tests": [ + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_warning_diagnostic_preserves_level_without_error_payload" + ], + "human_review_status": "not required by technical policy; not performed by this AI assessment" + } + ] +} diff --git a/.quality/exceptions.json b/.quality/exceptions.json index 2c3dea1..e9c5984 100644 --- a/.quality/exceptions.json +++ b/.quality/exceptions.json @@ -7,7 +7,9 @@ "symbol": "register_feedback_handlers.handle_feedback", "review": "Initial administrative adoption by @placerda; not independent review: https://github.com/Azure/GPT-RAG/issues/681#issuecomment-5601804634", "handler_fingerprint": "9601a863df38f68150ffe5e5e271bcca00a3aa72b29aef0908922b67e312c832", - "caught_types": ["Exception"], + "caught_types": [ + "Exception" + ], "boundary": "Optional feedback form removal after backend outcome", "reason": "A removal failure must not block notification of the established backend outcome or trigger another write. Retain the form reference for explicit later cleanup; cancellation propagates.", "failure_outcome": "contractual-best-effort", @@ -25,7 +27,9 @@ "symbol": "configure_copilot_bridge_guards.disconnect_socket", "review": "Initial administrative adoption by @placerda; not independent review: https://github.com/Azure/GPT-RAG/issues/681#issuecomment-5601804634", "handler_fingerprint": "2d819a865b36cc04496168f08aea00216a82347f49d65411f9faa4ab1b9a6ec8", - "caught_types": ["Exception"], + "caught_types": [ + "Exception" + ], "boundary": "Secondary Engine.IO close failure during failed disconnect", "reason": "Record ordinary secondary failure without replacing the primary disconnect exception. Cancellation is not suppressed; no claim of successful termination or lifecycle reconciliation.", "failure_outcome": "cleanup-propagation", @@ -42,18 +46,19 @@ "id": "ui-boundary-01", "module_id": "gpt_rag_ui.api.download_routes", "symbol": "register_secure_download_route.download_blob_file", - "review": "Initial administrative adoption by @placerda; not independent review: https://github.com/Azure/GPT-RAG/issues/681#issuecomment-5601804634", - "handler_fingerprint": "b756bff96eda34351f57aba6b1c3fe329e1d500e669db581dcdb139a1e6e0a2d", + "review": "AI technical assessment in .quality/exception-technical-review.json; eligible for source-bound technical validation, not independent approval.", + "handler_fingerprint": "ea27f01138258ee18dc29fc3f0cf19d238a02cfd203f0bd0121e13d05df132cb", "caught_types": [ "Exception" ], "boundary": "Authorized synchronous downloader acquisition via asyncio.to_thread only", "reason": "Stage 2 P2: unexpected downloader acquisition failures become generic HTTP 500 with no-store after grant, principal, ownership and target checks. Conversation resolution occurs before this try and lazy DownloadStream iteration occurs after it; neither is translated by this catch. No resolver/late-stream response guarantee or new boundary design is proposed.", "failure_outcome": "failure-translation", - "diagnostic_path": "logger.exception; generic 500 response with no-store", + "diagnostic_path": "log_boundary_failure: static operation, exception class and code location; no dependency message, chain, locals or raw citation URL; existing outcome unchanged", "evidence_tests": [ "tests/test_download_security.py::DownloadSecurityTests.test_secure_download_route_fails_closed", - "tests/test_download_security.py::DownloadSecurityTests.test_download_acquisition_boundary_excludes_resolver_and_late_iteration" + "tests/test_download_security.py::DownloadSecurityTests.test_download_acquisition_boundary_excludes_resolver_and_late_iteration", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged" ], "state": "active", "review_by_stage": "blocking", @@ -85,18 +90,19 @@ "id": "ui-boundary-03", "module_id": "embed_auth", "symbol": "CopilotSessionStore._notify_invalidated", - "review": "Initial administrative adoption by @placerda; not independent review: https://github.com/Azure/GPT-RAG/issues/681#issuecomment-5601804634", - "handler_fingerprint": "ee84c052306e126a4806e67c8e15feffcf73a63294804d629ae0c1e59d693b62", + "review": "AI technical assessment in .quality/exception-technical-review.json; eligible for source-bound technical validation, not independent approval.", + "handler_fingerprint": "6b8fd6402ce8aefe8bdf5927e3220e2e77c4da5043fb1370ea3382d3920dd481", "caught_types": [ "Exception" ], "boundary": "Per-session invalidation notification callback", "reason": "Session invalidation is already committed; a failed injected socket callback must be logged while remaining invalidations continue. Cancellation is not swallowed.", "failure_outcome": "contractual-best-effort", - "diagnostic_path": "logger.exception; subsequent callback invocation", + "diagnostic_path": "log_boundary_failure: static operation, exception class and code location; no dependency message, chain, locals or raw citation URL; existing outcome unchanged", "evidence_tests": [ "tests/test_boundary_failures.py::BoundaryFailureTests.test_invalidation_callback_failure_does_not_skip_other_sessions", - "tests/test_boundary_failures.py::BoundaryFailureTests.test_cancellation_is_not_converted_to_successful_invalidation" + "tests/test_boundary_failures.py::BoundaryFailureTests.test_cancellation_is_not_converted_to_successful_invalidation", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged" ], "state": "active", "review_by_stage": "blocking", @@ -106,17 +112,18 @@ "id": "ui-boundary-04", "module_id": "embed_security", "symbol": "CopilotSocketRegistry.bind_connection", - "review": "Initial administrative adoption by @placerda; not independent review: https://github.com/Azure/GPT-RAG/issues/681#issuecomment-5601804634", - "handler_fingerprint": "6e6629412e9cc7c57e4a00e23d815331c9645753f8c54b583b2e494c18931a78", + "review": "AI technical assessment in .quality/exception-technical-review.json; eligible for source-bound technical validation, not independent approval.", + "handler_fingerprint": "7c9e97958fc3a394bf5a7ba3186e149e343084ae96608a0d68b1a1c5d231b4f1", "caught_types": [ "Exception" ], "boundary": "Bounded socket replacement teardown", "reason": "Replacement must not steal the old association when an injected teardown callback fails; preserve tracking and reject the replacement for retry.", "failure_outcome": "failure-translation", - "diagnostic_path": "logger.exception; rejected binding", + "diagnostic_path": "log_boundary_failure: static operation, exception class and code location; no dependency message, chain, locals or raw citation URL; existing outcome unchanged", "evidence_tests": [ - "tests/test_embed_security.py::BridgeGuardTests.test_failed_replacement_disconnect_keeps_old_association" + "tests/test_embed_security.py::BridgeGuardTests.test_failed_replacement_disconnect_keeps_old_association", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged" ], "state": "active", "review_by_stage": "blocking", @@ -126,17 +133,18 @@ "id": "ui-boundary-05", "module_id": "embed_security", "symbol": "CopilotSocketRegistry.disconnect_session", - "review": "Initial administrative adoption by @placerda; not independent review: https://github.com/Azure/GPT-RAG/issues/681#issuecomment-5601804634", - "handler_fingerprint": "4c0b76598807ea2c2354b694e6dfa8231207d104845e22dd2234d142d5c83a37", + "review": "AI technical assessment in .quality/exception-technical-review.json; eligible for source-bound technical validation, not independent approval.", + "handler_fingerprint": "f8fafe292eb7bd758684abcabe3abf5cd2f679db0f5175a965592c323363663d", "caught_types": [ "Exception" ], "boundary": "Invalidated session per-socket teardown", "reason": "Deny the binding before teardown, continue independent socket cleanup on arbitrary callback failure and count only actual disconnects; retain failed associations as inactive.", "failure_outcome": "contractual-best-effort", - "diagnostic_path": "logger.exception; inactive retained socket and successful-only count", + "diagnostic_path": "log_boundary_failure: static operation, exception class and code location; no dependency message, chain, locals or raw citation URL; existing outcome unchanged", "evidence_tests": [ - "tests/test_embed_security.py::BridgeGuardTests.test_failed_session_disconnect_retains_denied_binding" + "tests/test_embed_security.py::BridgeGuardTests.test_failed_session_disconnect_retains_denied_binding", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged" ], "state": "active", "review_by_stage": "blocking", @@ -146,17 +154,18 @@ "id": "ui-boundary-06", "module_id": "embed_security", "symbol": "_target_has_copilot_sockets", - "review": "Initial administrative adoption by @placerda; not independent review: https://github.com/Azure/GPT-RAG/issues/681#issuecomment-5601804634", - "handler_fingerprint": "f32bcc076c9df5109e20369857d83491585c57dfcaff7fbfd856f9fbc2462095", + "review": "AI technical assessment in .quality/exception-technical-review.json; eligible for source-bound technical validation, not independent approval.", + "handler_fingerprint": "6647ec551e5e108b83c460853cef4b82694614f4190195f6940f49867534b2c3", "caught_types": [ "Exception" ], "boundary": "Socket.IO recipient enumeration safety check", "reason": "An unknown manager failure cannot prove the room excludes Copilot clients; conservatively retain the Copilot restriction when any protected sockets exist.", "failure_outcome": "failure-translation", - "diagnostic_path": "logger.exception; conservative protected-recipient result", + "diagnostic_path": "log_boundary_failure: static operation, exception class and code location; no dependency message, chain, locals or raw citation URL; existing outcome unchanged", "evidence_tests": [ - "tests/test_embed_security.py::BridgeGuardTests.test_bridge_recipient_lookup_failure_is_conservative" + "tests/test_embed_security.py::BridgeGuardTests.test_bridge_recipient_lookup_failure_is_conservative", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged" ], "state": "active", "review_by_stage": "blocking", @@ -166,17 +175,18 @@ "id": "ui-boundary-07", "module_id": "embed_security", "symbol": "disconnect_copilot_session", - "review": "Initial administrative adoption by @placerda; not independent review: https://github.com/Azure/GPT-RAG/issues/681#issuecomment-5601804634", - "handler_fingerprint": "2a8561cb32d5bc9bbd7d8a97ed77033906a84087036941eb24a435f460d59d61", + "review": "AI technical assessment in .quality/exception-technical-review.json; eligible for source-bound technical validation, not independent approval.", + "handler_fingerprint": "f126f99ec58d631ae2495d9aae0df0046ee8e82df29e25af1f5682965d9ed793", "caught_types": [ "Exception" ], "boundary": "Invalidated session Engine.IO transport cleanup", "reason": "Invalidation already denies the session; one failed transport closure must not abort cleanup of other reserved transports or claim a successful disconnect.", "failure_outcome": "contractual-best-effort", - "diagnostic_path": "logger.exception; preserved invalidation and actual disconnect count", + "diagnostic_path": "log_boundary_failure: static operation, exception class and code location; no dependency message, chain, locals or raw citation URL; existing outcome unchanged", "evidence_tests": [ - "tests/test_embed_security.py::BridgeGuardTests.test_invalidation_transport_failures_are_logged_without_counting_success" + "tests/test_embed_security.py::BridgeGuardTests.test_invalidation_transport_failures_are_logged_without_counting_success", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged" ], "state": "active", "review_by_stage": "blocking", @@ -186,17 +196,18 @@ "id": "ui-boundary-08", "module_id": "embed_security", "symbol": "disconnect_copilot_session", - "review": "Initial administrative adoption by @placerda; not independent review: https://github.com/Azure/GPT-RAG/issues/681#issuecomment-5601804634", - "handler_fingerprint": "767f30df3d2c994058add75878ffceb0b5647093ee1fe4a5d70a9d55c88b135e", + "review": "AI technical assessment in .quality/exception-technical-review.json; eligible for source-bound technical validation, not independent approval.", + "handler_fingerprint": "7d897438c4c05800a7f1a24d48b817d314dc5c441beedab8da458ba1a8d4d069", "caught_types": [ "Exception" ], "boundary": "Legacy per-socket invalidation without registry", "reason": "When no registry exists, close every known socket independently and count only successful callbacks; arbitrary callback failures remain observable.", "failure_outcome": "contractual-best-effort", - "diagnostic_path": "logger.exception; successful-only disconnect count", + "diagnostic_path": "log_boundary_failure: static operation, exception class and code location; no dependency message, chain, locals or raw citation URL; existing outcome unchanged", "evidence_tests": [ - "tests/test_embed_security.py::BridgeGuardTests.test_invalidation_transport_failures_are_logged_without_counting_success" + "tests/test_embed_security.py::BridgeGuardTests.test_invalidation_transport_failures_are_logged_without_counting_success", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged" ], "state": "active", "review_by_stage": "blocking", @@ -206,17 +217,18 @@ "id": "ui-boundary-09", "module_id": "embed_security", "symbol": "_terminate_socket", - "review": "Initial administrative adoption by @placerda; not independent review: https://github.com/Azure/GPT-RAG/issues/681#issuecomment-5601804634", - "handler_fingerprint": "3c54455817d43acb6b53599c65f8068719f50f7cbd87d4a8b7772cd507ef61ce", + "review": "AI technical assessment in .quality/exception-technical-review.json; eligible for source-bound technical validation, not independent approval.", + "handler_fingerprint": "fe54814940b16bf710cba38c94cee0ef7c4e4b146d4676b5a04d9e6b2b30e8bb", "caught_types": [ "Exception" ], "boundary": "Rejected socket physical teardown", "reason": "Do not untrack an invalidated socket while its arbitrary disconnect callback has failed; return false so callers retain denial and retry capacity.", "failure_outcome": "failure-translation", - "diagnostic_path": "logger.exception; false result and retained inactive association", + "diagnostic_path": "log_boundary_failure: static operation, exception class and code location; no dependency message, chain, locals or raw citation URL; existing outcome unchanged", "evidence_tests": [ - "tests/test_embed_security.py::BridgeGuardTests.test_failed_termination_retains_invalidated_association" + "tests/test_embed_security.py::BridgeGuardTests.test_failed_termination_retains_invalidated_association", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged" ], "state": "active", "review_by_stage": "blocking", @@ -226,17 +238,18 @@ "id": "ui-boundary-10", "module_id": "embed_security", "symbol": "_terminate_socket", - "review": "Initial administrative adoption by @placerda; not independent review: https://github.com/Azure/GPT-RAG/issues/681#issuecomment-5601804634", - "handler_fingerprint": "4be83a749d9b20b8afcb438353ab909c8a7a8ff11b952de879ff1acea435e8a8", + "review": "AI technical assessment in .quality/exception-technical-review.json; eligible for source-bound technical validation, not independent approval.", + "handler_fingerprint": "389838963dc7b28cf257f55ae3b102a8e58cfe92dc3f9b7990366d04ef031520", "caught_types": [ "Exception" ], "boundary": "Orphaned Chainlit session deletion after disconnect", "reason": "The socket is already disconnected; a framework session deletion failure must be logged while finally unbinding the dead socket. The session stays marked for clearing.", "failure_outcome": "contractual-best-effort", - "diagnostic_path": "logger.exception; finally unbind_socket", + "diagnostic_path": "log_boundary_failure: static operation, exception class and code location; no dependency message, chain, locals or raw citation URL; existing outcome unchanged", "evidence_tests": [ - "tests/test_embed_security.py::BridgeGuardTests.test_failed_session_delete_still_unbinds_terminated_socket" + "tests/test_embed_security.py::BridgeGuardTests.test_failed_session_delete_still_unbinds_terminated_socket", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged" ], "state": "active", "review_by_stage": "blocking", @@ -268,17 +281,18 @@ "id": "ui-boundary-12", "module_id": "embed_security", "symbol": "configure_copilot_bridge_guards.close_rejected_socket", - "review": "Initial administrative adoption by @placerda; not independent review: https://github.com/Azure/GPT-RAG/issues/681#issuecomment-5601804634", - "handler_fingerprint": "db80c8a03d2058c018fb9d7fc1e0a316c3acb5a507ab8fff57657f35d2f07659", + "review": "AI technical assessment in .quality/exception-technical-review.json; eligible for source-bound technical validation, not independent approval.", + "handler_fingerprint": "79105f21e100ac3ea82664d7af2197631559d32f7a75f2582f305e431c20ca54", "caught_types": [ "Exception" ], "boundary": "Transport cleanup after connection rejection", "reason": "Cleanup failure must not replace or undo the established connection denial; log failed closure and retain the Socket.IO refusal.", "failure_outcome": "contractual-best-effort", - "diagnostic_path": "logger.exception; SocketIOConnectionRefusedError", + "diagnostic_path": "log_boundary_failure: static operation, exception class and code location; no dependency message, chain, locals or raw citation URL; existing outcome unchanged", "evidence_tests": [ - "tests/test_embed_security.py::BridgeGuardTests.test_rejected_socket_remains_denied_if_transport_close_fails" + "tests/test_embed_security.py::BridgeGuardTests.test_rejected_socket_remains_denied_if_transport_close_fails", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged" ], "state": "active", "review_by_stage": "blocking", @@ -308,19 +322,20 @@ "id": "ui-boundary-14", "module_id": "embed_security", "symbol": "configure_copilot_bridge_guards.guarded_engineio_disconnect", - "review": "Initial administrative adoption by @placerda; not independent review: https://github.com/Azure/GPT-RAG/issues/681#issuecomment-5601804634", - "handler_fingerprint": "4ac0d7b3caeac5a1bc46a3e0b61139458a46947fbb78ba13f887e6f57196bab1", + "review": "AI technical assessment in .quality/exception-technical-review.json; eligible for source-bound technical validation, not independent approval.", + "handler_fingerprint": "7ffd476afc58f95422c5c4e904edeb344751e58e6c40c1952ea7b8e514386bf2", "caught_types": [ "Exception" ], "boundary": "Engine.IO original disconnect handler", "reason": "Invalidate bound sockets before the original callback so a still-closing transport cannot dispatch application messages. An ordinary callback failure triggers explicit namespace cleanup; unresolved bindings stay inactive and capacity-counted until successful reconciliation.", "failure_outcome": "contractual-best-effort", - "diagnostic_path": "logger.exception; namespace fallback and Engine.IO reservation release, with unresolved Socket.IO bindings retained", + "diagnostic_path": "log_boundary_failure: static operation, exception class and code location; no dependency message, chain, locals or raw citation URL; existing outcome unchanged", "evidence_tests": [ "tests/test_embed_security.py::BridgeGuardTests.test_real_engineio_original_failure_denies_messages_while_closing", "tests/test_embed_security.py::BridgeGuardTests.test_engineio_cancellation_retains_invalidated_association", - "tests/test_embed_security.py::BridgeGuardTests.test_transport_invalidation_blocks_inflight_binding_not_other_transport" + "tests/test_embed_security.py::BridgeGuardTests.test_transport_invalidation_blocks_inflight_binding_not_other_transport", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged" ], "state": "active", "review_by_stage": "blocking", @@ -330,17 +345,18 @@ "id": "ui-boundary-15", "module_id": "embed_security", "symbol": "configure_copilot_bridge_guards.guarded_engineio_disconnect", - "review": "Initial administrative adoption by @placerda; not independent review: https://github.com/Azure/GPT-RAG/issues/681#issuecomment-5601804634", - "handler_fingerprint": "7ed9c1f24af71facb62bbf72f2bcddb5902b989ffa7b59b4004320b7fe0d218d", + "review": "AI technical assessment in .quality/exception-technical-review.json; eligible for source-bound technical validation, not independent approval.", + "handler_fingerprint": "5634f57850672be0ef6d80d8292a730d7d637480cdaf885b94d50d0729f60c7b", "caught_types": [ "Exception" ], "boundary": "Socket.IO namespace enumeration during failed teardown", "reason": "Stored Engine.IO associations allow invalidation even when namespace enumeration fails. Release the Engine.IO reservation after ordinary failure handling, but retain unresolved Socket.IO bindings and their capacity until successful reconciliation; callback completion is not proof of physical shutdown.", "failure_outcome": "contractual-best-effort", - "diagnostic_path": "logger.exception; release Engine.IO reservation while retaining invalidated, capacity-counted Socket.IO bindings", + "diagnostic_path": "log_boundary_failure: static operation, exception class and code location; no dependency message, chain, locals or raw citation URL; existing outcome unchanged", "evidence_tests": [ - "tests/test_embed_security.py::BridgeGuardTests.test_real_engineio_enumeration_failure_retains_invalidated_binding" + "tests/test_embed_security.py::BridgeGuardTests.test_real_engineio_enumeration_failure_retains_invalidated_binding", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged" ], "state": "active", "review_by_stage": "blocking", @@ -350,17 +366,18 @@ "id": "ui-boundary-16", "module_id": "embed_security", "symbol": "configure_copilot_bridge_guards.guarded_engineio_disconnect", - "review": "Initial administrative adoption by @placerda; not independent review: https://github.com/Azure/GPT-RAG/issues/681#issuecomment-5601804634", - "handler_fingerprint": "1abdf18fb4f6b76ab843ebf18b2ea6b0b951603cc8ed5e1e80bf92760f76f55d", + "review": "AI technical assessment in .quality/exception-technical-review.json; eligible for source-bound technical validation, not independent approval.", + "handler_fingerprint": "19a67130404f9b819c37276d16207bc6afa732f728030e342139624ae2170e11", "caught_types": [ "Exception" ], "boundary": "Per-namespace Socket.IO ID resolution during teardown", "reason": "One manager lookup failure must be logged without stopping independent namespace cleanup or final Engine.IO reservation release. The unresolved Socket.IO association remains invalidated and capacity-counted until successful reconciliation.", "failure_outcome": "contractual-best-effort", - "diagnostic_path": "logger.exception; continue independent namespace cleanup and release Engine.IO reservation, retaining unresolved bindings", + "diagnostic_path": "log_boundary_failure: static operation, exception class and code location; no dependency message, chain, locals or raw citation URL; existing outcome unchanged", "evidence_tests": [ - "tests/test_embed_security.py::BridgeGuardTests.test_real_engineio_lookup_failure_isolates_namespaces_and_recovers" + "tests/test_embed_security.py::BridgeGuardTests.test_real_engineio_lookup_failure_isolates_namespaces_and_recovers", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged" ], "state": "active", "review_by_stage": "blocking", @@ -370,17 +387,18 @@ "id": "ui-boundary-17", "module_id": "embed_security", "symbol": "configure_copilot_bridge_guards.guarded_engineio_disconnect", - "review": "Initial administrative adoption by @placerda; not independent review: https://github.com/Azure/GPT-RAG/issues/681#issuecomment-5601804634", - "handler_fingerprint": "63616d3ef8bcf5d51354e23eb3ca5c5ba36004d9e99e0189f42de771a70db981", + "review": "AI technical assessment in .quality/exception-technical-review.json; eligible for source-bound technical validation, not independent approval.", + "handler_fingerprint": "9b41637957b7fe156c2f44eefe944b17a24c096054de53794df4c8d49b0f1bc0", "caught_types": [ "Exception" ], "boundary": "Per-namespace manager disconnect fallback", "reason": "An ordinary fallback manager failure remains observable without unbinding an unresolved socket. Retain its invalidated association and capacity until successful reconciliation, while continuing independent namespace cleanup and releasing the Engine.IO reservation.", "failure_outcome": "contractual-best-effort", - "diagnostic_path": "logger.exception; retain failed bindings, unbind only after successful manager cleanup, and release Engine.IO reservation", + "diagnostic_path": "log_boundary_failure: static operation, exception class and code location; no dependency message, chain, locals or raw citation URL; existing outcome unchanged", "evidence_tests": [ - "tests/test_embed_security.py::BridgeGuardTests.test_real_engineio_manager_failure_retains_binding_until_recovery" + "tests/test_embed_security.py::BridgeGuardTests.test_real_engineio_manager_failure_retains_binding_until_recovery", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged" ], "state": "active", "review_by_stage": "blocking", @@ -390,17 +408,18 @@ "id": "ui-boundary-18", "module_id": "embed_security", "symbol": "configure_copilot_bridge_guards.guarded_connect", - "review": "Initial administrative adoption by @placerda; not independent review: https://github.com/Azure/GPT-RAG/issues/681#issuecomment-5601804634", - "handler_fingerprint": "6023a5c3b914404c725fb2ccb659e0080e9855a8972b24ea78c54e4e881b33b3", + "review": "AI technical assessment in .quality/exception-technical-review.json; eligible for source-bound technical validation, not independent approval.", + "handler_fingerprint": "ffd0495b5a7fffaeeccb089f1863573b04715fad7cfd05500d8c4537c8ae77e5", "caught_types": [ "Exception" ], "boundary": "Authorized Chainlit connect callback", "reason": "Failure of the framework's session construction must tear down the bound socket and become a connection refusal, never leave an accepted orphan.", "failure_outcome": "failure-translation", - "diagnostic_path": "logger.exception; _terminate_socket and SocketIOConnectionRefusedError", + "diagnostic_path": "log_boundary_failure: static operation, exception class and code location; no dependency message, chain, locals or raw citation URL; existing outcome unchanged", "evidence_tests": [ - "tests/test_embed_security.py::BridgeGuardTests.test_chainlit_connect_failure_disconnects_before_untracking" + "tests/test_embed_security.py::BridgeGuardTests.test_chainlit_connect_failure_disconnects_before_untracking", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged" ], "state": "active", "review_by_stage": "blocking", @@ -450,17 +469,18 @@ "id": "ui-boundary-21", "module_id": "main", "symbol": "_create_chainlit_app._safe_openapi", - "review": "Initial administrative adoption by @placerda; not independent review: https://github.com/Azure/GPT-RAG/issues/681#issuecomment-5601804634", - "handler_fingerprint": "2f189faefdf72b8005e7dab4cda547b0fd93e39b1fa193f67a25cd7497522ea1", + "review": "AI technical assessment in .quality/exception-technical-review.json; eligible for source-bound technical validation, not independent approval.", + "handler_fingerprint": "9bb99cf79178e9fb367752844f21d5405a1a31a436298c333cde4a369057652e", "caught_types": [ "Exception" ], "boundary": "Optional framework OpenAPI generation", "reason": "H3 bounded implementation: failed schema introspection is logged and propagated, never advertised as a valid metadata-only schema. No failed schema is cached; a later request retries and only successful generation is cached. This individual exception proposal remains pending review, not approved.", "failure_outcome": "propagation", - "diagnostic_path": "logger.exception; schema generation raises and the HTTP endpoint returns 500, with later regeneration", + "diagnostic_path": "log_boundary_failure: static operation, exception class and code location; no dependency message, chain, locals or raw citation URL; existing outcome unchanged", "evidence_tests": [ - "tests/test_installed_package.py::InstalledPackageTests.test_installed_startup_preserves_documented_failure_boundaries" + "tests/test_installed_package.py::InstalledPackageTests.test_installed_startup_preserves_documented_failure_boundaries", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged" ], "state": "active", "review_by_stage": "blocking", @@ -470,17 +490,18 @@ "id": "ui-boundary-22", "module_id": "app", "symbol": "get_auth_info", - "review": "Initial administrative adoption by @placerda; not independent review: https://github.com/Azure/GPT-RAG/issues/681#issuecomment-5601804634", - "handler_fingerprint": "340bc8608e8c309f545703ffec7de3d0f8593ef209c5ec5fc2926bc4ef62611a", + "review": "AI technical assessment in .quality/exception-technical-review.json; eligible for source-bound technical validation, not independent approval.", + "handler_fingerprint": "bf0ec6030fe1c98ded509c27e73724060f97261d38264fb8982fc8b3ba2a1ee7", "caught_types": [ "Exception" ], "boundary": "Chainlit OAuth refresh boundary", "reason": "An unexpected refresh callback failure must clear the local user and return session_expired without an access token; it cannot preserve authenticated success.", "failure_outcome": "failure-translation", - "diagnostic_path": "logger.warning; cleared user session and explicit auth_error", + "diagnostic_path": "log_boundary_failure: static operation, exception class and code location; no dependency message, chain, locals or raw citation URL; existing outcome unchanged", "evidence_tests": [ - "tests/test_boundary_failures.py::BoundaryFailureTests.test_oauth_refresh_failure_clears_session_and_denies_request" + "tests/test_boundary_failures.py::BoundaryFailureTests.test_oauth_refresh_failure_clears_session_and_denies_request", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged" ], "state": "active", "review_by_stage": "blocking", @@ -490,19 +511,20 @@ "id": "ui-boundary-23", "module_id": "app", "symbol": "handle_message", - "review": "Initial administrative adoption by @placerda; not independent review: https://github.com/Azure/GPT-RAG/issues/681#issuecomment-5601804634", - "handler_fingerprint": "b1b0561a84d3188d82a2b8640cfc758f118e0a1886cc95bc1419bd3406abd4d2", + "review": "AI technical assessment in .quality/exception-technical-review.json; eligible for source-bound technical validation, not independent approval.", + "handler_fingerprint": "5c98bae1918793c0ad9a242c52a7127cafe99b6fc4d0091f823482a054d9ea7d", "caught_types": [ "Exception" ], "boundary": "Uploaded-file ingestion interaction", "reason": "H6 bounded implementation: false ingestion results and exceptions stop the accompanying question before backend invocation and finalize a reference-bearing notice requesting reattachment and resubmission. A failed attempt that allocated a new conversation ID restores the prior session value; retries ingest every reattached file under a fresh ID before a turn, without changing existing conversation ownership checks. uploaded_docs changes only after confirmed batch ingestion; failures preserve previous confirmed names and never claim Files received. The boolean client contract does not expose per-file partial success, so no partial batch is claimed confirmed. This individual exception proposal remains pending review, not approved.", "failure_outcome": "failure-translation", - "diagnostic_path": "logger.exception for raised failures or logger.warning for false results; finalized ingestion failure with question reference and retry guidance, no question continuation", + "diagnostic_path": "log_boundary_failure: static operation, exception class and code location; no dependency message, chain, locals or raw citation URL; existing outcome unchanged", "evidence_tests": [ "tests/test_boundary_failures.py::BoundaryFailureTests.test_ingestion_failure_does_not_claim_processed_success", "tests/test_boundary_failures.py::BoundaryFailureTests.test_upload_bookkeeping_requires_confirmation_before_question_continuation", - "tests/test_boundary_failures.py::BoundaryFailureTests.test_failed_upload_retry_ingests_all_files_before_turn" + "tests/test_boundary_failures.py::BoundaryFailureTests.test_failed_upload_retry_ingests_all_files_before_turn", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged" ], "state": "active", "review_by_stage": "blocking", @@ -512,17 +534,18 @@ "id": "ui-boundary-24", "module_id": "app", "symbol": "handle_message", - "review": "Initial administrative adoption by @placerda; not independent review: https://github.com/Azure/GPT-RAG/issues/681#issuecomment-5601804634", - "handler_fingerprint": "745f35b8acf978f9806573704dd103ef503cecb3dcbd1b84eb5df58488d9ebd2", + "review": "AI technical assessment in .quality/exception-technical-review.json; eligible for source-bound technical validation, not independent approval.", + "handler_fingerprint": "ad6979e446f0019651c4c18b10b66abf39ce0c75ed3b030df95cba4c4a9f10e0", "caught_types": [ "Exception" ], "boundary": "Hosted continuity streaming interaction", "reason": "The outer Chainlit event boundary translates arbitrary coordinator/stream failures into a reference-bearing technical error and always closes the generator.", "failure_outcome": "failure-translation", - "diagnostic_path": "logger.exception; user-visible technical error; generator.aclose in finally", + "diagnostic_path": "log_boundary_failure: static operation, exception class and code location; no dependency message, chain, locals or raw citation URL; existing outcome unchanged", "evidence_tests": [ - "tests/test_boundary_failures.py::BoundaryFailureTests.test_chat_failure_boundaries_report_errors_and_close_generators" + "tests/test_boundary_failures.py::BoundaryFailureTests.test_chat_failure_boundaries_report_errors_and_close_generators", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged" ], "state": "active", "review_by_stage": "blocking", @@ -532,17 +555,18 @@ "id": "ui-boundary-25", "module_id": "app", "symbol": "handle_message", - "review": "Initial administrative adoption by @placerda; not independent review: https://github.com/Azure/GPT-RAG/issues/681#issuecomment-5601804634", - "handler_fingerprint": "1c63d8d3596fd52c1ca0c8f8ef8e81cce533d248265762e77b05fed6b31d3a8a", + "review": "AI technical assessment in .quality/exception-technical-review.json; eligible for source-bound technical validation, not independent approval.", + "handler_fingerprint": "98757639cc367aacceacedee9b66ed04260a44defd022087f363614f1891a107", "caught_types": [ "Exception" ], "boundary": "Direct hosted-agent streaming interaction", "reason": "Keep the existing user-visible error and generator cleanup if the hosted stream fails outside its narrower authentication/cancellation mappings.", "failure_outcome": "failure-translation", - "diagnostic_path": "logger.exception; user-visible technical error; generator.aclose in finally", + "diagnostic_path": "log_boundary_failure: static operation, exception class and code location; no dependency message, chain, locals or raw citation URL; existing outcome unchanged", "evidence_tests": [ - "tests/test_boundary_failures.py::BoundaryFailureTests.test_chat_failure_boundaries_report_errors_and_close_generators" + "tests/test_boundary_failures.py::BoundaryFailureTests.test_chat_failure_boundaries_report_errors_and_close_generators", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged" ], "state": "active", "review_by_stage": "blocking", @@ -552,17 +576,18 @@ "id": "ui-boundary-26", "module_id": "app", "symbol": "handle_message", - "review": "Initial administrative adoption by @placerda; not independent review: https://github.com/Azure/GPT-RAG/issues/681#issuecomment-5601804634", - "handler_fingerprint": "207aa65b444e45b6567319918d07ecdeebb01080c281dab60bd57326131f6b63", + "review": "AI technical assessment in .quality/exception-technical-review.json; eligible for source-bound technical validation, not independent approval.", + "handler_fingerprint": "b197d32b6d5d9ae50f4040e7a9bee6a654c52ca382ac97c930c6ec6763f80a01", "caught_types": [ "Exception" ], "boundary": "Classic orchestrator streaming interaction", "reason": "The Chainlit event must display a reference-bearing technical error on arbitrary orchestrator stream failure and close the generator rather than terminate silently.", "failure_outcome": "failure-translation", - "diagnostic_path": "logger.exception; user-visible technical error; generator.aclose in finally", + "diagnostic_path": "log_boundary_failure: static operation, exception class and code location; no dependency message, chain, locals or raw citation URL; existing outcome unchanged", "evidence_tests": [ - "tests/test_boundary_failures.py::BoundaryFailureTests.test_chat_failure_boundaries_report_errors_and_close_generators" + "tests/test_boundary_failures.py::BoundaryFailureTests.test_chat_failure_boundaries_report_errors_and_close_generators", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged" ], "state": "active", "review_by_stage": "blocking", @@ -572,17 +597,18 @@ "id": "ui-boundary-27", "module_id": "gpt_rag_ui.services.citations", "symbol": "_resolve_legacy_reference_href", - "review": "Initial administrative adoption by @placerda; not independent review: https://github.com/Azure/GPT-RAG/issues/681#issuecomment-5601804634", - "handler_fingerprint": "03cb111ed45f097249afcbd161767a555a30e0614aee2a3a906bdb6120dbc93a", + "review": "AI technical assessment in .quality/exception-technical-review.json; eligible for source-bound technical validation, not independent approval.", + "handler_fingerprint": "d31971852bf36742a19710bbdeaf95cd888d88c3e28243348a31e3c57053dd70", "caught_types": [ "Exception" ], "boundary": "Optional legacy citation link rendering", "reason": "A failed optional signing/link callback omits that citation with a warning instead of failing the already-generated answer or emitting a broken link.", "failure_outcome": "contractual-best-effort", - "diagnostic_path": "logger.warning; omitted citation", + "diagnostic_path": "log_boundary_failure: static operation, exception class and code location; no dependency message, chain, locals or raw citation URL; existing outcome unchanged", "evidence_tests": [ - "tests/test_boundary_failures.py::BoundaryFailureTests.test_optional_legacy_citation_failure_is_logged_and_omitted" + "tests/test_boundary_failures.py::BoundaryFailureTests.test_optional_legacy_citation_failure_is_logged_and_omitted", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged" ], "state": "active", "review_by_stage": "blocking", @@ -592,18 +618,19 @@ "id": "ui-boundary-28", "module_id": "hosted_continuity", "symbol": "HostedContinuityCoordinator.run_turn", - "review": "Initial administrative adoption by @placerda; not independent review: https://github.com/Azure/GPT-RAG/issues/681#issuecomment-5601804634", - "handler_fingerprint": "d15439dcd8f2a94ee9c6aecc64dbd49fb1d4be3cd8c174047105052f2687fbfc", + "review": "AI technical assessment in .quality/exception-technical-review.json; eligible for source-bound technical validation, not independent approval.", + "handler_fingerprint": "d1e58c9a181daa2e14557aa350d733791d8ea47639193e748d216721153dffaa", "caught_types": [ "Exception" ], "boundary": "Optional post-create panel owner-index hook", - "reason": "H4 bounded implementation: the existing turn continues after an explicitly unconfirmed owner-index write with repair-required logging. A missing row hides the conversation from panel listing and causes panel read/feedback/delete to fail closed before accessing managed content. No successful index write is claimed, no authorization bypass or automatic repair is added, and this individual exception proposal remains pending review, not approved.", + "reason": "H4 bounded implementation: the existing turn continues after an explicitly unconfirmed owner-index write with repair-required logging. A missing row hides the conversation from panel listing and causes panel read/feedback/delete to fail closed before accessing managed content. No successful index write is claimed, no authorization bypass or automatic repair is added. This individual exception requires exact-source passing technical evidence.", "failure_outcome": "contractual-best-effort", - "diagnostic_path": "logger.exception identifies panel list and read/feedback/delete unavailability and explicit owner-index repair need; main streaming turn continues", + "diagnostic_path": "log_boundary_failure: static operation, exception class and code location; no dependency message, chain, locals or raw citation URL; existing outcome unchanged", "evidence_tests": [ "tests/test_hosted_continuity_panel_hook.py::OwnerIndexHookTests.test_hook_failure_does_not_fail_the_turn", - "tests/test_hosted_continuity_panel_hook.py::OwnerIndexHookTests.test_failed_index_write_hides_and_denies_panel_operations" + "tests/test_hosted_continuity_panel_hook.py::OwnerIndexHookTests.test_failed_index_write_hides_and_denies_panel_operations", + "tests/test_boundary_diagnostics.py::BoundaryDiagnosticsTests.test_dependency_payload_chain_and_locals_are_not_logged" ], "state": "active", "review_by_stage": "blocking", diff --git a/.quality/handler-inventory.json b/.quality/handler-inventory.json index 4c19f83..a22dbda 100644 --- a/.quality/handler-inventory.json +++ b/.quality/handler-inventory.json @@ -1,13 +1,33 @@ { "schema_version": 1, - "source_revision": "1ef7539d7a04e388afe850b09e53f5fb12be3a76", + "source_revision": "ec1a12912ae0771231274da9970684ffd92ac851", "state": "unapproved", "handlers": [ + { + "module_id": "gpt_rag_ui.api.feedback", + "symbol": "register_feedback_handlers.handle_feedback", + "line": 144, + "handler_fingerprint": "9601a863df38f68150ffe5e5e271bcca00a3aa72b29aef0908922b67e312c832", + "caught_types": [ + "Exception" + ], + "unsupported": false + }, + { + "module_id": "embed_security", + "symbol": "configure_copilot_bridge_guards.disconnect_socket", + "line": 1267, + "handler_fingerprint": "2d819a865b36cc04496168f08aea00216a82347f49d65411f9faa4ab1b9a6ec8", + "caught_types": [ + "Exception" + ], + "unsupported": false + }, { "module_id": "gpt_rag_ui.api.download_routes", "symbol": "register_secure_download_route.download_blob_file", - "line": 104, - "handler_fingerprint": "b756bff96eda34351f57aba6b1c3fe329e1d500e669db581dcdb139a1e6e0a2d", + "line": 105, + "handler_fingerprint": "ea27f01138258ee18dc29fc3f0cf19d238a02cfd203f0bd0121e13d05df132cb", "caught_types": [ "Exception" ], @@ -16,8 +36,8 @@ { "module_id": "gpt_rag_ui.api.feedback", "symbol": "register_feedback_handlers.handle_feedback", - "line": 136, - "handler_fingerprint": "960a5e976e25e1e71e579ca4d06749b5e3ec868e375ff47353f60884ddf28fd4", + "line": 127, + "handler_fingerprint": "5754b34c00eb31d439a9770d6ee1ef8e2bf6c44c77193ef80f64c00d0e20d26a", "caught_types": [ "Exception" ], @@ -26,8 +46,8 @@ { "module_id": "embed_auth", "symbol": "CopilotSessionStore._notify_invalidated", - "line": 174, - "handler_fingerprint": "ee84c052306e126a4806e67c8e15feffcf73a63294804d629ae0c1e59d693b62", + "line": 175, + "handler_fingerprint": "6b8fd6402ce8aefe8bdf5927e3220e2e77c4da5043fb1370ea3382d3920dd481", "caught_types": [ "Exception" ], @@ -36,8 +56,8 @@ { "module_id": "embed_security", "symbol": "CopilotSocketRegistry.bind_connection", - "line": 298, - "handler_fingerprint": "6e6629412e9cc7c57e4a00e23d815331c9645753f8c54b583b2e494c18931a78", + "line": 308, + "handler_fingerprint": "7c9e97958fc3a394bf5a7ba3186e149e343084ae96608a0d68b1a1c5d231b4f1", "caught_types": [ "Exception" ], @@ -46,8 +66,8 @@ { "module_id": "embed_security", "symbol": "CopilotSocketRegistry.disconnect_session", - "line": 479, - "handler_fingerprint": "4c0b76598807ea2c2354b694e6dfa8231207d104845e22dd2234d142d5c83a37", + "line": 506, + "handler_fingerprint": "f8fafe292eb7bd758684abcabe3abf5cd2f679db0f5175a965592c323363663d", "caught_types": [ "Exception" ], @@ -56,8 +76,8 @@ { "module_id": "embed_security", "symbol": "_target_has_copilot_sockets", - "line": 984, - "handler_fingerprint": "f32bcc076c9df5109e20369857d83491585c57dfcaff7fbfd856f9fbc2462095", + "line": 1011, + "handler_fingerprint": "6647ec551e5e108b83c460853cef4b82694614f4190195f6940f49867534b2c3", "caught_types": [ "Exception" ], @@ -66,8 +86,8 @@ { "module_id": "embed_security", "symbol": "disconnect_copilot_session", - "line": 1133, - "handler_fingerprint": "2a8561cb32d5bc9bbd7d8a97ed77033906a84087036941eb24a435f460d59d61", + "line": 1160, + "handler_fingerprint": "f126f99ec58d631ae2495d9aae0df0046ee8e82df29e25af1f5682965d9ed793", "caught_types": [ "Exception" ], @@ -76,8 +96,8 @@ { "module_id": "embed_security", "symbol": "disconnect_copilot_session", - "line": 1144, - "handler_fingerprint": "767f30df3d2c994058add75878ffceb0b5647093ee1fe4a5d70a9d55c88b135e", + "line": 1171, + "handler_fingerprint": "7d897438c4c05800a7f1a24d48b817d314dc5c441beedab8da458ba1a8d4d069", "caught_types": [ "Exception" ], @@ -86,8 +106,8 @@ { "module_id": "embed_security", "symbol": "_terminate_socket", - "line": 1170, - "handler_fingerprint": "3c54455817d43acb6b53599c65f8068719f50f7cbd87d4a8b7772cd507ef61ce", + "line": 1197, + "handler_fingerprint": "fe54814940b16bf710cba38c94cee0ef7c4e4b146d4676b5a04d9e6b2b30e8bb", "caught_types": [ "Exception" ], @@ -96,8 +116,8 @@ { "module_id": "embed_security", "symbol": "_terminate_socket", - "line": 1180, - "handler_fingerprint": "4be83a749d9b20b8afcb438353ab909c8a7a8ff11b952de879ff1acea435e8a8", + "line": 1207, + "handler_fingerprint": "389838963dc7b28cf257f55ae3b102a8e58cfe92dc3f9b7990366d04ef031520", "caught_types": [ "Exception" ], @@ -106,8 +126,8 @@ { "module_id": "embed_security", "symbol": "configure_copilot_bridge_guards.disconnect_socket", - "line": 1237, - "handler_fingerprint": "f33d760a3bd5d791b6b43556292ecd9a6982463b182f4fd59381af6fb6580161", + "line": 1264, + "handler_fingerprint": "3e7d5533c6158c88d52cd614b75ca662d6c061055693f4aaabcfe710485db7c8", "caught_types": [ "Exception" ], @@ -116,8 +136,8 @@ { "module_id": "embed_security", "symbol": "configure_copilot_bridge_guards.close_rejected_socket", - "line": 1267, - "handler_fingerprint": "db80c8a03d2058c018fb9d7fc1e0a316c3acb5a507ab8fff57657f35d2f07659", + "line": 1298, + "handler_fingerprint": "79105f21e100ac3ea82664d7af2197631559d32f7a75f2582f305e431c20ca54", "caught_types": [ "Exception" ], @@ -126,7 +146,7 @@ { "module_id": "embed_security", "symbol": "configure_copilot_bridge_guards.guarded_engineio_connect", - "line": 1308, + "line": 1339, "handler_fingerprint": "6f3cb7b0bb2d3b8fe7a19bc2559160c28ba9aa1887bf38fad362232b30592595", "caught_types": [ "BaseException" @@ -136,8 +156,8 @@ { "module_id": "embed_security", "symbol": "configure_copilot_bridge_guards.guarded_engineio_disconnect", - "line": 1342, - "handler_fingerprint": "46c8675c61b6caf2e6e8d12cb8589d6bddac1f01a44764cbdfbc2921add13c05", + "line": 1377, + "handler_fingerprint": "7ffd476afc58f95422c5c4e904edeb344751e58e6c40c1952ea7b8e514386bf2", "caught_types": [ "Exception" ], @@ -146,8 +166,8 @@ { "module_id": "embed_security", "symbol": "configure_copilot_bridge_guards.guarded_engineio_disconnect", - "line": 1350, - "handler_fingerprint": "7ed9c1f24af71facb62bbf72f2bcddb5902b989ffa7b59b4004320b7fe0d218d", + "line": 1385, + "handler_fingerprint": "5634f57850672be0ef6d80d8292a730d7d637480cdaf885b94d50d0729f60c7b", "caught_types": [ "Exception" ], @@ -156,8 +176,8 @@ { "module_id": "embed_security", "symbol": "configure_copilot_bridge_guards.guarded_engineio_disconnect", - "line": 1362, - "handler_fingerprint": "1abdf18fb4f6b76ab843ebf18b2ea6b0b951603cc8ed5e1e80bf92760f76f55d", + "line": 1397, + "handler_fingerprint": "19a67130404f9b819c37276d16207bc6afa732f728030e342139624ae2170e11", "caught_types": [ "Exception" ], @@ -166,8 +186,8 @@ { "module_id": "embed_security", "symbol": "configure_copilot_bridge_guards.guarded_engineio_disconnect", - "line": 1376, - "handler_fingerprint": "ceb8bbe7ef1967073771e38ea5f53368cee4e33defd71f090a3f8688d7ebf9e6", + "line": 1411, + "handler_fingerprint": "9b41637957b7fe156c2f44eefe944b17a24c096054de53794df4c8d49b0f1bc0", "caught_types": [ "Exception" ], @@ -176,8 +196,8 @@ { "module_id": "embed_security", "symbol": "configure_copilot_bridge_guards.guarded_connect", - "line": 1639, - "handler_fingerprint": "6023a5c3b914404c725fb2ccb659e0080e9855a8972b24ea78c54e4e881b33b3", + "line": 1675, + "handler_fingerprint": "ffd0495b5a7fffaeeccb089f1863573b04715fad7cfd05500d8c4537c8ae77e5", "caught_types": [ "Exception" ], @@ -186,7 +206,7 @@ { "module_id": "embed_security", "symbol": "configure_copilot_bridge_guards.guarded_disconnect", - "line": 1711, + "line": 1747, "handler_fingerprint": "7ee3c2cc97faa8c215207137892fee1838625a03721792843ddfb6a41e2857a9", "caught_types": [ "Exception" @@ -196,8 +216,8 @@ { "module_id": "main", "symbol": "_create_chainlit_app.handle_file_download", - "line": 819, - "handler_fingerprint": "3c9d182751070ab794e358cbda085758a40232508aa6834d6510b420075e37df", + "line": 828, + "handler_fingerprint": "f12b32d3233cf4a947884face7bdc8a86851c3f502456545ad3271c0f315e487", "caught_types": [ "Exception" ], @@ -206,8 +226,8 @@ { "module_id": "main", "symbol": "_create_chainlit_app._safe_openapi", - "line": 914, - "handler_fingerprint": "362e2634ddc54a64a9ccb55ce7f04722bd7d0d1e1d18f53b6bcae77484367205", + "line": 916, + "handler_fingerprint": "9bb99cf79178e9fb367752844f21d5405a1a31a436298c333cde4a369057652e", "caught_types": [ "Exception" ], @@ -216,8 +236,8 @@ { "module_id": "app", "symbol": "get_auth_info", - "line": 299, - "handler_fingerprint": "340bc8608e8c309f545703ffec7de3d0f8593ef209c5ec5fc2926bc4ef62611a", + "line": 300, + "handler_fingerprint": "bf0ec6030fe1c98ded509c27e73724060f97261d38264fb8982fc8b3ba2a1ee7", "caught_types": [ "Exception" ], @@ -226,8 +246,8 @@ { "module_id": "app", "symbol": "handle_message", - "line": 619, - "handler_fingerprint": "28432097d5e316c53871f35a15bae778d07207b7d5bfe4456053b36d6c2d0d4a", + "line": 624, + "handler_fingerprint": "5c98bae1918793c0ad9a242c52a7127cafe99b6fc4d0091f823482a054d9ea7d", "caught_types": [ "Exception" ], @@ -236,8 +256,8 @@ { "module_id": "app", "symbol": "handle_message", - "line": 893, - "handler_fingerprint": "745f35b8acf978f9806573704dd103ef503cecb3dcbd1b84eb5df58488d9ebd2", + "line": 914, + "handler_fingerprint": "ad6979e446f0019651c4c18b10b66abf39ce0c75ed3b030df95cba4c4a9f10e0", "caught_types": [ "Exception" ], @@ -246,8 +266,8 @@ { "module_id": "app", "symbol": "handle_message", - "line": 1043, - "handler_fingerprint": "1c63d8d3596fd52c1ca0c8f8ef8e81cce533d248265762e77b05fed6b31d3a8a", + "line": 1064, + "handler_fingerprint": "98757639cc367aacceacedee9b66ed04260a44defd022087f363614f1891a107", "caught_types": [ "Exception" ], @@ -256,8 +276,8 @@ { "module_id": "app", "symbol": "handle_message", - "line": 1220, - "handler_fingerprint": "207aa65b444e45b6567319918d07ecdeebb01080c281dab60bd57326131f6b63", + "line": 1241, + "handler_fingerprint": "b197d32b6d5d9ae50f4040e7a9bee6a654c52ca382ac97c930c6ec6763f80a01", "caught_types": [ "Exception" ], @@ -266,8 +286,8 @@ { "module_id": "gpt_rag_ui.services.citations", "symbol": "_resolve_legacy_reference_href", - "line": 157, - "handler_fingerprint": "03cb111ed45f097249afcbd161767a555a30e0614aee2a3a906bdb6120dbc93a", + "line": 158, + "handler_fingerprint": "d31971852bf36742a19710bbdeaf95cd888d88c3e28243348a31e3c57053dd70", "caught_types": [ "Exception" ], @@ -276,8 +296,8 @@ { "module_id": "hosted_continuity", "symbol": "HostedContinuityCoordinator.run_turn", - "line": 354, - "handler_fingerprint": "dc6e603ecd7756c1eb6ae4ec785ef18eff71de07bf79d56f150443c1b4157363", + "line": 356, + "handler_fingerprint": "d1e58c9a181daa2e14557aa350d733791d8ea47639193e748d216721153dffaa", "caught_types": [ "Exception" ], diff --git a/.quality/policy.json b/.quality/policy.json index a2d9acd..e46a853 100644 --- a/.quality/policy.json +++ b/.quality/policy.json @@ -1459,6 +1459,19 @@ "allowed_importers": [], "typing_status": "blocking" }, + { + "id": "gpt_rag_ui.telemetry.boundary_diagnostics", + "path": "src/gpt_rag_ui/telemetry/boundary_diagnostics.py", + "import_name": "gpt_rag_ui.telemetry.boundary_diagnostics", + "area": "telemetry", + "public_exports": ["log_boundary_failure"], + "legacy_aliases": [], + "source_revision": "ec1a12912ae0771231274da9970684ffd92ac851", + "responsibilities": "Payload-free failure diagnostics for retained application boundaries", + "private_modules": [], + "allowed_importers": [], + "typing_status": "blocking" + }, { "id": "telemetry", "path": "src/gpt_rag_ui/telemetry/monitoring.py", diff --git a/.quality/typing-scope.json b/.quality/typing-scope.json index 7245b91..26dd659 100644 --- a/.quality/typing-scope.json +++ b/.quality/typing-scope.json @@ -19,6 +19,7 @@ "gpt_rag_ui.services", "gpt_rag_ui.services.citations", "gpt_rag_ui.telemetry", + "gpt_rag_ui.telemetry.boundary_diagnostics", "gpt_rag_ui.util", "hosted_continuity_config", "legacy:app", diff --git a/AGENTS.md b/AGENTS.md index 9e31312..ebb76d6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -151,8 +151,11 @@ orchestrator. exact-source receipt requirements and protected aggregation. - The quality evaluator uses a separate environment built from protected requirements/tool pins and does not install candidate application code. - Keep isolated static-tool execution intact. Candidate exception proposals - are review requests, never active approvals or blanket inherited waivers. + Keep isolated static-tool execution intact. Candidate exception records are + technically validated with exact-source passing evidence, expiry and stage; + `active` is eligibility, not human approval. Independent review is not + mandatory and registry edits alone do not block. Checker/tool/workflow + integrity remains protected; no blanket inherited waivers are permitted. - `container-tests` builds an ephemeral Linux image and runs the explicit offline `tests/container_smoke.py` helper with read-only mounted tests. `quality-gate` requires its real result; no image is published or deployed. diff --git a/CHANGELOG.md b/CHANGELOG.md index 082ac25..482e603 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,23 @@ # Changelog -## [Unreleased] +## [v3.2.2] - 2026-10-14 + +### Fixed + +- **Hosted invocation compatibility.** Include the required `api-version=v1` + query parameter and accept the current Responses `conversation.id` envelope + alongside legacy `conversation_id`, rejecting malformed or conflicting IDs. +- **Backend-specific history registration.** Hosted chat no longer registers + the classic Dapr-backed data layer; OAuth, feedback, session context and + classic authenticated history remain unchanged. + +### Changed + +- Technically validate candidate exception registry updates without mandatory + independent approval or registry-only policy blocking. Preserve expiry, + source-bound passing behavior evidence, protected evaluator/tool/workflow + integrity and fail-closed CI aggregation. Protected-checker adoption remains + required; this does not change runtime behavior or repository review settings. ## [v3.2.1] - 2026-10-08 diff --git a/VERSION b/VERSION index 0444f32..acf9bf0 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.2.1 \ No newline at end of file +3.2.2 \ No newline at end of file diff --git a/docs/adr/README.md b/docs/adr/README.md index 65dbe1d..46ec693 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -13,3 +13,10 @@ measurable compliance and a review trigger. These records cover this UI component. Platform or orchestrator decisions belong in the appropriate `Azure/agent-landing-zone` repository and should be linked rather than duplicated. + +The exception-registry technical validation decision is platform ADR-0020, +`docs/adr/ADR-0020-technical-exception-validation.md` in +`Azure/agent-landing-zone`. It replaces mandatory independent approval and +registry-only policy blocking, while retaining protected evaluator integrity +and exact-source passing evidence. See `../python-development.md` for the +implementation and pending protected-policy adoption. diff --git a/docs/python-development.md b/docs/python-development.md index 7ebf525..f4ee840 100644 --- a/docs/python-development.md +++ b/docs/python-development.md @@ -192,10 +192,15 @@ can be consumed. The separate `.github/scripts/run-unittest.py` uses standard `unittest` discovery and records each exact test method, source fingerprints and outcome. Referenced skipped/expected-failure tests are not passing evidence; failed subtests, unexpected successes, stale source or a failed suite cannot -approve a boundary. Exception records must also be active in the protected base, +validate a boundary. Candidate exception records must be active (eligible for +technical validation, not independently approved), match the exact source/caught types, have an unexpired `expires_on` date and not have passed their `review_by_stage` (`bootstrap`, `blocking`, `strict`). Contractual best-effort outcomes remain representable; logging is not approval. +Registry edits alone do not cause a policy-change finding. Independent human +review and approval strings are not prerequisites; `review` remains nonempty +provenance for schema compatibility. Proposed/retired records cannot be consumed. +Only technically validated exact sites exempt BLE001; other lint findings remain. For a local, same-run receipt in PowerShell: @@ -246,30 +251,53 @@ without being described as full-repository typing coverage. Ruff selects F821, E722, BLE001, PGH003, PGH004 and RUF100. The supplementary AST handler inventory includes logged and re-raised broad handlers that BLE001 exempts. `.quality/handler-inventory.json` records the remaining sites. -`exceptions.json` contains 30 individually authorized initial-adoption boundaries: +`exceptions.json` contains 30 individually documented technical boundaries: each identifies its exact operation/catch fingerprint, rationale, diagnostic path, observable outcome and executed failure-test selector. Records retain their -2026-10-06 expiry and blocking review stage. No blanket inherited +2027-01-06 expiry and blocking review stage. No blanket inherited handler waiver is granted. Narrowing removes 35 of the original 63 broad sites; the remaining 28 plus two companion cleanup sites preserve application-level translation, cleanup/propagation or contractual best-effort behavior. Logging alone does not approve them. +The technical review in `.quality/exception-technical-review.json` records a +decision for every initial-adoption boundary. It is an AI technical assessment, +not independent human approval. Twenty-three diagnostic paths needed correction: +dependency exception text, exception chains and raw citation URLs must not enter +their logs. `telemetry.boundary_diagnostics.log_boundary_failure` preserves a +static operation description, exception class and terminal code location without +exception text, chained messages, source lines or local variables. The seven +unchanged boundaries retain their existing propagation/static-diagnostic behavior. +Changed handler fingerprints are now `active`, meaning eligible for technical +validation under the revised policy, not independently approved. Each must +match current source and passing bound evidence; no approval is inherited. +The new evaluator was adopted into the protected `develop` base by PR #144 +(merged `ec18a73`), so the protected checker now applies it to subsequent +PRs. This assessment +does not extend expiry, reduce typing coverage, suppress checks or authorize +production deployment. + The [explicit authorization by repository administrator @placerda](https://github.com/Azure/agent-landing-zone/issues/681#issuecomment-5601804634) accepts these exact existing records and the policy for initial administrative adoption under Q5. It is not an independent GitHub review, a claim that bootstrap -checks pass, or authorization for production deployment. Only protected active, +checks pass, or authorization for production deployment. Only exact active, unexpired records with same-source passing evidence can authorize an exception. -The initial PR still reports a missing protected policy as `bootstrap-review`; -candidate activation cannot approve itself. A real reference PR against the -adopted `develop` base and coordinator-owned required-check controls remain -necessary before enforcement can be claimed. +The initial PR (#144) reported a missing protected policy as `bootstrap-review` +because its base predated adoption; candidate data still cannot change the +evaluator's rules. This reference PR runs against the adopted `develop` base, +exercising coordinator-owned required-check controls. The recorded exception +decisions are governed by their technical evidence — exact fingerprints, +same-source bound tests and unexpired stage validation — not by a separate +independent human review step; this PR demonstrates base adoption with that +technical governance already in force. Installed acceptance covers real auth order, upload writes/cleanup and standalone download/OpenAPI failures outside the checkout. Linux acceptance is supplied by the actual `container-tests` job for the PR head, not inferred from local Windows results. The quality fixtures include disposable Git repositories and real unittest/aggregate subprocesses for protected-policy, receipt and false-green mutations; they do not substitute for controlled -required-check acceptance PRs. Subsequent policy changes still require maintainer review. +required-check acceptance PRs. Subsequent checker/tool/workflow and other protected +policy changes still require authorized protected-policy adoption; ordinary +exception registry edits do not. The bootstrap PR cannot approve itself by editing JSON: a base without policy reports `bootstrap-review`. Ordinary PR checks execute the protected base's checker and tool config as their minimum, and separately report candidate @@ -278,13 +306,26 @@ matrix/unit-test/container results and all five fresh bound quality reports plus the unittest receipt. An administrator must separately require `quality-gate` and `unit-tests` on -development/release-target branches, require code-owner approval on the latest -head, dismiss stale approvals and restrict bypass. `@placerda` was confirmed +development/release-target branches and restrict bypass. Q5 no longer mandates +independent/latest-head human approval. Existing repository review settings, if +any, are separate controls; this change does not modify them. `@placerda` was confirmed as a repository administrator before adding CODEOWNERS. No settings were applied. Workflow YAML, approval strings and a green local command cannot prove required merge enforcement; bootstrap administration and controlled negative PR evidence remain separate acceptance activities. +The decision and rollback policy are recorded in platform +`docs/adr/ADR-0020-technical-exception-validation.md`. Adopt the new protected +evaluator before claiming this policy is enforced remotely. Revert evaluator +and registry together if rolling back; do not disable checks or fabricate green +results to bridge adoption. Runtime behavior, expiry (2027-01-06), tool pins, +workflow permissions and aggregate requirements are unchanged. +Policy diagnostics describe differences from the protected files/settings, +not requests for human approval. The legacy `bootstrap-review` rule name is +retained for report compatibility; it denotes a missing protected policy, not +a requirement to solicit reviewers or obtain an administrative statement. +`CODEOWNERS` routes ownership but does not mandate latest-head approval. + ### Implementation and failure-fixture map | Scope | Implementation and executable evidence | diff --git a/src/gpt_rag_ui/api/callbacks.py b/src/gpt_rag_ui/api/callbacks.py index ce962b1..124c861 100644 --- a/src/gpt_rag_ui/api/callbacks.py +++ b/src/gpt_rag_ui/api/callbacks.py @@ -1,9 +1,12 @@ """Single registration owner for Chainlit callbacks and data-layer wiring.""" +import logging + import chainlit as cl from gpt_rag_ui.services import chat +logger = logging.getLogger(__name__) _registered = False @@ -16,9 +19,15 @@ def register_callbacks() -> None: register_oauth_callback() if chat.OAUTH_CONFIGURED or chat.COPILOT_ENABLED: - from gpt_rag_ui.api.history import register_data_layer - - register_data_layer() + if chat.CHAT_BACKEND == "orchestrator": + from gpt_rag_ui.api.history import register_data_layer + + register_data_layer() + else: + logger.info( + "Hosted chat does not register the classic orchestrator history " + "data layer; conversation context remains session-scoped" + ) if chat.ENABLE_FEEDBACK: from gpt_rag_ui.api.feedback import register_feedback_handlers diff --git a/src/gpt_rag_ui/api/download_routes.py b/src/gpt_rag_ui/api/download_routes.py index 8a25e20..ace3fc0 100644 --- a/src/gpt_rag_ui/api/download_routes.py +++ b/src/gpt_rag_ui/api/download_routes.py @@ -11,6 +11,7 @@ from fastapi.responses import StreamingResponse from gpt_rag_ui.auth.embed_auth import CopilotSessionStore +from gpt_rag_ui.telemetry.boundary_diagnostics import log_boundary_failure from gpt_rag_ui.services.conversation_security import get_owned_conversation from gpt_rag_ui.services.download_security import ( BlobDownloader, ConversationResolver, DownloadStream, DownloadTokenManager, @@ -102,7 +103,7 @@ async def download_blob_file(grant_token: str, request: Request) -> StreamingRes headers={"Cache-Control": "no-store"}, ) from exc except Exception as exc: - logger.exception( + log_boundary_failure(logger, "Authorized download failed: conversation=%s container=%s", grant.conversation_id, grant.container, diff --git a/src/gpt_rag_ui/auth/embed_auth.py b/src/gpt_rag_ui/auth/embed_auth.py index 2a45d97..d8f53aa 100644 --- a/src/gpt_rag_ui/auth/embed_auth.py +++ b/src/gpt_rag_ui/auth/embed_auth.py @@ -27,6 +27,7 @@ from gpt_rag_ui.config.appconfig import AppConfigClient from gpt_rag_ui.config.embed_config import EmbedSettings from gpt_rag_ui.auth.entra_token import EntraTokenError +from gpt_rag_ui.telemetry.boundary_diagnostics import log_boundary_failure COPILOT_SESSION_COOKIE = "gpt_rag_copilot_session" @@ -172,7 +173,7 @@ async def _notify_invalidated(self, session_ids: list[str]) -> None: try: await self._on_invalidate(session_id) except Exception: - logger.exception( + log_boundary_failure(logger, "Failed to disconnect invalidated Copilot session" ) diff --git a/src/gpt_rag_ui/auth/embed_security.py b/src/gpt_rag_ui/auth/embed_security.py index 9206190..26ee738 100644 --- a/src/gpt_rag_ui/auth/embed_security.py +++ b/src/gpt_rag_ui/auth/embed_security.py @@ -21,6 +21,7 @@ reset_request_copilot_session, ) from gpt_rag_ui.config.embed_config import EmbedSettings +from gpt_rag_ui.telemetry.boundary_diagnostics import log_boundary_failure logger = logging.getLogger("gpt_rag_ui.embed_security") @@ -305,7 +306,7 @@ async def bind_connection( for replaced_socket_id in replaced_socket_ids: await disconnect(replaced_socket_id) except Exception: - logger.exception( + log_boundary_failure(logger, "Failed to disconnect a superseded Copilot socket" ) return False @@ -503,7 +504,7 @@ async def disconnect_session( await disconnect(socket_id) disconnected_socket_ids.append(socket_id) except Exception: - logger.exception("Failed to disconnect a Copilot socket") + log_boundary_failure(logger, "Failed to disconnect a Copilot socket") async with self._lock: for socket_id in disconnected_socket_ids: @@ -1008,7 +1009,7 @@ def _target_has_copilot_sockets(sio, target: str | None) -> bool: return True return False except Exception: - logger.exception("Could not resolve Socket.IO bridge recipients") + log_boundary_failure(logger, "Could not resolve Socket.IO bridge recipients") # Default deny only when a Copilot socket could receive the event. return _has_copilot_sockets() @@ -1157,7 +1158,7 @@ async def disconnect(socket_id: str) -> None: engineio_sid, ) except Exception: - logger.exception( + log_boundary_failure(logger, "Failed to disconnect a Copilot Engine.IO transport" ) return disconnected @@ -1168,7 +1169,7 @@ async def disconnect(socket_id: str) -> None: await disconnect(socket_id) disconnected += 1 except Exception: - logger.exception("Failed to disconnect a Copilot socket") + log_boundary_failure(logger, "Failed to disconnect a Copilot socket") return disconnected @@ -1194,7 +1195,7 @@ async def _terminate_socket( try: await (disconnect or sio.disconnect)(socket_id) except Exception: - logger.exception("Failed to terminate an invalid Copilot socket") + log_boundary_failure(logger, "Failed to terminate an invalid Copilot socket") return False try: if ( @@ -1204,7 +1205,7 @@ async def _terminate_socket( ): await delete_session() except Exception: - logger.exception("Failed to delete an invalid Chainlit session") + log_boundary_failure(logger, "Failed to delete an invalid Chainlit session") finally: await registry.unbind_socket(socket_id) return True @@ -1295,7 +1296,7 @@ async def close_rejected_socket(socket_id: str) -> None: _engineio_sid_for_socket(sio, socket_id), ) except Exception: - logger.exception( + log_boundary_failure(logger, "Failed to close a rejected Copilot transport" ) @@ -1374,7 +1375,7 @@ async def guarded_engineio_disconnect( **kwargs, ) except Exception: - logger.exception( + log_boundary_failure(logger, "Engine.IO disconnect cleanup failed; forcing " "transport cleanup" ) @@ -1383,7 +1384,7 @@ async def guarded_engineio_disconnect( namespaces = tuple(manager.get_namespaces()) except Exception: namespaces = () - logger.exception( + log_boundary_failure(logger, "Failed to enumerate Socket.IO namespaces " "during Engine.IO cleanup" ) @@ -1394,7 +1395,7 @@ async def guarded_engineio_disconnect( namespace, ) except Exception: - logger.exception( + log_boundary_failure(logger, "Failed to resolve a Socket.IO transport " "during Engine.IO cleanup" ) @@ -1408,7 +1409,7 @@ async def guarded_engineio_disconnect( ignore_queue=True, ) except Exception: - logger.exception( + log_boundary_failure(logger, "Failed to force Socket.IO manager cleanup" ) continue @@ -1672,7 +1673,7 @@ async def disconnect_superseded( ) raise except Exception: - logger.exception("Chainlit Socket.IO connection failed") + log_boundary_failure(logger, "Chainlit Socket.IO connection failed") await _terminate_socket( sio, registry, diff --git a/src/gpt_rag_ui/bootstrap.py b/src/gpt_rag_ui/bootstrap.py index d5548c8..12b13c8 100644 --- a/src/gpt_rag_ui/bootstrap.py +++ b/src/gpt_rag_ui/bootstrap.py @@ -13,6 +13,7 @@ from fastapi.responses import JSONResponse, StreamingResponse from gpt_rag_ui.config.appconfig import AppConfigClient +from gpt_rag_ui.telemetry.boundary_diagnostics import log_boundary_failure from gpt_rag_ui.clients.blob import BlobClient from gpt_rag_ui.config.dependencies import get_config from gpt_rag_ui.config.resources import get_asset_root @@ -913,7 +914,7 @@ def _safe_openapi(): routes=chainlit_app.routes, ) except Exception: - logger.exception("OpenAPI generation failed; schema unavailable, retry required") + log_boundary_failure(logger, "OpenAPI generation failed; schema unavailable, retry required") raise return chainlit_app.openapi_schema diff --git a/src/gpt_rag_ui/clients/hosted_agent_client.py b/src/gpt_rag_ui/clients/hosted_agent_client.py index b958e1b..89cfbfa 100644 --- a/src/gpt_rag_ui/clients/hosted_agent_client.py +++ b/src/gpt_rag_ui/clients/hosted_agent_client.py @@ -549,6 +549,7 @@ async def stream( async with self._http_client.stream( "POST", self.settings.invocations_url, + params={"api-version": "v1"}, json=payload, headers=headers, ) as response: @@ -574,9 +575,21 @@ async def stream( "response.created is missing its response object." ) new_conversation_id = response_data.get("conversation_id") - if not isinstance(new_conversation_id, str) or not new_conversation_id: + conversation = response_data.get("conversation") + if conversation is not None: + if not isinstance(conversation, dict): + raise HostedAgentProtocolError( + "response.created has an invalid conversation object." + ) + nested_id = conversation.get("id") + if new_conversation_id is not None and new_conversation_id != nested_id: + raise HostedAgentProtocolError( + "response.created has conflicting conversation identifiers." + ) + new_conversation_id = nested_id + if not isinstance(new_conversation_id, str) or not new_conversation_id.strip(): raise HostedAgentProtocolError( - "response.created is missing managed conversation_id." + "response.created is missing a conversation identifier." ) managed_conversation_id = new_conversation_id yield "", {"conversation_id": new_conversation_id} diff --git a/src/gpt_rag_ui/services/chat.py b/src/gpt_rag_ui/services/chat.py index 542b2a2..5a8d414 100644 --- a/src/gpt_rag_ui/services/chat.py +++ b/src/gpt_rag_ui/services/chat.py @@ -28,6 +28,7 @@ from gpt_rag_ui.util.constants import APPLICATION_INSIGHTS_CONNECTION_STRING, APP_NAME, UUID_REGEX, REFERENCE_REGEX, TERMINATE_TOKEN from gpt_rag_ui.telemetry.monitoring import Telemetry +from gpt_rag_ui.telemetry.boundary_diagnostics import log_boundary_failure from opentelemetry.trace import SpanKind from chainlit.types import ThreadDict @@ -298,7 +299,10 @@ async def get_auth_info() -> dict: cl.user_session.set("user", app_user) except Exception: # If refresh fails, clear the user session so the UI can re-auth. - logger.warning("User access token refresh failed; clearing session to force re-auth", exc_info=True) + log_boundary_failure( + logger, "User access token refresh failed; clearing session to force re-auth", + level=logging.WARNING, + ) cl.user_session.set("user", None) return { 'authorized': False, @@ -618,7 +622,7 @@ def _trim_for_log(value: str, limit: int = 400) -> str: files=uploaded_files, ) except Exception: - logger.exception( + log_boundary_failure(logger, "File ingestion failed: conversation=%s question_id=%s", conversation_id or "new", message.id, @@ -913,7 +917,7 @@ def _trim_for_log(value: str, limit: int = 400) -> str: "Please contact the application support team and share reference " f"{message.id}." ) - logger.exception( + log_boundary_failure(logger, "Failed while processing hosted agent continuity response: " "conversation=%s question_id=%s", conversation_id or "pending", @@ -1063,7 +1067,7 @@ def _trim_for_log(value: str, limit: int = 400) -> str: "Please contact the application support team and share reference " f"{message.id}." ) - logger.exception( + log_boundary_failure(logger, "Failed while processing hosted agent response: conversation=%s question_id=%s", conversation_id or "pending", message.id, @@ -1240,7 +1244,7 @@ def _trim_for_log(value: str, limit: int = 400) -> str: "Please contact the application support team and share reference " f"{message.id}." ) - logger.exception( + log_boundary_failure(logger, "Failed while processing orchestrator response: conversation=%s question_id=%s", conversation_id or "pending", message.id, diff --git a/src/gpt_rag_ui/services/citations.py b/src/gpt_rag_ui/services/citations.py index 28bc3dd..daf45ce 100644 --- a/src/gpt_rag_ui/services/citations.py +++ b/src/gpt_rag_ui/services/citations.py @@ -10,6 +10,7 @@ from gpt_rag_ui.config.dependencies import get_config from gpt_rag_ui.services.download_security import get_download_tokens, is_download_target_allowed from gpt_rag_ui.util.constants import REFERENCE_REGEX +from gpt_rag_ui.telemetry.boundary_diagnostics import log_boundary_failure logger = logging.getLogger("gpt_rag_ui.app") config = get_config() @@ -155,10 +156,10 @@ def _resolve_legacy_reference_href(raw_href: str) -> Optional[str]: ) return None except Exception: - logger.warning( - "Failed to build SAS URL for reference '%s'", - raw_href, - exc_info=True, + log_boundary_failure( + logger, + "Failed to build SAS URL for legacy reference; reference omitted", + level=logging.WARNING, ) return None diff --git a/src/gpt_rag_ui/services/hosted_continuity.py b/src/gpt_rag_ui/services/hosted_continuity.py index b9eb446..fc08b6f 100644 --- a/src/gpt_rag_ui/services/hosted_continuity.py +++ b/src/gpt_rag_ui/services/hosted_continuity.py @@ -67,6 +67,7 @@ from typing import Any from gpt_rag_ui.auth.auth_common import normalize_guid +from gpt_rag_ui.telemetry.boundary_diagnostics import log_boundary_failure from gpt_rag_ui.clients.hosted_agent_client import HostedAgentError, InvocationMessage, call_hosted_agent_stream from gpt_rag_ui.config.hosted_continuity_config import HostedContinuitySettings from gpt_rag_ui.clients.hosted_conversation_capability import ( @@ -353,7 +354,7 @@ async def run_turn( try: await self._on_conversation_created(canonical_oid, conversation_id) except Exception: - logger.exception( + log_boundary_failure(logger, "Panel owner-index write failed for a newly created " "hosted conversation; continuing the turn (a missed " "write hides the panel list row and denies panel " diff --git a/src/gpt_rag_ui/telemetry/boundary_diagnostics.py b/src/gpt_rag_ui/telemetry/boundary_diagnostics.py new file mode 100644 index 0000000..9fbad55 --- /dev/null +++ b/src/gpt_rag_ui/telemetry/boundary_diagnostics.py @@ -0,0 +1,33 @@ +"""Failure diagnostics without dependency messages, locals or exception chains.""" + +import logging +import sys +from pathlib import Path + + +def log_boundary_failure( + logger: logging.Logger, + message: str, + *args: object, + level: int = logging.ERROR, +) -> None: + error = sys.exception() + error_type = type(error).__name__ if error is not None else "unknown" + location = "unknown" + if error is not None: + traceback = error.__traceback__ + while traceback is not None: + frame = traceback.tb_frame + location = ( + f"{Path(frame.f_code.co_filename).name}:" + f"{traceback.tb_lineno}:{frame.f_code.co_name}" + ) + traceback = traceback.tb_next + logger.log( + level, + message + " [exception_type=%s failure_site=%s]", + *args, + error_type, + location, + stacklevel=2, + ) diff --git a/tests/test_boundary_diagnostics.py b/tests/test_boundary_diagnostics.py new file mode 100644 index 0000000..3a91b8f --- /dev/null +++ b/tests/test_boundary_diagnostics.py @@ -0,0 +1,42 @@ +import logging +import unittest + +from gpt_rag_ui.telemetry.boundary_diagnostics import log_boundary_failure + + +class BoundaryDiagnosticsTests(unittest.TestCase): + def test_dependency_payload_chain_and_locals_are_not_logged(self): + logger = logging.getLogger("tests.boundary_diagnostics") + private_value = "private-token-cookie-claim-url-sentinel" + with self.assertLogs(logger, level="ERROR") as logs: + try: + try: + raise ValueError(private_value) + except ValueError as cause: + raise RuntimeError(private_value) from cause + except RuntimeError: + log_boundary_failure(logger, "Boundary failed: reference=%s", "reference-123") + output = "\n".join(logs.output) + self.assertNotIn(private_value, output) + self.assertIn("reference-123", output) + self.assertIn("exception_type=RuntimeError", output) + self.assertIn("test_boundary_diagnostics.py:", output) + self.assertIsNone(logs.records[0].exc_info) + self.assertIsNone(logs.records[0].exc_text) + self.assertEqual(__name__, logs.records[0].module) + + def test_warning_diagnostic_preserves_level_without_error_payload(self): + logger = logging.getLogger("tests.boundary_diagnostics") + with self.assertLogs(logger, level="WARNING") as logs: + try: + raise ValueError("private-refresh-token") + except ValueError: + log_boundary_failure(logger, "Refresh denied", level=logging.WARNING) + self.assertEqual(logging.WARNING, logs.records[0].levelno) + self.assertNotIn("private-refresh-token", "\n".join(logs.output)) + + def test_no_active_exception_is_explicit(self): + logger = logging.getLogger("tests.boundary_diagnostics") + with self.assertLogs(logger, level="ERROR") as logs: + log_boundary_failure(logger, "Failure without active exception") + self.assertIn("exception_type=unknown failure_site=unknown", logs.output[0]) diff --git a/tests/test_boundary_failures.py b/tests/test_boundary_failures.py index 0e647f6..86f7033 100644 --- a/tests/test_boundary_failures.py +++ b/tests/test_boundary_failures.py @@ -29,7 +29,7 @@ async def test_chat_failure_boundaries_report_errors_and_close_generators(self): async def failure(*args, **kwargs): try: - raise RuntimeError("backend failure") + raise RuntimeError("private-backend-token-sentinel") yield finally: closed.append(True) @@ -56,8 +56,10 @@ async def failure(*args, **kwargs): } for name, value in patches.items(): stack.enter_context(patch.object(chat, name, value, create=True)) - with self.assertLogs(chat.logger, level="ERROR"): + with self.assertLogs(chat.logger, level="ERROR") as logs: await chat.handle_message(SimpleNamespace(id="failure-reference", content="question", elements=[])) + self.assertNotIn("private-backend-token-sentinel", "\n".join(logs.output)) + self.assertTrue(all(record.exc_info is None for record in logs.records)) self.assertIn("technical issue", response.content) self.assertIn("failure-reference", response.content) self.assertEqual([True], closed) @@ -260,15 +262,17 @@ async def test_oauth_refresh_failure_clears_session_and_denies_request(self): with ( patch.object(chat, "cl", SimpleNamespace(user_session=session)), patch.object(chat, "auth_oauth", SimpleNamespace( - ensure_fresh_user_access_token=AsyncMock(side_effect=RuntimeError("refresh failure")), + ensure_fresh_user_access_token=AsyncMock(side_effect=RuntimeError("private-refresh-token")), ), create=True), - self.assertLogs(chat.logger, level="WARNING"), + self.assertLogs(chat.logger, level="WARNING") as logs, ): result = await chat.get_auth_info() self.assertFalse(result["authorized"]) self.assertEqual("session_expired", result["auth_error"]) self.assertIsNone(result["access_token"]) session.set.assert_called_once_with("user", None) + self.assertNotIn("private-refresh-token", "\n".join(logs.output)) + self.assertTrue(all(record.exc_info is None for record in logs.records)) async def test_feedback_boundary_emits_failure_toast_not_success(self): from gpt_rag_ui.api import feedback @@ -397,9 +401,10 @@ def decorator(callback): async def test_invalidation_callback_failure_does_not_skip_other_sessions(self): callback = AsyncMock(side_effect=[RuntimeError("disconnect failure"), None]) store = CopilotSessionStore(max_sessions=2, ttl_seconds=120, on_invalidate=callback) - with self.assertLogs("gpt_rag_ui.embed_auth", level="ERROR"): + with self.assertLogs("gpt_rag_ui.embed_auth", level="ERROR") as logs: await store._notify_invalidated(["first", "second", "first"]) self.assertEqual(["first", "second"], [call.args[0] for call in callback.await_args_list]) + self.assertNotIn("disconnect failure", "\n".join(logs.output)) async def test_cancellation_is_not_converted_to_successful_invalidation(self): store = CopilotSessionStore( @@ -412,6 +417,9 @@ async def test_optional_legacy_citation_failure_is_logged_and_omitted(self): from gpt_rag_ui.services import citations with ( patch.object(citations, "generate_blob_sas_url", side_effect=RuntimeError("signing failure")), - self.assertLogs(citations.logger, level="WARNING"), + self.assertLogs(citations.logger, level="WARNING") as logs, ): - self.assertIsNone(citations._resolve_legacy_reference_href("document.pdf")) + self.assertIsNone(citations._resolve_legacy_reference_href("document.pdf?sig=private-sas-token")) + self.assertNotIn("signing failure", "\n".join(logs.output)) + self.assertNotIn("private-sas-token", "\n".join(logs.output)) + self.assertTrue(all(record.exc_info is None for record in logs.records)) diff --git a/tests/test_callback_registration.py b/tests/test_callback_registration.py new file mode 100644 index 0000000..36a001d --- /dev/null +++ b/tests/test_callback_registration.py @@ -0,0 +1,90 @@ +"""Backend-specific callback registration without network or Chainlit sessions.""" + +import importlib.util +import sys +import unittest +from types import ModuleType, SimpleNamespace +from unittest.mock import Mock, patch + + +class TestCallbackRegistration(unittest.TestCase): + def register(self, backend, *, oauth=True, copilot=False, feedback=False): + spec = importlib.util.find_spec("gpt_rag_ui.api.callbacks") + self.assertIsNotNone(spec) + self.assertIsNotNone(spec.loader) + chainlit = ModuleType("chainlit") + for name in ("on_chat_start", "on_chat_resume", "on_message"): + setattr(chainlit, name, Mock()) + chat = SimpleNamespace( + CHAT_BACKEND=backend, + OAUTH_CONFIGURED=oauth, + COPILOT_ENABLED=copilot, + ENABLE_FEEDBACK=feedback, + ALLOW_ANONYMOUS=not oauth, + get_auth_info=Mock(), + on_chat_start=Mock(), + on_chat_resume=Mock(), + handle_message=Mock(), + ) + services = ModuleType("gpt_rag_ui.services") + services.chat = chat + history = ModuleType("gpt_rag_ui.api.history") + history.register_data_layer = Mock() + auth = ModuleType("gpt_rag_ui.api.oauth") + auth.register_oauth_callback = Mock() + feedback_module = ModuleType("gpt_rag_ui.api.feedback") + feedback_module.register_feedback_handlers = Mock() + modules = { + "chainlit": chainlit, + "gpt_rag_ui.services": services, + "gpt_rag_ui.api.history": history, + "gpt_rag_ui.api.oauth": auth, + "gpt_rag_ui.api.feedback": feedback_module, + } + callbacks = importlib.util.module_from_spec(spec) + with patch.dict(sys.modules, modules): + spec.loader.exec_module(callbacks) + callbacks.register_callbacks() + callbacks.register_callbacks() + for name in ("on_chat_start", "on_chat_resume", "on_message"): + getattr(chainlit, name).assert_called_once_with(getattr(chat, { + "on_message": "handle_message", + }.get(name, name))) + return history, auth, feedback_module, chat + + def test_hosted_retains_oauth_without_classic_history(self): + history, auth, _, _ = self.register("hosted_agent") + history.register_data_layer.assert_not_called() + auth.register_oauth_callback.assert_called_once() + + def test_classic_retains_authenticated_history(self): + history, auth, _, _ = self.register("orchestrator") + history.register_data_layer.assert_called_once() + auth.register_oauth_callback.assert_called_once() + + def test_hosted_copilot_does_not_register_classic_history(self): + history, auth, _, _ = self.register("hosted_agent", oauth=False, copilot=True) + history.register_data_layer.assert_not_called() + auth.register_oauth_callback.assert_not_called() + + def test_classic_copilot_preserves_history(self): + history, auth, _, _ = self.register("orchestrator", oauth=False, copilot=True) + history.register_data_layer.assert_called_once() + auth.register_oauth_callback.assert_not_called() + + def test_anonymous_classic_does_not_enable_history(self): + history, auth, _, _ = self.register("orchestrator", oauth=False) + history.register_data_layer.assert_not_called() + auth.register_oauth_callback.assert_not_called() + + def test_hosted_preserves_authenticated_feedback(self): + _, _, feedback, chat = self.register("hosted_agent", feedback=True) + feedback.register_feedback_handlers.assert_called_once_with( + chat.get_auth_info, allow_standalone_anonymous=False + ) + + def test_hosted_preserves_explicit_anonymous_feedback(self): + _, _, feedback, chat = self.register("hosted_agent", oauth=False, feedback=True) + feedback.register_feedback_handlers.assert_called_once_with( + chat.get_auth_info, allow_standalone_anonymous=True + ) diff --git a/tests/test_embed_security.py b/tests/test_embed_security.py index 767dbce..a185701 100644 --- a/tests/test_embed_security.py +++ b/tests/test_embed_security.py @@ -1205,7 +1205,14 @@ async def test_engineio_cleanup_dependency_failures_release_transport_reservatio await sio.eio.handlers["connect"]("engine", transport_environ()) with self.assertLogs(embed_security.logger, level="ERROR") as logs: self.assertIsNone(await sio.eio.handlers["disconnect"]("engine")) - self.assertTrue(any(f"{stage} failed" in line for line in logs.output)) + expected = { + "namespaces": "Failed to enumerate Socket.IO namespaces", + "lookup": "Failed to resolve a Socket.IO transport", + "disconnect": "Failed to force Socket.IO manager cleanup", + }[stage] + self.assertTrue(any(expected in line for line in logs.output)) + self.assertNotIn(f"{stage} failed", "\n".join(logs.output)) + self.assertTrue(all(record.exc_info is None for record in logs.records)) self.assertIsNone(await embed_security._copilot_socket_registry.engineio_session("engine")) async def test_failed_session_disconnect_retains_denied_binding(self): diff --git a/tests/test_hosted_agent_client.py b/tests/test_hosted_agent_client.py index 0bb74da..624003b 100644 --- a/tests/test_hosted_agent_client.py +++ b/tests/test_hosted_agent_client.py @@ -546,7 +546,7 @@ async def test_real_frames_surface_text_citation_tool_and_completion(self): self.assertEqual(capture[0]["method"], "POST") self.assertEqual( capture[0]["url"], - "https://agent.example.com/protocol/invocations", + "https://agent.example.com/protocol/invocations?api-version=v1", ) transmitted_authorization = capture[0]["headers"]["authorization"] self.assertEqual( @@ -676,6 +676,65 @@ async def handler(request: httpx.Request) -> httpx.Response: ): self.assertNotIn(forbidden, captures[1]) + async def test_accepts_canonical_conversation_object(self): + body = _frame( + "response.created", + {"response": {"conversation": {"id": "conv-managed"}}}, + ) + _completed() + client = await self._client_for_body(body) + results = await self._collect(client, [InvocationMessage("user", "hello")]) + self.assertIn(("", {"conversation_id": "conv-managed"}), results) + self.assertTrue(any(meta.get("completed") for _, meta in results)) + + async def test_accepts_matching_canonical_and_legacy_identifiers(self): + body = _frame( + "response.created", + { + "response": { + "conversation": {"id": "conv-managed"}, + "conversation_id": "conv-managed", + } + }, + ) + _completed() + client = await self._client_for_body(body) + results = await self._collect(client, [InvocationMessage("user", "hello")]) + self.assertIn(("", {"conversation_id": "conv-managed"}), results) + + async def test_rejects_malformed_conversation_identifiers(self): + invalid_responses = [ + {}, + {"conversation_id": ""}, + {"conversation_id": " "}, + {"conversation_id": 42}, + {"conversation": "conv-managed"}, + {"conversation": {}}, + {"conversation": {"id": ""}}, + {"conversation": {"id": " "}}, + {"conversation": {"id": 42}}, + {"conversation": {"id": None}}, + ] + for response in invalid_responses: + with self.subTest(response=response): + client = await self._client_for_body( + _frame("response.created", {"response": response}) + _completed() + ) + with self.assertRaises(HostedAgentProtocolError): + await self._collect(client, [InvocationMessage("user", "hello")]) + + async def test_rejects_conflicting_conversation_identifiers(self): + body = _frame( + "response.created", + { + "response": { + "conversation": {"id": "conv-managed"}, + "conversation_id": "another-conversation", + } + }, + ) + _completed() + client = await self._client_for_body(body) + with self.assertRaisesRegex(HostedAgentProtocolError, "conflicting"): + await self._collect(client, [InvocationMessage("user", "hello")]) + async def test_error_frame_raises_typed_error(self): body = _created("conv-1") + _frame( "error", diff --git a/tests/test_installed_package.py b/tests/test_installed_package.py index 4d4e06e..5723520 100644 --- a/tests/test_installed_package.py +++ b/tests/test_installed_package.py @@ -138,7 +138,9 @@ def test_both_import_orders_have_one_owner_and_one_registration(self): register_callbacks() register_callbacks() assert start.call_count == resume.call_count == message.call_count == 1 - assert data.call_count == oauth.call_count == 1 + from gpt_rag_ui.services import chat + assert data.call_count == int(chat.CHAT_BACKEND == "orchestrator") + assert oauth.call_count == 1 import dependencies from gpt_rag_ui.config import dependencies as owner assert dependencies.get_config is owner.get_config diff --git a/tests/test_quality_policy.py b/tests/test_quality_policy.py index fb99b0b..bb51213 100644 --- a/tests/test_quality_policy.py +++ b/tests/test_quality_policy.py @@ -20,6 +20,31 @@ class QualityPolicyTests(unittest.TestCase): + def test_technical_decisions_match_exact_eligible_handlers_without_human_approval(self): + root = SCRIPT.parents[2] + registry = json.loads((root / ".quality/exceptions.json").read_text(encoding="utf-8"))["entries"] + review = json.loads((root / ".quality/exception-technical-review.json").read_text(encoding="utf-8")) + decisions = {entry["id"]: entry for entry in review["decisions"]} + self.assertEqual(30, len(registry)) + self.assertEqual({entry["id"] for entry in registry}, set(decisions)) + self.assertEqual({"reviewed": 30, "keep": 7, "correct": 23, "remove": 0}, review["summary"]) + for entry in registry: + with self.subTest(exception_id=entry["id"]): + decision = decisions[entry["id"]] + handlers = quality.broad_handlers( + entry["module_id"], (root / decision["source_path"]).read_text(encoding="utf-8"), + ) + self.assertTrue(any( + handler["symbol"] == entry["symbol"] + and handler["handler_fingerprint"] == entry["handler_fingerprint"] + for handler in handlers + )) + self.assertEqual(entry["handler_fingerprint"], decision["candidate_fingerprint"]) + self.assertEqual("active", entry["state"]) + self.assertEqual("not required by technical policy; not performed by this AI assessment", + decision["human_review_status"]) + self.assertEqual("2027-01-06", entry["expires_on"]) + def test_subprocess_environment_preserves_empty_values(self): with patch.dict(os.environ, {"GPT_RAG_EMPTY_ENV_FIXTURE": ""}): result = quality.execute( @@ -512,7 +537,9 @@ def test_expiry_stage_and_executed_exception_evidence_are_required(self): "today": datetime.date(2026, 9, 6)} self.assertEqual([], quality.validate_exception_records([handler], [record], Path.cwd(), **kwargs)[0]) for changed in ({**record, "expires_on": "2026-09-05"}, {**record, "review_by_stage": "bootstrap"}, - {**record, "state": "maintainer-approved"}, {**record, "caught_types": ["BaseException"]}): + {**record, "state": "maintainer-approved"}, {**record, "state": "proposed"}, + {**record, "state": "retired"}, {**record, "review": ""}, + {**record, "caught_types": ["BaseException"]}): self.assertTrue(quality.validate_exception_records([handler], [changed], Path.cwd(), **kwargs)[0]) self.assertTrue(quality.validate_exception_records([handler], [record], Path.cwd())[0]) @@ -624,7 +651,7 @@ def protected_fixture(self, root): "commit", "--quiet", "-m", "protected policy"], cwd=root) return quality.git(root, "rev-parse", "HEAD") - def test_ruff_allowance_requires_exact_protected_handler_and_bound_evidence(self): + def test_ruff_allowance_requires_exact_candidate_handler_and_bound_evidence(self): source = ( "def boundary(callback):\n" " try:\n" @@ -634,7 +661,8 @@ def test_ruff_allowance_requires_exact_protected_handler_and_bound_evidence(self ) selector = "tests/test_boundary.py::Boundary.test_failure" for case in ("active", "proposed", "stale", "missing-evidence", "expired", - "other-rule", "other-handler", "other-module", "candidate-activation", "bootstrap", + "other-rule", "other-handler", "other-module", "candidate-activation", + "candidate-addition", "candidate-update", "candidate-retirement", "bootstrap", "stale-receipt", "failed-evidence", "skipped-evidence"): with self.subTest(case=case), tempfile.TemporaryDirectory() as directory: root = Path(directory).resolve() @@ -668,7 +696,8 @@ def test_ruff_allowance_requires_exact_protected_handler_and_bound_evidence(self if case == "expired": entry["expires_on"] = "2000-01-01" ledger = root / ".quality" / "exceptions.json" - ledger.write_text(json.dumps({"schema_version": 1, "entries": [entry]}), encoding="utf-8") + ledger.write_text(json.dumps({"schema_version": 1, "entries": + [] if case == "candidate-addition" else [entry]}), encoding="utf-8") with (root / "pyproject.toml").open("a", encoding="utf-8") as config: config.write('\n[tool.ruff.lint]\nselect=["BLE001", "F821"]\n') quality.execute(["git", "add", "."], cwd=root) @@ -678,6 +707,25 @@ def test_ruff_allowance_requires_exact_protected_handler_and_bound_evidence(self if case == "candidate-activation": entry["state"] = "active" ledger.write_text(json.dumps({"schema_version": 1, "entries": [entry]}), encoding="utf-8") + if case == "candidate-addition": + ledger.write_text(json.dumps({"schema_version": 1, "entries": [entry]}), encoding="utf-8") + if case == "candidate-update": + updated_source = source.replace("return None", "return False") + (root / "sample.py").write_text(updated_source, encoding="utf-8") + handler = quality.broad_handlers("sample", updated_source)[0] + entry["handler_fingerprint"] = handler["handler_fingerprint"] + entry["review"] = "Technical provenance only; no independent approval" + ledger.write_text(json.dumps({"schema_version": 1, "entries": [entry]}), encoding="utf-8") + test_path = root / "tests" / "test_boundary.py" + test_path.write_text(test_path.read_text().replace( + "self.assertIsNone(boundary(fail))", "self.assertIs(boundary(fail), False)"), encoding="utf-8") + if case == "candidate-retirement": + (root / "sample.py").write_text("def boundary(callback):\n return callback()\n", encoding="utf-8") + ledger.write_text(json.dumps({"schema_version": 1, "entries": []}), encoding="utf-8") + test_path = root / "tests" / "test_boundary.py" + test_path.write_text(test_path.read_text().replace( + "self.assertIsNone(boundary(fail))", + "with self.assertRaises(RuntimeError): boundary(fail)"), encoding="utf-8") if case == "stale": (root / "sample.py").write_text( source.replace("return None", "return None if callback else False"), encoding="utf-8") @@ -708,17 +756,23 @@ def test_ruff_allowance_requires_exact_protected_handler_and_bound_evidence(self root, base, ("lint", "exceptions"), test_evidence=None if case == "missing-evidence" else evidence) lint = report["checks"]["lint"]["findings"] - expected = [] if case == "active" else ["F821"] if case == "other-rule" else ["BLE001"] + passing = {"active", "candidate-activation", "candidate-addition", + "candidate-update", "candidate-retirement"} + expected = [] if case in passing else ["F821"] if case == "other-rule" else ["BLE001"] self.assertEqual(expected, [item["rule"] for item in lint]) if case == "other-handler": self.assertEqual(10, lint[0]["line"]) if case == "other-module": self.assertEqual("other.py", lint[0]["module"]) - if case in ("active", "other-rule", "other-handler", "other-module"): + if case in (passing - {"candidate-retirement"}) | {"other-rule", "other-handler", "other-module"}: self.assertEqual(["reviewed-boundary"], report["exception_ids_used"]) else: self.assertEqual([], report["exception_ids_used"]) - # Lint-only execution must enforce the same protected evidence. + if case in passing: + self.assertEqual([], report["checks"]["exceptions"]["findings"]) + policy_report = quality.run_checks(root, base, ("policy",), test_evidence=evidence) + self.assertEqual("passed", policy_report["status"]) + # Lint-only execution must enforce the same protected rules/evidence. lint_only = quality.run_checks( root, base, ("lint",), test_evidence=None if case == "missing-evidence" else evidence) @@ -742,6 +796,9 @@ def test_real_protected_base_rejects_config_codeowners_and_annotation_tampering( try: report = quality.run_checks(root, base, ("policy",)) self.assertIn(rule, {f["rule"] for f in report["checks"]["policy"]["findings"]}) + for item in report["checks"]["policy"]["findings"]: + self.assertNotIn("review required", item["reason"].lower()) + self.assertNotIn("requires protected review", item["reason"].lower()) finally: if old is None: target.unlink() @@ -752,6 +809,19 @@ def test_real_protected_base_rejects_config_codeowners_and_annotation_tampering( self.assertEqual("violations", report["status"]) self.assertIn("new", report["coverage"]["blocking"]) + def test_bootstrap_still_blocks_without_requesting_human_approval(self): + with tempfile.TemporaryDirectory() as directory: + root = Path(directory).resolve() + base = self.protected_fixture(root) + pre_policy_base = quality.git(root, "rev-parse", f"{base}^") + report = quality.run_checks(root, pre_policy_base, ("policy",)) + self.assertEqual("violations", report["status"]) + bootstrap = [item for item in report["checks"]["policy"]["findings"] + if item["rule"] == "bootstrap-review"] + self.assertEqual(1, len(bootstrap)) + self.assertNotIn("approval", bootstrap[0]["reason"].lower()) + self.assertNotIn("administrator", bootstrap[0]["reason"].lower()) + def test_auto_typed_new_module_scope_must_survive_the_next_pr(self): with tempfile.TemporaryDirectory() as directory: root = Path(directory).resolve()