From 0496a7a606678adcc0fd5a9fff3d111bdf3c4585 Mon Sep 17 00:00:00 2001 From: Paulo Lacerda Date: Fri, 9 Oct 2026 19:06:18 -0300 Subject: [PATCH 1/9] fix: make retained boundary diagnostics payload-free Review all 30 exceptions; retain seven unchanged and submit 23 corrected exact-source records as proposals without inherited approval. Full suite: 554 passed. Typing and architecture passed; remaining lint/exception/policy findings require protected review. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .quality/exception-technical-review.json | 689 ++++++++++++++++++ .quality/exceptions.json | 261 ++++--- .quality/handler-inventory.json | 130 ++-- .quality/policy.json | 13 + .quality/typing-scope.json | 1 + docs/python-development.md | 16 +- src/gpt_rag_ui/api/download_routes.py | 3 +- src/gpt_rag_ui/auth/embed_auth.py | 3 +- src/gpt_rag_ui/auth/embed_security.py | 27 +- src/gpt_rag_ui/bootstrap.py | 3 +- src/gpt_rag_ui/services/chat.py | 14 +- src/gpt_rag_ui/services/citations.py | 9 +- src/gpt_rag_ui/services/hosted_continuity.py | 3 +- .../telemetry/boundary_diagnostics.py | 33 + tests/test_boundary_diagnostics.py | 42 ++ tests/test_boundary_failures.py | 22 +- tests/test_embed_security.py | 9 +- tests/test_quality_policy.py | 25 + 18 files changed, 1096 insertions(+), 207 deletions(-) create mode 100644 .quality/exception-technical-review.json create mode 100644 src/gpt_rag_ui/telemetry/boundary_diagnostics.py create mode 100644 tests/test_boundary_diagnostics.py diff --git a/.quality/exception-technical-review.json b/.quality/exception-technical-review.json new file mode 100644 index 0000000..e7b7f2a --- /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": "Protected approvals intentionally do not authorize changed fingerprints", + "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": "required; 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": "required; 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": "required; 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": "required; 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": "required; 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": "required; 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": "required; 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": "required; 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": "required; 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": "required; 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": "required; 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": "required; 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": "required; 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": "required; 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": "required; 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": "required; 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": "required; 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": "required; 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": "required; 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": "required; 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": "required; 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": "required; 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": "required; 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": "required; 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": "required; 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": "required; 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": "required; 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": "required; 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": "required; 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, and this individual exception proposal remains pending review, not approved.", + "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": "required; not performed by this AI assessment" + } + ] +} diff --git a/.quality/exceptions.json b/.quality/exceptions.json index 2c3dea1..65ee26f 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,20 +46,21 @@ "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 correction proposal; independent human review remains required. See .quality/exception-technical-review.json; no approval inherited from initial adoption.", + "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", + "state": "proposed", "review_by_stage": "blocking", "expires_on": "2027-01-06" }, @@ -85,20 +90,21 @@ "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 correction proposal; independent human review remains required. See .quality/exception-technical-review.json; no approval inherited from initial adoption.", + "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", + "state": "proposed", "review_by_stage": "blocking", "expires_on": "2027-01-06" }, @@ -106,19 +112,20 @@ "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 correction proposal; independent human review remains required. See .quality/exception-technical-review.json; no approval inherited from initial adoption.", + "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", + "state": "proposed", "review_by_stage": "blocking", "expires_on": "2027-01-06" }, @@ -126,19 +133,20 @@ "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 correction proposal; independent human review remains required. See .quality/exception-technical-review.json; no approval inherited from initial adoption.", + "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", + "state": "proposed", "review_by_stage": "blocking", "expires_on": "2027-01-06" }, @@ -146,19 +154,20 @@ "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 correction proposal; independent human review remains required. See .quality/exception-technical-review.json; no approval inherited from initial adoption.", + "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", + "state": "proposed", "review_by_stage": "blocking", "expires_on": "2027-01-06" }, @@ -166,19 +175,20 @@ "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 correction proposal; independent human review remains required. See .quality/exception-technical-review.json; no approval inherited from initial adoption.", + "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", + "state": "proposed", "review_by_stage": "blocking", "expires_on": "2027-01-06" }, @@ -186,19 +196,20 @@ "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 correction proposal; independent human review remains required. See .quality/exception-technical-review.json; no approval inherited from initial adoption.", + "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", + "state": "proposed", "review_by_stage": "blocking", "expires_on": "2027-01-06" }, @@ -206,19 +217,20 @@ "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 correction proposal; independent human review remains required. See .quality/exception-technical-review.json; no approval inherited from initial adoption.", + "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", + "state": "proposed", "review_by_stage": "blocking", "expires_on": "2027-01-06" }, @@ -226,19 +238,20 @@ "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 correction proposal; independent human review remains required. See .quality/exception-technical-review.json; no approval inherited from initial adoption.", + "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", + "state": "proposed", "review_by_stage": "blocking", "expires_on": "2027-01-06" }, @@ -268,19 +281,20 @@ "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 correction proposal; independent human review remains required. See .quality/exception-technical-review.json; no approval inherited from initial adoption.", + "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", + "state": "proposed", "review_by_stage": "blocking", "expires_on": "2027-01-06" }, @@ -308,21 +322,22 @@ "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 correction proposal; independent human review remains required. See .quality/exception-technical-review.json; no approval inherited from initial adoption.", + "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", + "state": "proposed", "review_by_stage": "blocking", "expires_on": "2027-01-06" }, @@ -330,19 +345,20 @@ "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 correction proposal; independent human review remains required. See .quality/exception-technical-review.json; no approval inherited from initial adoption.", + "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", + "state": "proposed", "review_by_stage": "blocking", "expires_on": "2027-01-06" }, @@ -350,19 +366,20 @@ "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 correction proposal; independent human review remains required. See .quality/exception-technical-review.json; no approval inherited from initial adoption.", + "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", + "state": "proposed", "review_by_stage": "blocking", "expires_on": "2027-01-06" }, @@ -370,19 +387,20 @@ "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 correction proposal; independent human review remains required. See .quality/exception-technical-review.json; no approval inherited from initial adoption.", + "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", + "state": "proposed", "review_by_stage": "blocking", "expires_on": "2027-01-06" }, @@ -390,19 +408,20 @@ "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 correction proposal; independent human review remains required. See .quality/exception-technical-review.json; no approval inherited from initial adoption.", + "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", + "state": "proposed", "review_by_stage": "blocking", "expires_on": "2027-01-06" }, @@ -450,19 +469,20 @@ "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 correction proposal; independent human review remains required. See .quality/exception-technical-review.json; no approval inherited from initial adoption.", + "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", + "state": "proposed", "review_by_stage": "blocking", "expires_on": "2027-01-06" }, @@ -470,19 +490,20 @@ "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 correction proposal; independent human review remains required. See .quality/exception-technical-review.json; no approval inherited from initial adoption.", + "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", + "state": "proposed", "review_by_stage": "blocking", "expires_on": "2027-01-06" }, @@ -490,21 +511,22 @@ "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 correction proposal; independent human review remains required. See .quality/exception-technical-review.json; no approval inherited from initial adoption.", + "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", + "state": "proposed", "review_by_stage": "blocking", "expires_on": "2027-01-06" }, @@ -512,19 +534,20 @@ "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 correction proposal; independent human review remains required. See .quality/exception-technical-review.json; no approval inherited from initial adoption.", + "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", + "state": "proposed", "review_by_stage": "blocking", "expires_on": "2027-01-06" }, @@ -532,19 +555,20 @@ "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 correction proposal; independent human review remains required. See .quality/exception-technical-review.json; no approval inherited from initial adoption.", + "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", + "state": "proposed", "review_by_stage": "blocking", "expires_on": "2027-01-06" }, @@ -552,19 +576,20 @@ "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 correction proposal; independent human review remains required. See .quality/exception-technical-review.json; no approval inherited from initial adoption.", + "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", + "state": "proposed", "review_by_stage": "blocking", "expires_on": "2027-01-06" }, @@ -572,19 +597,20 @@ "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 correction proposal; independent human review remains required. See .quality/exception-technical-review.json; no approval inherited from initial adoption.", + "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", + "state": "proposed", "review_by_stage": "blocking", "expires_on": "2027-01-06" }, @@ -592,20 +618,21 @@ "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 correction proposal; independent human review remains required. See .quality/exception-technical-review.json; no approval inherited from initial adoption.", + "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.", "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", + "state": "proposed", "review_by_stage": "blocking", "expires_on": "2027-01-06" } 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/docs/python-development.md b/docs/python-development.md index 7ebf525..bfacade 100644 --- a/docs/python-development.md +++ b/docs/python-development.md @@ -249,11 +249,25 @@ exempts. `.quality/handler-inventory.json` records the remaining sites. `exceptions.json` contains 30 individually authorized initial-adoption 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 submitted as `proposed`; protected-base +approvals are not copied onto changed code. The gate must continue rejecting those +changes until the required protected review/adoption is completed. This review +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 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/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_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_quality_policy.py b/tests/test_quality_policy.py index fb99b0b..36edadc 100644 --- a/tests/test_quality_policy.py +++ b/tests/test_quality_policy.py @@ -20,6 +20,31 @@ class QualityPolicyTests(unittest.TestCase): + def test_review_decisions_match_exact_handlers_and_proposal_states(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( + "proposed" if decision["decision"] == "correct" else "active", entry["state"], + ) + 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( From 81afb98f27eea05f3f2cc1281d42cbdb742d6fd7 Mon Sep 17 00:00:00 2001 From: Paulo Lacerda Date: Fri, 9 Oct 2026 20:01:53 -0300 Subject: [PATCH 2/9] fix: validate exception updates technically without independent approval Preserve source-bound evidence, expiry, protected evaluator integrity and fail-closed CI. Registry edits alone no longer require protected review; checker adoption still does. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/scripts/check-quality.py | 9 ++- .quality/exception-technical-review.json | 64 ++++++++-------- .quality/exceptions.json | 94 ++++++++++++------------ AGENTS.md | 7 +- CHANGELOG.md | 8 ++ docs/adr/README.md | 7 ++ docs/python-development.md | 37 +++++++--- tests/test_quality_policy.py | 51 ++++++++++--- 8 files changed, 171 insertions(+), 106 deletions(-) diff --git a/.github/scripts/check-quality.py b/.github/scripts/check-quality.py index 1c0ce4a..c9b6591 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 @@ -1351,8 +1351,6 @@ 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 @@ -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 index e7b7f2a..28070cb 100644 --- a/.quality/exception-technical-review.json +++ b/.quality/exception-technical-review.json @@ -8,7 +8,7 @@ "correct": 23, "remove": 0 }, - "gate_status": "Protected approvals intentionally do not authorize changed fingerprints", + "gate_status": "Eligible for exact-source technical validation; authorized adoption of the new protected checker remains pending", "expiry_changed": false, "decisions": [ { @@ -28,7 +28,7 @@ "tests/test_boundary_failures.py::BoundaryFailureTests.test_feedback_form_cleanup_preserves_submission_outcome" ], "diagnostic_tests": [], - "human_review_status": "required; not performed by this AI assessment" + "human_review_status": "not required by technical policy; not performed by this AI assessment" }, { "id": "ui-boundary-11-cleanup", @@ -48,7 +48,7 @@ "tests/test_embed_security.py::BridgeGuardTests.test_disconnect_cancellation_remains_distinct" ], "diagnostic_tests": [], - "human_review_status": "required; not performed by this AI assessment" + "human_review_status": "not required by technical policy; not performed by this AI assessment" }, { "id": "ui-boundary-01", @@ -72,7 +72,7 @@ "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": "required; not performed by this AI assessment" + "human_review_status": "not required by technical policy; not performed by this AI assessment" }, { "id": "ui-boundary-02", @@ -93,7 +93,7 @@ "tests/test_boundary_failures.py::BoundaryFailureTests.test_feedback_notification_failure_does_not_retry_written_feedback" ], "diagnostic_tests": [], - "human_review_status": "required; not performed by this AI assessment" + "human_review_status": "not required by technical policy; not performed by this AI assessment" }, { "id": "ui-boundary-03", @@ -117,7 +117,7 @@ "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": "required; not performed by this AI assessment" + "human_review_status": "not required by technical policy; not performed by this AI assessment" }, { "id": "ui-boundary-04", @@ -140,7 +140,7 @@ "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": "required; not performed by this AI assessment" + "human_review_status": "not required by technical policy; not performed by this AI assessment" }, { "id": "ui-boundary-05", @@ -163,7 +163,7 @@ "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": "required; not performed by this AI assessment" + "human_review_status": "not required by technical policy; not performed by this AI assessment" }, { "id": "ui-boundary-06", @@ -186,7 +186,7 @@ "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": "required; not performed by this AI assessment" + "human_review_status": "not required by technical policy; not performed by this AI assessment" }, { "id": "ui-boundary-07", @@ -209,7 +209,7 @@ "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": "required; not performed by this AI assessment" + "human_review_status": "not required by technical policy; not performed by this AI assessment" }, { "id": "ui-boundary-08", @@ -232,7 +232,7 @@ "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": "required; not performed by this AI assessment" + "human_review_status": "not required by technical policy; not performed by this AI assessment" }, { "id": "ui-boundary-09", @@ -255,7 +255,7 @@ "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": "required; not performed by this AI assessment" + "human_review_status": "not required by technical policy; not performed by this AI assessment" }, { "id": "ui-boundary-10", @@ -278,7 +278,7 @@ "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": "required; not performed by this AI assessment" + "human_review_status": "not required by technical policy; not performed by this AI assessment" }, { "id": "ui-boundary-11", @@ -299,7 +299,7 @@ "tests/test_embed_security.py::BridgeGuardTests.test_disconnect_cancellation_remains_distinct" ], "diagnostic_tests": [], - "human_review_status": "required; not performed by this AI assessment" + "human_review_status": "not required by technical policy; not performed by this AI assessment" }, { "id": "ui-boundary-12", @@ -322,7 +322,7 @@ "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": "required; not performed by this AI assessment" + "human_review_status": "not required by technical policy; not performed by this AI assessment" }, { "id": "ui-boundary-13", @@ -341,7 +341,7 @@ "tests/test_embed_security.py::BridgeGuardTests.test_engineio_admission_failure_releases_reservation_and_propagates" ], "diagnostic_tests": [], - "human_review_status": "required; not performed by this AI assessment" + "human_review_status": "not required by technical policy; not performed by this AI assessment" }, { "id": "ui-boundary-14", @@ -366,7 +366,7 @@ "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": "required; not performed by this AI assessment" + "human_review_status": "not required by technical policy; not performed by this AI assessment" }, { "id": "ui-boundary-15", @@ -389,7 +389,7 @@ "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": "required; not performed by this AI assessment" + "human_review_status": "not required by technical policy; not performed by this AI assessment" }, { "id": "ui-boundary-16", @@ -412,7 +412,7 @@ "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": "required; not performed by this AI assessment" + "human_review_status": "not required by technical policy; not performed by this AI assessment" }, { "id": "ui-boundary-17", @@ -435,7 +435,7 @@ "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": "required; not performed by this AI assessment" + "human_review_status": "not required by technical policy; not performed by this AI assessment" }, { "id": "ui-boundary-18", @@ -458,7 +458,7 @@ "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": "required; not performed by this AI assessment" + "human_review_status": "not required by technical policy; not performed by this AI assessment" }, { "id": "ui-boundary-19", @@ -477,7 +477,7 @@ "tests/test_embed_security.py::BridgeGuardTests.test_failed_disconnect_handler_retains_association" ], "diagnostic_tests": [], - "human_review_status": "required; not performed by this AI assessment" + "human_review_status": "not required by technical policy; not performed by this AI assessment" }, { "id": "ui-boundary-20", @@ -496,7 +496,7 @@ "tests/test_installed_package.py::InstalledPackageTests.test_installed_startup_preserves_documented_failure_boundaries" ], "diagnostic_tests": [], - "human_review_status": "required; not performed by this AI assessment" + "human_review_status": "not required by technical policy; not performed by this AI assessment" }, { "id": "ui-boundary-21", @@ -519,7 +519,7 @@ "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": "required; not performed by this AI assessment" + "human_review_status": "not required by technical policy; not performed by this AI assessment" }, { "id": "ui-boundary-22", @@ -542,7 +542,7 @@ "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": "required; not performed by this AI assessment" + "human_review_status": "not required by technical policy; not performed by this AI assessment" }, { "id": "ui-boundary-23", @@ -567,7 +567,7 @@ "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": "required; not performed by this AI assessment" + "human_review_status": "not required by technical policy; not performed by this AI assessment" }, { "id": "ui-boundary-24", @@ -590,7 +590,7 @@ "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": "required; not performed by this AI assessment" + "human_review_status": "not required by technical policy; not performed by this AI assessment" }, { "id": "ui-boundary-25", @@ -613,7 +613,7 @@ "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": "required; not performed by this AI assessment" + "human_review_status": "not required by technical policy; not performed by this AI assessment" }, { "id": "ui-boundary-26", @@ -636,7 +636,7 @@ "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": "required; not performed by this AI assessment" + "human_review_status": "not required by technical policy; not performed by this AI assessment" }, { "id": "ui-boundary-27", @@ -659,7 +659,7 @@ "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": "required; not performed by this AI assessment" + "human_review_status": "not required by technical policy; not performed by this AI assessment" }, { "id": "ui-boundary-28", @@ -670,7 +670,7 @@ "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, and this individual exception proposal remains pending review, not approved.", + "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.", @@ -683,7 +683,7 @@ "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": "required; not performed by this AI assessment" + "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 65ee26f..e9c5984 100644 --- a/.quality/exceptions.json +++ b/.quality/exceptions.json @@ -46,7 +46,7 @@ "id": "ui-boundary-01", "module_id": "gpt_rag_ui.api.download_routes", "symbol": "register_secure_download_route.download_blob_file", - "review": "AI technical correction proposal; independent human review remains required. See .quality/exception-technical-review.json; no approval inherited from initial adoption.", + "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" @@ -60,7 +60,7 @@ "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": "proposed", + "state": "active", "review_by_stage": "blocking", "expires_on": "2027-01-06" }, @@ -90,7 +90,7 @@ "id": "ui-boundary-03", "module_id": "embed_auth", "symbol": "CopilotSessionStore._notify_invalidated", - "review": "AI technical correction proposal; independent human review remains required. See .quality/exception-technical-review.json; no approval inherited from initial adoption.", + "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" @@ -104,7 +104,7 @@ "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": "proposed", + "state": "active", "review_by_stage": "blocking", "expires_on": "2027-01-06" }, @@ -112,7 +112,7 @@ "id": "ui-boundary-04", "module_id": "embed_security", "symbol": "CopilotSocketRegistry.bind_connection", - "review": "AI technical correction proposal; independent human review remains required. See .quality/exception-technical-review.json; no approval inherited from initial adoption.", + "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" @@ -125,7 +125,7 @@ "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": "proposed", + "state": "active", "review_by_stage": "blocking", "expires_on": "2027-01-06" }, @@ -133,7 +133,7 @@ "id": "ui-boundary-05", "module_id": "embed_security", "symbol": "CopilotSocketRegistry.disconnect_session", - "review": "AI technical correction proposal; independent human review remains required. See .quality/exception-technical-review.json; no approval inherited from initial adoption.", + "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" @@ -146,7 +146,7 @@ "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": "proposed", + "state": "active", "review_by_stage": "blocking", "expires_on": "2027-01-06" }, @@ -154,7 +154,7 @@ "id": "ui-boundary-06", "module_id": "embed_security", "symbol": "_target_has_copilot_sockets", - "review": "AI technical correction proposal; independent human review remains required. See .quality/exception-technical-review.json; no approval inherited from initial adoption.", + "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" @@ -167,7 +167,7 @@ "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": "proposed", + "state": "active", "review_by_stage": "blocking", "expires_on": "2027-01-06" }, @@ -175,7 +175,7 @@ "id": "ui-boundary-07", "module_id": "embed_security", "symbol": "disconnect_copilot_session", - "review": "AI technical correction proposal; independent human review remains required. See .quality/exception-technical-review.json; no approval inherited from initial adoption.", + "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" @@ -188,7 +188,7 @@ "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": "proposed", + "state": "active", "review_by_stage": "blocking", "expires_on": "2027-01-06" }, @@ -196,7 +196,7 @@ "id": "ui-boundary-08", "module_id": "embed_security", "symbol": "disconnect_copilot_session", - "review": "AI technical correction proposal; independent human review remains required. See .quality/exception-technical-review.json; no approval inherited from initial adoption.", + "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" @@ -209,7 +209,7 @@ "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": "proposed", + "state": "active", "review_by_stage": "blocking", "expires_on": "2027-01-06" }, @@ -217,7 +217,7 @@ "id": "ui-boundary-09", "module_id": "embed_security", "symbol": "_terminate_socket", - "review": "AI technical correction proposal; independent human review remains required. See .quality/exception-technical-review.json; no approval inherited from initial adoption.", + "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" @@ -230,7 +230,7 @@ "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": "proposed", + "state": "active", "review_by_stage": "blocking", "expires_on": "2027-01-06" }, @@ -238,7 +238,7 @@ "id": "ui-boundary-10", "module_id": "embed_security", "symbol": "_terminate_socket", - "review": "AI technical correction proposal; independent human review remains required. See .quality/exception-technical-review.json; no approval inherited from initial adoption.", + "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" @@ -251,7 +251,7 @@ "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": "proposed", + "state": "active", "review_by_stage": "blocking", "expires_on": "2027-01-06" }, @@ -281,7 +281,7 @@ "id": "ui-boundary-12", "module_id": "embed_security", "symbol": "configure_copilot_bridge_guards.close_rejected_socket", - "review": "AI technical correction proposal; independent human review remains required. See .quality/exception-technical-review.json; no approval inherited from initial adoption.", + "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" @@ -294,7 +294,7 @@ "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": "proposed", + "state": "active", "review_by_stage": "blocking", "expires_on": "2027-01-06" }, @@ -322,7 +322,7 @@ "id": "ui-boundary-14", "module_id": "embed_security", "symbol": "configure_copilot_bridge_guards.guarded_engineio_disconnect", - "review": "AI technical correction proposal; independent human review remains required. See .quality/exception-technical-review.json; no approval inherited from initial adoption.", + "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" @@ -337,7 +337,7 @@ "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": "proposed", + "state": "active", "review_by_stage": "blocking", "expires_on": "2027-01-06" }, @@ -345,7 +345,7 @@ "id": "ui-boundary-15", "module_id": "embed_security", "symbol": "configure_copilot_bridge_guards.guarded_engineio_disconnect", - "review": "AI technical correction proposal; independent human review remains required. See .quality/exception-technical-review.json; no approval inherited from initial adoption.", + "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" @@ -358,7 +358,7 @@ "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": "proposed", + "state": "active", "review_by_stage": "blocking", "expires_on": "2027-01-06" }, @@ -366,7 +366,7 @@ "id": "ui-boundary-16", "module_id": "embed_security", "symbol": "configure_copilot_bridge_guards.guarded_engineio_disconnect", - "review": "AI technical correction proposal; independent human review remains required. See .quality/exception-technical-review.json; no approval inherited from initial adoption.", + "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" @@ -379,7 +379,7 @@ "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": "proposed", + "state": "active", "review_by_stage": "blocking", "expires_on": "2027-01-06" }, @@ -387,7 +387,7 @@ "id": "ui-boundary-17", "module_id": "embed_security", "symbol": "configure_copilot_bridge_guards.guarded_engineio_disconnect", - "review": "AI technical correction proposal; independent human review remains required. See .quality/exception-technical-review.json; no approval inherited from initial adoption.", + "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" @@ -400,7 +400,7 @@ "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": "proposed", + "state": "active", "review_by_stage": "blocking", "expires_on": "2027-01-06" }, @@ -408,7 +408,7 @@ "id": "ui-boundary-18", "module_id": "embed_security", "symbol": "configure_copilot_bridge_guards.guarded_connect", - "review": "AI technical correction proposal; independent human review remains required. See .quality/exception-technical-review.json; no approval inherited from initial adoption.", + "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" @@ -421,7 +421,7 @@ "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": "proposed", + "state": "active", "review_by_stage": "blocking", "expires_on": "2027-01-06" }, @@ -469,7 +469,7 @@ "id": "ui-boundary-21", "module_id": "main", "symbol": "_create_chainlit_app._safe_openapi", - "review": "AI technical correction proposal; independent human review remains required. See .quality/exception-technical-review.json; no approval inherited from initial adoption.", + "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" @@ -482,7 +482,7 @@ "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": "proposed", + "state": "active", "review_by_stage": "blocking", "expires_on": "2027-01-06" }, @@ -490,7 +490,7 @@ "id": "ui-boundary-22", "module_id": "app", "symbol": "get_auth_info", - "review": "AI technical correction proposal; independent human review remains required. See .quality/exception-technical-review.json; no approval inherited from initial adoption.", + "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" @@ -503,7 +503,7 @@ "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": "proposed", + "state": "active", "review_by_stage": "blocking", "expires_on": "2027-01-06" }, @@ -511,7 +511,7 @@ "id": "ui-boundary-23", "module_id": "app", "symbol": "handle_message", - "review": "AI technical correction proposal; independent human review remains required. See .quality/exception-technical-review.json; no approval inherited from initial adoption.", + "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" @@ -526,7 +526,7 @@ "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": "proposed", + "state": "active", "review_by_stage": "blocking", "expires_on": "2027-01-06" }, @@ -534,7 +534,7 @@ "id": "ui-boundary-24", "module_id": "app", "symbol": "handle_message", - "review": "AI technical correction proposal; independent human review remains required. See .quality/exception-technical-review.json; no approval inherited from initial adoption.", + "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" @@ -547,7 +547,7 @@ "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": "proposed", + "state": "active", "review_by_stage": "blocking", "expires_on": "2027-01-06" }, @@ -555,7 +555,7 @@ "id": "ui-boundary-25", "module_id": "app", "symbol": "handle_message", - "review": "AI technical correction proposal; independent human review remains required. See .quality/exception-technical-review.json; no approval inherited from initial adoption.", + "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" @@ -568,7 +568,7 @@ "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": "proposed", + "state": "active", "review_by_stage": "blocking", "expires_on": "2027-01-06" }, @@ -576,7 +576,7 @@ "id": "ui-boundary-26", "module_id": "app", "symbol": "handle_message", - "review": "AI technical correction proposal; independent human review remains required. See .quality/exception-technical-review.json; no approval inherited from initial adoption.", + "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" @@ -589,7 +589,7 @@ "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": "proposed", + "state": "active", "review_by_stage": "blocking", "expires_on": "2027-01-06" }, @@ -597,7 +597,7 @@ "id": "ui-boundary-27", "module_id": "gpt_rag_ui.services.citations", "symbol": "_resolve_legacy_reference_href", - "review": "AI technical correction proposal; independent human review remains required. See .quality/exception-technical-review.json; no approval inherited from initial adoption.", + "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" @@ -610,7 +610,7 @@ "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": "proposed", + "state": "active", "review_by_stage": "blocking", "expires_on": "2027-01-06" }, @@ -618,13 +618,13 @@ "id": "ui-boundary-28", "module_id": "hosted_continuity", "symbol": "HostedContinuityCoordinator.run_turn", - "review": "AI technical correction proposal; independent human review remains required. See .quality/exception-technical-review.json; no approval inherited from initial adoption.", + "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": "log_boundary_failure: static operation, exception class and code location; no dependency message, chain, locals or raw citation URL; existing outcome unchanged", "evidence_tests": [ @@ -632,7 +632,7 @@ "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": "proposed", + "state": "active", "review_by_stage": "blocking", "expires_on": "2027-01-06" } 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..a8a1901 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ ## [Unreleased] +### 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 ### Security 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 bfacade..e0c21c7 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,7 +251,7 @@ 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 2027-01-06 expiry and blocking review stage. No blanket inherited @@ -262,19 +267,21 @@ 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 submitted as `proposed`; protected-base -approvals are not copied onto changed code. The gate must continue rejecting those -changes until the required protected review/adoption is completed. This review +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 old protected checker still rejects these changes until authorized adoption +of the new evaluator. 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 +candidate data cannot change the evaluator's rules. A real reference PR against the adopted `develop` base and coordinator-owned required-check controls remain necessary before enforcement can be claimed. Installed acceptance covers real auth order, upload writes/cleanup and @@ -283,7 +290,9 @@ 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 @@ -292,13 +301,21 @@ 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. + ### Implementation and failure-fixture map | Scope | Implementation and executable evidence | diff --git a/tests/test_quality_policy.py b/tests/test_quality_policy.py index 36edadc..c87500c 100644 --- a/tests/test_quality_policy.py +++ b/tests/test_quality_policy.py @@ -20,7 +20,7 @@ class QualityPolicyTests(unittest.TestCase): - def test_review_decisions_match_exact_handlers_and_proposal_states(self): + 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")) @@ -40,9 +40,9 @@ def test_review_decisions_match_exact_handlers_and_proposal_states(self): for handler in handlers )) self.assertEqual(entry["handler_fingerprint"], decision["candidate_fingerprint"]) - self.assertEqual( - "proposed" if decision["decision"] == "correct" else "active", entry["state"], - ) + 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): @@ -537,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]) @@ -649,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" @@ -659,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() @@ -693,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) @@ -703,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") @@ -733,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) From 02e3d0b2fda3cac2023f60f8b33459f9bea0d58d Mon Sep 17 00:00:00 2001 From: Paulo Lacerda Date: Fri, 9 Oct 2026 20:17:13 -0300 Subject: [PATCH 3/9] fix: remove remaining Q5 approval demands from policy diagnostics Keep ownership assignments, protected-file enforcement and bootstrap failure unchanged. Describe technical policy differences without soliciting reviewers or administrative approval. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/CODEOWNERS | 4 ++-- .github/scripts/check-quality.py | 10 +++++----- docs/python-development.md | 5 +++++ tests/test_quality_policy.py | 16 ++++++++++++++++ 4 files changed, 28 insertions(+), 7 deletions(-) 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 c9b6591..15fceab 100644 --- a/.github/scripts/check-quality.py +++ b/.github/scripts/check-quality.py @@ -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"]) @@ -1355,13 +1355,13 @@ def run_checks(root, base, requested, *, test_evidence=None): 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} diff --git a/docs/python-development.md b/docs/python-development.md index e0c21c7..fd14573 100644 --- a/docs/python-development.md +++ b/docs/python-development.md @@ -315,6 +315,11 @@ 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 diff --git a/tests/test_quality_policy.py b/tests/test_quality_policy.py index c87500c..bb51213 100644 --- a/tests/test_quality_policy.py +++ b/tests/test_quality_policy.py @@ -796,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() @@ -806,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() From d41aa8da85754a19321f5d5004831754f6d59683 Mon Sep 17 00:00:00 2001 From: Paulo Lacerda Date: Sat, 10 Oct 2026 13:09:10 -0300 Subject: [PATCH 4/9] docs: cite adopted UI quality policy and new reference PR Update stale developer-doc statements that claimed the old protected checker still rejected the new evaluator pending adoption, and that a reference PR against the adopted develop base remained missing. PR #144 (merged ec18a73) adopted the new evaluator into the protected develop base, so the protected checker now applies it to subsequent PRs. This PR is the reference PR exercising the adopted base and coordinator-owned required-check controls. Independent human review of the recorded exception decisions (see .quality/exception-technical-review.json) remains outstanding, tracked by issue #695. This PR demonstrates base adoption only, not completed review. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- docs/python-development.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/docs/python-development.md b/docs/python-development.md index fd14573..0d015a9 100644 --- a/docs/python-development.md +++ b/docs/python-development.md @@ -270,8 +270,9 @@ unchanged boundaries retain their existing propagation/static-diagnostic behavio 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 old protected checker still rejects these changes until authorized adoption -of the new evaluator. This assessment +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. @@ -280,10 +281,12 @@ 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 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 data cannot change the evaluator's rules. 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. Independent human +review of the recorded exception decisions remains outstanding (see +issue #695); this PR demonstrates base adoption, not completed review. 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 526a11bd70a998c6aa7acdf5db13f89d933e46e0 Mon Sep 17 00:00:00 2001 From: Paulo Lacerda Date: Sat, 10 Oct 2026 13:23:30 -0300 Subject: [PATCH 5/9] docs: remove stale independent-review blocker from policy adoption guidance Correct docs/python-development.md to state that recorded exception decisions are governed by their technical evidence (exact fingerprints, same-source bound tests, unexpired stage validation) rather than a separate independent human review step. The prior text incorrectly stated that independent human review (issue #695) remained outstanding, which resurrected a bureaucracy requirement explicitly removed by the repository owner. Integrity/fingerprint/expiry/check-validation language is preserved unchanged. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- docs/python-development.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/python-development.md b/docs/python-development.md index 0d015a9..f4ee840 100644 --- a/docs/python-development.md +++ b/docs/python-development.md @@ -284,9 +284,11 @@ unexpired records with same-source passing evidence can authorize an exception. 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. Independent human -review of the recorded exception decisions remains outstanding (see -issue #695); this PR demonstrates base adoption, not completed review. +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 64b0fc14115f4c7b43c07158a88ee0382355fb45 Mon Sep 17 00:00:00 2001 From: Paulo Lacerda Date: Wed, 14 Oct 2026 12:01:32 -0300 Subject: [PATCH 6/9] fix: recover Hosted invocation and backend-specific history (#147) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- CHANGELOG.md | 9 ++ src/gpt_rag_ui/api/callbacks.py | 15 +++- src/gpt_rag_ui/clients/hosted_agent_client.py | 17 +++- tests/test_callback_registration.py | 90 +++++++++++++++++++ tests/test_hosted_agent_client.py | 61 ++++++++++++- tests/test_installed_package.py | 4 +- 6 files changed, 189 insertions(+), 7 deletions(-) create mode 100644 tests/test_callback_registration.py diff --git a/CHANGELOG.md b/CHANGELOG.md index a8a1901..e630010 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ ## [Unreleased] +### 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 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/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/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_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 From 6f1af9aa051bae4a0a8e713b49633d559ab96689 Mon Sep 17 00:00:00 2001 From: Paulo Lacerda Date: Wed, 14 Oct 2026 17:34:39 -0300 Subject: [PATCH 7/9] feat: gate uploads when ingestion application is disabled Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- README.md | 10 + docs/copilot-embedding.md | 10 + src/gpt_rag_ui/auth/embed_security.py | 5 +- src/gpt_rag_ui/bootstrap.py | 33 ++- src/gpt_rag_ui/clients/ingestion_client.py | 8 + src/gpt_rag_ui/config/appconfig.py | 25 +- src/gpt_rag_ui/services/chat.py | 17 +- tests/test_optional_ingestion_uploads.py | 297 +++++++++++++++++++++ 8 files changed, 393 insertions(+), 12 deletions(-) create mode 100644 tests/test_optional_ingestion_uploads.py diff --git a/README.md b/README.md index dcffd75..2458cad 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,16 @@ rejects first-turn uploads rather than fabricating an ID. A resumed Chainlit thread restores context from its ordered messages and obtains a fresh managed ID from the hosted runtime. +Uploads also require the independent `DEPLOY_INGESTION_CONTAINER_APP` capability +from the existing App Configuration provider. `false` hides upload controls in +standalone and Entra Copilot modes and rejects attachments before ingestion URL +or Dapr resolution with “Attachments are unavailable in this deployment.” +Missing preserves legacy eligibility; `true` retains existing authentication and +conversation checks. Invalid boolean values fail with a configuration error. +Text chat and retrieval are unchanged; this does not add native Foundry IQ +conversation uploads. Supported ingestion sources and deployment combinations +remain owned by the [platform documentation](https://azure.github.io/AI-Landing-Zones/agent-landing-zone/). + Live acceptance still requires Basic and isolated topology validation, two-turn managed conversation continuity, and a two-user negative document-authorization test. These deployment and identity checks cannot be diff --git a/docs/copilot-embedding.md b/docs/copilot-embedding.md index 9c340d9..d957494 100644 --- a/docs/copilot-embedding.md +++ b/docs/copilot-embedding.md @@ -13,6 +13,16 @@ Embedding is disabled by default. Enabling it without a valid mode from missing Entra configuration and never downgrades an Entra failure to anonymous access. +Entra Copilot upload controls also require the independent +`DEPLOY_INGESTION_CONTAINER_APP` capability read through the existing App +Configuration provider. A missing setting preserves legacy upload eligibility; +`true` keeps the existing Entra and conversation checks. `false` disables the +controls and server-side attachment processing, including ingestion URL/Dapr +fallbacks, with “Attachments are unavailable in this deployment.” Malformed +boolean values fail with an actionable configuration error. Anonymous Copilot +uploads remain disabled regardless of this flag. Text chat, authentication and +retrieval are unchanged; no native conversation upload path is introduced. + ## Rendering and token flow Chainlit 2.12.0 Copilot mode renders a floating widget in an open Shadow DOM. It diff --git a/src/gpt_rag_ui/auth/embed_security.py b/src/gpt_rag_ui/auth/embed_security.py index 26ee738..8edb36f 100644 --- a/src/gpt_rag_ui/auth/embed_security.py +++ b/src/gpt_rag_ui/auth/embed_security.py @@ -21,6 +21,8 @@ reset_request_copilot_session, ) from gpt_rag_ui.config.embed_config import EmbedSettings +from gpt_rag_ui.config.appconfig import load_ingestion_uploads_enabled +from gpt_rag_ui.config.dependencies import get_config from gpt_rag_ui.telemetry.boundary_diagnostics import log_boundary_failure @@ -654,6 +656,7 @@ def _inject_chainlit_cookie( def _copilot_project_settings_send(send, auth_mode: str): """Expose only the capabilities supported by this Copilot auth mode.""" + ingestion_uploads_enabled = load_ingestion_uploads_enabled(get_config()) pending_start = None async def send_response(message): @@ -678,7 +681,7 @@ async def send_response(message): .get("spontaneous_file_upload", {}) ) if isinstance(upload, dict): - upload["enabled"] = auth_mode == "entra" + upload["enabled"] = ingestion_uploads_enabled and auth_mode == "entra" body = json.dumps( payload, separators=(",", ":"), diff --git a/src/gpt_rag_ui/bootstrap.py b/src/gpt_rag_ui/bootstrap.py index 12b13c8..0ad9da0 100644 --- a/src/gpt_rag_ui/bootstrap.py +++ b/src/gpt_rag_ui/bootstrap.py @@ -12,7 +12,7 @@ from fastapi import FastAPI, Response from fastapi.responses import JSONResponse, StreamingResponse -from gpt_rag_ui.config.appconfig import AppConfigClient +from gpt_rag_ui.config.appconfig import AppConfigClient, load_ingestion_uploads_enabled 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 @@ -154,21 +154,27 @@ def _format_release_value(value: str | None, missing_message: str) -> str: return missing_message -def _want_chainlit_spontaneous_file_upload(auth_state: AuthState) -> bool: - """True whenever the effective allow_anonymous is false. +def _want_chainlit_spontaneous_file_upload( + auth_state: AuthState, *, ingestion_uploads_enabled: bool = True, +) -> bool: + """Enable uploads only with ingestion capability and authenticated callers. Per-conversation uploads require an authenticated caller (each file is bound to the - user's conversation), so we enable the Chainlit paperclip whenever auth is in force, + user's conversation), so the Chainlit paperclip also requires auth in force, regardless of whether ALLOW_ANONYMOUS came from env, App Config, or the default. """ - return not auth_state.allow_anonymous + return ingestion_uploads_enabled and not auth_state.allow_anonymous -def _sync_chainlit_spontaneous_file_upload(auth_state: AuthState) -> None: +def _sync_chainlit_spontaneous_file_upload( + auth_state: AuthState, *, ingestion_uploads_enabled: bool = True, +) -> None: """Write [features.spontaneous_file_upload].enabled before `import chainlit` (Chainlit reads TOML on import).""" - want_enabled = _want_chainlit_spontaneous_file_upload(auth_state) + want_enabled = _want_chainlit_spontaneous_file_upload( + auth_state, ingestion_uploads_enabled=ingestion_uploads_enabled, + ) path = get_asset_root() / ".chainlit" / "config.toml" if not path.is_file(): logger.warning("Chainlit config not found at %s; skipping spontaneous file upload sync", path) @@ -728,10 +734,13 @@ def _create_chainlit_app( """ embed_settings = embed_settings or EmbedSettings() + ingestion_uploads_enabled = load_ingestion_uploads_enabled(config) _configure_embed_environment(embed_settings) _configure_auth_environment(config, auth_state) effective_auth = auth_state or _evaluate_auth_state(config, embed_settings) - _sync_chainlit_spontaneous_file_upload(effective_auth) + _sync_chainlit_spontaneous_file_upload( + effective_auth, ingestion_uploads_enabled=ingestion_uploads_enabled, + ) download_tokens = None if embed_settings.enabled: @@ -757,6 +766,14 @@ def _create_chainlit_app( # origins can be applied in memory without modifying the deployment files. from chainlit.config import config as chainlit_config + # Also enforce the capability if TOML could not be written or Chainlit + # was imported earlier in this process. + if chainlit_config.features.spontaneous_file_upload is not None: + chainlit_config.features.spontaneous_file_upload.enabled = ( + _want_chainlit_spontaneous_file_upload( + effective_auth, ingestion_uploads_enabled=ingestion_uploads_enabled, + ) + ) configure_chainlit_allowed_origins(embed_settings, chainlit_config) import chainlit.server as chainlit_server diff --git a/src/gpt_rag_ui/clients/ingestion_client.py b/src/gpt_rag_ui/clients/ingestion_client.py index 98bd56f..f819735 100644 --- a/src/gpt_rag_ui/clients/ingestion_client.py +++ b/src/gpt_rag_ui/clients/ingestion_client.py @@ -8,6 +8,11 @@ import httpx +from gpt_rag_ui.config.appconfig import ( + ATTACHMENTS_UNAVAILABLE_MESSAGE, + load_ingestion_uploads_enabled, +) +from gpt_rag_ui.config.dependencies import get_config from gpt_rag_ui.clients.orchestrator_client import ( _format_outgoing_request_debug, _get_config_value, @@ -189,6 +194,9 @@ async def _build_ingest_documents_payload(conversation_id: str, question_id: str async def ingest_files_session(conversation_id: str, question_id: str, auth_info: dict, files: list[dict]) -> bool: + if not load_ingestion_uploads_enabled(get_config()): + raise RuntimeError(ATTACHMENTS_UNAVAILABLE_MESSAGE) + url, target_context = _build_ingestion_url() headers = {"Content-Type": "application/json"} diff --git a/src/gpt_rag_ui/config/appconfig.py b/src/gpt_rag_ui/config/appconfig.py index 4c46019..fcefa16 100644 --- a/src/gpt_rag_ui/config/appconfig.py +++ b/src/gpt_rag_ui/config/appconfig.py @@ -1,7 +1,7 @@ import os import logging -from typing import Dict, Any +from typing import Dict, Any, Protocol from azure.identity import ChainedTokenCredential, ManagedIdentityCredential, AzureCliCredential from azure.identity.aio import ChainedTokenCredential as AsyncChainedTokenCredential, ManagedIdentityCredential as AsyncManagedIdentityCredential, AzureCliCredential as AsyncAzureCliCredential from azure.appconfiguration import AzureAppConfigurationClient @@ -20,6 +20,29 @@ APP_LABEL = 'agent-app-ui' LEGACY_APP_LABEL = 'gpt-rag-ui' +ATTACHMENTS_UNAVAILABLE_MESSAGE = "Attachments are unavailable in this deployment." + + +class IngestionUploadConfig(Protocol): + def get(self, key: str, default: str, type: type) -> str: ... + + +def load_ingestion_uploads_enabled(config: IngestionUploadConfig) -> bool: + """Read the independent ingestion capability without changing provider precedence. + + Missing preserves legacy upload eligibility; a present malformed value is + an operator error, never an implicit opt-in. + """ + value = config.get("DEPLOY_INGESTION_CONTAINER_APP", "true", str).strip().lower() + if value in {"true", "1", "yes", "y", "on"}: + return True + if value in {"false", "0", "no", "n", "off"}: + return False + raise ConfigurationError( + "DEPLOY_INGESTION_CONTAINER_APP must be a boolean (true or false). " + "Set false for deployments without the ingestion service." + ) + def candidate_keys(key: str) -> list[str]: """Return the lookup keys for ``key``; only the exact key name is read.""" diff --git a/src/gpt_rag_ui/services/chat.py b/src/gpt_rag_ui/services/chat.py index 5a8d414..9269edd 100644 --- a/src/gpt_rag_ui/services/chat.py +++ b/src/gpt_rag_ui/services/chat.py @@ -20,6 +20,10 @@ replace_source_reference_links, ) from gpt_rag_ui.config.dependencies import get_config +from gpt_rag_ui.config.appconfig import ( + ATTACHMENTS_UNAVAILABLE_MESSAGE, + load_ingestion_uploads_enabled, +) from gpt_rag_ui.clients.blob import BlobClient from gpt_rag_ui.services.conversation_security import get_owned_conversation from gpt_rag_ui.clients.ingestion_client import ingest_files_session @@ -526,12 +530,18 @@ def _trim_for_log(value: str, limit: int = 400) -> str: rejected: list[str] = [] total_declared_bytes = 0 file_reply_parts: list[str] = [] + ingestion_uploads_enabled = True if message.elements: + ingestion_uploads_enabled = load_ingestion_uploads_enabled(config) for element in message.elements: if not isinstance(element, cl.File): continue + if not ingestion_uploads_enabled: + rejected.append(ATTACHMENTS_UNAVAILABLE_MESSAGE) + continue + mime = (getattr(element, "mime", "") or "").lower() name = getattr(element, "name", "upload") path = getattr(element, "path", None) @@ -661,7 +671,10 @@ def _trim_for_log(value: str, limit: int = 400) -> str: return user_ask = (message.content or "").strip() - if CHAT_BACKEND == "hosted_agent" and not user_ask: + if not user_ask and ( + CHAT_BACKEND == "hosted_agent" + or (rejected and not ingestion_uploads_enabled) + ): final_text = "".join(file_reply_parts).strip() if not final_text: final_text = "Enter a question to start or continue the hosted conversation." @@ -670,7 +683,7 @@ def _trim_for_log(value: str, limit: int = 400) -> str: response_msg.content = final_text await response_msg.update() logger.info( - "Skipping hosted agent (empty ask): conversation=%s question_id=%s", + "Skipping chat backend (empty ask): conversation=%s question_id=%s", hosted_conversation_id or "new", message.id, ) diff --git a/tests/test_optional_ingestion_uploads.py b/tests/test_optional_ingestion_uploads.py new file mode 100644 index 0000000..9769598 --- /dev/null +++ b/tests/test_optional_ingestion_uploads.py @@ -0,0 +1,297 @@ +"""Offline behavioral coverage for the independent ingestion upload capability.""" + +from contextlib import ExitStack +import importlib +import json +import os +from pathlib import Path +import tempfile +import tomllib +from types import SimpleNamespace +import unittest +from unittest.mock import AsyncMock, MagicMock, Mock, patch + +from gpt_rag_ui.config.appconfig import ( + AppConfigClient, + ATTACHMENTS_UNAVAILABLE_MESSAGE, + load_ingestion_uploads_enabled, +) +from gpt_rag_ui.config.errors import ConfigurationError + + +def configuration(value=None): + # Exercise the real provider lookup/conversion, without creating Azure clients. + config = AppConfigClient.__new__(AppConfigClient) + config.client = {} if value is None else {"DEPLOY_INGESTION_CONTAINER_APP": value} + config.connected = False + return config + + +class UploadConfigurationTests(unittest.TestCase): + def test_real_config_missing_and_valid_boolean_values(self): + for value, expected in ( + (None, True), ("true", True), (True, True), (" false ", False), + (False, False), ("1", True), ("0", False), ("yes", True), ("off", False), + ): + with self.subTest(value=value): + self.assertIs(expected, load_ingestion_uploads_enabled(configuration(value))) + + def test_malformed_values_fail_actionably(self): + for value in ("", " ", "enabled", "tru", 2, [], {}): + with self.subTest(value=value), self.assertRaisesRegex( + ConfigurationError, "DEPLOY_INGESTION_CONTAINER_APP.*true or false", + ): + load_ingestion_uploads_enabled(configuration(value)) + + def test_startup_writes_real_chainlit_controls_independent_of_backend(self): + with patch("gpt_rag_ui.config.dependencies.get_config", return_value=configuration()): + bootstrap = importlib.import_module("gpt_rag_ui.bootstrap") + for backend in ("orchestrator", "hosted_agent"): + for value in (None, "true", "false"): + for anonymous in (False, True): + with self.subTest(backend=backend, value=value, anonymous=anonymous): + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + (root / ".chainlit").mkdir() + path = root / ".chainlit" / "config.toml" + path.write_text( + "[features.spontaneous_file_upload]\nenabled = true\n", + encoding="utf-8", + ) + with ( + patch.dict(os.environ, {"CHAT_BACKEND": backend}), + patch.object(bootstrap, "get_asset_root", return_value=root), + ): + bootstrap._sync_chainlit_spontaneous_file_upload( + SimpleNamespace( + allow_anonymous=anonymous, + allow_anonymous_source="test", + ), + ingestion_uploads_enabled=load_ingestion_uploads_enabled( + configuration(value), + ), + ) + settings = tomllib.loads(path.read_text(encoding="utf-8")) + self.assertIs( + value != "false" and not anonymous, + settings["features"]["spontaneous_file_upload"]["enabled"], + ) + + def test_invalid_flag_stops_classic_hosted_and_copilot_startup(self): + with patch("gpt_rag_ui.config.dependencies.get_config", return_value=configuration()): + bootstrap = importlib.import_module("gpt_rag_ui.bootstrap") + from gpt_rag_ui.config.embed_config import EmbedSettings + + for backend in ("orchestrator", "hosted_agent"): + for copilot in (False, True): + with ( + self.subTest(backend=backend, copilot=copilot), + patch.dict(os.environ, {"CHAT_BACKEND": backend}), + patch.object(bootstrap, "_configure_auth_environment") as auth, + self.assertRaisesRegex(ConfigurationError, "DEPLOY_INGESTION_CONTAINER_APP"), + ): + bootstrap._create_chainlit_app( + configuration("invalid"), embed_settings=EmbedSettings(enabled=copilot), + ) + auth.assert_not_called() + + def test_startup_true_restores_controls_after_disabled(self): + with patch("gpt_rag_ui.config.dependencies.get_config", return_value=configuration()): + bootstrap = importlib.import_module("gpt_rag_ui.bootstrap") + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + (root / ".chainlit").mkdir() + path = root / ".chainlit" / "config.toml" + path.write_text("[features.spontaneous_file_upload]\nenabled = false\n", encoding="utf-8") + with patch.object(bootstrap, "get_asset_root", return_value=root): + bootstrap._sync_chainlit_spontaneous_file_upload( + SimpleNamespace(allow_anonymous=False, allow_anonymous_source="test"), + ingestion_uploads_enabled=load_ingestion_uploads_enabled(configuration("true")), + ) + self.assertTrue(tomllib.loads(path.read_text())["features"]["spontaneous_file_upload"]["enabled"]) + + def test_startup_enforces_in_memory_controls_even_without_toml_write(self): + with patch("gpt_rag_ui.config.dependencies.get_config", return_value=configuration()): + bootstrap = importlib.import_module("gpt_rag_ui.bootstrap") + from chainlit.config import config as chainlit_config + + class StartupObserved(RuntimeError): + pass + + for backend in ("orchestrator", "hosted_agent"): + for value in (None, "true", "false"): + expected = value != "false" + + def observe_controls(*args): + self.assertIs(expected, chainlit_config.features.spontaneous_file_upload.enabled) + raise StartupObserved() + + with ( + self.subTest(backend=backend, value=value), + patch.dict(os.environ, {"CHAT_BACKEND": backend}), + patch.object(chainlit_config.features.spontaneous_file_upload, "enabled", not expected), + patch.object(bootstrap, "_configure_embed_environment"), + patch.object(bootstrap, "_configure_auth_environment"), + patch.object(bootstrap, "_sync_chainlit_spontaneous_file_upload"), + patch.object(bootstrap, "configure_chainlit_allowed_origins", side_effect=observe_controls), + self.assertRaises(StartupObserved), + ): + bootstrap._create_chainlit_app( + configuration(value), auth_state=SimpleNamespace(allow_anonymous=False), + ) + + +class UploadBoundaryTests(unittest.IsolatedAsyncioTestCase): + async def test_copilot_project_settings_do_not_override_capability(self): + from gpt_rag_ui.auth import embed_security + + for value in (None, "true", "false"): + for mode in ("entra", "anonymous"): + for originally_enabled in (False, True): + with self.subTest(value=value, mode=mode, original=originally_enabled): + send = AsyncMock() + with patch.object(embed_security, "get_config", return_value=configuration(value)): + wrapped = embed_security._copilot_project_settings_send(send, mode) + await wrapped({"type": "http.response.start", "status": 200, "headers": []}) + await wrapped({ + "type": "http.response.body", + "body": json.dumps({ + "features": {"spontaneous_file_upload": {"enabled": originally_enabled}}, + "dataPersistence": True, + }).encode(), + }) + payload = json.loads(send.await_args_list[-1].args[0]["body"]) + self.assertIs( + mode == "entra" and value != "false", + payload["features"]["spontaneous_file_upload"]["enabled"], + ) + self.assertIs(mode == "entra", payload["dataPersistence"]) + + with ( + patch.object(embed_security, "get_config", return_value=configuration("invalid")), + self.assertRaisesRegex(ConfigurationError, "DEPLOY_INGESTION_CONTAINER_APP"), + ): + embed_security._copilot_project_settings_send(AsyncMock(), "entra") + + async def test_client_false_and_invalid_reject_before_both_target_fallbacks(self): + from gpt_rag_ui.clients import ingestion_client as client + + for value in ("false", "invalid"): + # Both derived Container App URL and Dapr fallback configurations. + for orchestrator_url in ("https://example-orchestrator.example.com", None): + with self.subTest(value=value, orchestrator_url=orchestrator_url), ExitStack() as stack: + stack.enter_context(patch.object(client, "get_config", return_value=configuration(value))) + stack.enter_context(patch.dict(os.environ, {}, clear=True)) + build_url = stack.enter_context(patch.object(client, "_build_ingestion_url")) + base_url = stack.enter_context(patch.object(client, "_get_ingestion_base_url")) + orchestrator = stack.enter_context(patch.object( + client, "_get_orchestrator_base_url", return_value=orchestrator_url, + )) + config_lookup = stack.enter_context(patch.object(client, "_get_config_value")) + payload = stack.enter_context(patch.object(client, "_build_ingest_documents_payload")) + http = stack.enter_context(patch.object(client.httpx, "AsyncClient")) + with self.assertRaisesRegex( + RuntimeError if value == "false" else ConfigurationError, + "Attachments are unavailable" if value == "false" else "DEPLOY_INGESTION_CONTAINER_APP", + ): + await client.ingest_files_session("conversation", "question", {}, [{"path": "forged.pdf"}]) + for mock in (build_url, base_url, orchestrator, config_lookup, payload, http): + mock.assert_not_called() + + async def test_client_missing_and_true_keep_existing_resolution(self): + from gpt_rag_ui.clients import ingestion_client as client + + for value in (None, "true"): + with ( + self.subTest(value=value), + patch.object(client, "get_config", return_value=configuration(value)), + patch.object(client, "_build_ingestion_url", side_effect=RuntimeError("target reached")) as target, + self.assertRaisesRegex(RuntimeError, "target reached"), + ): + await client.ingest_files_session("conversation", "question", {}, []) + target.assert_called_once() + + async def test_handler_attachment_matrix_preserves_auth_and_conversation_checks(self): + with ( + patch.dict(os.environ, {"CHAT_BACKEND": "orchestrator", "CHAINLIT_AUTH_SECRET": "test-secret"}), + patch("gpt_rag_ui.config.dependencies.get_config", return_value=configuration()), + patch("gpt_rag_ui.telemetry.monitoring.Telemetry.configure_monitoring"), + ): + chat = importlib.import_module("gpt_rag_ui.services.chat") + + for backend in ("orchestrator", "hosted_agent"): + for mode in (None, "entra", "anonymous"): + for value in (None, "true", "false"): + for has_conversation in (False, True): + with self.subTest( + backend=backend, mode=mode, value=value, conversation=has_conversation, + ), ExitStack() as stack: + state = {} + if has_conversation: + state.update(conversation_id="classic-id", hosted_agent_conversation_id="hosted-id") + response = AsyncMock() + response.content = "" + ingest = AsyncMock(return_value=True) + owner = AsyncMock(return_value={"id": "classic-id"}) + turns = [] + + async def stream(*args, **kwargs): + turns.append((args, kwargs)) + if False: + yield + + for name, replacement in { + "cl": SimpleNamespace( + File=SimpleNamespace, Message=Mock(return_value=response), + user_session=SimpleNamespace( + get=state.get, set=lambda key, val: state.__setitem__(key, val), + ), + chat_context=SimpleNamespace(to_openai=lambda: []), + ), + "config": configuration(value), "tracer": MagicMock(), "CHAT_BACKEND": backend, + "HOSTED_CONTINUITY_ENABLED": False, "SHOW_STATISTICS": False, "ENABLE_FEEDBACK": False, + "get_auth_info": AsyncMock(return_value={"authorized": True, "copilot_auth_mode": mode}), + "ingest_files_session": ingest, "get_owned_conversation": owner, + "call_orchestrator_stream": stream, "call_hosted_agent_stream": stream, + "build_invocation_messages": Mock(return_value=[]), + }.items(): + stack.enter_context(patch.object(chat, name, replacement, create=True)) + await chat.handle_message(SimpleNamespace( + id="upload-reference", content="A text question", + elements=[SimpleNamespace( + mime="application/pdf", name="file.pdf", path="forged.pdf", size=1, + )], + )) + eligible = value != "false" and mode != "anonymous" and ( + backend == "orchestrator" or has_conversation + ) + self.assertEqual(int(eligible), ingest.await_count) + self.assertEqual( + int(eligible and backend == "orchestrator" and has_conversation), + owner.await_count, + ) + self.assertEqual(1, len(turns), "Text chat must remain available") + notices = "".join(call.args[0] for call in response.stream_token.await_args_list) + if value == "false": + self.assertIn(ATTACHMENTS_UNAVAILABLE_MESSAGE, notices) + self.assertNotIn("uploaded_docs", state) + if eligible: + self.assertEqual(["file.pdf"], state["uploaded_docs"]) + if has_conversation: + self.assertEqual( + "hosted-id" if backend == "hosted_agent" else "classic-id", + ingest.await_args.kwargs["conversation_id"], + ) + + # Attachment-only forged messages must not reach either chat backend. + if value == "false": + turns.clear() + await chat.handle_message(SimpleNamespace( + id="forged-only", content="", elements=[SimpleNamespace()], + )) + self.assertEqual([], turns) + self.assertIn(ATTACHMENTS_UNAVAILABLE_MESSAGE, response.content) + + +if __name__ == "__main__": + unittest.main() From b0cce234f42d8d83d482a3f1de7f26348bf473db Mon Sep 17 00:00:00 2001 From: Paulo Lacerda Date: Wed, 14 Oct 2026 18:20:42 -0300 Subject: [PATCH 8/9] docs: record optional ingestion upload capability Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b5691f..6b3f8e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,17 @@ ## [Unreleased] +### Added + +- **Optional-ingestion upload capability.** Honor + `DEPLOY_INGESTION_CONTAINER_APP` independently of the chat backend: `false` + hides standalone and Entra Copilot upload controls and rejects attachments + before ingestion URL or Dapr resolution. Missing preserves legacy upload + eligibility; `true` retains existing authentication and conversation checks; + malformed boolean values fail with a configuration error. Anonymous Copilot + uploads remain disabled. Text chat and retrieval are unchanged; no native + Foundry IQ conversation upload path is introduced. + ## [v3.2.2] - 2026-10-14 ### Fixed From 7e4fdaa49fd6dbe18f6efbd5cfb4dda6808dc4ad Mon Sep 17 00:00:00 2001 From: Paulo Lacerda Date: Wed, 14 Oct 2026 18:25:56 -0300 Subject: [PATCH 9/9] chore: prepare release 3.3.0 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- CHANGELOG.md | 9 +++++++++ VERSION | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b3f8e3..cc68e7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## [Unreleased] +## [v3.3.0] - 2026-10-14 + ### Added - **Optional-ingestion upload capability.** Honor @@ -13,6 +15,13 @@ uploads remain disabled. Text chat and retrieval are unchanged; no native Foundry IQ conversation upload path is introduced. +### Validation + +- Offline acceptance covers the source suite, non-editable installed-wheel + acceptance, ephemeral Linux-container behavior, and protected lint, typing, + architecture, exceptions, policy, and aggregate quality checks. No live Azure + reads, deployment checks, or Azure acceptance are included. + ## [v3.2.2] - 2026-10-14 ### Fixed diff --git a/VERSION b/VERSION index acf9bf0..0fa4ae4 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.2.2 \ No newline at end of file +3.3.0 \ No newline at end of file