From b4d013582da9dca1fd3c0c615a6ccaa08ae11ac3 Mon Sep 17 00:00:00 2001
From: THOTH
Date: Sat, 3 Oct 2026 04:30:00 +0900
Subject: [PATCH] First-run model connection, evidence-centered results,
interruption handling and public scope cleanup
- First-run setup: ChatGPT sign-in, API keys and model catalog refresh with clearer connection states.
- Result screen: criteria coverage, evidence use, confirmed findings, criterion changes and per-result usage.
- Model calls: interruption display, resume reuse, runaway-output guard; automatic retry of cut-off calls is off by default.
- Memory: automatic recall narrowed; user corrections stay recallable.
- Scope lookups keep their data cache but authorize each traversal, so the ancestry depth limit holds.
- Stream observations keep canonical numeric values, including missing ones.
- Unused external credential-store adapters removed; private planning and development notes are no longer tracked.
Validation: Python full run 2,135 passed / 40 failed / 1 skipped / 1 expected failure; the 40 were fixed and rerun separately (39 passed, 1 baseline recovery case now a strict expected failure, see docs/VERIFICATION.md). Web 409 passed / 1 skipped; lint, type checks, build and first-setup smoke passed.
---
.github/workflows/product-check.yml | 84 +
.gitignore | 84 +-
AGENTS.md | 10 +-
.../30_ARCHITECTURE/current-ocp-gaps.md | 124 -
.../behavioral-acceptance-contracts.md | 257 -
.../implementation-maturity-matrix.md | 162 -
PROJECT_WIKI/NOW.md | 44 -
README.ko.md | 34 +-
README.md | 38 +-
SOURCE_MANIFEST.json | 1577 +-
apps/web/src/App.tsx | 2 +-
.../api/fixtures/iris-judgment-excerpt.json | 287 +
apps/web/src/api/history.ts | 4 +-
apps/web/src/api/historyModels.ts | 11 +
apps/web/src/api/historySchemas.ts | 6 +
apps/web/src/api/research.ts | 17 +-
apps/web/src/api/researchFollowup.ts | 19 +
apps/web/src/api/researchStatusRead.test.ts | 46 +
apps/web/src/api/researchStatusRead.ts | 20 +
apps/web/src/api/rpcClient.ts | 3 +-
apps/web/src/api/sourceTime.ts | 9 +
.../web/src/components/ActionCompare.test.tsx | 94 +
apps/web/src/components/ActionCompare.tsx | 60 +
.../components/ActionCompareApproval.test.tsx | 49 +
apps/web/src/components/ActionEffort.test.tsx | 84 +
apps/web/src/components/ActionEffort.tsx | 87 +
apps/web/src/components/AnswerBody.tsx | 20 +
.../src/components/AnswerCitations.test.tsx | 109 +
apps/web/src/components/AnswerHold.tsx | 27 +
apps/web/src/components/ApprovalCard.test.tsx | 125 +
apps/web/src/components/ApprovalCard.tsx | 49 +
apps/web/src/components/ApprovalSection.tsx | 51 +
.../src/components/AutoRetrySwitch.test.tsx | 49 +
apps/web/src/components/AutoRetrySwitch.tsx | 32 +
.../components/CapabilityWorkbench.test.tsx | 1 +
apps/web/src/components/ClaudeCodeHelp.tsx | 9 +
apps/web/src/components/CodexToolHelp.tsx | 9 +
.../src/components/ConfirmedFacts.test.tsx | 60 +
apps/web/src/components/ConfirmedFacts.tsx | 52 +
.../src/components/ConversationTimeline.tsx | 18 +-
apps/web/src/components/EvidenceRail.tsx | 29 +-
.../web/src/components/EvidenceUseSummary.tsx | 19 +
.../src/components/FileConnectionPanel.tsx | 3 +-
.../web/src/components/FirstRunSetup.test.tsx | 422 +-
apps/web/src/components/FirstRunSetup.tsx | 95 +-
.../src/components/HypothesisCompare.test.tsx | 113 +
apps/web/src/components/HypothesisCompare.tsx | 57 +
.../src/components/HypothesisCompareLive.tsx | 46 +
.../src/components/HypothesisReview.test.tsx | 110 +
.../src/components/LiveProjectWorkspace.tsx | 42 +-
.../LiveProjectWorkspaceRestart.test.tsx | 96 +-
apps/web/src/components/MemoryEditDialog.tsx | 38 +
.../src/components/MemoryInjectionSwitch.tsx | 31 +
.../src/components/ModelCredentialPanel.tsx | 11 +-
.../web/src/components/ModelSettings.test.tsx | 66 +-
apps/web/src/components/ModelSettings.tsx | 15 +-
apps/web/src/components/OAuthLoginFlow.tsx | 90 +-
.../components/ProjectMemoryPanel.test.tsx | 214 +
.../web/src/components/ProjectMemoryPanel.tsx | 69 +
.../web/src/components/ResearchDetailPane.tsx | 20 +-
.../components/ResearchFollowupSummary.tsx | 78 +-
apps/web/src/components/ResearchHold.test.tsx | 77 +
.../components/ResearchResultCard.test.tsx | 54 +-
.../web/src/components/ResearchResultCard.tsx | 51 +-
.../src/components/ResultEvidencePanel.tsx | 19 +-
apps/web/src/components/ResultExample.tsx | 18 +-
apps/web/src/components/ResultScreen.test.tsx | 143 +
apps/web/src/components/ReviewDialog.tsx | 40 +
apps/web/src/components/ReviewStatusLine.tsx | 20 +
apps/web/src/components/StatusBadge.tsx | 3 +-
apps/web/src/components/ToolInstallHelp.tsx | 61 +
apps/web/src/components/XaiDeviceLogin.tsx | 28 +-
apps/web/src/components/answerText.test.ts | 43 +
apps/web/src/components/answerText.ts | 53 +
apps/web/src/components/approvalView.ts | 58 +
apps/web/src/components/connectionCopy.ts | 117 +
apps/web/src/components/effortEstimates.ts | 8 +
apps/web/src/components/evidenceUse.ts | 28 +
.../history/CriteriaChange.test.tsx | 65 +
.../src/components/history/CriteriaChange.tsx | 65 +
.../history/DecisionDeltaComparison.tsx | 10 +-
.../history/HistoryChangeSummary.test.tsx | 73 +
.../history/HistoryChangeSummary.tsx | 44 +
.../src/components/history/HistoryDetail.tsx | 7 +-
.../components/history/HistoryTimeline.tsx | 2 +
.../components/history/ProjectReviewList.tsx | 4 +-
.../history/ResearchHistoryWorkspace.tsx | 3 +-
.../components/history/historyPresentation.ts | 3 +
apps/web/src/components/hypothesisView.ts | 56 +
apps/web/src/components/judgmentReview.ts | 57 +
apps/web/src/components/memoryEdit.ts | 50 +
.../modelCatalogPresentation.test.ts | 36 +
.../components/modelCatalogPresentation.ts | 56 +
.../components/modelCredentialPresentation.ts | 32 +-
apps/web/src/components/resultUsage.ts | 32 +
apps/web/src/components/statusLabels.test.tsx | 45 +
apps/web/src/components/statusLabels.ts | 127 +
apps/web/src/components/useResearchSession.ts | 33 +-
apps/web/src/sourceDisplay.test.ts | 6 +
apps/web/src/sourceDisplay.ts | 4 +-
apps/web/src/types.ts | 1 +
apps/web/src/workstation.css | 61 +-
config/atomicity-paths.json | 142380 ++++++++-------
config/inactive-development-hook-tests.json | 297 +-
config/module-responsibility-budget.json | 10 +-
docs/INSTALL.md | 28 +-
docs/PACKAGING.md | 13 +-
docs/PUBLICATION_REVIEW.md | 11 +
docs/VERIFICATION.md | 40 +
docs/architecture/extension-point-catalog.md | 90 +-
docs/architecture/model-auth.md | 51 +-
docs/architecture/model-settings.md | 6 +-
docs/architecture/public-web-acquisition.md | 2 +-
.../research-context-completion-contract.md | 36 +
.../architecture}/rpc-method-catalog.md | 14 +-
docs/architecture/verification-profiles.md | 107 +-
schemas/protocol/decision-delta.schema.json | 129 +
schemas/protocol/history-detail.schema.json | 94 +
schemas/protocol/history-page.schema.json | 94 +
schemas/protocol/public-method-catalog.json | 10432 +-
scripts/audit_canonical_drift.py | 20 +-
scripts/audit_design_coverage.py | 2 +-
scripts/audit_full_product_coverage.py | 5 +-
scripts/build_public_method_catalog.py | 43 +-
scripts/check_acceptance_coverage.py | 11 +-
scripts/check_public_protocol_parity.py | 3 +-
scripts/check_truth_drift.py | 17 +-
scripts/memory_selection_metrics.py | 182 +
scripts/memory_selection_replay.py | 290 +
scripts/required_architecture_checks.py | 2 -
src/thoth/adapters/http/hosted_review.py | 2 +
src/thoth/adapters/models/auth_registry.py | 6 +
src/thoth/adapters/models/catalog.py | 75 +-
src/thoth/adapters/models/catalog_marks.py | 111 +
src/thoth/adapters/models/catalog_startup.py | 115 +
src/thoth/adapters/models/catalog_store.py | 77 +
src/thoth/adapters/models/claude_catalog.py | 2 +-
src/thoth/adapters/models/claude_code.py | 380 +-
.../adapters/models/claude_code_login.py | 414 +
.../adapters/models/claude_code_tooling.py | 78 +
src/thoth/adapters/models/claude_messages.py | 99 +-
src/thoth/adapters/models/claude_oauth.py | 4 +-
src/thoth/adapters/models/codex_broker.py | 299 +-
src/thoth/adapters/models/codex_http.py | 23 +-
src/thoth/adapters/models/codex_oauth.py | 55 +-
src/thoth/adapters/models/codex_profile.py | 14 +
src/thoth/adapters/models/codex_tooling.py | 66 +
.../adapters/models/local_credentials.py | 171 +-
src/thoth/adapters/models/omo_native.py | 171 -
src/thoth/adapters/models/receive_stats.py | 21 +
src/thoth/adapters/models/sse_stream.py | 4 +
src/thoth/adapters/models/stream_pace.py | 146 +
src/thoth/adapters/models/tool_installers.py | 130 +
.../adapters/models/windows_process_job.py | 26 +
src/thoth/adapters/models/xai_catalog.py | 21 -
src/thoth/adapters/models/xai_oauth.py | 107 +-
src/thoth/adapters/parsers/pdf_parser.py | 39 +-
.../application/commands/action_effort.py | 166 +
.../application/commands/actions_full.py | 49 +-
.../application/commands/judgment_review.py | 119 +
.../application/commands/memory_edits.py | 57 +
.../application/commands/memory_revisions.py | 132 +
.../application/commands/memory_settings.py | 54 +
.../commands/model_call_settings.py | 57 +
.../application/commands/model_settings.py | 123 +-
.../application/commands/research_followup.py | 5 +
.../application/commands/research_threads.py | 146 +-
src/thoth/application/commands/sources.py | 7 +
.../application/services/action_compiler.py | 2 +
.../services/action_currentness.py | 9 +-
.../application/services/action_projection.py | 23 +-
.../application/services/action_service.py | 77 +-
.../services/authorization_currentness.py | 179 +
.../services/authorization_prepare.py | 99 +
.../services/cycle_memory_preparation.py | 58 +
.../services/domain_reference_memory.py | 15 +-
.../services/execution_frontier.py | 3 +-
.../application/services/execution_service.py | 12 +-
.../services/full_project_memory.py | 421 +-
.../services/history_change_summary.py | 248 +
.../services/history_projection.py | 48 +-
.../services/judgment_review_service.py | 305 +
.../services/memory_edit_service.py | 248 +
.../application/services/memory_recall.py | 280 +
.../application/services/memory_relation.py | 82 +
.../services/memory_relation_judge.py | 70 +
.../services/memory_relation_resolver.py | 264 +
.../services/memory_revision_seal.py | 108 +
.../application/services/memory_safety.py | 19 +
.../application/services/memory_settings.py | 50 +
.../application/services/memory_shape.py | 168 +
.../application/services/memory_summary.py | 44 +
.../services/memory_supersession.py | 36 +
.../services/model_call_settings.py | 50 +
.../application/services/model_outcomes.py | 36 +
.../services/post_execution_memory.py | 7 +-
.../application/services/research_analysis.py | 82 +-
.../services/research_attempt_runner.py | 45 +-
.../services/research_commit_scope.py | 42 +
.../services/research_completion_view.py | 12 +
.../services/research_criterion_delta.py | 86 +
.../application/services/research_findings.py | 43 +
.../services/research_followup_projection.py | 51 +-
.../services/research_freshness.py | 18 +
.../services/research_identity_service.py | 22 +-
.../services/research_model_hold.py | 43 +
.../services/research_read_summary.py | 68 +
.../services/research_stage_reuse.py | 71 +
.../application/services/research_stages.py | 161 +-
.../services/research_thread_read_snapshot.py | 54 +-
.../application/services/research_usage.py | 102 +-
.../services/resource_scope_read_context.py | 16 +-
.../application/services/revision_service.py | 16 +-
.../application/services/running_research.py | 18 +
.../application/workflows/thread_cycle.py | 105 +-
src/thoth/apps/hosted_review_composition.py | 2 +
src/thoth/apps/judgment_review_composition.py | 50 +
src/thoth/apps/memory_composition.py | 65 +-
src/thoth/apps/outcome_receipt_routes.py | 39 +
src/thoth/apps/projectpack_execution.py | 2 +-
src/thoth/apps/research_entry_composition.py | 100 +-
.../apps/research_history_composition.py | 18 +-
src/thoth/apps/runtime.py | 47 +-
src/thoth/domain/action.py | 51 +-
src/thoth/domain/action_full.py | 8 +
src/thoth/domain/effort_bands.py | 175 +
src/thoth/domain/enums.py | 1 +
src/thoth/domain/evidence_requirements.py | 16 +
src/thoth/domain/judgment_review.py | 110 +
src/thoth/domain/memory.py | 28 +
src/thoth/domain/memory_preparation.py | 12 +
src/thoth/domain/memory_relation.py | 66 +
src/thoth/domain/memory_settings.py | 14 +
src/thoth/domain/model_call_settings.py | 20 +
src/thoth/domain/model_catalog.py | 100 +
src/thoth/domain/model_dispatch.py | 26 +-
src/thoth/domain/model_settings.py | 7 +
src/thoth/domain/oauth_retry.py | 31 +-
src/thoth/domain/operation.py | 2 +
src/thoth/domain/protected_action.py | 306 +
src/thoth/domain/research_codec.py | 4 +
src/thoth/domain/research_execution.py | 6 +
src/thoth/domain/research_followup.py | 22 +
src/thoth/domain/research_history.py | 22 +
src/thoth/domain/research_projection.py | 3 +-
src/thoth/domain/research_stage.py | 14 +
src/thoth/domain/revision.py | 2 +
src/thoth/ports/memory.py | 13 +
src/thoth/ports/model_catalog.py | 19 +-
src/thoth/ports/model_tooling.py | 13 +
src/thoth/protocol/bus.py | 5 +-
src/thoth/protocol/registry.py | 10 +
tests/architecture/public_source_profile.py | 34 +-
.../test_atomicity_debt_contract.py | 2 +-
.../test_metadata_rule_transaction.py | 32 +-
.../architecture/test_portable_build_paths.py | 7 +-
.../test_public_protocol_exact_parity.py | 24 +-
.../test_public_source_profile.py | 45 +-
tests/architecture/test_reevaluated_guards.py | 2 +-
.../test_wiki_status_references.py | 11 +-
tests/conftest.py | 19 +-
tests/integration/public_web_test_support.py | 2 +-
tests/integration/readset_helpers.py | 79 +
.../test_a06_full_project_memory.py | 8 +-
.../test_a06_memory_preparation.py | 8 +-
.../test_action_effort_estimates.py | 312 +
.../test_claude_code_login_entry.py | 190 +
.../test_claude_code_normal_entry.py | 190 +
.../test_confirmed_findings_result.py | 115 +
tests/integration/test_full_thread_cycle.py | 10 +-
.../test_history_change_summary_read.py | 48 +
tests/integration/test_judgment_review.py | 201 +
.../test_local_first_run_model_boundary.py | 15 +-
tests/integration/test_memory_edit.py | 584 +
tests/integration/test_memory_selection.py | 549 +
.../test_model_call_hold_failure.py | 72 +
tests/integration/test_model_call_settings.py | 126 +
.../test_model_catalog_refresh_rpc.py | 111 +
.../integration/test_model_catalog_startup.py | 95 +
.../integration/test_model_execution_marks.py | 121 +
.../test_model_settings_cache_only.py | 156 +
tests/integration/test_model_tooling_rpc.py | 89 +
.../test_post_learning_running_recovery.py | 28 +-
.../test_protected_authorization.py | 249 +
.../test_research_assembly_cache_basis.py | 16 +
.../test_research_boundaries_v2.py | 4 +-
.../test_research_cycle_restore.py | 12 +-
.../test_research_final_repairs.py | 4 +-
.../integration/test_research_input_queue.py | 7 +
..._research_memory_correction_currentness.py | 105 +
tests/integration/test_research_readset.py | 267 +
.../test_research_readset_memory.py | 200 +
tests/integration/test_research_request_v2.py | 10 +-
.../test_research_scoped_conflicts.py | 4 +-
.../test_resource_scope_memory_b.py | 3 +-
.../test_resource_scope_memory_preparation.py | 43 +-
.../test_restore_currentness_consumers.py | 7 +-
.../test_result_compare_criteria.py | 117 +
tests/integration/test_result_usage_read.py | 73 +
.../test_resume_interrupted_research.py | 445 +
.../test_retrieval_gap_reassessment.py | 4 +-
tests/integration/test_revision_full_rpc.py | 1060 +-
tests/integration/test_revision_service.py | 27 +
tests/integration/test_scope_read_memo.py | 4 +-
.../test_scope_read_request_memo.py | 90 +
.../test_thread_read_currentness_cost.py | 153 +
.../test_thread_read_summary_view.py | 149 +
.../test_user_activity_thread_read.py | 1 +
tests/parser/test_pdf.py | 110 +-
.../test_research_freshness_memory.py | 109 +
tests/unit/domain/test_effort_bands.py | 178 +
tests/unit/domain/test_oauth_retry_policy.py | 2 +-
tests/unit/domain/test_protected_payload.py | 135 +
.../test_receive_observation_canonical.py | 36 +
tests/unit/models/test_account_connections.py | 158 +-
tests/unit/models/test_action_effort_wire.py | 35 +
tests/unit/models/test_catalog_marks.py | 104 +
.../models/test_catalog_startup_refresh.py | 127 +
tests/unit/models/test_catalog_store.py | 116 +
tests/unit/models/test_claude_code.py | 360 +-
.../models/test_claude_code_executable.py | 139 +
tests/unit/models/test_claude_code_login.py | 313 +
.../models/test_codex_catalog_snapshot.py | 242 +
.../unit/models/test_codex_isolated_broker.py | 72 +-
.../models/test_codex_login_completion.py | 133 +
.../models/test_interrupted_call_retry.py | 288 +
tests/unit/models/test_omo_native.py | 90 -
.../models/test_ranking_reference_contract.py | 7 +-
tests/unit/models/test_stream_pace.py | 303 +
tests/unit/models/test_tool_installers.py | 183 +
tests/unit/models/test_windows_process_job.py | 17 +
tests/unit/models/test_xai_responses.py | 134 +-
.../test_authorization_currentness.py | 142 +
.../services/test_history_change_summary.py | 165 +
.../services/test_judgment_review_service.py | 362 +
.../services/test_retry_model_mismatch.py | 2 +-
tests/unit/test_confirmed_findings.py | 63 +
tests/unit/test_criterion_delta.py | 128 +
tests/unit/test_history_projection_titles.py | 23 +
.../unit/test_hosted_review_credential_rpc.py | 2 +
tests/unit/test_memory_recall.py | 250 +
tests/unit/test_memory_relation.py | 109 +
tests/unit/test_memory_relation_resolver.py | 234 +
tests/unit/test_memory_revision_list.py | 168 +
tests/unit/test_memory_selection_metrics.py | 92 +
tests/unit/test_memory_shape.py | 118 +
tests/unit/test_memory_summary.py | 58 +
tests/unit/test_memory_supersession.py | 47 +
tests/unit/test_research_commit_scope.py | 45 +
tests/unit/test_research_usage.py | 79 +-
tests/unit/test_running_research.py | 51 +
tests/unit/test_scope_read_memo_context.py | 8 +-
352 files changed, 103243 insertions(+), 79238 deletions(-)
create mode 100644 .github/workflows/product-check.yml
delete mode 100644 PROJECT_WIKI/30_ARCHITECTURE/current-ocp-gaps.md
delete mode 100644 PROJECT_WIKI/50_SEED_ROADMAP/behavioral-acceptance-contracts.md
delete mode 100644 PROJECT_WIKI/50_SEED_ROADMAP/implementation-maturity-matrix.md
delete mode 100644 PROJECT_WIKI/NOW.md
create mode 100644 apps/web/src/api/fixtures/iris-judgment-excerpt.json
create mode 100644 apps/web/src/api/researchStatusRead.test.ts
create mode 100644 apps/web/src/api/researchStatusRead.ts
create mode 100644 apps/web/src/components/ActionCompare.test.tsx
create mode 100644 apps/web/src/components/ActionCompare.tsx
create mode 100644 apps/web/src/components/ActionCompareApproval.test.tsx
create mode 100644 apps/web/src/components/ActionEffort.test.tsx
create mode 100644 apps/web/src/components/ActionEffort.tsx
create mode 100644 apps/web/src/components/AnswerBody.tsx
create mode 100644 apps/web/src/components/AnswerCitations.test.tsx
create mode 100644 apps/web/src/components/AnswerHold.tsx
create mode 100644 apps/web/src/components/ApprovalCard.test.tsx
create mode 100644 apps/web/src/components/ApprovalCard.tsx
create mode 100644 apps/web/src/components/ApprovalSection.tsx
create mode 100644 apps/web/src/components/AutoRetrySwitch.test.tsx
create mode 100644 apps/web/src/components/AutoRetrySwitch.tsx
create mode 100644 apps/web/src/components/ClaudeCodeHelp.tsx
create mode 100644 apps/web/src/components/CodexToolHelp.tsx
create mode 100644 apps/web/src/components/ConfirmedFacts.test.tsx
create mode 100644 apps/web/src/components/ConfirmedFacts.tsx
create mode 100644 apps/web/src/components/EvidenceUseSummary.tsx
create mode 100644 apps/web/src/components/HypothesisCompare.test.tsx
create mode 100644 apps/web/src/components/HypothesisCompare.tsx
create mode 100644 apps/web/src/components/HypothesisCompareLive.tsx
create mode 100644 apps/web/src/components/HypothesisReview.test.tsx
create mode 100644 apps/web/src/components/MemoryEditDialog.tsx
create mode 100644 apps/web/src/components/MemoryInjectionSwitch.tsx
create mode 100644 apps/web/src/components/ProjectMemoryPanel.test.tsx
create mode 100644 apps/web/src/components/ProjectMemoryPanel.tsx
create mode 100644 apps/web/src/components/ResearchHold.test.tsx
create mode 100644 apps/web/src/components/ResultScreen.test.tsx
create mode 100644 apps/web/src/components/ReviewDialog.tsx
create mode 100644 apps/web/src/components/ReviewStatusLine.tsx
create mode 100644 apps/web/src/components/ToolInstallHelp.tsx
create mode 100644 apps/web/src/components/answerText.test.ts
create mode 100644 apps/web/src/components/answerText.ts
create mode 100644 apps/web/src/components/approvalView.ts
create mode 100644 apps/web/src/components/connectionCopy.ts
create mode 100644 apps/web/src/components/effortEstimates.ts
create mode 100644 apps/web/src/components/evidenceUse.ts
create mode 100644 apps/web/src/components/history/CriteriaChange.test.tsx
create mode 100644 apps/web/src/components/history/CriteriaChange.tsx
create mode 100644 apps/web/src/components/history/HistoryChangeSummary.test.tsx
create mode 100644 apps/web/src/components/history/HistoryChangeSummary.tsx
create mode 100644 apps/web/src/components/hypothesisView.ts
create mode 100644 apps/web/src/components/judgmentReview.ts
create mode 100644 apps/web/src/components/memoryEdit.ts
create mode 100644 apps/web/src/components/modelCatalogPresentation.test.ts
create mode 100644 apps/web/src/components/modelCatalogPresentation.ts
create mode 100644 apps/web/src/components/resultUsage.ts
create mode 100644 apps/web/src/components/statusLabels.test.tsx
create mode 100644 apps/web/src/components/statusLabels.ts
rename {PROJECT_WIKI/30_ARCHITECTURE => docs/architecture}/rpc-method-catalog.md (90%)
create mode 100644 scripts/memory_selection_metrics.py
create mode 100644 scripts/memory_selection_replay.py
create mode 100644 src/thoth/adapters/models/catalog_marks.py
create mode 100644 src/thoth/adapters/models/catalog_startup.py
create mode 100644 src/thoth/adapters/models/catalog_store.py
create mode 100644 src/thoth/adapters/models/claude_code_login.py
create mode 100644 src/thoth/adapters/models/claude_code_tooling.py
create mode 100644 src/thoth/adapters/models/codex_tooling.py
delete mode 100644 src/thoth/adapters/models/omo_native.py
create mode 100644 src/thoth/adapters/models/stream_pace.py
create mode 100644 src/thoth/adapters/models/tool_installers.py
create mode 100644 src/thoth/application/commands/action_effort.py
create mode 100644 src/thoth/application/commands/judgment_review.py
create mode 100644 src/thoth/application/commands/memory_edits.py
create mode 100644 src/thoth/application/commands/memory_revisions.py
create mode 100644 src/thoth/application/commands/memory_settings.py
create mode 100644 src/thoth/application/commands/model_call_settings.py
create mode 100644 src/thoth/application/services/authorization_currentness.py
create mode 100644 src/thoth/application/services/authorization_prepare.py
create mode 100644 src/thoth/application/services/cycle_memory_preparation.py
create mode 100644 src/thoth/application/services/history_change_summary.py
create mode 100644 src/thoth/application/services/judgment_review_service.py
create mode 100644 src/thoth/application/services/memory_edit_service.py
create mode 100644 src/thoth/application/services/memory_recall.py
create mode 100644 src/thoth/application/services/memory_relation.py
create mode 100644 src/thoth/application/services/memory_relation_judge.py
create mode 100644 src/thoth/application/services/memory_relation_resolver.py
create mode 100644 src/thoth/application/services/memory_revision_seal.py
create mode 100644 src/thoth/application/services/memory_safety.py
create mode 100644 src/thoth/application/services/memory_settings.py
create mode 100644 src/thoth/application/services/memory_shape.py
create mode 100644 src/thoth/application/services/memory_summary.py
create mode 100644 src/thoth/application/services/memory_supersession.py
create mode 100644 src/thoth/application/services/model_call_settings.py
create mode 100644 src/thoth/application/services/model_outcomes.py
create mode 100644 src/thoth/application/services/research_commit_scope.py
create mode 100644 src/thoth/application/services/research_criterion_delta.py
create mode 100644 src/thoth/application/services/research_findings.py
create mode 100644 src/thoth/application/services/research_model_hold.py
create mode 100644 src/thoth/application/services/research_read_summary.py
create mode 100644 src/thoth/application/services/research_stage_reuse.py
create mode 100644 src/thoth/application/services/running_research.py
create mode 100644 src/thoth/apps/judgment_review_composition.py
create mode 100644 src/thoth/apps/outcome_receipt_routes.py
create mode 100644 src/thoth/domain/effort_bands.py
create mode 100644 src/thoth/domain/judgment_review.py
create mode 100644 src/thoth/domain/memory_relation.py
create mode 100644 src/thoth/domain/memory_settings.py
create mode 100644 src/thoth/domain/model_call_settings.py
create mode 100644 src/thoth/domain/model_catalog.py
create mode 100644 src/thoth/domain/protected_action.py
create mode 100644 src/thoth/ports/model_tooling.py
create mode 100644 tests/integration/readset_helpers.py
create mode 100644 tests/integration/test_action_effort_estimates.py
create mode 100644 tests/integration/test_claude_code_login_entry.py
create mode 100644 tests/integration/test_claude_code_normal_entry.py
create mode 100644 tests/integration/test_confirmed_findings_result.py
create mode 100644 tests/integration/test_history_change_summary_read.py
create mode 100644 tests/integration/test_judgment_review.py
create mode 100644 tests/integration/test_memory_edit.py
create mode 100644 tests/integration/test_memory_selection.py
create mode 100644 tests/integration/test_model_call_hold_failure.py
create mode 100644 tests/integration/test_model_call_settings.py
create mode 100644 tests/integration/test_model_catalog_refresh_rpc.py
create mode 100644 tests/integration/test_model_catalog_startup.py
create mode 100644 tests/integration/test_model_execution_marks.py
create mode 100644 tests/integration/test_model_settings_cache_only.py
create mode 100644 tests/integration/test_model_tooling_rpc.py
create mode 100644 tests/integration/test_protected_authorization.py
create mode 100644 tests/integration/test_research_memory_correction_currentness.py
create mode 100644 tests/integration/test_research_readset.py
create mode 100644 tests/integration/test_research_readset_memory.py
create mode 100644 tests/integration/test_result_compare_criteria.py
create mode 100644 tests/integration/test_result_usage_read.py
create mode 100644 tests/integration/test_resume_interrupted_research.py
create mode 100644 tests/integration/test_scope_read_request_memo.py
create mode 100644 tests/integration/test_thread_read_currentness_cost.py
create mode 100644 tests/integration/test_thread_read_summary_view.py
create mode 100644 tests/unit/application/test_research_freshness_memory.py
create mode 100644 tests/unit/domain/test_effort_bands.py
create mode 100644 tests/unit/domain/test_protected_payload.py
create mode 100644 tests/unit/domain/test_receive_observation_canonical.py
create mode 100644 tests/unit/models/test_action_effort_wire.py
create mode 100644 tests/unit/models/test_catalog_marks.py
create mode 100644 tests/unit/models/test_catalog_startup_refresh.py
create mode 100644 tests/unit/models/test_catalog_store.py
create mode 100644 tests/unit/models/test_claude_code_executable.py
create mode 100644 tests/unit/models/test_claude_code_login.py
create mode 100644 tests/unit/models/test_codex_catalog_snapshot.py
create mode 100644 tests/unit/models/test_codex_login_completion.py
create mode 100644 tests/unit/models/test_interrupted_call_retry.py
delete mode 100644 tests/unit/models/test_omo_native.py
create mode 100644 tests/unit/models/test_stream_pace.py
create mode 100644 tests/unit/models/test_tool_installers.py
create mode 100644 tests/unit/services/test_authorization_currentness.py
create mode 100644 tests/unit/services/test_history_change_summary.py
create mode 100644 tests/unit/services/test_judgment_review_service.py
create mode 100644 tests/unit/test_confirmed_findings.py
create mode 100644 tests/unit/test_criterion_delta.py
create mode 100644 tests/unit/test_history_projection_titles.py
create mode 100644 tests/unit/test_memory_recall.py
create mode 100644 tests/unit/test_memory_relation.py
create mode 100644 tests/unit/test_memory_relation_resolver.py
create mode 100644 tests/unit/test_memory_revision_list.py
create mode 100644 tests/unit/test_memory_selection_metrics.py
create mode 100644 tests/unit/test_memory_shape.py
create mode 100644 tests/unit/test_memory_summary.py
create mode 100644 tests/unit/test_memory_supersession.py
create mode 100644 tests/unit/test_research_commit_scope.py
create mode 100644 tests/unit/test_running_research.py
diff --git a/.github/workflows/product-check.yml b/.github/workflows/product-check.yml
new file mode 100644
index 0000000..cf54074
--- /dev/null
+++ b/.github/workflows/product-check.yml
@@ -0,0 +1,84 @@
+name: Product check
+
+on:
+ pull_request:
+ push:
+ branches: [main]
+
+permissions:
+ contents: read
+
+jobs:
+ product-check:
+ # The documented quick start supports Windows; Linux remains unverified.
+ runs-on: windows-latest
+ timeout-minutes: 180
+ defaults:
+ run:
+ shell: pwsh
+ env:
+ PYTHONUTF8: "1"
+ PLAYWRIGHT_BROWSERS_PATH: ${{ github.workspace }}/.thoth-test-browsers
+ THOTH_WORKSPACE: .thoth-ci
+ THOTH_IRIS_RESULT_FIXTURE: .thoth-ci/no-private-result.json
+ steps:
+ - uses: actions/checkout@v5
+ - uses: actions/setup-python@v6
+ with:
+ python-version: "3.13"
+ - uses: actions/setup-node@v4
+ with:
+ node-version: "24"
+ - uses: pnpm/action-setup@v4
+ with:
+ version: "11.21.0"
+ - uses: astral-sh/setup-uv@v7
+ with:
+ version: "0.12.5"
+ - name: Install locked dependencies
+ run: |
+ python -m venv --copies --without-pip .venv
+ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
+ uv sync --python .\.venv\Scripts\python.exe --no-managed-python --no-python-downloads --frozen --extra dev --dev --extra browser
+ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
+ pnpm install --frozen-lockfile
+ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
+ .\.venv\Scripts\python.exe -m playwright install --only-shell chromium
+ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
+ - name: Python product tests
+ run: .\.venv\Scripts\python.exe -m pytest
+ - name: Python lint
+ run: .\.venv\Scripts\python.exe -m ruff check src apps tests migrations conftest.py scripts/pytest_environment.py
+ - name: Runtime architecture
+ run: .\.venv\Scripts\python.exe scripts/check_architecture.py
+ - name: Web tests
+ run: pnpm --dir apps/web run test --run
+ - name: Web lint
+ run: pnpm --dir apps/web run lint
+ - name: Web types
+ run: pnpm --dir apps/web run typecheck
+ - name: Web build
+ run: pnpm --dir apps/web run build
+ - name: README quick-start server smoke
+ run: |
+ .\.venv\Scripts\python.exe -m thoth.cli workspace
+ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
+ $thothApi = Start-Process .\.venv\Scripts\python.exe -ArgumentList @('-m', 'thoth.cli', 'serve', '--workspace', '.thoth-ci', '--port', '18766') -WindowStyle Hidden -PassThru
+ try {
+ $thothReady = $false
+ for ($thothAttempt = 0; $thothAttempt -lt 30; $thothAttempt++) {
+ try {
+ Invoke-RestMethod http://127.0.0.1:18766/healthz -ErrorAction Stop | Out-Null
+ $thothReady = $true
+ break
+ } catch { Start-Sleep -Seconds 1 }
+ }
+ if (-not $thothReady) { throw 'Quick-start API did not become ready.' }
+ $thothPage = Invoke-WebRequest http://127.0.0.1:18766/
+ if ($thothPage.StatusCode -ne 200 -or $thothPage.Content -notmatch 'type="module"') {
+ throw 'Built Web entry did not load.'
+ }
+ # Web tests and local browser acceptance check the rendered first-run UI.
+ } finally {
+ & taskkill /PID $thothApi.Id /T /F | Out-Null
+ }
diff --git a/.gitignore b/.gitignore
index bb7e5ae..6fbfc00 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,40 +1,44 @@
-.venv/
-__pycache__/
-*.py[cod]
-.pytest_cache/
-.ruff_cache/
-.basedpyright/
-.coverage
-htmlcov/
-dist/
-build/
-*.egg-info/
-.env
-.thoth/
-.thoth-*/
-node_modules/
-apps/web/node_modules/
-apps/web/dist/
-apps/web/*.tsbuildinfo
-apps/web/vite.config.js
-apps/web/vite.config.d.ts
-playwright-report/
-test-results/
-artifacts/qa/
-*.log
-field-validation/private/
-field-validation/results/
-
-# Local research and verification artifacts
-outputs/
-.codex/verification/
-.omo/
-*.db
-*.sqlite
-*.sqlite3
-*.sqlite3-wal
-*.sqlite3-shm
-*.pem
-*.key
-tmp_*
-_deploy_*
+.venv/
+__pycache__/
+*.py[cod]
+.pytest_cache/
+.ruff_cache/
+.basedpyright/
+.coverage
+htmlcov/
+dist/
+build/
+*.egg-info/
+.env
+.thoth/
+.thoth-*/
+node_modules/
+apps/web/node_modules/
+apps/web/dist/
+apps/web/*.tsbuildinfo
+apps/web/vite.config.js
+apps/web/vite.config.d.ts
+playwright-report/
+test-results/
+artifacts/qa/
+*.log
+field-validation/private/
+field-validation/results/
+
+# Local research and verification artifacts
+docs/plans/
+docs/research/
+docs/verification/
+PROJECT_WIKI/
+outputs/
+.codex/verification/
+.omo/
+*.db
+*.sqlite
+*.sqlite3
+*.sqlite3-wal
+*.sqlite3-shm
+*.pem
+*.key
+tmp_*
+_deploy_*
diff --git a/AGENTS.md b/AGENTS.md
index 40e7ae5..00f8bfd 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -14,7 +14,7 @@
## 작업 범위
- 조사·설명·설계 요청은 제품 수정을 허가하지 않는다. 구현 요청에서는 합의한 범위를 끝까지 수행하되, 의미 변경·외부 권한·비용이 필요한 부분만 질문한다.
-- 현재 작업서는 해당 인수조건의 기준이다. PROJECT_WIKI/NOW.md에서 최신 작업을 확인하고, 아래 상세 계약은 변경 영역에 해당하는 것만 읽는다. 과거 완료 보고·기억보다 현재 코드와 해당 source의 실행 증거를 우선한다.
+- 현재 작업서는 해당 인수조건의 기준이다. PROJECT_WIKI와 계획·리서치·과정 기록은 로컬 전용이며 공개 checkout의 필수 입력이 아니다. 아래 공개 상세 계약은 변경 영역에 해당하는 것만 읽는다. 과거 완료 보고·기억보다 현재 코드와 해당 source의 실행 증거를 우선한다.
- 기존 dirty 변경과 사용자 데이터를 보존한다. 검사 실패를 숨기려고 테스트·정책·기준값·예외 허용을 바꾸지 않는다. 변경이 필요하면 이유와 새 계약을 명시한다.
## 제품을 보존할 것
@@ -51,9 +51,11 @@
- 버그 수정은 별도 입력이나 실패 주입으로 재현하고 원하는 동작을 회귀 테스트에 남긴다. 새 기능은 정상 사용자 진입→소비자→저장 결과를 확인한다. 테스트 double의 고정 답은 live 의미 품질 증거가 아니다.
- 현재 적용되는 검증 정책을 따르되, 개발 중에는 영향 범위의 검사부터 수행한다. 동일 source의 살아 있는 검증을 중복 실행하지 않는다. 검사 범위를 줄이려면 기존 정책을 조용히 무시하지 말고 변경안을 명시한다.
+- 개발 단계의 기본 검사는 가볍게 한다. 각 단계에서는 바꾼 코드의 테스트와 해당 언어의 lint(웹 lint·typecheck, Python ruff check·format)만 돌린다. 여러 화면·호출자가 함께 쓰는 컴포넌트나 모듈을 바꿨을 때만 그 소비자 테스트 또는 웹 테스트 전체를 추가한다. 빌드·architecture·basedpyright는 import 경계나 타입 계약을 바꾼 경우에만 돌린다.
+- 병합·공개 전에는 깨끗한 checkout에서 제품 확인(Python 제품 시험 전체, 웹 시험 전체, 웹 lint·typecheck·build, Python ruff check, 계층 검사, README 빠른 시작)을 한 번 실행하고, 결과를 보고 사용자가 병합을 승인한다. 비활성 개발 Hook·과정 점검 시험은 명시 목록에 따라 제외하고 제외 이유와 수를 보고한다. 실패·중단·NOT_RUN을 통과로 표현하지 않는다.
- 실패/중단/NOT_RUN을 정확히 보고한다. 관련 테스트 PASS와 전체 회귀 PASS, controlled D4와 live D5를 구분한다. 파일/RPC/테스트 개수를 완성률로 쓰지 않는다.
- step 완료는 key 존재가 아니라 exact input revision·실제 상태 전이·후속 소비로 증명한다. NOT_TRIGGERED/NO_CHANGE/NOT_RUN을 SUCCESS와 구분한다.
-- 인계에는 해결한 기능, 실제 검증, 미완료/차단, 관련 source와 기록 위치를 남긴다. 제품 의미가 바뀌면 정본을 먼저, 해당 Wiki projection을 다음으로 갱신한다. 상태만 바뀌면 NOW/검증 기록만 갱신한다.
+- 인계에는 해결한 기능, 실제 검증, 미완료/차단, 관련 source와 기록 위치를 남긴다. 제품 의미가 바뀌면 정본을 먼저, 해당 Wiki projection을 다음으로 갱신한다. 상태만 바뀌면 NOW/검증 기록만 갱신한다. 이 기록은 커밋·병합·세션 인계처럼 넘기는 지점에서 짧게 남기고, 개발 중간 단계마다 쓰지 않는다.
- 배포·외부 발송·공식 제출·새 credential/비용·물리 장비·commit/push는 사용자의 해당 실행 지시 범위를 따른다. 이 문서 자체는 그 권한을 부여하지 않는다.
## 상세 계약 길잡이
@@ -65,5 +67,5 @@
- registry 계약: docs/architecture/extension-point-catalog.md
- 현재 검증 선택 정책: docs/architecture/verification-profiles.md
- 제품 기능의 고정 중심: research-briefs/CORE_PRODUCT_INVARIANTS.md
-- 정상 진입/성숙도: PROJECT_WIKI/50_SEED_ROADMAP/behavioral-acceptance-contracts.md 및 implementation-maturity-matrix.md
-- 이번 보완 범위: docs/plans/THOTH_POST_AUDIT_REPAIR_SCOPE_20260913.md
+- 공개 RPC 정본: docs/architecture/rpc-method-catalog.md
+- 제품 확인 결과와 한계: docs/VERIFICATION.md
diff --git a/PROJECT_WIKI/30_ARCHITECTURE/current-ocp-gaps.md b/PROJECT_WIKI/30_ARCHITECTURE/current-ocp-gaps.md
deleted file mode 100644
index 77c7fe8..0000000
--- a/PROJECT_WIKI/30_ARCHITECTURE/current-ocp-gaps.md
+++ /dev/null
@@ -1,124 +0,0 @@
----
-id: CURRENT-OCP-GAPS
-status: ACTIVE
-page_role: implementation_gap_projection
-authority: ../../research-briefs/CANONICAL_RND_EVIDENCE_HARNESS_DESIGN.md
-last_updated: 2026-09-06
----
-
-# Current OCP Gaps
-
-이 페이지는 canonical OCP 설계와 현재 local reference prototype 사이의 구체적인 차이를 기록한다.
-Architecture PASS나 RPC parity를 전체 OCP 준수로 해석하지 않는다.
-
-현재 사실과 남은 normal-loop 간극은 [2026-09-06 교정](current-truth-and-next-gap-20260906.md)을 따른다.
-마지막 A11 completion은 PASS다. 중첩 경로 오탐은 닫혔지만 읽기 전용 inline interpreter를
-mutation으로 분류하는 보수적 경계는 남아 있다. Hook 파일·검사 통과만으로 모든 작업의 실제
-Hook 신뢰·실행이나 모든 기능의 의미적 완성을 인증하지 않는다.
-
-## 확인된 준수
-
-- `application -> adapters` 직접 import 0건
-- domain/application core의 Hero·ProjectPack·provider 이름 분기 0건
-- model, parser, connector, sandbox, Criterion Profile, ProjectPack, migration port/registry 존재
-- canonical owner 13개, Acceptance 13개, ratcheted exception 0, migration single head
-- 현재 정상 진입점 bounded loop와 G01/G02 이후 backend `361 passed` 두 차례
-
-## OCP-CONNECTOR-001 — CLOSED LOCAL
-
-`ConnectorCapability`가 typed selector contract를 제공하고 `ConnectorRegistry.route`가 exact,
-ambiguous, no-match를 판정한다. `ConnectorService`의 selector field와
-`GIT/S3/POSTGRES/MCP/LOCAL` 열거는 제거됐다.
-
-목표 계약:
-
-```text
-ConnectorCapability가 selector schema·source kind·priority를 제공
-→ ConnectorRegistry가 후보를 계산
-→ application은 connector 이름을 알지 않고 exact/ambiguous/no-match만 처리
-```
-
-Synthetic REST/CUSTOM Connector의 registry-only route, ambiguous/no-match fail-closed, raw
-credential 우선 차단, 기존 A02/A03/A11/source 회귀와 첫 full backend `366 passed`를 통과했다.
-구조 수정은 기존 A02 D4를 유지하며 credentialed Connector product-loop D5를 뜻하지 않는다.
-
-## OCP-SANDBOX-001 — CLOSED LOCAL
-
-모든 `SandboxPort`가 typed `SandboxCapability`를 제공하며 `SandboxFactoryRegistry`가 adapter ID,
-capability, factory와 runtime version을 함께 등록한다. `create_runtime`의 concrete class-name
-profile map은 제거됐고 factory가 선언과 다른 capability를 반환하면 fail closed한다.
-
-목표 계약:
-
-```text
-SandboxFactoryRegistry가 profile·capability·factory·runtime version을 소유
-→ create_runtime은 선택된 registration만 조합
-→ 새 Sandbox 추가 시 class-name map과 application core 수정 0
-```
-
-Unfamiliar adapter 등록, capability mismatch 거부, A04/A05/A11 회귀, architecture와 첫 full
-backend `368 passed`를 통과했다. 구조 수정은 기존 A04 D4를 유지하며 개별 adapter나 production
-isolation의 새 D5 증거를 뜻하지 않는다.
-
-## OCP REGRESSION GUARDS — ACTIVE
-
-AST 기반 `check_ocp_extensions.py`가 Connector application route의 kind/selector 열거와
-`create_runtime`의 concrete Sandbox class-name routing을 검사한다. 이 검사는 architecture와 새
-preflight 양쪽에 포함되며, deliberate violation fixtures와 현재 코드의 positive path를 모두
-검증한다. `OCP-HERO-001`은 Hero trace 구현과 같은 노드에서 활성화한다.
-
-추가 enforcement:
-
-- `OCP-REGISTRY-001`: 필수 extension registry가 누락되거나 PARTIAL인데 debt owner가 없으면 실패
-- `OCP-FACTORY-001`: IMPLEMENTED factory target이 실제 source symbol이 아니면 실패
-- `OCP-CORE-CLOSED-001`: application이 concrete adapter 이름을 import·branch하면 실패
-- `OCP-DOC-DRIFT-001`: NOW·maturity·OCP projection이 닫힌 항목을 다시 OPEN으로 쓰거나 debt를 숨기면 실패
-
-Parser/Connector/Sandbox의 factory target은 이제 정확한 module의 concrete registry이며,
-기존 `ParserRegistryPort`/`ConnectorRegistryPort`/`SandboxFactoryRegistryPort` signature와
-실제 registration/composition 및 runtime consumer에 결속된다. Known-string 검사는 제한적 회귀
-방지이며 전체 Python OCP의 완전 정적 증명이 아니다. A11 최종 backend 462/Web/doctor 및 receipt
-`3cbf8ae09482a6ac8ec4a6e90f80161ebab9c82fe7d1994755d0403274cc08e5`를 따른다.
-Store는 `AtomicUnitOfWorkPort`가 있으나
-namespace 전체 원자성은 아직 PARTIAL이다.
-
-## Canonical atomicity debt — OPEN
-
-```text
-ATOMICITY DEBT: 10 OPEN
-```
-
-현재 owner 13개 중 3개만 `ATOMIC`이고, 9개는 `PARTIAL`, Improvement는
-`MATERIALIZED_ONLY`다. 총 10개 non-atomic owner는 각각 debt ID, owning Acceptance와 필요한 fault
-injection evidence를 manifest에 가진다. 이 등록은 구현 완료나 예외 승인이 아니라 숨겨지지 않는
-다음 작업 목록이다.
-
-## HERO-TRACE-001 — CLOSED LOCAL
-
-Hero는 fresh workspace-scoped `TuiSessionService`에서 `/project`·`/thread` 없이 자연어 한 번으로
-진입한다. Project-scoped SQLite operation store에서 TUI method·operation ID·scope digest·state를 읽어
-`thread/input` 1회, 수동 semantic RPC 0회와 trace digest를 계산한다.
-
-목표 계약:
-
-```text
-Operation journal/CommandBus trace에서 실제 호출 method 관측
-→ thread/input 정확히 1회
-→ 금지된 수동 semantic RPC 조립 0회
-→ manifest는 관측값에서 생성
-```
-
-Sealed Hero focused 회귀와 첫 full backend `378 passed`를 통과했다. Architecture의
-`OCP-HERO-001`은 해당 manifest 값을 literal로 자기기입하면 실패한다. Fresh live OAuth 시연과 D5
-갱신은 별도 실행 증거다.
-
-## 현재 판정
-
-마지막 workflow guard 보완은 중첩 경로 오판과 NOW/OCP/maturity 현재 부채 표시를 검증했다.
-Architecture117·full backend676/Web/doctor PASS, 최종 완료 영수증은 pending이다.
-상세는 [최종 기록](../../docs/verification/reevaluated-storage-authority-final-20260906.md)을 따른다.
-
-`CORE LAYERING PASS / REGISTRY+FACTORY+CORE-CLOSED GUARDS ACTIVE / ATOMICITY DEBT 10 OPEN`
-
-Connector·Sandbox·Hero OCP 항목은 local code와 regression guard 수준에서 닫혔다. Store와 10개
-atomicity debt, operator-grade TUI, 기관 SSO, Field D6, Production 배포와 전체 UI는 별도 gate다.
diff --git a/PROJECT_WIKI/50_SEED_ROADMAP/behavioral-acceptance-contracts.md b/PROJECT_WIKI/50_SEED_ROADMAP/behavioral-acceptance-contracts.md
deleted file mode 100644
index 2e96e95..0000000
--- a/PROJECT_WIKI/50_SEED_ROADMAP/behavioral-acceptance-contracts.md
+++ /dev/null
@@ -1,257 +0,0 @@
----
-id: BEHAVIORAL-ACCEPTANCE-CONTRACTS
-status: ACTIVE
-page_role: preimplementation_acceptance_contract
-authority: ../../research-briefs/CANONICAL_RND_EVIDENCE_HARNESS_DESIGN.md
-last_updated: 2026-09-07
----
-
-# Behavioral Acceptance Contracts
-
-이 문서는 제품 코드를 쓰기 전에 정상 사용자 시작점, 자동 전이, terminal evidence를 고정한다. 현재는 명세이며 실행 테스트 코드가 아니다. UI는 범위에서 제외한다.
-
-## 공통 완료 규칙
-
-각 scenario는 다음을 모두 증명해야 D4 이상이다.
-
-```text
-normal entry
-→ canonical input readback
-→ automatic upstream/downstream transition
-→ policy/schema/authority gate
-→ success 또는 typed HOLD/ABSTAIN/FAIL
-→ immutable revision/event/receipt
-→ reload 후 동일 상태
-```
-
-내부 RPC를 사람이 순서대로 직접 호출하거나 테스트가 최종 객체를 미리 조립하면 D3 이상 증거로 사용하지 않는다.
-
-## A01 — Existing-evidence reasoning loop
-
-**Given** 사용자가 project를 만들고 권한 있는 계획서·결과 문서를 연결한다.
-**When** 같은 Thread에서 막힌 문제를 한 번 입력한다.
-**Then** THOTH가 source span, sufficiency, competing hypotheses, counter-search obligations, action alternatives와 Working Revision을 자동 생성한다.
-**Terminal evidence** project/thread/object/hypothesis/action/revision/receipt readback과 동일 OAuth model-call evidence.
-**현재 판정** D5.
-
-### A01-TUI — Operator-grade human surface extension
-
-**Given** workspace에 Project가 하나 이상 존재하고 사용자가 별도 QA scenario가 아니라 Windows Terminal/PowerShell에서 `thoth`를 직접 실행한다.
-**When** 사용자가 bare prompt에 막힌 문제를 자연어로 입력한다.
-**Then** 시작 화면은 active workspace/project/thread, model route, connector/sandbox capability와 policy/authority 경계를 secret 없이 표시한다.
-**And** 긴 실행은 context restore→thread/input→model→acquisition/counter-search→sandbox→revision/memory/receipt의 단계·상태·경과시간을 기존 operation/event에서 projection한다.
-**And** terminal 결과는 evidence/source span, competing hypothesis, next action, HOLD/ABSTAIN, revision/memory와 receipt/trace를 구분해 표시하고 drill-down을 제공한다.
-**And** command palette, history, multiline editing, pause/resume/cancel이 현재 operation state에 맞게 동작한다.
-**금지** raw prompt 저장, private chain-of-thought 노출, 별도 CLI truth/state machine, R3/R4 자동 실행, progress를 receipt truth로 오인, QA manifest를 사용자 TUI로 대체.
-**Terminal evidence** 실제 bare `thoth` PTY transcript, ordered progress projection, typed final panels와 drill-down, operation/receipt readback, redaction scan, interrupt/resume/cancel negative paths.
-**현재 판정** D0 DESIGNED. 기존 A01 engine D5와 별도이며 소급 하향하지 않는다.
-
-## A02 — Missing evidence to autonomous acquisition
-
-**Given** A01 결과에 decision ranking을 바꾸는 `dataset_version` 또는 동급 필수정보가 빠져 있고, Project Policy가 read-only connector를 허용한다.
-**When** sufficiency reducer가 해당 gap을 확정한다.
-**Then** 별도 사용자 명령 없이 typed SearchIntent, BOUNDED Investigation, query families와 stop contract가 생성되고 Coordinator가 허용 Connector를 실행한다.
-**And** 결과가 Observation→Lead/Claim candidate gate→Evidence Graph로 들어가며 sufficiency와 hypothesis/action ranking이 다시 계산된다.
-**Else** connector 소진·budget·authority gap이면 최소 질문 하나 또는 typed HOLD/ABSTAIN으로 끝난다.
-**금지** GPT의 임의 filesystem/network tool access, 권한 밖 route, 발견 즉시 공식 Claim 승격.
-**현재 판정** D5 local PostgreSQL product slice / D4 general. Fresh local workspace에서 actual Codex OAuth 자연어 TUI `thread/input` 1회가 project-policy-defined `dataset_version` gap을 typed SearchIntent·BOUNDED Investigation으로 만들고, A11-bound runtime-credentialed SELECT-only PostgreSQL approved view→Connector receipt→Observation/Lead/Claim candidate Evidence UoW→같은 Thread sufficiency·hypothesis/action 재계산까지 자동 연결했다. Terminal `SUFFICIENT`, manual semantic RPC 0, DB write/raw-table/raw-SQL denial, credential 비기록과 container cleanup을 확인했다. Manifest trace digest: `90536eadbc404e8b44be96d35c90c38bf180b2dfbe0c4640a6f85b545eaa280e`. 기존 policy HOLD, Evidence UoW fault rollback과 runtime reopen readback 증거도 유지한다. S3/MCP live product loop, ULW general multi-track·computed VOI·open-ended saturation은 후속 범위다.
-
-## A03 — Critical counter-search
-
-**Given** 현재 우세 가설이 high-risk 또는 contested로 분류된다.
-**When** counterevidence obligation이 열린다.
-**Then** CRITICAL Investigation이 독립 source territory와 alternative explanation track을 실행하고, source independence·authority·temporal validity를 검증한다.
-**And** support/counterevidence diff가 같은 Hypothesis revision에 반영된다.
-**Terminal** supported/refuted-within-scope/unresolved 중 하나와 non-truth receipt.
-**현재 판정** D4. 정상 `thread/input`에서 deterministic high-risk/contested leading candidate가 CRITICAL Investigation을 열고, typed Challenger와 Independent Gate Reviewer가 project scope·source independence·authority·cutoff·forbidden-context·명시적 match contract를 검증한다. Bounded multi-wave 확장은 복수 policy route, independent/alternative/excursion tracks, priority·content dedupe·computed VOI·checkpoint/resume·wave별 sufficiency/decision-rank 변화와 hard budget을 포함한다. 같은 Hypothesis/portfolio ID의 새 revision과 `semantic_truth_certified=false` receipt를 유지하며 기존 9 terminal 경로와 `SEARCH_SATURATED`·`BUDGET_EXHAUSTED`·`POLICY_BLOCKED`·`AUTHORITY_REQUIRED`·`ABSTAINED` 확장 경로를 통과했다. Base receipt `38712d204c78e8b2713b59939c05bacb50e779ffde6f5404ede2c75d26207e3a`; multi-wave extension receipt `542afd8b4f81226bda980f5341cb89c322f35bae46949fe34de317d8d698f54d`.
-
-The A03 bounded storage follow-up groups public Hypothesis/Evidence writes and binds current
-revisions, projections and audit digests. Combined60 and full backend640/Web/doctor passed;
-immutable completion passed with receipt
-`6f5e8ffac15e5df0a56786fa5a261028e7d0c0c41b2c71c591dd20895d053f52`. See the
-[A03 storage record](../../docs/verification/a03-storage-atomicity-20260905.md).
-
-THOTH-NEXT N02는 사용자 승인 D01의 full Hypothesis/Portfolio·Action/Portfolio/Plan owner를 정상
-thread/input과 public read/edit에 연결했다. 후보·sealed record 구분, immutable legacy snapshot,
-current head/history/restore/reopen, scope·CAS·중간 fault와 전체740/Web3/doctor가 통과했다.
-[N02 검증](../../docs/verification/next-n02-identity-20260906.md)의 완료 gate도 full740/Web3/doctor와
-receipt `f34d65f2d535352405a02d2b36444d95f2bf884117744e7afc5a1f67f1fd6e05`로 완료됐다. 양방향 로컬 통합 D4다.
-새 live D5, 실제 시험 validity/appraisal 연결, owner 전체 원자성 완료로 해석하지 않는다.
-Whole-owner lifecycle remains unresolved; the N02 receipt closes the bounded normal/public identity gap.
-
-착수 시 A03 연결 간극: normal portfolio와 public HypothesisRecord가 같은 HYPOTHESIS head 공간에서
-다른 schema를 사용해 public record를 normal reader가 읽을 때 ValidationError가 재현됐다.
-이 identity 문제는 N02 증거로 해소했다. N03은 관측 전 sealed Prediction, 실제 Execution/attempt,
-독립 계산한 TestValidity/fit, 같은 가설 appraisal과 다음 자연어 행동을 연결한다. 무효·POST_HOC·
-미검증 가정은 지지로 승격하지 않으며 stale/duplicate/fault/restore/reopen을 검사한다.
-2026-09-07 묶음66개와 cutoff 보완 집중8개, full765/Web3/lint/type/build/doctor가 exit0으로 통과했다.
-완료 gate도 full765/Web3/doctor exit0, receiptca0b850a와 봉인 source 일치로 확인했다.
-정상 로컬 D4 프로토콜 연결 증거다. 더 이른 cutoff는 시간 적격성을
-추정하지 않고 명시적 HOLD하며, 현재 Project cutoff의 봉인된 예측을 지원한다.
-
-## A04 — R2 action closed loop
-
-**Given** Action Planner가 policy-allowed R2 후보를 safe frontier로 선택한다.
-**When** Coordinator가 Action Candidate를 SandboxRunSpec으로 컴파일한다.
-**Then** authoritative project-policy digest, current HeadSet, input artifact digest와 runtime capability를 검증한 뒤 sandbox를 자동 실행한다.
-**And** result를 Observation으로 봉인하고 Outcome validity, hypothesis appraisal, next-action ranking과 Working/Candidate Revision을 자동 갱신한다.
-**금지** R3/R4 자동 실행, sandbox의 canonical DB 직접쓰기, 같은 semantic revision 무한 재실행.
-**현재 판정** D4. 정상 `thread/input`의 safe-frontier R2 ActionCandidate가 policy-owned SandboxRunSpec compiler, authoritative policy/current HeadSet/exact input digest preflight, automatic sandbox, non-truth Observation, typed Outcome validity, neutral Hypothesis appraisal와 next-action ActionPlan revision으로 자동 연결된다. Outcome·Hypothesis·Action revision은 한 changeset/receipt로 커밋되며 process success는 scientific truth·criterion attainment와 분리된다. Policy HOLD와 Action semantic UoW rollback 통과. Verification receipt: `115e4fd3c694c28d852c8d27e716f456f165585db4cb8e4f7a7060084741bb5b`.
-
-**N02 추가 판정** 6G local product path D5. Natural TUI→normal `thread/input`→actual E2B
-MANAGED/DENY_ALL→Observation/Outcome/Hypothesis/Action revision을 통과했고 cleanup DESTROYED,
-active sandbox 0을 확인했다. Manifest `3cd2ec366b82f9955ed24770f2dbca5fbcf9093beede5c1547e1c0786a1c5df0`;
-completion receipt `f3f4b3e0fd60148a64b490377e87df87aa91098e900e6a7ba365aaa1348542b6`.
-
-## A05 — Failed execution and bounded recovery
-
-**Given** A04 실행이 semantic/code/infra 오류 중 하나로 실패한다.
-**When** failure taxonomy가 확정된다.
-**Then** transient infra만 제한 retry하고, semantic/code failure는 새 candidate revision과 수정 근거를 만든다.
-**And** 개선되지 않으면 원 baseline을 유지하고 typed failure receipt로 끝난다.
-**현재 판정** D4. 정상 `thread/input`에서 transient infra만 bounded retry하고 semantic/code failure는 repair-basis candidate branch revision, ambiguous external result는 no-auto-retry reconciliation, retry exhaustion은 baseline Head 보존과 typed non-truth failure receipt로 종료한다. Verification receipt: `6027188a22887b88039a93284e39b0c9b71df7b10e54971ca1a0022e3aaebf12`.
-
-## A06 — Project Memory promotion and recall
-
-**Given** Outcome과 revision에서 재사용 가능한 사실·교훈·가설 후보가 생성된다.
-**When** Memory policy가 payload mode, recall class, risk와 transition을 평가한다.
-**Then** 필요한 Facts/Reflection/Dream/Team 역할이 실제 독립 검토를 수행하고 reducer가 commit/revise/hold/quarantine 중 하나를 선택한다.
-**And** 다음 같은-project Thread의 query에 relevant·cutoff-valid·authority-valid memory만 주입된다.
-**금지** cross-project 자동 recall, ambiguous/conflicting memory의 Action Context 주입.
-**현재 판정** D4. 정상 `thread/input`이 reusable Hypothesis/Action/Outcome revision을 typed Facts·Reflection·Dream·Team 독립 검토로 보내고 reducer가 COMMIT/REVISE/HOLD/QUARANTINE을 결정한다. 다음 same-project Thread는 query relevance·scope·current owner revision·authority·cutoff·support·별도 recall/action eligibility gate를 통과한 memory만 자동 주입한다. Poisoning/secret-shaped content redaction, conflict/ambiguous HOLD, cross-project exclusion, cycle head+memory atomic rollback, runtime reopen readback과 keyword/vector/graph/summary non-canonical rebuild를 통과했다. Verification receipt: `d6c3818c673cf78324b7b90a981e58fafeb0f28b7d27db80adf6eb3915064297`.
-
-**N04 추가 판정** 6G local product path D5. Actual Codex OAuth role reviewer가 model/prompt/
-input/output/schema/basis digest를 남기고 deterministic override/reducer만 transition을 결정한다.
-Required-role failure는 HOLD이며 동일 GPT 역할은 institutional independence를 주장하지 않는다.
-Same-project recall 1, cross-project 0 manifest
-`0c2a0ff3e03296a967672eba72e6d7eee5ac9b72983542222b437996f3970154`;
-completion receipt `a31297dbf741242ec464f61b91191cddd46ace7c0bcc3172ed94188b28e09c9c`.
-
-The local A06 storage follow-up separates typed preparation, review/projection I/O and one short
-current-basis final commit. Initial and reanalysis head conflicts preserve the semantic branch and
-stop downstream use; concurrent successful memory, authority/session drift and fault/reopen controls
-are covered. Backend548/Web/doctor and completion passed with receipt
-`142c224fc2352ff64378d4cef3fd71a44efe6bc571c2229d73cc6cace28f8704`. See
-[A06 storage verification](../../docs/verification/a06-memory-storage-boundary-20260905.md).
-This preserves the bounded maturity level and does not rerun or extend N04 live evidence.
-
-## A07 — Branch, semantic merge and restore
-
-**Given** 같은 parent에서 서로 다른 actor/agent 변경이 발생한다.
-**When** 두 ChangeSet이 Working Head를 갱신하려 한다.
-**Then** 둘 다 branch로 보존되고 common ancestor 기준으로 schema·authority·cutoff·dependency·domain policy를 통과한 독립 필드만 자동 merge한다.
-**And** 충돌은 OPEN_CONFLICT로 남으며 restore는 새 revision으로 생성되고 dependents가 invalidate/recalculate된다.
-**현재 판정** D4. Public revision proposal/change-set 경로가 같은 parent의 stale candidate를 immutable `BRANCHED` revision으로 보존하고, canonical DAG common ancestor 대비 한쪽만 변경한 field path만 자동 merge한다. Overlap과 authority/cutoff/policy/security/dependency/criterion/disposition, schema mismatch, domain type mismatch, stale merge head는 typed `OPEN_CONFLICT`와 non-truth receipt로 종료하며 head를 바꾸지 않는다. Restore는 과거 snapshot을 새 child revision으로 만들고 dependent를 같은 transaction에서 `RECALCULATION_REQUIRED`로 갱신하며 stale expected head를 pre-write 거부한다. Verification receipt: `a7d82bf88bb6e571a0697a3b54fba4fa14a278edf0312662d4f2205a5e8ac152`.
-
-## A08 — Recursive improvement canary and rollback
-
-**Given** 동일 failure pattern이 policy threshold 이상 반복된다.
-**When** prompt/retrieval/workflow/evaluator 후보가 생성된다.
-**Then** immutable baseline과 candidate를 같은 frozen fixture·external evaluator·hidden holdout에서 실행하고 품질·안전·비용을 비교한다.
-**And** exact-digest canary에서 guardrail을 통과할 때만 promotion 후보가 되며 no-improvement·critical regression·leak·timeout이면 자동 rollback한다.
-**금지** 공식 KPI·안전 임계값·waiver·model weight 자동 변경.
-**현재 판정(2026-09-06 교정)** 관리·저장 loop만 bounded D4. 반복 실패에서 candidate/baseline, registry 호출, 현재 권한·시간 검사, active digest와 rollback 기록은 연결됐다. 기본 evaluator는 실제 paired execution 없이 품질5000→7500 등 고정값을 반환하는 test double이다. OFFLINE/SANDBOX/SHADOW/CANARY label 생성은 각 stage 실행 증거가 아니며, 활성 artifact 내용을 실제 model/retrieval/workflow가 소비하는 통합도 미완료다. 위 Given/When/Then의 전체 인수는 아직 충족하지 못했다. 과거 receipt `bab78e78104d3a1e7a765e754ffcd996681221e929aa2fb184aa9ee0b664f89c`는 좁은 관리·저장 검증으로 보존한다. 자세한 현재 근거는 [TRUTH-G02](../../research-briefs/THOTH_IMPLEMENTATION_TRUTH_20260906.md)를 따른다. 실제 canary/최종 baseline의 승인 경계는 canonical §14.2를 따르며 routine R0/R1/R2 전체를 결재 대상으로 바꾸지 않는다.
-
-The A08 storage-boundary follow-up is specified in
-[`THOTH-STORAGE-20260905-01`](../../research-briefs/THOTH_STORAGE_PHASE_BOUNDARIES_20260905.md).
-Its phase-aware failure/request persistence supersedes the old evaluator-fault observation-deletion
-test expectation. The historical A08 receipt does not certify this follow-up. The new normal-entry
-session expiry, real SQLite lock deadline, active-baseline preservation, public evidence binding
-and fault/reopen regressions passed, followed by backend 525/Web/doctor. See the
-[A08 verification record](../../docs/verification/a08-storage-authority-20260905.md); its immutable
-completion rerun exposed a shared Operation claim race, corrected under a replacement preflight
-with normal idempotency/CAS and unchanged R3 regression checks. Replacement backend 528/Web/doctor
-passed, then completion sealed the source with receipt
-`38280c8553661116b8794daf14dc80e517ca2ee6e3afd958b750f264fabd9bb1` and another backend 528/Web/doctor
-PASS. No A08 D-level promotion or new live evaluation is implied.
-
-## A09 — Dynamic Criterion/Profile selection
-
-**Given** 시스템 엔지니어링과 다른 domain 문서가 들어온다.
-**When** Criterion Contract 후보를 compile한다.
-**Then** general core와 후보 profile을 source-grounded하게 선택하고, profile 차이가 required evidence/evaluator/authority/exit criterion을 바꾸면 PROFILE_DECISION_REQUIRED/HOLD한다.
-**금지** 모든 project에 SYSTEMS_ENGINEERING_VERIFICATION 자동 적용.
-**현재 판정** D4. 정상 Thread가 versioned `CriterionProfileRouterPort`를 통해 source-grounded profile 후보를 계산한다. 비시스템 근거는 `GENERAL_RND`로 계속하고, specialized profile이 required evidence/evaluator/authority/exit criterion을 바꾸면 criterion compile을 `PROFILE_DECISION_REQUIRED`로 HOLD한다. Explicit profile RPC는 유지하고 모든 project의 systems-engineering 자동 적용을 제거했다. Verification receipt: `bc3709060d66ca4e5862a8032af2a1e7b9184f88a91fff28ba9d908edd2616c7`.
-
-The A09 bounded storage follow-up groups contract/revision/head/projection/audit/receipt writes
-and rejects stale canonical or projected revisions. Combined35 normal/public/profile/fault/reopen
-regressions and full backend654/Web/doctor passed. A completion contention-fixture failure led to
-test-only wait-budget separation with strict outcomes unchanged and focused20 PASS. Final completion
-passed654/Web/doctor with receipt `68c06c0012602f32a51684b03d3ff076b7334e0c9d372336adf7ae0ecf8b440f`. See
-[A09 storage verification](../../docs/verification/a09-storage-atomicity-20260905.md).
-Existing profile-selection D4 and official authority boundaries are unchanged.
-
-## A10 — Unified audit closure
-
-**Given** acquisition, reasoning, execution, outcome, memory와 revision이 한 cycle에서 발생한다.
-**When** Thread 또는 Project를 닫는다.
-**Then** source/connector, evidence, model, action, sandbox, outcome, memory, revision과 closure receipt가 하나의 검증 가능한 DAG와 manifest에 포함된다.
-**And** Connector/Sandbox receipt도 canonical receipt list/bundle/verify/export에서 조회된다.
-**현재 판정** D4 unified foundation. 정상 R2 cycle의 Connector/Sandbox/Evidence/Model/Action/Execution/Outcome/Revision/Memory receipt가 typed canonical DAG/manifest/bundle/verification에 합류하고 trust axes가 분리된다. Corruption·missing-parent INVALID와 local generate/verify·R3 external release separation을 통과했다. Completion receipt: `f317b65bb20a752f102fe8b766ec5277a57b2bc6394e74efb48cf57b47966972`.
-
-The local storage/authority follow-up binds exact snapshot resources, rights and release scope,
-preserves public PARTIAL assertions in DAG trust axes, and separates file staging from atomic
-final reference creation. Normal-entry/fault/peer-write and legacy integrity-only controls passed,
-followed by backend567/Web/doctor and completion receipt
-`bdccc7929975af24226c886e0c1862789b05c178a38e6343c2d002d0d638d139`; see
-[A10 storage verification](../../docs/verification/a10-storage-authority-20260905.md).
-No external release, new live evidence or whole-namespace maturity promotion is claimed.
-
-## A11 — Fail-closed policy integrity
-
-2026-09-08 개발용 규칙 거래·복구 후속은 [현재 검증 기록](../../docs/verification/a11-rule-transaction-final-20260908.md)을 따른다.
-정상 CLI/Hook에서 후보 검증→거래→재개/되돌리기→새 preflight를 검증하며, 아래 제품 A11 D4와는 별개다.
-현재 감사 영수증이 최종 코드 범위를 확정한다. N07·N08 구현이나 제품 전체 완료로 전이하지 않는다.
-
-**Given** empty connector allowlist, stale policy digest, security ceiling 초과 또는 egress 위반 중 하나가 존재한다.
-**When** acquisition 또는 sandbox preflight가 실행된다.
-**Then** driver/runtime I/O 전에 typed denial이 발생하며 artifact, head, memory와 receipt truth state가 변하지 않는다.
-**And** receipt에는 authoritative policy revision/digest와 denial basis가 남는다.
-**현재 판정** D4. 정상 `project/source/connect`와 `execution/start`에서 authoritative policy revision/digest를 결속하고 empty allowlist·stale digest·security ceiling·egress 위반을 driver/runtime I/O 전에 typed denial로 차단한다. denial 시 artifact/head/memory/truth receipt state 무변경 readback과 atomic Acquisition UoW fault rollback을 통과했다. Verification receipt: `68478e90e38e006972ec2d4424ea2744be9f8b950f069bd7165eb9c234000de2`.
-
-The final A11 workflow-guard follow-up preserves product D4 and checks nested protected paths
-plus exact current debt markers in NOW/OCP/maturity. Architecture117 and full backend676/Web/doctor
-passed; immutable completion remains pending. See
-[final A11 verification](../../docs/verification/a11-final-followup-20260906.md).
-
-## A12 — Field usefulness and workflow reduction
-
-**Given** preregistered cases, fixed thresholds와 기존 도구 조합 baseline이 결과 확인 전에 봉인된다.
-**When** 독립 연구자·검토자가 같은 유형의 과제를 baseline과 THOTH로 수행한다.
-**Then** critical defect recall, decision completeness, active human time, manual tool transition/re-entry와 안전 hard-zero를 측정한다.
-**And** 사전 봉인된 기준을 통과한 결과만 D6로 승격하며 인터뷰 호감·synthetic fixture·내부 개발자 평가는 대체 증거가 아니다.
-**현재 판정** tooling D4 / field result D1·D6 NOT_RUN. Protocol/case/baseline/threshold/hard-zero를 결과 전에 seal하고, keyed pseudonym session, normal RPC method/outcome event, active-time/manual-burden metric, blind score와 privacy-safe export가 제품 경로에 연결됐다. Reseal·raw sensitive metadata·early score·invalid session은 fail closed한다. 모든 export는 external participant/expert session, time saving, WTP를 `NOT_RUN`, `field_validated=false`, `d6_claimed=false`로 강제한다. Tooling verification receipt: `80b2e02133c83499d95e972a236e6135407247a3c2f51e604173fd95e21ba2fa`. 실제 D6 결과는 없음.
-
-## A13 — Authenticated multiplayer attribution
-
-**Given** 두 명 이상의 actor가 같은 project의 서로 다른 role과 scope로 인증된다.
-**When** 독립 Thread/ChangeSet 작업이 동시에 발생한다.
-**Then** actor identity가 session, project, role, revision, receipt에 결속되고 권한 밖 read/write는 I/O 전에 거부된다.
-**And** 동시 변경은 last-write-wins가 아니라 branch/merge 계약을 따르며 routine work는 순차 승인 Inbox가 되지 않는다.
-**현재 판정** D4 local productization. Local HTTP가 credential을 검증하고 token digest만 저장하며 session을 actor/project/role assignment/capability/data scope에 결속한다. Role은 매 요청 ACTIVE 상태와 capability/scope drift를 재검증하고, project/list·protected baseline·revision sibling provenance·receipt correction·operation cancellation은 authenticated identity에 결속된다. 두 인증 actor의 same-parent proposal은 fast-forward+immutable branch로 보존되고 A07 merge를 따른다. Stored Thread workstream은 claim 전에 검증되지만 revision·receipt·operation-read·source/evidence 전반의 cross-resource workstream ownership은 canonical model 부재로 DEFERRED다. Routine work는 approval Inbox를 만들지 않는다. External institutional SSO와 deployed multi-user concurrency는 NOT_RUN. Latest verification receipt: `56d923bddbebf795a557b835ca42fc59f864e54213dce0c5e28d21d8d69d9550`.
-
-The storage follow-up groups local Project/governance mutations and applies conservative stored
-Operation origin-permission coverage before claim and access, preserving exact-origin cancellation.
-These permissions do not define exact resource ownership. Explicit claim-busy rollback and same-key
-retry behavior passed along with normal HTTP/fault/CAS/reopen controls and backend605/Web/doctor.
-Completion passed with receipt `b780c8d7c10352635d7998cd97bda90f35efa80801f5b5dde790447ff1147418`;
-see [A13 storage verification](../../docs/verification/a13-project-operation-20260905.md).
-Wider sharing/backfill semantics and whole-owner debt remain unresolved; no D-level promotion is made.
-
-## 구현 진입 순서
-
-```text
-A11 policy integrity
-→ A02 autonomous acquisition
-→ A03 counter-search
-→ A04 R2 closed loop
-→ A10 unified receipt
-→ A06 full memory
-→ A07 semantic merge
-→ A08 improvement
-→ A09 dynamic profile
-→ A13 multiplayer productization
-→ A12 D6 field validation
-```
-
-A11은 A02가 권한 밖 Connector를 열지 않게 하는 선행 안전조건이다. 첫 제품 vertical slice는 `A11 + A02`다.
diff --git a/PROJECT_WIKI/50_SEED_ROADMAP/implementation-maturity-matrix.md b/PROJECT_WIKI/50_SEED_ROADMAP/implementation-maturity-matrix.md
deleted file mode 100644
index d0743e2..0000000
--- a/PROJECT_WIKI/50_SEED_ROADMAP/implementation-maturity-matrix.md
+++ /dev/null
@@ -1,162 +0,0 @@
----
-id: IMPLEMENTATION-MATURITY-MATRIX
-status: ACTIVE
-page_role: implementation_truth_ledger
-authority: ../../research-briefs/CANONICAL_RND_EVIDENCE_HARNESS_DESIGN.md
-last_updated: 2026-09-20
----
-
-# Implementation Maturity Matrix
-
-이 문서는 설계 존재, RPC 등록, 실제 제품 동작을 혼동하지 않기 위한 구현 진실 원장이다. UI는 이번 판정 범위에서 제외한다.
-
-## 성숙도 정의
-
-2026-09-08 [A11 검사기 거래/복구 후속](../../docs/verification/a11-rule-transaction-final-20260908.md)은
-개발 workflow 검증이다. 제품 A11 D4, owner debt 10 OPEN, N07/N08 및 field 상태를 승격하지 않는다.
-최종 source/통과 여부는 해당 하위 계획의 완료 영수증으로 판정한다.
-
-```text
-D0 DESIGNED canonical 설계에 요구사항이 존재
-D1 SCHEMA_DEFINED 타입·상태·입출력 계약이 존재
-D2 HANDLER_MATERIALIZED handler가 객체·레코드를 생성하고 읽을 수 있음
-D3 ISOLATED_TESTED 독립 단위·통합 테스트에서 해당 기능이 동작
-D4 INTEGRATED_IN_LOOP 정상 upstream에서 자동 진입하고 downstream으로 자동 전달
-D5 LIVE_E2E_VERIFIED 실제 모델·runtime·connector를 포함한 정상 사용자 시작점 E2E 통과
-D6 FIELD_VALIDATED 외부 사용자·전문가·현장 결과로 유용성과 안전성 검증
-```
-
-`구현 완료`는 최소 D4를 뜻한다. D1~D3은 `계약`, `materialized`, `isolated pass`로만 표현한다. Adapter 자체의 D5와 그 adapter를 사용하는 제품 loop의 D5는 별도 행으로 기록한다.
-
-## 현재 판정 — 2026-09-06 / 제품 baseline 1136e90
-
-[구현 사실 교정](../30_ARCHITECTURE/current-truth-and-next-gap-20260906.md)이 과거 broad completion 문구를 대체한다.
-아래 live D5는 해당 과거 source/fixture/route의 증거이며 이번 문서 교정이 live를 재실행한 것은 아니다.
-
-| 기능 | 현재 수준 | 확인된 범위 | 부족한 연결·증거 |
-|---|---:|---|---|
-| Project 생성·격리·cutoff·SQLite persistence | D5 | CLI/HTTP와 canonical readback | multi-user actor authorization은 별도 항목 |
-| Operator-grade human TUI | D0 | 2026-09-05 startup context·streaming progress·typed panels·palette/editing·drill-down·control 계약을 잠금 | 현재 `input()`+summary JSON shell은 engine 진입 증거일 뿐 operator UX가 아님; A01 RED부터 필요 |
-| HWPX·PDF·DOCX·XLSX·구조화 텍스트 ingestion | D5 | artifact, source span, hash, warning 보존 | OCR·binary HWP는 명시적 경계 |
-| Source Registry·lineage·authority·cutoff | D4 / 선택된 A02 live slice D5 | source/connect와 bounded Investigation의 source/span 등록 | S3/MCP general live, 공유 자료 ownership과 owner 전체 lifecycle은 미검증 |
-| 자료 기준시점 판정 UX | D3 / runtime 연결 범위 D4 local | PDF/HTML 문서 날짜의 ELIGIBLE/AFTER_CUTOFF/UNKNOWN_TIME 서버 판정, artifact/span 일관성, 인용 시 UNKNOWN 확인 | 전체 parser 일반화·live 사용자 E2E·FULL은 미검증 |
-| Evidence/Observation/Claim 후보 상태축 | D4 bounded | source-bound candidate와 Acquisition의 Observation/Lead/Claim UoW | 전체 public lifecycle와 공유 source ownership은 미완료 |
-| 정보 충분성 reducer | D4 bounded | missing/conflict/counterevidence와 A02 자동 acquisition·재분석 연결 | general non-critical multi-wave와 arbitrary route 확장은 미검증 |
-| Codex OAuth semantic adapter | D5 | 실제 OAuth schema canary와 synthetic main cycle | tool 사용은 의도적으로 금지; acquisition은 Coordinator 책임 |
-| 질문 언어 사용자 prose 계약 | D3 local | Codex/OpenAI/xAI/Anthropic prompt wire와 prompt version/input digest 집중 검사; UI 한국어 진행 표시 Web 회귀 | 실제 한국어 질문→한국어 live 모델 응답 E2E는 NOT_RUN |
-| 기존 근거 기반 경쟁가설 생성 | 기존 normal D5 / N02 양방향 통합 D4 | full Hypothesis/Portfolio owner와 candidate/read view 분리, normal/public 편집·재분석·history·restore/reopen·fault/CAS; receiptf34d65f2 | 새 live 증거 아님. Prediction/TestValidity 실연결은 N03 |
-| 기존 근거 기반 다음 행동 비교 | 기존 선택된 normal D5 / N02 양방향 통합 D4 | full Action/Portfolio/Plan owner 공유, public draft 보존, 정상/R2/recovery 동일 identity와 보수적 권한; receiptf34d65f2 | 실제 시험 유효성·전체 owner closure는 N03/N07 |
-| Investigation Contract | D4 | 정상 `thread/input`의 decision-critical gap에서 typed SearchIntent와 BOUNDED Investigation이 자동 생성되고 budget·stop·audit·reload readback 통과 | CRITICAL·SATURATION 실행은 A03/후속 범위 |
-| Authorized Evidence Acquisition Loop | D5 local PostgreSQL / D4 general | actual Codex OAuth 자연어 TUI `thread/input` 1회에서 SearchIntent→A11-bound runtime-credentialed SELECT-only PostgreSQL approved view→Connector receipt→Observation/Lead/Claim candidate atomic gate→sufficiency·hypothesis/action 자동 재계산, terminal `SUFFICIENT`, manual semantic RPC 0, write/raw-table/raw-SQL denial과 cleanup 통과 | S3/MCP live product loop와 ULW general 다중 track·computed VOI·open-ended saturation은 미검증 |
-| ULW식 다중 search track·expansion | D4 bounded CRITICAL profile | A03에서 복수 policy route, priority, independent/alternative/excursion track, content-digest dedupe, checkpoint/resume 자동 실행 | general non-critical A02 expansion과 arbitrary dynamic route는 미검증 |
-| Lead Graph·VOI·saturation | D4 bounded CRITICAL profile | A03 wave별 typed Lead, computed VOI, confirmation/sufficiency·decision-rank 변화, duplicate exclusion과 deterministic convergence/general saturation | cross-project/global graph와 external live generalization은 미검증 |
-| Counterevidence request | D4 | 정상 `thread/input`에서 high-risk/contested 우세 후보가 bounded multi-wave CRITICAL Investigation, 복수 A11-bound Connector route, deterministic independence/authority/temporal/context gate, 같은 Hypothesis revision diff와 non-truth receipt로 자동 연결 | policy-declared bounds만 검증; open-ended research 금지 |
-| Local/Git/Postgres/S3/MCP Connector adapter | D5 adapter / D5 PostgreSQL A02 product slice / D4 other bounded slices | PostgreSQL은 actual OAuth 자연어 TUI의 A11-bound BOUNDED Investigation에서 실제 runtime credential·approved view로 자동 호출; 개별 adapter live·contract와 수동 connect도 유지 | S3/MCP autonomous live E2E와 general multi-wave는 미검증 |
-| 프로젝트 공개 웹 동의·허용 host 실행 경계 | D3 local | workspace grant→project policy 결속, stale/revoked grant I/O 전 차단, managed allowlist/egress, 등록된 arXiv 진입점·same-host 후보와 자유형 query 거부를 단위/통합 검사 | 실제 공개 인터넷 취득·live 연구 loop·FULL·commit/push는 NOT_RUN |
-| Project/environment connector·sandbox policy integrity | D4 | 정상 `project/source/connect`·`execution/start`에서 authoritative ProjectPolicy revision/digest, empty allowlist, security ceiling, egress를 I/O 전 검증하고 typed denial·무변경 readback·atomic Acquisition UoW 통과 | deployment/field NOT_RUN; unified external-runtime receipt DAG는 A10 |
-| Action/Execution 상태기계 | D4 R2 slice | 정상 `thread/input`의 safe-frontier R2 ActionCandidate가 typed SandboxRunSpec·policy/head/input preflight·sandbox·Observation·Outcome/Hypothesis/next-action revision으로 자동 연결 | A05 bounded recovery와 R3 authorization은 별도 |
-| Docker·gVisor·Firecracker·E2B sandbox adapter | D5 adapter / E2B 선택된 A04 D5 | 개별 live run과 N02 6G normal Thread→E2B→Outcome 증거 | Docker/gVisor/Firecracker normal-loop D5는 별도 미검증; adapter D5를 전이하지 않음 |
-| R2 closed loop | D4 | Planner candidate→policy template compiler→current Head/input digest preflight→sandbox→non-truth Observation→Outcome validity→neutral Hypothesis appraisal→next-action order·3-revision atomic commit | A05 retry/reconciliation과 live adapter product E2E는 별도 |
-| Outcome과 same-Thread 재분석 | D4 | 새 evidence 재분석과 A04 execution-linked neutral appraisal/next-action revision | comparator·criterion·causal truth는 의도적으로 NOT_ASSESSED |
-| N03 봉인 예측·범위 내 시험 평가 | D4 로컬 정상 연결 완료 | thread/input→source-bound Prediction→실제 Execution/attempt→원자료 평균 계산→같은 가설 appraisal→다음 행동; invalid/fault/CAS/reopen/restore, full765/Web3/doctor 두 차례 exit0와 receiptca0b850a | TEST_ONLY 프로토콜 증거, RANGE/ARITHMETIC_MEAN 한정; 과거 cutoff 적격성 미상은 HOLD. 새 live D5·공식 Criterion·인과 진리·owner 전체 원자성 주장이 아님 |
-| Revision·diff·restore | D5 | live CLI/HTTP cycle과 dependent invalidation/recalc | 없음; merge는 별도 항목 |
-| Semantic branch | D4 | expected-head 충돌과 counterevidence branch | 일반 동시작업·멀티 actor 실시간성은 미검증 |
-| Semantic three-way merge | D4 | public ChangeSet sibling branch 보존, canonical common ancestor 기반 독립 field-path auto merge, protected/schema/domain overlap OPEN_CONFLICT, typed non-truth receipt | authenticated real multi-user concurrency는 A13; domain-specific validator profile은 A09/extension 범위 |
-| Multi-Baseline normal loop | D4 | normal Thread ProjectHeadSet/scoped candidate, protected BaselineSet decision, Outcome/Improvement/Closure/Export comparator, routine change/restore invalidation | official milestone authority는 protected; external milestone use NOT_RUN |
-| Criterion Contract compiler | D4 bounded | source candidate·산식·단위·조건·authority와 등록 profile | 모든 domain 일반화 아님; 조건부 reference range/interview의 정상 실행 증거 없음 |
-| Dynamic Criterion/Profile router | D4 | six versioned YAML packs가 same normal Thread core를 사용하고 source applicability evidence, latest-version selection, non-system GENERAL_RND continuation, specialized/ambiguous decision-dimension 차이 PROFILE_DECISION_REQUIRED/HOLD를 통과 | 외부 domain authority activation과 field vocabulary는 registry extension/owner decision |
-| Basic project-scoped memory | D4 | project isolation, append-only history, basic recall | full lifecycle와 같은 normal Thread 경로에서 회귀 검증 |
-| Facts·Reflection·Dream·Team memory lifecycle | D4 | 정상 Thread 결과의 typed 4-role review→COMMIT/REVISE/HOLD/QUARANTINE, 다음 same-project Thread 자동 relevance recall, authority/cutoff/support/action gate, poisoning/conflict exclusion, atomic rollback과 reopen readback | 외부 live multi-user memory는 NOT_RUN; vector/summary는 rebuildable non-canonical projection |
-| Improvement 관리·저장 loop | D4 bounded management | repeated failure→candidate/baseline/typed registry→current-basis finalize→DB active pointer/rollback; 기본 평가기 미설정은 HOLD | 고정 점수는 명시적 TEST_ONLY 관리 회귀 전용. 실제 비교/정책 적용은 아래 N05/N06의 별도 증거 |
-| 실제 baseline/candidate 비교 실행 | N05 bounded D4 local | 정상 Thread의 명시적 runnable plan→실제 분리 subprocess pair→독립 scorer→공개 평가, 현재 권한·예산·재시작; backend837/Web3와 receipt289b096d | PURE_TRANSFORM_V1의 명시적 계획 비교 범위. 자동 실행 후보 생성과 실제 적용은 N06, live D5 미실행 |
-| 실제 behavior activation·shadow/canary | N06 bounded D4 local | prompt·검색·의미보완 실제 소비, 공통 정책 subprocess 비교, 자동 제한 후보, scoped exposure·별도 승인·관측·budget/CAS/rollback/reopen; final backend860/Web3/full gate, receipt4a5fea12 및 봉인 source 일치 | 정책 shadow만 관측; 실모델 품질 NOT_ASSESSED/live D5 미실행; weights/R4 제외. Owner10 전체 closure는 N07 |
-| 조건부 reference-range 후보·인터뷰 | D0 설계 | canonical 3-lane와 applicability/uncertainty 계약 | source packet·결정론적 비교/계산·질문/응답 revision·정상 사용자 loop 미구현 |
-| Canonical receipt·revision integrity | D5 | digest verification, revision/closure/export receipt | external signature/TSA는 deferred |
-| Connector/Sandbox unified receipt DAG | D4 foundation | typed runtime/cycle nodes, canonical DAG/manifest/bundle/verification, separated trust axes, corruption/missing-parent detection과 closure/export local boundary | external signature/TSA와 실제 R3 release는 NOT_RUN |
-| Closure·LOCAL_SEALED export | D5 | live local lifecycle과 manifest/receipt | external release는 R3 경계 |
-| Universal multiplayer semantics | D4 local | credential-verified session→ACTIVE role/capability/scope request-time revalidation, project-list isolation, protected authority/actor/session/receipt attribution, stored Thread pre-claim scope, authenticated concurrent branch/merge; N04 D02/B Source·derived·memory·query·receipt·operation·export 공유/회수 local D4, backend809/Web3 및 receipt0c94759b | N04 영수증은 해당 봉인 source 범위다. 새 N05 변경의 전체 증거로 승계하지 않음; external institutional SSO와 deployed multi-user concurrency는 NOT_RUN |
-| Field measurement tooling / usefulness·time saving·WTP | D5 local replay tooling / D1 field | six-case sealed baseline import, 6×6 counterbalance, pseudonymous session/event/score, normal RPC instrumentation, timer/tool/manual burden replay, blind adjudication and privacy-safe export with forced NOT_RUN | 실제 participant consent/session, independent expert score, measured time saving·WTP는 NOT_RUN; D6 아님 |
-
-## 현재 전체 판정
-
-```text
-CONTRACT SURFACE: BROAD
-LOCAL CORE: PARTIAL WORKING
-INTEGRATED AUTONOMY: PARTIAL
-LIVE ADAPTERS: VERIFIED SEPARATELY
-FIELD VALIDATION: NOT_RUN
-PRODUCTION: NO_DEPLOY
-```
-
-```text
-ATOMICITY DEBT: 10 OPEN
-bounded slice D4/D5 does not certify full namespace atomicity
-```
-
-Manifest의 9 `PARTIAL` owner와 1 `MATERIALIZED_ONLY` owner는 각자 debt ID, owning Acceptance와
-closure fault-injection evidence를 가진다. 이 표시는 예외 승인이나 구현 완료가 아니며, 실제 atomic
-UoW receipt 없이는 `ATOMIC`으로 승격하지 않는다.
-
-## Post-D4 phase ledger — 역사적 실행 증거
-
-이하 기록은 당시 source의 좁은 성공 범위를 보존한다. A08/P6의 stage 이름·registry 변경을
-실제 평가/적용 완료로 해석했던 부분은 위 현재 판정과 TRUTH-G02가 대체한다.
-
-G01/G03 Hook revision은 interpreter mutation 분류, canonical preflight 검증, create-only receipt archive, verified scope와 wiki-sync 재검증을 focused `30 passed` 및 backend `363 passed` 두 차례로 검증했다. Fresh project task에서 SessionStart·allow·preflight-less deny·probe absent·receipt binding과 non-blocking completion을 관측했다. Hook은 D-level 승격 증거가 아니며 동일 OS 사용자 악성 프로세스를 막는 보안 경계도 아니다.
-
-| Phase | 상태 | 근거 | 성숙도 영향 |
-|---|---|---|---|
-| P0 POST_D4_BASELINE | PASS | baseline `bbb597f4d1e9ace15ab8a1b61249b1a13d4a44ad4affb9de37d1c3d7c06aa5a6`, receipt `ff92cfaec7c2a79195b3074383341181498d1671ff5a79983cc1f2973068435e` | 기존 A01-A13 level 재정의 없음 |
-| P1 RPC/wiki truth | PASS | 303 canonical + 6 aliases = 309 runtime, 219 notifications; receipt `21bc50b442f5081f41afebbe45e606db6992ea3571bf7772ee0844a2d950e139` | catalog materialization이며 behavioral D-level 승격 아님 |
-| P2 Multi-Baseline | D4 | normal-loop receipt `670d75a0d1b6e4716d3fc6737b88cf1b4d2c534facaeb317cd7f3e3fab1532b3` | milestone decision protected |
-| P3/C08 real Connector lanes | D5 local PostgreSQL / D4 other lanes | normal A02 network-share receipt `b26ae98ddc438c779aaa9697a347a99d4512b1ea0124bd916196aec7d164df2a`; C08 actual OAuth PostgreSQL trace `90536eadbc404e8b44be96d35c90c38bf180b2dfbe0c4640a6f85b545eaa280e` | credentialed S3/MCP live product loop NOT_RUN |
-| P4 Sandbox router | D4 scripted/router | exact-profile receipt `53f079bb24d11bdfde878ace8952ea335783e1fbeeb31f25897613d68d31140b` | Docker/gVisor/Firecracker/E2B live D5 NOT_RUN |
-| P5 semantic Memory | D4 local adapters | receipt `b335a26a64c25d10de3d9da113bbd0ba6638435b13929efc395e87b6f1c59fe1` | real-model reviewer D5 NOT_RUN |
-| P6 Improvement 관리·artifact registry | D4 관리 범위, 실제 적용 미완료 | receipt `3c7b4172d2f554fc6e49d497bab8805da2ad4277a459c262bd833d88dc05668a` | external evaluator/production traffic and weight changes NOT_RUN |
-| P7 six Criterion Profiles | D4 same core | receipt `0af3312ff1b776ae52399668947bc1b62164253595abcf44788b3d2f63a234b5` | external profile authority decisions/field use NOT_RUN |
-| P8 natural-language TUI | D5 local on 6G OAuth / D4 fixture generalization | P8 receipt `220868cae204f1c96ace5e34a735dde31223c007835fde90fa6724f1ef5c972b`; P10 OAuth receipt `b24a8f30c99d9ad614c8705edf2ab4f931c51c00fcf6d9025c0a54be282b8410` | public OAuth was P10 LIVE_HOLD and later closed by N01B; full 16-screen UI out of scope |
-| P10 current Hero/secondary/holdout | 6G D5 local; public/SUNRISE D4 sealed; holdout evaluator PASS at P10 | 6G real OAuth manifest, same-trace sealed manifests, independent OpenDreamKit oracle evaluation; receipt `b24a8f30c99d9ad614c8705edf2ab4f931c51c00fcf6d9025c0a54be282b8410` | public synthetic OAuth was LIVE_HOLD at this historical gate; N01B later supplied D5 local evidence |
-| P9 A12 field execution tooling | D5 local replay tooling / D1 field | sealed baseline `e628bd1216b6e2e51beaa4220b76f89df1559c48702c3b32c41fb9ce06bb1f29`; receipt `5665b86f9700928d596423336f8fe4d2fbf7903888145b1b937f4a357255ea31` | participant/expert sessions, measured time saving and WTP remain D6 NOT_RUN |
-| N01A public OAuth diagnostics | D4 diagnostic integration; A01 level unchanged | generic public error, privacy-safe diagnostic ledger, fresh OAuth fault fingerprint; receipt `73b8b0fa944de74d74b7c68a0490d2392536db454a9287684823b2a445a151f8` | concrete `_content_excerpt` TypeError found; public OAuth remains LIVE_HOLD until N01B |
-| N01B public OAuth exact fix | D5 local A01 public synthetic path | actual OAuth natural TUI→ChangeSet→Memory→Receipt manifest `146ed34fb7aa20077207cce556b719cd712aae6f6d755f165768a32137464471`; receipt `819c2ccd6ee8b8129a1949f2872d56fa00e2d00248106c62af2571b286352381` | deployment, institutional identity and field evidence NOT_RUN |
-| N02 managed E2B normal Thread | D5 local A04 on 6G | actual OAuth natural TUI→normal `thread/input`→actual E2B MANAGED/DENY_ALL→Observation/Outcome/Hypothesis/Action revision; cleanup DESTROYED, active 0; manifest `3cd2ec366b82f9955ed24770f2dbca5fbcf9093beede5c1547e1c0786a1c5df0`; receipt `f3f4b3e0fd60148a64b490377e87df87aa91098e900e6a7ba365aaa1348542b6` | public-membrane fresh attempt held before sandbox on semantic alternative contract; deployment/field NOT_RUN |
-| N03/C08 credentialed Connector | D5 local PostgreSQL A02 / S3·MCP NOT_RUN | runtime-generated credential, SELECT-only role, approved view, actual OAuth natural TUI→SearchIntent→A11→PostgreSQL→Evidence UoW→reanalysis; cleanup and secret scan PASS | S3/MCP live product loop, deployment and institution credentials NOT_RUN |
-| N04 real-model Memory reviewer | D5 local A06 on 6G | actual Codex OAuth Facts/Reflection/Dream/Team, deterministic override/reducer, model/schema/input/output/basis digests, same-project recall 1/cross-project 0; manifest `0c2a0ff3e03296a967672eba72e6d7eee5ac9b72983542222b437996f3970154`; receipt `a31297dbf741242ec464f61b91191cddd46ace7c0bcc3172ed94188b28e09c9c` | same-GPT roles are not institutionally independent; deployment/field NOT_RUN |
-| N05/C07 evaluator extension | D4 local registry / external process DEFERRED | typed capability registry가 normal A08 loop에서 budget·timeout·schema/digest를 검증하고 candidate-only 결과를 반환 | opaque holdout transfer, subprocess/Sandbox external evaluator와 production traffic unopened |
-| N06 audit checkpoint | AUDIT complete / checkpoint NOT READY | architecture/full verify/doctor/field/diff/dependency/security scan; security inventory 228/228 | 25 open findings, 4 deferred external-control gaps; no commit/release |
-| N07 external field | D1 field / D6 NOT_RUN | sealed local field tooling only | consented humans, 36 sessions, blind scores, time, demand and WTP absent |
-| G01 Hook/gate hardening | code/contract VERIFIED; new runtime trust NOT_RUN | A11 preflight `45d8f20bd66dc1f23a969cdbc75e26a620e5ab8fd569ef05771b48dae09edcc1`; completion `7bdd536b00c81cbeac34011ffbb3fb88e38f94827b57efafe8987e6cfa573bed`; backend 361 twice | fresh Codex session에서 changed hooks.json 재신뢰와 live allow/deny/Stop 관측 필요; product A11 D4 unchanged |
-| G02 authenticated Receipt hardening | A10/A13 D-level unchanged; regression FIXED_LOCAL | authenticated actor/session/role canonical digest binding, pre-insert spoof denial, local PARTIAL attribution, unknown bundle selector fail closed; receipt `bbc716c1960fa4e3e86b15929b3742cfd65ff32565bff186a28e1204f16900a1`; backend 361 twice | unauthenticated loopback has no verified identity; institutional auth/signature/TSA NOT_RUN |
-| G03 wiki-sync Hook/runtime | workflow guard runtime PASS; product D-level unchanged | wiki-sync `ec027c4dd512197b5b455605104a616cf67b5d5888ab04edaffaede02d9f71d8`; completion `9db919d1203c12bc415953016477ac3952471cf75b84d7ee6c81409dbc2e8f5c`; backend 363 twice; fresh task allow/deny/probe/receipt/completed PASS | semantic correctness still requires drift audit; same-OS malicious process outside boundary |
-| A11 architecture truth enforcement | PASS; product D-level unchanged | registry/factory/core-closed/doc-drift guards, actual extension targets, atomicity debt 10/fully_atomic false, backend 428 twice; receipt `17a60b677719f18b7e3cafaa87908bceac4e99297abb1aa23ef455c06b10757f` | does not implement or close the ten UoW debts |
-
-## 기능군별 인수계약 매핑
-
-| 기능군 | 인수계약 |
-|---|---|
-| Project·parser·Source Registry·Evidence·OAuth·sufficiency·hypothesis·action proposal | A01 |
-| Investigation·Authorized Acquisition·ULW track·Lead·VOI·Connector product loop | A02 |
-| Counterevidence·Critical review | A03 |
-| Action/Execution·Sandbox·execution-linked Outcome | A04 |
-| 실행 오류 taxonomy·retry·candidate revise | A05 D4 — transient-only bounded retry, semantic/code candidate branch, ambiguous reconciliation, budget exhaustion baseline preservation |
-| Basic/Full Project Memory | A06 |
-| Revision·branch·merge·restore | A07 |
-| Recursive Improvement | A08 |
-| Criterion Contract·dynamic profile | A09 |
-| Receipt·Closure·Export | A10 |
-| Connector/Sandbox policy integrity | A11 |
-| Field usefulness·time saving·WTP | A12 |
-| Universal multiplayer actor binding | A13 |
-
-인수계약 원문은 [Behavioral Acceptance Contracts](behavioral-acceptance-contracts.md)에서 관리한다.
-
-`303 canonical callable + 6 compatibility aliases = 309 runtime methods / 219 notifications`는 D1~D2 surface materialization 증거다. 개별 테스트가 있으면 D3까지 올릴 수 있지만, 그 숫자만으로 D4 이상의 제품-loop 완성을 주장하지 않는다.
-
-## 갱신 규칙
-
-1. 각 행은 대응하는 행동 기반 acceptance contract를 가져야 한다.
-2. D4 승격에는 정상 upstream 자동 진입과 downstream 자동 전달 증거가 모두 필요하다.
-3. D5 승격에는 수동 내부 객체 조립이 아닌 정상 사용자 시작점 E2E가 필요하다.
-4. Adapter의 live pass를 제품 loop의 live pass로 전이하지 않는다.
-5. 실패·HOLD·ABSTAIN 경로도 동일한 성숙도 증거에 포함한다.
-6. 상태 변경 시 명령, fixture, digest, 결과와 잔여 미검증 범위를 함께 기록한다.
diff --git a/PROJECT_WIKI/NOW.md b/PROJECT_WIKI/NOW.md
deleted file mode 100644
index aa94daa..0000000
--- a/PROJECT_WIKI/NOW.md
+++ /dev/null
@@ -1,44 +0,0 @@
-# Public source preview status
-
-This is a source-only experimental preview. The private development timeline is not distributed.
-Current verification and limits are recorded in [docs/VERIFICATION.md](../docs/VERIFICATION.md).
-
-```text
-ATOMICITY DEBT: 10 OPEN
-```
-
-## 2026-09-29 public authentication and restart source update
-
-This experimental source update adds a common authentication-method contract, THOTH-owned
-provider profiles, login status/cancellation and manual Claude response handling.
-API keys and OAuth remain separate choices. Login does not change a saved model or effort.
-Transient authorization URLs and device codes are excluded from durable operation results.
-
-Codex uses its isolated profile and explicit model discovery. xAI preserves an existing
-connection when a new login is cancelled and reports inconsistent credential files as HOLD.
-The Claude OAuth/Messages adapter is implemented, but login is disabled without explicit
-client configuration; permission to offer this third-party subscription route and real
-provider execution remain unresolved. This is not a claim of supported live Claude login.
-
-SQLite research records and object files remain the canonical local store. Controlled
-process reopen/cold-copy checks preserve results and references without rerunning a model.
-Damaged setup files and browser records are distinguished from first run and preserved;
-existing research remains readable while execution requiring the missing setup is held.
-
-Scoped backend and Web checks passed on their recorded stages, and the final Web bundle
-builds. Independent r2 Chrome/RPC checks with synthetic providers accepted a partial scope:
-explicit authentication routes, isolated workspaces, damaged-setup read access and saved
-results reopened in a separate process. A QA diagnostic database-lock failure and unrun
-scenarios remain recorded. This is not complete acceptance or real-provider output evidence.
-
-A current-source FULL, physical Windows reboot, real account/provider acceptance, Codex
-Desktop coexistence and field-effect evidence remain unverified. The source update is
-published for inspection and local testing; the hosted service remains unchanged.
-
-The latest recorded completed backend FULL had 1,694 passes, 41 failures and one skip;
-that run did not reach Web or doctor. Later scoped repairs do not turn it into a pass.
-The older 25-case selected ledger and source-archive digest are historical evidence.
-
-See [model authentication](../docs/architecture/model-auth.md),
-[installation and recovery](../docs/INSTALL.md) and
-[verification limits](../docs/VERIFICATION.md).
diff --git a/README.ko.md b/README.ko.md
index 291964b..21b5182 100644
--- a/README.ko.md
+++ b/README.ko.md
@@ -36,7 +36,7 @@
> [!NOTE]
> **개인·로컬 사용을 위한 실험적 소스 공개본입니다.** UI는 한국어 중심입니다.
> 집중 기능 검사는 통과했지만 최신 전체 회귀 검사는 통과하지 못했습니다. 아래 검증 범위를 확인하세요.
-> **2026-09-29 반영:** 모델별 전용 인증과 재시작·저장 복구 수정을 포함합니다. 실제 모델 분석과 현재 소스의 전체 검사 통과는 미확인이고, Claude 구독 로그인은 클라이언트 설정이 없어 비활성입니다.
+> **2026-09-29 반영:** 모델별 전용 인증과 재시작·저장 복구 수정을 포함합니다. 실제 모델 분석과 현재 소스의 전체 검사 통과는 미확인이고, Claude 로그인은 공식 Claude Code 실행 파일을 거치며 실계정으로는 아직 확인하지 않았습니다.
> 결과를 사용하기 전에 [현재 상태와 한계](#현재-상태)를 확인하세요.
## 무엇을 할 수 있나요
@@ -73,38 +73,50 @@ cd thoth
GitHub의 **Code → Download ZIP**으로 받았다면 압축을 풀고 `thoth-main` 폴더에서 PowerShell을
엽니다. 압축을 푼 상위 폴더에 있다면 `cd thoth-main`을 실행하세요.
-먼저 [공식 Python으로 Windows 실행 환경을 준비](docs/INSTALL.md#prepare-python-on-windows)한 뒤,
-저장소 폴더에서 잠금 파일에 맞춰 의존성을 설치합니다.
+
+**1. Python을 확인하고 잠금 파일에 맞춰 의존성을 설치합니다.** 저장소 폴더에서 실행하세요.
```powershell
+$thothPython = Join-Path $env:LOCALAPPDATA 'Programs\Python\Python313\python.exe'
+Test-Path -LiteralPath $thothPython
+& $thothPython -m venv --copies --without-pip .venv
uv sync --python .\.venv\Scripts\python.exe --no-managed-python --no-python-downloads --frozen --extra dev --dev
pnpm.cmd install --frozen-lockfile
```
-첫 번째 터미널에서 API를 시작합니다.
+`Test-Path`가 `False`를 출력하면 공식 Python을 먼저 설치하세요. 서명 확인을 포함한 절차는
+[Windows Python 준비](docs/INSTALL.md#prepare-python-on-windows)에 있습니다.
+
+Python을 다른 경로에 설치했다면 위 `$thothPython`을 설치한 `python.exe` 경로로 바꿉니다.
+
+**2. 첫 번째 터미널에서 API를 시작합니다.**
```powershell
.\.venv\Scripts\python.exe -m thoth.cli workspace
.\.venv\Scripts\python.exe -m thoth.cli serve --port 8765
```
-두 번째 터미널에서도 같은 `thoth` 또는 `thoth-main` 폴더로 이동한 뒤 웹 화면을 시작합니다.
+**3. 두 번째 터미널에서** 같은 `thoth` 또는 `thoth-main` 폴더로 이동한 뒤 웹 화면을 시작합니다.
```powershell
pnpm.cmd --dir apps/web run dev
```
-****을 엽니다. 처음 설정 또는 설정 화면에서 지원되는 로그인 경로를 선택하거나 본인의 API 키를 입력한 뒤 모델을 고릅니다. 실제 호출에는 해당 서비스의 사용량이 소모될 수 있습니다.
+**4. 앱 안에서 모델을 연결합니다.** ****을 열면 처음 설정 화면에 ChatGPT·Claude·xAI가
+표시됩니다. 원하는 서비스의 로그인 버튼을 눌러 브라우저에서 로그인을 마치면 THOTH가 그 계정의 모델 목록을
+불러옵니다. 본인의 API 키로 연결할 수도 있으며, 이 단계에는 터미널이 필요 없습니다.
+필요한 도구(ChatGPT의 Codex CLI, Claude의 Claude Code)가 없으면 카드에 자동 설치 버튼이 나옵니다.
+Node.js가 필요하고 `%LOCALAPPDATA%\THOTH\tools`에만 설치하며, 전역 설치나 다른 프로그램의 로그인은 건드리지 않습니다.
+수동 명령은 [docs/INSTALL.md](docs/INSTALL.md)에 있습니다. 계정 로그인은 그 계정의 다른 앱과 사용량 한도를 함께 쓰고,
+실제 호출에는 해당 서비스의 사용량이 소모될 수 있습니다.
새 Windows 설치의 연구 데이터는 소스 폴더와 별개인 `%LOCALAPPDATA%\THOTH`에 저장됩니다.
이전 버전을 사용했다면 `serve --workspace "C:\기존경로\.thoth-local" --port 8765`로
원래 데이터 폴더를 지정하세요. 시작할 때 실제 저장 위치를 표시하며, 기존 자료를 자동 이동하지 않습니다.
-Codex 로그인은 THOTH 전용 프로필을 사용하며 호환되는 공식 standalone CLI가 필요합니다.
-THOTH의 연결 절차로 로그인하세요. 기존 Codex Desktop 로그인은 가져오지 않습니다.
-이번 소스에는 THOTH 전용 프로필을 사용하는 실험적 xAI 기기 코드 로그인도 추가됐습니다.
-가짜 제공자와 연결한 실제 Chrome/RPC 흐름은 통과했고, 실계정 검증은 아직 남아 있습니다.
-Claude와 xAI의 API 키 연결은 별도로 유지합니다.
+로그인은 THOTH 전용 프로필을 사용하며, 다른 프로그램의 기존 로그인 정보는 가져오지 않습니다.
+Claude 로그인은 그 프로필에서 공식 Claude Code를 실행하고, xAI는 기기 코드 로그인을 사용합니다.
+둘 다 가짜 제공자·가짜 실행 파일로만 확인했고(xAI는 Chrome/RPC 흐름도 통과), 실계정 검증은 아직 남아 있습니다.
[연결 조건과 재시작 안내](docs/INSTALL.md)를 확인하세요.
API는 로컬 루프백 주소에 바인딩되고, Vite는 API 요청을 8765 포트로 전달합니다. 소스와 migration 디렉터리를 함께 사용해야 하며, 이 공개본은 독립 wheel 설치를 지원하지 않습니다.
diff --git a/README.md b/README.md
index fe5d247..4afa1d3 100644
--- a/README.md
+++ b/README.md
@@ -37,7 +37,7 @@ see what remains unresolved, and compare two saved results before deciding what
> [!NOTE]
> **Experimental source preview for personal/local use.** The interface is Korean-first.
> Focused feature checks passed; the latest repository-wide verification is not passing. See the verification limits below.
-> **2026-09-29 update:** isolated model authentication and restart recovery are included. Real-provider analysis and a current-source FULL pass remain unverified; Claude subscription login is disabled without client configuration.
+> **2026-09-29 update:** isolated model authentication and restart recovery are included. Real-provider analysis and a current-source FULL pass remain unverified. Claude sign-in goes through the official Claude Code executable and has not been checked with a real account.
> See [status and limits](#status) before relying on a result.
## What you can do
@@ -78,40 +78,52 @@ cd thoth
Or choose **Code → Download ZIP** on GitHub, extract it, and open PowerShell in the extracted
`thoth-main` folder. If PowerShell is in the folder containing that extraction, run `cd thoth-main`.
-First [prepare the Windows Python environment](docs/INSTALL.md#prepare-python-on-windows),
-then install the locked dependencies from the repository folder:
+
+**1. Check Python and install the locked dependencies** from the repository folder:
```powershell
+$thothPython = Join-Path $env:LOCALAPPDATA 'Programs\Python\Python313\python.exe'
+Test-Path -LiteralPath $thothPython
+& $thothPython -m venv --copies --without-pip .venv
uv sync --python .\.venv\Scripts\python.exe --no-managed-python --no-python-downloads --frozen --extra dev --dev
pnpm.cmd install --frozen-lockfile
```
-Start the API in one terminal:
+If `Test-Path` prints `False`, set `$thothPython` to your installed Python 3.12/3.13
+`python.exe` path, or install the official Python first. The signature-checked version of
+these steps is in [Prepare Python on Windows](docs/INSTALL.md#prepare-python-on-windows).
+
+**2. Start the API** in one terminal:
```powershell
.\.venv\Scripts\python.exe -m thoth.cli workspace
.\.venv\Scripts\python.exe -m thoth.cli serve --port 8765
```
-Start the Web interface in a second terminal, after entering the same `thoth` or `thoth-main` folder:
+**3. Start the Web interface** in a second terminal, after entering the same `thoth` or `thoth-main` folder:
```powershell
pnpm.cmd --dir apps/web run dev
```
-Open ****. In setup/settings, select a supported login route or enter your
-own provider key, then choose a model before starting research. Provider use may consume its quota.
+**4. Connect a model inside the app.** Open ****. The first-run screen
+lists ChatGPT, Claude and xAI: choose the sign-in button for a provider, finish the sign-in in your
+browser, and THOTH loads that account's model list. Or connect your own provider API key. No
+terminal is needed for this step. If a helper program is missing (the Codex CLI for ChatGPT,
+Claude Code for Claude), the card offers an automatic install. It needs Node.js, installs into
+`%LOCALAPPDATA%\THOTH\tools`, and leaves your global installs and other programs' logins alone.
+Manual commands are in [docs/INSTALL.md](docs/INSTALL.md). A sign-in uses the same account's
+usage limits as its other apps, and provider use may consume quota.
New Windows installations keep research data in `%LOCALAPPDATA%\THOTH`, independently of the
downloaded source folder. If you used an earlier version, open its existing data directory with
`serve --workspace "C:\path\to\your\old\.thoth-local" --port 8765`.
THOTH reports the selected directory at startup and does not move old data automatically.
-Codex sign-in uses a separate THOTH profile and requires a compatible official standalone CLI.
-Use THOTH's connection flow; an existing Desktop login is not imported. This source version adds
-experimental xAI device-code sign-in with a THOTH-owned profile. Its controlled Chrome/RPC flow
-passed with fake providers; real-account acceptance is pending. Claude and xAI API keys remain separate routes. See
-[connection requirements and restart behavior](docs/INSTALL.md).
+Sign-in uses separate THOTH profiles; existing login information from other programs is not
+imported. Claude sign-in runs the official Claude Code executable in that profile, and xAI uses
+a device-code flow. Both were checked only with fake providers or fake executables (xAI also with a
+Chrome/RPC flow); real-account acceptance is pending. See [connection requirements and restart behavior](docs/INSTALL.md).
The API binds to loopback; Vite proxies API calls to port 8765. Keep the migration tree with the
source checkout: a standalone wheel is not supported by this preview.
@@ -123,7 +135,7 @@ The isolated Windows check used Python 3.13.15, Node 24.19.0, pnpm 12.6.0 and uv
The basic PDF path uses pypdf. Structured PDF parsing, browser acquisition, additional connectors
and managed sandboxes have optional dependencies and prerequisites.
-The default connection does not import OMO or Codex Desktop credentials. The experimental Codex
+The default connection does not import credentials from other programs. The experimental Codex
bridge uses its own profile for login, token refresh and model discovery. No account configuration
is distributed here; successful login alone does not establish a successful model response.
diff --git a/SOURCE_MANIFEST.json b/SOURCE_MANIFEST.json
index 09df1c2..c828306 100644
--- a/SOURCE_MANIFEST.json
+++ b/SOURCE_MANIFEST.json
@@ -16,15 +16,20 @@
"sha256": "19974abeb14a36c4f359a9a964bf18f4a6b48f4f645a07e6d26314828ad3d018",
"bytes": 237
},
+ {
+ "path": ".github/workflows/product-check.yml",
+ "sha256": "716790b9e61a1182faec87badc6accaaa27805f1b95a8eb19ad56f4dfdc5d1c8",
+ "bytes": 3306
+ },
{
"path": ".gitignore",
- "sha256": "da2cadd33c0407dea78e4d54a1ebfd71775e94affd7c0c7590aac9a9c5823e23",
- "bytes": 571
+ "sha256": "a6cb90a84a1ba2f6e02518dc980f4786735715a610bce2240d403a77dbc47223",
+ "bytes": 591
},
{
"path": "AGENTS.md",
- "sha256": "26bc35a412c42c2e4e0415bf864e7e67d230c197bc7ddf323c4becfd7d88522b",
- "bytes": 8482
+ "sha256": "1a0ba35d210506d5d1b75e787aeee57190c6d9aef07a100ef56df81e85bb9d8c",
+ "bytes": 9502
},
{
"path": "CONTRIBUTING.md",
@@ -41,31 +46,6 @@
"sha256": "995bbee79e590f99ee9b91fc60adb8b937045b4aeb24c93c2b65abcde757b908",
"bytes": 6150
},
- {
- "path": "PROJECT_WIKI/30_ARCHITECTURE/current-ocp-gaps.md",
- "sha256": "408d93eae7ecdc97d9c86e5819e511dcbd7ba5c970c4ffd4da7e443864f03d03",
- "bytes": 6277
- },
- {
- "path": "PROJECT_WIKI/30_ARCHITECTURE/rpc-method-catalog.md",
- "sha256": "c43a2ab3ff17bd4dae2bb6d70e39257f8d66781849f967f6375610b5ff029f98",
- "bytes": 11108
- },
- {
- "path": "PROJECT_WIKI/50_SEED_ROADMAP/behavioral-acceptance-contracts.md",
- "sha256": "93e4bdee6361fd788e8162657637b9c08f2bbda7072432024a8c1e104918821f",
- "bytes": 25909
- },
- {
- "path": "PROJECT_WIKI/50_SEED_ROADMAP/implementation-maturity-matrix.md",
- "sha256": "01de68c425c6a65e4bae5b4a0522605f5fdbdf027bcfac27f5cfe9a8cfc0ae92",
- "bytes": 23957
- },
- {
- "path": "PROJECT_WIKI/NOW.md",
- "sha256": "cb6a71b4550fd6d342f9d83b5086ff6e917906247ebcde87489a6df34eb1224c",
- "bytes": 2615
- },
{
"path": "PUBLICATION_STATUS.md",
"sha256": "8a9d9877ce9645b52b9b313760dc89d337a9888c9824f12edef07fb2f7e91565",
@@ -73,13 +53,13 @@
},
{
"path": "README.ko.md",
- "sha256": "eb73983b848fc7fdd1a1b8053951cd218bdb2ca9b0770b3f8287ec5e94798aa8",
- "bytes": 10858
+ "sha256": "e92c248d8605ecd6f502710e89293146ad4e63db88f20105fa91115e615b38e7",
+ "bytes": 11855
},
{
"path": "README.md",
- "sha256": "1a1541d7c71430231212ad5654caa15d631c47dca4462ea55d40e1fe441728a9",
- "bytes": 9533
+ "sha256": "706bb9d807e7b339b0f5d18ee52a8ec5133388fccc0950f1140230665d811535",
+ "bytes": 10429
},
{
"path": "THIRD_PARTY_NOTICES.md",
@@ -133,8 +113,8 @@
},
{
"path": "apps/web/src/App.tsx",
- "sha256": "e0042698d6e4079e2704c04cffd0daeeae46bc94aef902dd7e5cb9880e6abde3",
- "bytes": 893
+ "sha256": "79d0d4978f19ed1f31f128a744e9c38af52970ea710f9edcf1a6e98343fdb6d6",
+ "bytes": 922
},
{
"path": "apps/web/src/api/capabilities.ts",
@@ -171,6 +151,11 @@
"sha256": "eed636eb9d4807d6737309095d3b2d8ef38ed83f90ace9d08628ec0acc524602",
"bytes": 660
},
+ {
+ "path": "apps/web/src/api/fixtures/iris-judgment-excerpt.json",
+ "sha256": "6b6501dbd8fef4638def34ef38f93c401e98fb2e74eaa4585651580f61dc6fe9",
+ "bytes": 20314
+ },
{
"path": "apps/web/src/api/fixtures/qa04-cited-spans.json",
"sha256": "bd8c7f2cb1b9302e709d2029b0ee34475f8eb68ca63f46aea3645f6fc64151e0",
@@ -193,8 +178,8 @@
},
{
"path": "apps/web/src/api/history.ts",
- "sha256": "00cb14cbc1ba89a303e1e225b01859fd86af51223297ded489ba2c8d4a0090fc",
- "bytes": 7528
+ "sha256": "ce2fbeabbc282f8cf632fc29399a677f3655df70f994039a6aa7b2c9389d07d3",
+ "bytes": 7608
},
{
"path": "apps/web/src/api/historyComparison.ts",
@@ -208,13 +193,13 @@
},
{
"path": "apps/web/src/api/historyModels.ts",
- "sha256": "d48a2e56cfdf550e885baa1bda91817c6e74925696148ccf216a895da27b5b20",
- "bytes": 3471
+ "sha256": "fbd648972568fc9c8d3d7a30ee7ea2c00e946eb80410f402e611af3315b96167",
+ "bytes": 3958
},
{
"path": "apps/web/src/api/historySchemas.ts",
- "sha256": "e11617d12074cea36e376486b893d73348a771e3dff00f17f9de13fa633792c4",
- "bytes": 3715
+ "sha256": "eff96431efe1199a0901b86860f87ebb4fcbe0bcd400ce3cc97e3942d9bc5404",
+ "bytes": 4263
},
{
"path": "apps/web/src/api/historyServerContract.test.ts",
@@ -268,13 +253,13 @@
},
{
"path": "apps/web/src/api/research.ts",
- "sha256": "c9b06b67b7f4fadf56fd8623932c9c55f1c21d7a5ce8660cc5c4dfccfed363ae",
- "bytes": 9478
+ "sha256": "5217c5800b69f85bfadb5bd50ff3c175d2b797b915a8349e5634b36b1760975c",
+ "bytes": 10429
},
{
"path": "apps/web/src/api/researchFollowup.ts",
- "sha256": "29468272e9c35b63fb076925bcc52c6590d9fd9f6589c38eb6a6a3a90e974b08",
- "bytes": 7221
+ "sha256": "6816d683795cf713a7d40d0f16b39024daee9b84b22d74f24103324065a5cad8",
+ "bytes": 8236
},
{
"path": "apps/web/src/api/researchProgress.test.tsx",
@@ -286,6 +271,16 @@
"sha256": "65ed5362638fbdf9da1a5bba14976cce49f597ed7d11cb311b51d45579651eac",
"bytes": 33012
},
+ {
+ "path": "apps/web/src/api/researchStatusRead.test.ts",
+ "sha256": "0c1a9cbfbf8291edc53ffe50d99b4547653cc5d3e5d72f80758f40aeef2dc649",
+ "bytes": 2622
+ },
+ {
+ "path": "apps/web/src/api/researchStatusRead.ts",
+ "sha256": "c61efd66fc6c68975a51631226a8331714e90a0abafd10aa6f69604335947dd1",
+ "bytes": 1186
+ },
{
"path": "apps/web/src/api/restore.test.ts",
"sha256": "3be583e0164d621e730783908b9b8ad8d4b4f03cb33acf4d9d4f368af8734cc9",
@@ -338,8 +333,8 @@
},
{
"path": "apps/web/src/api/rpcClient.ts",
- "sha256": "86ae7120f3726cb36912aef03978aef47baeb9d39a3c7d34e84592e08b9651f9",
- "bytes": 6505
+ "sha256": "d4c8f7f230f6ff7144cfb3a6e52fe40c8566055c379e1ed1beed630a72b9bbc9",
+ "bytes": 6567
},
{
"path": "apps/web/src/api/rpcTransport.test.ts",
@@ -353,38 +348,123 @@
},
{
"path": "apps/web/src/api/sourceTime.ts",
- "sha256": "dd7d5b647cbac473673ac3892a16bf2e1ed9beb61e5321dd47d7ccc2a0e0aeeb",
- "bytes": 2502
+ "sha256": "922044f244905b243fae73807a93792d9ca7bb8d750d603cc205d31a7d019b6e",
+ "bytes": 2972
},
{
"path": "apps/web/src/api/timelineExample.ts",
"sha256": "1d8ac2a17ffa2a86720c472ce953d89bb2e1e5d99d614e5b86ebec1a0b2d4cbc",
"bytes": 8913
},
+ {
+ "path": "apps/web/src/components/ActionCompare.test.tsx",
+ "sha256": "8b26648c751ba842c6b40bf7522548cd4fcd426cd985b179378816e3a7da20f7",
+ "bytes": 5168
+ },
+ {
+ "path": "apps/web/src/components/ActionCompare.tsx",
+ "sha256": "aff7854ae5d8b560c3199e4b510879b2991fd2687787140d8aede188b5ffa30d",
+ "bytes": 5303
+ },
+ {
+ "path": "apps/web/src/components/ActionCompareApproval.test.tsx",
+ "sha256": "263b69b8545050ab59f872605bb7e3a67655712f683a26046984954957da6da5",
+ "bytes": 3171
+ },
+ {
+ "path": "apps/web/src/components/ActionEffort.test.tsx",
+ "sha256": "6c853309bae39c97c85d6b315b5706a93981df9bd8501e8edf1632403dda2f72",
+ "bytes": 5361
+ },
+ {
+ "path": "apps/web/src/components/ActionEffort.tsx",
+ "sha256": "720b5b8b8e773fc0c2ad5e0f678b1224df10cca799d25ff58ab1072e647f1b63",
+ "bytes": 6434
+ },
{
"path": "apps/web/src/components/AnalysisWorkspace.tsx",
"sha256": "beb669b6df7443dc218300579906613e61671bec2b57cc64df9d327fe0ec256e",
"bytes": 7198
},
+ {
+ "path": "apps/web/src/components/AnswerBody.tsx",
+ "sha256": "be2daee6eed12f38a08fb4cef7fd0133a4679fa3520ad510421621ba93689f25",
+ "bytes": 1182
+ },
+ {
+ "path": "apps/web/src/components/AnswerCitations.test.tsx",
+ "sha256": "1095e55b7d501343a120c18a7f52db8a3dc9f7169caf05c790e39cfcee211915",
+ "bytes": 5868
+ },
+ {
+ "path": "apps/web/src/components/AnswerHold.tsx",
+ "sha256": "e682f1265361256d4f5e433fa4e7098eba2e094c87fc38c9ce597019f9ccfb6e",
+ "bytes": 1561
+ },
+ {
+ "path": "apps/web/src/components/ApprovalCard.test.tsx",
+ "sha256": "b3427e26f27c43168ec3af555be2f8034b099df35c45b127560931d171aac29d",
+ "bytes": 10073
+ },
+ {
+ "path": "apps/web/src/components/ApprovalCard.tsx",
+ "sha256": "f7f785c484a7a17a28883a6a88880824e74916a39d90f9c8d6d8a9cedbd73927",
+ "bytes": 3999
+ },
+ {
+ "path": "apps/web/src/components/ApprovalSection.tsx",
+ "sha256": "60e8441c6af7d4971832fb1cc6eebb18b0fa2d603028644b974d86c01982cace",
+ "bytes": 3590
+ },
{
"path": "apps/web/src/components/AuditTrail.tsx",
"sha256": "91e6d770cd55fe85c687dde67d4b92c125454b11f8e7ca07d8e2303cab989904",
"bytes": 17633
},
+ {
+ "path": "apps/web/src/components/AutoRetrySwitch.test.tsx",
+ "sha256": "af9da06d8bbda53c0466b106a9b8ee8f7149cada6dc47c1a2d1d9238ee5b742d",
+ "bytes": 3343
+ },
+ {
+ "path": "apps/web/src/components/AutoRetrySwitch.tsx",
+ "sha256": "3b9fbf56775473d6d0f05161fb265faf7229c9d586c85b8cef02d39f45c15727",
+ "bytes": 1982
+ },
{
"path": "apps/web/src/components/CapabilityWorkbench.test.tsx",
- "sha256": "6bb31dfdae68a5282f7c3a19d277383ef12a3cca7ff227784618b541a53af1cf",
- "bytes": 7720
+ "sha256": "2041ac3ee4b92c1f734e0aa0dcc02553cb36158f1a5f2d21337292777ba47c51",
+ "bytes": 7758
},
{
"path": "apps/web/src/components/CapabilityWorkbench.tsx",
"sha256": "057a0064da214f98cc9289530d37f6e0ca3964e8da6416a41cc2c4c276144397",
"bytes": 8163
},
+ {
+ "path": "apps/web/src/components/ClaudeCodeHelp.tsx",
+ "sha256": "55cb6cb26dbda674e69b6df93fdcb0034b4a42b57d5cef789173721496486772",
+ "bytes": 590
+ },
+ {
+ "path": "apps/web/src/components/CodexToolHelp.tsx",
+ "sha256": "a353746e797b15f0dc44b08f1f84e0d798fa8f231a02eee2ae1c53e90f1ffb5b",
+ "bytes": 578
+ },
+ {
+ "path": "apps/web/src/components/ConfirmedFacts.test.tsx",
+ "sha256": "f85bb81dcf6c91774d76eda5193e34f3752653debed2140c990cdeb52694ebda",
+ "bytes": 3649
+ },
+ {
+ "path": "apps/web/src/components/ConfirmedFacts.tsx",
+ "sha256": "5f799a7c2ef75cdc6964222dde53865fd83b4a20328fe843e97bc5de9a179359",
+ "bytes": 3107
+ },
{
"path": "apps/web/src/components/ConversationTimeline.tsx",
- "sha256": "3657facd583e2ce5ef58469b58eda9df81415da759dd730dd44fa12d800d8cc8",
- "bytes": 8149
+ "sha256": "a4d519a26a3d38c362079df77247568571e40a0a5440809ec0d771659508880f",
+ "bytes": 9452
},
{
"path": "apps/web/src/components/ConversationTimelineDeniedRetention.test.tsx",
@@ -418,59 +498,104 @@
},
{
"path": "apps/web/src/components/EvidenceRail.tsx",
- "sha256": "f4ffd0fc364d36db5f16fc89761ef76b30c2eac775410531ced2dc4319a31be4",
- "bytes": 3702
+ "sha256": "b85594f9b5c963f816b79013741c587696fe585506a4185875988088424c7b0e",
+ "bytes": 4675
+ },
+ {
+ "path": "apps/web/src/components/EvidenceUseSummary.tsx",
+ "sha256": "2aa9b1e216a5cc502288464feab1bfda0d5e8175e1d1b95230dba6d0c509ef52",
+ "bytes": 1145
},
{
"path": "apps/web/src/components/FileConnectionPanel.tsx",
- "sha256": "4315886baf1f3767c01bf4068f7b56c2683f30861ce28ff35226e18dfea9f79b",
- "bytes": 4072
+ "sha256": "88f4e972e9b621008c32f84b52336b17bd6ceca2abc351d98b1ff4cfa465f841",
+ "bytes": 4138
},
{
"path": "apps/web/src/components/FirstRunSetup.test.tsx",
- "sha256": "e83ee7ef88bceb39052132fe430ee509e4d5a65881192db26eb94be6f1a5fd99",
- "bytes": 71616
+ "sha256": "bb34dc110bf3b80795720a3a12964d88ee1aa21fe1dfcd33168be751cbd36da2",
+ "bytes": 94889
},
{
"path": "apps/web/src/components/FirstRunSetup.tsx",
- "sha256": "a2ba1e16b42c046328eee55613dd331b0688d89f5614685bf4e9830d73a0ced3",
- "bytes": 13858
+ "sha256": "6c19dbb637486d54cceb7bca4d848f7e2d2313fa616fbae968b2c2d677a5b3f4",
+ "bytes": 16736
+ },
+ {
+ "path": "apps/web/src/components/HypothesisCompare.test.tsx",
+ "sha256": "efd9ebb1f547365b188d4d0c34efb93dc945676eca6baaeb316ecfd7476dd818",
+ "bytes": 6869
+ },
+ {
+ "path": "apps/web/src/components/HypothesisCompare.tsx",
+ "sha256": "9464c69cf662496bcd9bb55655d038999fb3e89e1c350024e4e351206970cf22",
+ "bytes": 4566
+ },
+ {
+ "path": "apps/web/src/components/HypothesisCompareLive.tsx",
+ "sha256": "a34d314862e74e1d7beaf9c8a7dd63915991f0963fac3e19b9e54e2001cefae8",
+ "bytes": 3229
+ },
+ {
+ "path": "apps/web/src/components/HypothesisReview.test.tsx",
+ "sha256": "9ed16b30a10acb02c7f31013c67e6133c9f0536f5d6d8a9615122800ada654fa",
+ "bytes": 8119
},
{
"path": "apps/web/src/components/LiveProjectWorkspace.tsx",
- "sha256": "2431b1e9204d90a2ce752fcbc29038e241c63a58973788d7fd6527c1cd3f6163",
- "bytes": 38054
+ "sha256": "6e6124f2532e262f4a15fbc2e8c4b0730950eff840b5ebb4e656b8b9444f5e92",
+ "bytes": 39897
},
{
"path": "apps/web/src/components/LiveProjectWorkspaceRestart.test.tsx",
- "sha256": "ccdccf5048237ecf644a38704ba0c37486401430a09a9f5db48c27338ebd1fa1",
- "bytes": 33703
+ "sha256": "aee0372a08b8a555d10af98fe86115e17e748a301c94e22f1270380797975e28",
+ "bytes": 39368
+ },
+ {
+ "path": "apps/web/src/components/MemoryEditDialog.tsx",
+ "sha256": "ac0b815bf17f503ab95b598c760f51ed31bfea32d977f669c61530d2ae795a03",
+ "bytes": 2579
+ },
+ {
+ "path": "apps/web/src/components/MemoryInjectionSwitch.tsx",
+ "sha256": "c18cab413d4f0adf4d1ba205ed0e1fa069bf245e8fa36c15f81367db8c5b7deb",
+ "bytes": 1807
},
{
"path": "apps/web/src/components/ModelCredentialPanel.tsx",
- "sha256": "11fcb02fba9f3f53371254f6e429a8f259e7f331078606fab66498aeda38b2f6",
- "bytes": 6780
+ "sha256": "b41136c0bffe6f48e5cdd7f4cbe9267184d36f685df4bf4318534406f2687fed",
+ "bytes": 7094
},
{
"path": "apps/web/src/components/ModelSettings.test.tsx",
- "sha256": "dfdcfa8e0a8c59154668d083f06dee1f553cc850effd8011a31e46d1831a221c",
- "bytes": 19728
+ "sha256": "705a546a1c3f8c619f0764be312dde531c9ffa2c616f149d6a90afda5d346c23",
+ "bytes": 23231
},
{
"path": "apps/web/src/components/ModelSettings.tsx",
- "sha256": "aee3a39d55697309abef759ba7c1cd719ef6e00cc0771216cdf654de8b77c243",
- "bytes": 8680
+ "sha256": "077ccdd32ba9f1a6630fbb9feaf2d96d704b7394ecbec74a2914b76380ca6616",
+ "bytes": 9721
},
{
"path": "apps/web/src/components/OAuthLoginFlow.tsx",
- "sha256": "48efafde25e52f602abd460eed51bf95cfc56f469682d941d372482e217ecbf2",
- "bytes": 16422
+ "sha256": "a94bc861fcb5403c5575f0424bcdf9f6548feb87d1b8eac090415d6a31829293",
+ "bytes": 19518
},
{
"path": "apps/web/src/components/ProjectLanding.test.tsx",
"sha256": "7320a8b86b880d32d0bf66d06452c0ca1ecaf72b743fc5c5c76786b9c1758cea",
"bytes": 1103
},
+ {
+ "path": "apps/web/src/components/ProjectMemoryPanel.test.tsx",
+ "sha256": "4db7598f749ef9475e6eba4e5074571d077e4ab2559cd2a86ece3893842366c8",
+ "bytes": 15907
+ },
+ {
+ "path": "apps/web/src/components/ProjectMemoryPanel.tsx",
+ "sha256": "afd324cdfa02d940900be179fbe82b0b8c051563a6cb74342226135aa248714b",
+ "bytes": 5636
+ },
{
"path": "apps/web/src/components/ProjectWebSetup.test.tsx",
"sha256": "b91b32ca278b3843bf45a57cccd86ab48b7af0f68c4e77e36fe2a4b01d9d81ca",
@@ -508,8 +633,8 @@
},
{
"path": "apps/web/src/components/ResearchDetailPane.tsx",
- "sha256": "addec8f6d24597632d1af3f08ba9bad36fac8a0988cfb90a84e9540e5eaadb16",
- "bytes": 5574
+ "sha256": "9583f90ba77ab4d32c3262a73ddc356b9d81b7da9b0338d45a0fa89390a41697",
+ "bytes": 4650
},
{
"path": "apps/web/src/components/ResearchEvidenceDetail.test.tsx",
@@ -518,8 +643,13 @@
},
{
"path": "apps/web/src/components/ResearchFollowupSummary.tsx",
- "sha256": "e5e506787f0df99e051f555bbc6d5b5e344462b3e40296717c8c58ddbe6b7e2d",
- "bytes": 6200
+ "sha256": "5485d544af139419f1ddbe782ba51ad7702256992754546167b56a72c563ab38",
+ "bytes": 9126
+ },
+ {
+ "path": "apps/web/src/components/ResearchHold.test.tsx",
+ "sha256": "4c99039a3f0189fb080f4dee407173673d4749bd6dca560d5aa7f33cc5615ad1",
+ "bytes": 5848
},
{
"path": "apps/web/src/components/ResearchLiveProgress.tsx",
@@ -533,13 +663,13 @@
},
{
"path": "apps/web/src/components/ResearchResultCard.test.tsx",
- "sha256": "d03c79df3df70d69b30e9ccd663efc48e1a150f4f4b522a6d907657c21ce2dc8",
- "bytes": 3315
+ "sha256": "0bd9adaf182852dad6612a32420f58966cb3396252ef4b871cc32cf1f7f2d71f",
+ "bytes": 8807
},
{
"path": "apps/web/src/components/ResearchResultCard.tsx",
- "sha256": "e8caea9830368a7aa5c8e9a0d8ceb1bbfe1de5efca8d1ac3b5bc8a5a86279c25",
- "bytes": 7541
+ "sha256": "067ea374b695d8fcc329f5f50211d01c75ade6091c3a8ae7ef207f8ef04d07d8",
+ "bytes": 11179
},
{
"path": "apps/web/src/components/ResearchSidePanel.tsx",
@@ -548,13 +678,28 @@
},
{
"path": "apps/web/src/components/ResultEvidencePanel.tsx",
- "sha256": "b3d41131419b9498246547244e48679ece8a5ddfec69819bc1f7d1e5f25a5a3f",
- "bytes": 3912
+ "sha256": "46ac8d2966cfd8533ebae696ad7f232d1393d9d5da04d32d0b6fc9bab2fc7a11",
+ "bytes": 5106
},
{
"path": "apps/web/src/components/ResultExample.tsx",
- "sha256": "a873379eec3b8671c8cee33a7e48b32bdfaf1b152e0d00a867091e9981b88dd5",
- "bytes": 5584
+ "sha256": "6b3c1fd323ba60ad8b87e7ef2551305b1f5a709d89427c793e71e72324cf4017",
+ "bytes": 7856
+ },
+ {
+ "path": "apps/web/src/components/ResultScreen.test.tsx",
+ "sha256": "516aab01871f28251804e92d223c5191a418e24edd849944acded63844e10473",
+ "bytes": 11101
+ },
+ {
+ "path": "apps/web/src/components/ReviewDialog.tsx",
+ "sha256": "2d05f30f317ae9e15ab959652c7ad87ce43fff30015bbb141756717a606ffe2c",
+ "bytes": 2500
+ },
+ {
+ "path": "apps/web/src/components/ReviewStatusLine.tsx",
+ "sha256": "c00f537d577a0a7d147f1e33981aafb2f38a9b7ddd475462431aea2fb9ebc5e3",
+ "bytes": 1605
},
{
"path": "apps/web/src/components/SourceCitationGate.test.tsx",
@@ -578,8 +723,13 @@
},
{
"path": "apps/web/src/components/StatusBadge.tsx",
- "sha256": "68a6017de6907f99ee691fdf753e67694e6c80c026ce9952d102583f084dc6ed",
- "bytes": 662
+ "sha256": "8ba883cf96879aa2254fd9d4919a4d33c0fc93d9e9bc77c8e653a58dc521c977",
+ "bytes": 742
+ },
+ {
+ "path": "apps/web/src/components/ToolInstallHelp.tsx",
+ "sha256": "6d229ab8daf3859e02897805027f84a8dcdaf2e90f6d1b733de14983a1c397be",
+ "bytes": 2968
},
{
"path": "apps/web/src/components/UsageSummary.test.tsx",
@@ -593,18 +743,68 @@
},
{
"path": "apps/web/src/components/XaiDeviceLogin.tsx",
- "sha256": "b75dcbe7d6d73416c670bb9c23b3ea936eae65bd45476a6a77ece7ee672b63be",
- "bytes": 13805
+ "sha256": "cf3a225903c783285919a14876a60470669e6f96cff0612327efc9547f1abc0b",
+ "bytes": 14439
+ },
+ {
+ "path": "apps/web/src/components/answerText.test.ts",
+ "sha256": "0c49d8c9a771e20f2b1cab22ecf67da4dc759ac9252f07c050dbe57677c89fa2",
+ "bytes": 2377
+ },
+ {
+ "path": "apps/web/src/components/answerText.ts",
+ "sha256": "1c95cad7462705328f0783c36195f74f9361baecedc40ac6ed76cd90646255e7",
+ "bytes": 2461
+ },
+ {
+ "path": "apps/web/src/components/approvalView.ts",
+ "sha256": "9443637c0ecced38821de70bcf223db0bef3773e120e7d835bdfae83eafc57f8",
+ "bytes": 3495
+ },
+ {
+ "path": "apps/web/src/components/connectionCopy.ts",
+ "sha256": "81ecddef774b7a17eadf1719fd40e62b978d54f677a5f7da8edcde18d87cbd5a",
+ "bytes": 8528
},
{
"path": "apps/web/src/components/credentialAuthMethods.fixture.json",
"sha256": "b4608b7147622b28852b715bfa1898bb063b843cabe2e3204f473827be32094c",
"bytes": 4624
},
+ {
+ "path": "apps/web/src/components/effortEstimates.ts",
+ "sha256": "19057d4a4ba4673f6e8686c48f794e50c29d50d4a74a81a628002e52baf7903f",
+ "bytes": 434
+ },
+ {
+ "path": "apps/web/src/components/evidenceUse.ts",
+ "sha256": "8f23ec66acb67f5b8efe7f0aa6856c626ddca77030ae050c233c9b9523d24935",
+ "bytes": 1823
+ },
+ {
+ "path": "apps/web/src/components/history/CriteriaChange.test.tsx",
+ "sha256": "89ad421e14754fa59fcd1d722a24e8c2143f842530602af38dbff8caa5302cb9",
+ "bytes": 4511
+ },
+ {
+ "path": "apps/web/src/components/history/CriteriaChange.tsx",
+ "sha256": "2a2053a1e48efd6f3a763211bb89723ff017554285893015cb5fef798a56fab5",
+ "bytes": 4056
+ },
{
"path": "apps/web/src/components/history/DecisionDeltaComparison.tsx",
- "sha256": "216f72c308896ef698db4cd991987605d80926130cfef45e848bc859f5b77469",
- "bytes": 12140
+ "sha256": "038348b58ea6460149f53a9dc322987d83819b6dc93428e1015e980d0897a6b5",
+ "bytes": 12632
+ },
+ {
+ "path": "apps/web/src/components/history/HistoryChangeSummary.test.tsx",
+ "sha256": "d38697c24be276f1e47e53c80edf9d1ea9d100c14857d1b38610e1043b0fea9e",
+ "bytes": 5371
+ },
+ {
+ "path": "apps/web/src/components/history/HistoryChangeSummary.tsx",
+ "sha256": "f4a9947ab08eb58bb79ff756dd24b17851c0b3d9c1a64c93742f16e12ec9e5af",
+ "bytes": 2964
},
{
"path": "apps/web/src/components/history/HistoryContent.tsx",
@@ -613,8 +813,8 @@
},
{
"path": "apps/web/src/components/history/HistoryDetail.tsx",
- "sha256": "2ba21c90efea9c7bcd57d0feb1c7a8a13c71f516342006ff6d10f88c9c098696",
- "bytes": 8185
+ "sha256": "41d420e20eafed7a896bd3c85583e9989e764b585389fa92fcfca1375fc7efee",
+ "bytes": 8461
},
{
"path": "apps/web/src/components/history/HistoryExample.tsx",
@@ -623,13 +823,13 @@
},
{
"path": "apps/web/src/components/history/HistoryTimeline.tsx",
- "sha256": "0d35c1e19c90ecf0917d7fb4143ba1061f16fbe7ef9e98963277cba23a868a94",
- "bytes": 4403
+ "sha256": "7118c15559e63416f4665a60e375b60bf3c3a95e1daee60e339a41dc7155ba35",
+ "bytes": 4511
},
{
"path": "apps/web/src/components/history/ProjectReviewList.tsx",
- "sha256": "424003d3d40d8fd50b5a0a81c039826ef430cb916ace89151e1971f7f862ab83",
- "bytes": 3328
+ "sha256": "ec99ea673cb1c5582c23d9148b71e5fdd59fd0260b7a8c37ed9aea9dd5248925",
+ "bytes": 3522
},
{
"path": "apps/web/src/components/history/ResearchHistoryWorkspace.test.tsx",
@@ -638,8 +838,8 @@
},
{
"path": "apps/web/src/components/history/ResearchHistoryWorkspace.tsx",
- "sha256": "51741a9c4355f1e068bd22a8e478ce870928f63d1683ce37ef0152b885362ee2",
- "bytes": 8807
+ "sha256": "510754838897c4ed2a33eb1030f65ff372bf5f36c3b7d907259552b357d212fb",
+ "bytes": 8873
},
{
"path": "apps/web/src/components/history/RestorePreviewDialog.tsx",
@@ -658,14 +858,24 @@
},
{
"path": "apps/web/src/components/history/historyPresentation.ts",
- "sha256": "7fea7b0c3f227731f727a12cc44f84821b461bdf2011e1fab1de7f481afe67f9",
- "bytes": 1942
+ "sha256": "c50deb36bdbc718d8997b53e1faaaa2906367ba427b4b3e5df1b38a02347ff04",
+ "bytes": 2165
},
{
"path": "apps/web/src/components/history/useRestoreFlow.ts",
"sha256": "f3bc34423dce12301f50dd47af2b842d07f5700db42038c70ac375f8fb69f710",
"bytes": 4569
},
+ {
+ "path": "apps/web/src/components/hypothesisView.ts",
+ "sha256": "4f4263a00a3e66e6de4d46bb62fe05c1ad02d9dfce4d4d1a3adffbfeb32f1791",
+ "bytes": 2728
+ },
+ {
+ "path": "apps/web/src/components/judgmentReview.ts",
+ "sha256": "eb5f09c056868f9063200cc58aead6d8dcffa5361930d6bde77e4e74db8ba982",
+ "bytes": 3677
+ },
{
"path": "apps/web/src/components/localCredentialWorkspace.ts",
"sha256": "e219b8695eb51419c4e502647dca6aa564516ec6baebdc0fb7b6435e30f9149f",
@@ -676,10 +886,40 @@
"sha256": "f51f853cbdb9911850ce15291766019cb0e68b5bdad46a7322054cfb2776191d",
"bytes": 327
},
+ {
+ "path": "apps/web/src/components/memoryEdit.ts",
+ "sha256": "f5315cff2ae5d02e2488ef8330287c5e48889ae92186aadf507fd5747e716301",
+ "bytes": 3515
+ },
+ {
+ "path": "apps/web/src/components/modelCatalogPresentation.test.ts",
+ "sha256": "b5d2fa2e8a1d3647de8fd04f166bbb0570099c5617b20a367977a30d7236f4ec",
+ "bytes": 2765
+ },
+ {
+ "path": "apps/web/src/components/modelCatalogPresentation.ts",
+ "sha256": "3f7452bd0cc94516ad5cb8605a5a54242d8f1d728db5a93881a67099e71bf285",
+ "bytes": 3446
+ },
{
"path": "apps/web/src/components/modelCredentialPresentation.ts",
- "sha256": "6a28d5ba08b09c1c60f558679c3737d40906a2960daa3d8d783ec3aeea900dd2",
- "bytes": 11803
+ "sha256": "56f9cd0351756f39bfcda34b57e0040319ec6783b9376ed4ca24efdae195199f",
+ "bytes": 14523
+ },
+ {
+ "path": "apps/web/src/components/resultUsage.ts",
+ "sha256": "27745fd9889d75c6a508473d6d7014e1840476127145b36922e76f5107affe9e",
+ "bytes": 2169
+ },
+ {
+ "path": "apps/web/src/components/statusLabels.test.tsx",
+ "sha256": "61408be219f66de4f27e30c6bf133e29a218d8fac53fd5648db9a698c25bed01",
+ "bytes": 2635
+ },
+ {
+ "path": "apps/web/src/components/statusLabels.ts",
+ "sha256": "bfad2969efb8481fb8b369371f840c48963fdf43683cb50f8191e156220a24d6",
+ "bytes": 9480
},
{
"path": "apps/web/src/components/useConversationReadProtection.ts",
@@ -688,8 +928,8 @@
},
{
"path": "apps/web/src/components/useResearchSession.ts",
- "sha256": "8dacee54eab1a2085da12ce7e2a01db3974ee11fcf95189789ab613d698bccf3",
- "bytes": 11565
+ "sha256": "83d61ea7b65f5ef3988f98ffc5601a35819ea9f66b7a88948e35b5bd611f3477",
+ "bytes": 12956
},
{
"path": "apps/web/src/components/useResultEvidenceProtection.ts",
@@ -713,13 +953,13 @@
},
{
"path": "apps/web/src/sourceDisplay.test.ts",
- "sha256": "ecc5a5e77af42eb8def58f0b8d153a0a394aaf0e9cc6b7f29ca00bd511c35cee",
- "bytes": 588
+ "sha256": "9e95e927122c48ea24490a539643c1bd1f2a3bbec85f8c5df2cddbde82cfd855",
+ "bytes": 1057
},
{
"path": "apps/web/src/sourceDisplay.ts",
- "sha256": "65187edac7a811ac6e815f913bd44fca68617de67cf08d5d5d6dc2906995ede4",
- "bytes": 476
+ "sha256": "1e45715ab12bae8545799281a5cd8eecfb0bea3eb0ad1b01f64b58384269b995",
+ "bytes": 680
},
{
"path": "apps/web/src/styles.css",
@@ -733,8 +973,8 @@
},
{
"path": "apps/web/src/types.ts",
- "sha256": "9dd46a74169cc1e9e6f0385f867b65f47ca28efe8e2470a61a0a15be6937bdc4",
- "bytes": 4410
+ "sha256": "92d6352426103345e11be02356bef4bec0ff7ca0f185ea3934cd9788451fc27b",
+ "bytes": 4441
},
{
"path": "apps/web/src/vite-env.d.ts",
@@ -743,8 +983,8 @@
},
{
"path": "apps/web/src/workstation.css",
- "sha256": "1a6a23ffd0b2ba2b622b596942f34949f82f13da8bcb609b443785c54f20ccc1",
- "bytes": 50286
+ "sha256": "5db0d589f58fe4b5addebb8a123a5f58c725b4f92937533417bdf47a0fb45d16",
+ "bytes": 55489
},
{
"path": "apps/web/timeline-preview.html",
@@ -778,8 +1018,8 @@
},
{
"path": "config/atomicity-paths.json",
- "sha256": "153331d1357e77d326ba0618ba842f628197398ba3370a7c6c87677d02552ea6",
- "bytes": 3293843
+ "sha256": "61c8bffe0b051521da8a28e14c81b5fbb478daf52c63e0897da716214923e351",
+ "bytes": 3263708
},
{
"path": "config/atomicity-requirements.json",
@@ -828,13 +1068,13 @@
},
{
"path": "config/inactive-development-hook-tests.json",
- "sha256": "853ac31fb86eabb97d3899e09aebc38089a2a81842934d2e9015ffc7f83493cc",
- "bytes": 15237
+ "sha256": "4026cdd397b72752aab2422bd14e7898b772908c0075e8ddf1aa66a879356895",
+ "bytes": 25987
},
{
"path": "config/module-responsibility-budget.json",
- "sha256": "de76514ad3700855042e2ba98f5743902ebedfcde46a357cdf12f303310ad841",
- "bytes": 4709
+ "sha256": "1d5078767c4d4b093bc02f6f5e4bb2f5a2d720ad6dc2daf8bd9a3bd7a979102d",
+ "bytes": 4708
},
{
"path": "config/post-d4-baseline.json",
@@ -898,8 +1138,8 @@
},
{
"path": "docs/INSTALL.md",
- "sha256": "aaecd9059b7c8f9a09880c20c67cba3812fee3a5bcc02514eae9475b59ea7284",
- "bytes": 16406
+ "sha256": "79779cd2081432e913e5a5d1da20c326d3264296ef4c01dfb386e5567f4c5d73",
+ "bytes": 17803
},
{
"path": "docs/KNOWN_TEST_FAILURES.json",
@@ -908,18 +1148,18 @@
},
{
"path": "docs/PACKAGING.md",
- "sha256": "41a0fa720f13d383581e8579856e72b0703bfd4576b44d8e7cfd2b308a666fbb",
- "bytes": 3284
+ "sha256": "ebf8a1551d5baab8a0dad2d4c6ece9bc4e6c80c804e6f2d097e621760a0487a5",
+ "bytes": 3276
},
{
"path": "docs/PUBLICATION_REVIEW.md",
- "sha256": "9d6f055fbaaf9c082dc401f0ed2708d2c45bc1e281e79a6df7db8e24d8c98be6",
- "bytes": 3409
+ "sha256": "f81ca5e34b0262e2f44c65b701ad96706dc4e86b762d7feb91b5b88e3b9317d5",
+ "bytes": 4102
},
{
"path": "docs/VERIFICATION.md",
- "sha256": "be127e5f1a5ec0b5e6e9bdc971f208df9fd42c2d0f201d950decda033efccfaa",
- "bytes": 22205
+ "sha256": "be9abd852759fa06e9cb24d43105779ffb6a0005ebf2173cff73915259b84f7b",
+ "bytes": 24775
},
{
"path": "docs/architecture/agent-execution-rules.md",
@@ -953,8 +1193,8 @@
},
{
"path": "docs/architecture/extension-point-catalog.md",
- "sha256": "45c75583a9d3c267ebddac39d02ae6cd7047b46feca2022b826afd2af8cb2968",
- "bytes": 5873
+ "sha256": "2841af6aeee35a7ea835a13e077dc701230b1d0fe72d349e341f3ed35e22c2ed",
+ "bytes": 5803
},
{
"path": "docs/architecture/governance-history.md",
@@ -968,13 +1208,13 @@
},
{
"path": "docs/architecture/model-auth.md",
- "sha256": "d8f17beb70c4e3a7aa2d87d43c6f7d5e4a35468f7ae27f69d23f070525e72ae0",
- "bytes": 12377
+ "sha256": "5d165dc379b3863faf39915c7930d40f479207c7c802ff2353efed01874405c5",
+ "bytes": 14409
},
{
"path": "docs/architecture/model-settings.md",
- "sha256": "e03f68d97db4abd8b6eb5497ecb0c2457dad5122848e115d0eebf9be8118f57b",
- "bytes": 11886
+ "sha256": "3fcfd2d374f746f30bc9d0d96660d944417dc332f16c4f9e356ab21c7c23ec51",
+ "bytes": 11703
},
{
"path": "docs/architecture/nfr-evidence-matrix.md",
@@ -988,13 +1228,13 @@
},
{
"path": "docs/architecture/public-web-acquisition.md",
- "sha256": "28b0df07eef94d4b693cb7101c2a7e41aba2ad16035d2a20539d51f21d2e3097",
- "bytes": 4162
+ "sha256": "fdbea1f6e6e9d9daf92c4348ef72b3526f123ae9f9230e64a80f859bde479712",
+ "bytes": 4153
},
{
"path": "docs/architecture/research-context-completion-contract.md",
- "sha256": "b5be8c2bfe4f2cb4ec282a6ac0fb4f4fa76fa0a7fc7f6aa0425ed845868edd5c",
- "bytes": 4812
+ "sha256": "43601e8613431e53ab1b1b53db6c3ae6555868caf3a6dbfac009b9387525597d",
+ "bytes": 7991
},
{
"path": "docs/architecture/research-dispatch-controls.md",
@@ -1011,6 +1251,11 @@
"sha256": "7d0e527db936204b9d97b1a5d1b9e08e6ae794fde3147752d6ccf4a931a2e5a0",
"bytes": 3411
},
+ {
+ "path": "docs/architecture/rpc-method-catalog.md",
+ "sha256": "80a3b4b5f8a552a656b83ddf609285b3bf7be58cdd73cd08dc4dfb1e47c9d13f",
+ "bytes": 12269
+ },
{
"path": "docs/architecture/user-activity-projection.md",
"sha256": "61161e87ed209d5884de40f848f3aceab714cb33bd6f77dd308bef2e2eba7a4e",
@@ -1018,8 +1263,8 @@
},
{
"path": "docs/architecture/verification-profiles.md",
- "sha256": "4a7a1f858d780e86d9dae5ef0512aef7ea73e2db39b7fec91d8939238815a176",
- "bytes": 5545
+ "sha256": "05d1c7d5d776b23f27536c711dee5fdbaf89fec0472afccee47e6c5263f5bf34",
+ "bytes": 2656
},
{
"path": "docs/brand/README.md",
@@ -1613,8 +1858,8 @@
},
{
"path": "schemas/protocol/decision-delta.schema.json",
- "sha256": "2afbf26b1145a20b9c8758587efe13ddc7f2b3e8c6e87f91fa32fe9e0d826355",
- "bytes": 5812
+ "sha256": "6ffc0173d4905b97285f0deac3c4dd0b439ccd5db0dd81f05b28f38bbfa07b52",
+ "bytes": 8633
},
{
"path": "schemas/protocol/historical-result-input.schema.json",
@@ -1628,8 +1873,8 @@
},
{
"path": "schemas/protocol/history-detail.schema.json",
- "sha256": "7f9ccfc6a175d8acb9beaa6d565dbfd01d27a6814773afb52067b30a8e09a8dc",
- "bytes": 8964
+ "sha256": "793cd846d4cf696b715fd6c3a98da885caccf0b36a90262b5a869a384414ce6a",
+ "bytes": 11105
},
{
"path": "schemas/protocol/history-diff.schema.json",
@@ -1643,8 +1888,8 @@
},
{
"path": "schemas/protocol/history-page.schema.json",
- "sha256": "d604841a7b38796cca2d28e4da33226c8ce7a52a0fa70cbfbc5bc243bde7ca2e",
- "bytes": 8985
+ "sha256": "2353f126583a2310db4a4dabbb73448b36736ee920a09cf989d1fea7f6a9bf3d",
+ "bytes": 11126
},
{
"path": "schemas/protocol/history-timeline-input.schema.json",
@@ -1678,8 +1923,8 @@
},
{
"path": "schemas/protocol/public-method-catalog.json",
- "sha256": "63dc5330fd86a3567a9c02f8d3341894fa6a9afe1d36b4cff0f6c27582d99736",
- "bytes": 151798
+ "sha256": "e9c33b11d61e95b8946953dbc1fc7e792e4df4e6e99645971a9124b50222a7c5",
+ "bytes": 151175
},
{
"path": "schemas/protocol/restore-apply-input.schema.json",
@@ -1748,13 +1993,13 @@
},
{
"path": "scripts/audit_canonical_drift.py",
- "sha256": "e2f9f1e24aa1be7f7bf193b62b558fae0e27077d2d541294941b69c626439904",
- "bytes": 2469
+ "sha256": "cb448c24f6d40839d05ffb4d82aa06c00c5b7bc033d2b871d80ccf60e289ec89",
+ "bytes": 2654
},
{
"path": "scripts/audit_design_coverage.py",
- "sha256": "a32bb6678b4870624183bcc7819cc84f8c07bec36bd0e010185024b561aafaf1",
- "bytes": 7231
+ "sha256": "df9d2d17d2f7cd0ce3dc0b939e681f64c81180ae1e9c938003319f883c539fa7",
+ "bytes": 7219
},
{
"path": "scripts/audit_final_backend_acceptance.py",
@@ -1763,8 +2008,8 @@
},
{
"path": "scripts/audit_full_product_coverage.py",
- "sha256": "1c4157c1e36d8b0a0cf05af667aa31db27f0c7eed766395ae870134a1772e312",
- "bytes": 10057
+ "sha256": "c1ce41dc155ed4e48f6ccac73c50efa9daa331ef7ea891163462d1530563ffb2",
+ "bytes": 9958
},
{
"path": "scripts/audit_live_project_e2e.py",
@@ -1783,8 +2028,8 @@
},
{
"path": "scripts/build_public_method_catalog.py",
- "sha256": "56a9d7cd2529eec0edae3fff812b0a63994a918623118bc00cc7b545558a74a3",
- "bytes": 12472
+ "sha256": "d981178c33f3d55fe8cdf47fff1b2a165934d48fa72f0b01a9fbc794c121f1b1",
+ "bytes": 13716
},
{
"path": "scripts/cancel_architecture_preflight.py",
@@ -1798,8 +2043,8 @@
},
{
"path": "scripts/check_acceptance_coverage.py",
- "sha256": "7ef9ac0ce8b4abdb1d9f9e0213a0835b7d3bde7af27637918086894f0ca56664",
- "bytes": 2375
+ "sha256": "f6e9cf3d763ec6de963e8c65d66ca04dd6492b19541a442ac3242ee840603bc5",
+ "bytes": 2604
},
{
"path": "scripts/check_architecture.py",
@@ -1838,13 +2083,13 @@
},
{
"path": "scripts/check_public_protocol_parity.py",
- "sha256": "402dbddbf33244bd5aaa6f9908dc45420fa071acdedefc04e7e3e1c6d76d1ca9",
- "bytes": 2419
+ "sha256": "c303536dfc6c80b1c642529fe7de9d57b4ff0e0cd94038a24e87d146a7a7ea9f",
+ "bytes": 2333
},
{
"path": "scripts/check_truth_drift.py",
- "sha256": "3b1c2e6e4d7950de91496f964013e75a6b3598ebadbc113184a6ec69ee780e7e",
- "bytes": 2686
+ "sha256": "9ae9399fa64863cc3af51dee1c28bb920944de1f15fd33fd7b90073938720b64",
+ "bytes": 2992
},
{
"path": "scripts/checkpoint_commit_contract.py",
@@ -1876,6 +2121,16 @@
"sha256": "3b7212b126c2a076c6b4cea8db292617db6b32eae9ddeca22a6d184031d3b007",
"bytes": 8909
},
+ {
+ "path": "scripts/memory_selection_metrics.py",
+ "sha256": "1d320dd98ef0b6352630d0917f5ea45b9671f8550088649a4ca106b67ea0c759",
+ "bytes": 7904
+ },
+ {
+ "path": "scripts/memory_selection_replay.py",
+ "sha256": "afe635ffa9cd083778e86f78b0a83c3bf43e13fdfd97ef52c3d8884b3c203368",
+ "bytes": 11777
+ },
{
"path": "scripts/parse_shell_units.ps1",
"sha256": "68b0e0f3dbb61c581ad546fc3fdeef4c2c2e1c4ad295fefcf3952571ca133783",
@@ -1923,8 +2178,8 @@
},
{
"path": "scripts/required_architecture_checks.py",
- "sha256": "a1440b27108a8a30dbd64f39cf98030d548b7c64186743b993254040ac1aa0ec",
- "bytes": 2448
+ "sha256": "fc570efb0d33661835e3e259d8c2b674435751be80d0c73da07b971fba1f3889",
+ "bytes": 2384
},
{
"path": "scripts/research_history_fixture.py",
@@ -2193,8 +2448,8 @@
},
{
"path": "src/thoth/adapters/http/hosted_review.py",
- "sha256": "d9ae7ccf2cd078a05b6912a2d3f0fa818b145f4564fcac10ce7edf9000bcae25",
- "bytes": 2397
+ "sha256": "d66f7d355c945c4d672334c6bcd09cfe4ea5975d569664920bdcb71284b3b52c",
+ "bytes": 2463
},
{
"path": "src/thoth/adapters/improvement.py",
@@ -2253,33 +2508,58 @@
},
{
"path": "src/thoth/adapters/models/auth_registry.py",
- "sha256": "117195d2c5e916e1148f9c48d81fa884dcc41b9eb43dce679a6a08b2deaa53fb",
- "bytes": 2199
+ "sha256": "dd0730b5e3c0288ad284578d7841973b970f43f357fdbe3043c1b82c3dad96fb",
+ "bytes": 2483
},
{
"path": "src/thoth/adapters/models/catalog.py",
- "sha256": "95aaeb0231d73665bc2876d627c5ba25dfdd6d79fb8a9fb82948c9b32a78355b",
- "bytes": 3943
+ "sha256": "2ac2524024456caf9dce1a106b935fb282dff038454b1532e7a214f0f7b7a711",
+ "bytes": 6411
+ },
+ {
+ "path": "src/thoth/adapters/models/catalog_marks.py",
+ "sha256": "9b8aa5ab97e94392a0f66b0ea5da7d9392124bcf9cc49f4c092248d230a40d3a",
+ "bytes": 3895
+ },
+ {
+ "path": "src/thoth/adapters/models/catalog_startup.py",
+ "sha256": "13b94459798eb167baee9d233ac14384278cefbf83ec9c3548f160a260b5652e",
+ "bytes": 4145
+ },
+ {
+ "path": "src/thoth/adapters/models/catalog_store.py",
+ "sha256": "882b5406774b278d31fcc459363bd182eed142cc5290ee96298f1d0d4ac185f2",
+ "bytes": 3045
},
{
"path": "src/thoth/adapters/models/claude_catalog.py",
- "sha256": "0d1ff5497226a693341252e7c911fcacd3114fdc371166bbe01ad146215a66e5",
- "bytes": 1433
+ "sha256": "936853c7f2a8906c6c2f5488f9d1fc0d30102f125bc4b800eaa98513c8986fb0",
+ "bytes": 1438
},
{
"path": "src/thoth/adapters/models/claude_code.py",
- "sha256": "b0ebaab916929dca05f937489acf849d848daa659c34c71bdd49409c05c7e61d",
- "bytes": 21730
+ "sha256": "387625ecbb9e8e3fe3bf53f877e177ae65d8aa577046e537a475e243e4fbb2b7",
+ "bytes": 31094
+ },
+ {
+ "path": "src/thoth/adapters/models/claude_code_login.py",
+ "sha256": "b1f8505b858383a16b56f64c1f5ff47f6443d74fa50c56543028b9390c3bcd56",
+ "bytes": 15585
+ },
+ {
+ "path": "src/thoth/adapters/models/claude_code_tooling.py",
+ "sha256": "76ea6b71043e54ff26b5363d9b29dae35ae3b31da85699a12824217535f23074",
+ "bytes": 2837
},
{
"path": "src/thoth/adapters/models/claude_messages.py",
- "sha256": "328c7dc5c0165b55e3622bcecdc39abce02e1f35146acd97fc00c3efab6abf20",
- "bytes": 14348
+ "sha256": "19e8c8e97179bc43ae2c25c35660e1ed1fbb675afc222177741aed0fbee8e3c4",
+ "bytes": 14618
},
{
"path": "src/thoth/adapters/models/claude_oauth.py",
- "sha256": "39208ec89c19a563962c86a9db0f94f9d314259a791f00c872216ef902c7b411",
- "bytes": 27059
+ "sha256": "7ca825f69c281297ae70ff8aa25847961dc4f4ab1268f54099ef6b695acbb1d9",
+ "bytes": 27063
},
{
"path": "src/thoth/adapters/models/claude_profile.py",
@@ -2298,23 +2578,28 @@
},
{
"path": "src/thoth/adapters/models/codex_broker.py",
- "sha256": "48d4555b9aa51f10adf7414c06ea8bd1bd4c1943f4644d8cd1964693c6ba328b",
- "bytes": 25451
+ "sha256": "19b677314272b6dd2f5d58016fc82f5db186bc543278a656ea11ce3f1c13a535",
+ "bytes": 35870
},
{
"path": "src/thoth/adapters/models/codex_http.py",
- "sha256": "1e6881f2b2284af8cb5d64b21ab3965b35c3f2a664f01d76c7c9ccf176543dc7",
- "bytes": 15895
+ "sha256": "c312739f9f47b7bcff520d0c6df7fca4f0734d396eddffdb7cb14df3a8cb9dac",
+ "bytes": 16693
},
{
"path": "src/thoth/adapters/models/codex_oauth.py",
- "sha256": "2108c9176ff48a63ac699b532e07fd5507b4d4afa22332741ce81674999617e0",
- "bytes": 22691
+ "sha256": "2f006ff6d5d18eba5ad1a1b3d4bfa4c86e75e1ca68dd73d2118489f95305385c",
+ "bytes": 24243
},
{
"path": "src/thoth/adapters/models/codex_profile.py",
- "sha256": "701c299b0fe17e0c3f50aa392a07f5a9e11f58cdbe936ae115dcaf4a655166d8",
- "bytes": 20472
+ "sha256": "8a7a8e46db2b3ad5bf48adae8c450557492b828dc5fab010c90fe22e8847734f",
+ "bytes": 21029
+ },
+ {
+ "path": "src/thoth/adapters/models/codex_tooling.py",
+ "sha256": "a8480440ebdad6680db06431aa26cb41f2decc0974561eeb8514d5b490bb5693",
+ "bytes": 2030
},
{
"path": "src/thoth/adapters/models/companies.py",
@@ -2328,13 +2613,8 @@
},
{
"path": "src/thoth/adapters/models/local_credentials.py",
- "sha256": "39ed74547c0412174572a80343366762cba3b27ac200969f11c0194380201335",
- "bytes": 21318
- },
- {
- "path": "src/thoth/adapters/models/omo_native.py",
- "sha256": "1c82e00210c8a1bb86994c6676240d5a7ee9c6570e47b73c76ec9a61df504e55",
- "bytes": 5889
+ "sha256": "5e20ef8a84ba9c6bdd513dc5c206f03c24aba96569ec21aa60dc5cc692c1f4c1",
+ "bytes": 25441
},
{
"path": "src/thoth/adapters/models/openai_responses.py",
@@ -2343,8 +2623,8 @@
},
{
"path": "src/thoth/adapters/models/receive_stats.py",
- "sha256": "9fe812303724ec5f9de48602bcc41aae6d50f77da2e063c32f982c1d6db26082",
- "bytes": 4349
+ "sha256": "f7c40959062a97bac619d97414df7161267c18d327617bb2b1b282722138c3b7",
+ "bytes": 5469
},
{
"path": "src/thoth/adapters/models/reference_schema.py",
@@ -2368,8 +2648,18 @@
},
{
"path": "src/thoth/adapters/models/sse_stream.py",
- "sha256": "8ef1f255beacbd2df8aafddf55cb3ceb68b64df60cf24464a6665beb119bd162",
- "bytes": 5216
+ "sha256": "bb53a64b8f007c3bd26546cc85ed904557672ca87fd8d7e4d98004e0b2be89ad",
+ "bytes": 5566
+ },
+ {
+ "path": "src/thoth/adapters/models/stream_pace.py",
+ "sha256": "2ea38d7db4371b1557ffa543f5beeabe0a6bf2925777a25773c6eeab314bf150",
+ "bytes": 5758
+ },
+ {
+ "path": "src/thoth/adapters/models/tool_installers.py",
+ "sha256": "e75bccb88c515cfb595006138dbe192d88399909e867e685ff6e5b3b69c61199",
+ "bytes": 4163
},
{
"path": "src/thoth/adapters/models/transport_diagnostic.py",
@@ -2378,8 +2668,8 @@
},
{
"path": "src/thoth/adapters/models/windows_process_job.py",
- "sha256": "2a938ec637f6f609aefcc2f994f32bad1c72104b8a56b2aa30231873162389a3",
- "bytes": 15116
+ "sha256": "909a061491d14bfffc1b4b08d17bc101157730a53a0e23383a4de01ac60ba573",
+ "bytes": 15920
},
{
"path": "src/thoth/adapters/models/xai_account_usage.py",
@@ -2393,8 +2683,8 @@
},
{
"path": "src/thoth/adapters/models/xai_catalog.py",
- "sha256": "cb482fe3985825de6806410587fb41efe73cc0e737a6ad0faf78ab165466f9f4",
- "bytes": 1995
+ "sha256": "d2eeada4ab532bc91a4259d81dcb869076c4c37c63f2c675826b100bfabda389",
+ "bytes": 1261
},
{
"path": "src/thoth/adapters/models/xai_model.py",
@@ -2403,8 +2693,8 @@
},
{
"path": "src/thoth/adapters/models/xai_oauth.py",
- "sha256": "e2a192febf825be5fb09a27ec5d263e802c7d0da052d589098b49c4486b82894",
- "bytes": 4096
+ "sha256": "525275fa7b77178665ee914e9e9925c85360fd382683c33f440c76c4e1aacbac",
+ "bytes": 259
},
{
"path": "src/thoth/adapters/models/xai_profile.py",
@@ -2478,8 +2768,8 @@
},
{
"path": "src/thoth/adapters/parsers/pdf_parser.py",
- "sha256": "efc6d34779227075e7e97814921310e3d4b63fe596a11d07f447d99a1acb3244",
- "bytes": 3097
+ "sha256": "cf666c898ca19773b00f640df504e8b84bc60e8c8c9ebe863ac2f361c959a55b",
+ "bytes": 4774
},
{
"path": "src/thoth/adapters/parsers/registry.py",
@@ -2866,6 +3156,11 @@
"sha256": "4690d507f942d1fa997d6704c4f36bf935a5e1b2bbf94776088b9d4f88c6aa1c",
"bytes": 2462
},
+ {
+ "path": "src/thoth/application/commands/action_effort.py",
+ "sha256": "e64135390ff81db1d6fe78adb9ddb031e1d4f90994e11b3ce551f1949de3b4e0",
+ "bytes": 5759
+ },
{
"path": "src/thoth/application/commands/action_generation.py",
"sha256": "354607b3b15832aebc26439404d95355299b40e9abc81ce9d0f66f90ca6375a2",
@@ -2873,8 +3168,8 @@
},
{
"path": "src/thoth/application/commands/actions_full.py",
- "sha256": "75f1d9fc9a95a8dab026b25374340872d145e501f6c9a5911914f57247e58e78",
- "bytes": 43725
+ "sha256": "f692efbdc92f9ed050d3b06edb8f13f828009ebe75f389c0acea070599f678d3",
+ "bytes": 43451
},
{
"path": "src/thoth/application/commands/behavior_execution.py",
@@ -2936,20 +3231,45 @@
"sha256": "9dffc989fc7acb46d3668ecdc601c25110d9bc0a6fbb5e8e27f9c1d81bc957d9",
"bytes": 9244
},
+ {
+ "path": "src/thoth/application/commands/judgment_review.py",
+ "sha256": "884b79a0fe284512c9400f847bcade7f7b6bcb3f2e4e71e318dc8a8dc2a7283d",
+ "bytes": 5631
+ },
{
"path": "src/thoth/application/commands/lifecycle.py",
"sha256": "00e2dd553e92c6c003ada253961ec5a3e7ec8a8b5c672eb1ba035be8118fa5b8",
"bytes": 11881
},
+ {
+ "path": "src/thoth/application/commands/memory_edits.py",
+ "sha256": "4485b11cd7477b96911807d96bbb8f080338c89ae82d7b2be7839b5b22e4be22",
+ "bytes": 2348
+ },
{
"path": "src/thoth/application/commands/memory_full.py",
"sha256": "5e29df92a7cdef22fa48734a59ba63dddd3499e0c22ab5f1a08625c35cd639f9",
"bytes": 35680
},
+ {
+ "path": "src/thoth/application/commands/memory_revisions.py",
+ "sha256": "0a79894a19107114b6669f342b178a0fe62157a7a02434307a41a4fd27ef88c9",
+ "bytes": 5199
+ },
+ {
+ "path": "src/thoth/application/commands/memory_settings.py",
+ "sha256": "dc9e8cd32b199d72a3ee2734913466d653e8efe7905713ad0893edd1f116283a",
+ "bytes": 2283
+ },
+ {
+ "path": "src/thoth/application/commands/model_call_settings.py",
+ "sha256": "6aa809af8c37f53432cffb4e7ec7f838f56ab9bbcf6e9d4be4117f3c05c92f70",
+ "bytes": 2376
+ },
{
"path": "src/thoth/application/commands/model_settings.py",
- "sha256": "5f756ba4d72eeb31b4192c0118ebe3289957b3de86e08189b7d934e5619738de",
- "bytes": 13783
+ "sha256": "114b13d9d77a8f3ceeb5f53a88a4451b8f1b3ab19e8b770ead37370c2ed30b93",
+ "bytes": 18176
},
{
"path": "src/thoth/application/commands/objects_full.py",
@@ -2993,8 +3313,8 @@
},
{
"path": "src/thoth/application/commands/research_followup.py",
- "sha256": "36c6ee45532bdbe94d6b1ccbcf1a31c77eab7d60cc1d1f7fafed7015684b43c6",
- "bytes": 3844
+ "sha256": "3e248d985fd4044eea451e0b5e7d536a2aca87db300f3f47fe6d4dd327a30440",
+ "bytes": 4117
},
{
"path": "src/thoth/application/commands/research_history.py",
@@ -3013,8 +3333,8 @@
},
{
"path": "src/thoth/application/commands/research_threads.py",
- "sha256": "f8d4843029e2a41184c9f0dc28287cc9da75c181b99e0ce0f0e156ef57ba5943",
- "bytes": 36140
+ "sha256": "0d3934f2240ebc258a12e64bde5c45e0b08a2cb0f0d72a90396c0707eec734a2",
+ "bytes": 41398
},
{
"path": "src/thoth/application/commands/resource_scopes.py",
@@ -3043,8 +3363,8 @@
},
{
"path": "src/thoth/application/commands/sources.py",
- "sha256": "44eeae287d6942eadb34de515cca7fc3d7ce4a1533382a9fc6e74b29207bf639",
- "bytes": 23206
+ "sha256": "453ae9e3f8755dc7826a54e8f7a125ae7042996069f1512ebf2669d12d1b6486",
+ "bytes": 23677
},
{
"path": "src/thoth/application/commands/thread_analysis.py",
@@ -3103,13 +3423,13 @@
},
{
"path": "src/thoth/application/services/action_compiler.py",
- "sha256": "0e126760e919adc339c93296ed940af0409deaa49e373d8cf0e59ffd0befd397",
- "bytes": 6976
+ "sha256": "2c8850cfb8c0aa609586b6b53d2b26c9318fd56fc64e7be2ad2397989b0a18e2",
+ "bytes": 7112
},
{
"path": "src/thoth/application/services/action_currentness.py",
- "sha256": "76da3142c3f06385c4dbd44dca909180b3695b74c96455f60b909593d3bf5c3f",
- "bytes": 2002
+ "sha256": "94552220050ae1cfb4b957b04db747d1b517efda92c08af1fd83c9c7eb006b21",
+ "bytes": 2394
},
{
"path": "src/thoth/application/services/action_plan_validation.py",
@@ -3118,19 +3438,29 @@
},
{
"path": "src/thoth/application/services/action_projection.py",
- "sha256": "41718fbb3a9ef6e9fc44ff46aaef28eeb4107d07933d07fff0e60aaabcc7e20f",
- "bytes": 13491
+ "sha256": "7d97f54b6395681ab643bbefea406bd4047feeddff95a48ab81a118e4438e9d9",
+ "bytes": 14297
},
{
"path": "src/thoth/application/services/action_service.py",
- "sha256": "40c8ddca9f0bfba72bd6c03afa7c5361b72a8ebe009690514baecd5286356bc4",
- "bytes": 39835
+ "sha256": "712dce96ece381969ed94f051b3bf6215358896039019464e95901ce1ccba8d9",
+ "bytes": 38592
},
{
"path": "src/thoth/application/services/authorization_consumption.py",
"sha256": "6c0c252d8bd7405407aa52c5a6ea34d66677b399419a78fc4fb93450b609ba99",
"bytes": 8436
},
+ {
+ "path": "src/thoth/application/services/authorization_currentness.py",
+ "sha256": "314909284bdee460f10b110cacb2697c820c8f42d615345f42c488a85ee2b9cc",
+ "bytes": 6218
+ },
+ {
+ "path": "src/thoth/application/services/authorization_prepare.py",
+ "sha256": "f109b56f38397dc9c653c7950d856ad64ae58ddf3a4cbc068409d1384e799db5",
+ "bytes": 3860
+ },
{
"path": "src/thoth/application/services/baseline_router.py",
"sha256": "13505f7ce0826228fe5b8b9140de320133e7f6530b8dc08dfccbe89326ee3e48",
@@ -3241,6 +3571,11 @@
"sha256": "dcd0de4eebf2ea06afad5e3889b95110d3a7c2c770022cd2093738ad373bea58",
"bytes": 9520
},
+ {
+ "path": "src/thoth/application/services/cycle_memory_preparation.py",
+ "sha256": "dad5d7632cebacbc3c02c9b33c6185646a89608d70d89b352f37304b275bf1d2",
+ "bytes": 2231
+ },
{
"path": "src/thoth/application/services/decision_object_service.py",
"sha256": "48755d7970f68d78c91394f3cbfaed235f871b5f00e85d257c1c75081c496d84",
@@ -3248,8 +3583,8 @@
},
{
"path": "src/thoth/application/services/domain_reference_memory.py",
- "sha256": "5d04f1b651e5e4d1add32d4b3d70f6397220e9a0210237358f16e650d3f4f57b",
- "bytes": 1259
+ "sha256": "1fd0f1a6f872c4d24eff55fe6a3a576c7ba85e190f586a245399a9c0b03ea780",
+ "bytes": 1725
},
{
"path": "src/thoth/application/services/evaluation_inputs.py",
@@ -3278,8 +3613,8 @@
},
{
"path": "src/thoth/application/services/execution_frontier.py",
- "sha256": "7c0ad218ce8491c023c16247f3ca897ea57baff82ef440ee7bfd19d9e8088be7",
- "bytes": 2925
+ "sha256": "cda06b6990ac7dc62f7d6684ba7226a3a98f14065379578a1e7e3ccd4edbd78a",
+ "bytes": 3003
},
{
"path": "src/thoth/application/services/execution_persistence.py",
@@ -3298,8 +3633,8 @@
},
{
"path": "src/thoth/application/services/execution_service.py",
- "sha256": "2e1675e8e7563c682250f3aadb64ace7ec1199ee2ee0fd8d4e759edbdcc6ac53",
- "bytes": 35147
+ "sha256": "afdc8a783790d22ab153ea23e6003343b9c7c9313efe63ef18f6ff393b434bde",
+ "bytes": 34989
},
{
"path": "src/thoth/application/services/export_snapshot.py",
@@ -3318,8 +3653,8 @@
},
{
"path": "src/thoth/application/services/full_project_memory.py",
- "sha256": "1f8b91c5d7bfefa7285023fd18ebaef44aa09d1a8f3d4a9f79f466a11c654b94",
- "bytes": 25653
+ "sha256": "06d73927446bb67d4c17eb8c1e647a2e7e4601e147930201530c4dd9049e87a6",
+ "bytes": 32425
},
{
"path": "src/thoth/application/services/historical_access_verification.py",
@@ -3336,6 +3671,11 @@
"sha256": "2833ca3924a8222c1151f2d5339b9269fd3a73eaf156d18affa224f7aa87fd1c",
"bytes": 2725
},
+ {
+ "path": "src/thoth/application/services/history_change_summary.py",
+ "sha256": "7e6e867722a006ac8fc92be3b20f569f745f68abfdf9664e903e833a139ce606",
+ "bytes": 8901
+ },
{
"path": "src/thoth/application/services/history_cursor.py",
"sha256": "023e027fb5b4f06595473a399af5a525a1dc9bca7030b70499ee9483309fafaa",
@@ -3343,8 +3683,8 @@
},
{
"path": "src/thoth/application/services/history_projection.py",
- "sha256": "2206b7dd7c3adc51fd534cb2855a6dbb315aec4c349e2026f48580dc297f791f",
- "bytes": 10758
+ "sha256": "3ffe64328f3e6daf46cd91d1488ba004205f65cf0be2c9fad5bdc3fda4950b23",
+ "bytes": 13077
},
{
"path": "src/thoth/application/services/hypothesis_projection.py",
@@ -3406,6 +3746,11 @@
"sha256": "8132fe0043b71bf7085b57d12fadb99db14ecfb71b1568bf398e825038938d51",
"bytes": 12140
},
+ {
+ "path": "src/thoth/application/services/judgment_review_service.py",
+ "sha256": "6a239edb3aae354a88b6e76b5abcb281ffb4b6e44c2fad7bf21344d5017df684",
+ "bytes": 12122
+ },
{
"path": "src/thoth/application/services/local_auth.py",
"sha256": "7362a99218b04c4e1feb6bd27a3d4bbb670842c485a7a8b1409737dde8289c0f",
@@ -3416,16 +3761,81 @@
"sha256": "6c10258d3629d87a50e2199eb770450e8f42e89544aa458cb4fe9990299e0e6e",
"bytes": 5715
},
+ {
+ "path": "src/thoth/application/services/memory_edit_service.py",
+ "sha256": "873accda9e1bd333c0be00c4a40d52485937226a3c829ce3b4cb5fa306113922",
+ "bytes": 10201
+ },
+ {
+ "path": "src/thoth/application/services/memory_recall.py",
+ "sha256": "eb1d0d0595bf02487e75daa702b835ce31f1b96a6af7bcd5e3618701b7faf29a",
+ "bytes": 10112
+ },
+ {
+ "path": "src/thoth/application/services/memory_relation.py",
+ "sha256": "4e7ca90f77e7d7351d53d518deec5d6bad0f902daa08c2b880aea2bb214ccb5e",
+ "bytes": 3497
+ },
+ {
+ "path": "src/thoth/application/services/memory_relation_judge.py",
+ "sha256": "3a6e237735588da950b248b01b096b9cd9dd414a4bc716838f9cc197e1497deb",
+ "bytes": 2794
+ },
+ {
+ "path": "src/thoth/application/services/memory_relation_resolver.py",
+ "sha256": "ceecdf4862cb774a6d6fd3129f6b57cdf16f5d536c6a6e43acc30698196287b5",
+ "bytes": 10022
+ },
{
"path": "src/thoth/application/services/memory_review_service.py",
"sha256": "95305ef59227febe0fb9fa69eea2848952bf9ef0b4c3406682cef0a67abf2ecd",
"bytes": 9641
},
+ {
+ "path": "src/thoth/application/services/memory_revision_seal.py",
+ "sha256": "894cff307ffac6b7638adb45eebafaa4e42216250d75e4452726922356974089",
+ "bytes": 3977
+ },
{
"path": "src/thoth/application/services/memory_router.py",
"sha256": "ca20d9e45851ba5042a92cf2c48b56aab3c75cc0f78fa79060864c81eec6e3bf",
"bytes": 2083
},
+ {
+ "path": "src/thoth/application/services/memory_safety.py",
+ "sha256": "6e681840f8d9429676b8d1e7dbee8ae93c9b51f3f7cddc36269523c16e123785",
+ "bytes": 594
+ },
+ {
+ "path": "src/thoth/application/services/memory_settings.py",
+ "sha256": "56e5520628bf59183f26ba2071f760f11795e90691a32065302a7f43e9223b5e",
+ "bytes": 1787
+ },
+ {
+ "path": "src/thoth/application/services/memory_shape.py",
+ "sha256": "d86c63c5291baf6ab49ba347eb8d4e11f911b79fb94719fadc87bd3ee1c2b29f",
+ "bytes": 6539
+ },
+ {
+ "path": "src/thoth/application/services/memory_summary.py",
+ "sha256": "348a7161ba381b8a7b21dd1051cbbca53ac8a31549a48bc52e08d640c5626e5f",
+ "bytes": 1830
+ },
+ {
+ "path": "src/thoth/application/services/memory_supersession.py",
+ "sha256": "a6db7843fcaf74b387778037b57769eb9576cdc182d3732972a8dbcfc0f98b51",
+ "bytes": 1353
+ },
+ {
+ "path": "src/thoth/application/services/model_call_settings.py",
+ "sha256": "1a48056e887b05517abb092297f2504d3882b4cc5b49cc518944aeb4b106c389",
+ "bytes": 1807
+ },
+ {
+ "path": "src/thoth/application/services/model_outcomes.py",
+ "sha256": "a2ed2630d1dc52e1566606913b8b6dad97e76d65b9e8b9c87acdc42fecd29b43",
+ "bytes": 1337
+ },
{
"path": "src/thoth/application/services/model_settings.py",
"sha256": "8bb40157f052e84c8efe46c15229ac691c32d99c58580af9722c9b9087c40c25",
@@ -3463,8 +3873,8 @@
},
{
"path": "src/thoth/application/services/post_execution_memory.py",
- "sha256": "d0e9dce99cf69c00373b4ff7ce3a5011ad4a7d93b7fa788110265bc4bcce109f",
- "bytes": 7595
+ "sha256": "813a097563a8ce1a0e753e7f0088cd9820d532411fbb187a43fc046d7e1b102a",
+ "bytes": 7840
},
{
"path": "src/thoth/application/services/post_execution_resume.py",
@@ -3543,8 +3953,8 @@
},
{
"path": "src/thoth/application/services/research_analysis.py",
- "sha256": "cb61baf26e8681a5baf4da2a0ab37ef6e21f97802dd34636f1ce50a4428cfcfe",
- "bytes": 33902
+ "sha256": "141354dbdd7c800a811b9d7597883b517df7f1cc18b3074a1abea3ce11defb56",
+ "bytes": 37200
},
{
"path": "src/thoth/application/services/research_attempt_admission.py",
@@ -3553,8 +3963,8 @@
},
{
"path": "src/thoth/application/services/research_attempt_runner.py",
- "sha256": "7967e82670117665ded216d533cd9eb1cb1018293f8bbd9c0ec18ed7658749c6",
- "bytes": 16467
+ "sha256": "d0844b96da1730580ed0d83b5cef554eba3ce98b99bc5707100c5f9fb60dcaff",
+ "bytes": 18247
},
{
"path": "src/thoth/application/services/research_basis_capture.py",
@@ -3566,10 +3976,15 @@
"sha256": "360c2cc573b8edc1e7632b0ce62a23667fe604b81bff704a699ce6a8fdd695ff",
"bytes": 12350
},
+ {
+ "path": "src/thoth/application/services/research_commit_scope.py",
+ "sha256": "f9b0c79681efa2dc42791535858329d5114a6cc74594a115689da1c9e2f867a6",
+ "bytes": 1487
+ },
{
"path": "src/thoth/application/services/research_completion_view.py",
- "sha256": "1005447495c06ad9476af4b46f629b6d2824dcf8c668475055a41f189d2e10ac",
- "bytes": 5373
+ "sha256": "1f4fe1bdf2c0c0cef94323f442f1bffd6a923614ac0ffcc9c59fce593ead8711",
+ "bytes": 5885
},
{
"path": "src/thoth/application/services/research_conflicts.py",
@@ -3591,6 +4006,11 @@
"sha256": "0a63c286f2578d4efdbf1bb5ae9c16ee4300c894a1c88968036422f651c52a79",
"bytes": 7968
},
+ {
+ "path": "src/thoth/application/services/research_criterion_delta.py",
+ "sha256": "73e8419ebf1f994afbd502e3c4a4ca8b2afaebd2e8d88d2c37474d584e926ae8",
+ "bytes": 2884
+ },
{
"path": "src/thoth/application/services/research_currentness_view.py",
"sha256": "3ee7f86665c2ec6afdffb192b3cfe19fbfcf154ede6822e5d7b18deed9aa195d",
@@ -3601,15 +4021,20 @@
"sha256": "1a27f86da5839007b4f69813cc7b67273b2780053cb220341319e93ee592c5ae",
"bytes": 2056
},
+ {
+ "path": "src/thoth/application/services/research_findings.py",
+ "sha256": "a45f73deb8419f0e361a9ed9af28e01f07e1e864ccabca0b7254b0a340b3fb87",
+ "bytes": 1436
+ },
{
"path": "src/thoth/application/services/research_followup_projection.py",
- "sha256": "eecc195df924c38c359d664ba1eb56eef44e17424e7c9cd858b6b1c9177c12d7",
- "bytes": 26235
+ "sha256": "435134fceba2173f7f6b648e0b4c737d02de80f961b7285a7e9f75d4b2fd1502",
+ "bytes": 28348
},
{
"path": "src/thoth/application/services/research_freshness.py",
- "sha256": "03b71b818317adf5f42f0fffabfb78d29215b3acd715d1379f0a89fc0233b826",
- "bytes": 7174
+ "sha256": "6a3b3ef91297d844ac406f307ff6d8a13f5777ee3e1194a9dfeb1fa8e91df149",
+ "bytes": 8083
},
{
"path": "src/thoth/application/services/research_gaps.py",
@@ -3628,8 +4053,8 @@
},
{
"path": "src/thoth/application/services/research_identity_service.py",
- "sha256": "3afa259de1c110add685715e54db1a163f8d0fd1c72a564d21f8a74673b08024",
- "bytes": 34456
+ "sha256": "1ccbcd0e0b97b9a3a894a0dc4d342399b5445a69ceff83398ae3e410b9c94742",
+ "bytes": 35299
},
{
"path": "src/thoth/application/services/research_input_queue.py",
@@ -3641,6 +4066,11 @@
"sha256": "d049a950b5d7c2990ac4ee793a34a86f60f8d21f51dfe934a2994595df4f1bcf",
"bytes": 3490
},
+ {
+ "path": "src/thoth/application/services/research_model_hold.py",
+ "sha256": "bbd29f6a9f3882be4ee6878cde65067c35c29e3708b13a79c31d036a67a202d8",
+ "bytes": 1440
+ },
{
"path": "src/thoth/application/services/research_models.py",
"sha256": "7579ed734f931ae1c6cfe750882a7cdfb1b9a5832a86420c560a06dd0d2d98d1",
@@ -3656,6 +4086,11 @@
"sha256": "bf6723a925df3d7eff43405d4e42743259e038d6acecddd8ee59790a05da5e3d",
"bytes": 8309
},
+ {
+ "path": "src/thoth/application/services/research_read_summary.py",
+ "sha256": "807b28e504e53cd93dd989b394449172432bc2bdae2923fb89ce21c79b7a8b99",
+ "bytes": 2606
+ },
{
"path": "src/thoth/application/services/research_record_persistence.py",
"sha256": "39bfafc472db0ac090ca585f0bdfaedc20a70863f127ac0ac664f2f9896d2500",
@@ -3676,10 +4111,15 @@
"sha256": "dd7d4cf430472559ed344ef6857c6bce7a673ba975c997d4e9ce543538a8dd59",
"bytes": 2916
},
+ {
+ "path": "src/thoth/application/services/research_stage_reuse.py",
+ "sha256": "7a86a19d6564ab4f84c342868be5ea859c993306911a3e830713f1c95fe864bc",
+ "bytes": 3292
+ },
{
"path": "src/thoth/application/services/research_stages.py",
- "sha256": "f74be3df36b40e77010f09df4c41d43626b8694fbb5a3d4ba08626e898136b1e",
- "bytes": 5137
+ "sha256": "d61c714a1b25f6785b6890ba724aeb10ae1345643c8e7a529b863e6551ce9193",
+ "bytes": 8450
},
{
"path": "src/thoth/application/services/research_termination.py",
@@ -3688,13 +4128,13 @@
},
{
"path": "src/thoth/application/services/research_thread_read_snapshot.py",
- "sha256": "59820fbb8981987d71e3403ed30e9e4e1422f1baae7a6ba0f45252651da13f0f",
- "bytes": 7603
+ "sha256": "d1efd131cfb645b53fa86f43e48a14a12068791e3b51e87b4a6516797de53c6b",
+ "bytes": 8497
},
{
"path": "src/thoth/application/services/research_usage.py",
- "sha256": "531afff34ec0a850855209bd2a534db19c137bd629a2d0092f1bfa085301b898",
- "bytes": 3163
+ "sha256": "ca8fc08ab99f6467110977570e128c90e483cb5a21b3912c90ca336019769e49",
+ "bytes": 5650
},
{
"path": "src/thoth/application/services/resource_scope_context.py",
@@ -3713,8 +4153,8 @@
},
{
"path": "src/thoth/application/services/resource_scope_read_context.py",
- "sha256": "0b6bd8bf3051054d67959437dc84b2c2f69ca74a173aa0d6bb9275c1c3ce74df",
- "bytes": 2425
+ "sha256": "46bf616f07f3df60ad94e7f9c8be8a2a8424c1b368d9dd884e56679cfc1be7f4",
+ "bytes": 2845
},
{
"path": "src/thoth/application/services/resource_scope_service.py",
@@ -3773,8 +4213,13 @@
},
{
"path": "src/thoth/application/services/revision_service.py",
- "sha256": "8318f94d5b5c18182b67a436136bf8a35567ac52033076fc4c413d78d5756167",
- "bytes": 6249
+ "sha256": "dc3a3588f1b61bc17175815fda48fa13781be9fc4eea5e58083c7421ee310239",
+ "bytes": 6413
+ },
+ {
+ "path": "src/thoth/application/services/running_research.py",
+ "sha256": "3ee8e564dcb84a28ad64b12740821ad04accd68c14eb870cc9e03f032a8a65e0",
+ "bytes": 708
},
{
"path": "src/thoth/application/services/sandbox_router.py",
@@ -3863,8 +4308,8 @@
},
{
"path": "src/thoth/application/workflows/thread_cycle.py",
- "sha256": "87aac61f9a0e7294d40bc24e40e4e733308939c596944e0233cd3918afd8e46a",
- "bytes": 35827
+ "sha256": "b9c3bfd4e402377972a060d3be6c03baeee18a85423739592fa78d03ad6b9e2f",
+ "bytes": 38623
},
{
"path": "src/thoth/apps/__init__.py",
@@ -3903,8 +4348,8 @@
},
{
"path": "src/thoth/apps/hosted_review_composition.py",
- "sha256": "0b244745f436d0ce4382feaeed456d72097b036f2c67537feeab8f32d13845b9",
- "bytes": 13719
+ "sha256": "1dbc4ed188077448a4752d0558b0a14480a9f9e6a76bcbb751270c8c55e14130",
+ "bytes": 13883
},
{
"path": "src/thoth/apps/hosted_review_dispatch.py",
@@ -3926,6 +4371,11 @@
"sha256": "cc1800bd78ac7873d16ddd6dc5e5474c371325a79bd6c80a8cb780bfdc879c0b",
"bytes": 8024
},
+ {
+ "path": "src/thoth/apps/judgment_review_composition.py",
+ "sha256": "7fbe0ea74201e8ea18c58d06314125c76fa9e06eb2d1c84972aa4f5fb87ca3c2",
+ "bytes": 2088
+ },
{
"path": "src/thoth/apps/management_routes.py",
"sha256": "7454abd0c5187480b00090a3c31c078a4760735fb28897d25bad02297354b321",
@@ -3933,8 +4383,8 @@
},
{
"path": "src/thoth/apps/memory_composition.py",
- "sha256": "0e3526b3224682b03c5c77816cc9b1143845e877bb7ad55ebdd56d3d3f8dd809",
- "bytes": 5004
+ "sha256": "cc1b838a76faa0db8d05b9dca8cbf41a025ceb9fed33e88c38d76bcfcf97cda6",
+ "bytes": 7183
},
{
"path": "src/thoth/apps/memory_review_factory.py",
@@ -3946,9 +4396,14 @@
"sha256": "8b2d34a4abc6c17f8f70a00cff590da3aa4206fde4e8a00442d4d5a58c14deac",
"bytes": 3963
},
+ {
+ "path": "src/thoth/apps/outcome_receipt_routes.py",
+ "sha256": "05e7508bcea8d8be3d3d0357a658579c50ef70e33d14d7d7a89a092be6428f55",
+ "bytes": 2587
+ },
{
"path": "src/thoth/apps/projectpack_execution.py",
- "sha256": "a8c326f57f7ce2081c13fbff761dddb8da44c3d133870208e9f34ecd37bd87a0",
+ "sha256": "7d13b1a2f18f548780e62f6ead71553a3ed2406ae6543a47f4deb41f2437591c",
"bytes": 11535
},
{
@@ -3963,13 +4418,13 @@
},
{
"path": "src/thoth/apps/research_entry_composition.py",
- "sha256": "e7d4236d023cb076dd9d7b48006f55c9fe907a1c65949dcf16528aaf58c69224",
- "bytes": 10901
+ "sha256": "a7ae439f90642bc1c5d0bdd343e2dcfac1f0aeef25929f995c41d722fcfa6011",
+ "bytes": 13587
},
{
"path": "src/thoth/apps/research_history_composition.py",
- "sha256": "eb2696bfff9aa53bf64dc018571c3215acdd7997dcc55322fbb1bf41004c7a73",
- "bytes": 7725
+ "sha256": "0d4d439729d1686573f279a653baa0bab3e206ffe041dcf1457558bd2c87061d",
+ "bytes": 7915
},
{
"path": "src/thoth/apps/research_runtime.py",
@@ -3993,8 +4448,8 @@
},
{
"path": "src/thoth/apps/runtime.py",
- "sha256": "ad222b0701ed64821114617caa9d0bf7095252093f979851f79db689fbe25c5d",
- "bytes": 35931
+ "sha256": "c37eedd453198dae1fb1c1366d67bf2e58384adeae32a44744f336dd69a63429",
+ "bytes": 33926
},
{
"path": "src/thoth/apps/runtime_types.py",
@@ -4053,13 +4508,13 @@
},
{
"path": "src/thoth/domain/action.py",
- "sha256": "f84efa3545f70512da2132bd27a632281497350a9b548034c81c09369f4cace3",
- "bytes": 7265
+ "sha256": "9ac11aceb3cb79aee87edad2784a87e1bdfecefb07c16a6095923ae8abbca29c",
+ "bytes": 9197
},
{
"path": "src/thoth/domain/action_full.py",
- "sha256": "ee2463262459bc36d7b7495e4c2a7a06e9d38a9a6c1d91e67dd214bc0045348a",
- "bytes": 4886
+ "sha256": "b56b68f8862c388f6c3d7c155d99f2216fe9bbaecdac8908cd5b44963212a046",
+ "bytes": 5447
},
{
"path": "src/thoth/domain/actor.py",
@@ -4166,10 +4621,15 @@
"sha256": "b9e4a27251fa33861b76136d4adc54e9c2438a4c5c4b13f9601de7e0e64fafaf",
"bytes": 759
},
+ {
+ "path": "src/thoth/domain/effort_bands.py",
+ "sha256": "54dd1d958f0e5429eefd4b81aba12137f4649cab618b5a3b51b59c735cb0c623",
+ "bytes": 6911
+ },
{
"path": "src/thoth/domain/enums.py",
- "sha256": "f520c7e80e9664ad03c639ef8a8a1cb9ac54332f0bace2957e8ab48f2ae54a2b",
- "bytes": 10543
+ "sha256": "6afb932aa851257a988e5b9e9ea294b1a91fb1dbce73c2f5cbec97153f14779b",
+ "bytes": 10595
},
{
"path": "src/thoth/domain/environment.py",
@@ -4223,8 +4683,8 @@
},
{
"path": "src/thoth/domain/evidence_requirements.py",
- "sha256": "a57d32424ec2ea5120d7e721aab1c03b6aac7dbe3e3f24239575a907f2fba97e",
- "bytes": 9870
+ "sha256": "095ea775a2cffedf47d92d172914dde4eff4d5fbd57122d53d5c8b13626b0218",
+ "bytes": 10601
},
{
"path": "src/thoth/domain/execution.py",
@@ -4296,15 +4756,30 @@
"sha256": "1afdd0b553a32640cf0691db049add48f6881d610f651dfdf47c3ccd937b104d",
"bytes": 2541
},
+ {
+ "path": "src/thoth/domain/judgment_review.py",
+ "sha256": "c082f19952443923aafd5638e1be152bb61e4c01b11c5095dcfd9e648c0ee98e",
+ "bytes": 4361
+ },
{
"path": "src/thoth/domain/memory.py",
- "sha256": "adafea6ac50a0dff843e516882e9f6061c4e5065b58f1c360e179a509cb1cecd",
- "bytes": 5789
+ "sha256": "8614687f99123948e825a95edad714011f64a54b9acbc2ce949074425640dbce",
+ "bytes": 7105
},
{
"path": "src/thoth/domain/memory_preparation.py",
- "sha256": "4ce94f9759b86204fc11e2b4c443b2531e8463f2e25dd7231b80fa6d8c785f32",
- "bytes": 1847
+ "sha256": "1d601695d5283ab853681d3d0237c8bd2dbae73480b8b11ce48b22f74560a84b",
+ "bytes": 2537
+ },
+ {
+ "path": "src/thoth/domain/memory_relation.py",
+ "sha256": "307db478fcbcbdc9f1f734ca36a490441e17537ca23bf903e5f15abc6cbe36e7",
+ "bytes": 1752
+ },
+ {
+ "path": "src/thoth/domain/memory_settings.py",
+ "sha256": "ddd1d0644bff88b1e30f348bf11ad7afeee72565a0734a9b4161d3768d16ca5d",
+ "bytes": 413
},
{
"path": "src/thoth/domain/migration.py",
@@ -4316,25 +4791,35 @@
"sha256": "7d7b89574c5fdf939c6408c50d040ce61c13a459e0b875cd88023a230d800f76",
"bytes": 2227
},
+ {
+ "path": "src/thoth/domain/model_call_settings.py",
+ "sha256": "cabfd7f24b160004f9fffe362729eb0084f086a29b0433b050e4bf98ea7a00ab",
+ "bytes": 885
+ },
+ {
+ "path": "src/thoth/domain/model_catalog.py",
+ "sha256": "bc2f241bb15778c37ed239161228e05fad2262d877a5303c91d6d16cdfe32953",
+ "bytes": 3652
+ },
{
"path": "src/thoth/domain/model_dispatch.py",
- "sha256": "8bcf1f7b5aa189a6ec3db85cfaf17e630fec3476027c2158b07e464bf227530d",
- "bytes": 6594
+ "sha256": "83222c8727737d4973ed8b3015b5a9dc791414f20e2399ece266e67602343ae5",
+ "bytes": 8075
},
{
"path": "src/thoth/domain/model_settings.py",
- "sha256": "d29052303b70494e026c96c58036cc835b6bf3a856303561fc1a31a1b83d3e80",
- "bytes": 1346
+ "sha256": "5e59f380745fd3d99296e728386c030d102883913712acc9814a02f90439a0b8",
+ "bytes": 1864
},
{
"path": "src/thoth/domain/oauth_retry.py",
- "sha256": "2bedd1c7c33b32e38380934d997a0e6cde6ef98e6d71d6795112a5ee3f9b80f9",
- "bytes": 1270
+ "sha256": "45d51ff76e7fccc5b91030cc40b50657cec8ab0092df7ed3e190740a8048846a",
+ "bytes": 2331
},
{
"path": "src/thoth/domain/operation.py",
- "sha256": "a5e9e28e168290850a821f25a68e16a34c5569a503eaa24c825b69642efbd65d",
- "bytes": 1524
+ "sha256": "4cd49438c78a9ab1f7690a02d8a130d5a5d0de1a8fe31054dd80971a93194fb1",
+ "bytes": 1614
},
{
"path": "src/thoth/domain/outcome.py",
@@ -4371,6 +4856,11 @@
"sha256": "234d3940ac289f7d6a4ee8d2149da1aa3ecf6bbb246343709ef4dfbd38aa9e55",
"bytes": 513
},
+ {
+ "path": "src/thoth/domain/protected_action.py",
+ "sha256": "4c843b06a1325dd9f7dedcaaabdae634e93b65c79f5dc2b869a889e4b13a3f6a",
+ "bytes": 11074
+ },
{
"path": "src/thoth/domain/public_web_access.py",
"sha256": "82d92eb1abc833fbb719f673c81fc5ed38631d8308c3059f5782809b2b1e7a6d",
@@ -4418,13 +4908,13 @@
},
{
"path": "src/thoth/domain/research_codec.py",
- "sha256": "6e8453d2ba490fed47ab41257ac4e76b1f2c15ea235379ddc0dc9367a3bb1fa3",
- "bytes": 2315
+ "sha256": "d1673d90a74afe02a8ad55198943d182c0bcb8b18f9a51b2957cd89b7bd6756b",
+ "bytes": 2521
},
{
"path": "src/thoth/domain/research_execution.py",
- "sha256": "3d03ffc38e4389156aa11c2f285ef29e0daec2ccf02cab33b084015ac122df67",
- "bytes": 3988
+ "sha256": "2db4d8da5a1c1f75e50c6e1d1676ad33e56b5d7daca2f9a60ec43d87a37955e2",
+ "bytes": 4416
},
{
"path": "src/thoth/domain/research_failure.py",
@@ -4433,13 +4923,13 @@
},
{
"path": "src/thoth/domain/research_followup.py",
- "sha256": "9c6731c3e9bb3d71f21a1ce765ab6ea77071ce98e32f84574fe19537e3845632",
- "bytes": 4662
+ "sha256": "59e5b7f1fc01f9b08b5c63eaaafa2d7c7204d79b57f4785e5b6aaa309439d64d",
+ "bytes": 5396
},
{
"path": "src/thoth/domain/research_history.py",
- "sha256": "60ea4a0aca01dfe03ef80d7ea45a330dfa99645aa748589c4ddf7fe30d0a54d4",
- "bytes": 6251
+ "sha256": "32511765e75e0814b23e8d23b1b1239230dc4819ade5145716063e756b7021a8",
+ "bytes": 6934
},
{
"path": "src/thoth/domain/research_identity.py",
@@ -4453,8 +4943,8 @@
},
{
"path": "src/thoth/domain/research_projection.py",
- "sha256": "c9514dd552cc6ebe38b3c5608076d5b16431e299f1066209345a8ea8e9a1a494",
- "bytes": 2453
+ "sha256": "23ec337af0d3510ab132f38f93e6000d7b3c8a1e34af29565a0f452125164d87",
+ "bytes": 2525
},
{
"path": "src/thoth/domain/research_queue.py",
@@ -4473,8 +4963,8 @@
},
{
"path": "src/thoth/domain/research_stage.py",
- "sha256": "0dacc800439c1aa688cf17d5832da98cb64fbb7949faf0a0423c966400830b7b",
- "bytes": 2079
+ "sha256": "b37d0ac980303418e142ced9edb19c4a9d8e14fcacac7bfdfdf2a3b4597a6515",
+ "bytes": 2711
},
{
"path": "src/thoth/domain/resource_scope.py",
@@ -4488,8 +4978,8 @@
},
{
"path": "src/thoth/domain/revision.py",
- "sha256": "062f7ca9f9e55aab0be877bcb0c73980c5efa10b8f8c1d0ef52f833c96f0a0a8",
- "bytes": 4152
+ "sha256": "1b958c4dfdde6c04dcb74539948db2bd4952f04ff2b774435dc1ffc5dffc8a03",
+ "bytes": 4282
},
{
"path": "src/thoth/domain/sandbox.py",
@@ -4703,8 +5193,8 @@
},
{
"path": "src/thoth/ports/memory.py",
- "sha256": "f9dad69061a6b82aa754c509c90277bd957c8d0d69800cef97efb781e5c40e0a",
- "bytes": 1876
+ "sha256": "f2ddb6f5ec81b3721b97b705d255df42f741924b2eccda246cadf74b8129c1f0",
+ "bytes": 2357
},
{
"path": "src/thoth/ports/migration.py",
@@ -4718,14 +5208,19 @@
},
{
"path": "src/thoth/ports/model_catalog.py",
- "sha256": "01d1caf0830c298ced3c4303232b9226d288867c5adda71857f8aff203fe638f",
- "bytes": 233
+ "sha256": "de04cfaacdcc1761f62b10f81a2a21b6ea997f93edd05449fd20366e69deaec2",
+ "bytes": 804
},
{
"path": "src/thoth/ports/model_credentials.py",
"sha256": "22660c3b676b1eaff7aaa6c1b4366a336ce5eccf3f4a73d0bd8a56c630ae6521",
"bytes": 972
},
+ {
+ "path": "src/thoth/ports/model_tooling.py",
+ "sha256": "6f9f76aad69b2c01d7fc5fbef2232f8ceef5328952d7e1e1eb1025a68494634c",
+ "bytes": 369
+ },
{
"path": "src/thoth/ports/model_transport.py",
"sha256": "c0ffbea700ebc931cd03f441f1a92ce6694a647a8e82c2d1137962f87c916607",
@@ -4863,8 +5358,8 @@
},
{
"path": "src/thoth/protocol/bus.py",
- "sha256": "e37817f251bb52ef19d9f76b71f0b8702475c7fab38ba0b92779198d87d76bef",
- "bytes": 31889
+ "sha256": "9429120753362812774926d310e7535bca4c388684feebf2acfd5397f824e9f3",
+ "bytes": 31962
},
{
"path": "src/thoth/protocol/deferred.py",
@@ -4883,8 +5378,8 @@
},
{
"path": "src/thoth/protocol/registry.py",
- "sha256": "11f53c1653edbf72696a06b86189347503d18f6838d1823b3a8e1dc18fdfe431",
- "bytes": 12435
+ "sha256": "00d8145005899646cee5d8ab68083ca8d9161276cd4c9b059d6e80c3ee0e8e4f",
+ "bytes": 12743
},
{
"path": "src/thoth/protocol/schema_export.py",
@@ -4928,8 +5423,8 @@
},
{
"path": "tests/architecture/public_source_profile.py",
- "sha256": "058880d0b1046a3a9a32d2fe194ab99c81a878242fa57650c97e4fd66a7c758b",
- "bytes": 6757
+ "sha256": "c280b95acc1822ca317cb0239f01b63a6a3d077d6effc54f0a65f158dcb6ff89",
+ "bytes": 8030
},
{
"path": "tests/architecture/rule_transaction_helpers.py",
@@ -4953,8 +5448,8 @@
},
{
"path": "tests/architecture/test_atomicity_debt_contract.py",
- "sha256": "cc9211366db22ae29942656a942b1e0f912ff640337164dba29eff14dd42d5ba",
- "bytes": 1549
+ "sha256": "c914c278bbbe6ad22b0c6fb26615face646dfebe99b2df63ab9cca11985af61b",
+ "bytes": 1507
},
{
"path": "tests/architecture/test_checkpoint_commit_contract.py",
@@ -4993,8 +5488,8 @@
},
{
"path": "tests/architecture/test_metadata_rule_transaction.py",
- "sha256": "3d4852e12a1f57a37d5b8af1c8a9cac7a70de12f03e795d940fac212687ac485",
- "bytes": 14760
+ "sha256": "f9b5e6002b35b5b08cd92e33ef537b1f9bf2ee9ec6b038a9b4717803ff0f16da",
+ "bytes": 14854
},
{
"path": "tests/architecture/test_module_budget.py",
@@ -5018,8 +5513,8 @@
},
{
"path": "tests/architecture/test_portable_build_paths.py",
- "sha256": "2278c0da1167d55479b01890ca32346b03114f9bd8251fef61cf5f8fb5c89b8b",
- "bytes": 1052
+ "sha256": "c38c43f87367df07f416e83ef07707a1b169fa1fab032a3c4a6523cb0016b97c",
+ "bytes": 877
},
{
"path": "tests/architecture/test_portable_verification_bundle.py",
@@ -5038,13 +5533,13 @@
},
{
"path": "tests/architecture/test_public_protocol_exact_parity.py",
- "sha256": "53297d6c13678c1c2e36cdbac306b419367746ae72b882b04089674ee7ae77d0",
- "bytes": 4559
+ "sha256": "44bb4c8003758f065d434525aeca9712f386cb5ddfedbff0470294f2f681a75c",
+ "bytes": 4955
},
{
"path": "tests/architecture/test_public_source_profile.py",
- "sha256": "17dd2182f130f8189cf6b94767636e876d9697e8070c09e42b62c7b949ddced4",
- "bytes": 11481
+ "sha256": "ee865e0f8f07f1d5be7015b051829783f0853c0a983520a544b19aab5b7b5054",
+ "bytes": 12697
},
{
"path": "tests/architecture/test_read_only_command_contract.py",
@@ -5053,8 +5548,8 @@
},
{
"path": "tests/architecture/test_reevaluated_guards.py",
- "sha256": "c10b6eb6ed78a66d755ac4a7779cc6f35450e185d8a5c2644ab27a4ead8dee55",
- "bytes": 5498
+ "sha256": "a2b7382f7d68ea1088a9bb38caa658e88a9b79cfe2a7eb8c4def6c78daa869fd",
+ "bytes": 5482
},
{
"path": "tests/architecture/test_rule_candidate_pretool.py",
@@ -5128,8 +5623,8 @@
},
{
"path": "tests/architecture/test_wiki_status_references.py",
- "sha256": "a6e95aa92fbb367750bc03329d446ab9c25881d7810ecd8019380902dc46c2bf",
- "bytes": 2041
+ "sha256": "6f4505394e7435965b76f9ac2cb5d33096879cfe87e7c8870556d35c6ad98b44",
+ "bytes": 1765
},
{
"path": "tests/architecture/test_wiki_sync_gate.py",
@@ -5308,8 +5803,8 @@
},
{
"path": "tests/conftest.py",
- "sha256": "775a4f29e7fdfaa7bdfb5f1c80c9019e8ce6f6c1ff9f5109617071bd47408de5",
- "bytes": 2135
+ "sha256": "b841ef729cfb57ad804698412f001a5a9ad650490685617cdb3497d5fed1e7b5",
+ "bytes": 2776
},
{
"path": "tests/contract/conftest.py",
@@ -5398,8 +5893,13 @@
},
{
"path": "tests/integration/public_web_test_support.py",
- "sha256": "b307c69406703a93d0cd7f36f60663fac53ac1faa8e48cc3ececcb25da13fbaa",
- "bytes": 7862
+ "sha256": "b3bfad024134917b463d3cadf951b0965b3634eb348fc91d0b7ab19e54be6845",
+ "bytes": 7878
+ },
+ {
+ "path": "tests/integration/readset_helpers.py",
+ "sha256": "cb024ae982d678972494c6f3f19e15b651a7ecf63ed6a814b6179e1f06234a0b",
+ "bytes": 2661
},
{
"path": "tests/integration/reference_helpers.py",
@@ -5523,8 +6023,8 @@
},
{
"path": "tests/integration/test_a06_full_project_memory.py",
- "sha256": "749f65d85c7fda1a7aef41082d6e774b999aea02022476cfe3d9da805ea4e3c7",
- "bytes": 14531
+ "sha256": "d66f4f2c45334010adf7b348c5d9b439ace9c77b3c81f69a4be1c08cbd6513cb",
+ "bytes": 14743
},
{
"path": "tests/integration/test_a06_memory_branch_handoff.py",
@@ -5533,8 +6033,8 @@
},
{
"path": "tests/integration/test_a06_memory_preparation.py",
- "sha256": "828f704ae5b8f622f6deb02f41f73bf94d688350ebdc72e8a2de2ecc11d01c67",
- "bytes": 5772
+ "sha256": "f891d8082b95871319a655cad530effa480a213ebf02da9dfb1168d4e1b499c3",
+ "bytes": 6249
},
{
"path": "tests/integration/test_a06_memory_storage_boundary.py",
@@ -5666,6 +6166,11 @@
"sha256": "16fb26b6bd144b640e3436591dabcf65a6925c7c7db5697869c76398badd0d9c",
"bytes": 10306
},
+ {
+ "path": "tests/integration/test_action_effort_estimates.py",
+ "sha256": "a902f9f2c63008513a672f98333d5260e845d22d35871a91eedba17020427302",
+ "bytes": 12550
+ },
{
"path": "tests/integration/test_action_full_rpc.py",
"sha256": "1b537ff9167f48377d3dd8c9d63b3378b79ec4ae989f1e65272fdd2a944797b0",
@@ -5716,6 +6221,16 @@
"sha256": "ab70fd0cb764d0de1d1ce4850f407eaa91ac21471fce24c0e38b5a36914e745f",
"bytes": 2432
},
+ {
+ "path": "tests/integration/test_claude_code_login_entry.py",
+ "sha256": "389ccce8f16f7b2ca32ca751d8f5097789b8e6344b127f91a16eb69720d35ceb",
+ "bytes": 7023
+ },
+ {
+ "path": "tests/integration/test_claude_code_normal_entry.py",
+ "sha256": "5399dcfc3e518776ec6eec60356520bc7bfe4d94d01f02bad1a8724f4805cd46",
+ "bytes": 6651
+ },
{
"path": "tests/integration/test_claude_oauth_normal_entry.py",
"sha256": "6baf178eb2397abe8dbfc4ccf56c435d773c321aa3fb7303547fbd8783df9ab2",
@@ -5736,6 +6251,11 @@
"sha256": "daa480d1c52fbeadd2b2caee6bd7d56237d17b0b472513cb0b4ae7bca0602407",
"bytes": 6710
},
+ {
+ "path": "tests/integration/test_confirmed_findings_result.py",
+ "sha256": "4f7bbca356396549e83648d8c13d485ba8032672cd0361166c4533b506be55b3",
+ "bytes": 4348
+ },
{
"path": "tests/integration/test_connector_sandbox_cycle.py",
"sha256": "f96a05b2f80f883edfdaa3e18f86ef17e40bda7db0729a79d53bc6ad875a7d39",
@@ -5813,8 +6333,8 @@
},
{
"path": "tests/integration/test_full_thread_cycle.py",
- "sha256": "720b3a1f5a2b4da1560e6b07e55326228f40e037640af5d4486d17dd198923ff",
- "bytes": 11951
+ "sha256": "87dfa64f542bee953153472fb4c702b7a146dd4f756493a3b61ae76e07bfaad0",
+ "bytes": 12102
},
{
"path": "tests/integration/test_hero_overlay_and_baseline_preview.py",
@@ -5831,6 +6351,11 @@
"sha256": "2094dc306db94bb4aac566a0fa54239ddd2297128c7171a78592768ee083d07e",
"bytes": 12250
},
+ {
+ "path": "tests/integration/test_history_change_summary_read.py",
+ "sha256": "f6e1fcf89e65806674c8832094647eaa64d3d5fe293fe6b269101daefaac6de2",
+ "bytes": 1890
+ },
{
"path": "tests/integration/test_hosted_review_dispatch_gate.py",
"sha256": "0a61dd5d8069e9820ba1531b0e6061ee25563fa613f42c9527433cd9026f1510",
@@ -5881,6 +6406,11 @@
"sha256": "5e64d55d216651b3c1eb2669c9bb4eb04b881cd15e12dcb05d288af1427f98f0",
"bytes": 7761
},
+ {
+ "path": "tests/integration/test_judgment_review.py",
+ "sha256": "ac46307b0b84528e05aa99f6ff5f285ea4feaa32c4fa89f4032a1d7f3a562819",
+ "bytes": 7739
+ },
{
"path": "tests/integration/test_lifecycle_rpc.py",
"sha256": "4af68c5bcd03cfff24b5c5057f112cbc88f7b67fabc9869a8c134fb88d109b9f",
@@ -5888,19 +6418,29 @@
},
{
"path": "tests/integration/test_local_first_run_model_boundary.py",
- "sha256": "a9121583c13cc864f645898ea35dab5abc1d108b2fa1f13af184ccb8d97af177",
- "bytes": 13372
+ "sha256": "af45929a586dbbd649754cb1d98e1f1cbf23d6490354759078099c0747be1c51",
+ "bytes": 13507
},
{
"path": "tests/integration/test_local_workspace_restart.py",
"sha256": "4490d53dc07f9078c23d48a1bb81934d0ab3871588654462baa4de307c1aeb99",
"bytes": 6909
},
+ {
+ "path": "tests/integration/test_memory_edit.py",
+ "sha256": "3d94014cb5cf62e9d4d86ae25170d70607fd9b3b71294389f92f0a6d08b60f6a",
+ "bytes": 24703
+ },
{
"path": "tests/integration/test_memory_full_rpc.py",
"sha256": "6f9dde2178bf4868b0e4c26c00029f57f5cc2ac402962367d0aa8a1680af0e7f",
"bytes": 15987
},
+ {
+ "path": "tests/integration/test_memory_selection.py",
+ "sha256": "b9157c7654fb7785bda15b30d8e9cfefe9e11d871125b4d8179913099a2fbbd8",
+ "bytes": 21544
+ },
{
"path": "tests/integration/test_memory_store.py",
"sha256": "10eb73a68f02d0dc196a42f77e1f75053eaed787abc1dafd8a024fdb398b2c58",
@@ -5911,6 +6451,31 @@
"sha256": "161c0915421214899582f01c1880f482fa40e9a26a7209c7a2786142ced5e89d",
"bytes": 1149
},
+ {
+ "path": "tests/integration/test_model_call_hold_failure.py",
+ "sha256": "0f35662aee5a0cbc3068e50b7aae9e5fdba53d6f9dbb90b8b833b1332399a478",
+ "bytes": 2667
+ },
+ {
+ "path": "tests/integration/test_model_call_settings.py",
+ "sha256": "57452c0c44227051246cd74b61a3f76641dce80cf18d64b656dfa04743c7da46",
+ "bytes": 4371
+ },
+ {
+ "path": "tests/integration/test_model_catalog_refresh_rpc.py",
+ "sha256": "39591c2f561e3710022892c247ec1a958d8462c0a142d0894367f74244934cc1",
+ "bytes": 3934
+ },
+ {
+ "path": "tests/integration/test_model_catalog_startup.py",
+ "sha256": "cf871725ef3a0f1a5d4ec395e2ea1db7c2206dc67ef473261c58a8ce7e982a6f",
+ "bytes": 3629
+ },
+ {
+ "path": "tests/integration/test_model_execution_marks.py",
+ "sha256": "861b9f4e1b57585202731b1faf182f360057c87ee0cb0687580abc6adba93e5f",
+ "bytes": 4151
+ },
{
"path": "tests/integration/test_model_input_admission.py",
"sha256": "95daa9ff05bd5bb4303938cef09716c9f9814e60814d509ef9688f69d0ca9034",
@@ -5921,6 +6486,16 @@
"sha256": "6c507257d81c1e387eb63795d2fd00cfe722d221eae3e920eb6fd9473c9c20da",
"bytes": 9089
},
+ {
+ "path": "tests/integration/test_model_settings_cache_only.py",
+ "sha256": "fc19bef4ed76b4da78c5abee40d2cafe08189a14b69dda2dc6487c61b2336656",
+ "bytes": 5277
+ },
+ {
+ "path": "tests/integration/test_model_tooling_rpc.py",
+ "sha256": "47643d3c2c589666fa71f5047dd68eb08f0220f5827425754b06cafafb2c733b",
+ "bytes": 3020
+ },
{
"path": "tests/integration/test_model_wait_lease.py",
"sha256": "21b9e25d68054a8df449e0ffc9ae8acb8405bfb2614197519b3ff8bba5399c92",
@@ -5983,8 +6558,8 @@
},
{
"path": "tests/integration/test_post_learning_running_recovery.py",
- "sha256": "3d9d62b6a389184de2dee55be688d0d19062e8889820401112401a11a267a8bc",
- "bytes": 6381
+ "sha256": "2ef9bb939d556784e41fb0cfdfeae86c45b0c28a34d51b2337ab19f6fe47a395",
+ "bytes": 7634
},
{
"path": "tests/integration/test_profile_cross_domain_overfit.py",
@@ -6011,6 +6586,11 @@
"sha256": "614a65b6f283c2f4010228aa28fd628ce8688ef88d6d95b15a7bd594367f36b3",
"bytes": 6972
},
+ {
+ "path": "tests/integration/test_protected_authorization.py",
+ "sha256": "428f19742e7aee387efde46186b3c45d567e0978052921edbfef534f1b806954",
+ "bytes": 9716
+ },
{
"path": "tests/integration/test_psyche_connected_sources_live.py",
"sha256": "de8d574b336ec6fc8bdbfb4b2139cda9ffdccc793d74a86d1f403427ff30948b",
@@ -6118,13 +6698,13 @@
},
{
"path": "tests/integration/test_research_assembly_cache_basis.py",
- "sha256": "2f2337c4029c4b11c9811d6ed792caa332636ad2bee2c523d0f96c0ef2b2e5ae",
- "bytes": 6421
+ "sha256": "9cf0d9b7b30eed98f7f40da082025d5879524e5bba73702eb1df142efa48990e",
+ "bytes": 7267
},
{
"path": "tests/integration/test_research_boundaries_v2.py",
- "sha256": "ecbd092c8a9294d9aced650e29e6e944da2e5d44999ede1edfb3a99db4ec2a33",
- "bytes": 14711
+ "sha256": "9a3b624355e7a8cde03e1ac44d275743dcf8f85a6e9257c590b78706df3d227d",
+ "bytes": 14762
},
{
"path": "tests/integration/test_research_catalog_usage.py",
@@ -6133,8 +6713,8 @@
},
{
"path": "tests/integration/test_research_cycle_restore.py",
- "sha256": "5deb5e8ae9736785ad28d191c629c56d847f2a60ee3300ec63910231b3390b4c",
- "bytes": 27962
+ "sha256": "e93e7d22ea20199ad6ecff7145d46737aaa751f01a4e51a2feefc710b34b6efe",
+ "bytes": 27992
},
{
"path": "tests/integration/test_research_export_grounding.py",
@@ -6148,8 +6728,8 @@
},
{
"path": "tests/integration/test_research_final_repairs.py",
- "sha256": "b8fce7e89cd41dcac335014b1055812a1e5efd4f7e14c09dbe5cb7ed4fe52b53",
- "bytes": 5899
+ "sha256": "47e67533c913a0ef3350696d4c83cb754fd09d9a303c0981ac9216ae844001b5",
+ "bytes": 5956
},
{
"path": "tests/integration/test_research_focus_basis.py",
@@ -6178,8 +6758,8 @@
},
{
"path": "tests/integration/test_research_input_queue.py",
- "sha256": "7d56b1a26f8ad63996aa5f823cf2c32a7917212322f6b88728098daf655837d1",
- "bytes": 37865
+ "sha256": "5124f1ae397930f4c42a2f7a1b90cbb8d89f29479759ac0d8615cdc53d4eff74",
+ "bytes": 38082
},
{
"path": "tests/integration/test_research_lineage_publication.py",
@@ -6191,6 +6771,11 @@
"sha256": "ef4a7004d534105928059f587d261c707b06a69197a458bbacd9cd97c89dfe53",
"bytes": 8903
},
+ {
+ "path": "tests/integration/test_research_memory_correction_currentness.py",
+ "sha256": "03f4d862ebba445064bac1dc7c576128145c7ed60fb61401ecde53bb56b49ebe",
+ "bytes": 4148
+ },
{
"path": "tests/integration/test_research_process_and_queries.py",
"sha256": "65b9408ec7400e65fb0320b9349b84a843ef96d51088c233e4dfc51cbe8750cd",
@@ -6201,6 +6786,16 @@
"sha256": "f60df609b0864170b82c604b89ed5998d6510243dd26d847a0f0cc8bf13e9319",
"bytes": 2994
},
+ {
+ "path": "tests/integration/test_research_readset.py",
+ "sha256": "1b7cd38b9afd20772c68674ee5ded51a8169591c3aabaa7441ac804d29ef0ed4",
+ "bytes": 10423
+ },
+ {
+ "path": "tests/integration/test_research_readset_memory.py",
+ "sha256": "13fffdd3f6e7ee0b132e5e93ddcd3233b32494545fe1dca3072f73bf40fe86c3",
+ "bytes": 8258
+ },
{
"path": "tests/integration/test_research_recovery.py",
"sha256": "59dfd4f9a9cfc0872cbad7c42cd7a383a4ccf9494973584767065d37f394630c",
@@ -6218,13 +6813,13 @@
},
{
"path": "tests/integration/test_research_request_v2.py",
- "sha256": "f629d25243ac9c548f4e83e9af88754b454eca4d9f691cecf1f8b9fe2b3f5ac8",
- "bytes": 19492
+ "sha256": "bc18825c36f894799a60979bc4b929f0e1ed07b2bd9c8108bb49609f68fe3daa",
+ "bytes": 19644
},
{
"path": "tests/integration/test_research_scoped_conflicts.py",
- "sha256": "beacbb8269d93ad9aab996e3a46b04387a588c9edfe4d345e7f106b4712b42cc",
- "bytes": 9996
+ "sha256": "565b8e1030d60fc9a1d030d3ad8135b08f34c24d5722cf2d4264078264bd019d",
+ "bytes": 10053
},
{
"path": "tests/integration/test_resource_scope_atomicity.py",
@@ -6263,8 +6858,8 @@
},
{
"path": "tests/integration/test_resource_scope_memory_b.py",
- "sha256": "bd9ac33c5c23b4ca7f6c28c667dd6277f4b3d4bb8877432c77567e30f5566c12",
- "bytes": 13499
+ "sha256": "3f0285e6535eb08b8bae29e55ccdf84f58aa8e5ec59145076d2f1c69fe754ab6",
+ "bytes": 13597
},
{
"path": "tests/integration/test_resource_scope_memory_controls.py",
@@ -6273,8 +6868,8 @@
},
{
"path": "tests/integration/test_resource_scope_memory_preparation.py",
- "sha256": "36b0661c69da9666ac96860fa7dcf7e0e227bcdabdb71ba94c64d4558b7fdf0c",
- "bytes": 4244
+ "sha256": "6c59360f0bebc2840449b2a1c9c6a526f2b5d9094766095258b8f8e32c44b171",
+ "bytes": 4465
},
{
"path": "tests/integration/test_resource_scope_model_b.py",
@@ -6328,8 +6923,8 @@
},
{
"path": "tests/integration/test_restore_currentness_consumers.py",
- "sha256": "5276c472e575567663b7dcad5f10e54e9b3fbc89826116b89562f4d20b4707ff",
- "bytes": 7155
+ "sha256": "aa35e7393e4415cd666e2b9d52b3c92210864cfa9b7ca87a5eb0b1ad77d30d60",
+ "bytes": 7290
},
{
"path": "tests/integration/test_restore_drift.py",
@@ -6361,15 +6956,30 @@
"sha256": "9af1323e98d31c51b8f4b420a4bc52b6f1b237dc54553ff7dd97a4c7f2f2ca30",
"bytes": 8888
},
+ {
+ "path": "tests/integration/test_result_compare_criteria.py",
+ "sha256": "0f5e75025e0966554bd60dd2cbeca54f0bfd1eae3019676acc2ac30c1da74914",
+ "bytes": 4513
+ },
+ {
+ "path": "tests/integration/test_result_usage_read.py",
+ "sha256": "0741348bc846555cfd21d129eb85070a81bf4a1dd0d1b01999f7defd0f5292e1",
+ "bytes": 2825
+ },
+ {
+ "path": "tests/integration/test_resume_interrupted_research.py",
+ "sha256": "4caec6c001505cc58ec06769057d683401f2003bfbb905a98c1423cbbaec3311",
+ "bytes": 17235
+ },
{
"path": "tests/integration/test_retrieval_gap_reassessment.py",
- "sha256": "d35bc4add6b7568ed342d7a97c5ab2c2cf4d68eb8d415b651931fe66ea9afd90",
- "bytes": 2932
+ "sha256": "ca222a34a891dc5286f4abbe3b6cc89d3af5d5a9e8eff2850dbc4c5e24ba412b",
+ "bytes": 2989
},
{
"path": "tests/integration/test_revision_full_rpc.py",
- "sha256": "826fdf96776cdbbd82cb41414258b3fb95eb9d8ddf95e74291cf8a1cc76ee486",
- "bytes": 18035
+ "sha256": "0ad36d95ea809f99c6e74db0cea6ce5a2560eb0bcb1fdcf056df3e4bbdfe45f5",
+ "bytes": 22002
},
{
"path": "tests/integration/test_revision_rpc.py",
@@ -6378,13 +6988,18 @@
},
{
"path": "tests/integration/test_revision_service.py",
- "sha256": "8b5bce43451860568fab89c4df4a8d777e887f8f0f6034a4e0bea377d8dceed1",
- "bytes": 7486
+ "sha256": "1c51fb7073d05881dcaf6558bd9ac73b18ded8fd7b1d818797441d3e9ac36e18",
+ "bytes": 8577
},
{
"path": "tests/integration/test_scope_read_memo.py",
- "sha256": "1fc0978933510305dece3dc2820896af121fb4f02024f9dbbd26e0470e187972",
- "bytes": 13689
+ "sha256": "84561f4400c6b28e4f99f16d21fdcd6ac5ec6e1cc5e3f879be582231f84ceafc",
+ "bytes": 13771
+ },
+ {
+ "path": "tests/integration/test_scope_read_request_memo.py",
+ "sha256": "80fdd2d95682946dc72ebcda5ce7d99fd096da80328e2d246c516a5823f50999",
+ "bytes": 3393
},
{
"path": "tests/integration/test_selected_evidence_query.py",
@@ -6426,6 +7041,16 @@
"sha256": "e2247840510468a907c75a130cc583cd9bcb8cccd66e3dae2893b4db011fba17",
"bytes": 6490
},
+ {
+ "path": "tests/integration/test_thread_read_currentness_cost.py",
+ "sha256": "abbd204d3a12cb9c6475d0cc5ba2b7c96043da19281375e6698355896336f63c",
+ "bytes": 5793
+ },
+ {
+ "path": "tests/integration/test_thread_read_summary_view.py",
+ "sha256": "37d533a80e762faa79b471946c4d72a055f0f8a207110507577d04d42e5794a2",
+ "bytes": 5517
+ },
{
"path": "tests/integration/test_thread_rpc.py",
"sha256": "95dc24f7963420f18ffe8dc35395d98dfcd2da18400a01a0cd7c0acf51b18155",
@@ -6463,8 +7088,8 @@
},
{
"path": "tests/integration/test_user_activity_thread_read.py",
- "sha256": "5b2c52675d4c3774deabd9e45db5a9c5d8026a7ba53dff7527fcb195af7ade63",
- "bytes": 2336
+ "sha256": "fce16d64670557b795c4b6876487f9c996f8e89563e62c36fd520b9eb0645f6c",
+ "bytes": 2376
},
{
"path": "tests/integration/test_workspace_restart_storage.py",
@@ -6523,8 +7148,8 @@
},
{
"path": "tests/parser/test_pdf.py",
- "sha256": "4795c6c6b9a26aff65c8325f637454432a5a8c12270274c88a2a5dd37c45d6c3",
- "bytes": 1947
+ "sha256": "5cfee7f55ac08da9f52f912b73cf0ea775751a82de9c01d5035548449f4be299",
+ "bytes": 5778
},
{
"path": "tests/parser/test_test_log.py",
@@ -6581,6 +7206,11 @@
"sha256": "88801b7a5de2c9646fc2b4dcbd8c42d404461dced2ce96d3c6d973047dbf059e",
"bytes": 6364
},
+ {
+ "path": "tests/unit/application/test_research_freshness_memory.py",
+ "sha256": "1602b1b1c7860bd85b15d77e4257f5406a12714620d28a318324f212f60b4ef5",
+ "bytes": 3939
+ },
{
"path": "tests/unit/application/test_user_activity_projection.py",
"sha256": "908d63ef66f6baac5ea591402e5cbdc71dd79106d846689cd0b3e4dfa608c48c",
@@ -6636,6 +7266,11 @@
"sha256": "f71003b86b5846d2cc4b76a3addd22947713f18f8aaa0e71d2e8546fd5ffe328",
"bytes": 2627
},
+ {
+ "path": "tests/unit/domain/test_effort_bands.py",
+ "sha256": "6e5d0b91c2b04647b21377445ec13052e40917bc26c98cf9981d13693c7355c6",
+ "bytes": 6372
+ },
{
"path": "tests/unit/domain/test_environment_profile.py",
"sha256": "8a78047a6f92a78a91cf42f83680b01c600c0905108ca15a3db0df54070cbc3c",
@@ -6718,14 +7353,24 @@
},
{
"path": "tests/unit/domain/test_oauth_retry_policy.py",
- "sha256": "066c2912ff92cb9583b53c256b444dd5f414f8c9366b3617edc6094721d08945",
- "bytes": 487
+ "sha256": "b1874e6312b06aff5d158a878b3e144a26c0667e9b245b3e91d1432af36454b7",
+ "bytes": 478
+ },
+ {
+ "path": "tests/unit/domain/test_protected_payload.py",
+ "sha256": "7ec2375aebd2054f33fc57e9c0b8904c65912ef3a30c3dbf648a56a3479c111e",
+ "bytes": 5391
},
{
"path": "tests/unit/domain/test_receipt_evidence.py",
"sha256": "637a1966c04d315e5b33af61b3dbb4351cc24947ce2bb04e9f4e8137b59e48db",
"bytes": 1809
},
+ {
+ "path": "tests/unit/domain/test_receive_observation_canonical.py",
+ "sha256": "43c9b45bc7c6ee6b5c93d5d24b8d7fceefa9c26c71230b7b825ff4f398deb08a",
+ "bytes": 1338
+ },
{
"path": "tests/unit/domain/test_research_failure.py",
"sha256": "c0224f40d323e07d57c974ef5a90556ef266aff8d0ff2570891c75af347c1148",
@@ -6763,8 +7408,13 @@
},
{
"path": "tests/unit/models/test_account_connections.py",
- "sha256": "e192e369ce1a374e7ab06b6760a03abe35a23a6f8e6ecd653331b2601bcacb25",
- "bytes": 7979
+ "sha256": "884f9cfbe3dbb3779ff6bbf46cca65077de0da2692678586360bff411073d3ef",
+ "bytes": 12788
+ },
+ {
+ "path": "tests/unit/models/test_action_effort_wire.py",
+ "sha256": "a5bed874a53a129b079bd67f34a288076efe5ea8860023f88c2315d2e1310200",
+ "bytes": 1543
},
{
"path": "tests/unit/models/test_anthropic_messages.py",
@@ -6781,10 +7431,35 @@
"sha256": "1573cdea5b3e1493010ad1731e84afe6eb136ed08d3d77890e4224ca3e97ca21",
"bytes": 818
},
+ {
+ "path": "tests/unit/models/test_catalog_marks.py",
+ "sha256": "3ad4e9d89a3ec0a4174bca0aab994c126497e8ed40ea84fe4824aabcfb540675",
+ "bytes": 4058
+ },
+ {
+ "path": "tests/unit/models/test_catalog_startup_refresh.py",
+ "sha256": "a7304b587fd1a288fd77162c1b0c9df8eab7f3e7eea21a062d31b5290e5ee073",
+ "bytes": 4188
+ },
+ {
+ "path": "tests/unit/models/test_catalog_store.py",
+ "sha256": "a53e52aba376c04f32bb3bafd2a1afbe9a9dd679194f029db7c54d5c2cc75067",
+ "bytes": 4372
+ },
{
"path": "tests/unit/models/test_claude_code.py",
- "sha256": "228fadd7a136be3dbd9988be229fc676b96155d8ed609b66f66d085d0d3a3ff7",
- "bytes": 9294
+ "sha256": "fece5574aa6c90b6bcbb60104207ec3f24993caaf9a44945e6876fd922bcd9d5",
+ "bytes": 20479
+ },
+ {
+ "path": "tests/unit/models/test_claude_code_executable.py",
+ "sha256": "161ff42a04cc70400a6f81336c5923b583b903233425fe31575a454722be8461",
+ "bytes": 5070
+ },
+ {
+ "path": "tests/unit/models/test_claude_code_login.py",
+ "sha256": "38d4f9f100e5a9580d39b7f56a657fe90c89805cc167e8c6abb7274b58ca6d54",
+ "bytes": 12856
},
{
"path": "tests/unit/models/test_claude_messages.py",
@@ -6811,10 +7486,20 @@
"sha256": "f56e316353f2f83d92029360486e9f1a5bf38629a052aa2b1582ed285cc6f3dd",
"bytes": 7146
},
+ {
+ "path": "tests/unit/models/test_codex_catalog_snapshot.py",
+ "sha256": "445ba715249b1370c8dc9c05c21e51c73fa85c1daf7967f6cafa7a3a8b770dc9",
+ "bytes": 9691
+ },
{
"path": "tests/unit/models/test_codex_isolated_broker.py",
- "sha256": "8d84c39bc8b8b273fec0f3a2c56b8eccaa3ead5dfd6fb28ff658a5473e65bff3",
- "bytes": 33668
+ "sha256": "3eb985ccdb2e8030168f20f1d6757e1902cde23dd33dc167c3c762b7338810ba",
+ "bytes": 36281
+ },
+ {
+ "path": "tests/unit/models/test_codex_login_completion.py",
+ "sha256": "7d0528d8a60a0c0bdcd930e021f41aeec50b613c4c505cfbf67b77f07e81285a",
+ "bytes": 4820
},
{
"path": "tests/unit/models/test_codex_oauth_model.py",
@@ -6831,6 +7516,11 @@
"sha256": "78ed4bab51ec765276d773b403801a4df3898928c777a85d995df151aa17fb25",
"bytes": 2236
},
+ {
+ "path": "tests/unit/models/test_interrupted_call_retry.py",
+ "sha256": "56c4788c484e86b00e11f04a003e5ccb9a0f2437b4409beb01629a0f794abac0",
+ "bytes": 9882
+ },
{
"path": "tests/unit/models/test_local_credentials.py",
"sha256": "ba72dee5cb549e2fc382fbba7d22a3e002828df468731a92cb7c7134eeeda060",
@@ -6861,11 +7551,6 @@
"sha256": "d53c7dd5f901695ab9bea32094591b71cb3d17960f97b14b0cb79e77be4aa0b2",
"bytes": 5440
},
- {
- "path": "tests/unit/models/test_omo_native.py",
- "sha256": "d7332a0c115253f30d6d9f83e0eff3f6b3ac7e2f4cd2ff2f8e2d158c8e6b9857",
- "bytes": 2908
- },
{
"path": "tests/unit/models/test_openai_responses_model.py",
"sha256": "6f0095d687cb4f1b5cccba761e7c1d7134bf3d677e040bca6f25c468ceb57223",
@@ -6878,8 +7563,8 @@
},
{
"path": "tests/unit/models/test_ranking_reference_contract.py",
- "sha256": "769da8d31e58a64bb56423a2b05eb9f2a1e628a9c0072282346f0ccd19e1d124",
- "bytes": 11300
+ "sha256": "300693ce307872584006fd59a7acceba6a3fb924fe4391365aca747156bd4394",
+ "bytes": 11189
},
{
"path": "tests/unit/models/test_reference_schema_pinning.py",
@@ -6896,6 +7581,16 @@
"sha256": "5a564a4efa1d465ce9181de8115bd716c1c5244379feff55d56a8ca075f38f46",
"bytes": 2380
},
+ {
+ "path": "tests/unit/models/test_stream_pace.py",
+ "sha256": "b47b05879a706e7a458deee19ad86aafab8e7e34a97cb5b3811e00af8afda4d0",
+ "bytes": 11918
+ },
+ {
+ "path": "tests/unit/models/test_tool_installers.py",
+ "sha256": "cb200125b17e78c8d9c0009cb6ee99052ba706c2c290ed2df1516e2348795296",
+ "bytes": 6518
+ },
{
"path": "tests/unit/models/test_transport_diagnostic.py",
"sha256": "64a45abe7ff36353f692ba6e5e088f0eac0b7b3d378afcf63d63a7bac6b1bc1b",
@@ -6913,13 +7608,13 @@
},
{
"path": "tests/unit/models/test_windows_process_job.py",
- "sha256": "a8d2a942872199a8ef83c46c86c0d0feb5865fb5272286b25f48c27c664fd7d1",
- "bytes": 6532
+ "sha256": "c43376a60aec25945870ee67bcf684776ed55de2fb908aaeb95697c5c9b4947a",
+ "bytes": 7107
},
{
"path": "tests/unit/models/test_xai_responses.py",
- "sha256": "b93e16792e1bd3cc31b29ecf6dbd416a5b96e81e6209dba435d3a06b4c30bcf6",
- "bytes": 8854
+ "sha256": "edf96d61a0f9e7a8d76a442d4abb55a8564aeb4ab056e399fc95c9cd4bb6ff4c",
+ "bytes": 4415
},
{
"path": "tests/unit/models/test_xai_workspace_oauth.py",
@@ -6976,6 +7671,11 @@
"sha256": "d8d4c56f9fe2a568cec558f7ab1460403d0832d9cde04aca9439eeae68eaddc0",
"bytes": 7248
},
+ {
+ "path": "tests/unit/services/test_authorization_currentness.py",
+ "sha256": "b8e38880709329ad4fe4618305b07e7377fe03bc30659b303b333367a3b330b3",
+ "bytes": 4869
+ },
{
"path": "tests/unit/services/test_criterion_compiler.py",
"sha256": "ab7bdbc68f3c75b6c9262657059539ba4ec8b1c820d90fb5ccb2575705c2dd4f",
@@ -6991,6 +7691,16 @@
"sha256": "6589e31690e08661710c4d9684a2dcef9bd8fdea13979d274b6b1ffa86b78652",
"bytes": 1658
},
+ {
+ "path": "tests/unit/services/test_history_change_summary.py",
+ "sha256": "1802c58e727470625581ff23be1aa50c755b6603b78cefa0602b4f8ee50dc29d",
+ "bytes": 6539
+ },
+ {
+ "path": "tests/unit/services/test_judgment_review_service.py",
+ "sha256": "18ecbad5e0ed9b152db855902e271f10eb4ead60124b6bddaec3656c6ccd2f0f",
+ "bytes": 12715
+ },
{
"path": "tests/unit/services/test_model_route.py",
"sha256": "ee0f9a219d7daa46fd095b69b66a1bb48d3046be38130a238d1a30b0c1d9d743",
@@ -6998,8 +7708,8 @@
},
{
"path": "tests/unit/services/test_retry_model_mismatch.py",
- "sha256": "1e1d9530283b7f67e084171a6bb7c8afc20eb545066e5693f48d7c24efc22a9e",
- "bytes": 6362
+ "sha256": "0fcdbba868e3884608f6c618a869241a2b988891b089fe1d90526a161f1417e7",
+ "bytes": 6353
},
{
"path": "tests/unit/services/test_tui_intent_contract.py",
@@ -7026,11 +7736,21 @@
"sha256": "046981fad09b42daf35e895b6222f51691f168cc525efadee42b53cb1435ef29",
"bytes": 7797
},
+ {
+ "path": "tests/unit/test_confirmed_findings.py",
+ "sha256": "a7fbf475b8ae78c2c4e58114e790139c1679c29e1e61285b5bf68bc4e3dcc6e9",
+ "bytes": 2740
+ },
{
"path": "tests/unit/test_connector_cleanup_accounting.py",
"sha256": "4c5dbc74bf74481dd0b3b254c77ac942cbed38826f3d362ec1a7828b3c2464a4",
"bytes": 3722
},
+ {
+ "path": "tests/unit/test_criterion_delta.py",
+ "sha256": "a4785d843974a37b33a9e68aecf38956e3be8078c06762c790b0cf51f46c6b11",
+ "bytes": 4679
+ },
{
"path": "tests/unit/test_docling_multi_provenance.py",
"sha256": "f423c17960af9a001fbdda8743c66e238bbbd8b63c2d366993875d1d9d14cf35",
@@ -7051,10 +7771,15 @@
"sha256": "0f04415b1ed3499989f262513d926ebb735392973cfa8ed971f8e0ddc2bf62bb",
"bytes": 2471
},
+ {
+ "path": "tests/unit/test_history_projection_titles.py",
+ "sha256": "5d659b259602eaf5c8f75c57321b40ca9bdfa2a17d5ba5b6cc0290777a55700b",
+ "bytes": 738
+ },
{
"path": "tests/unit/test_hosted_review_credential_rpc.py",
- "sha256": "880dc9bdee53a9d26fc7ce25dcc4e6f39e60a522364d407125cc2d34c64c5dec",
- "bytes": 6618
+ "sha256": "47d3ac8ababb3560658fc43fd55db8c7378b34d6a042070217d86b72a2ef1689",
+ "bytes": 6692
},
{
"path": "tests/unit/test_hosted_review_deploy_scaffold.py",
@@ -7081,6 +7806,46 @@
"sha256": "ebade1c6326c80e6621a5533c03d2f9cfcb997fbb1feb8bfcb36dc5d98fa936e",
"bytes": 12975
},
+ {
+ "path": "tests/unit/test_memory_recall.py",
+ "sha256": "f804fa7b747e92f85f5dd9f9a013d6bfb1c47e9181edbeddafe0bd623a73fa3d",
+ "bytes": 10910
+ },
+ {
+ "path": "tests/unit/test_memory_relation.py",
+ "sha256": "5d9bd9ec58d976b1a08527e443642df631618d1d2005f4ede4a95ab40dbde9b6",
+ "bytes": 4807
+ },
+ {
+ "path": "tests/unit/test_memory_relation_resolver.py",
+ "sha256": "ecf9274c80911be3be539e9082e551c3da4b3af3a58b21b7ff1ddb6d386313bd",
+ "bytes": 8935
+ },
+ {
+ "path": "tests/unit/test_memory_revision_list.py",
+ "sha256": "6b3a924123fe311c3f52629cdffa3089ea861e48da9a783ef9a173408b44e9b1",
+ "bytes": 5998
+ },
+ {
+ "path": "tests/unit/test_memory_selection_metrics.py",
+ "sha256": "0efda4aca95fecb82b2beeda595344217a1e9962c6cf564a8ebf2e2fdf53aa4a",
+ "bytes": 4213
+ },
+ {
+ "path": "tests/unit/test_memory_shape.py",
+ "sha256": "143e2cd4ea76d2544aa2b50d73c33e7f3416dd0b8c69e2de1db6464175ea5ef2",
+ "bytes": 5021
+ },
+ {
+ "path": "tests/unit/test_memory_summary.py",
+ "sha256": "2a748fc3bff4ba39ed28898dd96537ea875bc7000225e1ac197713ebe7d13383",
+ "bytes": 2326
+ },
+ {
+ "path": "tests/unit/test_memory_supersession.py",
+ "sha256": "7791a9df46888ede201ac557313fb51d713e48674ffc607bd5fbc3b8cb3aae36",
+ "bytes": 1853
+ },
{
"path": "tests/unit/test_operation_reentry_registry.py",
"sha256": "019af79fa67e596211ce244e2429988d202c4ea7ff32b314e2069219548d7082",
@@ -7121,6 +7886,11 @@
"sha256": "c1f5ce7a1da63e213fad01551684cb5015c1ab75fb58f5df42ffe0cc6796c5ad",
"bytes": 3016
},
+ {
+ "path": "tests/unit/test_research_commit_scope.py",
+ "sha256": "493592df544950af94ca6eb2223d23c5136f26a66ac8b8001a737df56db714d5",
+ "bytes": 1619
+ },
{
"path": "tests/unit/test_research_failure_cause.py",
"sha256": "25654f1178e5d42dd17ba860883773520b88e277c8493a92558f17324fa175f4",
@@ -7138,8 +7908,8 @@
},
{
"path": "tests/unit/test_research_usage.py",
- "sha256": "ab051ade1cd9b911ebd4ba9998f8c8c0bc200ea9eee524021ebadb8d5ad835c6",
- "bytes": 3349
+ "sha256": "f729d6f2523f8530179f24d2aa9c5e8b546e8b1cfb54358bbc31580e53e2ee51",
+ "bytes": 6489
},
{
"path": "tests/unit/test_resource_intake_basis.py",
@@ -7151,10 +7921,15 @@
"sha256": "982706eecd409c7fa9a11959a98b50602fbda5a6049d8eb8a420aa60dd8b7548",
"bytes": 1747
},
+ {
+ "path": "tests/unit/test_running_research.py",
+ "sha256": "01a57e036610112afb6b0c2b1dac47dc1ade4a7b1070a2d2d736d283dbf8fca4",
+ "bytes": 1940
+ },
{
"path": "tests/unit/test_scope_read_memo_context.py",
- "sha256": "7f4934199f2949d12ad025155a5606ec15c60f7e57b2c61675f84645ceca0187",
- "bytes": 3222
+ "sha256": "748c245fba5caeea53f783e245da8c21c3348e62809664637583554677d01c5e",
+ "bytes": 3596
},
{
"path": "tests/unit/test_selected_evidence_reader.py",
@@ -7195,5 +7970,5 @@
"source_archive_sha256": "523b6b783080f57fc229cdf0449683ffc26aad999376b410f6fb1c78a383b75c",
"public_repository": "https://github.com/hyperdrivebeep/thoth",
"snapshot_type": "MIT source-only public preview",
- "verification_basis": "2026-09-29 public experimental auth/restart source update. Scoped backend/Web checks and final Web build passed on recorded stages. Independent r2 Chrome/RPC with synthetic providers accepted a partial scope and separate-process saved-result reopen; diagnostic failure and unrun scenarios remain documented. Current-source FULL, real provider output, Desktop coexistence and physical reboot are unverified. Historical selected failure count 25 and original archive digest are retained as historical evidence only; latest prior completed backend FULL was 1694 PASS, 41 FAIL, 1 SKIP (Web/doctor not reached). Publication is source availability, not verification completion or hosted deployment. Published runtime/test/dependency file bytes match the recorded r2 snapshot."
+ "verification_basis": "Current public file inventory; the original source_archive_sha256 is retained as historical publication evidence. Product verification results are recorded separately."
}
diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx
index 028450e..84a337a 100644
--- a/apps/web/src/App.tsx
+++ b/apps/web/src/App.tsx
@@ -10,7 +10,7 @@ export function App() {
if (HistoryExample && example === "history") {
return 이력 화면 예시를 여는 중…
}>;
}
- if (ResultExample && (example === "result-before" || example === "result-after")) {
+ if (ResultExample && (example === "result-before" || example === "result-after" || example === "result-iris")) {
return 결과 화면 예시를 여는 중…}>;
}
return ;
diff --git a/apps/web/src/api/fixtures/iris-judgment-excerpt.json b/apps/web/src/api/fixtures/iris-judgment-excerpt.json
new file mode 100644
index 0000000..3bac1f4
--- /dev/null
+++ b/apps/web/src/api/fixtures/iris-judgment-excerpt.json
@@ -0,0 +1,287 @@
+{
+ "source": "Trimmed from QA IRIS run 2 operation/result/read (real model output). Only fields the judgment screen reads.",
+ "answer": "검증 보류 · 부분 초안: FACT — 기준은 IRIS 공고의 「붙임 08. 품목개요서(RFP).pdf」(IRIS 공고 HTML 1037행, span:1580e7b4-64bb-4eb0-99c2-5a8fc6113f70) 중 통합형-07-01 **1세부 ‘항만 모빌리티 무인 운용을 위한 항만 특화 자율주행 기술 개발’**이다(RFP p.36, 1·20행, span:3ac72414-5d0f-4a8c-a2eb-8b85a20b69e4, span:5107fe50-16bb-4bc1-a62b-5f1885de51e4). p.37, 20–22행은 계획서에 정량적 목표치를 제시하도록 하고 ‘비가시 객체 감지율(%)’과 ‘충돌 위험 인지 및 제어 지연시간(ms)’을 열거한다(span:b97dce8e-863b-4597-bfb9-0ffbfd7a97a7, span:f125282c-515c-476f-a320-b01c38bc6a62, span:d9af94fd-f4de-4b31-94ba-95121b9c5b90). p.44–45의 **3세부** ‘실시간 처리 지연시간(ms)’ 및 ‘위험 상황 감지 및 대응 지연시간(sec)’은 1세부 지표와 혼동하면 안 된다(span:54c64cdf-ddb9-405a-b2d5-d83b41266154, span:bddd96e1-9799-4768-b71a-3adc90134748).\n\n**참고 가능:** Yao 2023 survey의 융합 방법 논의와 CenterFusion 소개·nuScenes 결과 요약(p.14, 85–86행; p.16, 81–82행; p.39, 65–67행, span:5cafb75e-087e-41d1-b437-8f84f7668bbb, span:db2b956b-ba8c-4f5f-91ce-54ae20a143b7, span:dd70f6e8-a2bb-4f4a-b73f-45fb192decdc, span:2d934ff7-6931-46b2-a50b-f7a763b032fb, span:57c06842-fd12-4f29-aa3a-60b892f3832e), CenterFusion 원문의 레이더·카메라 결합 구조와 nuScenes 평가(p.2, 38–39·88–89행, span:e0190a7b-f6c9-458d-8101-ed7827d32fdd, span:76dd48db-22e6-4c3f-b740-2a8282ebb034, span:67964368-89f4-4caa-a451-e1c14cecce19), AI-Hub 4D 이미징 레이다 자료의 센서·라벨 설계(HTML 3697·3989–3998행, span:b3a66922-6ae1-4742-b639-5db27437b4d9, span:6cd5d0ab-63e1-45ef-91dc-65e4071836e2, span:10eab22b-a1f8-4ce5-a3d1-84c49cab909d)는 방법·데이터 설계에 참고할 수 있다. AI-Hub에는 카메라+레이다 융합 검증 모델로 CenterFusion이 열거된다(HTML 3649행, span:ff2c5ba7-ab7e-4f6a-8bd8-183d34cc017d).\n\n**항만 성능의 직접 근거로 사용 불가:** AI-Hub의 수집 환경은 고속도로·도심이다(HTML 3249행, span:7759d43a-1cc0-4e45-ba28-588554656458). `camera_visibility`의 ‘y’/‘n’은 카메라상 식별 가능 여부를 나타내는 라벨이지 항만 비가시 객체 감지율 측정값이 아니다(HTML 3989–3998행, span:6cd5d0ab-63e1-45ef-91dc-65e4071836e2, span:10eab22b-a1f8-4ce5-a3d1-84c49cab909d). survey의 CenterFusion 수치 32.6 mAP·44.9 NDS는 nuScenes 결과로 서술되어 있으며(p.16, 81–82행, span:db2b956b-ba8c-4f5f-91ce-54ae20a143b7, span:dd70f6e8-a2bb-4f4a-b73f-45fb192decdc), 항만 감지율(%)이나 인지·제어 지연시간(ms)으로 환산할 근거가 없다.\n\n**UNKNOWN — 추가 확보 필요:** 제공된 1세부 발췌에서는 레이더·카메라 **조합의 RFP 명시를 확인하지 못했다**. ‘RFP에 명시되지 않았다’는 결론도 RFP 전체 확인 전에는 유보한다. p.37의 지표와 단위는 **제출할 목표치의 항목**이지 실제 측정값이 아니다. 제공 자료에는 두 항만 지표의 실측값·원자료·시험 승인 기록이 없어 적합성, 성능 충족 또는 시험 승인을 판정할 수 없다. 다른 연결 국내외 데이터셋 전체의 조건·원문 위치 역시 이 발췌로 완결되지 않는다.",
+ "answer_status": "PARTIAL_HOLD",
+ "assessment": {
+ "missing_items": [
+ "criteria exist but official evaluator inputs are incomplete",
+ "evidence is extracted but not support-verified",
+ "comparison conditions have not been confirmed",
+ "counterevidence has not been checked"
+ ]
+ },
+ "portfolio": {
+ "hypotheses": [
+ {
+ "hypothesis_id": "hypothesis:object:807e7119-d046-412c-a6ca-3cfa9d3f50a4:rfp-sensor",
+ "statement": "UNKNOWN — AI-Hub의 ‘카메라 + 레이다 융합 | CenterFusion’ 표기를 RFP의 필수 센서 조합으로 옮겨 읽었을 가능성이 있다. 제공된 RFP 발췌만으로 조합의 명시 여부는 확정할 수 없다.",
+ "uncertainty": "RFP 발췌의 비명시는 RFP 전체의 비명시를 증명하지 않는다. 적용 문서 버전도 재확인이 필요하다.",
+ "support_evidence_refs": [
+ "span:3ac72414-5d0f-4a8c-a2eb-8b85a20b69e4",
+ "span:5107fe50-16bb-4bc1-a62b-5f1885de51e4",
+ "span:ff2c5ba7-ab7e-4f6a-8bd8-183d34cc017d"
+ ],
+ "counterevidence_refs": [],
+ "counterevidence_queries": [
+ "RFP p.36–37과 해당 1세부 전체에서 레이더·카메라 조합을 필수로 지정하는 문구가 있는가?"
+ ],
+ "critical_review": null,
+ "discriminating_tests": [
+ {
+ "expected_if_alternative": "적용 RFP 또는 공식 변경 문서에 필수 조합과 그 위치가 명시된다.",
+ "expected_if_true": "AI-Hub 표기만 확인되고 RFP의 필수 조합 지정은 확인되지 않는다.",
+ "procedure_candidate": "담당자에게 적용 RFP 원본·변경본과 센서 구성 문서를 요청해 1세부 문구를 AI-Hub HTML 3649행과 출처별로 대조한다.",
+ "risk_tier": "R0"
+ }
+ ],
+ "missing_evidence": [
+ "해당 공고에 적용되는 1세부 RFP 원본·변경본의 센서 구성 문구",
+ "공식 과제 설계 문서의 필수·선택 센서 구분"
+ ]
+ },
+ {
+ "hypothesis_id": "hypothesis:object:807e7119-d046-412c-a6ca-3cfa9d3f50a4:transfer",
+ "statement": "외부 자료는 융합 방법·데이터 설계의 참고자료일 수 있지만, 확인된 도로 환경과 nuScenes 평가 결과를 항만 비가시 객체 감지율(%) 또는 충돌 위험 인지 및 제어 지연시간(ms)의 직접 근거로 전용할 수는 없다.",
+ "uncertainty": "survey p.16, 81–82행 및 p.39, 65–67행의 CenterFusion 32.6 mAP·44.9 NDS는 nuScenes 결과로 제시된다. 연결 자료 전체의 범위는 아직 확인되지 않았다.",
+ "support_evidence_refs": [
+ "span:7759d43a-1cc0-4e45-ba28-588554656458",
+ "span:6cd5d0ab-63e1-45ef-91dc-65e4071836e2",
+ "span:10eab22b-a1f8-4ce5-a3d1-84c49cab909d",
+ "span:e0190a7b-f6c9-458d-8101-ed7827d32fdd",
+ "span:76dd48db-22e6-4c3f-b740-2a8282ebb034",
+ "span:67964368-89f4-4caa-a451-e1c14cecce19",
+ "span:57c06842-fd12-4f29-aa3a-60b892f3832e",
+ "span:f125282c-515c-476f-a320-b01c38bc6a62",
+ "span:d9af94fd-f4de-4b31-94ba-95121b9c5b90"
+ ],
+ "counterevidence_refs": [],
+ "counterevidence_queries": [
+ "연결된 다른 데이터셋이나 CenterFusion 후속 평가에 동일한 항만 조건과 동일한 두 지표의 측정 기록이 있는가?"
+ ],
+ "critical_review": null,
+ "discriminating_tests": [
+ {
+ "expected_if_alternative": "동일 항만 조건·지표·프로토콜의 결과와 원자료가 원문 위치와 함께 확인된다.",
+ "expected_if_true": "방법·라벨 설계의 참고 가능성은 확인되나, 항만의 동일 지표 실측은 확인되지 않는다.",
+ "procedure_candidate": "survey p.14·16·39, CenterFusion 원문 p.2, AI-Hub HTML 3249·3649·3989–3998행과 다른 연결 데이터셋의 원문에서 환경·지표·시험 조건을 대조한다.",
+ "risk_tier": "R0"
+ }
+ ],
+ "missing_evidence": [
+ "연결된 국내외 데이터셋 전체의 원문별 수집 환경·평가 프로토콜과 위치",
+ "외부 지표와 항만 지표의 비교 가능성을 입증하는 정의서·항만 재현시험"
+ ]
+ },
+ {
+ "hypothesis_id": "hypothesis:object:807e7119-d046-412c-a6ca-3cfa9d3f50a4:measurement",
+ "statement": "제공된 RFP p.37, 20–22행은 정량적 목표치 제출 항목을 열거한다. 이 발췌와 외부 결과만으로는 항만 비가시 객체 감지율(%)·충돌 위험 인지 및 제어 지연시간(ms)의 실제 측정 또는 시험 승인을 확인할 수 없다.",
+ "uncertainty": "UNKNOWN — 측정 수행·결과·승인 여부는 확인되지 않았다. RFP p.45, 20행의 3세부 ‘실시간 처리 지연시간(ms)’과 ‘위험 상황 감지 및 대응 지연시간(sec)’은 1세부 지표의 측정값이 아니다.",
+ "support_evidence_refs": [
+ "span:b97dce8e-863b-4597-bfb9-0ffbfd7a97a7",
+ "span:f125282c-515c-476f-a320-b01c38bc6a62",
+ "span:d9af94fd-f4de-4b31-94ba-95121b9c5b90",
+ "span:54c64cdf-ddb9-405a-b2d5-d83b41266154",
+ "span:bddd96e1-9799-4768-b71a-3adc90134748"
+ ],
+ "counterevidence_refs": [],
+ "counterevidence_queries": [
+ "항만 현장 시험에서 비가시 조건·감지율 분자와 분모, 지연시간 시작·종료점을 정의하고 두 값을 실제 측정한 날짜별 원자료와 승인 기록이 있는가?"
+ ],
+ "critical_review": null,
+ "discriminating_tests": [
+ {
+ "expected_if_alternative": "해당 장비 버전·항만 조건에서 측정한 두 값과 판정·승인 기록이 별도 문서로 확인된다.",
+ "expected_if_true": "제공된 RFP의 항목만으로는 실측·승인을 확인할 수 없으며, 별도 기록의 확인이 필요하다.",
+ "procedure_candidate": "담당자에게 1세부 지표 정의서·계획서·항만 시험계획서·원자료·시험성적서·승인 기록을 요청하고 목표치와 측정값을 구분한다.",
+ "risk_tier": "R0"
+ }
+ ],
+ "missing_evidence": [
+ "1세부 지표 정의서·연구개발계획서의 목표치",
+ "항만 시험계획서, 장비·센서 구성 및 동기화 기록",
+ "두 지표의 측정 원자료·시험성적서·승인 기준과 승인 기록"
+ ]
+ }
+ ]
+ },
+ "action_plan": {
+ "frontier": [
+ "action:object:807e7119-d046-412c-a6ca-3cfa9d3f50a4:compare",
+ "action:object:807e7119-d046-412c-a6ca-3cfa9d3f50a4:request-draft"
+ ],
+ "alternatives": [
+ {
+ "action_id": "action:object:807e7119-d046-412c-a6ca-3cfa9d3f50a4:compare",
+ "action_family": "READ_ONLY_ANALYSIS",
+ "specification": "제공된 span만 출처별로 대조해 RFP 1세부 목표 항목, AI-Hub의 검증 모델·라벨, survey와 CenterFusion의 nuScenes 평가를 분리한다. 각 주장의 원문 위치를 붙이고 센서 필수 지정·항만 실측·승인은 UNKNOWN으로 유지한다.",
+ "expected_information_value": "현재 자료로 뒷받침되는 참고 가능 범위와 항만 성능의 직접 근거가 아닌 범위를 정리한다. 미제공 문서의 존재 여부까지 확인하지는 못한다.",
+ "risk_tier": "R0",
+ "execution_authority": "AUTO_R0",
+ "reversibility": "FULL",
+ "state": "AUTO_ALLOWED",
+ "source_refs": [
+ "span:5107fe50-16bb-4bc1-a62b-5f1885de51e4",
+ "span:b97dce8e-863b-4597-bfb9-0ffbfd7a97a7",
+ "span:f125282c-515c-476f-a320-b01c38bc6a62",
+ "span:d9af94fd-f4de-4b31-94ba-95121b9c5b90",
+ "span:ff2c5ba7-ab7e-4f6a-8bd8-183d34cc017d",
+ "span:7759d43a-1cc0-4e45-ba28-588554656458",
+ "span:6cd5d0ab-63e1-45ef-91dc-65e4071836e2",
+ "span:10eab22b-a1f8-4ce5-a3d1-84c49cab909d",
+ "span:67964368-89f4-4caa-a451-e1c14cecce19",
+ "span:57c06842-fd12-4f29-aa3a-60b892f3832e"
+ ],
+ "missing_evidence": [
+ "적용 RFP 전체·변경본의 센서 구성 문구",
+ "다른 연결 데이터셋의 원문과 평가 조건",
+ "항만 시험 원자료·시험성적서·승인 기록"
+ ],
+ "estimated_cost": null,
+ "estimated_seconds": null
+ },
+ {
+ "action_id": "action:object:807e7119-d046-412c-a6ca-3cfa9d3f50a4:request-draft",
+ "action_family": "EVIDENCE_REQUEST",
+ "specification": "담당자에게 보낼 문서 요청·질문을 로컬 초안으로만 작성한다. 적용 RFP 원본·변경본의 필수 센서 문구, 각 연결 자료의 원문·프로토콜, 두 항만 지표의 정의·목표치·측정 원자료·승인 기록과 시험 당시 장비 버전을 요청한다. 발송은 이 행동에 포함하지 않는다.",
+ "expected_information_value": "초안 자체는 UNKNOWN을 해소하지 않는다. 별도 발송 후 회신을 받으면 필수 센서 여부와 실제 측정·승인 여부를 구별할 수 있다.",
+ "risk_tier": "R1",
+ "execution_authority": "PREAUTHORIZED_R1",
+ "reversibility": "FULL",
+ "state": "RISK_CLASSIFIED",
+ "source_refs": [
+ "span:1580e7b4-64bb-4eb0-99c2-5a8fc6113f70",
+ "span:3ac72414-5d0f-4a8c-a2eb-8b85a20b69e4",
+ "span:b97dce8e-863b-4597-bfb9-0ffbfd7a97a7",
+ "span:f125282c-515c-476f-a320-b01c38bc6a62",
+ "span:d9af94fd-f4de-4b31-94ba-95121b9c5b90",
+ "span:54c64cdf-ddb9-405a-b2d5-d83b41266154",
+ "span:bddd96e1-9799-4768-b71a-3adc90134748"
+ ],
+ "missing_evidence": [
+ "해당 공고에 적용되는 1세부 RFP 원본·변경 이력과 공식 센서 설계 문서",
+ "비가시 조건·감지율 분자와 분모·지연시간 시작점과 종료점의 정의",
+ "항만 시험계획서·로그·원자료·시험성적서·승인 기준과 기록"
+ ],
+ "estimated_cost": null,
+ "estimated_seconds": null
+ },
+ {
+ "action_id": "action:object:807e7119-d046-412c-a6ca-3cfa9d3f50a4:sandbox-mapping",
+ "action_family": "SANDBOX_REPLAY",
+ "specification": "sandbox-owner 승인 뒤, 제공된 인용문만 격리된 로컬 표에 수동 배치해 환경·라벨·지표의 대응 가능성을 점검한다. 외부 자료 수치의 항만 지표 환산, 코드 실행, 실제 시험은 하지 않는다.",
+ "expected_information_value": "비교 불가능한 지표를 전용할 위험과 추가로 필요한 항만 재현시험 조건을 드러낼 수 있다. 항만 성능값은 생성하지 못한다.",
+ "risk_tier": "R2",
+ "execution_authority": "SANDBOX_ONLY_R2",
+ "reversibility": "FULL",
+ "state": "RISK_CLASSIFIED",
+ "source_refs": [
+ "span:7759d43a-1cc0-4e45-ba28-588554656458",
+ "span:6cd5d0ab-63e1-45ef-91dc-65e4071836e2",
+ "span:10eab22b-a1f8-4ce5-a3d1-84c49cab909d",
+ "span:db2b956b-ba8c-4f5f-91ce-54ae20a143b7",
+ "span:dd70f6e8-a2bb-4f4a-b73f-45fb192decdc",
+ "span:67964368-89f4-4caa-a451-e1c14cecce19",
+ "span:f125282c-515c-476f-a320-b01c38bc6a62",
+ "span:d9af94fd-f4de-4b31-94ba-95121b9c5b90"
+ ],
+ "missing_evidence": [
+ "sandbox-owner 승인",
+ "외부 자료와 항만 지표의 정의·시험 조건 비교 자료",
+ "항만 재현시험 결과"
+ ],
+ "estimated_cost": null,
+ "estimated_seconds": null
+ }
+ ],
+ "decision_analysis": {
+ "criteria": [
+ {
+ "criterion_id": "criterion:evidence-boundary",
+ "mandatory": true,
+ "name": "근거와 결론의 분리",
+ "rationale": "RFP 목표 항목, 외부 평가 결과, 항만 실측값을 서로 대체하지 않는다."
+ },
+ {
+ "criterion_id": "criterion:missing-records",
+ "mandatory": true,
+ "name": "판정에 필요한 자료 확보",
+ "rationale": "센서 필수 지정과 측정·승인 여부는 적용 문서 및 시험 기록 없이는 확정할 수 없다."
+ },
+ {
+ "criterion_id": "criterion:action-scope",
+ "mandatory": true,
+ "name": "행동 범위와 승인",
+ "rationale": "로컬 분석·초안과 승인 필요한 sandbox 행동을 구분한다."
+ }
+ ],
+ "decision": "현재는 제공된 span의 읽기 전용 대조와 담당자 요청 초안 작성을 우선한다. sandbox 대조는 별도 승인 후 검토하며, 어느 행동도 적합성·성능 충족·시험 승인을 판정하지 않는다.",
+ "evaluations": [
+ {
+ "action_id": "action:object:807e7119-d046-412c-a6ca-3cfa9d3f50a4:compare",
+ "assessment": "우선 시행 가능. RFP p.37의 목표 항목과 도로·nuScenes 자료의 결과를 분리하되, RFP 전체의 센서 명시 여부는 UNKNOWN으로 둔다.",
+ "criterion_id": "criterion:evidence-boundary",
+ "evidence_refs": [
+ "span:b97dce8e-863b-4597-bfb9-0ffbfd7a97a7",
+ "span:ff2c5ba7-ab7e-4f6a-8bd8-183d34cc017d",
+ "span:7759d43a-1cc0-4e45-ba28-588554656458",
+ "span:67964368-89f4-4caa-a451-e1c14cecce19"
+ ],
+ "scenario": "제공된 span만 사용"
+ },
+ {
+ "action_id": "action:object:807e7119-d046-412c-a6ca-3cfa9d3f50a4:compare",
+ "assessment": "현재 근거의 한계는 명확히 할 수 있으나 실측·승인 여부를 새로 확인할 수는 없다.",
+ "criterion_id": "criterion:missing-records",
+ "evidence_refs": [
+ "span:f125282c-515c-476f-a320-b01c38bc6a62",
+ "span:d9af94fd-f4de-4b31-94ba-95121b9c5b90"
+ ],
+ "scenario": "추가 문서가 제공되지 않음"
+ },
+ {
+ "action_id": "action:object:807e7119-d046-412c-a6ca-3cfa9d3f50a4:request-draft",
+ "assessment": "회신을 받는 경우 가장 중요한 UNKNOWN을 해소할 가능성이 있다. 초안 작성만으로 자료가 확보된 것으로 취급하지 않는다.",
+ "criterion_id": "criterion:missing-records",
+ "evidence_refs": [
+ "span:1580e7b4-64bb-4eb0-99c2-5a8fc6113f70",
+ "span:b97dce8e-863b-4597-bfb9-0ffbfd7a97a7"
+ ],
+ "scenario": "로컬 초안 작성 후 별도 발송·회신"
+ },
+ {
+ "action_id": "action:object:807e7119-d046-412c-a6ca-3cfa9d3f50a4:request-draft",
+ "assessment": "로컬에서 되돌릴 수 있다. 담당자에게 실제 발송하는 행동은 이 계획의 범위 밖이다.",
+ "criterion_id": "criterion:action-scope",
+ "evidence_refs": [
+ "span:1580e7b4-64bb-4eb0-99c2-5a8fc6113f70"
+ ],
+ "scenario": "발송하지 않는 초안"
+ },
+ {
+ "action_id": "action:object:807e7119-d046-412c-a6ca-3cfa9d3f50a4:sandbox-mapping",
+ "assessment": "지표 불일치를 시각화하는 보조 수단이다. 누락된 항만 실측값이나 비교 가능성을 입증하지 못한다.",
+ "criterion_id": "criterion:evidence-boundary",
+ "evidence_refs": [
+ "span:6cd5d0ab-63e1-45ef-91dc-65e4071836e2",
+ "span:db2b956b-ba8c-4f5f-91ce-54ae20a143b7",
+ "span:f125282c-515c-476f-a320-b01c38bc6a62"
+ ],
+ "scenario": "승인된 격리 표에서 제공 인용문만 대조"
+ },
+ {
+ "action_id": "action:object:807e7119-d046-412c-a6ca-3cfa9d3f50a4:sandbox-mapping",
+ "assessment": "승인 전에는 시행 대상으로 올리지 않는다. 읽기 전용 대조보다 우선할 근거도 없다.",
+ "criterion_id": "criterion:action-scope",
+ "evidence_refs": [
+ "span:7759d43a-1cc0-4e45-ba28-588554656458",
+ "span:67964368-89f4-4caa-a451-e1c14cecce19"
+ ],
+ "scenario": "sandbox-owner 승인이 아직 없음"
+ }
+ ],
+ "preference_question": null,
+ "sensitivity": "적용 RFP에 필수 조합이 명시되거나 동일 항만 조건의 원자료·승인 기록이 회신되면 분석 결과를 수정해야 한다. 그 전에는 외부 지표로 항만 성능을 판정하지 않는다.",
+ "uncertainty": "UNKNOWN — 적용 RFP 전체·변경 이력의 센서 문구, 다른 연결 자료의 전체 범위, 두 항만 지표의 실측값 및 승인 기록은 확인되지 않았다."
+ }
+ }
+}
diff --git a/apps/web/src/api/history.ts b/apps/web/src/api/history.ts
index ed9c943..bae2353 100644
--- a/apps/web/src/api/history.ts
+++ b/apps/web/src/api/history.ts
@@ -30,7 +30,8 @@ function rowOf(item: WireHistoryItem, scope: HistoryScope): HistoryRow {
: { kind: "record", scope, record };
return { id: item.item_id, kind: item.kind, title: item.title, occurredAt: item.occurred_at,
association: item.association, membership: item.head_membership, availability: item.availability,
- currentness: item.currentness, selection, completion: item.completion, phase: item.phase };
+ currentness: item.currentness, selection, completion: item.completion, phase: item.phase,
+ changeSummary: item.change_summary };
}
export async function readHistoryPage(scope: HistoryScope, cursor: string | null, signal?: AbortSignal, kinds?: HistoryKind[]): Promise {
@@ -92,6 +93,7 @@ export async function readHistoryEntry(selection: HistorySelection, signal?: Abo
question: item.kind === "REQUEST" && value.content ? questionOf(value.content) : null, result: null,
content: item.availability === "UNAVAILABLE" ? null : value.content, currentness: item.currentness,
availability: item.availability, reasons: value.coverage.reasons, currentHead: head, restoreSelection,
+ changeSummary: item.change_summary,
capability: { restore: item.capability.restore, applyReady: item.capability.apply_ready, previewSupported: item.capability.preview_supported, reasons: item.capability.reason_codes },
technical: item.availability === "UNAVAILABLE" ? { ...value, content: null } : value };
}
diff --git a/apps/web/src/api/historyModels.ts b/apps/web/src/api/historyModels.ts
index 90af894..8a4221a 100644
--- a/apps/web/src/api/historyModels.ts
+++ b/apps/web/src/api/historyModels.ts
@@ -13,6 +13,15 @@ export type HistorySelection =
| { kind: "result"; scope: HistoryScope & { threadId: string; requestDigest: string }; operationId?: string; resultDigest?: string; record?: RecordReference; occurredAt?: string; title?: string }
| { kind: "record"; scope: HistoryScope; record: RecordReference };
export type Currentness = { state: string; reasons: string[] };
+export type ChangeSummary = {
+ lines: { label: string; before: string; after: string }[];
+ more: number;
+ flags: ("NUMBER" | "NEGATION" | "STATUS" | "CITATION" | "AUTHORITY" | "TIME")[];
+ text_diff_recommended: boolean;
+ /** Fields without a reader-facing name: only counted by default, raw form in `technical`. */
+ other?: number;
+ technical?: { label: string; before: string; after: string }[];
+};
export type HistoryRow = {
id: string;
kind: string;
@@ -25,6 +34,7 @@ export type HistoryRow = {
phase?: string | null;
currentness: Currentness;
selection: HistorySelection;
+ changeSummary?: ChangeSummary | null;
};
export type HistoryPage = {
items: HistoryRow[];
@@ -55,6 +65,7 @@ export type HistoryEntry = {
currentHead: string | null;
capability: { restore: string; applyReady: boolean; previewSupported: boolean; reasons: string[] };
restoreSelection: RestoreSelection | null;
+ changeSummary?: ChangeSummary | null;
technical: unknown;
};
export type RevisionChange = {
diff --git a/apps/web/src/api/historySchemas.ts b/apps/web/src/api/historySchemas.ts
index 45001f2..2189cd1 100644
--- a/apps/web/src/api/historySchemas.ts
+++ b/apps/web/src/api/historySchemas.ts
@@ -18,6 +18,11 @@ export const recordRefSchema = z.object({
owner_kind: z.enum(["SEMANTIC_REVISION", "FULL_MEMORY"]), project_id: z.string(),
immutable_id: z.string(), revision_digest: digestSchema,
});
+export const changeSummarySchema = z.object({
+ lines: z.array(z.object({ label: z.string(), before: z.string(), after: z.string() })), more: z.number().int().min(0).default(0),
+ flags: z.array(z.enum(["NUMBER", "NEGATION", "STATUS", "CITATION", "AUTHORITY", "TIME"])).default([]), text_diff_recommended: z.boolean().default(false),
+ other: z.number().int().min(0).default(0), technical: z.array(z.object({ label: z.string(), before: z.string(), after: z.string() })).default([]),
+});
export const historyItemSchema = z.object({
item_id: z.string(), kind: z.enum(["REQUEST", "RESULT", "REVISION", "RESTORE", "MEMORY"]),
occurred_at: z.string().datetime({ offset: true }), record_ref: recordRefSchema,
@@ -30,6 +35,7 @@ export const historyItemSchema = z.object({
entity_type: z.string().nullable().optional(), entity_id: z.string().nullable().optional(),
schema_family: z.string().nullable().optional(), currentness: currentnessSchema, capability: capabilitySchema,
completion: z.enum(["CHECKPOINT", "TERMINAL"]).nullable().optional(), phase: z.string().nullable().optional(),
+ change_summary: changeSummarySchema.nullable().optional(),
});
export const historyPageSchema = z.object({
contract_version: z.literal(2), items: z.array(historyItemSchema), coverage: coverageSchema,
diff --git a/apps/web/src/api/research.ts b/apps/web/src/api/research.ts
index 273863f..74ff6e7 100644
--- a/apps/web/src/api/research.ts
+++ b/apps/web/src/api/research.ts
@@ -15,7 +15,7 @@ export type ResultManifest = {
request_ref?: { revision_digest: string };
operation_id: string; phase: string; state: string; completion: string; terminal_reason: string | null;
basis_digest: string; result: Record; gaps: string[]; next_steps: string[];
- source_refs: string[]; record_refs: unknown[];
+ source_refs: string[]; /** Not sent by the summary read; the result read has it. */ record_refs?: unknown[];
};
export type CompletedStage = {
role?: string;
@@ -23,6 +23,8 @@ export type CompletedStage = {
elapsed_ms?: number | null;
context_bytes?: number | null;
dispatch_ids?: string[];
+ /** Set when this stage's output came from an interrupted run the user resumed; no model call was made for it. */
+ reused_from_operation_id?: string | null;
};
export type ActivityEvent = {
seq?: number;
@@ -113,9 +115,17 @@ export type UserActivityEvent = {
revision_ref?: string | null;
};
};
+export type ResultUsage = {
+ operation_id: string; calls: number; input_tokens: number | null; output_tokens: number | null; total_tokens: number | null;
+ cached_input_tokens: number | null; unreported_calls: number; state: string; wall_ms: number | null;
+ /** Calls sent again on their own after an earlier call was cut off. */
+ auto_retries?: number;
+};
export type ResearchStatus = WorkThread & {
usage?: { input_tokens: number | null; output_tokens: number | null; total_tokens: number | null; state: string; unreported_calls: number; cumulative_token_limit_enforced: boolean; cached_input_tokens?: number | null };
operation_state?: string; operation_error?: ExecutionError | null; request_epoch?: number;
+ /** Usage per result (operation id), computed by the server when the status is read. */
+ result_usage?: Record;
failure?: ResearchFailure | null;
execution_summary?: {effective_execution_state:string;state_inconsistent:boolean;last_checkpoint_only:boolean;automatic_retry:boolean};
freshness?: string; current_result?: ResultManifest | null; previous_result?: ResultManifest | null;
@@ -125,6 +135,7 @@ export type ResearchStatus = WorkThread & {
next_user_action?: NextUserAction | null;
request?: { operation_id: string; authored_text?: string; effective_question?: string; model_settings?: unknown; policy_digest?: string; cutoff_at?: string };
completed_stages?: CompletedStage[];
+ stage_reuse?: { reused: number; new: number };
activity_events?: ActivityEvent[];
user_activity_events?: UserActivityEvent[];
model_dispatches?: ModelDispatch[];
@@ -157,7 +168,9 @@ const analysisDisplaySchema=z.object({
scope_identity:dimension,criterion_authority:dimension,evidence_coverage:dimension,comparability:dimension}),
portfolio:z.object({portfolio_id:z.string(),hypotheses:z.array(z.object({
hypothesis_id:z.string(),statement:z.string(),primary_locus:z.string().nullable(),uncertainty:z.string(),
- support_evidence_refs:stringList,counterevidence_queries:stringList,predicted_observations:stringList,
+ support_evidence_refs:stringList.optional(),evidence_refs:stringList.optional(),counterevidence_refs:stringList.optional(),
+ critical_review:z.object({terminal:z.string()}).passthrough().nullable().optional(),
+ counterevidence_queries:stringList,predicted_observations:stringList,
discriminating_tests:z.array(z.object({procedure_candidate:z.string(),expected_if_true:z.string(),expected_if_alternative:z.string(),risk_tier:z.string()})),
}))}),
action_plan:z.object({plan_id:z.string(),frontier:stringList,alternatives:z.array(z.object({
diff --git a/apps/web/src/api/researchFollowup.ts b/apps/web/src/api/researchFollowup.ts
index 1ea238f..b166a76 100644
--- a/apps/web/src/api/researchFollowup.ts
+++ b/apps/web/src/api/researchFollowup.ts
@@ -68,6 +68,21 @@ const resultIdentitySchema = z.object({
result_revision_digest: digestSchema,
});
+const criterionStateSchema = z.object({
+ status: z.enum(["SATISFIED", "UNRESOLVED", "NOT_APPLICABLE", "NOT_ASSESSED"]),
+ relation: z.string(), validation: z.string(), blocker: z.string(),
+});
+
+const criterionDeltaSchema = z.object({
+ requirement_id: z.string(), target: z.string(), question: z.string(),
+ match: z.enum(["SAME", "ADDED", "REMOVED"]),
+ before: criterionStateSchema.nullable().optional().default(null),
+ after: criterionStateSchema.nullable().optional().default(null),
+ evidence_refs_added: z.array(z.string()).optional().default([]),
+ evidence_refs_removed: z.array(z.string()).optional().default([]),
+ unchanged_hold: z.boolean().optional().default(false),
+});
+
const decisionDeltaSchema = z.object({
schema_version: z.literal("1.0.0").optional().default("1.0.0"),
contract_version: z.literal(2).optional().default(2),
@@ -91,6 +106,9 @@ const decisionDeltaSchema = z.object({
after: z.unknown().nullable().optional(),
})),
})).optional().default([]),
+ // Results saved by an older server carry neither field: criteria are then reported as not comparable.
+ criteria: z.array(criterionDeltaSchema).optional().default([]),
+ criteria_state: z.enum(["AVAILABLE", "UNAVAILABLE"]).optional().default("UNAVAILABLE"),
});
const projectReviewItemSchema = z.object({
@@ -122,6 +140,7 @@ export type UserProgressSummary = z.infer;
export type CoverageMatrix = z.infer;
export type CoverageMatrixRow = z.infer;
export type DecisionDelta = z.infer;
+export type CriterionDelta = z.infer;
export type ResultIdentity = z.infer;
export type ProjectReviewList = z.infer;
export type ProjectReviewItem = z.infer;
diff --git a/apps/web/src/api/researchStatusRead.test.ts b/apps/web/src/api/researchStatusRead.test.ts
new file mode 100644
index 0000000..dd9a243
--- /dev/null
+++ b/apps/web/src/api/researchStatusRead.test.ts
@@ -0,0 +1,46 @@
+import { QueryClient } from "@tanstack/react-query";
+import { beforeEach, expect, it, vi } from "vitest";
+import type { ResearchStatus } from "./research";
+
+const calls: { method: string; input: Record }[] = [];
+const replies: Record[] = [];
+vi.mock("./rpcClient", () => ({
+ rpc: async (method: string, input: Record) => {
+ calls.push({ method, input });
+ const value = replies.shift();
+ if (!value) throw new Error("no reply queued");
+ return { operation_id: "", state: "SUCCEEDED", value };
+ },
+}));
+const { readResearchStatus } = await import("./researchStatusRead");
+
+const key = ["research", "p", "t"];
+const status = (patch: Record) => ({ operation_state: "SUCCEEDED", usage: { total_tokens: 1 }, ...patch });
+let client: QueryClient;
+beforeEach(() => { calls.length = 0; replies.length = 0; client = new QueryClient(); });
+
+it("reads in full when nothing is running", async () => {
+ replies.push(status({ current_result: { operation_id: "op" } }));
+ const result = await readResearchStatus(client, key, "p", "t");
+ expect(calls).toEqual([{ method: "thread/read", input: { project_id: "p", thread_id: "t" } }]);
+ expect((result.value as ResearchStatus).current_result).toBeTruthy();
+});
+
+it("polls the light progress view while running and keeps the earlier full fields", async () => {
+ client.setQueryData(key, { operation_id: "", state: "SUCCEEDED", value: status({ operation_state: "RUNNING", coverage_matrix: { rows: [1] }, usage: { total_tokens: 1 } }) });
+ replies.push(status({ operation_state: "RUNNING", usage: { total_tokens: 99 } }));
+ const result = await readResearchStatus(client, key, "p", "t");
+ expect(calls).toEqual([{ method: "thread/read", input: { project_id: "p", thread_id: "t", view: "PROGRESS" } }]);
+ expect(result.value.operation_state).toBe("RUNNING");
+ expect((result.value.usage as { total_tokens: number }).total_tokens).toBe(99);
+ expect((result.value as Record).coverage_matrix).toEqual({ rows: [1] });
+});
+
+it("reads in full once, right after the light read says the run ended", async () => {
+ client.setQueryData(key, { operation_id: "", state: "SUCCEEDED", value: status({ operation_state: "RUNNING" }) });
+ replies.push(status({ operation_state: "SUCCEEDED" }), status({ operation_state: "SUCCEEDED", current_result: { operation_id: "op" } }));
+ const result = await readResearchStatus(client, key, "p", "t");
+ expect(calls.map(call => call.input.view)).toEqual(["PROGRESS", undefined]);
+ expect((result.value as ResearchStatus).current_result).toBeTruthy();
+});
+
diff --git a/apps/web/src/api/researchStatusRead.ts b/apps/web/src/api/researchStatusRead.ts
new file mode 100644
index 0000000..b4090dc
--- /dev/null
+++ b/apps/web/src/api/researchStatusRead.ts
@@ -0,0 +1,20 @@
+import type { QueryClient } from "@tanstack/react-query";
+import type { ResearchStatus } from "./research";
+import { rpc, type RpcResult } from "./rpcClient";
+
+/**
+ * Reads a thread's status. While the last read said the operation is running, polling asks for the
+ * progress view only (no stored result, no criteria projection) and lays it over the last full read;
+ * the first read that no longer says running is a full one, so the result appears exactly once.
+ */
+export async function readResearchStatus(
+ client: QueryClient, queryKey: readonly unknown[], projectId: string, threadId: string, signal?: AbortSignal,
+): Promise> {
+ const prior = client.getQueryData>(queryKey);
+ if (prior?.value.operation_state === "RUNNING") {
+ const light = await rpc("thread/read", { project_id: projectId, thread_id: threadId, view: "PROGRESS" }, crypto.randomUUID(), signal);
+ if (light.value.operation_state === "RUNNING") return { ...light, value: { ...prior.value, ...light.value } };
+ }
+ return rpc("thread/read", { project_id: projectId, thread_id: threadId }, crypto.randomUUID(), signal);
+}
+
diff --git a/apps/web/src/api/rpcClient.ts b/apps/web/src/api/rpcClient.ts
index 8e812b0..1d8a9aa 100644
--- a/apps/web/src/api/rpcClient.ts
+++ b/apps/web/src/api/rpcClient.ts
@@ -60,8 +60,9 @@ export async function rpc(
input: Record,
idempotencyKey: string,
signal?: AbortSignal,
+ options: { timeoutMs?: number } = {},
): Promise> {
- const bounded = timeoutSignal(signal, 15000);
+ const bounded = timeoutSignal(signal, options.timeoutMs ?? 15000);
try {
const response = await transportRead(() => fetch(readQueries.has(method) ? "/rpc/query" : "/rpc", {
method: "POST",
diff --git a/apps/web/src/api/sourceTime.ts b/apps/web/src/api/sourceTime.ts
index d215d36..d52bb18 100644
--- a/apps/web/src/api/sourceTime.ts
+++ b/apps/web/src/api/sourceTime.ts
@@ -38,6 +38,15 @@ export function cutoffLabel(state: string) {
return state;
}
+/** Project-level cutoff shown in the header. Source-level states use cutoffLabel. */
+export function projectCutoffText(cutoffAt?: string | null) {
+ if (!cutoffAt) return "";
+ const date = new Date(cutoffAt);
+ if (Number.isNaN(date.getTime())) return "";
+ const pad = (value: number) => String(value).padStart(2, "0");
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
+}
+
export async function confirmSourceTime(input: z.infer & {assertion:"ON_OR_BEFORE_CUTOFF"|"AFTER_CUTOFF"}) {
return rpc<{source_time: SourceTimeAssessment}>("project/source/time/confirm", input, crypto.randomUUID());
}
diff --git a/apps/web/src/components/ActionCompare.test.tsx b/apps/web/src/components/ActionCompare.test.tsx
new file mode 100644
index 0000000..8b4c996
--- /dev/null
+++ b/apps/web/src/components/ActionCompare.test.tsx
@@ -0,0 +1,94 @@
+import { renderToStaticMarkup } from "react-dom/server";
+import { expect, it } from "vitest";
+import excerpt from "../api/fixtures/iris-judgment-excerpt.json";
+import { ActionCompare } from "./ActionCompare";
+import { actionFamilyLabel } from "./statusLabels";
+
+const render = (patch: (result: Record) => void = () => {}) => {
+ const result = structuredClone(excerpt) as Record;
+ patch(result);
+ return renderToStaticMarkup();
+};
+// Internal ids may sit in title attributes and technical details only.
+const visible = (html: string) => html.replace(//g, "").replace(/ title="[^"]*"/g, "");
+const analysis = excerpt.action_plan.decision_analysis;
+
+it("leads with the model's recommendation, its uncertainty and what would change it", () => {
+ const html = render();
+ expect(html.indexOf("추천")).toBeLessThan(html.indexOf("필수 기준"));
+ expect(html).toContain(analysis.decision);
+ expect(html).toContain(analysis.uncertainty);
+ expect(html).toContain(analysis.sensitivity);
+});
+
+it("lists mandatory criteria and the action-by-criterion assessments with source numbers", () => {
+ const html = render();
+ for (const criterion of analysis.criteria) {
+ expect(html).toContain(criterion.name);
+ expect(html).toContain("필수");
+ }
+ for (const evaluation of analysis.evaluations) expect(html).toContain(evaluation.assessment);
+ expect(html).toMatch(/\[1\]/);
+ expect(visible(html)).not.toMatch(/span:|criterion:|action:object/);
+});
+
+it("shows risk, execution authority, reversibility and unknown cost and time in plain Korean", () => {
+ const html = render();
+ for (const text of ["자동 실행 가능", "사전 허용 범위", "격리 실행만", "되돌릴 수 있음"]) expect(html).toContain(text);
+ expect(html).toContain("비용·시간: 미확인");
+ expect(visible(html)).not.toMatch(/AUTO_R0|PREAUTHORIZED|SANDBOX_ONLY|FULL|READ_ONLY_ANALYSIS/);
+ expect(actionFamilyLabel("SANDBOX_REPLAY")).toBe("격리 환경 재현");
+ expect(actionFamilyLabel("SOMETHING_ELSE")).toBeNull();
+});
+
+const estimate = (dimension: string, band: string, type: string, basis: string, at = "2026-09-30T00:00:00Z") =>
+ ({ dimension, band, estimator_type: type, estimator_ref: type === "AI" ? "ACTION_PLANNER" : "human:local-user", basis_text: basis, assumptions: [], created_at: at });
+const withEstimates = (list: unknown[]) => render(result => {
+ ((result.action_plan as { alternatives: Record[] }).alternatives[0]).effort_estimates = list;
+});
+
+it("replaces the unknown cost and time with the band, who estimated it and why", () => {
+ const html = withEstimates([estimate("TIME", "MEDIUM", "AI", "담당자 회신이 필요"), estimate("COST_EFFORT", "LOW", "AI", "기존 자료 재대조")]);
+ expect(html).toContain("시간: 보통 · AI 추정 · 담당자 회신이 필요");
+ expect(html).toContain("비용·품: 낮음 · AI 추정 · 기존 자료 재대조");
+ expect(visible(html)).not.toMatch(/MEDIUM|COST_EFFORT|ACTION_PLANNER/);
+});
+
+it("puts an unknown band in the needs-confirmation wording, never as a cheap option", () => {
+ const html = withEstimates([estimate("TIME", "UNKNOWN", "AI", "")]);
+ expect(html).toContain("시간: 미확인 · 확인 필요");
+ expect(html).not.toMatch(/시간: (짧음|낮음)/);
+});
+
+it("shows the AI and the person side by side when they differ, and keeps the AI value", () => {
+ const html = withEstimates([estimate("TIME", "MEDIUM", "AI", "담당자 회신"), estimate("TIME", "HIGH", "HUMAN", "현장 시험이 필요", "2026-09-30T01:00:00Z")]);
+ expect(html).toContain("AI 추정: 보통");
+ expect(html).toContain("사람 추정: 김");
+ expect(html).toContain("현장 시험이 필요");
+});
+
+it("shows one value when the AI and the person agree, and never a number for a band", () => {
+ const html = withEstimates([estimate("TIME", "HIGH", "AI", "시험 일정"), estimate("TIME", "HIGH", "HUMAN", "동의", "2026-09-30T01:00:00Z")]);
+ expect(html).toContain("시간: 김 · AI·사람 추정 일치");
+ expect(html).not.toMatch(/시간: [0-9]/);
+});
+
+it("never shows a total score or rank", () => {
+ expect(render()).not.toMatch(/총점|순위|1위|점수/);
+});
+
+it("asks the user's preference question when the model recorded one", () => {
+ const html = render(result => {
+ const plan = result.action_plan as { decision_analysis: Record };
+ plan.decision_analysis.preference_question = "비용과 속도 중 무엇이 더 중요합니까?";
+ });
+ expect(html).toContain("확인하고 싶은 점");
+ expect(html).toContain("비용과 속도 중 무엇이 더 중요합니까?");
+});
+
+it("still lists the actions when no decision analysis was recorded", () => {
+ const html = render(result => { delete (result.action_plan as Record).decision_analysis; });
+ expect(html).not.toContain("필수 기준");
+ expect(html).toContain("비용·시간: 미확인");
+ expect(render(result => { (result as Record).action_plan = { alternatives: [] }; })).toContain("아직 제안된 행동이 없습니다");
+});
diff --git a/apps/web/src/components/ActionCompare.tsx b/apps/web/src/components/ActionCompare.tsx
new file mode 100644
index 0000000..b311a52
--- /dev/null
+++ b/apps/web/src/components/ActionCompare.tsx
@@ -0,0 +1,60 @@
+import { HTMLTable, Tag } from "@blueprintjs/core";
+import { objectList, objectValue, stringValues, textValue } from "../api/presentation";
+import { numberSources } from "./answerText";
+import { ActionEffort, EffortLines } from "./ActionEffort";
+import { ApprovalSection } from "./ApprovalSection";
+import { parseEstimates } from "./effortEstimates";
+import { StatusBadge } from "./StatusBadge";
+import { actionFamilyLabel, actionStateLabel, reversibilityLabel } from "./statusLabels";
+
+const legacyEffort = (action: Record) => action.estimated_cost == null && action.estimated_seconds == null ? undefined
+ : `기록됨 (${String(action.estimated_cost ?? "비용 미확인")} · ${String(action.estimated_seconds ?? "시간 미확인")})`;
+
+/** Next actions: the model's recommendation, the criteria it used, and how each action fares. No score, no rank. */
+export function ActionCompare({ result, projectId }: { result: Record; projectId?: string }) {
+ const plan = objectValue(result.action_plan);
+ const actions = objectList(plan.alternatives);
+ const analysis = objectValue(plan.decision_analysis);
+ const criteria = objectList(analysis.criteria);
+ const evaluations = objectList(analysis.evaluations);
+ const extra = [...actions.flatMap(action => stringValues(action.source_refs)), ...evaluations.flatMap(item => stringValues(item.evidence_refs))];
+ const numbers = numberSources(typeof result.answer === "string" ? result.answer : "", extra);
+ const cite = (refs: string[]) => refs.map(ref => [{numbers.get(ref)}]);
+ if (actions.length === 0) return 아직 제안된 행동이 없습니다.
;
+ const columnLabel = (action: Record, index: number) => `행동 ${index + 1}${actionFamilyLabel(textValue(action.action_family)) ? ` · ${actionFamilyLabel(textValue(action.action_family))}` : ""}`;
+ return <>
+ {textValue(analysis.decision) &&
+ 추천
+ {textValue(analysis.decision)}
+ {textValue(analysis.uncertainty) && 불확실성
{textValue(analysis.uncertainty)}
}
+ {textValue(analysis.sensitivity) && 이 판단이 바뀌는 조건
{textValue(analysis.sensitivity)}
}
+ {textValue(analysis.preference_question) && 확인하고 싶은 점
{textValue(analysis.preference_question)}
}
+ }
+ {projectId && textValue(plan.plan_id) && actions.some(action => action.execution_authority === "HUMAN_REQUIRED_R3")
+ && }
+ {criteria.length > 0 &&
+ 필수 기준
+ {criteria.map((criterion, index) => - {criterion.mandatory === true && 필수} {textValue(criterion.name)}
+ {textValue(criterion.rationale) && <>
{textValue(criterion.rationale)}>} )}
+ {evaluations.length > 0 &&
+ | 기준 | {actions.map((action, index) => {columnLabel(action, index)} | )}
+ {criteria.map((criterion, index) => | {textValue(criterion.name)} |
+ {actions.map((action, column) => {evaluations.filter(item => item.action_id === action.action_id && item.criterion_id === criterion.criterion_id)
+ .map((item, k) => {textValue(item.scenario) && {textValue(item.scenario)}: }{textValue(item.assessment)} {cite(stringValues(item.evidence_refs))} )} | )}
)}
+ }
+ }
+ {actions.map((action, index) =>
+ {textValue(action.specification)}
+
+ {reversibilityLabel(textValue(action.reversibility)) ?? "되돌림 미확인"}
+ {actionStateLabel(textValue(action.state)) && {actionStateLabel(textValue(action.state))}}
+ {textValue(action.expected_information_value)}
+ {projectId && textValue(action.action_id)
+ ?
+ : }
+ {stringValues(action.missing_evidence).map((value, j) => 필요한 근거: {value}
)}
+ {stringValues(action.source_refs).length > 0 && 근거 {cite(stringValues(action.source_refs))}
}
+ {action.execution_authority === "HUMAN_REQUIRED_R3" ? "보호된 실행 제안 · 대상과 영향에 대한 권한 결정이 필요합니다." : "제안과 실제 실행 결과는 별도로 기록됩니다."}
+ )}
+ >;
+}
diff --git a/apps/web/src/components/ActionCompareApproval.test.tsx b/apps/web/src/components/ActionCompareApproval.test.tsx
new file mode 100644
index 0000000..c03ebb0
--- /dev/null
+++ b/apps/web/src/components/ActionCompareApproval.test.tsx
@@ -0,0 +1,49 @@
+// @vitest-environment jsdom
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import { act } from "react";
+import { createRoot, type Root } from "react-dom/client";
+import { afterEach, expect, it, vi } from "vitest";
+import excerpt from "../api/fixtures/iris-judgment-excerpt.json";
+import { ActionCompare } from "./ActionCompare";
+
+const state = vi.hoisted(() => ({ calls: [] as string[] }));
+const auth = { authorization_id: "auth:1", step_id: "step:send", state: "STALE", stale_reason: "MATERIAL_CHANGE", payload: {},
+ material_changes: [{ field: "channel", label: "전달 경로", before: "EMAIL", after: "FORM", words: [], attachments: [] }], decision_history: [], created_at: "2026-09-30T00:00:00Z" };
+vi.mock("../api/rpcClient", async importOriginal => ({ ...(await importOriginal()),
+ rpc: async (method: string, input: Record) => {
+ state.calls.push(method);
+ if (method === "action/plan/read") return { value: { plan: { plan_id: input.plan_id }, authorizations: [auth] }, state: "SUCCEEDED", operation_id: "" };
+ if (method === "action/authorization/read") return { value: { authorization: auth, effective_state: "STALE", payload_current: false, review_needed: false }, state: "SUCCEEDED", operation_id: "" };
+ return { value: {}, state: "SUCCEEDED", operation_id: "" };
+ } }));
+
+let root: Root | undefined; let node: HTMLDivElement;
+afterEach(async () => { if (root) await act(async () => root!.unmount()); root = undefined; node?.remove(); state.calls = []; });
+async function mount(result: Record, projectId?: string) {
+ Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true });
+ node = document.createElement("div"); document.body.append(node); root = createRoot(node);
+ const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
+ await act(async () => root!.render());
+ for (let i = 0; i < 8; i++) await act(async () => { await new Promise(r => setTimeout(r, 10)); });
+}
+const protectedResult = () => {
+ const result = structuredClone(excerpt) as unknown as { action_plan: { plan_id?: string; alternatives: Record[] } };
+ result.action_plan.plan_id = "plan:1";
+ result.action_plan.alternatives[0].execution_authority = "HUMAN_REQUIRED_R3";
+ return result as unknown as Record;
+};
+
+it("shows what changed since approval next to a protected action", async () => {
+ await mount(protectedResult(), "project:p");
+ expect(state.calls).toContain("action/plan/read");
+ expect(node.textContent).toContain("승인 뒤 바뀜: 전달 경로");
+});
+
+it("does not read approvals when no action needs one or when no project is given", async () => {
+ await mount(protectedResult());
+ expect(state.calls).not.toContain("action/plan/read");
+ await act(async () => root!.unmount()); node.remove();
+ const plain = structuredClone(excerpt) as unknown as Record;
+ await mount(plain, "project:p");
+ expect(state.calls).not.toContain("action/plan/read");
+});
diff --git a/apps/web/src/components/ActionEffort.test.tsx b/apps/web/src/components/ActionEffort.test.tsx
new file mode 100644
index 0000000..910c56a
--- /dev/null
+++ b/apps/web/src/components/ActionEffort.test.tsx
@@ -0,0 +1,84 @@
+// @vitest-environment jsdom
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import { act } from "react";
+import { createRoot, type Root } from "react-dom/client";
+import { afterEach, expect, it, vi } from "vitest";
+import { ActionEffort } from "./ActionEffort";
+
+const state = vi.hoisted(() => ({ estimates: [] as unknown[], running: false, readFails: false, calls: [] as { method: string; input: Record }[] }));
+vi.mock("../api/rpcClient", async importOriginal => ({ ...(await importOriginal()),
+ rpc: async (method: string, input: Record) => {
+ state.calls.push({ method, input });
+ if (method === "action/read") {
+ if (state.readFails) throw new Error("synthetic read failure");
+ return { value: { research_running: state.running, action: { revision_digest: "a".repeat(64), generation_details: { effort_estimates: state.estimates } } }, state: "SUCCEEDED", operation_id: "" };
+ }
+ return { value: { action: { revision_digest: "b".repeat(64) } }, state: "SUCCEEDED", operation_id: "" };
+ } }));
+
+let root: Root | undefined; let container: HTMLDivElement;
+const ai = { dimension: "TIME", band: "MEDIUM", estimator_type: "AI", estimator_ref: "ACTION_PLANNER", basis_text: "담당자 회신이 필요", assumptions: [], created_at: "2026-09-30T00:00:00Z" };
+async function mount(initial: unknown[] = [ai]) {
+ Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true });
+ container = document.createElement("div"); document.body.append(container); root = createRoot(container);
+ const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
+ await act(async () => root!.render());
+ for (let i = 0; i < 10; i++) await act(async () => { await new Promise(r => setTimeout(r, 10)); });
+}
+async function open() { await act(async () => (container.querySelector("button[aria-label='사람 추정 입력 열기']") as HTMLButtonElement).click()); }
+async function pick(label: string, value: string) {
+ await act(async () => {
+ const select = container.querySelector(`select[aria-label='${label}']`)!;
+ select.value = value; select.dispatchEvent(new Event("change", { bubbles: true }));
+ });
+}
+async function type(label: string, value: string) {
+ await act(async () => {
+ const input = container.querySelector(`input[aria-label='${label}']`)!;
+ Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")!.set!.call(input, value);
+ input.dispatchEvent(new Event("input", { bubbles: true }));
+ });
+}
+afterEach(async () => { if (root) await act(async () => root!.unmount()); root = undefined; container?.remove(); state.estimates = []; state.running = false; state.readFails = false; state.calls = []; });
+
+it("turns the human estimate button off with a reason while an investigation runs", async () => {
+ state.running = true;
+ await mount();
+ const button = container.querySelector("button[aria-label='사람 추정 입력 열기']")!;
+ expect(button.disabled).toBe(true);
+ expect(container.textContent).toContain("조사가 끝난 뒤 제안할 수 있습니다");
+});
+
+it("reads the current record so an estimate added after the result still shows", async () => {
+ state.estimates = [ai, { ...ai, band: "HIGH", estimator_type: "HUMAN", estimator_ref: "human:local-user", basis_text: "현장 시험", created_at: "2026-09-30T01:00:00Z" }];
+ await mount([ai]);
+ expect(state.calls[0]).toEqual({ method: "action/read", input: { project_id: "project:p", action_id: "action:1" } });
+ expect(container.textContent).toContain("AI 추정: 보통");
+ expect(container.textContent).toContain("사람 추정: 김");
+});
+
+it("falls back to the result's own estimates when the record cannot be read", async () => {
+ state.readFails = true;
+ await mount([ai]);
+ expect(container.textContent).toContain("시간: 보통 · AI 추정 · 담당자 회신이 필요");
+});
+
+it("sends a person's band with its basis as a new revision bound to the record it read", async () => {
+ await mount([ai]);
+ await open();
+ await pick("시간 추정", "HIGH"); await type("시간 근거", "현장 시험이 필요");
+ await act(async () => (container.querySelector("button[aria-label='사람 추정 저장']") as HTMLButtonElement).click());
+ const revise = state.calls.find(call => call.method === "action/revise")!;
+ expect(revise.input).toMatchObject({ project_id: "project:p", action_id: "action:1", expected_revision_digest: "a".repeat(64), patch: {},
+ estimator_ref: "human:local-user", human_effort_estimates: [{ dimension: "TIME", band: "HIGH", basis_text: "현장 시험이 필요" }] });
+ expect(JSON.stringify(revise.input)).not.toMatch(/"band":\s*[0-9]/);
+});
+
+it("does not send a band without a basis sentence", async () => {
+ await mount([ai]);
+ await open();
+ await pick("비용·품 추정", "LOW");
+ await act(async () => (container.querySelector("button[aria-label='사람 추정 저장']") as HTMLButtonElement).click());
+ expect(state.calls.some(call => call.method === "action/revise")).toBe(false);
+ expect(container.textContent).toContain("근거 한 줄을 적어 주세요");
+});
diff --git a/apps/web/src/components/ActionEffort.tsx b/apps/web/src/components/ActionEffort.tsx
new file mode 100644
index 0000000..99e8acd
--- /dev/null
+++ b/apps/web/src/components/ActionEffort.tsx
@@ -0,0 +1,87 @@
+import { Button, HTMLSelect, InputGroup } from "@blueprintjs/core";
+import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
+import { useState } from "react";
+import { objectValue, textValue } from "../api/presentation";
+import { rpc } from "../api/rpcClient";
+import { parseEstimates, type Estimate } from "./effortEstimates";
+import { researchRunningNotice } from "./memoryEdit";
+import { effortBandLabel, effortBandOptions, effortDimensionLabel, estimatorLabel } from "./statusLabels";
+
+const DIMENSIONS = ["TIME", "COST_EFFORT"] as const;
+const HUMAN_REF = "human:local-user";
+
+const latest = (estimates: Estimate[], dimension: string, type: string) =>
+ [...estimates].reverse().find(item => item.dimension === dimension && item.estimator_type === type);
+
+function DimensionLine({ dimension, estimates }: { dimension: string; estimates: Estimate[] }) {
+ const ai = latest(estimates, dimension, "AI");
+ const human = latest(estimates, dimension, "HUMAN");
+ const name = effortDimensionLabel(dimension);
+ const band = (item: Estimate) => effortBandLabel(dimension, item.band);
+ if (ai && human && ai.band !== human.band) {
+ return {name}: {estimatorLabel("AI")}: {band(ai)} / {estimatorLabel("HUMAN")}: {band(human)}
+ {human.basis_text && · {human.basis_text}}{ai.basis_text && · AI 근거: {ai.basis_text}}
;
+ }
+ if (ai && human) {
+ return {name}: {band(human)} · AI·사람 추정 일치{human.basis_text && · {human.basis_text}}
;
+ }
+ const only = ai ?? human;
+ if (!only || only.band === "UNKNOWN") return {name}: 미확인 · 확인 필요{only?.basis_text && · {only.basis_text}}
;
+ return {name}: {band(only)} · {estimatorLabel(only.estimator_type)}{only.basis_text && <> · {only.basis_text}>}
;
+}
+
+/** Time and cost-effort bands for one action. Bands are labels only; nothing here adds, ranks or converts them. */
+export function EffortLines({ estimates, legacy }: { estimates: Estimate[]; legacy?: string }) {
+ if (estimates.length === 0) return 비용·시간: {legacy ?? "미확인"}
;
+ return {DIMENSIONS.map(dimension => )}
;
+}
+
+type Draft = Record;
+const emptyDraft = (): Draft => ({ TIME: { band: "", basis: "" }, COST_EFFORT: { band: "", basis: "" } });
+
+/** Shows the record's current estimates and lets a person add their own as a new revision. */
+export function ActionEffort({ projectId, actionId, initial, legacy }: { projectId: string; actionId: string; initial: unknown; legacy?: string }) {
+ const client = useQueryClient();
+ const key = ["action-effort", projectId, actionId];
+ const record = useQuery({ queryKey: key, retry: false,
+ queryFn: ({ signal }) => rpc<{ action: Record; research_running?: boolean }>("action/read", { project_id: projectId, action_id: actionId }, crypto.randomUUID(), signal),
+ refetchInterval: current => current.state.data?.value.research_running ? 3000 : false });
+ const running = record.data?.value.research_running === true;
+ const action = objectValue(record.data?.value.action);
+ const details = objectValue(action.generation_details);
+ const estimates = parseEstimates(record.data && Array.isArray(details.effort_estimates) ? details.effort_estimates : initial);
+ const digest = textValue(action.revision_digest);
+ const [open, setOpen] = useState(false);
+ const [draft, setDraft] = useState(emptyDraft);
+ const [message, setMessage] = useState("");
+ const save = useMutation({
+ mutationFn: (entries: { dimension: string; band: string; basis_text: string }[]) => rpc("action/revise", {
+ project_id: projectId, action_id: actionId, expected_revision_digest: digest, patch: {}, evidence_refs: [],
+ reason: "사람 추정 입력", estimator_ref: HUMAN_REF, human_effort_estimates: entries }, crypto.randomUUID()),
+ onSuccess: async () => { setDraft(emptyDraft()); setOpen(false); setMessage(""); await client.invalidateQueries({ queryKey: key }); },
+ });
+ const submit = () => {
+ const entries = DIMENSIONS.filter(dimension => draft[dimension].band).map(dimension => ({ dimension, band: draft[dimension].band, basis_text: draft[dimension].basis.trim() }));
+ if (entries.length === 0) { setMessage("저장할 추정을 고르세요."); return; }
+ if (entries.some(entry => entry.band !== "UNKNOWN" && !entry.basis_text)) { setMessage("근거 한 줄을 적어 주세요."); return; }
+ setMessage(""); save.mutate(entries);
+ };
+ const set = (dimension: string, part: "band" | "basis", value: string) => setDraft(current => ({ ...current, [dimension]: { ...current[dimension], [part]: value } }));
+ return
+
+
+ {running && {researchRunningNotice}}
+ {open &&
+ {DIMENSIONS.map(dimension =>
+
+ set(dimension, "basis", event.target.value)}/>
+
)}
+
AI 추정은 지워지지 않고 함께 표시됩니다.
+
+ {message &&
{message}
}
+ {save.error &&
추정을 저장하지 못했습니다. {save.error.message}
}
+
}
+ ;
+}
diff --git a/apps/web/src/components/AnswerBody.tsx b/apps/web/src/components/AnswerBody.tsx
new file mode 100644
index 0000000..fff9503
--- /dev/null
+++ b/apps/web/src/components/AnswerBody.tsx
@@ -0,0 +1,20 @@
+import { Fragment, useMemo } from "react";
+import { parseAnswer, type Inline } from "./answerText";
+
+/** The answer as paragraphs, lists and bold text, with each source shown as a numbered button. */
+export function AnswerBody({ answer, onCite }: {
+ answer: string;
+ onCite: (spanId: string, numbers: Record) => void;
+}) {
+ const parsed = useMemo(() => parseAnswer(answer), [answer]);
+ const numbers = useMemo(() => Object.fromEntries(parsed.citations.map(item => [item.spanId, item.n])), [parsed]);
+ const render = (items: Inline[]) => items.map((item, index) => {
+ const body = item.kind === "text"
+ ? item.text
+ : ;
+ return {item.bold ? {body} : body};
+ });
+ return {parsed.blocks.map((block, index) => block.kind === "p"
+ ?
{render(block.inline)}
+ :
{block.items.map((item, itemIndex) => - {render(item)}
)}
)}
;
+}
diff --git a/apps/web/src/components/AnswerCitations.test.tsx b/apps/web/src/components/AnswerCitations.test.tsx
new file mode 100644
index 0000000..c51e7e3
--- /dev/null
+++ b/apps/web/src/components/AnswerCitations.test.tsx
@@ -0,0 +1,109 @@
+// @vitest-environment jsdom
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import { act, useState } from "react";
+import { createRoot, type Root } from "react-dom/client";
+import { afterEach, expect, it, vi } from "vitest";
+import fixture from "../api/fixtures/qa04-partial-result.json";
+import excerpt from "../api/fixtures/iris-judgment-excerpt.json";
+import { rpc } from "../api/rpcClient";
+import { ResearchResultCard, type ResearchDetail } from "./ResearchResultCard";
+import { ResearchDetailPane } from "./ResearchDetailPane";
+import type { PackRunResult } from "../types";
+import { evidenceResponse, manifest } from "../api/resultEvidenceTestFixture";
+import { webcrypto } from "node:crypto";
+
+vi.mock("../api/rpcClient", async original => ({ ...await original(), rpc: vi.fn() }));
+let root: Root | undefined;
+let node: HTMLDivElement;
+let client: QueryClient;
+const project = { project_id: fixture.scope.projectId } as PackRunResult["project"];
+const thread = { thread_id: fixture.scope.threadId } as PackRunResult["thread"];
+const scrolled = vi.fn();
+
+afterEach(async () => {
+ if (root) await act(async () => root!.unmount());
+ root = undefined; node?.remove(); client?.clear(); vi.resetAllMocks(); scrolled.mockReset();
+});
+
+function Flow({ result }: { result: Record }) {
+ const [detail, setDetail] = useState(null);
+ return <>
+ {detail && setDetail(null)}/>}>;
+}
+async function mount(node2: React.ReactElement) {
+ Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true });
+ Object.defineProperty(crypto, "subtle", { value: webcrypto.subtle, configurable: true });
+ Element.prototype.scrollIntoView = scrolled;
+ client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
+ node = document.createElement("div"); document.body.append(node); root = createRoot(node);
+ await act(async () => root!.render({node2}));
+}
+async function settle(check: () => boolean) {
+ for (let i = 0; i < 200; i++) {
+ await act(async () => { await new Promise(resolve => setTimeout(resolve, 10)); });
+ if (check()) return;
+ }
+ throw new Error(`not rendered: ${node.textContent?.slice(-500)}`);
+}
+const cites = () => Array.from(node.querySelectorAll("button.answer-citation"));
+
+it("shows numbered citations and bold text instead of internal ids and asterisks", async () => {
+ const opened: ResearchDetail[] = [];
+ await mount( opened.push(detail)}
+ historySelection={{ kind: "result", scope: fixture.scope, operationId: fixture.operationId }}/>);
+ const text = node.querySelector(".answer-text")!.textContent!;
+ expect(text).not.toContain("span:");
+ expect(text).not.toContain("**");
+ expect(text).toContain("[1]");
+ expect(node.querySelector(".answer-text strong")?.textContent).toContain("1세부");
+ const first = cites()[0];
+ expect(first.textContent).toBe("[1]");
+ await act(async () => first.click());
+ expect(opened).toHaveLength(1);
+ expect(opened[0].kind).toBe("evidence");
+ expect(opened[0].focusSpanId).toBe("span:1580e7b4-64bb-4eb0-99c2-5a8fc6113f70");
+ expect(opened[0].citations?.["span:1580e7b4-64bb-4eb0-99c2-5a8fc6113f70"]).toBe(1);
+});
+
+it("keeps one number for a source cited twice", async () => {
+ await mount( {}}/>);
+ expect(cites().map(item => item.textContent)).toEqual(["[1]", "[2]", "[1]"]);
+});
+
+function serve(result: Record) {
+ vi.mocked(rpc).mockImplementation(async (_method, args) => evidenceResponse(Number(args.selected_evidence_offset ?? 0), args.include_selected_evidence === true, result));
+}
+const cited = (id: string) => ({ ...fixture.result, answer: `이 문장은 근거에 기대어 있습니다 (${id}).` });
+
+it("moves to the cited source card in the evidence panel and highlights it", async () => {
+ const target = manifest.source_refs[10];
+ const result = cited(target);
+ serve(result);
+ await mount();
+ await act(async () => cites()[0].click());
+ await settle(() => Boolean(node.querySelector('.evidence-item[data-span-id="' + target + '"]')));
+ const card = node.querySelector('.evidence-item[data-span-id="' + target + '"]')!;
+ expect(card.classList.contains("focused")).toBe(true);
+ expect(card.textContent).toContain("[1]");
+ expect(scrolled).toHaveBeenCalled();
+ expect(node.textContent).not.toContain("이 근거를 목록에서 찾지 못했습니다");
+});
+
+it("reads later pages to find a cited source whose original text cannot be shown", async () => {
+ const result = cited(manifest.source_refs[120]);
+ serve(result);
+ await mount();
+ await act(async () => cites()[0].click());
+ await settle(() => Boolean(node.textContent?.includes("이 근거는 원문을 표시할 수 없습니다")));
+ expect(node.textContent).toContain("전체 149개 중 149개 확인");
+});
+
+it("says so when the cited source is not in the answer's evidence list", async () => {
+ const result = cited("span:no-such-source-0001");
+ serve(result);
+ await mount();
+ await act(async () => cites()[0].click());
+ await settle(() => Boolean(node.textContent?.includes("이 근거를 목록에서 찾지 못했습니다")));
+ expect(node.textContent).toContain("전체 149개 중 149개 확인");
+});
diff --git a/apps/web/src/components/AnswerHold.tsx b/apps/web/src/components/AnswerHold.tsx
new file mode 100644
index 0000000..e669cfa
--- /dev/null
+++ b/apps/web/src/components/AnswerHold.tsx
@@ -0,0 +1,27 @@
+import type { ReactNode } from "react";
+import { Callout } from "@blueprintjs/core";
+import type { CoverageMatrix } from "../api/researchFollowup";
+import { EvidenceUseSummary } from "./EvidenceUseSummary";
+import { answerStatusLabel } from "./statusLabels";
+
+/**
+ * The first lines under an answer, in a fixed order: conclusion status, confirmed facts (slot), what each criterion's
+ * evidence can be used for, how many criteria are fulfilled, the counter-evidence search, and what to secure next.
+ */
+export function AnswerHold({ status, coverage, facts, counter, next }: {
+ status: string; coverage?: CoverageMatrix | null; facts?: ReactNode; counter?: string | null; next?: string | null;
+}) {
+ const label = answerStatusLabel(status) ?? status;
+ const rows = coverage && coverage.availability !== "UNAVAILABLE" ? coverage.rows : [];
+ const supported = rows.filter(row => row.status === "SATISFIED").length;
+ const needsCheck = rows.filter(row => row.status === "UNRESOLVED" || row.status === "NOT_ASSESSED").length;
+ return
+ 결론 상태: {label}
+ {facts}
+ {rows.length > 0 && }
+ 답할 수 있는 범위: {rows.length ? `기준 충족 ${supported}/${rows.length} · 확인 필요 ${needsCheck}` : "평가기준 정보를 아직 읽지 못했습니다"}
+ {counter && {counter}
}
+ {next && 다음 확보: {next}
}
+ ;
+}
+
diff --git a/apps/web/src/components/ApprovalCard.test.tsx b/apps/web/src/components/ApprovalCard.test.tsx
new file mode 100644
index 0000000..0e7612a
--- /dev/null
+++ b/apps/web/src/components/ApprovalCard.test.tsx
@@ -0,0 +1,125 @@
+// @vitest-environment jsdom
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import { act } from "react";
+import { createRoot, type Root } from "react-dom/client";
+import { afterEach, expect, it, vi } from "vitest";
+import { ApprovalCard } from "./ApprovalCard";
+import { ApprovalSection } from "./ApprovalSection";
+import { parseApproval } from "./approvalView";
+
+const state = vi.hoisted(() => ({ authorizations: [] as Record[], planFails: false, calls: [] as { method: string; input: Record }[] }));
+vi.mock("../api/rpcClient", async importOriginal => ({ ...(await importOriginal()),
+ rpc: async (method: string, input: Record) => {
+ state.calls.push({ method, input });
+ if (method === "action/plan/read") {
+ if (state.planFails) throw new Error("synthetic plan failure");
+ return { value: { plan: { plan_id: input.plan_id, revision_digest: "e".repeat(64) }, authorizations: state.authorizations }, state: "SUCCEEDED", operation_id: "" };
+ }
+ if (method === "action/authorization/read") {
+ const item = state.authorizations.find(entry => entry.authorization_id === input.authorization_id)!;
+ return { value: { authorization: item, effective_state: item.state, payload_current: item.state === "APPROVED", review_needed: false }, state: "SUCCEEDED", operation_id: "" };
+ }
+ if (method === "action/authorization/prepare") {
+ state.authorizations = [...state.authorizations, { ...state.authorizations[0], authorization_id: "auth:2", state: "PENDING", stale_reason: null, material_changes: [],
+ decision_history: [], created_at: "2026-09-30T02:00:00Z" }];
+ }
+ return { value: {}, state: "SUCCEEDED", operation_id: "" };
+ } }));
+
+let root: Root | undefined; let node: HTMLDivElement;
+afterEach(async () => { if (root) await act(async () => root!.unmount()); root = undefined; node?.remove(); state.authorizations = []; state.planFails = false; state.calls = []; });
+async function mount(element: React.ReactElement) {
+ Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true });
+ node = document.createElement("div"); document.body.append(node); root = createRoot(node);
+ const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
+ await act(async () => root!.render({element}));
+ for (let i = 0; i < 8; i++) await act(async () => { await new Promise(r => setTimeout(r, 10)); });
+}
+const payload = { action_kind: "COMMUNICATION_SUBMISSION", channel: "EMAIL", content: { body_text: "안녕하세요 담당자님 시험 성적서를 보내 주세요", attachments: [{ name: "요청서.pdf", digest: "1".repeat(64) }] },
+ targets: ["b".repeat(64)], disclosed_data: ["프로젝트 이름"], execution_roles: ["project-owner"], risk_tier: "R3", reversibility: "IRREVERSIBLE" };
+const record = (patch: Record = {}) => ({ authorization_id: "auth:1", step_id: "step:send", state: "APPROVED", stale_reason: null, plan_revision_digest: "a".repeat(64),
+ predecessor_output_digests: [], target_baseline_digests: ["b".repeat(64)], policy_version: "policy:current",
+ payload, material_changes: [], decision_history: [{ decision: "APPROVE", decided_at: "2026-09-30T00:00:00Z" }], schema_version: "2.0.0", created_at: "2026-09-30T00:00:00Z", ...patch });
+const bodyChange = { field: "body", label: "본문", before: "", after: "", attachments: [],
+ words: [{ op: "keep", text: "안녕하세요" }, { op: "keep", text: "담당자님" }, { op: "keep", text: "시험" }, { op: "del", text: "성적서를" }, { op: "add", text: "성적서와 원자료를" },
+ { op: "keep", text: "보내" }, { op: "keep", text: "주세요" }, { op: "keep", text: "빨리" }, { op: "keep", text: "부탁드립니다" }] };
+const attachmentChange = { field: "attachments", label: "첨부", before: "요청서.pdf", after: "요청서-v2.pdf", words: [],
+ attachments: [{ before_name: "요청서.pdf", before_digest: "1".repeat(64), after_name: "요청서-v2.pdf", after_digest: "2".repeat(64) }] };
+const stale = (changes: unknown[]) => record({ state: "STALE", stale_reason: "MATERIAL_CHANGE", material_changes: changes,
+ decision_history: [{ decision: "APPROVE", decided_at: "2026-09-30T00:00:00Z" }, { decision: "STALE", stale_at: "2026-09-30T01:00:00Z" }] });
+const view = (item: Record, extra: Record = {}) => parseApproval({ authorization: item, effective_state: item.state, payload_current: item.state === "APPROVED", review_needed: false, ...extra });
+
+it("puts the changed parts first: what moved, and only the changed words with a little context", async () => {
+ await mount();
+ const text = node.textContent ?? "";
+ expect(text).toContain("승인 뒤 바뀜: 본문 · 첨부");
+ expect(node.querySelector("del")?.textContent).toBe("성적서를");
+ expect(node.querySelector("ins")?.textContent).toBe("성적서와 원자료를");
+ expect(text).toContain("…");
+ expect(text).not.toContain("안녕하세요");
+ expect(text).toContain("요청서.pdf"); expect(text).toContain("요청서-v2.pdf");
+ expect(text).toContain("11111111"); expect(text).toContain("22222222");
+ expect(text).not.toMatch(/MATERIAL_CHANGE|attachments|"body"/);
+});
+
+it("keeps the earlier approval as history of the version it covered and keeps the full content folded", async () => {
+ await mount();
+ expect(node.textContent).toContain("이전 승인은 바뀌기 전 버전에 유효했음");
+ const toggle = [...node.querySelectorAll("button")].find(b => b.textContent === "전체 내용 보기") as HTMLButtonElement;
+ expect(toggle.getAttribute("aria-expanded")).toBe("false");
+ await act(async () => toggle.click());
+ expect(toggle.getAttribute("aria-expanded")).toBe("true");
+ expect(node.textContent).toContain("안녕하세요 담당자님 시험 성적서를 보내 주세요");
+});
+
+it("offers re-approval and not-sending only for a stale approval", async () => {
+ const calls: string[] = [];
+ await mount( calls.push("again")} onDecline={() => calls.push("no")}/>);
+ const buttons = [...node.querySelectorAll("button")].map(b => b.textContent);
+ expect(buttons).toContain("바뀐 내용으로 승인 요청 다시 만들기"); expect(buttons).toContain("보내지 않음");
+ expect(buttons).not.toContain("바뀐 내용 확인 후 다시 승인");
+ for (const label of ["바뀐 내용으로 승인 요청 다시 만들기", "보내지 않음"]) await act(async () => ([...node.querySelectorAll("button")].find(b => b.textContent === label) as HTMLButtonElement).click());
+ expect(calls).toEqual(["again", "no"]);
+ await act(async () => root!.unmount()); node.remove();
+ await mount( undefined} onDecline={() => undefined}/>);
+ expect([...node.querySelectorAll("button")].map(b => b.textContent)).not.toContain("보내지 않음");
+ expect(node.textContent).toContain("승인됨");
+});
+
+it("says a look is advised when only the evidence moved, without voiding the approval", async () => {
+ await mount();
+ expect(node.textContent).toContain("승인됨"); expect(node.textContent).toContain("근거가 바뀌어 다시 살펴보는 것이 좋습니다");
+ expect(node.textContent).not.toContain("승인 뒤 바뀜");
+});
+
+it("re-approval prepares a new approval for the current plan revision with the same digests and policy", async () => {
+ state.authorizations = [stale([bodyChange])];
+ await mount();
+ await act(async () => ([...node.querySelectorAll("button")].find(b => b.textContent === "바뀐 내용으로 승인 요청 다시 만들기") as HTMLButtonElement).click());
+ for (let i = 0; i < 8; i++) await act(async () => { await new Promise(r => setTimeout(r, 10)); });
+ expect(node.textContent).toContain("승인 대기");
+ expect(node.textContent).toContain("승인 요청을 만들었습니다. 승인자가 결정하기 전까지 아무것도 보내지 않습니다");
+ expect(node.textContent).not.toContain("승인됨");
+ const prepared = state.calls.find(call => call.method === "action/authorization/prepare")!;
+ expect(prepared.input).toEqual({ project_id: "project:p", plan_id: "plan:1", step_id: "step:send", plan_revision_digest: "e".repeat(64),
+ predecessor_output_digests: [], target_baseline_digests: ["b".repeat(64)], policy_version: "policy:current" });
+});
+
+it("marks a change as not sent on this screen only, without any server call that sends", async () => {
+ state.authorizations = [stale([bodyChange])];
+ await mount();
+ await act(async () => ([...node.querySelectorAll("button")].find(b => b.textContent === "보내지 않음") as HTMLButtonElement).click());
+ expect(node.querySelector(".approval-card")).toBeNull();
+ expect(node.textContent).toContain("이 화면에만 적용");
+ expect(state.calls.map(call => call.method).filter(method => !["action/plan/read", "action/authorization/read"].includes(method))).toEqual([]);
+});
+
+it("reads the plan's approvals and shows the newest state per step, or nothing when the plan cannot be read", async () => {
+ state.authorizations = [stale([bodyChange])];
+ await mount( undefined}/>);
+ expect(state.calls[0]).toEqual({ method: "action/plan/read", input: { project_id: "project:p", plan_id: "plan:1" } });
+ expect(node.textContent).toContain("승인 뒤 바뀜: 본문");
+ await act(async () => root!.unmount()); node.remove(); state.planFails = true;
+ await mount();
+ expect(node.textContent).toBe("");
+});
diff --git a/apps/web/src/components/ApprovalCard.tsx b/apps/web/src/components/ApprovalCard.tsx
new file mode 100644
index 0000000..9193277
--- /dev/null
+++ b/apps/web/src/components/ApprovalCard.tsx
@@ -0,0 +1,49 @@
+import { Button, Callout, Collapse, Tag } from "@blueprintjs/core";
+import { useState } from "react";
+import { objectList, objectValue, textValue } from "../api/presentation";
+import { compactWords, listOf, shortDigest, stateLabels, type Approval, type ApprovalChange } from "./approvalView";
+import { statusLabel } from "./statusLabels";
+
+function ChangeView({ change }: { change: ApprovalChange }) {
+ return
+ {change.label}
+ {change.words.length > 0 && {compactWords(change.words).map((part, index) =>
+ part.op === "del" ? {part.text}{" "} : part.op === "add" ? {part.text}{" "}
+ : part.op === "gap" ? … : {part.text} )}
}
+ {change.attachments.map((pair, index) =>
+ {pair.beforeName ?? "없음"}{pair.beforeDigest && ({shortDigest(pair.beforeDigest)}…)}
+ {" → "}{pair.afterName ?? "없음"}{pair.afterDigest && ({shortDigest(pair.afterDigest)}…)}
)}
+ {change.words.length === 0 && change.attachments.length === 0 && {change.before || "없음"} → {change.after || "없음"}
}
+ ;
+}
+
+function FullContent({ payload }: { payload: Record }) {
+ const content = objectValue(payload.content);
+ const attachments = objectList(content.attachments).map(item => textValue(item.name)).filter(Boolean);
+ const rows: [string, string][] = [
+ ["행동 종류", textValue(payload.action_kind)], ["전달 경로", textValue(payload.channel)], ["본문", textValue(content.body_text)],
+ ["첨부", attachments.join(", ")], ["공개되는 정보", listOf(payload.disclosed_data).join(", ")],
+ ["실행 권한 역할", listOf(payload.execution_roles).join(", ")], ["위험 등급", statusLabel(textValue(payload.risk_tier)) ?? textValue(payload.risk_tier)],
+ ];
+ return {rows.filter(([, text]) => text).map(([label, text]) => - {label}
- {text}
)}
;
+}
+
+/** An approval, with what changed since it was given shown first and the full content folded away. */
+export function ApprovalCard({ approval, onReapprove, onDecline }: { approval: Approval; onReapprove?: () => void; onDecline?: () => void }) {
+ const [open, setOpen] = useState(false);
+ const stale = approval.state === "STALE";
+ return
+ {stateLabels[approval.state] ?? approval.state}
+ {stale && change.label).join(" · ") || "내용"}`}>
+ {approval.changes.map(change => )}
+ 이전 승인은 바뀌기 전 버전에 유효했음 (승인 기록은 그대로 남아 있습니다).
+ }
+ {!stale && approval.reviewNeeded && 근거가 바뀌어 다시 살펴보는 것이 좋습니다. 보낼 내용은 그대로이므로 승인은 유지됩니다.}
+
+
+ {stale && (onReapprove || onDecline) &&
+ {onReapprove && }
+ {onDecline && }
+
}
+ ;
+}
diff --git a/apps/web/src/components/ApprovalSection.tsx b/apps/web/src/components/ApprovalSection.tsx
new file mode 100644
index 0000000..b042d9f
--- /dev/null
+++ b/apps/web/src/components/ApprovalSection.tsx
@@ -0,0 +1,51 @@
+import { Callout } from "@blueprintjs/core";
+import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
+import { useState } from "react";
+import { objectList, objectValue, textValue } from "../api/presentation";
+import { rpc } from "../api/rpcClient";
+import { ApprovalCard } from "./ApprovalCard";
+import { parseApproval, type Approval } from "./approvalView";
+
+/**
+ * The plan's protected-step approvals, newest per step. Shows nothing when they cannot be read.
+ * "Make the approval request again" only prepares a pending request for the current content; approvers still decide it.
+ * "보내지 않음" is remembered on this screen only: no message is sent either way.
+ */
+export function ApprovalSection({ projectId, planId, onReapprove, onDecline }: {
+ projectId: string; planId: string; onReapprove?: (approval: Approval) => void; onDecline?: (approval: Approval) => void;
+}) {
+ const client = useQueryClient();
+ const key = ["approvals", projectId, planId];
+ const [declined, setDeclined] = useState([]);
+ const query = useQuery({
+ queryKey: key, retry: false,
+ queryFn: async ({ signal }) => {
+ const plan = await rpc>("action/plan/read", { project_id: projectId, plan_id: planId }, crypto.randomUUID(), signal);
+ const latest = new Map>();
+ for (const record of objectList(plan.value.authorizations)) {
+ const seen = latest.get(textValue(record.step_id));
+ if (!seen || textValue(record.created_at) >= textValue(seen.created_at)) latest.set(textValue(record.step_id), record);
+ }
+ const approvals = await Promise.all([...latest.values()].map(async record => parseApproval(objectValue((await rpc>("action/authorization/read",
+ { project_id: projectId, authorization_id: textValue(record.authorization_id) }, crypto.randomUUID(), signal)).value))));
+ return { approvals, planRevision: textValue(objectValue(plan.value.plan).revision_digest) };
+ },
+ });
+ const reapprove = useMutation({
+ mutationFn: (approval: Approval) => rpc("action/authorization/prepare", { project_id: projectId, plan_id: planId, step_id: approval.stepId,
+ plan_revision_digest: query.data?.planRevision ?? "", predecessor_output_digests: approval.predecessors, target_baseline_digests: approval.targetBaselines,
+ policy_version: approval.policyVersion }, crypto.randomUUID()),
+ onSuccess: () => client.invalidateQueries({ queryKey: key }),
+ });
+ const requested = reapprove.isSuccess;
+ const shown = (query.data?.approvals ?? []).filter(approval => !declined.includes(approval.id));
+ if (!query.data || query.data.approvals.length === 0) return null;
+ return <>
+ {reapprove.error && 승인 요청을 다시 만들지 못했습니다. {reapprove.error.message}}
+ {requested && 승인 요청을 만들었습니다. 승인자가 결정하기 전까지 아무것도 보내지 않습니다.}
+ {shown.map(approval => { onReapprove?.(approval); if (!onReapprove) reapprove.mutate(approval); }}
+ onDecline={() => { onDecline?.(approval); setDeclined(current => [...current, approval.id]); }}/>)}
+ {declined.length > 0 && 보내지 않기로 표시한 변경이 있습니다. 이 표시는 이 화면에만 적용되며, 승인 없이는 아무것도 전송되지 않습니다.
}
+ >;
+}
diff --git a/apps/web/src/components/AutoRetrySwitch.test.tsx b/apps/web/src/components/AutoRetrySwitch.test.tsx
new file mode 100644
index 0000000..3f39a2b
--- /dev/null
+++ b/apps/web/src/components/AutoRetrySwitch.test.tsx
@@ -0,0 +1,49 @@
+// @vitest-environment jsdom
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import { act } from "react";
+import { createRoot, type Root } from "react-dom/client";
+import { afterEach, expect, it, vi } from "vitest";
+import { AutoRetrySwitch, autoRetryHelp } from "./AutoRetrySwitch";
+
+const state = vi.hoisted(() => ({ enabled: false, digest: null as string | null, calls: [] as { method: string; input: Record }[], fail: false }));
+vi.mock("../api/rpcClient", async importOriginal => ({ ...(await importOriginal()),
+ rpc: async (method: string, input: Record) => {
+ state.calls.push({ method, input });
+ if (state.fail) throw new Error("synthetic failure");
+ if (method === "model/callSettings/update") { state.enabled = input.auto_retry_interrupted_model_call as boolean; state.digest = "s".repeat(64); }
+ return { value: { auto_retry_interrupted_model_call: state.enabled, settings_digest: state.digest }, state: "SUCCEEDED", operation_id: "" };
+ } }));
+
+let root: Root | undefined; let container: HTMLDivElement;
+const settle = async () => { for (let i = 0; i < 8; i++) await act(async () => { await new Promise(r => setTimeout(r, 10)); }); };
+async function mount() {
+ Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true });
+ container = document.createElement("div"); document.body.append(container); root = createRoot(container);
+ const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
+ await act(async () => root!.render());
+ await settle();
+}
+afterEach(async () => { if (root) await act(async () => root!.unmount()); root = undefined; container?.remove(); document.body.innerHTML = ""; state.enabled = false; state.digest = null; state.calls = []; state.fail = false; });
+
+it("starts off as the server says, says what turning it on costs, and sends the digest it read when turned on", async () => {
+ await mount();
+ const input = container.querySelector("input[type=checkbox]")!;
+ expect(input.checked).toBe(false);
+ expect(container.textContent).toContain("모델 호출이 중간에 끊기면 그 호출만 최대 2번 자동으로 다시 시도합니다. 토큰이 더 쓰입니다.");
+ expect(container.textContent).toContain("끄면 끊긴 결과 카드의 \"이어서 조사\"로 다시 할 수 있습니다.");
+ expect(autoRetryHelp).toContain("토큰이 더 쓰입니다");
+ expect(state.calls).toEqual([{ method: "model/callSettings/read", input: { project_id: "project:p" } }]);
+ await act(async () => input.click());
+ await settle();
+ const update = state.calls.find(call => call.method === "model/callSettings/update")!;
+ expect(update.input).toEqual({ project_id: "project:p", auto_retry_interrupted_model_call: true, expected_digest: null });
+ expect(container.querySelector("input[type=checkbox]")!.checked).toBe(true);
+});
+
+it("says so when the setting cannot be read instead of showing it as off", async () => {
+ state.fail = true;
+ await mount();
+ expect(container.textContent).toContain("자동 재시도 설정을 읽지 못했습니다");
+ expect(container.querySelector("input[type=checkbox]")).toBeNull();
+});
+
diff --git a/apps/web/src/components/AutoRetrySwitch.tsx b/apps/web/src/components/AutoRetrySwitch.tsx
new file mode 100644
index 0000000..bee33cb
--- /dev/null
+++ b/apps/web/src/components/AutoRetrySwitch.tsx
@@ -0,0 +1,32 @@
+import { Switch } from "@blueprintjs/core";
+import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
+import { rpc } from "../api/rpcClient";
+
+type CallSettings = { auto_retry_interrupted_model_call: boolean; settings_digest: string | null };
+
+export const autoRetryHelp = "모델 호출이 중간에 끊기면 그 호출만 최대 2번 자동으로 다시 시도합니다. 토큰이 더 쓰입니다. 끄면 끊긴 결과 카드의 \"이어서 조사\"로 다시 할 수 있습니다.";
+
+/** The project's switch for sending a cut-off model call again (at most twice). It shows what the server says. */
+export function AutoRetrySwitch({ projectId }: { projectId: string }) {
+ const client = useQueryClient();
+ const key = ["model-call-settings", projectId];
+ const query = useQuery({
+ queryKey: key,
+ queryFn: ({ signal }) => rpc("model/callSettings/read", { project_id: projectId }, crypto.randomUUID(), signal),
+ });
+ const change = useMutation({
+ mutationFn: (enabled: boolean) => rpc("model/callSettings/update", {
+ project_id: projectId, auto_retry_interrupted_model_call: enabled, expected_digest: query.data?.value.settings_digest ?? null,
+ }, crypto.randomUUID()),
+ onSettled: () => client.invalidateQueries({ queryKey: key }),
+ });
+ if (query.isPending) return null;
+ if (query.error || !query.data) return 자동 재시도 설정을 읽지 못했습니다.
;
+ return
+ change.mutate(event.currentTarget.checked)}/>
+ {autoRetryHelp}
+ {change.error && 설정을 바꾸지 못했습니다. 화면을 다시 읽었으니 다시 시도해 주세요.}
+
;
+}
+
diff --git a/apps/web/src/components/CapabilityWorkbench.test.tsx b/apps/web/src/components/CapabilityWorkbench.test.tsx
index ee676f9..b6823a8 100644
--- a/apps/web/src/components/CapabilityWorkbench.test.tsx
+++ b/apps/web/src/components/CapabilityWorkbench.test.tsx
@@ -62,6 +62,7 @@ it("does not expose ProjectPack or field fixtures in the local API workbench", a
it("late capability response is not relabeled with the next project's context",async()=>{
await mount();
+ await click("model/settings/read");
fixture.defer=true;await click("이 조회 실행");
await render();
await act(async()=>{fixture.resolve!();await tick();});
diff --git a/apps/web/src/components/ClaudeCodeHelp.tsx b/apps/web/src/components/ClaudeCodeHelp.tsx
new file mode 100644
index 0000000..0f56ff0
--- /dev/null
+++ b/apps/web/src/components/ClaudeCodeHelp.tsx
@@ -0,0 +1,9 @@
+import { CLAUDE_CODE_INSTALL_COMMAND } from "./connectionCopy";
+import { ToolInstallHelp } from "./ToolInstallHelp";
+
+/** Shown when the official Claude Code executable is missing or too old. */
+export function ClaudeCodeHelp({ onInstalled }: { onInstalled?: () => void }) {
+ return ;
+}
diff --git a/apps/web/src/components/CodexToolHelp.tsx b/apps/web/src/components/CodexToolHelp.tsx
new file mode 100644
index 0000000..5bf0eb8
--- /dev/null
+++ b/apps/web/src/components/CodexToolHelp.tsx
@@ -0,0 +1,9 @@
+import { CODEX_TOOL_INSTALL_COMMAND } from "./connectionCopy";
+import { ToolInstallHelp } from "./ToolInstallHelp";
+
+/** Shown only after the server reports a missing or mismatched pinned Codex tool. */
+export function CodexToolHelp({ onInstalled }: { onInstalled?: () => void }) {
+ return ;
+}
diff --git a/apps/web/src/components/ConfirmedFacts.test.tsx b/apps/web/src/components/ConfirmedFacts.test.tsx
new file mode 100644
index 0000000..626590f
--- /dev/null
+++ b/apps/web/src/components/ConfirmedFacts.test.tsx
@@ -0,0 +1,60 @@
+import { renderToStaticMarkup } from "react-dom/server";
+import { expect, it } from "vitest";
+import excerpt from "../api/fixtures/iris-judgment-excerpt.json";
+import { ResearchResultCard } from "./ResearchResultCard";
+
+const held = { ...excerpt, answer_status: "PARTIAL_HOLD" } as Record;
+const spanA = (excerpt.answer.match(/span:[0-9A-Za-z][0-9A-Za-z_-]*/) ?? [""])[0];
+const render = (result: Record) => renderToStaticMarkup( {}}/>);
+const withFindings = (findings: unknown, extra: Record = {}) => ({ ...held, confirmed_findings: findings, ...extra });
+const fact = (statement: string, refs: string[], kind = "DOCUMENT_FACT") => ({ statement, evidence_refs: refs, requirement_id: null, kind });
+
+it("says the result predates confirmed facts instead of pulling facts out of the answer text", () => {
+ expect(held.answer).toContain("FACT");
+ const html = render(held);
+ expect(html).toContain("확인된 사실");
+ expect(html).toContain("이 결과에는 정리된 확인 사실이 없습니다(이전 형식)");
+});
+
+it("lists the stored facts with their source numbers and marks a valid negative finding", () => {
+ expect(spanA).not.toBe("");
+ const html = render(withFindings([
+ fact("RFP p.36에 1세부 과제명이 적혀 있다", [spanA]),
+ fact("1세부 발췌에서 센서 조합의 필수 지정을 찾아봤지만 없다", [spanA, "span:not-cited-in-answer"], "VALID_NEGATIVE_FINDING"),
+ ]));
+ expect(html).toContain("RFP p.36에 1세부 과제명이 적혀 있다");
+ expect(html).toContain("찾아봤지만 없음");
+ expect(html).toMatch(/RFP p\.36에 1세부 과제명이 적혀 있다[\s\S]{0,300}\[1\]/);
+ expect(html).not.toContain("이전 형식");
+ // a source the answer never cited still gets its own number after the answer's
+ expect(html).toMatch(/aria-label="근거 \d+ 보기">\[\d+\]<\/button><\/li>/);
+});
+
+it("keeps facts visible on a held answer, directly under the conclusion status", () => {
+ const html = render(withFindings([fact("문서에 적힌 사실", [spanA])]));
+ const at = (text: string) => html.lastIndexOf(text);
+ expect(html.indexOf("결론 상태: 판단 보류")).toBeGreaterThan(-1);
+ expect(html.indexOf("결론 상태: 판단 보류")).toBeLessThan(html.indexOf("확인된 사실"));
+ expect(html.indexOf("확인된 사실")).toBeLessThan(html.indexOf("문서에 적힌 사실"));
+ expect(at("문서에 적힌 사실")).toBeGreaterThan(-1);
+});
+
+it("tells an empty list apart from an old result, and reports dropped findings without listing them", () => {
+ const html = render(withFindings([], { confirmed_findings_dropped: 2 }));
+ expect(html).toContain("정리된 확인 사실 없음");
+ expect(html).not.toContain("이전 형식");
+ expect(html).toContain("근거를 확인하지 못해 제외된 항목 2개");
+});
+
+it("ignores a stored entry that has no source or an unknown kind rather than showing it", () => {
+ const html = render(withFindings([fact("근거 없는 문장", []), fact("종류를 모르는 문장", [spanA], "SOMETHING"), fact("정상 문장", [spanA])]));
+ expect(html).toContain("정상 문장");
+ expect(html).not.toContain("근거 없는 문장");
+ expect(html).not.toContain("종류를 모르는 문장");
+});
+
+it("does not turn a stored fact into a claim that criteria are fulfilled", () => {
+ const html = render(withFindings([fact("문서에 적힌 사실", [spanA])]));
+ expect(html).toContain("문서에 적힌 내용이며 기준 충족을 뜻하지 않습니다");
+});
+
diff --git a/apps/web/src/components/ConfirmedFacts.tsx b/apps/web/src/components/ConfirmedFacts.tsx
new file mode 100644
index 0000000..ec720c8
--- /dev/null
+++ b/apps/web/src/components/ConfirmedFacts.tsx
@@ -0,0 +1,52 @@
+import { Tag } from "@blueprintjs/core";
+import { numberSources } from "./answerText";
+
+type Finding = { statement: string; refs: string[]; negative: boolean };
+
+/** Reads only the stored structured list; stored entries that are malformed are skipped, never repaired. */
+function readFindings(value: unknown): Finding[] {
+ if (!Array.isArray(value)) return [];
+ const found: Finding[] = [];
+ for (const item of value as unknown[]) {
+ if (typeof item !== "object" || item === null) continue;
+ const entry = item as Record;
+ const refs = Array.isArray(entry.evidence_refs) ? entry.evidence_refs.filter((ref): ref is string => typeof ref === "string") : [];
+ if (typeof entry.statement !== "string" || !entry.statement.trim() || refs.length === 0) continue;
+ if (entry.kind !== "DOCUMENT_FACT" && entry.kind !== "VALID_NEGATIVE_FINDING") continue;
+ found.push({ statement: entry.statement, refs, negative: entry.kind === "VALID_NEGATIVE_FINDING" });
+ }
+ return found;
+}
+
+/**
+ * Facts the reviewer confirmed against the supplied documents: what a document states, and what was looked for and
+ * not found. Taken from the stored result field only; results saved before the field existed say so.
+ */
+export function ConfirmedFacts({ result, onCite, heading = "확인된 사실", onlyWhenPresent = false }: {
+ result: Record;
+ onCite: (spanId: string, numbers: Record) => void;
+ /** Wording for the heading, e.g. when the research stopped early. */
+ heading?: string;
+ /** Render nothing unless there is at least one stored fact. */
+ onlyWhenPresent?: boolean;
+}) {
+ const stored = Object.hasOwn(result, "confirmed_findings");
+ const findings = readFindings(result.confirmed_findings);
+ const dropped = typeof result.confirmed_findings_dropped === "number" ? result.confirmed_findings_dropped : 0;
+ const answer = typeof result.answer === "string" ? result.answer : "";
+ const numbers = numberSources(answer, findings.flatMap(item => item.refs));
+ const numberMap = Object.fromEntries(numbers);
+ if (onlyWhenPresent && findings.length === 0) return null;
+ return
+ {heading}{findings.length > 0 && · 문서에 적힌 내용이며 기준 충족을 뜻하지 않습니다}
+ {!stored ? 이 결과에는 정리된 확인 사실이 없습니다(이전 형식).
+ : findings.length === 0 ? 정리된 확인 사실 없음
+ : {findings.map((item, index) => -
+ {item.negative && 찾아봤지만 없음} {item.statement}{" "}
+ {item.refs.map(ref => )}
+
)}
}
+ {dropped > 0 && 근거를 확인하지 못해 제외된 항목 {dropped}개}
+ ;
+}
+
diff --git a/apps/web/src/components/ConversationTimeline.tsx b/apps/web/src/components/ConversationTimeline.tsx
index a073585..396f00c 100644
--- a/apps/web/src/components/ConversationTimeline.tsx
+++ b/apps/web/src/components/ConversationTimeline.tsx
@@ -13,11 +13,17 @@ import {
} from "../api/timelineExample";
import { ResearchResultCard, type ResearchDetail } from "./ResearchResultCard";
import { ResearchLiveProgress } from "./ResearchLiveProgress";
+import { resultUsageLine } from "./resultUsage";
-export function ConversationTimeline({ projectId, threadId, status, sourceUris, onDetail, onContinue, onOpenHistory }: {
- projectId: string; threadId: string; status?: ResearchStatus; sourceUris?: string[];
+export function ConversationTimeline({ projectId, threadId, status, sourceUris, onDetail, onContinue, onOpenHistory, onRetry, onResume, resuming = false, queuedInstruction = false }: {
+ projectId: string; threadId: string; status?: ResearchStatus; sourceUris?: string[]; queuedInstruction?: boolean;
onDetail: (detail: ResearchDetail) => void;
onContinue?: () => void;
+ /** Put the question back in the composer for another run; nothing is sent until the user sends it. */
+ onRetry?: (question: string) => void;
+ /** Continue the latest interrupted run from where it stopped (the user pressed the button; it starts at once). */
+ onResume?: (operationId: string) => void;
+ resuming?: boolean;
onOpenHistory?: () => void;
}) {
const example = isTimelineExampleRequested();
@@ -52,6 +58,8 @@ export function ConversationTimeline({ projectId, threadId, status, sourceUris,
: {};
const currentQuestion = example || turns.length === 0 ? liveStatus?.request?.authored_text : undefined;
const running = liveStatus?.operation_state === "RUNNING";
+ const finishedStages = (liveStatus?.completed_stages ?? []).filter(stage => stage.state === "COMPLETED").length;
+ const resumeOperation = liveStatus?.request?.operation_id;
const emptyExistingThread = Boolean(threadId && query.data && turns.length === 0 && !currentQuestion && !running);
return
{query.hasNextPage && }
@@ -59,7 +67,7 @@ export function ConversationTimeline({ projectId, threadId, status, sourceUris,
{query.data?.pages.some(page => page.limited) && 조회 범위를 넘는 과거 이력이 있습니다. 기록에서 확인할 수 있습니다.}
{turns.map(turn =>
질문 {turn.input.edit_kind === "REPLACE" ? "수정" : turn.input.edit_kind === "STEER" ? "· 방향 변경" : ""}{turn.input.text}
-
onRetry(turn.input.text) : undefined} usageLine={resultUsageLine(liveStatus, turn.input.operation_id)}
currentness={turn.currentness} historySelection={{kind:"result",scope:{projectId,threadId,requestDigest:turn.input.request_revision_digest},operationId:turn.input.operation_id,
resultDigest:currentResultDigest(status,{projectId,threadId,requestDigest:turn.input.request_revision_digest},turn.input.operation_id)}} {...followupFor(turn.input.request_revision_digest)} />
)}
@@ -67,7 +75,9 @@ export function ConversationTimeline({ projectId, threadId, status, sourceUris,
{(currentQuestion || running) &&
{currentQuestion &&
질문{example ? " · 예시" : ""}{example ? : null}{currentQuestion}
}
- {!running &&
현재 조사가 끝나면 이 지시를 이어서 반영합니다}
+ {!running && onRetry(liveStatus.request!.authored_text!) : undefined}
+ onResume={onResume && resumeOperation ? () => onResume(resumeOperation) : undefined} resumeCompleted={finishedStages} resuming={resuming} usageLine={resultUsageLine(liveStatus, currentRequestResult?.operation_id)}
currentness={currentRequestResult ? liveStatus?.basis_currentness : undefined} historySelection={currentRequestResult?.request_ref?.revision_digest ? {kind:"result",scope:{projectId,threadId,requestDigest:currentRequestResult.request_ref.revision_digest},operationId:currentRequestResult.operation_id,
resultDigest:currentResultDigest(liveStatus,{projectId,threadId,requestDigest:currentRequestResult.request_ref.revision_digest},currentRequestResult.operation_id)} : undefined} {...followupFor(currentRequestResult?.request_ref?.revision_digest)}/>}
}
diff --git a/apps/web/src/components/EvidenceRail.tsx b/apps/web/src/components/EvidenceRail.tsx
index e4ed69a..6188be0 100644
--- a/apps/web/src/components/EvidenceRail.tsx
+++ b/apps/web/src/components/EvidenceRail.tsx
@@ -1,8 +1,9 @@
-import { useMemo, useState } from "react";
+import { useEffect, useMemo, useRef, useState } from "react";
import { Callout, HTMLSelect, InputGroup } from "@blueprintjs/core";
import type { EvidenceSpan } from "../types";
import { StatusBadge } from "./StatusBadge";
+import { eyebrowLabel } from "./statusLabels";
function locator(span: EvidenceSpan) {
const values = [
@@ -22,7 +23,11 @@ export function EvidenceRail({
loading = false,
error,
heading,
+ focusSpanId,
+ citations,
}: {
+ focusSpanId?: string;
+ citations?: Record;
evidence: EvidenceSpan[];
totalCount?: number;
loading?: boolean;
@@ -31,22 +36,30 @@ export function EvidenceRail({
}) {
const [query, setQuery] = useState("");
const [authority, setAuthority] = useState("ALL");
+ const listRef = useRef(null);
const filtered = useMemo(
() =>
evidence.filter(
(span) =>
+ span.span_id === focusSpanId ||
(authority === "ALL" || span.authority_state === authority) &&
(!query.trim() ||
span.exact_text.toLowerCase().includes(query.trim().toLowerCase()) ||
span.span_id.toLowerCase().includes(query.trim().toLowerCase())),
),
- [authority, evidence, query],
+ [authority, evidence, query, focusSpanId],
);
+ useEffect(() => {
+ if (!focusSpanId) return;
+ const card = Array.from(listRef.current?.querySelectorAll("[data-span-id]") ?? [])
+ .find(item => item.getAttribute("data-span-id") === focusSpanId);
+ card?.scrollIntoView?.({ block: "center", behavior: "smooth" });
+ }, [focusSpanId, evidence.length]);
return (