From e9d50e6a32a84000bae24bc2c11b981999376170 Mon Sep 17 00:00:00 2001 From: devmrko Date: Mon, 3 Aug 2026 10:20:36 +0900 Subject: [PATCH] refs #741 #742: reorganize repository by application --- .env.example | 8 +- .gitignore | 6 + README.md | 52 +- ai-web-agent-console/.env.example | 48 + ai-web-agent-console/README.md | 57 + .../ai_web_agent_console}/__init__.py | 0 .../ai_web_agent_console}/audit.py | 0 .../ai_web_agent_console}/auth_gateway.py | 32 +- .../ai_web_agent_console}/mcp_result.py | 0 .../ai_web_agent_console}/mcp_tool_router.py | 4 +- .../ai_web_agent_console}/model_registry.py | 84 +- .../ai_web_agent_console}/oci_genai_sdk.py | 0 .../ai_web_agent_console}/presentation.py | 0 .../ai_web_agent_console}/profile.py | 0 .../ai_web_agent_console/query_contracts.py | 407 +++++ .../ai_web_agent_console}/questions.py | 6 +- .../ai_web_agent_console}/scenarios.py | 0 .../app.py | 1586 ++--------------- .../config/app_profile.json | 0 .../config/hmm_demo_scenarios.json | 0 .../config/hmm_hr_query_contracts.json | 84 + .../config/mcp_servers.json | 2 +- .../config/model_profiles.json | 12 +- .../config/vpd_token_presets.json | 0 .../requirements-langgraph.txt | 0 .../requirements.txt | 0 .../tests/test_auth_gateway.py | 2 +- .../tests/test_query_contracts.py | 181 ++ .../tests/test_scenarios.py | 37 +- {sql => database}/adb/00_cleanup.sql | 0 {sql => database}/adb/01_dblinks.sql | 0 {sql => database}/adb/02_perm_tables.sql | 0 {sql => database}/adb/03_seed.sql | 2 +- {sql => database}/adb/04_secure_ctx.sql | 0 {sql => database}/adb/05_views.sql | 0 {sql => database}/adb/06_policy.sql | 0 {sql => database}/adb/06a_redaction.sql | 0 {sql => database}/adb/07_end_users.sql | 0 {sql => database}/adb/08_tests_user_my.sql | 0 {sql => database}/adb/09_tests_user_pg.sql | 0 {sql => database}/adb/10_tests_user_both.sql | 0 {sql => database}/adb/11_tests_user_none.sql | 0 .../adb/12_tests_admin_audit.sql | 0 {sql => database}/adb/13_dds_variant.sql | 0 {sql => database}/adb/14_tests_dds_user.sql | 0 {sql => database}/adb/15_dds_cleanup.sql | 0 .../16_agent_ords_security_local_cleanup.sql | 0 ...17_agent_ords_security_local_vpd_setup.sql | 0 .../18_agent_ords_security_local_vpd_test.sql | 0 ...19_agent_ords_security_local_dds_setup.sql | 0 .../20_agent_ords_security_local_dds_test.sql | 0 ...agent_ords_security_ords_enable_schema.sql | 0 ...agent_ords_security_ords_handler_setup.sql | 0 ..._agent_ords_security_ords_handler_test.sql | 0 .../adb/24_agent_ords_security_inventory.sql | 0 ...agent_ords_security_backoffice_support.sql | 0 ...agent_ords_security_dynamic_vpd_filter.sql | 0 ..._ords_security_dynamic_vpd_filter_test.sql | 2 +- .../28_agent_ords_vector_tag_vpd_setup.sql | 0 .../adb/29_agent_ords_vector_search_ords.sql | 0 .../adb/30_agent_ords_vector_tag_vpd_test.sql | 0 .../31_agent_ords_backoffice_ux_metadata.sql | 0 .../adb/31_dds_standalone_demo_setup.sql | 0 .../adb/32_dds_vector_tag_setup.sql | 0 .../adb/33_agent_ords_sql_trace_grant.sql | 0 ...34_agent_ords_execution_evidence_grant.sql | 0 .../34_dds_token_data_grant_common_auth.sql | 0 .../adb/35_dds_token_data_grant_cleanup.sql | 0 .../adb/36_dds_sales_knowledge_scenario.sql | 0 ...7_dds_sales_knowledge_scenario_cleanup.sql | 0 ...ds_vector_all_permission_normalization.sql | 0 ...9_agent_ords_default_user_display_name.sql | 0 .../adb/40_dds_protection_evidence.sql | 0 .../adb/41_dds_row_column_fixture.sql | 0 .../adb/42_agent_ords_fga_execution_audit.sql | 0 .../adb/42_dds_fga_execution_evidence.sql | 0 .../43_agent_ords_probe_id_handler_patch.sql | 0 .../adb/44_dds_mcp_local_end_user_setup.sql | 0 .../adb/45_dds_mcp_local_end_user_test.sql | 0 ...b_stakeholder_token_permission_example.sql | 0 ..._kb_stakeholder_permission_or_branches.sql | 0 .../adb/48_kb_permission_condition_codes.sql | 0 .../49_permission_static_sql_condition.sql | 0 .../adb/51_kb_vpd_admin_token.sql | 0 .../adb/52_cleanup_to_kb_vpd_poc.sql | 0 .../adb/53_drop_legacy_poc_objects.sql | 0 .../adb/54_drop_legacy_dds_ords_examples.sql | 0 .../55_reapply_kb_vpd_after_table_reload.sql | 0 .../adb/56_kb_select_ai_router_team.sql | 0 .../adb/57_kb_select_ai_router_team_test.sql | 0 .../adb/58_kb_select_ai_router_api.sql | 0 .../adb/59_kb_select_ai_router_ords.sql | 0 .../adb/60_kb_vpd_admin_full_access_token.sql | 0 .../62_kb_aso_masking_backoffice_metadata.sql | 0 .../adb/63_kb_aso_masking_rule_runtime.sql | 0 ...64_kb_aso_masking_default_column_rules.sql | 0 .../adb/65_kb_select_ai_vpd_query_api.sql | 0 .../adb/66_kb_select_ai_vpd_query_ords.sql | 0 ...cy_kb_contracts_premium_vpd_cls_policy.sql | 0 {sql => database}/adb/70_sg_tool_user.sql | 0 .../adb/71_sg_identity_administration.sql | 0 .../adb/72_hmm_leave_team_vpd.sql | 0 .../adb/73_hmm_mcp_vpd_runtime.sql | 0 .../adb/73_hmm_rds_pg_00_precheck.sql | 0 .../adb/74_hmm_rds_pg_01_credential.sql | 0 .../adb/75_hmm_rds_pg_02_acl.sql | 0 .../adb/76_hmm_rds_pg_03_dblink.sql | 0 .../adb/77_hmm_rds_pg_04_remote_query.sql | 0 .../adb/78_hmm_rds_pg_05_views.sql | 0 .../adb/79_hmm_rds_pg_06_verify.sql | 0 .../adb/80_hmm_rds_pg_99_cleanup.sql | 0 .../adb/81_hmm_carrier_access_groups.sql | 0 ...2_hmm_federation_relationship_comments.sql | 0 {sql => database}/source/mysql_setup.sql | 4 +- .../postgres_hmm_carrier_performance.sql | 4 +- ...ostgres_hmm_carrier_performance_verify.sql | 0 {sql => database}/source/postgres_setup.sql | 4 +- dds-backoffice/README.md | 8 +- .../nginx/hmm-portal-auth.locations.conf | 0 .../systemd/ai-web-agent-console-audit.conf | 0 .../systemd/hmm-portal-auth.service | 2 +- .../backoffice.env.example} | 0 .../caddy/Caddyfile.template | 0 .../caddy/install-caddy-config.sh | 0 .../vpd-backoffice-structured-data.conf | 0 docs/03-detailed-guide.md | 4 +- docs/04-source-setup.md | 10 +- docs/05-dds-variant.md | 6 +- docs/06-agent-ords-vpd-dds-security-brief.md | 20 +- .../424-spring-boot-vpd-backoffice/README.md | 12 +- .../456-vpd-predicate-rule-tests/README.md | 6 +- .../fn-cb_agent_doc_vpd_filter.md | 2 +- .../462-predicate-injection-defense/README.md | 6 +- docs/design/547-vm-https-hardening/README.md | 4 +- .../559-independent-vpd-dds-demos/README.md | 2 +- docs/design/567-ords-vpd-sql-trace/README.md | 4 +- .../fn-addLocalSqlTrace.md | 2 +- .../fn-findVpdPredicate.md | 2 +- .../617-dds-mcp-end-user-context/README.md | 4 +- .../README.md | 51 +- .../701-hmm-ai-agent-modularization/README.md | 19 +- .../702-hmm-backoffice-identity/README.md | 2 +- docs/design/707-hmm-leave-team-vpd/README.md | 2 +- .../723-hmm-configurable-backoffice/README.md | 2 +- .../README.md | 4 +- .../README.md | 112 ++ docs/notes/2026-06-08-dds-qa-session.md | 6 +- .../notes/hmm-brand-refresh.md | 3 +- docs/reference/ords-vpd-sql-trace.md | 4 +- ...026-07-10-existing-01-mcp-rag-diagnosis.md | 2 +- ...10-select-ai-profile-performance-report.md | 8 +- ...6-07-23-hmm-leave-team-vpd-verification.md | 2 +- ...026-07-23-hmm-portal-full-browser-audit.md | 2 +- docs/runbooks/460-sqlcl-vpd-deploy-runbook.md | 6 +- docs/runbooks/hmm-mcp-token-configuration.md | 2 +- poc4_active_source_20260714/.env.sample | 131 -- poc4_active_source_20260714/SOURCE_README.md | 65 - .../poc4_active_source_20260714.tar.gz | Bin 74102 -> 0 bytes .../poc4/start_8622_langgraph_tc_ui_nohup.sh | 4 - .../poc4/status_8622_langgraph_tc_ui_nohup.sh | 4 - .../src/poc4/__init__.py | 1 - run.sh | 50 +- scripts/configure-backoffice-https-vm.sh | 4 +- scripts/deploy-backoffice-vm.sh | 8 +- scripts/deploy-dds-backoffice-local.sh | 6 +- scripts/provision-dds-token-search.sh | 4 +- .../run-dds-mcp-local-end-user-scenario.sh | 4 +- scripts/run_agent_ords_security_adb_local.sh | 24 +- scripts/setup-dds-protection-evidence.sh | 2 +- scripts/setup-dds-token-data-grant.sh | 2 +- tests/README.md | 11 + tests/test_repository_layout.py | 58 + pom.xml => vpd-backoffice/pom.xml | 6 +- .../VpdBackofficeApplication.java | 0 .../vpdbackoffice/config/AppConfig.java | 0 .../config/BackofficeProperties.java | 3 +- .../config/CatalogProperties.java | 0 .../vpdbackoffice/config/DbPoolWarmup.java | 0 .../config/MaskingProperties.java | 0 .../vpdbackoffice/config/McpProperties.java | 0 .../config/OrdsClientConfig.java | 0 .../config/ProductProperties.java | 0 .../vpdbackoffice/config/SecurityConfig.java | 0 .../config/SecuritySqlScriptProperties.java | 0 .../domain/audit/AuditEvent.java | 0 .../domain/effective/EffectiveMatrixView.java | 0 .../effective/GroupEffectiveAccessView.java | 0 .../effective/RoleEffectiveImpactView.java | 0 .../effective/UserEffectiveAccessView.java | 0 .../vpdbackoffice/domain/group/AppGroup.java | 0 .../domain/group/GroupCreateCommand.java | 0 .../domain/group/GroupRoleView.java | 0 .../domain/group/GroupUserView.java | 0 .../domain/masking/ColumnMaskingRule.java | 0 .../domain/masking/MaskingPolicyStatus.java | 0 .../domain/masking/MaskingRule.java | 0 .../masking/MaskingRuleCreateCommand.java | 0 .../domain/masking/MaskingTemplate.java | 0 .../domain/masking/UserMaskingRule.java | 0 .../domain/mcp/McpChatbotResult.java | 0 .../domain/mcp/McpClientDemoResult.java | 0 .../domain/mcp/McpReasoningCommand.java | 0 .../domain/mcp/McpReasoningResult.java | 0 .../vpdbackoffice/domain/mcp/McpToolView.java | 0 .../operation/OperationHealthSummary.java | 0 .../domain/operation/OperationStatusRow.java | 0 .../domain/ords/OrdsHandlerUpdateCommand.java | 0 .../domain/ords/OrdsHandlerView.java | 0 .../domain/ords/OrdsObjectHandlerResult.java | 0 .../domain/permission/AppRole.java | 0 .../domain/permission/PermissionRule.java | 0 .../domain/permission/PermissionSet.java | 0 .../permission/PermissionSetCommand.java | 0 .../domain/permission/PermissionView.java | 0 .../domain/permission/RuleCommand.java | 0 .../domain/probe/FgaExecutionEvidence.java | 0 .../domain/probe/ProbeCommand.java | 0 .../domain/probe/ProbeResult.java | 0 .../domain/probe/ProbeStatus.java | 0 .../domain/probe/SqlExecutionCandidate.java | 0 .../domain/probe/SqlExecutionEvidence.java | 0 .../protectedobject/DatabaseObjectOption.java | 0 .../protectedobject/ProtectedColumn.java | 0 .../protectedobject/ProtectedObject.java | 0 .../ProtectedObjectCreateCommand.java | 0 .../domain/schema/SchemaActionResult.java | 0 .../domain/schema/SchemaPreflightCheck.java | 0 .../domain/schema/SchemaPreflightView.java | 0 .../schemametadata/SchemaAnnotation.java | 0 .../schemametadata/SchemaMetadataColumn.java | 0 .../schemametadata/SchemaMetadataView.java | 0 .../securityscript/SecuritySqlScript.java | 0 .../SecuritySqlScriptExplanation.java | 0 .../SecuritySqlScriptSummary.java | 0 .../domain/setting/BackofficeSetting.java | 0 .../stakeholder/StakeholderTokenSubject.java | 0 .../structured/StructuredDataPreview.java | 0 .../structured/StructuredDataTable.java | 0 .../domain/token/BearerTokenRecord.java | 0 .../domain/token/IssuedToken.java | 0 .../domain/token/TokenContextView.java | 0 .../domain/token/TokenIssueCommand.java | 0 .../vpdbackoffice/domain/user/AppUser.java | 0 .../domain/user/UserCreateCommand.java | 0 .../domain/user/UserRoleView.java | 0 .../domain/vector/VectorChunk.java | 0 .../domain/vector/VectorIngestCommand.java | 0 .../domain/vector/VectorIngestResult.java | 0 .../domain/vector/VectorKnowledgeSummary.java | 0 .../domain/vector/VectorQueryEmbedding.java | 0 .../domain/vector/VectorSearchResult.java | 0 .../domain/vpd/VpdBulkApplyResult.java | 0 .../domain/vpd/VpdDescriptionNote.java | 0 .../domain/vpd/VpdFunctionOption.java | 0 .../domain/vpd/VpdFunctionSource.java | 0 .../domain/vpd/VpdObjectFilterDetail.java | 0 .../domain/vpd/VpdPolicyCreateCommand.java | 0 .../domain/vpd/VpdPolicyDetail.java | 0 .../domain/vpd/VpdPolicyExplanation.java | 0 .../domain/vpd/VpdPolicyFormOptions.java | 0 .../domain/vpd/VpdPolicyTemplateOption.java | 0 .../domain/vpd/VpdPolicyView.java | 0 .../domain/vpd/VpdSchemaObjectOption.java | 0 .../domain/vpd/VpdTargetView.java | 0 .../vpdbackoffice/mapper/AuditMapper.java | 0 .../mapper/BearerTokenMapper.java | 0 .../vpdbackoffice/mapper/GroupMapper.java | 0 .../mapper/MaskingRuleMapper.java | 0 .../mapper/OperationStatusMapper.java | 0 .../mapper/OrdsMetadataMapper.java | 0 .../mapper/PermissionMapper.java | 0 .../mapper/ProtectedObjectMapper.java | 0 .../vpdbackoffice/mapper/SettingMapper.java | 0 .../mapper/StakeholderMapper.java | 0 .../vpdbackoffice/mapper/UserMapper.java | 0 .../vpdbackoffice/mapper/VpdPolicyMapper.java | 0 .../vpdbackoffice/service/AppException.java | 0 .../vpdbackoffice/service/AuditService.java | 0 .../service/BackofficeSchemaService.java | 4 +- .../service/BearerTokenService.java | 0 .../vpdbackoffice/service/DataCatalog.java | 0 .../service/EffectiveMatrixService.java | 0 .../service/EnvironmentDataCatalog.java | 0 .../EnvironmentMaskingPolicyCatalog.java | 0 .../service/EnvironmentMcpToolCatalog.java | 0 .../ExternalAuthorizationChangeNotifier.java | 0 .../ExternalAuthorizationSynchronizer.java | 0 .../vpdbackoffice/service/GroupService.java | 0 .../service/HmmAiAgentToolRunner.java | 0 .../HmmKnowledgeSchemaInitializer.java | 0 .../service/HmmMcpBearerAuthenticator.java | 0 .../service/HmmMcpPrincipal.java | 0 .../HmmRuntimeSettingsInitializer.java | 0 .../service/JdbcHmmAiAgentToolRunner.java | 0 .../JdbcHmmMcpBearerAuthenticator.java | 0 .../service/MaskingPolicyCatalog.java | 0 .../service/MaskingPolicySynchronizer.java | 0 .../service/MaskingPolicyTarget.java | 0 .../service/MaskingRuleService.java | 0 .../service/McpChatbotService.java | 0 .../service/McpClientDemoService.java | 0 .../service/McpReasoningService.java | 0 .../vpdbackoffice/service/McpSseService.java | 0 .../vpdbackoffice/service/McpToolCatalog.java | 0 .../service/McpToolDefinition.java | 0 .../service/McpToolRegistry.java | 0 .../service/McpUnauthorizedException.java | 0 .../service/OciGenerativeAiChatClient.java | 0 .../service/OpenAiCompatibleClient.java | 0 .../service/OperationStatusService.java | 0 .../service/OrdsMetadataService.java | 0 .../service/OrdsProbeService.java | 0 .../service/PermissionService.java | 0 .../service/ProbeErrorClassifier.java | 0 .../service/ProtectedObjectService.java | 0 .../service/SchemaMetadataService.java | 0 .../SecuritySqlScriptExplanationService.java | 0 .../service/SecuritySqlScriptService.java | 4 +- .../service/SelectAiAgentOrdsService.java | 0 .../service/SelectAiService.java | 50 +- .../vpdbackoffice/service/SettingService.java | 0 .../service/StructuredDataService.java | 0 .../vpdbackoffice/service/TokenGenerator.java | 0 .../vpdbackoffice/service/TokenHasher.java | 0 .../vpdbackoffice/service/UserService.java | 0 .../vpdbackoffice/service/VectorChunker.java | 0 .../service/VectorKnowledgeService.java | 0 .../service/VpdPolicyService.java | 0 .../VpdTokenAccessDeniedException.java | 0 .../web/AppExceptionHandler.java | 0 .../web/DashboardController.java | 0 .../web/EffectiveMatrixController.java | 0 .../vpdbackoffice/web/GroupController.java | 0 .../vpdbackoffice/web/LoginController.java | 0 .../web/MaskingRuleController.java | 0 .../web/McpChatbotController.java | 0 .../web/McpClientDemoController.java | 0 .../web/McpReasoningController.java | 0 .../vpdbackoffice/web/McpSseController.java | 0 .../web/OperationStatusController.java | 0 .../web/OrdsHandlerController.java | 0 .../web/PermissionController.java | 0 .../vpdbackoffice/web/ProbeController.java | 0 .../vpdbackoffice/web/ProductModelAdvice.java | 0 .../web/ProtectedObjectController.java | 0 .../vpdbackoffice/web/RoleController.java | 0 .../web/RuntimeErrorMessage.java | 0 .../web/RuntimeErrorMessages.java | 0 .../web/SchemaMetadataController.java | 0 .../web/SecurityModelAdvice.java | 0 .../web/SecuritySqlScriptController.java | 0 .../vpdbackoffice/web/SettingController.java | 0 .../web/StructuredDataController.java | 0 .../vpdbackoffice/web/TokenController.java | 0 .../vpdbackoffice/web/UserController.java | 0 .../web/UserMaskingRuleController.java | 0 .../web/VectorKnowledgeController.java | 0 .../web/VpdPolicyController.java | 0 .../src}/main/resources/application.yml | 2 + .../main/resources/mapper/AuditMapper.xml | 0 .../resources/mapper/BearerTokenMapper.xml | 0 .../main/resources/mapper/GroupMapper.xml | 0 .../resources/mapper/MaskingRuleMapper.xml | 0 .../mapper/OperationStatusMapper.xml | 0 .../resources/mapper/OrdsMetadataMapper.xml | 0 .../resources/mapper/PermissionMapper.xml | 0 .../mapper/ProtectedObjectMapper.xml | 0 .../main/resources/mapper/SettingMapper.xml | 0 .../resources/mapper/StakeholderMapper.xml | 0 .../src}/main/resources/mapper/UserMapper.xml | 0 .../main/resources/mapper/VpdPolicyMapper.xml | 0 .../src}/main/resources/static/css/app.css | 0 .../src}/main/resources/static/js/app.js | 0 .../main/resources/templates/dashboard.html | 0 .../resources/templates/effective-matrix.html | 0 .../src}/main/resources/templates/error.html | 0 .../resources/templates/fragments/layout.html | 0 .../fragments/mcp-chatbot-result.html | 0 .../fragments/mcp-client-demo-result.html | 0 .../fragments/mcp-reasoning-result.html | 0 .../fragments/ords-handler-source.html | 0 .../templates/fragments/probe-result.html | 0 .../security-sql-script-explanation.html | 0 .../fragments/vector-search-result.html | 0 .../fragments/vpd-function-source.html | 0 .../fragments/vpd-object-filter-detail.html | 0 .../fragments/vpd-policy-detail.html | 0 .../fragments/vpd-policy-explanation.html | 0 .../src}/main/resources/templates/groups.html | 0 .../src}/main/resources/templates/login.html | 0 .../resources/templates/masking-rules.html | 0 .../main/resources/templates/mcp-chatbot.html | 0 .../resources/templates/mcp-client-demo.html | 0 .../resources/templates/mcp-reasoning.html | 0 .../main/resources/templates/mcp-sse.html | 0 .../main/resources/templates/objects.html | 0 .../resources/templates/operation-status.html | 0 .../resources/templates/ords-handlers.html | 0 .../main/resources/templates/permissions.html | 0 .../src}/main/resources/templates/probe.html | 0 .../src}/main/resources/templates/roles.html | 0 .../resources/templates/schema-metadata.html | 0 .../templates/security-sql-scripts.html | 2 +- .../templates/settings-database.html | 2 +- .../main/resources/templates/settings.html | 0 .../resources/templates/structured-data.html | 0 .../src}/main/resources/templates/tokens.html | 0 .../templates/user-masking-rules.html | 0 .../src}/main/resources/templates/users.html | 0 .../resources/templates/vector-knowledge.html | 0 .../templates/vpd-filter-policies.html | 0 .../templates/vpd-filter-runtime.html | 0 .../resources/templates/vpd-policies.html | 0 .../config/TransportSecurityTest.java | 0 .../domain/masking/MaskingTemplateTest.java | 0 .../domain/permission/PermissionViewTest.java | 0 .../domain/probe/ProbeResultTest.java | 0 .../protectedobject/ProtectedColumnTest.java | 0 .../domain/vpd/VpdPolicyFormOptionsTest.java | 0 .../service/EffectiveMatrixServiceTest.java | 0 ...ternalAuthorizationChangeNotifierTest.java | 0 .../HmmRuntimeSettingsInitializerTest.java | 0 .../service/JdbcHmmAiAgentToolRunnerTest.java | 0 .../MaskingPolicySynchronizerTest.java | 0 .../service/McpSseServiceTest.java | 0 .../service/OrdsMetadataServiceTest.java | 0 .../service/OrdsProbeServiceTest.java | 0 .../service/PermissionServiceTest.java | 0 .../service/ProbeErrorClassifierTest.java | 0 .../service/SchemaMetadataServiceTest.java | 0 .../service/SecuritySqlScriptServiceTest.java | 0 .../service/SelectAiServiceTest.java | 0 .../service/StructuredDataServiceTest.java | 0 .../service/TokenGeneratorTest.java | 0 .../service/TokenHasherTest.java | 0 .../service/VectorChunkerTest.java | 0 .../service/VectorKnowledgeServiceTest.java | 0 .../service/VpdPolicyServiceTest.java | 0 .../EffectiveMatrixTemplateRenderTest.java | 0 .../web/GuidedFlowTemplateTest.java | 12 +- .../web/MaskingRuleTemplateRenderTest.java | 0 .../web/PermissionTemplateRenderTest.java | 0 .../web/ProbeResultTemplateRenderTest.java | 0 .../web/SettingsTemplateRenderTest.java | 0 .../org.mockito.plugins.MockMaker | 0 445 files changed, 1523 insertions(+), 1885 deletions(-) create mode 100644 ai-web-agent-console/.env.example create mode 100644 ai-web-agent-console/README.md rename {poc4_active_source_20260714/src/agent_console => ai-web-agent-console/ai_web_agent_console}/__init__.py (100%) rename {poc4_active_source_20260714/src/agent_console => ai-web-agent-console/ai_web_agent_console}/audit.py (100%) rename {poc4_active_source_20260714/src/agent_console => ai-web-agent-console/ai_web_agent_console}/auth_gateway.py (95%) rename {poc4_active_source_20260714/src => ai-web-agent-console/ai_web_agent_console}/mcp_result.py (100%) rename {poc4_active_source_20260714/src => ai-web-agent-console/ai_web_agent_console}/mcp_tool_router.py (98%) rename {poc4_active_source_20260714/src/poc3 => ai-web-agent-console/ai_web_agent_console}/model_registry.py (83%) rename {poc4_active_source_20260714/src => ai-web-agent-console/ai_web_agent_console}/oci_genai_sdk.py (100%) rename {poc4_active_source_20260714/src/agent_console => ai-web-agent-console/ai_web_agent_console}/presentation.py (100%) rename {poc4_active_source_20260714/src/agent_console => ai-web-agent-console/ai_web_agent_console}/profile.py (100%) create mode 100644 ai-web-agent-console/ai_web_agent_console/query_contracts.py rename {poc4_active_source_20260714/src/poc3 => ai-web-agent-console/ai_web_agent_console}/questions.py (97%) rename {poc4_active_source_20260714/src/poc4 => ai-web-agent-console/ai_web_agent_console}/scenarios.py (100%) rename poc4_active_source_20260714/apps/poc4/mcp_discovery_ui.py => ai-web-agent-console/app.py (87%) rename {poc4_active_source_20260714 => ai-web-agent-console}/config/app_profile.json (100%) rename {poc4_active_source_20260714 => ai-web-agent-console}/config/hmm_demo_scenarios.json (100%) create mode 100644 ai-web-agent-console/config/hmm_hr_query_contracts.json rename {poc4_active_source_20260714 => ai-web-agent-console}/config/mcp_servers.json (89%) rename poc4_active_source_20260714/config/poc3_model_profiles.json => ai-web-agent-console/config/model_profiles.json (94%) rename {poc4_active_source_20260714 => ai-web-agent-console}/config/vpd_token_presets.json (100%) rename {poc4_active_source_20260714 => ai-web-agent-console}/requirements-langgraph.txt (100%) rename {poc4_active_source_20260714 => ai-web-agent-console}/requirements.txt (100%) rename {poc4_active_source_20260714 => ai-web-agent-console}/tests/test_auth_gateway.py (99%) create mode 100644 ai-web-agent-console/tests/test_query_contracts.py rename {poc4_active_source_20260714 => ai-web-agent-console}/tests/test_scenarios.py (86%) rename {sql => database}/adb/00_cleanup.sql (100%) rename {sql => database}/adb/01_dblinks.sql (100%) rename {sql => database}/adb/02_perm_tables.sql (100%) rename {sql => database}/adb/03_seed.sql (98%) rename {sql => database}/adb/04_secure_ctx.sql (100%) rename {sql => database}/adb/05_views.sql (100%) rename {sql => database}/adb/06_policy.sql (100%) rename {sql => database}/adb/06a_redaction.sql (100%) rename {sql => database}/adb/07_end_users.sql (100%) rename {sql => database}/adb/08_tests_user_my.sql (100%) rename {sql => database}/adb/09_tests_user_pg.sql (100%) rename {sql => database}/adb/10_tests_user_both.sql (100%) rename {sql => database}/adb/11_tests_user_none.sql (100%) rename {sql => database}/adb/12_tests_admin_audit.sql (100%) rename {sql => database}/adb/13_dds_variant.sql (100%) rename {sql => database}/adb/14_tests_dds_user.sql (100%) rename {sql => database}/adb/15_dds_cleanup.sql (100%) rename {sql => database}/adb/16_agent_ords_security_local_cleanup.sql (100%) rename {sql => database}/adb/17_agent_ords_security_local_vpd_setup.sql (100%) rename {sql => database}/adb/18_agent_ords_security_local_vpd_test.sql (100%) rename {sql => database}/adb/19_agent_ords_security_local_dds_setup.sql (100%) rename {sql => database}/adb/20_agent_ords_security_local_dds_test.sql (100%) rename {sql => database}/adb/21_agent_ords_security_ords_enable_schema.sql (100%) rename {sql => database}/adb/22_agent_ords_security_ords_handler_setup.sql (100%) rename {sql => database}/adb/23_agent_ords_security_ords_handler_test.sql (100%) rename {sql => database}/adb/24_agent_ords_security_inventory.sql (100%) rename {sql => database}/adb/25_agent_ords_security_backoffice_support.sql (100%) rename {sql => database}/adb/26_agent_ords_security_dynamic_vpd_filter.sql (100%) rename {sql => database}/adb/27_agent_ords_security_dynamic_vpd_filter_test.sql (99%) rename {sql => database}/adb/28_agent_ords_vector_tag_vpd_setup.sql (100%) rename {sql => database}/adb/29_agent_ords_vector_search_ords.sql (100%) rename {sql => database}/adb/30_agent_ords_vector_tag_vpd_test.sql (100%) rename {sql => database}/adb/31_agent_ords_backoffice_ux_metadata.sql (100%) rename {sql => database}/adb/31_dds_standalone_demo_setup.sql (100%) rename {sql => database}/adb/32_dds_vector_tag_setup.sql (100%) rename {sql => database}/adb/33_agent_ords_sql_trace_grant.sql (100%) rename {sql => database}/adb/34_agent_ords_execution_evidence_grant.sql (100%) rename {sql => database}/adb/34_dds_token_data_grant_common_auth.sql (100%) rename {sql => database}/adb/35_dds_token_data_grant_cleanup.sql (100%) rename {sql => database}/adb/36_dds_sales_knowledge_scenario.sql (100%) rename {sql => database}/adb/37_dds_sales_knowledge_scenario_cleanup.sql (100%) rename {sql => database}/adb/38_agent_ords_vector_all_permission_normalization.sql (100%) rename {sql => database}/adb/39_agent_ords_default_user_display_name.sql (100%) rename {sql => database}/adb/40_dds_protection_evidence.sql (100%) rename {sql => database}/adb/41_dds_row_column_fixture.sql (100%) rename {sql => database}/adb/42_agent_ords_fga_execution_audit.sql (100%) rename {sql => database}/adb/42_dds_fga_execution_evidence.sql (100%) rename {sql => database}/adb/43_agent_ords_probe_id_handler_patch.sql (100%) rename {sql => database}/adb/44_dds_mcp_local_end_user_setup.sql (100%) rename {sql => database}/adb/45_dds_mcp_local_end_user_test.sql (100%) rename {sql => database}/adb/46_kb_stakeholder_token_permission_example.sql (100%) rename {sql => database}/adb/47_kb_stakeholder_permission_or_branches.sql (100%) rename {sql => database}/adb/48_kb_permission_condition_codes.sql (100%) rename {sql => database}/adb/49_permission_static_sql_condition.sql (100%) rename {sql => database}/adb/51_kb_vpd_admin_token.sql (100%) rename {sql => database}/adb/52_cleanup_to_kb_vpd_poc.sql (100%) rename {sql => database}/adb/53_drop_legacy_poc_objects.sql (100%) rename {sql => database}/adb/54_drop_legacy_dds_ords_examples.sql (100%) rename {sql => database}/adb/55_reapply_kb_vpd_after_table_reload.sql (100%) rename {sql => database}/adb/56_kb_select_ai_router_team.sql (100%) rename {sql => database}/adb/57_kb_select_ai_router_team_test.sql (100%) rename {sql => database}/adb/58_kb_select_ai_router_api.sql (100%) rename {sql => database}/adb/59_kb_select_ai_router_ords.sql (100%) rename {sql => database}/adb/60_kb_vpd_admin_full_access_token.sql (100%) rename {sql => database}/adb/62_kb_aso_masking_backoffice_metadata.sql (100%) rename {sql => database}/adb/63_kb_aso_masking_rule_runtime.sql (100%) rename {sql => database}/adb/64_kb_aso_masking_default_column_rules.sql (100%) rename {sql => database}/adb/65_kb_select_ai_vpd_query_api.sql (100%) rename {sql => database}/adb/66_kb_select_ai_vpd_query_ords.sql (100%) rename {sql => database}/adb/67_drop_legacy_kb_contracts_premium_vpd_cls_policy.sql (100%) rename {sql => database}/adb/70_sg_tool_user.sql (100%) rename {sql => database}/adb/71_sg_identity_administration.sql (100%) rename {sql => database}/adb/72_hmm_leave_team_vpd.sql (100%) rename {sql => database}/adb/73_hmm_mcp_vpd_runtime.sql (100%) rename {sql => database}/adb/73_hmm_rds_pg_00_precheck.sql (100%) rename {sql => database}/adb/74_hmm_rds_pg_01_credential.sql (100%) rename {sql => database}/adb/75_hmm_rds_pg_02_acl.sql (100%) rename {sql => database}/adb/76_hmm_rds_pg_03_dblink.sql (100%) rename {sql => database}/adb/77_hmm_rds_pg_04_remote_query.sql (100%) rename {sql => database}/adb/78_hmm_rds_pg_05_views.sql (100%) rename {sql => database}/adb/79_hmm_rds_pg_06_verify.sql (100%) rename {sql => database}/adb/80_hmm_rds_pg_99_cleanup.sql (100%) rename {sql => database}/adb/81_hmm_carrier_access_groups.sql (100%) rename {sql => database}/adb/82_hmm_federation_relationship_comments.sql (100%) rename {sql => database}/source/mysql_setup.sql (96%) rename {sql => database}/source/postgres_hmm_carrier_performance.sql (98%) rename {sql => database}/source/postgres_hmm_carrier_performance_verify.sql (100%) rename {sql => database}/source/postgres_setup.sql (96%) rename {poc4_active_source_20260714/infra => deploy/ai-web-agent-console}/nginx/hmm-portal-auth.locations.conf (100%) rename poc4_active_source_20260714/deploy/systemd/poc4-streamlit-audit.conf => deploy/ai-web-agent-console/systemd/ai-web-agent-console-audit.conf (100%) rename {poc4_active_source_20260714/infra => deploy/ai-web-agent-console}/systemd/hmm-portal-auth.service (88%) rename deploy/{hmm-backoffice.env.example => vpd-backoffice/backoffice.env.example} (100%) rename deploy/{ => vpd-backoffice}/caddy/Caddyfile.template (100%) rename deploy/{ => vpd-backoffice}/caddy/install-caddy-config.sh (100%) rename deploy/{systemd => vpd-backoffice}/vpd-backoffice-structured-data.conf (100%) create mode 100644 docs/design/742-application-oriented-repository-layout/README.md rename poc4_active_source_20260714/HMM_BRAND_REFRESH.md => docs/notes/hmm-brand-refresh.md (92%) delete mode 100644 poc4_active_source_20260714/.env.sample delete mode 100644 poc4_active_source_20260714/SOURCE_README.md delete mode 100644 poc4_active_source_20260714/poc4_active_source_20260714.tar.gz delete mode 100755 poc4_active_source_20260714/scripts/poc4/start_8622_langgraph_tc_ui_nohup.sh delete mode 100755 poc4_active_source_20260714/scripts/poc4/status_8622_langgraph_tc_ui_nohup.sh delete mode 100644 poc4_active_source_20260714/src/poc4/__init__.py create mode 100644 tests/README.md create mode 100644 tests/test_repository_layout.py rename pom.xml => vpd-backoffice/pom.xml (95%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/VpdBackofficeApplication.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/config/AppConfig.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/config/BackofficeProperties.java (97%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/config/CatalogProperties.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/config/DbPoolWarmup.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/config/MaskingProperties.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/config/McpProperties.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/config/OrdsClientConfig.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/config/ProductProperties.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/config/SecurityConfig.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/config/SecuritySqlScriptProperties.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/audit/AuditEvent.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/effective/EffectiveMatrixView.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/effective/GroupEffectiveAccessView.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/effective/RoleEffectiveImpactView.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/effective/UserEffectiveAccessView.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/group/AppGroup.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/group/GroupCreateCommand.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/group/GroupRoleView.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/group/GroupUserView.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/masking/ColumnMaskingRule.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/masking/MaskingPolicyStatus.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/masking/MaskingRule.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/masking/MaskingRuleCreateCommand.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/masking/MaskingTemplate.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/masking/UserMaskingRule.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/mcp/McpChatbotResult.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/mcp/McpClientDemoResult.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/mcp/McpReasoningCommand.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/mcp/McpReasoningResult.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/mcp/McpToolView.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/operation/OperationHealthSummary.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/operation/OperationStatusRow.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/ords/OrdsHandlerUpdateCommand.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/ords/OrdsHandlerView.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/ords/OrdsObjectHandlerResult.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/permission/AppRole.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/permission/PermissionRule.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/permission/PermissionSet.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/permission/PermissionSetCommand.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/permission/PermissionView.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/permission/RuleCommand.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/probe/FgaExecutionEvidence.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/probe/ProbeCommand.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/probe/ProbeResult.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/probe/ProbeStatus.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/probe/SqlExecutionCandidate.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/probe/SqlExecutionEvidence.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/protectedobject/DatabaseObjectOption.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/protectedobject/ProtectedColumn.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/protectedobject/ProtectedObject.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/protectedobject/ProtectedObjectCreateCommand.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/schema/SchemaActionResult.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/schema/SchemaPreflightCheck.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/schema/SchemaPreflightView.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/schemametadata/SchemaAnnotation.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/schemametadata/SchemaMetadataColumn.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/schemametadata/SchemaMetadataView.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/securityscript/SecuritySqlScript.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/securityscript/SecuritySqlScriptExplanation.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/securityscript/SecuritySqlScriptSummary.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/setting/BackofficeSetting.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/stakeholder/StakeholderTokenSubject.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/structured/StructuredDataPreview.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/structured/StructuredDataTable.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/token/BearerTokenRecord.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/token/IssuedToken.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/token/TokenContextView.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/token/TokenIssueCommand.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/user/AppUser.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/user/UserCreateCommand.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/user/UserRoleView.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/vector/VectorChunk.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/vector/VectorIngestCommand.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/vector/VectorIngestResult.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/vector/VectorKnowledgeSummary.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/vector/VectorQueryEmbedding.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/vector/VectorSearchResult.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdBulkApplyResult.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdDescriptionNote.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdFunctionOption.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdFunctionSource.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdObjectFilterDetail.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdPolicyCreateCommand.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdPolicyDetail.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdPolicyExplanation.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdPolicyFormOptions.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdPolicyTemplateOption.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdPolicyView.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdSchemaObjectOption.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdTargetView.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/mapper/AuditMapper.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/mapper/BearerTokenMapper.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/mapper/GroupMapper.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/mapper/MaskingRuleMapper.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/mapper/OperationStatusMapper.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/mapper/OrdsMetadataMapper.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/mapper/PermissionMapper.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/mapper/ProtectedObjectMapper.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/mapper/SettingMapper.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/mapper/StakeholderMapper.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/mapper/UserMapper.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/mapper/VpdPolicyMapper.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/AppException.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/AuditService.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/BackofficeSchemaService.java (99%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/BearerTokenService.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/DataCatalog.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/EffectiveMatrixService.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/EnvironmentDataCatalog.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/EnvironmentMaskingPolicyCatalog.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/EnvironmentMcpToolCatalog.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/ExternalAuthorizationChangeNotifier.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/ExternalAuthorizationSynchronizer.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/GroupService.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/HmmAiAgentToolRunner.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/HmmKnowledgeSchemaInitializer.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/HmmMcpBearerAuthenticator.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/HmmMcpPrincipal.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/HmmRuntimeSettingsInitializer.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/JdbcHmmAiAgentToolRunner.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/JdbcHmmMcpBearerAuthenticator.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/MaskingPolicyCatalog.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/MaskingPolicySynchronizer.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/MaskingPolicyTarget.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/MaskingRuleService.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/McpChatbotService.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/McpClientDemoService.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/McpReasoningService.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/McpSseService.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/McpToolCatalog.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/McpToolDefinition.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/McpToolRegistry.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/McpUnauthorizedException.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/OciGenerativeAiChatClient.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/OpenAiCompatibleClient.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/OperationStatusService.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/OrdsMetadataService.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/OrdsProbeService.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/PermissionService.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/ProbeErrorClassifier.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/ProtectedObjectService.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/SchemaMetadataService.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/SecuritySqlScriptExplanationService.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/SecuritySqlScriptService.java (96%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/SelectAiAgentOrdsService.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/SelectAiService.java (88%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/SettingService.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/StructuredDataService.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/TokenGenerator.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/TokenHasher.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/UserService.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/VectorChunker.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/VectorKnowledgeService.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/VpdPolicyService.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/service/VpdTokenAccessDeniedException.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/web/AppExceptionHandler.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/web/DashboardController.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/web/EffectiveMatrixController.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/web/GroupController.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/web/LoginController.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/web/MaskingRuleController.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/web/McpChatbotController.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/web/McpClientDemoController.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/web/McpReasoningController.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/web/McpSseController.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/web/OperationStatusController.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/web/OrdsHandlerController.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/web/PermissionController.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/web/ProbeController.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/web/ProductModelAdvice.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/web/ProtectedObjectController.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/web/RoleController.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/web/RuntimeErrorMessage.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/web/RuntimeErrorMessages.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/web/SchemaMetadataController.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/web/SecurityModelAdvice.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/web/SecuritySqlScriptController.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/web/SettingController.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/web/StructuredDataController.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/web/TokenController.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/web/UserController.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/web/UserMaskingRuleController.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/web/VectorKnowledgeController.java (100%) rename {src => vpd-backoffice/src}/main/java/com/cloudhandson/vpdbackoffice/web/VpdPolicyController.java (100%) rename {src => vpd-backoffice/src}/main/resources/application.yml (96%) rename {src => vpd-backoffice/src}/main/resources/mapper/AuditMapper.xml (100%) rename {src => vpd-backoffice/src}/main/resources/mapper/BearerTokenMapper.xml (100%) rename {src => vpd-backoffice/src}/main/resources/mapper/GroupMapper.xml (100%) rename {src => vpd-backoffice/src}/main/resources/mapper/MaskingRuleMapper.xml (100%) rename {src => vpd-backoffice/src}/main/resources/mapper/OperationStatusMapper.xml (100%) rename {src => vpd-backoffice/src}/main/resources/mapper/OrdsMetadataMapper.xml (100%) rename {src => vpd-backoffice/src}/main/resources/mapper/PermissionMapper.xml (100%) rename {src => vpd-backoffice/src}/main/resources/mapper/ProtectedObjectMapper.xml (100%) rename {src => vpd-backoffice/src}/main/resources/mapper/SettingMapper.xml (100%) rename {src => vpd-backoffice/src}/main/resources/mapper/StakeholderMapper.xml (100%) rename {src => vpd-backoffice/src}/main/resources/mapper/UserMapper.xml (100%) rename {src => vpd-backoffice/src}/main/resources/mapper/VpdPolicyMapper.xml (100%) rename {src => vpd-backoffice/src}/main/resources/static/css/app.css (100%) rename {src => vpd-backoffice/src}/main/resources/static/js/app.js (100%) rename {src => vpd-backoffice/src}/main/resources/templates/dashboard.html (100%) rename {src => vpd-backoffice/src}/main/resources/templates/effective-matrix.html (100%) rename {src => vpd-backoffice/src}/main/resources/templates/error.html (100%) rename {src => vpd-backoffice/src}/main/resources/templates/fragments/layout.html (100%) rename {src => vpd-backoffice/src}/main/resources/templates/fragments/mcp-chatbot-result.html (100%) rename {src => vpd-backoffice/src}/main/resources/templates/fragments/mcp-client-demo-result.html (100%) rename {src => vpd-backoffice/src}/main/resources/templates/fragments/mcp-reasoning-result.html (100%) rename {src => vpd-backoffice/src}/main/resources/templates/fragments/ords-handler-source.html (100%) rename {src => vpd-backoffice/src}/main/resources/templates/fragments/probe-result.html (100%) rename {src => vpd-backoffice/src}/main/resources/templates/fragments/security-sql-script-explanation.html (100%) rename {src => vpd-backoffice/src}/main/resources/templates/fragments/vector-search-result.html (100%) rename {src => vpd-backoffice/src}/main/resources/templates/fragments/vpd-function-source.html (100%) rename {src => vpd-backoffice/src}/main/resources/templates/fragments/vpd-object-filter-detail.html (100%) rename {src => vpd-backoffice/src}/main/resources/templates/fragments/vpd-policy-detail.html (100%) rename {src => vpd-backoffice/src}/main/resources/templates/fragments/vpd-policy-explanation.html (100%) rename {src => vpd-backoffice/src}/main/resources/templates/groups.html (100%) rename {src => vpd-backoffice/src}/main/resources/templates/login.html (100%) rename {src => vpd-backoffice/src}/main/resources/templates/masking-rules.html (100%) rename {src => vpd-backoffice/src}/main/resources/templates/mcp-chatbot.html (100%) rename {src => vpd-backoffice/src}/main/resources/templates/mcp-client-demo.html (100%) rename {src => vpd-backoffice/src}/main/resources/templates/mcp-reasoning.html (100%) rename {src => vpd-backoffice/src}/main/resources/templates/mcp-sse.html (100%) rename {src => vpd-backoffice/src}/main/resources/templates/objects.html (100%) rename {src => vpd-backoffice/src}/main/resources/templates/operation-status.html (100%) rename {src => vpd-backoffice/src}/main/resources/templates/ords-handlers.html (100%) rename {src => vpd-backoffice/src}/main/resources/templates/permissions.html (100%) rename {src => vpd-backoffice/src}/main/resources/templates/probe.html (100%) rename {src => vpd-backoffice/src}/main/resources/templates/roles.html (100%) rename {src => vpd-backoffice/src}/main/resources/templates/schema-metadata.html (100%) rename {src => vpd-backoffice/src}/main/resources/templates/security-sql-scripts.html (92%) rename {src => vpd-backoffice/src}/main/resources/templates/settings-database.html (99%) rename {src => vpd-backoffice/src}/main/resources/templates/settings.html (100%) rename {src => vpd-backoffice/src}/main/resources/templates/structured-data.html (100%) rename {src => vpd-backoffice/src}/main/resources/templates/tokens.html (100%) rename {src => vpd-backoffice/src}/main/resources/templates/user-masking-rules.html (100%) rename {src => vpd-backoffice/src}/main/resources/templates/users.html (100%) rename {src => vpd-backoffice/src}/main/resources/templates/vector-knowledge.html (100%) rename {src => vpd-backoffice/src}/main/resources/templates/vpd-filter-policies.html (100%) rename {src => vpd-backoffice/src}/main/resources/templates/vpd-filter-runtime.html (100%) rename {src => vpd-backoffice/src}/main/resources/templates/vpd-policies.html (100%) rename {src => vpd-backoffice/src}/test/java/com/cloudhandson/vpdbackoffice/config/TransportSecurityTest.java (100%) rename {src => vpd-backoffice/src}/test/java/com/cloudhandson/vpdbackoffice/domain/masking/MaskingTemplateTest.java (100%) rename {src => vpd-backoffice/src}/test/java/com/cloudhandson/vpdbackoffice/domain/permission/PermissionViewTest.java (100%) rename {src => vpd-backoffice/src}/test/java/com/cloudhandson/vpdbackoffice/domain/probe/ProbeResultTest.java (100%) rename {src => vpd-backoffice/src}/test/java/com/cloudhandson/vpdbackoffice/domain/protectedobject/ProtectedColumnTest.java (100%) rename {src => vpd-backoffice/src}/test/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdPolicyFormOptionsTest.java (100%) rename {src => vpd-backoffice/src}/test/java/com/cloudhandson/vpdbackoffice/service/EffectiveMatrixServiceTest.java (100%) rename {src => vpd-backoffice/src}/test/java/com/cloudhandson/vpdbackoffice/service/ExternalAuthorizationChangeNotifierTest.java (100%) rename {src => vpd-backoffice/src}/test/java/com/cloudhandson/vpdbackoffice/service/HmmRuntimeSettingsInitializerTest.java (100%) rename {src => vpd-backoffice/src}/test/java/com/cloudhandson/vpdbackoffice/service/JdbcHmmAiAgentToolRunnerTest.java (100%) rename {src => vpd-backoffice/src}/test/java/com/cloudhandson/vpdbackoffice/service/MaskingPolicySynchronizerTest.java (100%) rename {src => vpd-backoffice/src}/test/java/com/cloudhandson/vpdbackoffice/service/McpSseServiceTest.java (100%) rename {src => vpd-backoffice/src}/test/java/com/cloudhandson/vpdbackoffice/service/OrdsMetadataServiceTest.java (100%) rename {src => vpd-backoffice/src}/test/java/com/cloudhandson/vpdbackoffice/service/OrdsProbeServiceTest.java (100%) rename {src => vpd-backoffice/src}/test/java/com/cloudhandson/vpdbackoffice/service/PermissionServiceTest.java (100%) rename {src => vpd-backoffice/src}/test/java/com/cloudhandson/vpdbackoffice/service/ProbeErrorClassifierTest.java (100%) rename {src => vpd-backoffice/src}/test/java/com/cloudhandson/vpdbackoffice/service/SchemaMetadataServiceTest.java (100%) rename {src => vpd-backoffice/src}/test/java/com/cloudhandson/vpdbackoffice/service/SecuritySqlScriptServiceTest.java (100%) rename {src => vpd-backoffice/src}/test/java/com/cloudhandson/vpdbackoffice/service/SelectAiServiceTest.java (100%) rename {src => vpd-backoffice/src}/test/java/com/cloudhandson/vpdbackoffice/service/StructuredDataServiceTest.java (100%) rename {src => vpd-backoffice/src}/test/java/com/cloudhandson/vpdbackoffice/service/TokenGeneratorTest.java (100%) rename {src => vpd-backoffice/src}/test/java/com/cloudhandson/vpdbackoffice/service/TokenHasherTest.java (100%) rename {src => vpd-backoffice/src}/test/java/com/cloudhandson/vpdbackoffice/service/VectorChunkerTest.java (100%) rename {src => vpd-backoffice/src}/test/java/com/cloudhandson/vpdbackoffice/service/VectorKnowledgeServiceTest.java (100%) rename {src => vpd-backoffice/src}/test/java/com/cloudhandson/vpdbackoffice/service/VpdPolicyServiceTest.java (100%) rename {src => vpd-backoffice/src}/test/java/com/cloudhandson/vpdbackoffice/web/EffectiveMatrixTemplateRenderTest.java (100%) rename {src => vpd-backoffice/src}/test/java/com/cloudhandson/vpdbackoffice/web/GuidedFlowTemplateTest.java (96%) rename {src => vpd-backoffice/src}/test/java/com/cloudhandson/vpdbackoffice/web/MaskingRuleTemplateRenderTest.java (100%) rename {src => vpd-backoffice/src}/test/java/com/cloudhandson/vpdbackoffice/web/PermissionTemplateRenderTest.java (100%) rename {src => vpd-backoffice/src}/test/java/com/cloudhandson/vpdbackoffice/web/ProbeResultTemplateRenderTest.java (100%) rename {src => vpd-backoffice/src}/test/java/com/cloudhandson/vpdbackoffice/web/SettingsTemplateRenderTest.java (100%) rename {src => vpd-backoffice/src}/test/resources/mockito-extensions/org.mockito.plugins.MockMaker (100%) diff --git a/.env.example b/.env.example index f36b100..a97b948 100644 --- a/.env.example +++ b/.env.example @@ -62,6 +62,8 @@ export BACKOFFICE_SELECT_AI_PROFILE="" export BACKOFFICE_SELECT_AI_RUNTIME_DB_URL="${BACKOFFICE_DB_URL}" export BACKOFFICE_SELECT_AI_RUNTIME_DB_USERNAME="CB_ORDS" export BACKOFFICE_SELECT_AI_RUNTIME_DB_PASSWORD="" +# Optional deployment-specific JSON contract. Keep project rules out of Java. +export BACKOFFICE_SELECT_AI_QUERY_CONTRACT_FILE="" # --- (2c) 재사용 가능한 백오피스 카탈로그와 표시 설정 --- # 승인 객체는 key/tableName/objectType/businessName/description JSON 배열입니다. @@ -103,7 +105,7 @@ export BACKOFFICE_AI_OCI_COMPARTMENT_ID="" export VPDTEST1_API_KEY="" export VPDTEST2_API_KEY="" -# --- (3) 데모용 ADB 엔드유저 비밀번호 (sql/adb/07_end_users.sql 에서 사용) --- +# --- (3) 데모용 ADB 엔드유저 비밀번호 (database/adb/07_end_users.sql 에서 사용) --- # ADB 비번 정책: 12자 이상, 대/소/숫자/특수 조합. # 4명의 데모 유저: # vpduser_my → MySQL view 만 SELECT 가능 @@ -116,7 +118,7 @@ export VPDUSER_BOTH_PASSWORD="RowFilter#Both26" export VPDUSER_NONE_PASSWORD="RowFilter#None26" # --- (3b) OPTIONAL — DDS 변형 데모용 엔드유저 비밀번호 --- -# sql/adb/13_dds_variant.sql (Oracle 26ai Deep Data Security 재구현) 에서만 사용. +# database/adb/13_dds_variant.sql (Oracle 26ai Deep Data Security 재구현) 에서만 사용. # 동일한 4-user 매트릭스를 declarative Data Grants 로 재현. VPD 데모와 공존. export DDSUSER_MY_PASSWORD="DdsGrant#My2026" export DDSUSER_PG_PASSWORD="DdsGrant#Pg2026" @@ -137,7 +139,7 @@ export DDS_MCP_BASE_URL="http://127.0.0.1:8083" export DDS_MCP_TEST_BEARER="" # 발급한 활성 CB_APP_USER Bearer만 로컬 테스트에 사용 # --- (4) 원격 Postgres (AWS RDS, Cloud SQL, ...) --- -# sql/source/postgres_setup.sql 가 여기로 customers 테이블/seed 생성. +# database/source/postgres_setup.sql 가 여기로 customers 테이블/seed 생성. # ADB 의 RDS_POSTGRES_LINK 가 이 인스턴스를 가리킴. export PG_HOST="" # 예: vpd-poc.xxxxx.ap-northeast-2.rds.amazonaws.com export PG_PORT="5432" diff --git a/.gitignore b/.gitignore index 6631670..357e835 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,12 @@ logs/ # Java / Maven target/ +# Python +__pycache__/ +*.py[cod] +.venv/ +data/ + # Locally downloaded development tools (for example SQLcl) .tools/ diff --git a/README.md b/README.md index 9d9b1af..955555a 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ End-to-End 데모입니다. 행 자체는 보여주되 특정 컬럼만 가리는 통제입니다. 예: 영업팀은 고객 행은 다 보지만 `email` 컬럼은 항상 `NULL` 로 반환. -* VPD 경로: 별도의 `DBMS_REDACT` 정책 (`sql/adb/06a_redaction.sql`). +* VPD 경로: 별도의 `DBMS_REDACT` 정책 (`database/adb/06a_redaction.sql`). * DDS 경로: 데이터 그랜트 안에 `(ALL COLUMNS EXCEPT email)` 한 줄로 흡수. ### 3. 두 가지 구현 — VPD 와 DDS @@ -59,20 +59,21 @@ DDS 변형의 능력 (멀티테넌트, OAuth2 federated identity, MAC 모드, ## 소스 위치와 Git 저장 기준 -현재 이 저장소에는 **VPD 관리 백오피스**와 **PoC4 MCP AI Console 외피**가 함께 들어 있다. -두 소스는 별도 Git repository가 아니라 같은 repository의 서로 다른 경로로 관리한다. +현재 이 저장소에는 서로 독립적으로 빌드·실행되는 세 애플리케이션이 있다. 각 제품은 +최상위 폴더를 하나씩 사용하며 저장소 루트에 프레임워크 `src`를 두지 않는다. | 구분 | 저장 위치 | 역할 | |---|---|---| -| VPD/ASO/FGA 관리 백오피스 | `src/main/java/com/cloudhandson/vpdbackoffice/` | Spring Boot 기반 데이터 접근 제어 관리 콘솔 | -| VPD 백오피스 화면 | `src/main/resources/templates/` | Thymeleaf 서버 렌더링 화면 | -| VPD 백오피스 CSS/JS | `src/main/resources/static/` | 화면 동작과 스타일 | -| VPD/ASO/FGA/ORDS/Select AI SQL | `sql/adb/` | ADB 적용·검증 SQL 스크립트 | -| 원격 원장 seed/검증 SQL | `sql/source/` | PostgreSQL·MySQL 가상 원장과 HMM 선사 실적 데이터 | +| VPD/ASO/FGA 관리 백오피스 | `vpd-backoffice/` | Spring Boot 기반 데이터 접근 제어 관리 콘솔 | +| VPD 백오피스 화면 | `vpd-backoffice/src/main/resources/templates/` | Thymeleaf 서버 렌더링 화면 | +| VPD 백오피스 CSS/JS | `vpd-backoffice/src/main/resources/static/` | 화면 동작과 스타일 | +| VPD/ASO/FGA/ORDS/Select AI SQL | `database/adb/` | ADB 적용·검증 SQL 스크립트 | +| 원격 원장 seed/검증 SQL | `database/source/` | PostgreSQL·MySQL 가상 원장과 HMM 선사 실적 데이터 | | DDS 백오피스 | `dds-backoffice/` | VPD와 분리된 DDS 전용 Spring Boot 데모 | -| Streamlit 외피 / PoC4 MCP AI Console | `poc4_active_source_20260714/` | ReAct-style로 MCP tool을 호출하는 PoC4 데모 소스 snapshot | -| Streamlit 외피 메인 화면 | `poc4_active_source_20260714/apps/poc4/mcp_discovery_ui.py` | MCP discovery, tool routing, ReAct-style agent loop, FGA evidence 표시 | -| Streamlit 외피 MCP 설정 | `poc4_active_source_20260714/config/mcp_servers.json` | `kb_mcp`, `kb_vector_mcp` endpoint와 allowlist | +| AI Web Agent Console | `ai-web-agent-console/` | Streamlit 기반 MCP 업무 에이전트 콘솔 | +| AI Web Agent Console 진입점 | `ai-web-agent-console/app.py` | MCP discovery, 도구 호출, 답변 합성, 감사 증적 표시 | +| AI Web Agent Console 패키지 | `ai-web-agent-console/ai_web_agent_console/` | 인증, 화면, MCP, 모델과 질의 계약 모듈 | +| AI Web Agent Console 설정 | `ai-web-agent-console/config/` | 제품, MCP, 모델, 데모 사용자와 질문 JSON | Git 기준은 다음과 같다. @@ -81,14 +82,12 @@ Git 기준은 다음과 같다. | 로컬 작업 디렉터리 | `/home/opc/workspaces/vpd-permission-poc-20260628213409` | | 운영 기준 remote | `gitea` | | 운영 기준 repository | `https://gittea.cloud-handson.com/joungmin/vpd-permission-poc.git` | -| 기준 branch | `main` | -| 보조 remote | `origin` = `https://github.com/devmrko/vpd-permission-poc.git` | +| HMM 기준 branch | `hmm-backoffice` | 주의: -- 현재 운영·현행화 기준은 `gitea/main`이다. -- `origin/main`은 보조 remote이며, 이 작업공간의 `main`이 `origin/main`보다 앞서 있을 수 있다. -- `poc4_active_source_20260714/`는 배포 VM의 PoC4 활성 소스를 가져온 snapshot이다. 원본은 배포 VM의 `/home/opc/poc_4/` 계열 경로에서 운영되었고, 저장소에는 비밀정보 없이 소스와 설정 sample만 보관한다. +- HMM 운영·현행화 기준은 Gitea의 `hmm-backoffice` 브랜치다. +- 날짜 스냅샷과 PoC 번호는 현재 소스 경로로 사용하지 않는다. - 실제 DB password, wallet password, Bearer token, AI key, 대화 SQLite DB는 Git에 넣지 않는다. --- @@ -111,7 +110,7 @@ Git 기준은 다음과 같다. ## 빠른 시작 (One-Click) ```bash -git clone https://github.com//vpd-permission-poc.git +git clone --branch hmm-backoffice https://gittea.cloud-handson.com/joungmin/vpd-permission-poc.git cd vpd-permission-poc # 1) 환경값 채우기 @@ -169,7 +168,7 @@ $EDITOR .env ## 데모 시나리오 — 2×2 source access matrix -`sql/adb/03_seed.sql` 의 매핑 (4 유저, 4 케이스): +`database/adb/03_seed.sql` 의 매핑 (4 유저, 4 케이스): | DB 유저 | 그룹 | PG 뷰 | MySQL 뷰 | VPD 결과 | |---|---|---|---|---| @@ -187,7 +186,7 @@ $EDITOR .env (`1=0` predicate) — **deny by default**. * 누구든 원본 테이블 직접 접근 시도 (`@RDS_POSTGRES_LINK` 등) → 권한 없음. -`sql/adb/08_tests_user_my.sql` 가 우회 시도 5개 (원격 직접 SELECT, 컨텍스트 +`database/adb/08_tests_user_my.sql` 가 우회 시도 5개 (원격 직접 SELECT, 컨텍스트 스푸핑, DBMS_RLS 변경, 매핑 테이블 SELECT) 를 시도하고 모두 ORA-xxxxx 로 실패하는 것을 보여줍니다. 09/10/11 은 각 유저의 expected 행 수를 가볍게 확인합니다. @@ -199,7 +198,7 @@ $EDITOR .env ## DDS 설정 핵심 -DDS 변형을 직접 셋업할 때 알아야 하는 최소 단위. `sql/adb/13_dds_variant.sql` 가 이 +DDS 변형을 직접 셋업할 때 알아야 하는 최소 단위. `database/adb/13_dds_variant.sql` 가 이 순서로 돌아가며, 행/컬럼 통제가 모두 **선언형 DDL 한 줄** 로 끝납니다. ### 사전 조건 @@ -259,8 +258,19 @@ CREATE DATA GRANT admin.alice_apac_grant . ├── run.sh # 원클릭 엔트리포인트 ├── .env.example +├── vpd-backoffice/ # Spring Boot VPD 관리 백오피스 +│ ├── pom.xml +│ └── src/ +├── ai-web-agent-console/ # Streamlit MCP 업무 에이전트 +│ ├── app.py +│ ├── ai_web_agent_console/ +│ ├── config/ +│ └── tests/ +├── dds-backoffice/ # DDS 전용 Spring Boot 백오피스 +├── deploy/ # 애플리케이션별 배포 설정 +├── tests/ # 저장소 폴더 경계와 경로 규칙 검증 ├── scripts/lib/common.sh # log/ok/warn/die + env 검증 헬퍼 -├── sql/ +├── database/ │ ├── source/ │ │ ├── postgres_setup.sql # 원격 PG: customers + 12 rows │ │ ├── postgres_hmm_carrier_performance.sql # 원격 PG: HMM 가상 선사 8개 + 월간 KPI 144 rows diff --git a/ai-web-agent-console/.env.example b/ai-web-agent-console/.env.example new file mode 100644 index 0000000..e3d01e8 --- /dev/null +++ b/ai-web-agent-console/.env.example @@ -0,0 +1,48 @@ +# AI Web Agent Console local/VM configuration example. +# Copy to .env and keep real credentials out of Git. + +# MCP +HMM_MCP_BEARER_TOKEN= +AI_WEB_AGENT_CONSOLE_MCP_TIMEOUT_SECONDS=45 + +# OCI Generative AI SDK +OCI_AUTH_TYPE=config_file +OCI_CONFIG_FILE=~/.oci/config +OCI_PROFILE=DEFAULT +OCI_GENAI_COMPARTMENT_ID= +AI_WEB_AGENT_CONSOLE_MODEL_PROFILE=gpt54_mini_oci +AI_WEB_AGENT_CONSOLE_COMPLEX_REASONING_MODEL_PROFILE=gpt54_mini_oci + +# Optional model route overrides +# AI_WEB_AGENT_CONSOLE_LLM_GPT54_MINI_OCI_MODEL_ID=openai.gpt-5.4-mini +# AI_WEB_AGENT_CONSOLE_LLM_GPT54_MINI_OCI_REGION=us-chicago-1 +# AI_WEB_AGENT_CONSOLE_LLM_GPT54_MINI_OCI_ENDPOINT=https://inference.generativeai.us-chicago-1.oci.oraclecloud.com + +# Login gateway. Generate PBKDF2 hash and random cookie secret outside Git. +AI_WEB_AGENT_CONSOLE_LOGIN_USER=demo-admin +AI_WEB_AGENT_CONSOLE_LOGIN_PASSWORD_PBKDF2= +AI_WEB_AGENT_CONSOLE_LOGIN_COOKIE_SECRET= +PORTAL_AUTH_BIND_ADDRESS=127.0.0.1 +PORTAL_AUTH_PORT=8621 +PORTAL_AUTH_SESSION_SECONDS=43200 +PORTAL_AUTH_REMEMBER_SECONDS=604800 + +# Product display +AGENT_CONSOLE_NAME=HMM AI 업무 에이전트 +AGENT_CONSOLE_SHORT_NAME=HMM +AGENT_CONSOLE_PAGE_TITLE=HMM AI 업무 에이전트 +AGENT_CONSOLE_PRIMARY_COLOR=#004b87 + +# Local conversation store +AI_WEB_AGENT_CONSOLE_CHAT_DB_PATH=data/ai_web_agent_console_chat.sqlite3 + +# Optional audit DB. Password and Wallet remain outside Git. +AI_WEB_AGENT_CONSOLE_AUDIT_DB_ENV_FILE=.env +AI_WEB_AGENT_CONSOLE_AUDIT_DB_USER=ADMIN +AI_WEB_AGENT_CONSOLE_AUDIT_DB_PASSWORD= +AI_WEB_AGENT_CONSOLE_AUDIT_DSN= +AI_WEB_AGENT_CONSOLE_AUDIT_WALLET_DIR= +AI_WEB_AGENT_CONSOLE_AUDIT_WALLET_PASSWORD= + +# Optional query contract override +AI_WEB_AGENT_CONSOLE_QUERY_CONTRACTS_PATH=config/hmm_hr_query_contracts.json diff --git a/ai-web-agent-console/README.md b/ai-web-agent-console/README.md new file mode 100644 index 0000000..799316d --- /dev/null +++ b/ai-web-agent-console/README.md @@ -0,0 +1,57 @@ +# AI Web Agent Console + +HMM 업무 데이터를 MCP 도구로 조회하고 OCI Generative AI로 답변을 구성하는 Streamlit +애플리케이션이다. 과거 PoC 번호나 소스 추출 날짜는 애플리케이션 이름과 패키지 경계로 +사용하지 않는다. + +## 구조 + +```text +ai-web-agent-console/ +├── app.py Streamlit 실행 진입점 +├── ai_web_agent_console/ 인증, 화면, MCP, 모델, 질의 계약 모듈 +├── config/ 제품·모델·MCP·시나리오 JSON +├── tests/ Python 회귀 테스트 +├── .env.example 환경변수 예시 +└── requirements.txt +``` + +## 실행 + +```bash +cd ai-web-agent-console +python3 -m venv .venv +.venv/bin/pip install -r requirements.txt +.venv/bin/streamlit run app.py --server.address 0.0.0.0 --server.port 8622 +``` + +인증 게이트웨이는 같은 디렉토리에서 다음과 같이 실행한다. + +```bash +.venv/bin/python -m ai_web_agent_console.auth_gateway +``` + +## 설정 + +- `config/app_profile.json`: 제품명, 설명, 아이콘, 색상 +- `config/mcp_servers.json`: MCP endpoint와 허용 도구 +- `config/model_profiles.json`: OCI Generative AI 모델 profile +- `config/vpd_token_presets.json`: 데모 사용자와 토큰 환경변수 매핑 +- `config/hmm_demo_scenarios.json`: 화면의 예제 질문 +- `config/hmm_hr_query_contracts.json`: 질의별 필수 근거와 계산·시간 규칙 + +실제 토큰, DB 비밀번호, Wallet, OCI private key는 Git에 넣지 않는다. 데모 사용자 JSON은 +`HMM_MCP_BEARER_TOKEN` 같은 환경변수 이름만 참조한다. + +## 검증 + +```bash +python3 -m compileall -q app.py ai_web_agent_console tests +python3 -m unittest discover -s tests -p 'test_*.py' +``` + +## 운영 호환성 + +운영 서버의 물리 경로 `/opt/hmm-poc4`와 기존 SQLite 테이블명은 대화 이력과 서비스 전환 +호환을 위해 유지할 수 있다. Git 폴더, Python 패키지와 실행 명령은 이 문서의 새 이름만 +사용한다. diff --git a/poc4_active_source_20260714/src/agent_console/__init__.py b/ai-web-agent-console/ai_web_agent_console/__init__.py similarity index 100% rename from poc4_active_source_20260714/src/agent_console/__init__.py rename to ai-web-agent-console/ai_web_agent_console/__init__.py diff --git a/poc4_active_source_20260714/src/agent_console/audit.py b/ai-web-agent-console/ai_web_agent_console/audit.py similarity index 100% rename from poc4_active_source_20260714/src/agent_console/audit.py rename to ai-web-agent-console/ai_web_agent_console/audit.py diff --git a/poc4_active_source_20260714/src/agent_console/auth_gateway.py b/ai-web-agent-console/ai_web_agent_console/auth_gateway.py similarity index 95% rename from poc4_active_source_20260714/src/agent_console/auth_gateway.py rename to ai-web-agent-console/ai_web_agent_console/auth_gateway.py index f9be767..03fd632 100644 --- a/poc4_active_source_20260714/src/agent_console/auth_gateway.py +++ b/ai-web-agent-console/ai_web_agent_console/auth_gateway.py @@ -28,6 +28,14 @@ from typing import Deque from urllib.parse import parse_qs, urlsplit +def _environment_value(*names: str, default: str = "") -> str: + for name in names: + value = os.environ.get(name) + if value is not None and value.strip(): + return value.strip() + return default + + LOG = logging.getLogger("hmm_portal_auth") SESSION_COOKIE_NAME = "__Host-HMM_PORTAL_SESSION" CSRF_COOKIE_NAME = "__Host-HMM_LOGIN_CSRF" @@ -54,13 +62,17 @@ class AuthConfig: @classmethod def from_environment(cls) -> "AuthConfig": config = cls( - username=os.environ.get("POC4_LOGIN_USER", "").strip(), - password_pbkdf2=os.environ.get( - "POC4_LOGIN_PASSWORD_PBKDF2", "" - ).strip(), - cookie_secret=os.environ.get( - "POC4_LOGIN_COOKIE_SECRET", "" - ).strip(), + username=_environment_value( + "AI_WEB_AGENT_CONSOLE_LOGIN_USER", "POC4_LOGIN_USER" + ), + password_pbkdf2=_environment_value( + "AI_WEB_AGENT_CONSOLE_LOGIN_PASSWORD_PBKDF2", + "POC4_LOGIN_PASSWORD_PBKDF2", + ), + cookie_secret=_environment_value( + "AI_WEB_AGENT_CONSOLE_LOGIN_COOKIE_SECRET", + "POC4_LOGIN_COOKIE_SECRET", + ), bind_address=os.environ.get( "PORTAL_AUTH_BIND_ADDRESS", "127.0.0.1" ).strip(), @@ -96,9 +108,11 @@ class AuthConfig: def validate(self) -> None: if not self.username or not self.password_pbkdf2: - raise ValueError("POC4 portal login credentials are not configured") + raise ValueError("AI web agent console login credentials are not configured") if len(self.cookie_secret.encode("utf-8")) < 32: - raise ValueError("POC4_LOGIN_COOKIE_SECRET must be at least 32 bytes") + raise ValueError( + "AI_WEB_AGENT_CONSOLE_LOGIN_COOKIE_SECRET must be at least 32 bytes" + ) if self.bind_address not in {"127.0.0.1", "::1"}: raise ValueError("Portal authentication service must bind to loopback") if not 1 <= self.port <= 65535: diff --git a/poc4_active_source_20260714/src/mcp_result.py b/ai-web-agent-console/ai_web_agent_console/mcp_result.py similarity index 100% rename from poc4_active_source_20260714/src/mcp_result.py rename to ai-web-agent-console/ai_web_agent_console/mcp_result.py diff --git a/poc4_active_source_20260714/src/mcp_tool_router.py b/ai-web-agent-console/ai_web_agent_console/mcp_tool_router.py similarity index 98% rename from poc4_active_source_20260714/src/mcp_tool_router.py rename to ai-web-agent-console/ai_web_agent_console/mcp_tool_router.py index 3dfae60..be0edae 100644 --- a/poc4_active_source_20260714/src/mcp_tool_router.py +++ b/ai-web-agent-console/ai_web_agent_console/mcp_tool_router.py @@ -10,11 +10,11 @@ from dataclasses import dataclass import json from typing import Any, Mapping -from src.oci_genai_sdk import ( +from ai_web_agent_console.oci_genai_sdk import ( build_oci_genai_completion_client, temperature_for_model_profile, ) -from src.poc3.model_registry import resolve_model_profile +from ai_web_agent_console.model_registry import resolve_model_profile @dataclass(frozen=True) diff --git a/poc4_active_source_20260714/src/poc3/model_registry.py b/ai-web-agent-console/ai_web_agent_console/model_registry.py similarity index 83% rename from poc4_active_source_20260714/src/poc3/model_registry.py rename to ai-web-agent-console/ai_web_agent_console/model_registry.py index ee9adf6..16ed237 100644 --- a/poc4_active_source_20260714/src/poc3/model_registry.py +++ b/ai-web-agent-console/ai_web_agent_console/model_registry.py @@ -1,7 +1,7 @@ -"""8512/8513 전용 PoC_3 model profile registry. +"""8512/8513 전용 AI Web Agent Console model profile registry. 이 registry는 모델 metadata만 관리한다. ``provider=oci``는 모델의 출처를 뜻하며 -``POC3_MCP_PROVIDER``와 독립적이다. 따라서 기본 model profile이 GPT-5.5여도 현재 +``AI_WEB_AGENT_CONSOLE_MCP_PROVIDER``와 독립적이다. 따라서 기본 model profile이 GPT-5.5여도 현재 MCP 실행 경로는 계속 ``mock``일 수 있다. """ @@ -15,11 +15,11 @@ import re from typing import Any, Mapping, Optional -ROOT = Path(__file__).resolve().parents[2] -REGISTRY_PATH = ROOT / "config" / "poc3_model_profiles.json" +ROOT = Path(__file__).resolve().parents[1] +REGISTRY_PATH = ROOT / "config" / "model_profiles.json" DEFAULT_MODEL_PROFILE_KEY = "gpt55_oci" -MODEL_PROFILE_ENV = "POC3_MODEL_PROFILE" -MODEL_PROFILE_DEFAULT_ENV = "POC3_MODEL_PROFILE_DEFAULT" +MODEL_PROFILE_ENV = "AI_WEB_AGENT_CONSOLE_MODEL_PROFILE" +MODEL_PROFILE_DEFAULT_ENV = "AI_WEB_AGENT_CONSOLE_MODEL_PROFILE_DEFAULT" EXISTING_MODEL_PROFILE_KEYS = ("grok43", "llama4_maverick", "llama33_70b") MODEL_PROFILE_ALIASES = { "gpt54_mini": "gpt54_mini_oci", @@ -84,36 +84,36 @@ _REQUIRED_PROFILE_FIELDS = ( "poc2_select_ai_profile", "poc2_native_agent_team", "verification_status", - "default_for_poc3", + "default_for_console", "source_tag", "notes", ) _PROFILE_ROUTE_ENV_KEYS = { "gpt55_oci": ( - "POC3_LLM_GPT55_OCI_MODEL_ID", - "POC3_LLM_GPT55_OCI_REGION", - "POC3_LLM_GPT55_OCI_ENDPOINT", + "AI_WEB_AGENT_CONSOLE_LLM_GPT55_OCI_MODEL_ID", + "AI_WEB_AGENT_CONSOLE_LLM_GPT55_OCI_REGION", + "AI_WEB_AGENT_CONSOLE_LLM_GPT55_OCI_ENDPOINT", ), "gpt54_mini_oci": ( - "POC3_LLM_GPT54_MINI_OCI_MODEL_ID", - "POC3_LLM_GPT54_MINI_OCI_REGION", - "POC3_LLM_GPT54_MINI_OCI_ENDPOINT", + "AI_WEB_AGENT_CONSOLE_LLM_GPT54_MINI_OCI_MODEL_ID", + "AI_WEB_AGENT_CONSOLE_LLM_GPT54_MINI_OCI_REGION", + "AI_WEB_AGENT_CONSOLE_LLM_GPT54_MINI_OCI_ENDPOINT", ), "grok43": ( - "POC3_LLM_GROK43_MODEL_ID", - "POC3_LLM_GROK43_REGION", - "POC3_LLM_GROK43_ENDPOINT", + "AI_WEB_AGENT_CONSOLE_LLM_GROK43_MODEL_ID", + "AI_WEB_AGENT_CONSOLE_LLM_GROK43_REGION", + "AI_WEB_AGENT_CONSOLE_LLM_GROK43_ENDPOINT", ), "llama4_maverick": ( - "POC3_LLM_LLAMA4_MAVERICK_MODEL_ID", - "POC3_LLM_LLAMA4_MAVERICK_REGION", - "POC3_LLM_LLAMA4_MAVERICK_ENDPOINT", + "AI_WEB_AGENT_CONSOLE_LLM_LLAMA4_MAVERICK_MODEL_ID", + "AI_WEB_AGENT_CONSOLE_LLM_LLAMA4_MAVERICK_REGION", + "AI_WEB_AGENT_CONSOLE_LLM_LLAMA4_MAVERICK_ENDPOINT", ), "llama33_70b": ( - "POC3_LLM_LLAMA33_70B_MODEL_ID", - "POC3_LLM_LLAMA33_70B_REGION", - "POC3_LLM_LLAMA33_70B_ENDPOINT", + "AI_WEB_AGENT_CONSOLE_LLM_LLAMA33_70B_MODEL_ID", + "AI_WEB_AGENT_CONSOLE_LLM_LLAMA33_70B_REGION", + "AI_WEB_AGENT_CONSOLE_LLM_LLAMA33_70B_ENDPOINT", ), } @@ -154,7 +154,7 @@ class ModelProfile: poc2_select_ai_profile: str poc2_native_agent_team: str verification_status: str - default_for_poc3: bool + default_for_console: bool source_tag: str notes: str display_order: int = 999 @@ -163,9 +163,9 @@ class ModelProfile: def from_mapping(cls, value: Mapping[str, Any]) -> "ModelProfile": missing = [name for name in _REQUIRED_PROFILE_FIELDS if name not in value] if missing: - raise ValueError("PoC_3 model profile fields are missing") - if not isinstance(value.get("default_for_poc3"), bool): - raise ValueError("default_for_poc3 must be boolean") + raise ValueError("AI Web Agent Console model profile fields are missing") + if not isinstance(value.get("default_for_console"), bool): + raise ValueError("default_for_console must be boolean") order = value.get("display_order", 999) if isinstance(order, bool) or not isinstance(order, int) or order < 0: raise ValueError("model profile display_order is invalid") @@ -191,7 +191,7 @@ class ModelProfile: .strip() .upper(), verification_status=str(value["verification_status"]).strip().upper(), - default_for_poc3=value["default_for_poc3"], + default_for_console=value["default_for_console"], source_tag=str(value["source_tag"]).strip(), notes=str(value["notes"]).strip(), display_order=order, @@ -276,7 +276,7 @@ class ModelProfile: "poc2_select_ai_profile": self.poc2_select_ai_profile, "poc2_native_agent_team": self.poc2_native_agent_team, "verification_status": self.verification_status, - "default_for_poc3": self.default_for_poc3, + "default_for_console": self.default_for_console, "source_tag": self.source_tag, "notes": self.notes, } @@ -284,7 +284,7 @@ class ModelProfile: @dataclass(frozen=True) class ModelProfileRegistry: - """검증된 PoC_3 model profile 집합.""" + """검증된 AI Web Agent Console model profile 집합.""" profiles: tuple[ModelProfile, ...] default_model_profile: str @@ -338,32 +338,32 @@ def load_model_registry(path: Path = REGISTRY_PATH) -> ModelProfileRegistry: try: payload = json.loads(path.read_text(encoding="utf-8")) except (OSError, json.JSONDecodeError) as exc: - raise ValueError("PoC_3 model profile registry cannot be loaded") from exc + raise ValueError("AI Web Agent Console model profile registry cannot be loaded") from exc if not isinstance(payload, Mapping): - raise ValueError("PoC_3 model profile registry must be an object") + raise ValueError("AI Web Agent Console model profile registry must be an object") raw_profiles = payload.get("profiles") if not isinstance(raw_profiles, list) or not raw_profiles: - raise ValueError("PoC_3 model profile registry has no profiles") + raise ValueError("AI Web Agent Console model profile registry has no profiles") profiles = tuple( ModelProfile.from_mapping(item) for item in raw_profiles if isinstance(item, Mapping) ) if len(profiles) != len(raw_profiles): - raise ValueError("PoC_3 model profile registry contains an invalid profile") + raise ValueError("AI Web Agent Console model profile registry contains an invalid profile") keys = tuple(item.model_key for item in profiles) if len(set(keys)) != len(keys): - raise ValueError("PoC_3 model profile keys must be unique") + raise ValueError("AI Web Agent Console model profile keys must be unique") if len({item.display_name for item in profiles}) != len(profiles): - raise ValueError("PoC_3 model profile display names must be unique") - defaults = tuple(item.model_key for item in profiles if item.default_for_poc3) + raise ValueError("AI Web Agent Console model profile display names must be unique") + defaults = tuple(item.model_key for item in profiles if item.default_for_console) configured_default = str(payload.get("default_model_profile") or "").strip().lower() if defaults != (configured_default,): - raise ValueError("PoC_3 model profile default is inconsistent") + raise ValueError("AI Web Agent Console model profile default is inconsistent") if configured_default != DEFAULT_MODEL_PROFILE_KEY: - raise ValueError("PoC_3 GPT-5.5 default contract is not satisfied") + raise ValueError("AI Web Agent Console GPT-5.5 default contract is not satisfied") if not set(EXISTING_MODEL_PROFILE_KEYS).issubset(keys): - raise ValueError("existing PoC_3 selector models are missing") + raise ValueError("existing AI Web Agent Console selector models are missing") actual_answer_routes = { item.model_key: ( item.model_id, @@ -374,16 +374,16 @@ def load_model_registry(path: Path = REGISTRY_PATH) -> ModelProfileRegistry: for item in profiles } if actual_answer_routes != _EXPECTED_ANSWER_MODEL_ROUTES: - raise ValueError("PoC_3 answer model route mapping is inconsistent") + raise ValueError("AI Web Agent Console answer model route mapping is inconsistent") if str(payload.get("source_commit") or "").strip() != EXPECTED_SOURCE_COMMIT: raise ValueError("PoC_2 source commit is inconsistent") if any(item.source_tag != EXPECTED_SOURCE_TAG for item in profiles): raise ValueError("PoC_2 source tag is inconsistent") if payload.get("schema_version") != 1: - raise ValueError("unsupported PoC_3 model profile registry schema") + raise ValueError("unsupported AI Web Agent Console model profile registry schema") registry_name = str(payload.get("registry_name") or "").strip() if not registry_name: - raise ValueError("PoC_3 model profile registry name is missing") + raise ValueError("AI Web Agent Console model profile registry name is missing") return ModelProfileRegistry( profiles=profiles, default_model_profile=configured_default, diff --git a/poc4_active_source_20260714/src/oci_genai_sdk.py b/ai-web-agent-console/ai_web_agent_console/oci_genai_sdk.py similarity index 100% rename from poc4_active_source_20260714/src/oci_genai_sdk.py rename to ai-web-agent-console/ai_web_agent_console/oci_genai_sdk.py diff --git a/poc4_active_source_20260714/src/agent_console/presentation.py b/ai-web-agent-console/ai_web_agent_console/presentation.py similarity index 100% rename from poc4_active_source_20260714/src/agent_console/presentation.py rename to ai-web-agent-console/ai_web_agent_console/presentation.py diff --git a/poc4_active_source_20260714/src/agent_console/profile.py b/ai-web-agent-console/ai_web_agent_console/profile.py similarity index 100% rename from poc4_active_source_20260714/src/agent_console/profile.py rename to ai-web-agent-console/ai_web_agent_console/profile.py diff --git a/ai-web-agent-console/ai_web_agent_console/query_contracts.py b/ai-web-agent-console/ai_web_agent_console/query_contracts.py new file mode 100644 index 0000000..8427a73 --- /dev/null +++ b/ai-web-agent-console/ai_web_agent_console/query_contracts.py @@ -0,0 +1,407 @@ +"""Configuration-driven query and answer evidence contracts.""" + +from __future__ import annotations + +import ast +from decimal import Decimal, InvalidOperation +from functools import lru_cache +import json +import os +from pathlib import Path +import re +from typing import Any, Mapping + + +ROOT = Path(__file__).resolve().parents[1] +DEFAULT_CONTRACT_FILE = ROOT / "config" / "hmm_hr_query_contracts.json" + + +def _contract_file() -> Path: + configured = str( + os.getenv("AI_WEB_AGENT_CONSOLE_QUERY_CONTRACTS_PATH") + or os.getenv("POC4_QUERY_CONTRACTS_PATH") + or "" + ).strip() + if not configured: + return DEFAULT_CONTRACT_FILE + path = Path(configured).expanduser() + return path if path.is_absolute() else ROOT / path + + +@lru_cache(maxsize=4) +def _load_contract_file(path_text: str) -> tuple[Mapping[str, Any], ...]: + path = Path(path_text) + if not path.exists(): + return () + value = json.loads(path.read_text(encoding="utf-8")) + contracts = value.get("contracts") if isinstance(value, Mapping) else None + if not isinstance(contracts, list): + raise ValueError("query contracts must contain a contracts array") + return tuple(item for item in contracts if isinstance(item, Mapping)) + + +def load_query_contracts() -> tuple[Mapping[str, Any], ...]: + return _load_contract_file(str(_contract_file())) + + +def matching_query_contracts( + question: str, + tool_name: str, +) -> tuple[Mapping[str, Any], ...]: + normalized_question = " ".join(str(question or "").casefold().split()) + normalized_tool = str(tool_name or "").strip() + selected: list[Mapping[str, Any]] = [] + for contract in load_query_contracts(): + tools = contract.get("applies_to_tools") + if isinstance(tools, list) and normalized_tool not in { + str(item) for item in tools + }: + continue + intent = contract.get("intent_match") + if not isinstance(intent, Mapping): + continue + subject_terms = [ + str(item).casefold() + for item in intent.get("subject_terms_any", []) + if str(item).strip() + ] + action_terms = [ + str(item).casefold() + for item in intent.get("action_terms_any", []) + if str(item).strip() + ] + if subject_terms and not any( + term in normalized_question for term in subject_terms + ): + continue + if action_terms and not any( + term in normalized_question for term in action_terms + ): + continue + selected.append(contract) + return tuple(selected) + + +def query_contract_guidance(question: str, tool_name: str) -> str: + contracts = matching_query_contracts(question, tool_name) + if not contracts: + return "" + return json.dumps( + {"query_contracts": contracts}, + ensure_ascii=False, + separators=(",", ":"), + ) + + +def append_query_contract_guidance( + tool_query: str, + *, + original_question: str, + tool_name: str, +) -> str: + guidance = query_contract_guidance(original_question, tool_name) + normalized = str(tool_query or "").strip() + if not guidance: + return normalized + return ( + f"{normalized}\n" + "다음 질의 계약의 필드·계산·시간 기준을 반드시 지켜 결과를 반환하세요. " + f"계약: {guidance}" + ) + + +def _json_data_result(value: str) -> Any: + marker = "DATA_RESULT" + position = value.find(marker) + if position < 0: + return None + remainder = value[position + len(marker) :] + object_position = remainder.find("{") + array_position = remainder.find("[") + positions = [ + candidate + for candidate in (object_position, array_position) + if candidate >= 0 + ] + if not positions: + return None + candidate = remainder[min(positions) :] + try: + parsed, _ = json.JSONDecoder().raw_decode(candidate) + except (TypeError, ValueError, json.JSONDecodeError): + return None + return parsed + + +def _evidence_rows(value: Any) -> list[Mapping[str, Any]]: + rows: list[Mapping[str, Any]] = [] + if isinstance(value, Mapping): + rows.append(value) + for item in value.values(): + rows.extend(_evidence_rows(item)) + elif isinstance(value, list): + for item in value: + rows.extend(_evidence_rows(item)) + elif isinstance(value, str): + parsed = _json_data_result(value) + if parsed is not None: + rows.extend(_evidence_rows(parsed)) + return rows + + +def _decimal(value: Any) -> Decimal: + if isinstance(value, bool) or value is None: + raise InvalidOperation + return Decimal(str(value)) + + +def _evaluate_contract_expression( + expression: str, + row: Mapping[str, Any], +) -> Decimal: + tree = ast.parse(expression, mode="eval") + + def evaluate(node: ast.AST) -> Decimal: + if isinstance(node, ast.Expression): + return evaluate(node.body) + if isinstance(node, ast.Name): + key = node.id.casefold() + if key not in row: + raise InvalidOperation + return _decimal(row[key]) + if isinstance(node, ast.Constant): + return _decimal(node.value) + if isinstance(node, ast.UnaryOp) and isinstance( + node.op, (ast.UAdd, ast.USub) + ): + value = evaluate(node.operand) + return value if isinstance(node.op, ast.UAdd) else -value + if isinstance(node, ast.BinOp) and isinstance( + node.op, (ast.Add, ast.Sub, ast.Mult, ast.Div) + ): + left = evaluate(node.left) + right = evaluate(node.right) + if isinstance(node.op, ast.Add): + return left + right + if isinstance(node.op, ast.Sub): + return left - right + if isinstance(node.op, ast.Mult): + return left * right + return left / right + raise ValueError("unsupported contract expression") + + return evaluate(tree) + + +def evidence_contract_report( + contracts: tuple[Mapping[str, Any], ...], + evidence: Any, +) -> list[dict[str, Any]]: + rows = [ + {str(key).strip().casefold(): value for key, value in row.items()} + for row in _evidence_rows(evidence) + ] + reports: list[dict[str, Any]] = [] + for contract in contracts: + data_contract = contract.get("data_contract") + if not isinstance(data_contract, Mapping): + continue + required = [ + str(item).strip().casefold() + for item in data_contract.get("required_fields", []) + if str(item).strip() + ] + matching_row = next( + (row for row in rows if required and all(key in row for key in required)), + None, + ) + observed = sorted( + { + key + for row in rows + for key in row + if not required or key in required + } + ) + missing = [ + key.upper() + for key in required + if matching_row is None or key not in matching_row + ] + computed_checks: list[dict[str, Any]] = [] + computed_fields = data_contract.get("computed_fields") + if matching_row is not None and isinstance(computed_fields, Mapping): + for field, definition in computed_fields.items(): + normalized_field = str(field).strip().casefold() + expression = ( + str(definition.get("expression") or "").strip() + if isinstance(definition, Mapping) + else "" + ) + if not normalized_field or not expression: + continue + try: + actual = _decimal(matching_row.get(normalized_field)) + expected = _evaluate_contract_expression( + expression, + matching_row, + ) + matches = actual == expected + computed_checks.append( + { + "field": normalized_field.upper(), + "expression": expression, + "actual": str(actual), + "expected": str(expected), + "satisfied": matches, + } + ) + except (InvalidOperation, ValueError, ZeroDivisionError): + computed_checks.append( + { + "field": normalized_field.upper(), + "expression": expression, + "satisfied": False, + } + ) + calculations_satisfied = all( + bool(check.get("satisfied")) for check in computed_checks + ) + temporal_checks: list[dict[str, Any]] = [] + temporal_contract = contract.get("temporal_contract") + if matching_row is not None and isinstance(temporal_contract, Mapping): + status_fields = [ + str(item).strip().casefold() + for item in temporal_contract.get("period_status_fields", []) + if str(item).strip() + ] + past_status = str( + temporal_contract.get("past_period_status") or "" + ).strip() + observed_status = next( + ( + str(matching_row.get(field) or "").strip() + for field in status_fields + if str(matching_row.get(field) or "").strip() + ), + "", + ) + if observed_status == past_status and past_status: + decision_field = str( + temporal_contract.get("past_period_decision_field") or "" + ).strip().casefold() + decision_value = str( + temporal_contract.get("past_period_decision_value") or "" + ).strip() + actual_decision = str( + matching_row.get(decision_field) or "" + ).strip() + temporal_checks.append( + { + "check": "past_period_decision", + "field": decision_field.upper(), + "expected": decision_value, + "actual": actual_decision, + "satisfied": bool( + decision_field + and decision_value + and actual_decision == decision_value + ), + } + ) + forbidden_patterns = [ + str(item).strip() + for item in temporal_contract.get( + "forbidden_past_period_decision_field_patterns", [] + ) + if str(item).strip() + ] + forbidden_fields = sorted( + key.upper() + for key in matching_row + if any( + re.search(pattern, key, flags=re.IGNORECASE) + for pattern in forbidden_patterns + ) + ) + temporal_checks.append( + { + "check": "no_past_period_yes_no_decision", + "forbidden_fields": forbidden_fields, + "satisfied": not forbidden_fields, + } + ) + required_counts = [ + str(item).strip().casefold() + for item in temporal_contract.get( + "validate_stated_days_against", [] + ) + if str(item).strip() + ] + temporal_checks.append( + { + "check": "date_range_counts", + "required_fields": [ + field.upper() for field in required_counts + ], + "missing_fields": [ + field.upper() + for field in required_counts + if field not in matching_row + ], + "satisfied": all( + field in matching_row for field in required_counts + ), + } + ) + temporal_satisfied = all( + bool(check.get("satisfied")) for check in temporal_checks + ) + reports.append( + { + "contract_id": str(contract.get("id") or ""), + "satisfied": bool( + matching_row is not None + and not missing + and calculations_satisfied + and temporal_satisfied + ), + "required_fields": [key.upper() for key in required], + "observed_required_fields": [key.upper() for key in observed], + "missing_fields": missing, + "computed_field_checks": computed_checks, + "temporal_contract_checks": temporal_checks, + "validated_record": ( + { + key.upper(): matching_row.get(key) + for key in required + } + if matching_row is not None + else {} + ), + } + ) + return reports + + +def missing_evidence_message( + contracts: tuple[Mapping[str, Any], ...], +) -> str: + for contract in contracts: + answer_contract = contract.get("answer_contract") + if not isinstance(answer_contract, Mapping): + continue + message = str(answer_contract.get("missing_evidence_message") or "").strip() + if message: + return message + return "조회 결과가 답변 계약의 필수 근거를 충족하지 않아 결론을 제공할 수 없습니다." + + +__all__ = [ + "append_query_contract_guidance", + "evidence_contract_report", + "load_query_contracts", + "matching_query_contracts", + "missing_evidence_message", + "query_contract_guidance", +] diff --git a/poc4_active_source_20260714/src/poc3/questions.py b/ai-web-agent-console/ai_web_agent_console/questions.py similarity index 97% rename from poc4_active_source_20260714/src/poc3/questions.py rename to ai-web-agent-console/ai_web_agent_console/questions.py index da01a1e..ba6bd1b 100644 --- a/poc4_active_source_20260714/src/poc3/questions.py +++ b/ai-web-agent-console/ai_web_agent_console/questions.py @@ -1,4 +1,4 @@ -"""PoC_3 preset catalog와 현재 질문 기반의 결정적 intent router.""" +"""AI Web Agent Console preset catalog와 현재 질문 기반의 결정적 intent router.""" from __future__ import annotations @@ -90,7 +90,7 @@ COMMON_DEMO_QUESTIONS = ( _QUESTION_BY_ID = {item.question_id: item for item in COMMON_DEMO_QUESTIONS} if len(_QUESTION_BY_ID) != len(COMMON_DEMO_QUESTIONS): - raise RuntimeError("duplicate PoC_3 demo question id") + raise RuntimeError("duplicate AI Web Agent Console demo question id") def question_by_id(question_id: str) -> DemoQuestion: @@ -100,7 +100,7 @@ def question_by_id(question_id: str) -> DemoQuestion: try: return _QUESTION_BY_ID[normalized] except KeyError: - raise ValueError("unknown PoC_3 demo question id") from None + raise ValueError("unknown AI Web Agent Console demo question id") from None def normalize_scenario_id(value: object) -> str | None: diff --git a/poc4_active_source_20260714/src/poc4/scenarios.py b/ai-web-agent-console/ai_web_agent_console/scenarios.py similarity index 100% rename from poc4_active_source_20260714/src/poc4/scenarios.py rename to ai-web-agent-console/ai_web_agent_console/scenarios.py diff --git a/poc4_active_source_20260714/apps/poc4/mcp_discovery_ui.py b/ai-web-agent-console/app.py similarity index 87% rename from poc4_active_source_20260714/apps/poc4/mcp_discovery_ui.py rename to ai-web-agent-console/app.py index c1510ca..26d6e08 100644 --- a/poc4_active_source_20260714/apps/poc4/mcp_discovery_ui.py +++ b/ai-web-agent-console/app.py @@ -1,7 +1,7 @@ -"""Minimal MCP discovery UI for PoC4. +"""HMM AI Web Agent Console Streamlit entrypoint. Run: - streamlit run apps/poc4/mcp_discovery_ui.py --server.port 8622 + streamlit run app.py --server.port 8622 This screen intentionally does not use the existing LangGraph/actor-context pipeline. It discovers MCP tools, selects a query-capable tool, and calls it @@ -29,7 +29,7 @@ from urllib.error import HTTPError, URLError from urllib.parse import urlsplit from urllib.request import HTTPRedirectHandler, Request, build_opener -ROOT = Path(__file__).resolve().parents[2] +ROOT = Path(__file__).resolve().parent if str(ROOT) not in sys.path: sys.path.insert(0, str(ROOT)) @@ -37,32 +37,38 @@ import streamlit as st import streamlit.components.v1 as components import oracledb -from src.mcp_tool_router import ( +from ai_web_agent_console.mcp_tool_router import ( McpTool, McpToolRouterError, RoutedMcpTool, build_mcp_tool_arguments, route_mcp_tool_across_servers_with_llm, ) -from src.mcp_result import ( +from ai_web_agent_console.mcp_result import ( has_actionable_text_result, status_result_evidence, status_result_summary, text_result, ) -from src.oci_genai_sdk import ( +from ai_web_agent_console.oci_genai_sdk import ( build_oci_genai_completion_client, temperature_for_model_profile, ) -from src.poc3.model_registry import load_model_registry, resolve_model_profile -from src.agent_console.presentation import ( +from ai_web_agent_console.model_registry import load_model_registry, resolve_model_profile +from ai_web_agent_console.presentation import ( apply_console_theme, render_console_header, render_login_brand, ) -from src.agent_console.audit import render_hmm_audit_tab -from src.agent_console.profile import AppProfile, AppProfileError, load_app_profile -from src.poc4.scenarios import ScenarioConfigError, load_demo_scenarios +from ai_web_agent_console.audit import render_hmm_audit_tab +from ai_web_agent_console.profile import AppProfile, AppProfileError, load_app_profile +from ai_web_agent_console.scenarios import ScenarioConfigError, load_demo_scenarios +from ai_web_agent_console.query_contracts import ( + append_query_contract_guidance, + evidence_contract_report, + matching_query_contracts, + missing_evidence_message, +) LOG = logging.getLogger(__name__) @@ -75,7 +81,10 @@ CHAT_TURNS_PER_PAGE = 3 CHAT_CONTEXT_TURNS = 4 MAX_AGENT_TOOL_STEPS = 4 AGENT_FINAL_ROUTE = "__final__" -COMPLEX_REASONING_MODEL_PROFILE_ENV = "POC4_COMPLEX_REASONING_MODEL_PROFILE" +COMPLEX_REASONING_MODEL_PROFILE_ENV = ( + "AI_WEB_AGENT_CONSOLE_COMPLEX_REASONING_MODEL_PROFILE" +) +LEGACY_COMPLEX_REASONING_MODEL_PROFILE_ENV = "POC4_COMPLEX_REASONING_MODEL_PROFILE" DEFAULT_COMPLEX_REASONING_MODEL_PROFILE = "grok43" DEFAULT_SYNTHESIS_FALLBACK_MODEL_PROFILE = "gpt54_mini_oci" DEFAULT_QUERY_MODEL_PROFILE = "gpt54_mini_oci" @@ -92,7 +101,9 @@ PORTAL_AUTH_USER_KEY = "poc4_portal_auth_user" PORTAL_AUTH_PROXY_USER_HEADER = "X-HMM-Authenticated-User" PORTAL_AUTH_PROXY_EXPIRY_HEADER = "X-HMM-Auth-Expires" AUDIT_DB_ENV_FILE = Path( - os.environ.get("POC4_AUDIT_DB_ENV_FILE", str(ENV_FILE)) + os.environ.get("AI_WEB_AGENT_CONSOLE_AUDIT_DB_ENV_FILE") + or os.environ.get("POC4_AUDIT_DB_ENV_FILE") + or str(ENV_FILE) ).expanduser() DEFAULT_AUDIT_DB_DSN = ( "(description=(retry_count=3)(retry_delay=1)" @@ -102,1364 +113,6 @@ DEFAULT_AUDIT_DB_DSN = ( "(security=(ssl_server_dn_match=yes)))" ) _OPAQUE_BEARER = re.compile(r"^[\x21-\x7e]{1,4096}$") -KB_THEME_CSS = """ - -""" - -HMM_MINIMAL_THEME_CSS = """ - -""" - -HMM_CLEAN_THEME_CSS = """ - -""" - @dataclass(frozen=True) class McpServer: @@ -1549,7 +202,10 @@ def _utc_now() -> str: def _chat_db_path() -> Path: - configured = _runtime_env_value("POC4_CHAT_DB_PATH") + configured = _runtime_env_value( + "AI_WEB_AGENT_CONSOLE_CHAT_DB_PATH", + "POC4_CHAT_DB_PATH", + ) if not configured: return CHAT_DB_FILE path = Path(configured).expanduser() @@ -1907,8 +563,12 @@ def _apply_console_theme(profile: AppProfile) -> None: apply_console_theme(st, profile) -def _portal_auth_value(name: str) -> str: - return (os.environ.get(name) or _dotenv_value(name)).strip() +def _portal_auth_value(*names: str) -> str: + for name in names: + value = (os.environ.get(name) or _dotenv_value(name)).strip() + if value: + return value + return "" def _proxy_auth_headers() -> tuple[str, int]: @@ -1925,7 +585,10 @@ def _proxy_auth_headers() -> tuple[str, int]: def _restore_portal_proxy_session() -> None: username, expires_at = _proxy_auth_headers() - expected_username = _portal_auth_value("POC4_LOGIN_USER") + expected_username = _portal_auth_value( + "AI_WEB_AGENT_CONSOLE_LOGIN_USER", + "POC4_LOGIN_USER", + ) authenticated = bool( username and expected_username @@ -2040,25 +703,35 @@ class AuditLogError(RuntimeError): def _audit_db_env_value(name: str, default: str = "") -> str: - return ( - os.environ.get(name) - or _dotenv_value(name, AUDIT_DB_ENV_FILE) - or default - ).strip() + names = [name] + if name.startswith("AI_WEB_AGENT_CONSOLE_"): + names.append(name.replace("AI_WEB_AGENT_CONSOLE_", "POC4_", 1)) + for candidate in names: + value = ( + os.environ.get(candidate) + or _dotenv_value(candidate, AUDIT_DB_ENV_FILE) + ).strip() + if value: + return value + return default.strip() @st.cache_resource(show_spinner=False) def _audit_db_pool() -> Any: - password = _audit_db_env_value("POC4_AUDIT_DB_PASSWORD") + password = _audit_db_env_value("AI_WEB_AGENT_CONSOLE_AUDIT_DB_PASSWORD") if not password: raise AuditLogError("감사로그 DB 접속 설정을 확인해 주세요.") - dsn = _audit_db_env_value("POC4_AUDIT_DSN", DEFAULT_AUDIT_DB_DSN) - wallet_password = _audit_db_env_value("POC4_AUDIT_WALLET_PASSWORD") + dsn = _audit_db_env_value( + "AI_WEB_AGENT_CONSOLE_AUDIT_DSN", DEFAULT_AUDIT_DB_DSN + ) + wallet_password = _audit_db_env_value( + "AI_WEB_AGENT_CONSOLE_AUDIT_WALLET_PASSWORD" + ) pool_options: dict[str, Any] = {} if wallet_password: wallet_dir = Path( _audit_db_env_value( - "POC4_AUDIT_WALLET_DIR", + "AI_WEB_AGENT_CONSOLE_AUDIT_WALLET_DIR", "/home/opc/apps/vpd-backoffice/wallet", ) ).expanduser().resolve() @@ -2075,7 +748,9 @@ def _audit_db_pool() -> Any: ) try: return oracledb.create_pool( - user=_audit_db_env_value("POC4_AUDIT_DB_USER", "ADMIN"), + user=_audit_db_env_value( + "AI_WEB_AGENT_CONSOLE_AUDIT_DB_USER", "ADMIN" + ), password=password, dsn=dsn, min=1, @@ -3456,11 +2131,15 @@ def _enrich_answer_with_business_evidence( return f"{verified}\n\n{normalized}".strip() -def _runtime_env_value(name: object) -> str: - key = str(name or "").strip() - if not key: - return "" - return (os.environ.get(key) or _dotenv_value(key)).strip() +def _runtime_env_value(*names: object) -> str: + for name in names: + key = str(name or "").strip() + if not key: + continue + value = (os.environ.get(key) or _dotenv_value(key)).strip() + if value: + return value + return "" def _looks_like_http_url(value: str) -> bool: @@ -4022,6 +2701,8 @@ def _complex_reasoning_model_profile(default_model_profile: str) -> str: configured = ( os.environ.get(COMPLEX_REASONING_MODEL_PROFILE_ENV) or _dotenv_value(COMPLEX_REASONING_MODEL_PROFILE_ENV) + or os.environ.get(LEGACY_COMPLEX_REASONING_MODEL_PROFILE_ENV) + or _dotenv_value(LEGACY_COMPLEX_REASONING_MODEL_PROFILE_ENV) ).strip() return configured or DEFAULT_COMPLEX_REASONING_MODEL_PROFILE or default_model_profile @@ -4866,6 +3547,11 @@ def run_mcp_agent_loop( if route is None: route = next(iter(routes_by_key.values())) route_key = _route_key(route.server_id, route.tool.name) + tool_query = append_query_contract_guidance( + tool_query, + original_question=question, + tool_name=route.tool.name, + ) is_distinct_vector_query = ( _is_vector_route(route) and tool_query not in completed_vector_queries @@ -4890,6 +3576,11 @@ def run_mcp_agent_loop( route, observations, ) + tool_query = append_query_contract_guidance( + tool_query, + original_question=question, + tool_name=route.tool.name, + ) thought = ( f"{thought} / 중복 MCP route {repeated_route_key} 대신 " f"미호출 route {route_key}로 전환합니다." @@ -5340,8 +4031,15 @@ def _prepare_hmm_hr_tool_query( """Prepare an HMM HR query without inheriting retired KB/VPD prompt rules.""" fallback = str(question or "").strip() + def with_contract(value: str) -> str: + return append_query_contract_guidance( + value, + original_question=fallback, + tool_name=tool.name, + ) + if tool.name in {"resolve_hr_term", "search_hr_policy"}: - return fallback + return with_contract(fallback) contextual_question = _hmm_demo_user_context( fallback, user_id=selected_user_id.strip(), @@ -5350,7 +4048,7 @@ def _prepare_hmm_hr_tool_query( scope=selected_user_scope.strip(), ) if tool.name == "search_carrier_performance": - return contextual_question + return with_contract(contextual_question) try: profile = resolve_model_profile(model_profile_key) client = build_oci_genai_completion_client( @@ -5390,13 +4088,15 @@ def _prepare_hmm_hr_tool_query( parsed = json.loads(text) rewritten = _clean_agent_tool_query(parsed.get("tool_query"), fallback) except Exception: - return contextual_question - return _hmm_demo_user_context( - rewritten or fallback, - user_id=selected_user_id.strip(), - role=selected_user_role.strip(), - team=selected_user_team.strip(), - scope=selected_user_scope.strip(), + return with_contract(contextual_question) + return with_contract( + _hmm_demo_user_context( + rewritten or fallback, + user_id=selected_user_id.strip(), + role=selected_user_role.strip(), + team=selected_user_team.strip(), + scope=selected_user_scope.strip(), + ) ) @@ -5413,6 +4113,72 @@ def synthesize_answer( security_evidence: Mapping[str, Any] | None = None, business_evidence: Mapping[str, Any] | None = None, ) -> Mapping[str, Any]: + contract_tools = {tool.name} + contract_tools.update( + str(step.get("tool_name") or "") + for step in (agent_steps or []) + if isinstance(step, Mapping) + ) + active_contracts_by_id: dict[str, Mapping[str, Any]] = {} + for contract_tool in contract_tools: + for contract in matching_query_contracts(question, contract_tool): + contract_id = str(contract.get("id") or "") + active_contracts_by_id[contract_id] = contract + active_contracts = tuple(active_contracts_by_id.values()) + contract_evidence = { + "mcp_result": mcp_result, + "agent_steps": list(agent_steps or []), + } + contract_report = evidence_contract_report(active_contracts, contract_evidence) + + if contract_report and any( + not bool(report.get("satisfied")) for report in contract_report + ): + missing_fields = sorted( + { + str(field) + for report in contract_report + for field in report.get("missing_fields", []) + } + ) + failed_calculations = sorted( + { + str(check.get("field") or "") + for report in contract_report + for check in report.get("computed_field_checks", []) + if not bool(check.get("satisfied")) + } + ) + failed_temporal_checks = sorted( + { + str(check.get("check") or "") + for report in contract_report + for check in report.get("temporal_contract_checks", []) + if not bool(check.get("satisfied")) + } + ) + evidence_issues = [] + if missing_fields: + evidence_issues.append("필수 필드 누락: " + ", ".join(missing_fields)) + if failed_calculations: + evidence_issues.append( + "계산 결과 불일치: " + ", ".join(failed_calculations) + ) + if failed_temporal_checks: + evidence_issues.append( + "시간 기준 계약 불일치: " + ", ".join(failed_temporal_checks) + ) + return { + "answer": missing_evidence_message(active_contracts), + "basis": evidence_issues or ["질의 계약 검증 실패"], + "limitations": ( + "필수 원장 근거가 충족되지 않아 잔여일수나 승인 가능 여부를 " + "추정하지 않았습니다." + ), + "query_contracts": list(active_contracts), + "evidence_contract_report": contract_report, + } + def build_user_prompt(*, compact: bool) -> str: evidence_items = 10 if compact else 20 evidence_text = 260 if compact else 380 @@ -5421,6 +4187,8 @@ def synthesize_answer( prompt_payload = { "question": question, "required_answer_checks": _answer_requirements(question), + "query_contracts": list(active_contracts), + "evidence_contract_report": contract_report, "conversation": [] if compact else conversation[-4:], "selected_route": { "server_id": server.server_id, @@ -5536,6 +4304,10 @@ def synthesize_answer( "FC_ID scoping for customer data, plus active catalog document/chunk evidence. " "It takes precedence when Select AI returns an ambiguous or incorrect zero-row " "result. Never expose customer rows outside the business evidence scope. " + "The query_contracts and evidence_contract_report are authoritative. Follow " + "their required fields, calculations, temporal semantics, missing-record " + "semantics, forbidden fallbacks, and answer restrictions. Keep data facts " + "and policy requirements separate. " "Show audit_log_id when it is provided. Follow every required_answer_checks item in the user " "payload. When the user requests a result list, include every " "row provided in the MCP evidence, up to 20 rows, and state the total " @@ -5600,6 +4372,8 @@ def synthesize_answer( if evidence_verified: enriched["limitations"] = "" enriched["basis"] = basis[:5] + enriched["query_contracts"] = list(active_contracts) + enriched["evidence_contract_report"] = contract_report return enriched except Exception as exc: last_error = { @@ -6442,6 +5216,8 @@ def _process_submitted_question( ) assistant_message: dict[str, Any] synthesis_error_details: dict[str, Any] = {} + query_contract_details: list[Any] = [] + evidence_contract_details: list[Any] = [] answer_model_profile = reasoning_model_profile answer_synthesis_fallback_model = "" try: @@ -6473,6 +5249,10 @@ def _process_submitted_question( "basis": synthesized.get("basis", []), "limitations": str(synthesized.get("limitations") or ""), } + query_contract_details = list(synthesized.get("query_contracts") or []) + evidence_contract_details = list( + synthesized.get("evidence_contract_report") or [] + ) except AnswerSynthesisError as exc: synthesis_error_details = getattr(exc, "diagnostics", {}) or {} fallback_profile = DEFAULT_SYNTHESIS_FALLBACK_MODEL_PROFILE @@ -6512,6 +5292,12 @@ def _process_submitted_question( "basis": synthesized.get("basis", []), "limitations": str(synthesized.get("limitations") or ""), } + query_contract_details = list( + synthesized.get("query_contracts") or [] + ) + evidence_contract_details = list( + synthesized.get("evidence_contract_report") or [] + ) except AnswerSynthesisError as fallback_exc: synthesis_error_details = { "primary": synthesis_error_details, @@ -6586,6 +5372,8 @@ def _process_submitted_question( "agent_stop_reason": agent_stop_reason, "security_evidence": security_evidence, "business_evidence": business_evidence, + "query_contracts": query_contract_details, + "evidence_contract_report": evidence_contract_details, "execution_events": execution_events, "discovered_routes": [ { diff --git a/poc4_active_source_20260714/config/app_profile.json b/ai-web-agent-console/config/app_profile.json similarity index 100% rename from poc4_active_source_20260714/config/app_profile.json rename to ai-web-agent-console/config/app_profile.json diff --git a/poc4_active_source_20260714/config/hmm_demo_scenarios.json b/ai-web-agent-console/config/hmm_demo_scenarios.json similarity index 100% rename from poc4_active_source_20260714/config/hmm_demo_scenarios.json rename to ai-web-agent-console/config/hmm_demo_scenarios.json diff --git a/ai-web-agent-console/config/hmm_hr_query_contracts.json b/ai-web-agent-console/config/hmm_hr_query_contracts.json new file mode 100644 index 0000000..1014a9f --- /dev/null +++ b/ai-web-agent-console/config/hmm_hr_query_contracts.json @@ -0,0 +1,84 @@ +{ + "version": 1, + "contracts": [ + { + "id": "leave_balance_eligibility", + "description": "연차·휴가 잔여와 신청 가능 여부를 원장 연도 및 구성값으로 검증한다.", + "applies_to_tools": [ + "search_hr_data" + ], + "intent_match": { + "subject_terms_any": [ + "연차", + "휴가" + ], + "action_terms_any": [ + "잔여", + "가능", + "승인", + "신청", + "사용할 수" + ] + }, + "data_contract": { + "source_object": "HMM_LEAVE_BALANCES", + "identity_fields": [ + "EMPLOYEE_CODE" + ], + "required_fields": [ + "LEAVE_YEAR", + "ANNUAL_ENTITLEMENT", + "CARRYOVER_DAYS", + "USED_DAYS", + "PENDING_DAYS", + "AVAILABLE_REMAINING_DAYS" + ], + "computed_fields": { + "AVAILABLE_REMAINING_DAYS": { + "expression": "ANNUAL_ENTITLEMENT + CARRYOVER_DAYS - USED_DAYS - PENDING_DAYS", + "authoritative_source": "DATABASE_VIRTUAL_COLUMN" + } + }, + "missing_record_semantics": "NO_BALANCE_DATA_FOR_YEAR", + "forbidden_missing_record_fallbacks": [ + "ZERO", + "NVL", + "COALESCE", + "OUTER_JOIN_DEFAULT", + "LITERAL_VALUE" + ] + }, + "temporal_contract": { + "current_balance_year": "LATEST_AVAILABLE_LEAVE_YEAR", + "requested_period_and_current_balance_are_separate": true, + "period_status_fields": [ + "REQUEST_PERIOD_LABEL", + "PERIOD_STATUS" + ], + "past_period_status": "HISTORICAL_REQUEST_PERIOD", + "past_period_decision": "DO_NOT_DECIDE_NEW_APPLICATION_FROM_CURRENT_BALANCE", + "past_period_decision_field": "APPLICATION_DECISION_STATUS", + "past_period_decision_value": "NOT_APPLICABLE_FOR_HISTORICAL_PERIOD", + "forbidden_past_period_decision_field_patterns": [ + "^CAN_", + "^APPROVAL_(ALLOWED|DENIED)$" + ], + "validate_stated_days_against": [ + "INCLUSIVE_CALENDAR_DAYS", + "MONDAY_TO_FRIDAY_DAYS" + ], + "day_count_mismatch_status": "DATE_COUNT_REQUIRES_VALIDATION" + }, + "answer_contract": { + "separate_data_facts_and_policy_requirements": true, + "deny_conclusions_without_required_fields": [ + "ZERO_REMAINING_DAYS", + "LEAVE_APPROVAL_ALLOWED", + "LEAVE_APPROVAL_DENIED" + ], + "missing_evidence_response": "STATE_INSUFFICIENT_EVIDENCE_WITHOUT_INVENTING_VALUES", + "missing_evidence_message": "조회 결과가 필수 원장 근거나 시간 기준 계약을 충족하지 않아 휴가 가능 여부를 확정할 수 없습니다. 계약 필드가 충족된 원장 결과로 다시 조회해야 합니다." + } + } + ] +} diff --git a/poc4_active_source_20260714/config/mcp_servers.json b/ai-web-agent-console/config/mcp_servers.json similarity index 89% rename from poc4_active_source_20260714/config/mcp_servers.json rename to ai-web-agent-console/config/mcp_servers.json index 8a6242c..3da95f4 100644 --- a/poc4_active_source_20260714/config/mcp_servers.json +++ b/ai-web-agent-console/config/mcp_servers.json @@ -8,7 +8,7 @@ "transport": "http", "endpoint_url": "https://hmm-mcp.cloud-handson.com/mcp", "auth_token_env": "HMM_MCP_BEARER_TOKEN", - "timeout_seconds_env": "POC3_MCP_TIMEOUT_SECONDS", + "timeout_seconds_env": "AI_WEB_AGENT_CONSOLE_MCP_TIMEOUT_SECONDS", "default_tool": "search_hr_data", "router_model_profile": "gpt54_mini_oci", "tool_allowlist": [ diff --git a/poc4_active_source_20260714/config/poc3_model_profiles.json b/ai-web-agent-console/config/model_profiles.json similarity index 94% rename from poc4_active_source_20260714/config/poc3_model_profiles.json rename to ai-web-agent-console/config/model_profiles.json index 2883a08..f7c973f 100644 --- a/poc4_active_source_20260714/config/poc3_model_profiles.json +++ b/ai-web-agent-console/config/model_profiles.json @@ -1,6 +1,6 @@ { "schema_version": 1, - "registry_name": "POC3_8512_8513_MODEL_PROFILES", + "registry_name": "AI_WEB_AGENT_CONSOLE_MODEL_PROFILES", "default_model_profile": "gpt55_oci", "source_commit": "7a3b37f175b65ed5eab1d8bf37c9bf6114e7558f", "profiles": [ @@ -15,7 +15,7 @@ "poc2_select_ai_profile": "KB_AIDP_SELECTAI_GPT55_OCI_PROFILE_V2", "poc2_native_agent_team": "KB_AIDP_AGENT_TEAM_GPT55_OCI_V2", "verification_status": "VERIFIED_WITH_WARNINGS", - "default_for_poc3": true, + "default_for_console": true, "source_tag": "poc_2-gpt55-oci-partial", "notes": "PoC_2 Select AI S1~S5와 승인된 read-only 실행은 5/5 PASS. 8503 Native Agent는 S1/S2/R1/H1 PASS이나 S3 safe-conversion marker 미확인 WARN으로 PARTIAL이다.", "display_order": 0 @@ -31,7 +31,7 @@ "poc2_select_ai_profile": "KB_AIDP_SELECTAI_GPT54_MINI_OCI_PROFILE_V1", "poc2_native_agent_team": "KB_AIDP_AGENT_TEAM_GPT54_MINI_OCI_V1", "verification_status": "PARTIAL_VERIFIED", - "default_for_poc3": false, + "default_for_console": false, "source_tag": "poc_2-gpt55-oci-partial", "notes": "PoC4 MCP 실행 방식(single/agent) 판단용 경량 planner profile. Select AI/Agent Team 본 처리 기본값은 gpt55_oci를 유지한다.", "display_order": 1 @@ -47,7 +47,7 @@ "poc2_select_ai_profile": "KB_AIDP_SELECTAI_GROK43_PROFILE_V2", "poc2_native_agent_team": "KB_AIDP_AGENT_TEAM_GROK43_V3", "verification_status": "VERIFIED", - "default_for_poc3": false, + "default_for_console": false, "source_tag": "poc_2-gpt55-oci-partial", "notes": "PoC_2 기존 모델 회귀에서 S1/S3 SHOWSQL, profile 확인 및 S3 safe conversion PASS.", "display_order": 2 @@ -63,7 +63,7 @@ "poc2_select_ai_profile": "KB_AIDP_SELECTAI_LLAMA4_MAVERICK_PROFILE_V2", "poc2_native_agent_team": "KB_AIDP_AGENT_TEAM_LLAMA4_MAVERICK_V3", "verification_status": "VERIFIED", - "default_for_poc3": false, + "default_for_console": false, "source_tag": "poc_2-gpt55-oci-partial", "notes": "PoC_2 기존 모델 회귀에서 S1/S3 SHOWSQL, profile 확인 및 S3 safe conversion PASS.", "display_order": 3 @@ -79,7 +79,7 @@ "poc2_select_ai_profile": "KB_AIDP_SELECTAI_LLAMA33_PROFILE_V2", "poc2_native_agent_team": "KB_AIDP_AGENT_TEAM_LLAMA33_V3", "verification_status": "VERIFIED", - "default_for_poc3": false, + "default_for_console": false, "source_tag": "poc_2-gpt55-oci-partial", "notes": "PoC_2 기존 모델 회귀에서 S1/S3 SHOWSQL, profile 확인 및 S3 safe conversion PASS.", "display_order": 4 diff --git a/poc4_active_source_20260714/config/vpd_token_presets.json b/ai-web-agent-console/config/vpd_token_presets.json similarity index 100% rename from poc4_active_source_20260714/config/vpd_token_presets.json rename to ai-web-agent-console/config/vpd_token_presets.json diff --git a/poc4_active_source_20260714/requirements-langgraph.txt b/ai-web-agent-console/requirements-langgraph.txt similarity index 100% rename from poc4_active_source_20260714/requirements-langgraph.txt rename to ai-web-agent-console/requirements-langgraph.txt diff --git a/poc4_active_source_20260714/requirements.txt b/ai-web-agent-console/requirements.txt similarity index 100% rename from poc4_active_source_20260714/requirements.txt rename to ai-web-agent-console/requirements.txt diff --git a/poc4_active_source_20260714/tests/test_auth_gateway.py b/ai-web-agent-console/tests/test_auth_gateway.py similarity index 99% rename from poc4_active_source_20260714/tests/test_auth_gateway.py rename to ai-web-agent-console/tests/test_auth_gateway.py index 689150a..d4a46d8 100644 --- a/poc4_active_source_20260714/tests/test_auth_gateway.py +++ b/ai-web-agent-console/tests/test_auth_gateway.py @@ -13,7 +13,7 @@ from urllib.parse import urlencode ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT)) -from src.agent_console.auth_gateway import ( # noqa: E402 +from ai_web_agent_console.auth_gateway import ( # noqa: E402 AuthConfig, LoginAttemptLimiter, SESSION_COOKIE_NAME, diff --git a/ai-web-agent-console/tests/test_query_contracts.py b/ai-web-agent-console/tests/test_query_contracts.py new file mode 100644 index 0000000..f25a707 --- /dev/null +++ b/ai-web-agent-console/tests/test_query_contracts.py @@ -0,0 +1,181 @@ +import unittest + +from ai_web_agent_console.query_contracts import ( + append_query_contract_guidance, + evidence_contract_report, + matching_query_contracts, +) + + +def test_leave_eligibility_question_selects_external_contract(): + contracts = matching_query_contracts( + "직원 E9999의 연차 잔여와 신청 가능 여부를 알려줘", + "search_hr_data", + ) + + assert [contract["id"] for contract in contracts] == [ + "leave_balance_eligibility" + ] + + +def test_unrelated_tool_does_not_receive_leave_contract(): + contracts = matching_query_contracts( + "직원 E9999의 연차 잔여를 알려줘", + "search_carrier_performance", + ) + + assert contracts == () + + +def test_incomplete_result_cannot_be_interpreted_as_zero_balance(): + contracts = matching_query_contracts( + "직원 E9999의 연차 사용 가능 여부를 알려줘", + "search_hr_data", + ) + evidence = { + "result": ( + 'DATA_RESULT: {"items":[{"EMPLOYEE_CODE":"E9999",' + '"REMAINING_DAYS":0}]}' + ) + } + + report = evidence_contract_report(contracts, evidence) + + assert report[0]["satisfied"] is False + assert "AVAILABLE_REMAINING_DAYS" in report[0]["missing_fields"] + assert "LEAVE_YEAR" in report[0]["missing_fields"] + + +def test_complete_database_row_satisfies_contract_without_fixture_values(): + contracts = matching_query_contracts( + "직원 E9999의 연차 신청 가능 여부를 알려줘", + "search_hr_data", + ) + evidence = { + "result": ( + 'DATA_RESULT\n{"items":[{"EMPLOYEE_CODE":"E9999",' + '"LEAVE_YEAR":2031,"ANNUAL_ENTITLEMENT":17,' + '"CARRYOVER_DAYS":2,"USED_DAYS":4,"PENDING_DAYS":1,' + '"AVAILABLE_REMAINING_DAYS":14}]}' + ) + } + + report = evidence_contract_report(contracts, evidence) + + assert report[0]["satisfied"] is True + assert report[0]["validated_record"]["LEAVE_YEAR"] == 2031 + assert report[0]["validated_record"]["AVAILABLE_REMAINING_DAYS"] == 14 + + +def test_conflicting_computed_value_fails_contract(): + contracts = matching_query_contracts( + "직원 E9999의 연차 신청 가능 여부를 알려줘", + "search_hr_data", + ) + evidence = { + "result": ( + 'DATA_RESULT\n{"items":[{"EMPLOYEE_CODE":"E9999",' + '"LEAVE_YEAR":2031,"ANNUAL_ENTITLEMENT":17,' + '"CARRYOVER_DAYS":2,"USED_DAYS":4,"PENDING_DAYS":1,' + '"AVAILABLE_REMAINING_DAYS":0}]}' + ) + } + + report = evidence_contract_report(contracts, evidence) + + assert report[0]["satisfied"] is False + assert report[0]["computed_field_checks"][0]["expected"] == "14" + assert report[0]["computed_field_checks"][0]["actual"] == "0" + + +def test_historical_period_rejects_yes_no_decision_and_missing_counts(): + contracts = matching_query_contracts( + "직원 E9999의 과거 연차 신청 가능 여부를 알려줘", + "search_hr_data", + ) + evidence = { + "result": ( + 'DATA_RESULT\n{"items":[{"EMPLOYEE_CODE":"E9999",' + '"LEAVE_YEAR":2031,"ANNUAL_ENTITLEMENT":17,' + '"CARRYOVER_DAYS":2,"USED_DAYS":4,"PENDING_DAYS":1,' + '"AVAILABLE_REMAINING_DAYS":14,' + '"REQUEST_PERIOD_LABEL":"HISTORICAL_REQUEST_PERIOD",' + '"CAN_APPLY":"YES","INCLUSIVE_CALENDAR_DAYS":11}]}' + ) + } + + report = evidence_contract_report(contracts, evidence) + + assert report[0]["satisfied"] is False + failed = { + check["check"] + for check in report[0]["temporal_contract_checks"] + if not check["satisfied"] + } + assert failed == { + "past_period_decision", + "no_past_period_yes_no_decision", + "date_range_counts", + } + + +def test_historical_period_contract_accepts_non_decision_status(): + contracts = matching_query_contracts( + "직원 E9999의 과거 연차 신청 가능 여부를 알려줘", + "search_hr_data", + ) + evidence = { + "result": ( + 'DATA_RESULT\n{"items":[{"EMPLOYEE_CODE":"E9999",' + '"LEAVE_YEAR":2031,"ANNUAL_ENTITLEMENT":17,' + '"CARRYOVER_DAYS":2,"USED_DAYS":4,"PENDING_DAYS":1,' + '"AVAILABLE_REMAINING_DAYS":14,' + '"REQUEST_PERIOD_LABEL":"HISTORICAL_REQUEST_PERIOD",' + '"APPLICATION_DECISION_STATUS":' + '"NOT_APPLICABLE_FOR_HISTORICAL_PERIOD",' + '"INCLUSIVE_CALENDAR_DAYS":11,' + '"MONDAY_TO_FRIDAY_DAYS":7}]}' + ) + } + + report = evidence_contract_report(contracts, evidence) + + assert report[0]["satisfied"] is True + + +def test_guidance_is_appended_from_json_contract(): + guided = append_query_contract_guidance( + "직원 E9999의 휴가 잔여를 조회해줘", + original_question="직원 E9999의 휴가 잔여를 조회해줘", + tool_name="search_hr_data", + ) + + assert '"id":"leave_balance_eligibility"' in guided + assert "AVAILABLE_REMAINING_DAYS" in guided + + +class QueryContractTest(unittest.TestCase): + test_leave_eligibility_question_selects_external_contract = staticmethod( + test_leave_eligibility_question_selects_external_contract + ) + test_unrelated_tool_does_not_receive_leave_contract = staticmethod( + test_unrelated_tool_does_not_receive_leave_contract + ) + test_incomplete_result_cannot_be_interpreted_as_zero_balance = staticmethod( + test_incomplete_result_cannot_be_interpreted_as_zero_balance + ) + test_complete_database_row_satisfies_contract_without_fixture_values = ( + staticmethod(test_complete_database_row_satisfies_contract_without_fixture_values) + ) + test_conflicting_computed_value_fails_contract = staticmethod( + test_conflicting_computed_value_fails_contract + ) + test_historical_period_rejects_yes_no_decision_and_missing_counts = ( + staticmethod(test_historical_period_rejects_yes_no_decision_and_missing_counts) + ) + test_historical_period_contract_accepts_non_decision_status = staticmethod( + test_historical_period_contract_accepts_non_decision_status + ) + test_guidance_is_appended_from_json_contract = staticmethod( + test_guidance_is_appended_from_json_contract + ) diff --git a/poc4_active_source_20260714/tests/test_scenarios.py b/ai-web-agent-console/tests/test_scenarios.py similarity index 86% rename from poc4_active_source_20260714/tests/test_scenarios.py rename to ai-web-agent-console/tests/test_scenarios.py index d819430..7c0b3e6 100644 --- a/poc4_active_source_20260714/tests/test_scenarios.py +++ b/ai-web-agent-console/tests/test_scenarios.py @@ -6,27 +6,37 @@ import tempfile import unittest from unittest.mock import patch -from src.poc4.scenarios import ScenarioConfigError, load_demo_scenarios -from src.agent_console.profile import load_app_profile -from src.mcp_tool_router import McpTool, build_mcp_tool_arguments -from src.mcp_result import ( +from ai_web_agent_console.scenarios import ScenarioConfigError, load_demo_scenarios +from ai_web_agent_console.profile import load_app_profile +from ai_web_agent_console.mcp_tool_router import McpTool, build_mcp_tool_arguments +from ai_web_agent_console.mcp_result import ( has_actionable_text_result, status_result_evidence, status_result_summary, ) +from ai_web_agent_console.model_registry import load_model_registry -def _load_poc4_query_helpers(): +def _load_console_query_helpers(): """Load the Streamlit entrypoint only when its optional runtime is installed.""" try: - from apps.poc4.mcp_discovery_ui import _prepare_hmm_hr_tool_query + from app import _prepare_hmm_hr_tool_query except ModuleNotFoundError: return None return _prepare_hmm_hr_tool_query class DemoScenarioConfigTest(unittest.TestCase): + def test_model_registry_uses_console_names(self) -> None: + registry = load_model_registry() + + self.assertEqual( + registry.registry_name, + "AI_WEB_AGENT_CONSOLE_MODEL_PROFILES", + ) + self.assertTrue(registry.default_profile.default_for_console) + def test_profile_environment_overrides_json_defaults(self) -> None: path = Path(__file__).parents[1] / "config" / "app_profile.json" with patch.dict( @@ -54,7 +64,7 @@ class DemoScenarioConfigTest(unittest.TestCase): self.assertEqual(profile.short_name, "HMM") def test_common_theme_covers_lists_expanders_and_secondary_buttons(self) -> None: - path = Path(__file__).parents[1] / "src" / "agent_console" / "presentation.py" + path = Path(__file__).parents[1] / "ai_web_agent_console" / "presentation.py" source = path.read_text(encoding="utf-8") self.assertIn('[data-testid="stAppViewContainer"] li', source) @@ -66,17 +76,20 @@ class DemoScenarioConfigTest(unittest.TestCase): def test_audit_tab_uses_hmm_access_audit_loaders(self) -> None: root = Path(__file__).parents[1] - entrypoint = (root / "apps" / "poc4" / "mcp_discovery_ui.py").read_text( + entrypoint = (root / "app.py").read_text( encoding="utf-8" ) - renderer = (root / "src" / "agent_console" / "audit.py").read_text( + renderer = (root / "ai_web_agent_console" / "audit.py").read_text( encoding="utf-8" ) self.assertIn("FROM ADMIN.HMM_ACCESS_AUDIT", entrypoint) self.assertIn("_load_hmm_audit_inventory", entrypoint) self.assertIn("(protocol=tcps)(port=1521)", entrypoint) - self.assertIn("POC4_AUDIT_WALLET_PASSWORD", entrypoint) + self.assertIn( + "AI_WEB_AGENT_CONSOLE_AUDIT_WALLET_PASSWORD", + entrypoint, + ) self.assertIn("HMM 접근 관리", renderer) self.assertNotIn('AUDIT_SCHEMA = "POC_2"', entrypoint) @@ -197,9 +210,9 @@ class DemoScenarioConfigTest(unittest.TestCase): has_actionable_text_result({"status": "success", "result": "No data found"}) ) - @unittest.skipIf(_load_poc4_query_helpers() is None, "Streamlit runtime is optional") + @unittest.skipIf(_load_console_query_helpers() is None, "Streamlit runtime is optional") def test_policy_query_does_not_include_demo_user_context(self) -> None: - prepare = _load_poc4_query_helpers() + prepare = _load_console_query_helpers() assert prepare is not None tool = McpTool( name="search_hr_policy", diff --git a/sql/adb/00_cleanup.sql b/database/adb/00_cleanup.sql similarity index 100% rename from sql/adb/00_cleanup.sql rename to database/adb/00_cleanup.sql diff --git a/sql/adb/01_dblinks.sql b/database/adb/01_dblinks.sql similarity index 100% rename from sql/adb/01_dblinks.sql rename to database/adb/01_dblinks.sql diff --git a/sql/adb/02_perm_tables.sql b/database/adb/02_perm_tables.sql similarity index 100% rename from sql/adb/02_perm_tables.sql rename to database/adb/02_perm_tables.sql diff --git a/sql/adb/03_seed.sql b/database/adb/03_seed.sql similarity index 98% rename from sql/adb/03_seed.sql rename to database/adb/03_seed.sql index 9223f3e..8205191 100644 --- a/sql/adb/03_seed.sql +++ b/database/adb/03_seed.sql @@ -80,7 +80,7 @@ COMMIT; -- COMMIT; -- -- The policy function vpd_region_filter handles CSV → IN-list --- conversion automatically. See sql/adb/06_policy.sql. +-- conversion automatically. See database/adb/06_policy.sql. -- ------------------------------------------------------------ PROMPT === Seed complete === diff --git a/sql/adb/04_secure_ctx.sql b/database/adb/04_secure_ctx.sql similarity index 100% rename from sql/adb/04_secure_ctx.sql rename to database/adb/04_secure_ctx.sql diff --git a/sql/adb/05_views.sql b/database/adb/05_views.sql similarity index 100% rename from sql/adb/05_views.sql rename to database/adb/05_views.sql diff --git a/sql/adb/06_policy.sql b/database/adb/06_policy.sql similarity index 100% rename from sql/adb/06_policy.sql rename to database/adb/06_policy.sql diff --git a/sql/adb/06a_redaction.sql b/database/adb/06a_redaction.sql similarity index 100% rename from sql/adb/06a_redaction.sql rename to database/adb/06a_redaction.sql diff --git a/sql/adb/07_end_users.sql b/database/adb/07_end_users.sql similarity index 100% rename from sql/adb/07_end_users.sql rename to database/adb/07_end_users.sql diff --git a/sql/adb/08_tests_user_my.sql b/database/adb/08_tests_user_my.sql similarity index 100% rename from sql/adb/08_tests_user_my.sql rename to database/adb/08_tests_user_my.sql diff --git a/sql/adb/09_tests_user_pg.sql b/database/adb/09_tests_user_pg.sql similarity index 100% rename from sql/adb/09_tests_user_pg.sql rename to database/adb/09_tests_user_pg.sql diff --git a/sql/adb/10_tests_user_both.sql b/database/adb/10_tests_user_both.sql similarity index 100% rename from sql/adb/10_tests_user_both.sql rename to database/adb/10_tests_user_both.sql diff --git a/sql/adb/11_tests_user_none.sql b/database/adb/11_tests_user_none.sql similarity index 100% rename from sql/adb/11_tests_user_none.sql rename to database/adb/11_tests_user_none.sql diff --git a/sql/adb/12_tests_admin_audit.sql b/database/adb/12_tests_admin_audit.sql similarity index 100% rename from sql/adb/12_tests_admin_audit.sql rename to database/adb/12_tests_admin_audit.sql diff --git a/sql/adb/13_dds_variant.sql b/database/adb/13_dds_variant.sql similarity index 100% rename from sql/adb/13_dds_variant.sql rename to database/adb/13_dds_variant.sql diff --git a/sql/adb/14_tests_dds_user.sql b/database/adb/14_tests_dds_user.sql similarity index 100% rename from sql/adb/14_tests_dds_user.sql rename to database/adb/14_tests_dds_user.sql diff --git a/sql/adb/15_dds_cleanup.sql b/database/adb/15_dds_cleanup.sql similarity index 100% rename from sql/adb/15_dds_cleanup.sql rename to database/adb/15_dds_cleanup.sql diff --git a/sql/adb/16_agent_ords_security_local_cleanup.sql b/database/adb/16_agent_ords_security_local_cleanup.sql similarity index 100% rename from sql/adb/16_agent_ords_security_local_cleanup.sql rename to database/adb/16_agent_ords_security_local_cleanup.sql diff --git a/sql/adb/17_agent_ords_security_local_vpd_setup.sql b/database/adb/17_agent_ords_security_local_vpd_setup.sql similarity index 100% rename from sql/adb/17_agent_ords_security_local_vpd_setup.sql rename to database/adb/17_agent_ords_security_local_vpd_setup.sql diff --git a/sql/adb/18_agent_ords_security_local_vpd_test.sql b/database/adb/18_agent_ords_security_local_vpd_test.sql similarity index 100% rename from sql/adb/18_agent_ords_security_local_vpd_test.sql rename to database/adb/18_agent_ords_security_local_vpd_test.sql diff --git a/sql/adb/19_agent_ords_security_local_dds_setup.sql b/database/adb/19_agent_ords_security_local_dds_setup.sql similarity index 100% rename from sql/adb/19_agent_ords_security_local_dds_setup.sql rename to database/adb/19_agent_ords_security_local_dds_setup.sql diff --git a/sql/adb/20_agent_ords_security_local_dds_test.sql b/database/adb/20_agent_ords_security_local_dds_test.sql similarity index 100% rename from sql/adb/20_agent_ords_security_local_dds_test.sql rename to database/adb/20_agent_ords_security_local_dds_test.sql diff --git a/sql/adb/21_agent_ords_security_ords_enable_schema.sql b/database/adb/21_agent_ords_security_ords_enable_schema.sql similarity index 100% rename from sql/adb/21_agent_ords_security_ords_enable_schema.sql rename to database/adb/21_agent_ords_security_ords_enable_schema.sql diff --git a/sql/adb/22_agent_ords_security_ords_handler_setup.sql b/database/adb/22_agent_ords_security_ords_handler_setup.sql similarity index 100% rename from sql/adb/22_agent_ords_security_ords_handler_setup.sql rename to database/adb/22_agent_ords_security_ords_handler_setup.sql diff --git a/sql/adb/23_agent_ords_security_ords_handler_test.sql b/database/adb/23_agent_ords_security_ords_handler_test.sql similarity index 100% rename from sql/adb/23_agent_ords_security_ords_handler_test.sql rename to database/adb/23_agent_ords_security_ords_handler_test.sql diff --git a/sql/adb/24_agent_ords_security_inventory.sql b/database/adb/24_agent_ords_security_inventory.sql similarity index 100% rename from sql/adb/24_agent_ords_security_inventory.sql rename to database/adb/24_agent_ords_security_inventory.sql diff --git a/sql/adb/25_agent_ords_security_backoffice_support.sql b/database/adb/25_agent_ords_security_backoffice_support.sql similarity index 100% rename from sql/adb/25_agent_ords_security_backoffice_support.sql rename to database/adb/25_agent_ords_security_backoffice_support.sql diff --git a/sql/adb/26_agent_ords_security_dynamic_vpd_filter.sql b/database/adb/26_agent_ords_security_dynamic_vpd_filter.sql similarity index 100% rename from sql/adb/26_agent_ords_security_dynamic_vpd_filter.sql rename to database/adb/26_agent_ords_security_dynamic_vpd_filter.sql diff --git a/sql/adb/27_agent_ords_security_dynamic_vpd_filter_test.sql b/database/adb/27_agent_ords_security_dynamic_vpd_filter_test.sql similarity index 99% rename from sql/adb/27_agent_ords_security_dynamic_vpd_filter_test.sql rename to database/adb/27_agent_ords_security_dynamic_vpd_filter_test.sql index 8b6b1e2..649c7e5 100644 --- a/sql/adb/27_agent_ords_security_dynamic_vpd_filter_test.sql +++ b/database/adb/27_agent_ords_security_dynamic_vpd_filter_test.sql @@ -3,7 +3,7 @@ -- Unit-style checks for cb_agent_doc_vpd_filter. -- -- Run after: --- @sql/adb/26_agent_ords_security_dynamic_vpd_filter.sql +-- @database/adb/26_agent_ords_security_dynamic_vpd_filter.sql -- ============================================================ WHENEVER SQLERROR EXIT SQL.SQLCODE SET ECHO OFF diff --git a/sql/adb/28_agent_ords_vector_tag_vpd_setup.sql b/database/adb/28_agent_ords_vector_tag_vpd_setup.sql similarity index 100% rename from sql/adb/28_agent_ords_vector_tag_vpd_setup.sql rename to database/adb/28_agent_ords_vector_tag_vpd_setup.sql diff --git a/sql/adb/29_agent_ords_vector_search_ords.sql b/database/adb/29_agent_ords_vector_search_ords.sql similarity index 100% rename from sql/adb/29_agent_ords_vector_search_ords.sql rename to database/adb/29_agent_ords_vector_search_ords.sql diff --git a/sql/adb/30_agent_ords_vector_tag_vpd_test.sql b/database/adb/30_agent_ords_vector_tag_vpd_test.sql similarity index 100% rename from sql/adb/30_agent_ords_vector_tag_vpd_test.sql rename to database/adb/30_agent_ords_vector_tag_vpd_test.sql diff --git a/sql/adb/31_agent_ords_backoffice_ux_metadata.sql b/database/adb/31_agent_ords_backoffice_ux_metadata.sql similarity index 100% rename from sql/adb/31_agent_ords_backoffice_ux_metadata.sql rename to database/adb/31_agent_ords_backoffice_ux_metadata.sql diff --git a/sql/adb/31_dds_standalone_demo_setup.sql b/database/adb/31_dds_standalone_demo_setup.sql similarity index 100% rename from sql/adb/31_dds_standalone_demo_setup.sql rename to database/adb/31_dds_standalone_demo_setup.sql diff --git a/sql/adb/32_dds_vector_tag_setup.sql b/database/adb/32_dds_vector_tag_setup.sql similarity index 100% rename from sql/adb/32_dds_vector_tag_setup.sql rename to database/adb/32_dds_vector_tag_setup.sql diff --git a/sql/adb/33_agent_ords_sql_trace_grant.sql b/database/adb/33_agent_ords_sql_trace_grant.sql similarity index 100% rename from sql/adb/33_agent_ords_sql_trace_grant.sql rename to database/adb/33_agent_ords_sql_trace_grant.sql diff --git a/sql/adb/34_agent_ords_execution_evidence_grant.sql b/database/adb/34_agent_ords_execution_evidence_grant.sql similarity index 100% rename from sql/adb/34_agent_ords_execution_evidence_grant.sql rename to database/adb/34_agent_ords_execution_evidence_grant.sql diff --git a/sql/adb/34_dds_token_data_grant_common_auth.sql b/database/adb/34_dds_token_data_grant_common_auth.sql similarity index 100% rename from sql/adb/34_dds_token_data_grant_common_auth.sql rename to database/adb/34_dds_token_data_grant_common_auth.sql diff --git a/sql/adb/35_dds_token_data_grant_cleanup.sql b/database/adb/35_dds_token_data_grant_cleanup.sql similarity index 100% rename from sql/adb/35_dds_token_data_grant_cleanup.sql rename to database/adb/35_dds_token_data_grant_cleanup.sql diff --git a/sql/adb/36_dds_sales_knowledge_scenario.sql b/database/adb/36_dds_sales_knowledge_scenario.sql similarity index 100% rename from sql/adb/36_dds_sales_knowledge_scenario.sql rename to database/adb/36_dds_sales_knowledge_scenario.sql diff --git a/sql/adb/37_dds_sales_knowledge_scenario_cleanup.sql b/database/adb/37_dds_sales_knowledge_scenario_cleanup.sql similarity index 100% rename from sql/adb/37_dds_sales_knowledge_scenario_cleanup.sql rename to database/adb/37_dds_sales_knowledge_scenario_cleanup.sql diff --git a/sql/adb/38_agent_ords_vector_all_permission_normalization.sql b/database/adb/38_agent_ords_vector_all_permission_normalization.sql similarity index 100% rename from sql/adb/38_agent_ords_vector_all_permission_normalization.sql rename to database/adb/38_agent_ords_vector_all_permission_normalization.sql diff --git a/sql/adb/39_agent_ords_default_user_display_name.sql b/database/adb/39_agent_ords_default_user_display_name.sql similarity index 100% rename from sql/adb/39_agent_ords_default_user_display_name.sql rename to database/adb/39_agent_ords_default_user_display_name.sql diff --git a/sql/adb/40_dds_protection_evidence.sql b/database/adb/40_dds_protection_evidence.sql similarity index 100% rename from sql/adb/40_dds_protection_evidence.sql rename to database/adb/40_dds_protection_evidence.sql diff --git a/sql/adb/41_dds_row_column_fixture.sql b/database/adb/41_dds_row_column_fixture.sql similarity index 100% rename from sql/adb/41_dds_row_column_fixture.sql rename to database/adb/41_dds_row_column_fixture.sql diff --git a/sql/adb/42_agent_ords_fga_execution_audit.sql b/database/adb/42_agent_ords_fga_execution_audit.sql similarity index 100% rename from sql/adb/42_agent_ords_fga_execution_audit.sql rename to database/adb/42_agent_ords_fga_execution_audit.sql diff --git a/sql/adb/42_dds_fga_execution_evidence.sql b/database/adb/42_dds_fga_execution_evidence.sql similarity index 100% rename from sql/adb/42_dds_fga_execution_evidence.sql rename to database/adb/42_dds_fga_execution_evidence.sql diff --git a/sql/adb/43_agent_ords_probe_id_handler_patch.sql b/database/adb/43_agent_ords_probe_id_handler_patch.sql similarity index 100% rename from sql/adb/43_agent_ords_probe_id_handler_patch.sql rename to database/adb/43_agent_ords_probe_id_handler_patch.sql diff --git a/sql/adb/44_dds_mcp_local_end_user_setup.sql b/database/adb/44_dds_mcp_local_end_user_setup.sql similarity index 100% rename from sql/adb/44_dds_mcp_local_end_user_setup.sql rename to database/adb/44_dds_mcp_local_end_user_setup.sql diff --git a/sql/adb/45_dds_mcp_local_end_user_test.sql b/database/adb/45_dds_mcp_local_end_user_test.sql similarity index 100% rename from sql/adb/45_dds_mcp_local_end_user_test.sql rename to database/adb/45_dds_mcp_local_end_user_test.sql diff --git a/sql/adb/46_kb_stakeholder_token_permission_example.sql b/database/adb/46_kb_stakeholder_token_permission_example.sql similarity index 100% rename from sql/adb/46_kb_stakeholder_token_permission_example.sql rename to database/adb/46_kb_stakeholder_token_permission_example.sql diff --git a/sql/adb/47_kb_stakeholder_permission_or_branches.sql b/database/adb/47_kb_stakeholder_permission_or_branches.sql similarity index 100% rename from sql/adb/47_kb_stakeholder_permission_or_branches.sql rename to database/adb/47_kb_stakeholder_permission_or_branches.sql diff --git a/sql/adb/48_kb_permission_condition_codes.sql b/database/adb/48_kb_permission_condition_codes.sql similarity index 100% rename from sql/adb/48_kb_permission_condition_codes.sql rename to database/adb/48_kb_permission_condition_codes.sql diff --git a/sql/adb/49_permission_static_sql_condition.sql b/database/adb/49_permission_static_sql_condition.sql similarity index 100% rename from sql/adb/49_permission_static_sql_condition.sql rename to database/adb/49_permission_static_sql_condition.sql diff --git a/sql/adb/51_kb_vpd_admin_token.sql b/database/adb/51_kb_vpd_admin_token.sql similarity index 100% rename from sql/adb/51_kb_vpd_admin_token.sql rename to database/adb/51_kb_vpd_admin_token.sql diff --git a/sql/adb/52_cleanup_to_kb_vpd_poc.sql b/database/adb/52_cleanup_to_kb_vpd_poc.sql similarity index 100% rename from sql/adb/52_cleanup_to_kb_vpd_poc.sql rename to database/adb/52_cleanup_to_kb_vpd_poc.sql diff --git a/sql/adb/53_drop_legacy_poc_objects.sql b/database/adb/53_drop_legacy_poc_objects.sql similarity index 100% rename from sql/adb/53_drop_legacy_poc_objects.sql rename to database/adb/53_drop_legacy_poc_objects.sql diff --git a/sql/adb/54_drop_legacy_dds_ords_examples.sql b/database/adb/54_drop_legacy_dds_ords_examples.sql similarity index 100% rename from sql/adb/54_drop_legacy_dds_ords_examples.sql rename to database/adb/54_drop_legacy_dds_ords_examples.sql diff --git a/sql/adb/55_reapply_kb_vpd_after_table_reload.sql b/database/adb/55_reapply_kb_vpd_after_table_reload.sql similarity index 100% rename from sql/adb/55_reapply_kb_vpd_after_table_reload.sql rename to database/adb/55_reapply_kb_vpd_after_table_reload.sql diff --git a/sql/adb/56_kb_select_ai_router_team.sql b/database/adb/56_kb_select_ai_router_team.sql similarity index 100% rename from sql/adb/56_kb_select_ai_router_team.sql rename to database/adb/56_kb_select_ai_router_team.sql diff --git a/sql/adb/57_kb_select_ai_router_team_test.sql b/database/adb/57_kb_select_ai_router_team_test.sql similarity index 100% rename from sql/adb/57_kb_select_ai_router_team_test.sql rename to database/adb/57_kb_select_ai_router_team_test.sql diff --git a/sql/adb/58_kb_select_ai_router_api.sql b/database/adb/58_kb_select_ai_router_api.sql similarity index 100% rename from sql/adb/58_kb_select_ai_router_api.sql rename to database/adb/58_kb_select_ai_router_api.sql diff --git a/sql/adb/59_kb_select_ai_router_ords.sql b/database/adb/59_kb_select_ai_router_ords.sql similarity index 100% rename from sql/adb/59_kb_select_ai_router_ords.sql rename to database/adb/59_kb_select_ai_router_ords.sql diff --git a/sql/adb/60_kb_vpd_admin_full_access_token.sql b/database/adb/60_kb_vpd_admin_full_access_token.sql similarity index 100% rename from sql/adb/60_kb_vpd_admin_full_access_token.sql rename to database/adb/60_kb_vpd_admin_full_access_token.sql diff --git a/sql/adb/62_kb_aso_masking_backoffice_metadata.sql b/database/adb/62_kb_aso_masking_backoffice_metadata.sql similarity index 100% rename from sql/adb/62_kb_aso_masking_backoffice_metadata.sql rename to database/adb/62_kb_aso_masking_backoffice_metadata.sql diff --git a/sql/adb/63_kb_aso_masking_rule_runtime.sql b/database/adb/63_kb_aso_masking_rule_runtime.sql similarity index 100% rename from sql/adb/63_kb_aso_masking_rule_runtime.sql rename to database/adb/63_kb_aso_masking_rule_runtime.sql diff --git a/sql/adb/64_kb_aso_masking_default_column_rules.sql b/database/adb/64_kb_aso_masking_default_column_rules.sql similarity index 100% rename from sql/adb/64_kb_aso_masking_default_column_rules.sql rename to database/adb/64_kb_aso_masking_default_column_rules.sql diff --git a/sql/adb/65_kb_select_ai_vpd_query_api.sql b/database/adb/65_kb_select_ai_vpd_query_api.sql similarity index 100% rename from sql/adb/65_kb_select_ai_vpd_query_api.sql rename to database/adb/65_kb_select_ai_vpd_query_api.sql diff --git a/sql/adb/66_kb_select_ai_vpd_query_ords.sql b/database/adb/66_kb_select_ai_vpd_query_ords.sql similarity index 100% rename from sql/adb/66_kb_select_ai_vpd_query_ords.sql rename to database/adb/66_kb_select_ai_vpd_query_ords.sql diff --git a/sql/adb/67_drop_legacy_kb_contracts_premium_vpd_cls_policy.sql b/database/adb/67_drop_legacy_kb_contracts_premium_vpd_cls_policy.sql similarity index 100% rename from sql/adb/67_drop_legacy_kb_contracts_premium_vpd_cls_policy.sql rename to database/adb/67_drop_legacy_kb_contracts_premium_vpd_cls_policy.sql diff --git a/sql/adb/70_sg_tool_user.sql b/database/adb/70_sg_tool_user.sql similarity index 100% rename from sql/adb/70_sg_tool_user.sql rename to database/adb/70_sg_tool_user.sql diff --git a/sql/adb/71_sg_identity_administration.sql b/database/adb/71_sg_identity_administration.sql similarity index 100% rename from sql/adb/71_sg_identity_administration.sql rename to database/adb/71_sg_identity_administration.sql diff --git a/sql/adb/72_hmm_leave_team_vpd.sql b/database/adb/72_hmm_leave_team_vpd.sql similarity index 100% rename from sql/adb/72_hmm_leave_team_vpd.sql rename to database/adb/72_hmm_leave_team_vpd.sql diff --git a/sql/adb/73_hmm_mcp_vpd_runtime.sql b/database/adb/73_hmm_mcp_vpd_runtime.sql similarity index 100% rename from sql/adb/73_hmm_mcp_vpd_runtime.sql rename to database/adb/73_hmm_mcp_vpd_runtime.sql diff --git a/sql/adb/73_hmm_rds_pg_00_precheck.sql b/database/adb/73_hmm_rds_pg_00_precheck.sql similarity index 100% rename from sql/adb/73_hmm_rds_pg_00_precheck.sql rename to database/adb/73_hmm_rds_pg_00_precheck.sql diff --git a/sql/adb/74_hmm_rds_pg_01_credential.sql b/database/adb/74_hmm_rds_pg_01_credential.sql similarity index 100% rename from sql/adb/74_hmm_rds_pg_01_credential.sql rename to database/adb/74_hmm_rds_pg_01_credential.sql diff --git a/sql/adb/75_hmm_rds_pg_02_acl.sql b/database/adb/75_hmm_rds_pg_02_acl.sql similarity index 100% rename from sql/adb/75_hmm_rds_pg_02_acl.sql rename to database/adb/75_hmm_rds_pg_02_acl.sql diff --git a/sql/adb/76_hmm_rds_pg_03_dblink.sql b/database/adb/76_hmm_rds_pg_03_dblink.sql similarity index 100% rename from sql/adb/76_hmm_rds_pg_03_dblink.sql rename to database/adb/76_hmm_rds_pg_03_dblink.sql diff --git a/sql/adb/77_hmm_rds_pg_04_remote_query.sql b/database/adb/77_hmm_rds_pg_04_remote_query.sql similarity index 100% rename from sql/adb/77_hmm_rds_pg_04_remote_query.sql rename to database/adb/77_hmm_rds_pg_04_remote_query.sql diff --git a/sql/adb/78_hmm_rds_pg_05_views.sql b/database/adb/78_hmm_rds_pg_05_views.sql similarity index 100% rename from sql/adb/78_hmm_rds_pg_05_views.sql rename to database/adb/78_hmm_rds_pg_05_views.sql diff --git a/sql/adb/79_hmm_rds_pg_06_verify.sql b/database/adb/79_hmm_rds_pg_06_verify.sql similarity index 100% rename from sql/adb/79_hmm_rds_pg_06_verify.sql rename to database/adb/79_hmm_rds_pg_06_verify.sql diff --git a/sql/adb/80_hmm_rds_pg_99_cleanup.sql b/database/adb/80_hmm_rds_pg_99_cleanup.sql similarity index 100% rename from sql/adb/80_hmm_rds_pg_99_cleanup.sql rename to database/adb/80_hmm_rds_pg_99_cleanup.sql diff --git a/sql/adb/81_hmm_carrier_access_groups.sql b/database/adb/81_hmm_carrier_access_groups.sql similarity index 100% rename from sql/adb/81_hmm_carrier_access_groups.sql rename to database/adb/81_hmm_carrier_access_groups.sql diff --git a/sql/adb/82_hmm_federation_relationship_comments.sql b/database/adb/82_hmm_federation_relationship_comments.sql similarity index 100% rename from sql/adb/82_hmm_federation_relationship_comments.sql rename to database/adb/82_hmm_federation_relationship_comments.sql diff --git a/sql/source/mysql_setup.sql b/database/source/mysql_setup.sql similarity index 96% rename from sql/source/mysql_setup.sql rename to database/source/mysql_setup.sql index 82530e6..5eb8e78 100644 --- a/sql/source/mysql_setup.sql +++ b/database/source/mysql_setup.sql @@ -1,5 +1,5 @@ -- ============================================================ --- sql/source/mysql_setup.sql +-- database/source/mysql_setup.sql -- Run on the REMOTE MySQL instance (mysql CLI). -- -- Creates the customers table in the `ecommerce_poc` schema with @@ -12,7 +12,7 @@ -- -- 호출 예시 (run.sh source 가 자동 실행): -- mysql -h $MY_HOST -P $MY_PORT -u $MY_USER -p"$MY_PASSWORD" \ --- $MY_DB < sql/source/mysql_setup.sql +-- $MY_DB < database/source/mysql_setup.sql -- ============================================================ SELECT '=== Creating ecommerce_poc.customers (idempotent) ===' AS status; diff --git a/sql/source/postgres_hmm_carrier_performance.sql b/database/source/postgres_hmm_carrier_performance.sql similarity index 98% rename from sql/source/postgres_hmm_carrier_performance.sql rename to database/source/postgres_hmm_carrier_performance.sql index 509895b..db766f6 100644 --- a/sql/source/postgres_hmm_carrier_performance.sql +++ b/database/source/postgres_hmm_carrier_performance.sql @@ -13,7 +13,7 @@ -- PGPASSWORD="$PG_PASSWORD" psql \ -- "host=$PG_HOST port=5432 dbname=postgres user=$PG_USER \ -- sslmode=verify-full sslrootcert=$PG_SSL_ROOT_CERT" \ --- -X -f sql/source/postgres_hmm_carrier_performance.sql +-- -X -f database/source/postgres_hmm_carrier_performance.sql -- ============================================================ \set ON_ERROR_STOP on @@ -297,4 +297,4 @@ ALTER DEFAULT PRIVILEGES IN SCHEMA hmm_demo COMMIT; \echo === HMM carrier performance demo: complete === -\echo Next: run sql/source/postgres_hmm_carrier_performance_verify.sql +\echo Next: run database/source/postgres_hmm_carrier_performance_verify.sql diff --git a/sql/source/postgres_hmm_carrier_performance_verify.sql b/database/source/postgres_hmm_carrier_performance_verify.sql similarity index 100% rename from sql/source/postgres_hmm_carrier_performance_verify.sql rename to database/source/postgres_hmm_carrier_performance_verify.sql diff --git a/sql/source/postgres_setup.sql b/database/source/postgres_setup.sql similarity index 96% rename from sql/source/postgres_setup.sql rename to database/source/postgres_setup.sql index 332b802..41cd911 100644 --- a/sql/source/postgres_setup.sql +++ b/database/source/postgres_setup.sql @@ -1,5 +1,5 @@ -- ============================================================ --- sql/source/postgres_setup.sql +-- database/source/postgres_setup.sql -- Run on the REMOTE PostgreSQL instance (psql). -- -- Creates the customers table in the `public` schema with sample @@ -10,7 +10,7 @@ -- -- 호출 예시 (run.sh source 가 자동 실행): -- PGPASSWORD=$PG_PASSWORD psql -h $PG_HOST -U $PG_USER -d $PG_DB \ --- -f sql/source/postgres_setup.sql +-- -f database/source/postgres_setup.sql -- ============================================================ \echo === Creating public.customers (idempotent) === diff --git a/dds-backoffice/README.md b/dds-backoffice/README.md index cd9d665..3c62f4c 100644 --- a/dds-backoffice/README.md +++ b/dds-backoffice/README.md @@ -56,10 +56,10 @@ ORDS Handler가 Bearer 값을 문자열로 바꾸는 것만으로 DDS Context가 먼저 DDS 데모 전용 로컬 데이터셋과 권한 객체를 적용합니다. 이 스크립트는 VPD VIEW나 외부 RDS DB Link를 사용하지 않습니다. ```bash -sqlplus "$ADB_USER/$ADB_PASSWORD@$ADB_TNS" @sql/adb/31_dds_standalone_demo_setup.sql -sqlplus "$ADB_USER/$ADB_PASSWORD@$ADB_TNS" @sql/adb/32_dds_vector_tag_setup.sql +sqlplus "$ADB_USER/$ADB_PASSWORD@$ADB_TNS" @database/adb/31_dds_standalone_demo_setup.sql +sqlplus "$ADB_USER/$ADB_PASSWORD@$ADB_TNS" @database/adb/32_dds_vector_tag_setup.sql bash scripts/setup-dds-token-data-grant.sh -sqlplus "$ADB_USER/$ADB_PASSWORD@$ADB_TNS" @sql/adb/36_dds_sales_knowledge_scenario.sql +sqlplus "$ADB_USER/$ADB_PASSWORD@$ADB_TNS" @database/adb/36_dds_sales_knowledge_scenario.sql ``` `32_dds_vector_tag_setup.sql`은 공통 청크·태그 저장소를 읽는 DDS 전용 VIEW를 만들고, 동일한 `CB_PERMISSION`/`CB_PERMISSION_RULE`에 역할별 TAG 규칙을 등록합니다. `SPRING_BOOT`, `ORDS`, `ORACLE_VPD`, `MCP`처럼 한 역할에 여러 TAG가 있으면 `/dds-provision`이 OR 조건으로 합쳐 DDS DATA GRANT를 다시 만듭니다. 벡터 검색에 필요한 `EMBEDDING`은 DDS 엔진이 거리 계산에 사용하므로 Grant에서 제외하지 않고, 검색 응답에서는 애플리케이션이 반환하지 않습니다. 그 외 민감 컬럼은 애플리케이션 권한의 원문 허용 목록에 없으면 `ALL COLUMNS EXCEPT`로 제외됩니다. 검색 화면에서는 같은 문서를 청킹·임베딩한 뒤 선택한 END USER로 직접 조회하므로, DDS Grant가 없는 주체는 보호 VIEW 자체를 볼 수 없습니다. @@ -68,7 +68,7 @@ sqlplus "$ADB_USER/$ADB_PASSWORD@$ADB_TNS" @sql/adb/36_dds_sales_knowledge_scena `36_dds_sales_knowledge_scenario.sql`은 `agent_sales`와 `SALES_KNOWLEDGE_ROLE`을 만들고 `TECH_TAG=SALES`인 세일즈 청크(28004)를 추가합니다. 짧은 데모 토큰 `dds_sales_demo_token`을 사용하면 결과는 28004만 남아야 합니다. 운영에서는 이 토큰 대신 Bearer 관리 화면에서 발급한 임시 토큰을 사용합니다. -토큰 경로를 제거할 때는 사용 중인 애플리케이션이 없는지 확인한 뒤 `sql/adb/37_dds_sales_knowledge_scenario_cleanup.sql`과 `sql/adb/35_dds_token_data_grant_cleanup.sql`을 별도로 승인해 실행합니다. +토큰 경로를 제거할 때는 사용 중인 애플리케이션이 없는지 확인한 뒤 `database/adb/37_dds_sales_knowledge_scenario_cleanup.sql`과 `database/adb/35_dds_token_data_grant_cleanup.sql`을 별도로 승인해 실행합니다. 그 다음 DDS 인스턴스를 실행합니다. diff --git a/poc4_active_source_20260714/infra/nginx/hmm-portal-auth.locations.conf b/deploy/ai-web-agent-console/nginx/hmm-portal-auth.locations.conf similarity index 100% rename from poc4_active_source_20260714/infra/nginx/hmm-portal-auth.locations.conf rename to deploy/ai-web-agent-console/nginx/hmm-portal-auth.locations.conf diff --git a/poc4_active_source_20260714/deploy/systemd/poc4-streamlit-audit.conf b/deploy/ai-web-agent-console/systemd/ai-web-agent-console-audit.conf similarity index 100% rename from poc4_active_source_20260714/deploy/systemd/poc4-streamlit-audit.conf rename to deploy/ai-web-agent-console/systemd/ai-web-agent-console-audit.conf diff --git a/poc4_active_source_20260714/infra/systemd/hmm-portal-auth.service b/deploy/ai-web-agent-console/systemd/hmm-portal-auth.service similarity index 88% rename from poc4_active_source_20260714/infra/systemd/hmm-portal-auth.service rename to deploy/ai-web-agent-console/systemd/hmm-portal-auth.service index 80899ee..bc9e67f 100644 --- a/poc4_active_source_20260714/infra/systemd/hmm-portal-auth.service +++ b/deploy/ai-web-agent-console/systemd/hmm-portal-auth.service @@ -10,7 +10,7 @@ Group=opc WorkingDirectory=/opt/hmm-poc4 Environment=PYTHONUNBUFFERED=1 EnvironmentFile=/opt/hmm-poc4/.env -ExecStart=/opt/hmm-poc4/.venv/bin/python -m src.agent_console.auth_gateway +ExecStart=/opt/hmm-poc4/.venv/bin/python -m ai_web_agent_console.auth_gateway Restart=on-failure RestartSec=3 NoNewPrivileges=true diff --git a/deploy/hmm-backoffice.env.example b/deploy/vpd-backoffice/backoffice.env.example similarity index 100% rename from deploy/hmm-backoffice.env.example rename to deploy/vpd-backoffice/backoffice.env.example diff --git a/deploy/caddy/Caddyfile.template b/deploy/vpd-backoffice/caddy/Caddyfile.template similarity index 100% rename from deploy/caddy/Caddyfile.template rename to deploy/vpd-backoffice/caddy/Caddyfile.template diff --git a/deploy/caddy/install-caddy-config.sh b/deploy/vpd-backoffice/caddy/install-caddy-config.sh similarity index 100% rename from deploy/caddy/install-caddy-config.sh rename to deploy/vpd-backoffice/caddy/install-caddy-config.sh diff --git a/deploy/systemd/vpd-backoffice-structured-data.conf b/deploy/vpd-backoffice/vpd-backoffice-structured-data.conf similarity index 100% rename from deploy/systemd/vpd-backoffice-structured-data.conf rename to deploy/vpd-backoffice/vpd-backoffice-structured-data.conf diff --git a/docs/03-detailed-guide.md b/docs/03-detailed-guide.md index 5a0335f..806aecd 100644 --- a/docs/03-detailed-guide.md +++ b/docs/03-detailed-guide.md @@ -8,7 +8,7 @@ > 단위 매트릭스** 입니다 (README 참고). 본 상세 가이드는 그 위에 얹을 수 있는 **행 > 단위 region 필터링** 변형(`KR_ANALYSTS → APAC`, `GLOBAL_ADMINS → '*'`) 을 예시로 > 사용합니다 — VPD 메커니즘 자체는 동일하므로 개념 이해에는 차이가 없습니다. -> region 필터를 실제로 켜려면 `sql/adb/03_seed.sql` 하단의 주석을 해제하세요. +> region 필터를 실제로 켜려면 `database/adb/03_seed.sql` 하단의 주석을 해제하세요. --- @@ -543,7 +543,7 @@ SQL> SELECT COUNT(*) FROM admin.v_customers_pg; -- 0 SQL> SELECT COUNT(*) FROM admin.v_customers_my; -- 0 # region 필터 변형을 켰을 때 — vpduser_both 에게 APAC 만 허용한 경우 -# (sql/adb/03_seed.sql 하단 UPDATE 주석 해제 후) +# (database/adb/03_seed.sql 하단 UPDATE 주석 해제 후) sqlplus "vpduser_both/\"${VPDUSER_BOTH_PASSWORD}\"@$ADB_TNS" SQL> SELECT region, COUNT(*) FROM admin.v_customers_pg GROUP BY region; -- 결과: APAC 만 보임 diff --git a/docs/04-source-setup.md b/docs/04-source-setup.md index ffb49cb..d68e785 100644 --- a/docs/04-source-setup.md +++ b/docs/04-source-setup.md @@ -4,7 +4,7 @@ --- -## Postgres (`sql/source/postgres_setup.sql`) +## Postgres (`database/source/postgres_setup.sql`) ``` DB : 사용자가 .env 에서 지정 (PG_DB, 기본값 vpdpoc) @@ -22,12 +22,12 @@ seed rows : 12 (APAC 4 / EMEA 4 / AMER 4) ```bash PGPASSWORD=$PG_PASSWORD psql \ -h $PG_HOST -p $PG_PORT -U $PG_USER -d $PG_DB \ - -f sql/source/postgres_setup.sql + -f database/source/postgres_setup.sql ``` --- -## MySQL (`sql/source/mysql_setup.sql`) +## MySQL (`database/source/mysql_setup.sql`) ``` DB : MY_DB (기본 ecommerce_poc) @@ -47,12 +47,12 @@ PK 범위를 PG (1~12) 와 다르게 가져간 이유: ```bash mysql -h $MY_HOST -P $MY_PORT -u $MY_USER -p"$MY_PASSWORD" $MY_DB \ - < sql/source/mysql_setup.sql + < database/source/mysql_setup.sql ``` --- -## ADB → 원격 DB Link (`sql/adb/01_dblinks.sql`) +## ADB → 원격 DB Link (`database/adb/01_dblinks.sql`) `DBMS_CLOUD_ADMIN.CREATE_DATABASE_LINK` 가 ADB 안에서 직접 heterogeneous 연결을 처리합니다. 별도 Database Gateway 설치 불필요. diff --git a/docs/05-dds-variant.md b/docs/05-dds-variant.md index f6a35bf..8c835fc 100644 --- a/docs/05-dds-variant.md +++ b/docs/05-dds-variant.md @@ -2,7 +2,7 @@ > 본 문서는 **선택사항** 입니다. 메인 데모(`run.sh all`) 는 전통적 **VPD** 경로로 > 그대로 동작합니다. 이 문서는 동일한 4-user 매트릭스를 Oracle AI Database -> **26ai 신기능 Deep Data Security (DDS)** 로 재구현한 `sql/adb/13_dds_variant.sql` +> **26ai 신기능 Deep Data Security (DDS)** 로 재구현한 `database/adb/13_dds_variant.sql` > 의 사용법과, VPD ↔ DDS 1:1 매핑을 다룹니다. > > 본 변형은 **2026-05-26 실제 ADB (23.26.2.2.0) 에서 E2E 검증** 됨 (8장 결과). @@ -84,7 +84,7 @@ Oracle 이 **2026-04-09** 에 VPD/RAS 의 공식 후계자로 발표한 native a ```bash source .env # DDSUSER_*_PASSWORD 로드 -sqlplus "$ADB_USER/$ADB_PASSWORD@$ADB_TNS" @sql/adb/13_dds_variant.sql +sqlplus "$ADB_USER/$ADB_PASSWORD@$ADB_TNS" @database/adb/13_dds_variant.sql ``` 스크립트가 만드는 객체: @@ -218,7 +218,7 @@ DROP VIEW v_dds_customers_my; ## 8. E2E 검증 결과 (2026-05-26, ADB 23.26.2.2.0) -`sql/adb/13_dds_variant.sql` 실행 후 4명의 ddsuser 로 `v_dds_customers_*` 조회: +`database/adb/13_dds_variant.sql` 실행 후 4명의 ddsuser 로 `v_dds_customers_*` 조회: | 사용자 | `v_dds_customers_pg` | `v_dds_customers_my` | 판정 | |---|---|---|---| diff --git a/docs/06-agent-ords-vpd-dds-security-brief.md b/docs/06-agent-ords-vpd-dds-security-brief.md index d98cf64..6d0003a 100644 --- a/docs/06-agent-ords-vpd-dds-security-brief.md +++ b/docs/06-agent-ords-vpd-dds-security-brief.md @@ -1165,15 +1165,15 @@ ORDER BY rg.grantee, dg.object_name, dg.grant_name; | 파일 | 역할 | |---|---| | `scripts/run_agent_ords_security_adb_local.sh` | 전체 실행 | -| `sql/adb/16_agent_ords_security_local_cleanup.sql` | `CB_*` 예제 객체 정리 | -| `sql/adb/17_agent_ords_security_local_vpd_setup.sql` | VPD용 로컬 테이블, 권한 테이블, context, redaction, 정책 생성 | -| `sql/adb/18_agent_ords_security_local_vpd_test.sql` | `CB_ORDS`로 Bearer Key 기반 VPD 테스트 | -| `sql/adb/19_agent_ords_security_local_dds_setup.sql` | DDS용 로컬 테이블, END USER, DATA ROLE, DATA GRANT 생성 | -| `sql/adb/20_agent_ords_security_local_dds_test.sql` | DDS end user별 조회 테스트 | -| `sql/adb/21_agent_ords_security_ords_enable_schema.sql` | `CB_ORDS` schema를 ORDS에 enable | -| `sql/adb/22_agent_ords_security_ords_handler_setup.sql` | ORDS Module/Handler와 Handler Package 생성 | -| `sql/adb/23_agent_ords_security_ords_handler_test.sql` | Handler Package 직접 실행으로 VPD/DDS Bearer 경로 검증 | -| `sql/adb/24_agent_ords_security_inventory.sql` | VPD/DDS 정책과 권한을 중앙 조회 | +| `database/adb/16_agent_ords_security_local_cleanup.sql` | `CB_*` 예제 객체 정리 | +| `database/adb/17_agent_ords_security_local_vpd_setup.sql` | VPD용 로컬 테이블, 권한 테이블, context, redaction, 정책 생성 | +| `database/adb/18_agent_ords_security_local_vpd_test.sql` | `CB_ORDS`로 Bearer Key 기반 VPD 테스트 | +| `database/adb/19_agent_ords_security_local_dds_setup.sql` | DDS용 로컬 테이블, END USER, DATA ROLE, DATA GRANT 생성 | +| `database/adb/20_agent_ords_security_local_dds_test.sql` | DDS end user별 조회 테스트 | +| `database/adb/21_agent_ords_security_ords_enable_schema.sql` | `CB_ORDS` schema를 ORDS에 enable | +| `database/adb/22_agent_ords_security_ords_handler_setup.sql` | ORDS Module/Handler와 Handler Package 생성 | +| `database/adb/23_agent_ords_security_ords_handler_test.sql` | Handler Package 직접 실행으로 VPD/DDS Bearer 경로 검증 | +| `database/adb/24_agent_ords_security_inventory.sql` | VPD/DDS 정책과 권한을 중앙 조회 | ### 7.1 VPD + Redaction 실행 검증 @@ -1634,7 +1634,7 @@ ORA-20101: Authorization header must be Bearer ### 7.4 중앙 권한 인벤토리 -VPD와 DDS 모두 적용 결과를 Dictionary View로 확인할 수 있다. 이 예제에서는 `sql/adb/24_agent_ords_security_inventory.sql`을 실행해 정책 연결과 DDS Grant Matrix를 확인한다. +VPD와 DDS 모두 적용 결과를 Dictionary View로 확인할 수 있다. 이 예제에서는 `database/adb/24_agent_ords_security_inventory.sql`을 실행해 정책 연결과 DDS Grant Matrix를 확인한다. VPD 정책 연결 확인: diff --git a/docs/design/424-spring-boot-vpd-backoffice/README.md b/docs/design/424-spring-boot-vpd-backoffice/README.md index 9440080..ee761f2 100644 --- a/docs/design/424-spring-boot-vpd-backoffice/README.md +++ b/docs/design/424-spring-boot-vpd-backoffice/README.md @@ -3,7 +3,7 @@ > **상태**: Draft > **작성**: [AI] Architect · **최종수정**: 2026-06-23 > **추적성** — Redmine: #424 · 관련 ADR: 없음 -> · 구현 파일: `pom.xml`, `src/main/**`, `src/test/**` · 테스트: `mvn test` +> · 구현 파일: `vpd-backoffice/pom.xml`, `vpd-backoffice/src/main/**`, `vpd-backoffice/src/test/**` · 테스트: `mvn test` ## 1. 목적 (Why) @@ -58,8 +58,8 @@ ### 모듈/파일 구조 ```text -pom.xml -src/main/java/com/cloudhandson/vpdbackoffice/ +vpd-backoffice/pom.xml +vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/ VpdBackofficeApplication.java config/ DataSourceConfig.java @@ -88,13 +88,13 @@ src/main/java/com/cloudhandson/vpdbackoffice/ TokenController.java ProbeController.java DashboardController.java -src/main/resources/ +vpd-backoffice/src/main/resources/ application.yml mapper/*.xml templates/**/*.html static/css/app.css static/js/app.js -src/test/java/com/cloudhandson/vpdbackoffice/ +vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/ ``` ### 데이터 흐름 @@ -251,7 +251,7 @@ MCP-style reasoning ## 12. 미해결 질문 (Open Questions) -- ORDS 검증 API는 기존 `sql/adb/22_agent_ords_security_ords_handler_setup.sql`의 Handler를 그대로 사용할지, 백오피스 전용 Handler를 추가할지 결정이 필요하다. +- ORDS 검증 API는 기존 `database/adb/22_agent_ords_security_ords_handler_setup.sql`의 Handler를 그대로 사용할지, 백오피스 전용 Handler를 추가할지 결정이 필요하다. - 백오피스 관리자 로그인은 초기에는 local user로 둘지, 사내 인증과 연결할지 후속 결정이 필요하다. - 컬럼 정책을 Redaction DDL까지 자동 생성할지, 관리 테이블 저장 후 DBA 적용으로 둘지 결정이 필요하다. - 실제 구현 issue를 별도 Redmine 하위 이슈로 나눌지, #424를 Developer 단계로 계속 이동할지 결정이 필요하다. diff --git a/docs/design/456-vpd-predicate-rule-tests/README.md b/docs/design/456-vpd-predicate-rule-tests/README.md index 1f5475d..e27797f 100644 --- a/docs/design/456-vpd-predicate-rule-tests/README.md +++ b/docs/design/456-vpd-predicate-rule-tests/README.md @@ -3,7 +3,7 @@ > **상태**: Approved > **작성**: [AI] Architect · **최종수정**: 2026-06-25 > **추적성** — Redmine: #456 · 관련 ADR: 없음 -> · 구현 파일: `sql/adb/26_agent_ords_security_dynamic_vpd_filter.sql`, `sql/adb/27_agent_ords_security_dynamic_vpd_filter_test.sql` · 테스트: `sql/adb/27_agent_ords_security_dynamic_vpd_filter_test.sql`, `mvn test` +> · 구현 파일: `database/adb/26_agent_ords_security_dynamic_vpd_filter.sql`, `database/adb/27_agent_ords_security_dynamic_vpd_filter_test.sql` · 테스트: `database/adb/27_agent_ords_security_dynamic_vpd_filter_test.sql`, `mvn test` ## 1. 목적 (Why) @@ -78,8 +78,8 @@ I/O와 순수 로직 경계: ## 10. 테스트 계획 -- SQLcl: `@sql/adb/26_agent_ords_security_dynamic_vpd_filter.sql` -- SQLcl: `@sql/adb/27_agent_ords_security_dynamic_vpd_filter_test.sql` +- SQLcl: `@database/adb/26_agent_ords_security_dynamic_vpd_filter.sql` +- SQLcl: `@database/adb/27_agent_ords_security_dynamic_vpd_filter_test.sql` - Maven: `mvn test` - ORDS smoke: 기존 HR=3, SELF=1, ALL=6 결과 유지 확인. diff --git a/docs/design/456-vpd-predicate-rule-tests/fn-cb_agent_doc_vpd_filter.md b/docs/design/456-vpd-predicate-rule-tests/fn-cb_agent_doc_vpd_filter.md index 9db348a..4148a25 100644 --- a/docs/design/456-vpd-predicate-rule-tests/fn-cb_agent_doc_vpd_filter.md +++ b/docs/design/456-vpd-predicate-rule-tests/fn-cb_agent_doc_vpd_filter.md @@ -1,7 +1,7 @@ # 함수 설계서: `cb_agent_doc_vpd_filter` (#456) > **부모 설계서**: ./README.md · **상태**: Approved -> **작성**: [AI] Architect · **구현**: `sql/adb/26_agent_ords_security_dynamic_vpd_filter.sql:cb_agent_doc_vpd_filter` · **테스트**: `sql/adb/27_agent_ords_security_dynamic_vpd_filter_test.sql` +> **작성**: [AI] Architect · **구현**: `database/adb/26_agent_ords_security_dynamic_vpd_filter.sql:cb_agent_doc_vpd_filter` · **테스트**: `database/adb/27_agent_ords_security_dynamic_vpd_filter_test.sql` ## 1. 시그니처 diff --git a/docs/design/462-predicate-injection-defense/README.md b/docs/design/462-predicate-injection-defense/README.md index 2ffe20e..97cb16d 100644 --- a/docs/design/462-predicate-injection-defense/README.md +++ b/docs/design/462-predicate-injection-defense/README.md @@ -3,7 +3,7 @@ > **상태**: Approved > **작성**: [AI] Architect · **최종수정**: 2026-06-25 > **추적성** — Redmine: #462 · 관련 ADR: 없음 -> · 구현 파일: `sql/adb/27_agent_ords_security_dynamic_vpd_filter_test.sql` · 테스트: SQLcl `27`, `./run.sh backoffice-vpd-ords-test`, `mvn test` +> · 구현 파일: `database/adb/27_agent_ords_security_dynamic_vpd_filter_test.sql` · 테스트: SQLcl `27`, `./run.sh backoffice-vpd-ords-test`, `mvn test` ## 1. 목적 (Why) @@ -67,8 +67,8 @@ malicious cb_permission_rule rows ## 10. 테스트 계획 -- `@sql/adb/26_agent_ords_security_dynamic_vpd_filter.sql` -- `@sql/adb/27_agent_ords_security_dynamic_vpd_filter_test.sql` +- `@database/adb/26_agent_ords_security_dynamic_vpd_filter.sql` +- `@database/adb/27_agent_ords_security_dynamic_vpd_filter_test.sql` - `./run.sh backoffice-vpd-ords-test` - `mvn test` diff --git a/docs/design/547-vm-https-hardening/README.md b/docs/design/547-vm-https-hardening/README.md index 8a1fbc7..767b5f9 100644 --- a/docs/design/547-vm-https-hardening/README.md +++ b/docs/design/547-vm-https-hardening/README.md @@ -3,7 +3,7 @@ > **상태**: Approved > **작성**: [AI] Architect · **최종수정**: 2026-06-28 > **추적성** — Redmine: #547 · 관련 ADR: 없음 -> · 구현 파일: `src/main/resources/application.yml`, `SecurityConfig.java`, `scripts/deploy-backoffice-vm.sh`, `scripts/configure-backoffice-https-vm.sh`, `deploy/caddy/Caddyfile.template` +> · 구현 파일: `vpd-backoffice/src/main/resources/application.yml`, `SecurityConfig.java`, `scripts/deploy-backoffice-vm.sh`, `scripts/configure-backoffice-https-vm.sh`, `deploy/vpd-backoffice/caddy/Caddyfile.template` > · 테스트: `TransportSecurityTest.java`, `scripts/test-backoffice-https-config.sh` ## 1. 목적 (Why) @@ -49,7 +49,7 @@ Internet client Internet client ── HTTP :8082 ──X (loopback bind + firewalld/NSG deny) ``` -- `deploy/caddy/Caddyfile.template`: TLS/redirect/HSTS/reverse proxy의 선언적 설정. +- `deploy/vpd-backoffice/caddy/Caddyfile.template`: TLS/redirect/HSTS/reverse proxy의 선언적 설정. - `scripts/configure-backoffice-https-vm.sh`: 입력 검증, 원격 설정 검증·백업·적용·확인. - `scripts/deploy-backoffice-vm.sh`: 앱 배포 시 운영 보안 환경값을 강제하고 루프백 헬스체크. - Spring 설정: proxy가 전달한 HTTPS scheme을 인식하고 직접 HTTP 요청을 거부한다. diff --git a/docs/design/559-independent-vpd-dds-demos/README.md b/docs/design/559-independent-vpd-dds-demos/README.md index 7cb30bb..2d5775b 100644 --- a/docs/design/559-independent-vpd-dds-demos/README.md +++ b/docs/design/559-independent-vpd-dds-demos/README.md @@ -2,7 +2,7 @@ > **상태**: VPD·DDS 병행 검증 완료, 토큰 기반 객체별 Data Grant 적용 완료 > **추적성**: Redmine #565, #566 · DDS 구현 커밋 `1e48864`, `a5e70bc`, `04cca64`, `8978be4` -> **기준 구현**: `sql/adb/32_dds_vector_tag_setup.sql`, `sql/adb/34_dds_token_data_grant_common_auth.sql`, `sql/adb/36_dds_sales_knowledge_scenario.sql`, `dds-backoffice/src/main/java/com/cloudhandson/ddsbackoffice/service/DdsGrantPublisher.java`, `dds-backoffice/src/main/java/com/cloudhandson/ddsbackoffice/service/DdsVectorKnowledgeService.java` +> **기준 구현**: `database/adb/32_dds_vector_tag_setup.sql`, `database/adb/34_dds_token_data_grant_common_auth.sql`, `database/adb/36_dds_sales_knowledge_scenario.sql`, `dds-backoffice/src/main/java/com/cloudhandson/ddsbackoffice/service/DdsGrantPublisher.java`, `dds-backoffice/src/main/java/com/cloudhandson/ddsbackoffice/service/DdsVectorKnowledgeService.java` ## 목적 diff --git a/docs/design/567-ords-vpd-sql-trace/README.md b/docs/design/567-ords-vpd-sql-trace/README.md index af908b4..1ff3520 100644 --- a/docs/design/567-ords-vpd-sql-trace/README.md +++ b/docs/design/567-ords-vpd-sql-trace/README.md @@ -77,8 +77,8 @@ Authorization: Bearer ## 설치와 운영 절차 -1. `ADMIN`으로 [42_agent_ords_fga_execution_audit.sql](../../../sql/adb/42_agent_ords_fga_execution_audit.sql)을 실행해 활성 보호 객체마다 FGA `SELECT` 정책을 만든다. -2. `CB_ORDS`로 [22_agent_ords_security_ords_handler_setup.sql](../../../sql/adb/22_agent_ords_security_ords_handler_setup.sql), [29_agent_ords_vector_search_ords.sql](../../../sql/adb/29_agent_ords_vector_search_ords.sql), [43_agent_ords_probe_id_handler_patch.sql](../../../sql/adb/43_agent_ords_probe_id_handler_patch.sql)을 순서대로 실행한다. +1. `ADMIN`으로 [42_agent_ords_fga_execution_audit.sql](../../../database/adb/42_agent_ords_fga_execution_audit.sql)을 실행해 활성 보호 객체마다 FGA `SELECT` 정책을 만든다. +2. `CB_ORDS`로 [22_agent_ords_security_ords_handler_setup.sql](../../../database/adb/22_agent_ords_security_ords_handler_setup.sql), [29_agent_ords_vector_search_ords.sql](../../../database/adb/29_agent_ords_vector_search_ords.sql), [43_agent_ords_probe_id_handler_patch.sql](../../../database/adb/43_agent_ords_probe_id_handler_patch.sql)을 순서대로 실행한다. 3. 새 권한 결과 확인을 실행한다. 이전 요청에는 `CLIENT_ID`가 없으므로 소급해 매칭하지 않는다. 4. 백오피스 실행 계정이 Autonomous의 `UNIFIED_AUDIT_TRAIL`을 조회할 수 있어야 한다. 전통 FGA 환경은 `DBA_FGA_AUDIT_TRAIL`을 fallback으로 사용한다. 일반 계정이면 감사 조회 권한을 가진 전용 observer 계정을 사용한다. diff --git a/docs/design/567-ords-vpd-sql-trace/fn-addLocalSqlTrace.md b/docs/design/567-ords-vpd-sql-trace/fn-addLocalSqlTrace.md index 70f229e..a9e4007 100644 --- a/docs/design/567-ords-vpd-sql-trace/fn-addLocalSqlTrace.md +++ b/docs/design/567-ords-vpd-sql-trace/fn-addLocalSqlTrace.md @@ -1,7 +1,7 @@ # 함수 설계서: `addLocalSqlTrace` (#567) > **부모 설계서**: ./README.md · **상태**: Approved -> **작성**: [AI] Architect · **구현**: `src/main/java/com/cloudhandson/vpdbackoffice/service/OrdsProbeService.java:addLocalSqlTrace` · **테스트**: `ProbeResultTest`, `mvn test` +> **작성**: [AI] Architect · **구현**: `vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/OrdsProbeService.java:addLocalSqlTrace` · **테스트**: `ProbeResultTest`, `mvn test` ## 1. 시그니처 diff --git a/docs/design/567-ords-vpd-sql-trace/fn-findVpdPredicate.md b/docs/design/567-ords-vpd-sql-trace/fn-findVpdPredicate.md index a175bfe..46936d4 100644 --- a/docs/design/567-ords-vpd-sql-trace/fn-findVpdPredicate.md +++ b/docs/design/567-ords-vpd-sql-trace/fn-findVpdPredicate.md @@ -1,7 +1,7 @@ # 함수 설계서: `findVpdPredicate` (#567) > **부모 설계서**: ./README.md · **상태**: Approved -> **작성**: [AI] Architect · **구현**: `src/main/java/com/cloudhandson/vpdbackoffice/service/OrdsProbeService.java:findVpdPredicate` · **테스트**: `mvn test`, 운영 smoke +> **작성**: [AI] Architect · **구현**: `vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/OrdsProbeService.java:findVpdPredicate` · **테스트**: `mvn test`, 운영 smoke ## 1. 시그니처 diff --git a/docs/design/617-dds-mcp-end-user-context/README.md b/docs/design/617-dds-mcp-end-user-context/README.md index 135e862..7ddb6dc 100644 --- a/docs/design/617-dds-mcp-end-user-context/README.md +++ b/docs/design/617-dds-mcp-end-user-context/README.md @@ -4,8 +4,8 @@ > **최종수정**: 2026-07-19 > **추적성** — Redmine: #617 · 관련 ADR: [ADR-0001](../../adr/0001-dds-mcp-service-identity.md), [ADR-0002](../../adr/0002-dds-runtime-permission-source.md) > · 현재 구현: `DdsMcpBearerAuthenticator`, `DdsMcpEndUserResolver`, `DdsMcpContextExecutor`, `DdsMcpSseController` -> · 기존 PoC 게시: `sql/adb/44_dds_mcp_local_end_user_setup.sql` -> · 검증: `sql/adb/45_dds_mcp_local_end_user_test.sql`, SSE `tools/call` +> · 기존 PoC 게시: `database/adb/44_dds_mcp_local_end_user_setup.sql` +> · 검증: `database/adb/45_dds_mcp_local_end_user_test.sql`, SSE `tools/call` ## 1. 결정과 목적 diff --git a/docs/design/654-poc4-mcp-ai-console-vpd-audit/README.md b/docs/design/654-poc4-mcp-ai-console-vpd-audit/README.md index 2cb0ec7..a920a95 100644 --- a/docs/design/654-poc4-mcp-ai-console-vpd-audit/README.md +++ b/docs/design/654-poc4-mcp-ai-console-vpd-audit/README.md @@ -3,7 +3,9 @@ > **상태**: Implemented snapshot documented · follow-up contract pending > **작성**: [AI] Architect · **최종수정**: 2026-07-14 > **추적성** — Redmine: #654 · 관련 ADR: 없음 · 구현 커밋: `5bdd242`, `33399f9`, `1f6d974` -> · 스냅샷: `poc4_active_source_20260714/` · 원본 archive: `poc4_active_source_20260714/poc4_active_source_20260714.tar.gz` · 현행 구현 파일: `apps/poc4/mcp_discovery_ui.py`, `src/mcp_tool_router.py`, `src/oci_genai_sdk.py`, `src/poc3/model_registry.py`, `config/mcp_servers.json` · 테스트: Python 3.11 `py_compile`, 기존 백오피스 `mvn -q test` +> · 현재 소스: `ai-web-agent-console/` · 현행 구현 파일: `ai-web-agent-console/app.py`, `ai-web-agent-console/ai_web_agent_console/mcp_tool_router.py`, `ai-web-agent-console/ai_web_agent_console/oci_genai_sdk.py`, `ai-web-agent-console/ai_web_agent_console/model_registry.py`, `ai-web-agent-console/config/mcp_servers.json` · 테스트: Python 3.11 `compileall`/`unittest`, `mvn -f vpd-backoffice/pom.xml test` +> +> 이 문서의 PoC4 명칭과 원격 archive 경로는 2026-07-14 당시의 이력이다. 현재 저장소 구조는 #742 설계서를 기준으로 한다. ## 1. 목적 (Why) @@ -17,17 +19,17 @@ PoC4 MCP AI Console이 KB VPD MCP를 사용자별 Bearer 토큰으로 안전하 | 영역 | 현행 구현 | |---|---| -| 저장소 위치 | `poc4_active_source_20260714/` 단일 폴더에 격리 | -| 실행 진입점 | 직접 실행은 `streamlit run apps/poc4/mcp_discovery_ui.py --server.address 0.0.0.0 --server.port 8622` | +| 저장소 위치 | `ai-web-agent-console/` 단일 폴더에 격리 | +| 실행 진입점 | 직접 실행은 `streamlit run ai-web-agent-console/app.py --server.address 0.0.0.0 --server.port 8622` | | Python 런타임 | Python 3.11 이상. 배포 서버 검증 런타임은 `/home/opc/poc_4/.python-runtime/cpython-3.11.15+20260610/bin/python3.11` | -| Streamlit 화면 | `apps/poc4/mcp_discovery_ui.py` 단일 대형 UI. KB 테마, 포털 로그인, 대화 이력, MCP discovery/call, evidence 수집, 답변 합성을 포함 | -| MCP registry | `config/mcp_servers.json`. `kb_mcp`와 `kb_vector_mcp` 두 서버를 선언 | +| Streamlit 화면 | `ai-web-agent-console/app.py` 단일 대형 UI. KB 테마, 포털 로그인, 대화 이력, MCP discovery/call, evidence 수집, 답변 합성을 포함 | +| MCP registry | `ai-web-agent-console/config/mcp_servers.json` | | KB 정형 MCP | `https://kb.cloud-handson.com/mcp`, 기본 tool `ords.query.kb_select_ai_vpd` | | KB 벡터 MCP | `http://127.0.0.1:9978/mcp`, allowlist `hybrid_rerank_search` | -| 토큰 preset | `config/vpd_token_presets.json`. 실제 토큰 원문은 포함하지 않고 placeholder만 둠 | +| 토큰 preset | `ai-web-agent-console/config/vpd_token_presets.json`. 실제 토큰 원문은 포함하지 않고 placeholder만 둠 | | 대화 저장소 | 기본 `data/poc4_mcp_chat.sqlite3`, 환경변수 `POC4_CHAT_DB_PATH`로 변경 가능 | | DB evidence 연결 | 기본 env file `/home/opc/kbmcp/.env`, wallet fallback `/home/opc/wallet/kbaipoc` | -| OCI GenAI | `OCI_AUTH_TYPE=config_file`, `~/.oci/config`, `DEFAULT` profile 기반. model profile은 `config/poc3_model_profiles.json`에서 로드 | +| OCI GenAI | `OCI_AUTH_TYPE=config_file`, `~/.oci/config`, `DEFAULT` profile 기반. model profile은 `ai-web-agent-console/config/model_profiles.json`에서 로드 | | 보안 원칙 | wallet, DB password, wallet password, 실제 VPD token, 대화 DB는 저장소에 포함하지 않음 | 현행 데이터 흐름은 다음과 같다. @@ -60,25 +62,24 @@ SQLite 대화 이력 저장 | 파일 | 책임 | |---|---| -| `apps/poc4/mcp_discovery_ui.py` | Streamlit 화면, 포털 로그인, MCP discovery/call, agent loop, evidence 수집, 답변 합성, 대화 이력 저장 | -| `apps/poc4/ui_theme.py` | UI theme 보조 코드 | -| `src/mcp_tool_router.py` | 발견된 MCP tool descriptor를 기반으로 LLM router가 server/tool을 선택하고 tool arguments를 구성 | -| `src/oci_genai_sdk.py` | OCI Generative AI 호출 경계. MCP/VPD token을 알지 않는 최소 completion client | -| `src/poc3/model_registry.py` | 모델 profile registry 로드, region/endpoint 해석, 환경 override 처리 | -| `src/poc3/questions.py` | 데모 질문 목록 | -| `config/mcp_servers.json` | MCP 서버 registry. 현재는 `endpoint_url`이 있으면 이를 우선 사용 | -| `config/poc3_model_profiles.json` | `gpt55_oci`, `gpt54_mini_oci`, `grok43`, `llama4_maverick`, `llama33_70b` profile | -| `config/vpd_token_presets.json` | 데모 사용자 token preset 구조. 실제 토큰은 배포 환경에서 교체 | -| `scripts/poc4/start_8622_langgraph_tc_ui_nohup.sh` | 원본 PoC4 런처 wrapper. 현재 스냅샷의 직접 entrypoint와는 다르게 `/home/opc/poc_4/scripts/poc4/run_8622_langgraph_tc_ui.sh` 및 `apps/poc4/langgraph_tc_ui.py`를 참조 | +| `ai-web-agent-console/app.py` | Streamlit 화면, 포털 로그인, MCP discovery/call, agent loop, evidence 수집, 답변 합성, 대화 이력 저장 | +| `ai-web-agent-console/ai_web_agent_console/presentation.py` | 공통 UI theme와 화면 표현 보조 코드 | +| `ai-web-agent-console/ai_web_agent_console/mcp_tool_router.py` | 발견된 MCP tool descriptor를 기반으로 LLM router가 server/tool을 선택하고 tool arguments를 구성 | +| `ai-web-agent-console/ai_web_agent_console/oci_genai_sdk.py` | OCI Generative AI 호출 경계. MCP/VPD token을 알지 않는 최소 completion client | +| `ai-web-agent-console/ai_web_agent_console/model_registry.py` | 모델 profile registry 로드, region/endpoint 해석, 환경 override 처리 | +| `ai-web-agent-console/ai_web_agent_console/questions.py` | 데모 질문 목록 | +| `ai-web-agent-console/config/mcp_servers.json` | MCP 서버 registry | +| `ai-web-agent-console/config/model_profiles.json` | OCI GenAI model profile registry | +| `ai-web-agent-console/config/vpd_token_presets.json` | 데모 사용자 token preset 구조. 실제 토큰은 배포 환경에서 교체 | -런처 주의사항: 스냅샷에는 `mcp_discovery_ui.py` 직접 실행에 필요한 소스가 들어 있지만, 포함된 `start_8622...` script는 원본 서버의 공용 runtime wrapper에 의존한다. 이 저장소에서 독립 실행하려면 `SOURCE_README.md`의 직접 `streamlit run` 명령을 사용하거나, 별도 wrapper를 작성해야 한다. +현재 저장소에서는 `cd ai-web-agent-console && streamlit run app.py`를 독립 실행 진입점으로 사용한다. ## 1.3 현행 보안/비밀정보 경계 - `VpdTokenPreset.token`은 dataclass에서 `repr=False`이며, UI는 token을 정규화한 뒤 Authorization header에만 넣는다. - `_NoRedirectHandler`는 redirect 시 Authorization header가 다른 endpoint로 전달되는 것을 막는다. -- `src/mcp_tool_router.py`의 router는 질문과 tool descriptor만 받으며 bearer token 또는 provider credential을 받지 않는다. -- `src/oci_genai_sdk.py`는 `.env`에서 `OCI_AUTH_TYPE`, `OCI_CONFIG_FILE`, `OCI_GENAI_COMPARTMENT_ID`, `OCI_PROFILE`만 읽는다. +- `ai_web_agent_console.mcp_tool_router`의 router는 질문과 tool descriptor만 받으며 bearer token 또는 provider credential을 받지 않는다. +- `ai_web_agent_console.oci_genai_sdk`는 `.env`에서 `OCI_AUTH_TYPE`, `OCI_CONFIG_FILE`, `OCI_GENAI_COMPARTMENT_ID`, `OCI_PROFILE`만 읽는다. - Oracle audit/business evidence 조회는 `/home/opc/kbmcp/.env`에서 `ORACLE_DB_USER`, `ORACLE_DB_PASSWORD`, `ORACLE_DSN`, `ORACLE_WALLET_PASSWORD`, `ORACLE_WALLET_DIR`를 읽는다. - wallet 기본 fallback은 `/home/opc/wallet/kbaipoc`이다. - 저장소에는 실제 `.env`, wallet, SQLite 대화 DB, 실제 VPD bearer token 원문을 포함하지 않는다. @@ -97,7 +98,7 @@ SQLite 대화 이력 저장 ## 2. 범위 (Scope) - **포함**: - - `apps/poc4/mcp_discovery_ui.py`의 KB MCP endpoint, 인증 헤더, JSON-RPC, 오류 처리 정비 + - `ai-web-agent-console/app.py`의 KB MCP endpoint, 인증 헤더, JSON-RPC, 오류 처리 정비 - `config/mcp_servers.json` 및 sample의 KB MCP 선언 정비 - KB MCP의 단일 도구 `ords.query.kb_select_ai_vpd` 호출 계약 문서화 - VPD Backoffice `/mcp`과의 HTTP 상태·프로토콜 버전 호환성 점검 및 필요한 최소 보완 @@ -300,8 +301,8 @@ Authorization: Bearer 배포 서버의 PoC4 활성 화면 소스를 현재 저장소에 별도 폴더로 반입했다. - 원격 실제 위치: `/home/opc/poc_4/poc4_active_source_20260714.tar.gz` -- 사용자 제시 경로 `/home/opc/poc_4/poc4_active_source_20260714/poc4_active_source_20260714.tar.gz`에는 파일이 없었고, 실제 archive는 `/home/opc/poc_4/` 바로 아래에 있었다. -- 저장소 위치: `poc4_active_source_20260714/` +- 사용자 제시 경로 `/home/opc/poc_4/ai-web-agent-console/poc4_active_source_20260714.tar.gz`에는 파일이 없었고, 실제 archive는 `/home/opc/poc_4/` 바로 아래에 있었다. +- 저장소 위치: `ai-web-agent-console/` - 포함 파일: Streamlit UI, MCP router, OCI GenAI client, 모델 profile, token preset sample, 기동/status script, requirements - 보안 확인: 실제 `.env`, 실제 VPD token 원문, 대화 SQLite DB는 포함하지 않았다. `vpd_token_presets.json`에는 placeholder만 있다. - DB 참조 경로: VPD 개발본 배포 서버에서는 `/home/opc/kbmcp/.env`의 접속 정보를 사용하고, wallet directory는 `/home/opc/wallet/kbaipoc`를 사용한다. 두 경로의 파일 내용은 저장소에 포함하지 않는다. @@ -380,7 +381,7 @@ ENABLED_COUNT = 4 SELECT_POLICY_COUNT = 4 ``` -현재 DB의 정책명 계열은 모두 `KB_FGA_*`다. 저장소의 범용 적용 스크립트 `sql/adb/42_agent_ords_fga_execution_audit.sql`은 `CB_VPD_EXEC_AUDIT_` 형태의 정책을 만들도록 작성되어 있으나, 현행 ADB에는 이 계열이 아니라 `KB_FGA_*` 정책이 적용되어 있다. 따라서 운영 확인 시에는 “스크립트 파일명/예상명”보다 `DBA_AUDIT_POLICIES`의 실제 정책명을 기준으로 봐야 한다. +현재 DB의 정책명 계열은 모두 `KB_FGA_*`다. 저장소의 범용 적용 스크립트 `database/adb/42_agent_ords_fga_execution_audit.sql`은 `CB_VPD_EXEC_AUDIT_` 형태의 정책을 만들도록 작성되어 있으나, 현행 ADB에는 이 계열이 아니라 `KB_FGA_*` 정책이 적용되어 있다. 따라서 운영 확인 시에는 “스크립트 파일명/예상명”보다 `DBA_AUDIT_POLICIES`의 실제 정책명을 기준으로 봐야 한다. ### 15.4 감사 이벤트 저장 위치 @@ -437,7 +438,7 @@ DBA_FGA_AUDIT_TRAIL event_count_7d = 0 ### 15.6 소스 구현과 화면 연결 -PoC4 스냅샷의 `apps/poc4/mcp_discovery_ui.py`는 감사로그 탭에서 다음 두 쿼리를 사용한다. +PoC4 스냅샷의 `ai-web-agent-console/app.py`는 감사로그 탭에서 다음 두 쿼리를 사용한다. | 함수 | 조회 대상 | 역할 | |---|---|---| diff --git a/docs/design/701-hmm-ai-agent-modularization/README.md b/docs/design/701-hmm-ai-agent-modularization/README.md index 4bafd67..9726cf9 100644 --- a/docs/design/701-hmm-ai-agent-modularization/README.md +++ b/docs/design/701-hmm-ai-agent-modularization/README.md @@ -2,7 +2,9 @@ ## 프로젝트 개요 -`poc4_active_source_20260714`는 레거시 스냅샷 경로이며, 정식 서비스명은 HMM AI 업무 에이전트다. HMM MCP를 통해 HR 데이터, 표준 용어, 규정 문서를 조회하고 대화 이력과 보안 관리 화면을 제공한다. +정식 서비스명은 HMM AI 업무 에이전트이며 현재 소스 경계는 `ai-web-agent-console/`이다. +과거 `poc4_active_source_20260714` 스냅샷 경로는 #742에서 제거했다. HMM MCP를 통해 HR 데이터, +표준 용어, 규정 문서를 조회하고 대화 이력과 보안 관리 화면을 제공한다. ## 목표 @@ -10,7 +12,7 @@ ## 현재 문제 -- `apps/poc4/mcp_discovery_ui.py`가 화면, 설정, 인증, SQLite 대화 이력, MCP JSON-RPC, Agent 실행을 함께 관리한다. +- `ai-web-agent-console/app.py`가 화면, 설정, 인증, SQLite 대화 이력, MCP JSON-RPC, Agent 실행을 함께 관리한다. - MCP 설정과 인증 토큰 규칙을 수정할 때 화면 코드까지 함께 읽어야 한다. - MCP 프로토콜 처리의 단위 검증 지점이 없다. @@ -18,14 +20,15 @@ | 모듈 | 책임 | Streamlit 의존 | | --- | --- | --- | -| `src/poc4/runtime_config.py` | `.env`, MCP 서버 JSON, VPD preset 로드와 검증 | 없음 | -| `src/poc4/mcp_client.py` | endpoint 검증, JSON-RPC, 세션 fallback, tool discovery/call | 없음 | -| `src/poc4/chat_store.py` | SQLite 대화 이력 CRUD | 없음 | -| `apps/poc4/mcp_discovery_ui.py` | 사용자 입력, 상태, 화면 렌더링, 업무 Agent orchestration | 있음 | +| `ai-web-agent-console/ai_web_agent_console/profile.py` | 제품 프로필과 환경 override 로드 | 없음 | +| `ai-web-agent-console/ai_web_agent_console/mcp_tool_router.py` | MCP 도구 discovery 결과의 route와 arguments 구성 | 없음 | +| `ai-web-agent-console/ai_web_agent_console/auth_gateway.py` | 로그인·쿠키·세션 경계 | 없음 | +| `ai-web-agent-console/ai_web_agent_console/audit.py` | 감사·증적 조회 경계 | 없음 | +| `ai-web-agent-console/app.py` | 사용자 입력, 상태, 화면 렌더링, 업무 Agent orchestration | 있음 | ### 재사용 UI Shell과 제품 프로필 -공통 화면 shell은 `src/agent_console/`에서 제공하고, 특정 고객·PoC의 표현은 +공통 화면 shell은 `ai-web-agent-console/ai_web_agent_console/`에서 제공하고, 특정 고객·PoC의 표현은 `config/app_profile.json`에 둔다. 다른 프로젝트는 앱 코드를 복사·수정하지 않고 profile JSON을 교체할 수 있다. 실제 배포에서는 `AGENT_CONSOLE_NAME`, `AGENT_CONSOLE_HEADER_DESCRIPTION`, `AGENT_CONSOLE_PRIMARY_COLOR` 등 `AGENT_CONSOLE_*` 환경변수가 JSON 기본값보다 우선한다. @@ -48,7 +51,7 @@ 2. 변경은 Redmine 이슈에 설계·검증 결과와 Git commit SHA를 함께 기록한다. 3. Git commit message에는 Redmine 번호를 `refs #<번호>:` 형식으로 포함한다. 4. 환경별 값과 비밀값은 profile JSON에 넣지 않고 `.env` 또는 secret store에만 둔다. -5. UI CSS는 `src/agent_console/presentation.py` 한 곳에서 관리한다. 제품별 색상과 문구는 Python/CSS를 수정하지 않고 `app_profile.json`으로 조정한다. +5. UI CSS는 `ai-web-agent-console/ai_web_agent_console/presentation.py` 한 곳에서 관리한다. 제품별 색상과 문구는 Python/CSS를 수정하지 않고 `app_profile.json`으로 조정한다. 도메인 로직의 공개 오류는 `PublicMcpError`로 통일한다. UI는 이 오류를 사람이 이해할 수 있는 메시지로 표시하되 토큰과 HTTP 원문을 출력하지 않는다. diff --git a/docs/design/702-hmm-backoffice-identity/README.md b/docs/design/702-hmm-backoffice-identity/README.md index 0623966..7c53b84 100644 --- a/docs/design/702-hmm-backoffice-identity/README.md +++ b/docs/design/702-hmm-backoffice-identity/README.md @@ -60,7 +60,7 @@ HMM_KNOWLEDGE_DOCUMENTS ──< HMM_KNOWLEDGE_CHUNKS ──< HMM_KNOWLEDGE_TAGS - `/structured-data`와 `/schema-metadata`의 데이터 원본명, Oracle owner, 안내 문구, 최대 조회 건수, 허용 테이블 목록은 Java·HTML에 하드코딩하지 않는다. -- 기본 HMM 정의는 `src/main/resources/config/structured-data-catalog.json`에 둔다. 배포 환경에서는 +- 기본 HMM 정의는 `vpd-backoffice/src/main/resources/config/structured-data-catalog.json`에 둔다. 배포 환경에서는 `BACKOFFICE_STRUCTURED_DATA_CATALOG_LOCATION=file:/.../structured-data-catalog.json`으로 외부 JSON을 지정할 수 있어 다른 회사 PoC에서 애플리케이션 코드를 수정하지 않고 재사용할 수 있다. - 각 테이블 정의는 `key`, `tableName`, `businessName`, `description`을 기본으로 하고 필요하면 diff --git a/docs/design/707-hmm-leave-team-vpd/README.md b/docs/design/707-hmm-leave-team-vpd/README.md index 330611c..fc78203 100644 --- a/docs/design/707-hmm-leave-team-vpd/README.md +++ b/docs/design/707-hmm-leave-team-vpd/README.md @@ -73,7 +73,7 @@ E1001에는 `HMM_HR_MANAGER`, E1002~E1007에는 `HMM_HR_VIEWER`를 부여한다. ## 배포 순서 1. 애플리케이션 테스트와 SQL 구문 검사를 수행한다. -2. 멱등 SQL `sql/adb/72_hmm_leave_team_vpd.sql`을 `ADMIN`으로 실행한다. +2. 멱등 SQL `database/adb/72_hmm_leave_team_vpd.sql`을 `ADMIN`으로 실행한다. 3. VPD 정책·컴파일 상태·백오피스 화면을 확인한다. 4. 백오피스와 같은 SHA-256 저장 규칙으로 E1001과 E1002 임시 검증 토큰을 만든다. 5. 비면제 검증 사용자에서 토큰별 조회 결과를 확인하고 검증 토큰과 사용자를 제거한다. diff --git a/docs/design/723-hmm-configurable-backoffice/README.md b/docs/design/723-hmm-configurable-backoffice/README.md index 4f061b0..be3d389 100644 --- a/docs/design/723-hmm-configurable-backoffice/README.md +++ b/docs/design/723-hmm-configurable-backoffice/README.md @@ -182,7 +182,7 @@ HMM 운영 MCP는 현재 세 개의 Agent Tool을 사용하므로 Select AI 접 BACKOFFICE_SECURITY_SQL_SCRIPTS ``` -번들된 `sql/adb` 경로 안의 파일만 허용하며 요청값을 resource path로 사용하지 않는다. +번들된 `database/adb` 경로 안의 파일만 허용하며 요청값을 resource path로 사용하지 않는다. HMM 운영에서는 `72_hmm_leave_team_vpd.sql` 등 HMM 관련 SQL만 노출한다. ## 5. 코드 구조 diff --git a/docs/design/729-rds-carrier-performance-demo/README.md b/docs/design/729-rds-carrier-performance-demo/README.md index 897c6a2..c29c77c 100644 --- a/docs/design/729-rds-carrier-performance-demo/README.md +++ b/docs/design/729-rds-carrier-performance-demo/README.md @@ -110,8 +110,8 @@ CHECK 제약으로 보장한다. 월별 조회와 위험 선사 조회에 각각 ## 7. 적재 순서 1. RDS TLS와 현재 계정의 CREATE 권한을 확인한다. -2. `sql/source/postgres_hmm_carrier_performance.sql`을 실행한다. -3. `sql/source/postgres_hmm_carrier_performance_verify.sql`을 실행한다. +2. `database/source/postgres_hmm_carrier_performance.sql`을 실행한다. +3. `database/source/postgres_hmm_carrier_performance_verify.sql`을 실행한다. 4. 재실행 후에도 동일한 선사 8개와 실적 144행인지 확인한다. 5. 후속 작업에서 ADB credential과 PostgreSQL database link를 만든다. diff --git a/docs/design/742-application-oriented-repository-layout/README.md b/docs/design/742-application-oriented-repository-layout/README.md new file mode 100644 index 0000000..de90082 --- /dev/null +++ b/docs/design/742-application-oriented-repository-layout/README.md @@ -0,0 +1,112 @@ +# #742 애플리케이션 단위 저장소 구조 개편 + +## 문제 + +현재 저장소 루트의 `pom.xml`, `src/main`, `src/test`는 실제로 하나의 Spring Boot +백오피스지만 제품 폴더 없이 저장소 전체 애플리케이션처럼 보인다. Streamlit 앱은 +`poc4_active_source_20260714`라는 날짜 스냅샷 폴더에 있고 내부에서도 `poc3`, `poc4`, +`agent_console`, 단독 Python 모듈이 서로 참조한다. + +이 구조에서는 다음 내용을 파일 경로만 보고 알 수 없다. + +- 어떤 파일이 실제 배포 애플리케이션인지 +- Spring Boot와 Streamlit의 빌드·테스트 경계가 어디인지 +- `poc3`, `poc4`가 제품명인지 과거 실험 번호인지 +- DB 스크립트와 특정 애플리케이션의 관계가 무엇인지 + +## 목표 구조 + +```text +vpd-permission-poc/ +├── vpd-backoffice/ Spring Boot VPD·권한 관리 백오피스 +│ ├── pom.xml +│ ├── src/main/ +│ └── src/test/ +├── ai-web-agent-console/ Streamlit AI 업무 에이전트 +│ ├── app.py +│ ├── ai_web_agent_console/ 단일 Python 패키지 +│ ├── config/ +│ ├── tests/ +│ └── requirements.txt +├── dds-backoffice/ 별도 DDS 백오피스 +├── database/ +│ ├── adb/ +│ └── source/ +├── deploy/ 제품별 배포 설정 +├── docs/ 설계·운영·검증 문서 +├── scripts/ 저장소 공통 자동화 +└── README.md +``` + +## 이동 매핑 + +| 기존 | 변경 | 처리 | +|---|---|---| +| `pom.xml` | `vpd-backoffice/pom.xml` | Maven 프로젝트 경계 명시 | +| `src/main` | `vpd-backoffice/src/main` | Spring 애플리케이션 소스 | +| `src/test` | `vpd-backoffice/src/test` | Spring 단위·통합 테스트 | +| `sql/adb` | `database/adb` | Oracle/ADB 스크립트 | +| `sql/source` | `database/source` | 외부 원천 DB 스크립트 | +| `poc4_active_source_20260714` | `ai-web-agent-console` | 날짜·PoC 번호 제거 | +| `apps/poc4/mcp_discovery_ui.py` | `ai-web-agent-console/app.py` | 명확한 Streamlit 진입점 | +| `src/agent_console/*` | `ai-web-agent-console/ai_web_agent_console/*` | 단일 제품 패키지 | +| `src/poc3/model_registry.py` | `ai-web-agent-console/ai_web_agent_console/model_registry.py` | 실험 번호 제거 | +| `src/poc3/questions.py` | `ai-web-agent-console/ai_web_agent_console/questions.py` | 실험 번호 제거 | +| `src/poc4/scenarios.py` | `ai-web-agent-console/ai_web_agent_console/scenarios.py` | 실험 번호 제거 | +| `src/poc4/query_contracts.py` | `ai-web-agent-console/ai_web_agent_console/query_contracts.py` | 실험 번호 제거 | +| `src/mcp_*.py`, `src/oci_genai_sdk.py` | `ai-web-agent-console/ai_web_agent_console/` | 앱 전용 공용 모듈 통합 | +| `config/poc3_model_profiles.json` | `ai-web-agent-console/config/model_profiles.json` | 실험 번호 제거 | + +## 경로 규칙 + +1. 저장소 루트에는 애플리케이션 프레임워크의 `src`나 `pom.xml`을 두지 않는다. +2. 각 애플리케이션은 자신의 소스, 의존성 선언, 테스트 진입점을 가진다. +3. DB 스크립트는 `database`를 Git 원본으로 사용하고 Spring JAR는 해당 경로를 + 리소스로 포함한다. +4. 배포 설정은 `deploy/`에서 새 소스 경로를 참조한다. +5. 과거 경로를 유지하는 심볼릭 링크나 복제본은 만들지 않는다. 잘못된 경로가 다시 + 사용되면 테스트가 실패하도록 한다. +6. 운영 중인 환경변수와 SQLite 테이블명은 데이터 호환을 위해 이번 변경에서 유지한다. + 폴더·패키지·실행 경로에서만 PoC 명칭을 제거한다. + +## Maven 변경 + +`vpd-backoffice/pom.xml`의 SQL 리소스 경로는 `../database/adb`를 사용한다. 루트 자동화는 +`mvn -f vpd-backoffice/pom.xml`을 사용하며 앱 디렉토리 안에서는 기존처럼 `mvn test`가 +동작해야 한다. + +Java 테스트에서 파일을 직접 여는 경로는 앱 디렉토리를 기준으로 유지한다. 따라서 +`vpd-backoffice`에서 실행한 테스트와 루트에서 `-f`로 실행한 테스트를 모두 확인한다. + +## Python 변경 + +`ai-web-agent-console/app.py`는 자신의 부모 폴더를 애플리케이션 루트로 사용한다. +모든 import는 `ai_web_agent_console.` 형식으로 통일한다. 테스트도 같은 공개 +패키지 경로만 사용한다. + +`model_profiles.json` 안의 `default_for_poc3` 속성은 `default_for_console`로 바꾸고 +로더·검증 코드도 함께 변경한다. 배포된 비밀정보 파일과 대화 DB는 Git 이동 대상이 +아니다. + +## 배포 변경 + +- Spring 배포 빌드는 `vpd-backoffice/pom.xml`과 `vpd-backoffice/target`을 사용한다. +- Streamlit 실행은 `streamlit run app.py`를 사용한다. +- 인증 게이트웨이는 `python -m ai_web_agent_console.auth_gateway`를 사용한다. +- 기존 운영 디렉토리 `/opt/hmm-poc4`는 무중단 전환을 위해 물리 경로로 유지할 수 있지만, + 서비스의 Git 소스 경로와 Python 모듈명은 새 구조를 사용한다. + +## 검증 + +1. `git status --short`에서 기존 #741 변경이 새 경로에 남아 있는지 확인한다. +2. `mvn -f vpd-backoffice/pom.xml test`를 실행한다. +3. Python 전체 소스에 `compileall`을 실행한다. +4. `unittest discover -s ai-web-agent-console/tests`를 실행한다. +5. `rg`로 운영 코드의 `src.poc3`, `src.poc4`, `src.agent_console`, + `poc4_active_source_20260714`, `apps/poc4` 참조가 0건인지 확인한다. +6. 배포 스크립트의 dry-run 또는 정적 경로 검증을 수행한다. + +## 롤백 + +모든 이동은 한 Git 커밋으로 추적한다. 문제가 생기면 해당 커밋을 revert하여 이전 경로와 +import를 함께 복구한다. DB 스키마와 운영 데이터는 이 구조 변경에서 수정하지 않는다. diff --git a/docs/notes/2026-06-08-dds-qa-session.md b/docs/notes/2026-06-08-dds-qa-session.md index 34490d4..6ef6dc8 100644 --- a/docs/notes/2026-06-08-dds-qa-session.md +++ b/docs/notes/2026-06-08-dds-qa-session.md @@ -98,7 +98,7 @@ STEP 8 (선택) ALTER TABLE ... SET USE DATA GRANTS ONLY ENABLED -- MAC 모드 STEP 9 감사 (dba_data_grants 등) + 역순 정리 ``` -전체 스크립트는 세션 본문 참조. 이 POC 의 `sql/adb/13_dds_variant.sql` + `15_dds_cleanup.sql` 가 동일 패턴. +전체 스크립트는 세션 본문 참조. 이 POC 의 `database/adb/13_dds_variant.sql` + `15_dds_cleanup.sql` 가 동일 패턴. --- @@ -325,8 +325,8 @@ CREATE DATA GRANT admin.dg_scoped ### 이 POC 에서 보면 -- `sql/adb/06_policy.sql` + `permission` 테이블 = "앱/운영자가 행으로 권한 관리" → **VPD 답안** -- `sql/adb/13_dds_variant.sql` = "DBA 가 DDL 로 4 종 역할 정의" → **DDS 답안** +- `database/adb/06_policy.sql` + `permission` 테이블 = "앱/운영자가 행으로 권한 관리" → **VPD 답안** +- `database/adb/13_dds_variant.sql` = "DBA 가 DDL 로 4 종 역할 정의" → **DDS 답안** - 같은 결과를 두 다른 모델로 표현한 비교 데모 **권한 매핑 테이블 중심으로 가겠다면 VPD 그대로 두는 게 정답.** diff --git a/poc4_active_source_20260714/HMM_BRAND_REFRESH.md b/docs/notes/hmm-brand-refresh.md similarity index 92% rename from poc4_active_source_20260714/HMM_BRAND_REFRESH.md rename to docs/notes/hmm-brand-refresh.md index d98f22a..c945ae5 100644 --- a/poc4_active_source_20260714/HMM_BRAND_REFRESH.md +++ b/docs/notes/hmm-brand-refresh.md @@ -6,7 +6,8 @@ - 외부 KB 로고·전용 글꼴 의존성을 제거하고, 애플리케이션 내부 SVG 워드마크와 해양 청색 계열로 표시한다. - 기존 MCP, VPD, 데이터베이스 스키마 및 도구 계약은 변경하지 않는다. - 로그인 유지 기능은 서버 비밀키로 서명한 7일 만료 토큰을 사용하며, 로그아웃 시 즉시 폐기한다. -- 공통 화면 CSS와 로그인/헤더 renderer는 `src/agent_console/presentation.py`에서 관리한다. +- 공통 화면 CSS와 로그인/헤더 renderer는 + `ai-web-agent-console/ai_web_agent_console/presentation.py`에서 관리한다. HMM의 제품명·문구·색상은 `config/app_profile.json`에만 둔다. # HMM MCP runtime wiring diff --git a/docs/reference/ords-vpd-sql-trace.md b/docs/reference/ords-vpd-sql-trace.md index c78f478..f994562 100644 --- a/docs/reference/ords-vpd-sql-trace.md +++ b/docs/reference/ords-vpd-sql-trace.md @@ -12,10 +12,10 @@ 기존 Handler가 trace 필드를 반환하지 않으면 백오피스가 같은 DB 연결에서 `set_user_by_bearer` → `CB_AGENT_DOC_VPD_FILTER` → `clear_user` 순서로 기본 VPD predicate를 조회한다. 이 보조 조회가 실패해도 권한 결과 자체는 실패 처리하지 않는다. -기존 설치에 trace 권한을 추가하려면 ADMIN으로 `sql/adb/33_agent_ords_sql_trace_grant.sql`을 실행한 뒤 CB_ORDS로 `22_agent_ords_security_ords_handler_setup.sql`을 재실행하거나 `/ords-handlers`에서 trace가 포함된 Handler source를 저장한다. +기존 설치에 trace 권한을 추가하려면 ADMIN으로 `database/adb/33_agent_ords_sql_trace_grant.sql`을 실행한 뒤 CB_ORDS로 `22_agent_ords_security_ords_handler_setup.sql`을 재실행하거나 `/ords-handlers`에서 trace가 포함된 Handler source를 저장한다. 기본으로 표시되는 SQL은 해당 토큰 컨텍스트에서 VPD 정책 함수가 반환한 행 predicate를 기본 Handler SELECT에 결합한 확인용 SQL이다. Bearer 원문은 trace나 응답에 포함하지 않는다. 실행 DB가 `V$SQL`과 `DBMS_XPLAN` 조회를 허용하면 `/probe`는 최근 2분 내 같은 보호 객체의 SQL_ID, DB가 기록한 원문 SQL, cursor 누적 통계, Predicate Information도 함께 표시한다. `V$SQL` 원문은 VPD 주입 전 SQL이고, 실제 적용 조건은 `DBMS_XPLAN`의 Predicate Information에서 확인한다. 같은 객체에 동시 요청이 많은 경우에는 최근 cursor 매칭이므로 요청 ID 기반 상관 추적이 필요하다. -Autonomous DB에서 일반 `ADMIN` 계정은 `SYS.V_$SQL` 권한을 ORDS 계정에 위임하지 못할 수 있다. 이 경우에는 SYS/DBA가 [34_agent_ords_execution_evidence_grant.sql](../../sql/adb/34_agent_ords_execution_evidence_grant.sql)을 실행해 진단 권한을 준비해야 한다. 권한을 얻지 못해도 접근 검증 결과와 기존 권한 조건 재현 SQL은 그대로 제공된다. +Autonomous DB에서 일반 `ADMIN` 계정은 `SYS.V_$SQL` 권한을 ORDS 계정에 위임하지 못할 수 있다. 이 경우에는 SYS/DBA가 [34_agent_ords_execution_evidence_grant.sql](../../database/adb/34_agent_ords_execution_evidence_grant.sql)을 실행해 진단 권한을 준비해야 한다. 권한을 얻지 못해도 접근 검증 결과와 기존 권한 조건 재현 SQL은 그대로 제공된다. diff --git a/docs/reports/2026-07-10-existing-01-mcp-rag-diagnosis.md b/docs/reports/2026-07-10-existing-01-mcp-rag-diagnosis.md index f44bbe7..62009bb 100644 --- a/docs/reports/2026-07-10-existing-01-mcp-rag-diagnosis.md +++ b/docs/reports/2026-07-10-existing-01-mcp-rag-diagnosis.md @@ -54,7 +54,7 @@ EXTERNAL_CLAUSE_NAME=개인용애니카다이렉트자동차보험 변경 스크립트: -- `sql/adb/65_kb_select_ai_vpd_query_api.sql` +- `database/adb/65_kb_select_ai_vpd_query_api.sql` ## RAG 근거 검색 확인 결과 diff --git a/docs/reports/2026-07-10-select-ai-profile-performance-report.md b/docs/reports/2026-07-10-select-ai-profile-performance-report.md index ee3beb3..3856e52 100644 --- a/docs/reports/2026-07-10-select-ai-profile-performance-report.md +++ b/docs/reports/2026-07-10-select-ai-profile-performance-report.md @@ -202,7 +202,7 @@ c_profile_name CONSTANT VARCHAR2(128) := 'KB_AIDP_SELECTAI_GPT54_MINI_COMMENTS_P 대상 스크립트: ```text -sql/adb/65_kb_select_ai_vpd_query_api.sql +database/adb/65_kb_select_ai_vpd_query_api.sql ``` ### ORDS 응답 표시 @@ -216,7 +216,7 @@ profile=KB_AIDP_SELECTAI_GPT54_MINI_COMMENTS_PROFILE_V1 대상 스크립트: ```text -sql/adb/66_kb_select_ai_vpd_query_ords.sql +database/adb/66_kb_select_ai_vpd_query_ords.sql ``` ### MCP 응답 표시 @@ -226,8 +226,8 @@ MCP tool 응답 payload의 profile 표시와 화면 설명을 새 프로파일 대상 소스: ```text -src/main/java/com/cloudhandson/vpdbackoffice/service/McpSseService.java -src/main/resources/templates/mcp-sse.html +vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/McpSseService.java +vpd-backoffice/src/main/resources/templates/mcp-sse.html ``` ## 최종 속도 측정 diff --git a/docs/reports/2026-07-23-hmm-leave-team-vpd-verification.md b/docs/reports/2026-07-23-hmm-leave-team-vpd-verification.md index bba825a..d9573b3 100644 --- a/docs/reports/2026-07-23-hmm-leave-team-vpd-verification.md +++ b/docs/reports/2026-07-23-hmm-leave-team-vpd-verification.md @@ -8,7 +8,7 @@ ## 적용 결과 -`sql/adb/72_hmm_leave_team_vpd.sql`을 적용해 다음 구성을 활성화했다. +`database/adb/72_hmm_leave_team_vpd.sql`을 적용해 다음 구성을 활성화했다. | 구성 | 결과 | |---|---| diff --git a/docs/reports/2026-07-23-hmm-portal-full-browser-audit.md b/docs/reports/2026-07-23-hmm-portal-full-browser-audit.md index b499390..11d6312 100644 --- a/docs/reports/2026-07-23-hmm-portal-full-browser-audit.md +++ b/docs/reports/2026-07-23-hmm-portal-full-browser-audit.md @@ -32,7 +32,7 @@ ## 이번에 제거한 결함 1. 감사로그가 KB PoC의 `POC_2` FGA 메타데이터를 조회하던 경로를 제거했다. -2. 감사 원천을 `ADMIN.HMM_ACCESS_AUDIT`으로 교체하고 렌더러를 `src/agent_console/audit.py`로 +2. 감사 원천을 `ADMIN.HMM_ACCESS_AUDIT`으로 교체하고 렌더러를 `ai-web-agent-console/ai_web_agent_console/audit.py`로 분리했다. 3. Python Thin driver가 암호화 `ewallet.pem`의 암호를 대화형으로 기다리던 연결을 ADB TLS 1521 서버 인증 풀로 교체했다. diff --git a/docs/runbooks/460-sqlcl-vpd-deploy-runbook.md b/docs/runbooks/460-sqlcl-vpd-deploy-runbook.md index 9cea05f..65fb9fc 100644 --- a/docs/runbooks/460-sqlcl-vpd-deploy-runbook.md +++ b/docs/runbooks/460-sqlcl-vpd-deploy-runbook.md @@ -25,9 +25,9 @@ SQLCL_BIN=/path/to/sql SQLCL_JAVA_HOME=/path/to/jdk "$SQLCL_BIN" "USER/PASSWORD@ SQLcl 안에서 실행: ```sql -@sql/adb/26_agent_ords_security_dynamic_vpd_filter.sql +@database/adb/26_agent_ords_security_dynamic_vpd_filter.sql SHOW ERRORS FUNCTION cb_agent_doc_vpd_filter -@sql/adb/27_agent_ords_security_dynamic_vpd_filter_test.sql +@database/adb/27_agent_ords_security_dynamic_vpd_filter_test.sql ``` 기대 결과: @@ -64,7 +64,7 @@ SQLCL_JAVA_HOME=/path/to/jdk \ 가장 단순한 롤백은 직전 Git revision의 `26_agent_ords_security_dynamic_vpd_filter.sql`을 다시 적용하는 것이다. ```bash -git show HEAD~1:sql/adb/26_agent_ords_security_dynamic_vpd_filter.sql > /tmp/previous-vpd-filter.sql +git show HEAD~1:database/adb/26_agent_ords_security_dynamic_vpd_filter.sql > /tmp/previous-vpd-filter.sql SQLCL_BIN=/path/to/sql SQLCL_JAVA_HOME=/path/to/jdk "$SQLCL_BIN" "USER/PASSWORD@TNS" ``` diff --git a/docs/runbooks/hmm-mcp-token-configuration.md b/docs/runbooks/hmm-mcp-token-configuration.md index 83bffc1..86e423e 100644 --- a/docs/runbooks/hmm-mcp-token-configuration.md +++ b/docs/runbooks/hmm-mcp-token-configuration.md @@ -151,7 +151,7 @@ BACKOFFICE_SELECT_AI_RUNTIME_DB_USERNAME=CB_ORDS BACKOFFICE_SELECT_AI_RUNTIME_DB_PASSWORD=<운영 secret> ``` -DB 구성 스크립트는 `sql/adb/73_hmm_mcp_vpd_runtime.sql`이다. 이 스크립트는 승인된 HMM +DB 구성 스크립트는 `database/adb/73_hmm_mcp_vpd_runtime.sql`이다. 이 스크립트는 승인된 HMM HR 객체에 대한 개별 `SELECT`와 handler package 실행 권한만 부여하며 `EXEMPT ACCESS POLICY`는 부여하지 않는다. diff --git a/poc4_active_source_20260714/.env.sample b/poc4_active_source_20260714/.env.sample deleted file mode 100644 index c25caae..0000000 --- a/poc4_active_source_20260714/.env.sample +++ /dev/null @@ -1,131 +0,0 @@ -# PoC_4 runtime environment template -# -# 사용법: -# cp .env.sample .env -# chmod 600 .env -# 편집 후 scripts/poc4/start_*_nohup.sh 로 기동합니다. - -# 제품별 화면 값: 기본값은 config/app_profile.json, 아래 값이 있으면 환경변수가 우선합니다. -# AGENT_CONSOLE_NAME=HMM AI 업무 에이전트 -# AGENT_CONSOLE_SHORT_NAME=HMM -# AGENT_CONSOLE_PAGE_TITLE=HMM AI 업무 에이전트 -# AGENT_CONSOLE_PAGE_ICON=⛴️ -# AGENT_CONSOLE_HEADER_TITLE=AI 업무 에이전트 -# AGENT_CONSOLE_HEADER_DESCRIPTION=사용자 권한에 맞는 업무 질의와 보안 관리 기능을 제공합니다. -# AGENT_CONSOLE_LOGIN_KICKER=HMM SHIPPING & LOGISTICS DEMO -# AGENT_CONSOLE_LOGIN_TITLE=HMM AI 업무 에이전트 -# AGENT_CONSOLE_LOGIN_DESCRIPTION=사용자 인증 후 해운·물류 AI 질의와 보안 관리 기능을 이용할 수 있습니다. -# AGENT_CONSOLE_LOGIN_FOOTER=인증된 DEMO 사용자만 접근할 수 있습니다. -# AGENT_CONSOLE_PRIMARY_COLOR=#003b70 -# AGENT_CONSOLE_TEXT_COLOR=#172b3a -# AGENT_CONSOLE_MUTED_COLOR=#667785 -# AGENT_CONSOLE_BORDER_COLOR=#dfe7ed -# -# 실제 token, password, OCID, wallet 경로는 이 샘플에 기록하지 않습니다. -# 이 파일은 Bash에서 읽히므로 KEY=value 형식만 사용하고 명령 치환은 넣지 않습니다. - -# ----------------------------------------------------------------------------- -# MCP runtime (현재 PoC_4가 공유하는 PoC_3 호환 환경변수 계약) -# ----------------------------------------------------------------------------- -POC3_MCP_PROVIDER=custom_python -POC3_MCP_BASE_URL=http://127.0.0.1:8500 -POC3_MCP_AUTH_MODE=bearer -POC3_MCP_TOKEN= -# HMM MCP gateway token. 실제 값은 배포 서버 .env에만 둡니다. -HMM_MCP_BEARER_TOKEN= -POC3_MCP_TIMEOUT_SECONDS=30 -POC3_MCP_FALLBACK_TO_MOCK=false -POC3_MCP_LIVE_SMOKE=false - -# MCP server registry -# 실제 서버 URL/token 값은 JSON에 직접 넣지 않고 위 환경변수 이름을 참조합니다. -# 사용 전 config/mcp_servers.sample.json을 아래 파일명으로 복사해 조정합니다. -POC4_MCP_SERVERS_FILE=config/mcp_servers.json -POC4_MCP_DEFAULT_SERVER_ID=hmm_hr_mcp - -# MCP discovery UI conversation history store -# 기본값: /home/opc/poc_4/data/poc4_mcp_chat.sqlite3 -POC4_CHAT_DB_PATH=data/poc4_mcp_chat.sqlite3 - -# ----------------------------------------------------------------------------- -# Local trace / optional LangSmith metadata -# ----------------------------------------------------------------------------- -POC3_TRACE_MODE=local -POC3_TRACE_UI_ENABLED=true -LANGSMITH_TRACING=false -LANGSMITH_API_KEY= -LANGSMITH_PROJECT=kb-aidp-poc4 - -# ----------------------------------------------------------------------------- -# PoC_4 UI ports -# 현재 VM 기본 포트만 사용합니다. 공식 포트 전환은 .env만으로 허용되지 않으며 -# POC4_ALLOW_OFFICIAL_PORTS=1을 기동 명령의 환경에 별도로 지정해야 합니다. -# ----------------------------------------------------------------------------- -POC4_LANGGRAPH_UI_PORT=8612 -POC4_AGENT_TEAM_UI_PORT=8613 -POC4_LANGGRAPH_TC_UI_PORT=8622 -POC4_AGENT_TEAM_TC_UI_PORT=8623 - -# ----------------------------------------------------------------------------- -# OCI Generative AI -# compartment ID는 배포 환경의 값을 입력합니다. API key/private key 원문은 넣지 -# 않고 OCI config file 또는 instance/resource principal을 사용합니다. -# ----------------------------------------------------------------------------- -OCI_AUTH_TYPE=config_file -OCI_CONFIG_FILE=~/.oci/config -OCI_PROFILE=DEFAULT -OCI_GENAI_COMPARTMENT_ID= - -# 모델 route는 config/poc3_model_profiles.json의 검증된 기본값을 사용합니다. -# 배포 환경에서 route를 바꿔야 할 때만 아래 항목의 주석을 해제합니다. -# POC3_LLM_GPT55_OCI_MODEL_ID=openai.gpt-5.5 -# POC3_LLM_GPT55_OCI_REGION=us-chicago-1 -# POC3_LLM_GPT55_OCI_ENDPOINT=https://inference.generativeai.us-chicago-1.oci.oraclecloud.com -# POC3_LLM_GPT54_MINI_OCI_MODEL_ID=openai.gpt-5.4-mini -# POC3_LLM_GPT54_MINI_OCI_REGION=us-chicago-1 -# POC3_LLM_GPT54_MINI_OCI_ENDPOINT=https://inference.generativeai.us-chicago-1.oci.oraclecloud.com -# POC3_LLM_GROK43_MODEL_ID=xai.grok-4.3 -# POC3_LLM_GROK43_REGION=us-chicago-1 -# POC3_LLM_GROK43_ENDPOINT=https://inference.generativeai.us-chicago-1.oci.oraclecloud.com -# 화면 LLM 모델 선택의 기본값입니다. -# 선택 모델은 후속 질문 정리, 실행 방식 판단, MCP tool 라우팅, -# Agent planning, 최종 답변 합성에 사용됩니다. -# 실패 시 애플리케이션 기본 fallback인 gpt54_mini_oci로 재시도합니다. -POC4_COMPLEX_REASONING_MODEL_PROFILE=grok43 - -# ----------------------------------------------------------------------------- -# Audit / security evidence DB connection -# ----------------------------------------------------------------------------- -# VPD 개발본 배포 서버 기준: -# - DB 접속 secret은 /home/opc/kbmcp/.env 에 둡니다. -# - DB wallet은 /home/opc/wallet/kbaipoc 를 사용합니다. -# 이 샘플에는 DB password, wallet password, token 원문을 넣지 않습니다. -POC4_AUDIT_DB_ENV_FILE=/home/opc/kbmcp/.env -ORACLE_WALLET_DIR=/home/opc/wallet/kbaipoc - -# ----------------------------------------------------------------------------- -# Direct Oracle DB administration tools (목표 계약) -# Streamlit/MCP-only 배포에서는 모두 비워 둡니다. 현재 launcher는 .env 전체를 UI -# process에 export하므로 역할별 설정 loader가 구현되기 전에는 이 파일에 DB secret을 -# 채우지 않습니다. DB migration/audit 전용 process environment에서만 주입합니다. -# ----------------------------------------------------------------------------- -POC4_DB_ADMIN_ENABLED=false -POC4_DB_USERNAME= -POC4_DB_PASSWORD= -POC4_DB_DSN= -POC4_DB_WALLET_DIR= -POC4_DB_WALLET_PASSWORD= -POC4_DB_EXPECTED_SCHEMA= -POC4_DB_CLIENT_LIB_DIR= -POC4_DB_MODE=thick -POC4_DB_CONNECT_TIMEOUT_SECONDS=60 -POC4_DB_CALL_TIMEOUT_MS=120000 - -# 신규 tenancy 이관 preflight에서만 사용하며 값 자체는 출력하지 않습니다. -POC4_EXPECTED_TENANCY_ID= - -# 다음 값은 복제된 .env가 보안/프로세스 제어를 바꾸지 못하도록 launcher 호출자만 -# 지정할 수 있습니다. 이 파일에 활성 값으로 추가하지 않습니다. -# POC4_BIND_ADDRESS=0.0.0.0 -# POC4_ALLOW_OFFICIAL_PORTS=1 -# POC4_MANAGED_FOREGROUND=1 diff --git a/poc4_active_source_20260714/SOURCE_README.md b/poc4_active_source_20260714/SOURCE_README.md deleted file mode 100644 index e86baa2..0000000 --- a/poc4_active_source_20260714/SOURCE_README.md +++ /dev/null @@ -1,65 +0,0 @@ -# HMM AI 업무 에이전트 소스 - -생성일: 2026-07-14 GMT - -이 폴더는 HMM AI 업무 에이전트 실행 소스다. `poc4`라는 디렉터리·환경변수 접두어는 -기존 배포 호환성을 위한 레거시 경로이며, 서비스의 정식 이름은 **HMM AI 업무 에이전트**다. - -## Entrypoint - -```bash -streamlit run apps/poc4/mcp_discovery_ui.py --server.address 0.0.0.0 --server.port 8622 -``` - -## Runtime - -- Python 3.11 이상을 사용합니다. -- 이 개발 서버의 기본 `python3`가 3.6 계열이면 문법 검증이 실패합니다. -- 배포 서버는 `/opt/hmm-poc4/.venv`와 Python 3.11을 사용합니다. -- OCI Generative AI SDK 인증은 배포 서버의 `/home/opc/.oci/config` `DEFAULT` profile을 사용합니다. - private key와 compartment OCID는 Git에 저장하지 않고 서버의 `~/.oci`와 `.env`에서만 관리합니다. - -## Deployment DB reference - -- VPD 개발본 배포 서버의 DB 접속 정보는 `/home/opc/kbmcp/.env`를 사용합니다. -- DB wallet directory는 `/home/opc/wallet/kbaipoc`를 사용합니다. -- 이 저장소에는 wallet 파일, DB password, wallet password, 실제 VPD token 원문을 넣지 않습니다. - -## Included - -- `apps/poc4/mcp_discovery_ui.py` -- `src/agent_console/presentation.py` -- `src/agent_console/profile.py` -- `src/mcp_tool_router.py` -- `src/oci_genai_sdk.py` -- `src/poc3/model_registry.py` -- `src/poc3/questions.py` -- `config/mcp_servers.json` -- `config/poc3_model_profiles.json` -- `config/vpd_token_presets.json` -- `config/hmm_demo_scenarios.json` — 화면에 표시할 데모 질문. 코드 수정 없이 이 파일만 변경한다. -- `config/app_profile.json` — 제품명, 문구, 아이콘, 색상을 지정하는 제품 profile -- `.env.sample` -- `requirements.txt` -- `requirements-langgraph.txt` -- `scripts/poc4/start_8622_langgraph_tc_ui_nohup.sh` -- `scripts/poc4/status_8622_langgraph_tc_ui_nohup.sh` - -## Security note - -- 실제 `.env`는 복사하지 않았습니다. `.env.sample`을 기준으로 새로 만드세요. -- `config/vpd_token_presets.json`은 파일명 호환성을 유지한 HMM **데모 사용자** 목록입니다. - 사용자별 `mcp_token_env`는 배포 환경의 `HMM_MCP_BEARER_TOKEN`만 참조하며, token 원문은 JSON·Git에 저장하지 않습니다. -- 대화 DB `data/poc4_mcp_chat.sqlite3`는 개인정보/대화 내용이 포함될 수 있어 복사하지 않았습니다. -- MCP 호출 상세의 JSON/code 영역은 공통 presentation CSS에서 밝은 배경과 어두운 글자를 함께 지정합니다. - -## 구성 원칙 - -- `config/`: 운영자가 바꿀 수 있는 MCP, 모델, HMM 데모 사용자 preset, 데모 질문 JSON -- `src/poc4/`: Streamlit과 분리 가능한 화면 보조 모듈 및 도메인 로직 -- `apps/poc4/`: 레거시 호환 entrypoint. 화면 조립과 사용자 상호작용만 담당하도록 점진적으로 축소 -- `tests/`: 설정 파일과 순수 Python 모듈의 회귀 검증 - -다른 프로젝트에 적용할 때는 scenario/MCP JSON만 교체하고, 제품명·설명·색상은 -`AGENT_CONSOLE_*` 환경변수로 지정합니다. 환경변수가 없을 때만 `config/app_profile.json`의 -중립 기본값을 사용합니다. diff --git a/poc4_active_source_20260714/poc4_active_source_20260714.tar.gz b/poc4_active_source_20260714/poc4_active_source_20260714.tar.gz deleted file mode 100644 index 9df851ef6cebef7d017144bec76208d46c0c709a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 74102 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 z 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 diff --git a/poc4_active_source_20260714/scripts/poc4/start_8622_langgraph_tc_ui_nohup.sh b/poc4_active_source_20260714/scripts/poc4/start_8622_langgraph_tc_ui_nohup.sh deleted file mode 100755 index 58b03e4..0000000 --- a/poc4_active_source_20260714/scripts/poc4/start_8622_langgraph_tc_ui_nohup.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -source "/home/opc/poc_4/scripts/poc4/runtime_ui_lib.sh" -poc4_start_service "8622_langgraph_tc_ui" "/home/opc/poc_4/scripts/poc4/run_8622_langgraph_tc_ui.sh" "apps/poc4/langgraph_tc_ui.py" diff --git a/poc4_active_source_20260714/scripts/poc4/status_8622_langgraph_tc_ui_nohup.sh b/poc4_active_source_20260714/scripts/poc4/status_8622_langgraph_tc_ui_nohup.sh deleted file mode 100755 index 565daad..0000000 --- a/poc4_active_source_20260714/scripts/poc4/status_8622_langgraph_tc_ui_nohup.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -source "/home/opc/poc_4/scripts/poc4/runtime_ui_lib.sh" -poc4_status_service "8622_langgraph_tc_ui" "apps/poc4/langgraph_tc_ui.py" diff --git a/poc4_active_source_20260714/src/poc4/__init__.py b/poc4_active_source_20260714/src/poc4/__init__.py deleted file mode 100644 index ae16b2b..0000000 --- a/poc4_active_source_20260714/src/poc4/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""PoC4 UI support modules.""" diff --git a/run.sh b/run.sh index f7a3d2b..ea11a84 100755 --- a/run.sh +++ b/run.sh @@ -131,48 +131,48 @@ do_source() { PGPASSWORD="$PG_PASSWORD" psql \ -h "$PG_HOST" -p "$PG_PORT" -U "$PG_USER" -d "$PG_DB" \ -v ON_ERROR_STOP=1 \ - -f "$ROOT/sql/source/postgres_setup.sql" + -f "$ROOT/database/source/postgres_setup.sql" ok "Postgres source 준비 완료" log "-- MySQL ($MY_HOST:$MY_PORT/$MY_DB) --" mysql \ -h "$MY_HOST" -P "$MY_PORT" -u "$MY_USER" "-p${MY_PASSWORD}" \ - "$MY_DB" < "$ROOT/sql/source/mysql_setup.sql" + "$MY_DB" < "$ROOT/database/source/mysql_setup.sql" ok "MySQL source 준비 완료" } do_adb() { log "=== adb: ADB 측 cleanup → dblinks → perm/ctx/view/policy → end_users ===" - run_sqlplus_file "$ROOT/sql/adb/00_cleanup.sql" - run_sqlplus_file "$ROOT/sql/adb/01_dblinks.sql" - run_sqlplus_file "$ROOT/sql/adb/02_perm_tables.sql" - run_sqlplus_file "$ROOT/sql/adb/03_seed.sql" - run_sqlplus_file "$ROOT/sql/adb/04_secure_ctx.sql" - run_sqlplus_file "$ROOT/sql/adb/05_views.sql" - run_sqlplus_file "$ROOT/sql/adb/06_policy.sql" - run_sqlplus_file "$ROOT/sql/adb/06a_redaction.sql" - run_sqlplus_file "$ROOT/sql/adb/07_end_users.sql" + run_sqlplus_file "$ROOT/database/adb/00_cleanup.sql" + run_sqlplus_file "$ROOT/database/adb/01_dblinks.sql" + run_sqlplus_file "$ROOT/database/adb/02_perm_tables.sql" + run_sqlplus_file "$ROOT/database/adb/03_seed.sql" + run_sqlplus_file "$ROOT/database/adb/04_secure_ctx.sql" + run_sqlplus_file "$ROOT/database/adb/05_views.sql" + run_sqlplus_file "$ROOT/database/adb/06_policy.sql" + run_sqlplus_file "$ROOT/database/adb/06a_redaction.sql" + run_sqlplus_file "$ROOT/database/adb/07_end_users.sql" ok "ADB setup 완료" } do_tests() { log "=== tests: 4-user access matrix 검증 ===" - run_sqlplus_as vpduser_my "$VPDUSER_MY_PASSWORD" "$ROOT/sql/adb/08_tests_user_my.sql" - run_sqlplus_as vpduser_pg "$VPDUSER_PG_PASSWORD" "$ROOT/sql/adb/09_tests_user_pg.sql" - run_sqlplus_as vpduser_both "$VPDUSER_BOTH_PASSWORD" "$ROOT/sql/adb/10_tests_user_both.sql" - run_sqlplus_as vpduser_none "$VPDUSER_NONE_PASSWORD" "$ROOT/sql/adb/11_tests_user_none.sql" + run_sqlplus_as vpduser_my "$VPDUSER_MY_PASSWORD" "$ROOT/database/adb/08_tests_user_my.sql" + run_sqlplus_as vpduser_pg "$VPDUSER_PG_PASSWORD" "$ROOT/database/adb/09_tests_user_pg.sql" + run_sqlplus_as vpduser_both "$VPDUSER_BOTH_PASSWORD" "$ROOT/database/adb/10_tests_user_both.sql" + run_sqlplus_as vpduser_none "$VPDUSER_NONE_PASSWORD" "$ROOT/database/adb/11_tests_user_none.sql" ok "4명 (MY / PG / BOTH / NONE) 테스트 실행 완료" } do_audit() { log "=== audit: admin 으로 정책 / 뷰 / 매트릭스 점검 ===" - run_sqlplus_file "$ROOT/sql/adb/12_tests_admin_audit.sql" + run_sqlplus_file "$ROOT/database/adb/12_tests_admin_audit.sql" ok "audit 완료" } do_backoffice_support() { log "=== backoffice-support: Spring Boot 백오피스 보조 객체 생성 ===" - run_sqlplus_file "$ROOT/sql/adb/25_agent_ords_security_backoffice_support.sql" + run_sqlplus_file "$ROOT/database/adb/25_agent_ords_security_backoffice_support.sql" ok "backoffice-support 완료" } @@ -191,7 +191,7 @@ do_backoffice_env_check() { do_teardown() { log "=== teardown: ADB 측 객체 + dblink/credential 정리 ===" warn "원격 PG/MySQL 의 customers 테이블은 건드리지 않습니다 (수동으로 DROP 하세요)" - run_sqlplus_file "$ROOT/sql/adb/00_cleanup.sql" + run_sqlplus_file "$ROOT/database/adb/00_cleanup.sql" ok "teardown 완료" } @@ -209,24 +209,24 @@ do_dds_prereq() { do_dds_setup() { log "=== dds-setup: 13_dds_variant.sql (END USER + DATA ROLE + DATA GRANT) ===" # 멱등성: 부분 적용 상태를 먼저 청소한 뒤 재생성 - run_sqlplus_file "$ROOT/sql/adb/15_dds_cleanup.sql" - run_sqlplus_file "$ROOT/sql/adb/13_dds_variant.sql" + run_sqlplus_file "$ROOT/database/adb/15_dds_cleanup.sql" + run_sqlplus_file "$ROOT/database/adb/13_dds_variant.sql" ok "DDS 셋업 완료" } do_dds_tests() { log "=== dds-tests: 4-user (ddsuser_*) 매트릭스 검증 ===" # END USER 이름이 lowercase 라 사용자명을 큰따옴표로 감싸서 전달. - run_sqlplus_as '"ddsuser_my"' "$DDSUSER_MY_PASSWORD" "$ROOT/sql/adb/14_tests_dds_user.sql" - run_sqlplus_as '"ddsuser_pg"' "$DDSUSER_PG_PASSWORD" "$ROOT/sql/adb/14_tests_dds_user.sql" - run_sqlplus_as '"ddsuser_both"' "$DDSUSER_BOTH_PASSWORD" "$ROOT/sql/adb/14_tests_dds_user.sql" - run_sqlplus_as '"ddsuser_none"' "$DDSUSER_NONE_PASSWORD" "$ROOT/sql/adb/14_tests_dds_user.sql" + run_sqlplus_as '"ddsuser_my"' "$DDSUSER_MY_PASSWORD" "$ROOT/database/adb/14_tests_dds_user.sql" + run_sqlplus_as '"ddsuser_pg"' "$DDSUSER_PG_PASSWORD" "$ROOT/database/adb/14_tests_dds_user.sql" + run_sqlplus_as '"ddsuser_both"' "$DDSUSER_BOTH_PASSWORD" "$ROOT/database/adb/14_tests_dds_user.sql" + run_sqlplus_as '"ddsuser_none"' "$DDSUSER_NONE_PASSWORD" "$ROOT/database/adb/14_tests_dds_user.sql" ok "DDS 4명 (MY / PG / BOTH / NONE) 테스트 실행 완료" } do_dds_teardown() { log "=== dds-teardown: DDS 객체만 정리 (VPD 데모는 보존) ===" - run_sqlplus_file "$ROOT/sql/adb/15_dds_cleanup.sql" + run_sqlplus_file "$ROOT/database/adb/15_dds_cleanup.sql" ok "DDS teardown 완료" } diff --git a/scripts/configure-backoffice-https-vm.sh b/scripts/configure-backoffice-https-vm.sh index 40a37bc..3eb7631 100755 --- a/scripts/configure-backoffice-https-vm.sh +++ b/scripts/configure-backoffice-https-vm.sh @@ -112,7 +112,7 @@ render_caddyfile() { gsub(/{{TLS_OPTIONS}}/, tls_options) print } - ' "$ROOT/deploy/caddy/Caddyfile.template" > "$output" + ' "$ROOT/deploy/vpd-backoffice/caddy/Caddyfile.template" > "$output" } build_ssh_args() { @@ -264,7 +264,7 @@ INSPECT log "Caddy candidate 업로드" ssh "${SSH_ARGS[@]}" "$HOST" "mkdir -p \"$remote_stage\"" - cp "$ROOT/deploy/caddy/install-caddy-config.sh" "$tmp_dir/install-caddy-config.sh" + cp "$ROOT/deploy/vpd-backoffice/caddy/install-caddy-config.sh" "$tmp_dir/install-caddy-config.sh" tar -C "$tmp_dir" -czf - Caddyfile install-caddy-config.sh \ | ssh "${SSH_ARGS[@]}" "$HOST" "tar -C \"$remote_stage\" -xzf - && chmod 700 \"$remote_stage/install-caddy-config.sh\"" diff --git a/scripts/deploy-backoffice-vm.sh b/scripts/deploy-backoffice-vm.sh index 5fc2451..393c242 100755 --- a/scripts/deploy-backoffice-vm.sh +++ b/scripts/deploy-backoffice-vm.sh @@ -32,7 +32,7 @@ Production transport defaults are always applied on the VM: HTTPS required, Secure/HttpOnly/SameSite session cookie. The script uploads: - - target/*.jar as app.jar + - vpd-backoffice/target/*.jar as app.jar - .env rewritten for the VM wallet path - TNS_ADMIN wallet directory - start.sh / stop.sh / status.sh @@ -127,11 +127,11 @@ ssh "${SSH_ARGS[@]}" "$HOST" 'printf "connected %s %s\n" "$(hostname)" "$(whoami if [[ "$SKIP_BUILD" != "Y" ]]; then log "jar 빌드" - mvn -DskipTests package + mvn -f "$ROOT/vpd-backoffice/pom.xml" -DskipTests package fi -JAR_PATH="$(find "$ROOT/target" -maxdepth 1 -type f -name '*.jar' ! -name '*sources.jar' ! -name '*javadoc.jar' | sort | tail -n 1)" -[[ -n "$JAR_PATH" && -f "$JAR_PATH" ]] || die "target jar를 찾을 수 없습니다. mvn package를 먼저 실행하세요." +JAR_PATH="$(find "$ROOT/vpd-backoffice/target" -maxdepth 1 -type f -name '*.jar' ! -name '*sources.jar' ! -name '*javadoc.jar' | sort | tail -n 1)" +[[ -n "$JAR_PATH" && -f "$JAR_PATH" ]] || die "vpd-backoffice target jar를 찾을 수 없습니다. 배포 빌드를 먼저 실행하세요." TMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/vpd-backoffice-deploy.XXXXXX")" cleanup() { diff --git a/scripts/deploy-dds-backoffice-local.sh b/scripts/deploy-dds-backoffice-local.sh index b5bc50b..99bf0b0 100755 --- a/scripts/deploy-dds-backoffice-local.sh +++ b/scripts/deploy-dds-backoffice-local.sh @@ -13,10 +13,10 @@ if [[ -f /home/opc/apps/vpd-backoffice/.env ]]; then fi cd "$ROOT" -mvn -DskipTests package +mvn -f "$ROOT/vpd-backoffice/pom.xml" -DskipTests package mvn install:install-file \ - -Dfile="$ROOT/target/vpd-permission-backoffice-0.1.0-SNAPSHOT.jar.original" \ - -DpomFile="$ROOT/pom.xml" + -Dfile="$ROOT/vpd-backoffice/target/vpd-permission-backoffice-0.1.0-SNAPSHOT.jar.original" \ + -DpomFile="$ROOT/vpd-backoffice/pom.xml" cd "$ROOT/dds-backoffice" mvn -DskipTests package diff --git a/scripts/provision-dds-token-search.sh b/scripts/provision-dds-token-search.sh index 74c9f29..322902f 100644 --- a/scripts/provision-dds-token-search.sh +++ b/scripts/provision-dds-token-search.sh @@ -15,8 +15,8 @@ connect="${DDS_ADMIN_USERNAME}/${DDS_ADMIN_PASSWORD}@${DDS_ADMIN_DB_URL}" sqlplus -s "$connect" < None: + for relative in ( + "vpd-backoffice/pom.xml", + "vpd-backoffice/src/main", + "ai-web-agent-console/app.py", + "ai-web-agent-console/ai_web_agent_console", + "dds-backoffice/pom.xml", + "database/adb", + "deploy/vpd-backoffice", + "deploy/ai-web-agent-console", + ): + self.assertTrue((ROOT / relative).exists(), relative) + + def test_ambiguous_legacy_roots_are_absent(self) -> None: + for relative in ( + "pom.xml", + "src", + "sql", + "poc4_active_source_20260714", + ): + self.assertFalse((ROOT / relative).exists(), relative) + + def test_console_has_one_named_python_package(self) -> None: + console = ROOT / "ai-web-agent-console" + self.assertFalse((console / "src").exists()) + self.assertFalse((console / "apps").exists()) + self.assertTrue((console / "ai_web_agent_console" / "__init__.py").is_file()) + + source = "\n".join( + path.read_text(encoding="utf-8") + for path in (console / "ai_web_agent_console").glob("*.py") + ) + source += (console / "app.py").read_text(encoding="utf-8") + for forbidden in ( + "from src.", + "src.poc3", + "src.poc4", + "src.agent_console", + "apps/poc4", + ): + self.assertNotIn(forbidden, source) + + def test_vpd_backoffice_packages_database_sql(self) -> None: + pom = (ROOT / "vpd-backoffice" / "pom.xml").read_text(encoding="utf-8") + self.assertIn("../database/adb", pom) + self.assertNotIn("sql/adb", pom) + + +if __name__ == "__main__": + unittest.main() diff --git a/pom.xml b/vpd-backoffice/pom.xml similarity index 95% rename from pom.xml rename to vpd-backoffice/pom.xml index eefd731..d49518b 100644 --- a/pom.xml +++ b/vpd-backoffice/pom.xml @@ -92,11 +92,11 @@ src/main/resources - - sql/adb - sql/adb + ../database/adb + database/adb **/*.sql diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/VpdBackofficeApplication.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/VpdBackofficeApplication.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/VpdBackofficeApplication.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/VpdBackofficeApplication.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/config/AppConfig.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/config/AppConfig.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/config/AppConfig.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/config/AppConfig.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/config/BackofficeProperties.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/config/BackofficeProperties.java similarity index 97% rename from src/main/java/com/cloudhandson/vpdbackoffice/config/BackofficeProperties.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/config/BackofficeProperties.java index e5edcd2..3b31783 100644 --- a/src/main/java/com/cloudhandson/vpdbackoffice/config/BackofficeProperties.java +++ b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/config/BackofficeProperties.java @@ -82,7 +82,8 @@ public record BackofficeProperties( String profile, String runtimeDbUrl, String runtimeDbUsername, - String runtimeDbPassword + String runtimeDbPassword, + String queryContractFile ) { public boolean configured() { diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/config/CatalogProperties.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/config/CatalogProperties.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/config/CatalogProperties.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/config/CatalogProperties.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/config/DbPoolWarmup.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/config/DbPoolWarmup.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/config/DbPoolWarmup.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/config/DbPoolWarmup.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/config/MaskingProperties.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/config/MaskingProperties.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/config/MaskingProperties.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/config/MaskingProperties.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/config/McpProperties.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/config/McpProperties.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/config/McpProperties.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/config/McpProperties.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/config/OrdsClientConfig.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/config/OrdsClientConfig.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/config/OrdsClientConfig.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/config/OrdsClientConfig.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/config/ProductProperties.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/config/ProductProperties.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/config/ProductProperties.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/config/ProductProperties.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/config/SecurityConfig.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/config/SecurityConfig.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/config/SecurityConfig.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/config/SecurityConfig.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/config/SecuritySqlScriptProperties.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/config/SecuritySqlScriptProperties.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/config/SecuritySqlScriptProperties.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/config/SecuritySqlScriptProperties.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/audit/AuditEvent.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/audit/AuditEvent.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/audit/AuditEvent.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/audit/AuditEvent.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/effective/EffectiveMatrixView.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/effective/EffectiveMatrixView.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/effective/EffectiveMatrixView.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/effective/EffectiveMatrixView.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/effective/GroupEffectiveAccessView.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/effective/GroupEffectiveAccessView.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/effective/GroupEffectiveAccessView.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/effective/GroupEffectiveAccessView.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/effective/RoleEffectiveImpactView.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/effective/RoleEffectiveImpactView.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/effective/RoleEffectiveImpactView.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/effective/RoleEffectiveImpactView.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/effective/UserEffectiveAccessView.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/effective/UserEffectiveAccessView.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/effective/UserEffectiveAccessView.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/effective/UserEffectiveAccessView.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/group/AppGroup.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/group/AppGroup.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/group/AppGroup.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/group/AppGroup.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/group/GroupCreateCommand.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/group/GroupCreateCommand.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/group/GroupCreateCommand.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/group/GroupCreateCommand.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/group/GroupRoleView.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/group/GroupRoleView.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/group/GroupRoleView.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/group/GroupRoleView.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/group/GroupUserView.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/group/GroupUserView.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/group/GroupUserView.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/group/GroupUserView.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/masking/ColumnMaskingRule.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/masking/ColumnMaskingRule.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/masking/ColumnMaskingRule.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/masking/ColumnMaskingRule.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/masking/MaskingPolicyStatus.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/masking/MaskingPolicyStatus.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/masking/MaskingPolicyStatus.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/masking/MaskingPolicyStatus.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/masking/MaskingRule.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/masking/MaskingRule.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/masking/MaskingRule.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/masking/MaskingRule.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/masking/MaskingRuleCreateCommand.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/masking/MaskingRuleCreateCommand.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/masking/MaskingRuleCreateCommand.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/masking/MaskingRuleCreateCommand.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/masking/MaskingTemplate.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/masking/MaskingTemplate.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/masking/MaskingTemplate.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/masking/MaskingTemplate.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/masking/UserMaskingRule.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/masking/UserMaskingRule.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/masking/UserMaskingRule.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/masking/UserMaskingRule.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/mcp/McpChatbotResult.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/mcp/McpChatbotResult.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/mcp/McpChatbotResult.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/mcp/McpChatbotResult.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/mcp/McpClientDemoResult.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/mcp/McpClientDemoResult.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/mcp/McpClientDemoResult.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/mcp/McpClientDemoResult.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/mcp/McpReasoningCommand.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/mcp/McpReasoningCommand.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/mcp/McpReasoningCommand.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/mcp/McpReasoningCommand.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/mcp/McpReasoningResult.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/mcp/McpReasoningResult.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/mcp/McpReasoningResult.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/mcp/McpReasoningResult.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/mcp/McpToolView.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/mcp/McpToolView.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/mcp/McpToolView.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/mcp/McpToolView.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/operation/OperationHealthSummary.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/operation/OperationHealthSummary.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/operation/OperationHealthSummary.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/operation/OperationHealthSummary.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/operation/OperationStatusRow.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/operation/OperationStatusRow.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/operation/OperationStatusRow.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/operation/OperationStatusRow.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/ords/OrdsHandlerUpdateCommand.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/ords/OrdsHandlerUpdateCommand.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/ords/OrdsHandlerUpdateCommand.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/ords/OrdsHandlerUpdateCommand.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/ords/OrdsHandlerView.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/ords/OrdsHandlerView.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/ords/OrdsHandlerView.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/ords/OrdsHandlerView.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/ords/OrdsObjectHandlerResult.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/ords/OrdsObjectHandlerResult.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/ords/OrdsObjectHandlerResult.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/ords/OrdsObjectHandlerResult.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/permission/AppRole.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/permission/AppRole.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/permission/AppRole.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/permission/AppRole.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/permission/PermissionRule.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/permission/PermissionRule.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/permission/PermissionRule.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/permission/PermissionRule.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/permission/PermissionSet.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/permission/PermissionSet.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/permission/PermissionSet.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/permission/PermissionSet.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/permission/PermissionSetCommand.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/permission/PermissionSetCommand.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/permission/PermissionSetCommand.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/permission/PermissionSetCommand.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/permission/PermissionView.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/permission/PermissionView.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/permission/PermissionView.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/permission/PermissionView.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/permission/RuleCommand.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/permission/RuleCommand.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/permission/RuleCommand.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/permission/RuleCommand.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/probe/FgaExecutionEvidence.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/probe/FgaExecutionEvidence.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/probe/FgaExecutionEvidence.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/probe/FgaExecutionEvidence.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/probe/ProbeCommand.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/probe/ProbeCommand.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/probe/ProbeCommand.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/probe/ProbeCommand.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/probe/ProbeResult.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/probe/ProbeResult.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/probe/ProbeResult.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/probe/ProbeResult.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/probe/ProbeStatus.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/probe/ProbeStatus.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/probe/ProbeStatus.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/probe/ProbeStatus.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/probe/SqlExecutionCandidate.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/probe/SqlExecutionCandidate.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/probe/SqlExecutionCandidate.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/probe/SqlExecutionCandidate.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/probe/SqlExecutionEvidence.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/probe/SqlExecutionEvidence.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/probe/SqlExecutionEvidence.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/probe/SqlExecutionEvidence.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/protectedobject/DatabaseObjectOption.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/protectedobject/DatabaseObjectOption.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/protectedobject/DatabaseObjectOption.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/protectedobject/DatabaseObjectOption.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/protectedobject/ProtectedColumn.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/protectedobject/ProtectedColumn.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/protectedobject/ProtectedColumn.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/protectedobject/ProtectedColumn.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/protectedobject/ProtectedObject.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/protectedobject/ProtectedObject.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/protectedobject/ProtectedObject.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/protectedobject/ProtectedObject.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/protectedobject/ProtectedObjectCreateCommand.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/protectedobject/ProtectedObjectCreateCommand.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/protectedobject/ProtectedObjectCreateCommand.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/protectedobject/ProtectedObjectCreateCommand.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/schema/SchemaActionResult.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/schema/SchemaActionResult.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/schema/SchemaActionResult.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/schema/SchemaActionResult.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/schema/SchemaPreflightCheck.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/schema/SchemaPreflightCheck.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/schema/SchemaPreflightCheck.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/schema/SchemaPreflightCheck.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/schema/SchemaPreflightView.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/schema/SchemaPreflightView.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/schema/SchemaPreflightView.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/schema/SchemaPreflightView.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/schemametadata/SchemaAnnotation.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/schemametadata/SchemaAnnotation.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/schemametadata/SchemaAnnotation.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/schemametadata/SchemaAnnotation.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/schemametadata/SchemaMetadataColumn.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/schemametadata/SchemaMetadataColumn.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/schemametadata/SchemaMetadataColumn.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/schemametadata/SchemaMetadataColumn.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/schemametadata/SchemaMetadataView.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/schemametadata/SchemaMetadataView.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/schemametadata/SchemaMetadataView.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/schemametadata/SchemaMetadataView.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/securityscript/SecuritySqlScript.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/securityscript/SecuritySqlScript.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/securityscript/SecuritySqlScript.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/securityscript/SecuritySqlScript.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/securityscript/SecuritySqlScriptExplanation.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/securityscript/SecuritySqlScriptExplanation.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/securityscript/SecuritySqlScriptExplanation.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/securityscript/SecuritySqlScriptExplanation.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/securityscript/SecuritySqlScriptSummary.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/securityscript/SecuritySqlScriptSummary.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/securityscript/SecuritySqlScriptSummary.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/securityscript/SecuritySqlScriptSummary.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/setting/BackofficeSetting.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/setting/BackofficeSetting.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/setting/BackofficeSetting.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/setting/BackofficeSetting.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/stakeholder/StakeholderTokenSubject.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/stakeholder/StakeholderTokenSubject.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/stakeholder/StakeholderTokenSubject.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/stakeholder/StakeholderTokenSubject.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/structured/StructuredDataPreview.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/structured/StructuredDataPreview.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/structured/StructuredDataPreview.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/structured/StructuredDataPreview.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/structured/StructuredDataTable.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/structured/StructuredDataTable.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/structured/StructuredDataTable.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/structured/StructuredDataTable.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/token/BearerTokenRecord.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/token/BearerTokenRecord.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/token/BearerTokenRecord.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/token/BearerTokenRecord.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/token/IssuedToken.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/token/IssuedToken.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/token/IssuedToken.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/token/IssuedToken.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/token/TokenContextView.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/token/TokenContextView.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/token/TokenContextView.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/token/TokenContextView.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/token/TokenIssueCommand.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/token/TokenIssueCommand.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/token/TokenIssueCommand.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/token/TokenIssueCommand.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/user/AppUser.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/user/AppUser.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/user/AppUser.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/user/AppUser.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/user/UserCreateCommand.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/user/UserCreateCommand.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/user/UserCreateCommand.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/user/UserCreateCommand.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/user/UserRoleView.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/user/UserRoleView.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/user/UserRoleView.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/user/UserRoleView.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/vector/VectorChunk.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/vector/VectorChunk.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/vector/VectorChunk.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/vector/VectorChunk.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/vector/VectorIngestCommand.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/vector/VectorIngestCommand.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/vector/VectorIngestCommand.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/vector/VectorIngestCommand.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/vector/VectorIngestResult.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/vector/VectorIngestResult.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/vector/VectorIngestResult.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/vector/VectorIngestResult.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/vector/VectorKnowledgeSummary.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/vector/VectorKnowledgeSummary.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/vector/VectorKnowledgeSummary.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/vector/VectorKnowledgeSummary.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/vector/VectorQueryEmbedding.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/vector/VectorQueryEmbedding.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/vector/VectorQueryEmbedding.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/vector/VectorQueryEmbedding.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/vector/VectorSearchResult.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/vector/VectorSearchResult.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/vector/VectorSearchResult.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/vector/VectorSearchResult.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdBulkApplyResult.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdBulkApplyResult.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdBulkApplyResult.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdBulkApplyResult.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdDescriptionNote.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdDescriptionNote.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdDescriptionNote.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdDescriptionNote.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdFunctionOption.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdFunctionOption.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdFunctionOption.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdFunctionOption.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdFunctionSource.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdFunctionSource.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdFunctionSource.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdFunctionSource.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdObjectFilterDetail.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdObjectFilterDetail.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdObjectFilterDetail.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdObjectFilterDetail.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdPolicyCreateCommand.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdPolicyCreateCommand.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdPolicyCreateCommand.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdPolicyCreateCommand.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdPolicyDetail.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdPolicyDetail.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdPolicyDetail.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdPolicyDetail.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdPolicyExplanation.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdPolicyExplanation.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdPolicyExplanation.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdPolicyExplanation.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdPolicyFormOptions.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdPolicyFormOptions.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdPolicyFormOptions.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdPolicyFormOptions.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdPolicyTemplateOption.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdPolicyTemplateOption.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdPolicyTemplateOption.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdPolicyTemplateOption.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdPolicyView.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdPolicyView.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdPolicyView.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdPolicyView.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdSchemaObjectOption.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdSchemaObjectOption.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdSchemaObjectOption.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdSchemaObjectOption.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdTargetView.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdTargetView.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdTargetView.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdTargetView.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/mapper/AuditMapper.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/mapper/AuditMapper.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/mapper/AuditMapper.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/mapper/AuditMapper.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/mapper/BearerTokenMapper.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/mapper/BearerTokenMapper.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/mapper/BearerTokenMapper.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/mapper/BearerTokenMapper.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/mapper/GroupMapper.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/mapper/GroupMapper.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/mapper/GroupMapper.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/mapper/GroupMapper.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/mapper/MaskingRuleMapper.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/mapper/MaskingRuleMapper.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/mapper/MaskingRuleMapper.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/mapper/MaskingRuleMapper.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/mapper/OperationStatusMapper.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/mapper/OperationStatusMapper.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/mapper/OperationStatusMapper.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/mapper/OperationStatusMapper.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/mapper/OrdsMetadataMapper.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/mapper/OrdsMetadataMapper.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/mapper/OrdsMetadataMapper.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/mapper/OrdsMetadataMapper.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/mapper/PermissionMapper.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/mapper/PermissionMapper.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/mapper/PermissionMapper.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/mapper/PermissionMapper.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/mapper/ProtectedObjectMapper.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/mapper/ProtectedObjectMapper.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/mapper/ProtectedObjectMapper.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/mapper/ProtectedObjectMapper.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/mapper/SettingMapper.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/mapper/SettingMapper.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/mapper/SettingMapper.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/mapper/SettingMapper.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/mapper/StakeholderMapper.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/mapper/StakeholderMapper.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/mapper/StakeholderMapper.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/mapper/StakeholderMapper.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/mapper/UserMapper.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/mapper/UserMapper.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/mapper/UserMapper.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/mapper/UserMapper.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/mapper/VpdPolicyMapper.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/mapper/VpdPolicyMapper.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/mapper/VpdPolicyMapper.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/mapper/VpdPolicyMapper.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/AppException.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/AppException.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/AppException.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/AppException.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/AuditService.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/AuditService.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/AuditService.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/AuditService.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/BackofficeSchemaService.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/BackofficeSchemaService.java similarity index 99% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/BackofficeSchemaService.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/BackofficeSchemaService.java index a3b6603..173901d 100644 --- a/src/main/java/com/cloudhandson/vpdbackoffice/service/BackofficeSchemaService.java +++ b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/BackofficeSchemaService.java @@ -709,8 +709,8 @@ public class BackofficeSchemaService { return """ -- sqlcl에서 실행할 HMM VPD runtime 준비 순서 -- 1. ADMIN으로 접속 - @sql/adb/25_agent_ords_security_backoffice_support.sql - @sql/adb/72_hmm_leave_team_vpd.sql + @database/adb/25_agent_ords_security_backoffice_support.sql + @database/adb/72_hmm_leave_team_vpd.sql -- 2. 비면제 업무 runtime 사용자에 필요한 최소 권한 CONNECT %s/@ diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/BearerTokenService.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/BearerTokenService.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/BearerTokenService.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/BearerTokenService.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/DataCatalog.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/DataCatalog.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/DataCatalog.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/DataCatalog.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/EffectiveMatrixService.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/EffectiveMatrixService.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/EffectiveMatrixService.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/EffectiveMatrixService.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/EnvironmentDataCatalog.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/EnvironmentDataCatalog.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/EnvironmentDataCatalog.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/EnvironmentDataCatalog.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/EnvironmentMaskingPolicyCatalog.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/EnvironmentMaskingPolicyCatalog.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/EnvironmentMaskingPolicyCatalog.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/EnvironmentMaskingPolicyCatalog.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/EnvironmentMcpToolCatalog.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/EnvironmentMcpToolCatalog.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/EnvironmentMcpToolCatalog.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/EnvironmentMcpToolCatalog.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/ExternalAuthorizationChangeNotifier.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/ExternalAuthorizationChangeNotifier.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/ExternalAuthorizationChangeNotifier.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/ExternalAuthorizationChangeNotifier.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/ExternalAuthorizationSynchronizer.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/ExternalAuthorizationSynchronizer.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/ExternalAuthorizationSynchronizer.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/ExternalAuthorizationSynchronizer.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/GroupService.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/GroupService.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/GroupService.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/GroupService.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/HmmAiAgentToolRunner.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/HmmAiAgentToolRunner.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/HmmAiAgentToolRunner.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/HmmAiAgentToolRunner.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/HmmKnowledgeSchemaInitializer.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/HmmKnowledgeSchemaInitializer.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/HmmKnowledgeSchemaInitializer.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/HmmKnowledgeSchemaInitializer.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/HmmMcpBearerAuthenticator.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/HmmMcpBearerAuthenticator.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/HmmMcpBearerAuthenticator.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/HmmMcpBearerAuthenticator.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/HmmMcpPrincipal.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/HmmMcpPrincipal.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/HmmMcpPrincipal.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/HmmMcpPrincipal.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/HmmRuntimeSettingsInitializer.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/HmmRuntimeSettingsInitializer.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/HmmRuntimeSettingsInitializer.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/HmmRuntimeSettingsInitializer.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/JdbcHmmAiAgentToolRunner.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/JdbcHmmAiAgentToolRunner.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/JdbcHmmAiAgentToolRunner.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/JdbcHmmAiAgentToolRunner.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/JdbcHmmMcpBearerAuthenticator.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/JdbcHmmMcpBearerAuthenticator.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/JdbcHmmMcpBearerAuthenticator.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/JdbcHmmMcpBearerAuthenticator.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/MaskingPolicyCatalog.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/MaskingPolicyCatalog.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/MaskingPolicyCatalog.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/MaskingPolicyCatalog.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/MaskingPolicySynchronizer.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/MaskingPolicySynchronizer.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/MaskingPolicySynchronizer.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/MaskingPolicySynchronizer.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/MaskingPolicyTarget.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/MaskingPolicyTarget.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/MaskingPolicyTarget.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/MaskingPolicyTarget.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/MaskingRuleService.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/MaskingRuleService.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/MaskingRuleService.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/MaskingRuleService.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/McpChatbotService.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/McpChatbotService.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/McpChatbotService.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/McpChatbotService.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/McpClientDemoService.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/McpClientDemoService.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/McpClientDemoService.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/McpClientDemoService.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/McpReasoningService.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/McpReasoningService.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/McpReasoningService.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/McpReasoningService.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/McpSseService.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/McpSseService.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/McpSseService.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/McpSseService.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/McpToolCatalog.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/McpToolCatalog.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/McpToolCatalog.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/McpToolCatalog.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/McpToolDefinition.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/McpToolDefinition.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/McpToolDefinition.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/McpToolDefinition.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/McpToolRegistry.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/McpToolRegistry.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/McpToolRegistry.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/McpToolRegistry.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/McpUnauthorizedException.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/McpUnauthorizedException.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/McpUnauthorizedException.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/McpUnauthorizedException.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/OciGenerativeAiChatClient.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/OciGenerativeAiChatClient.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/OciGenerativeAiChatClient.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/OciGenerativeAiChatClient.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/OpenAiCompatibleClient.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/OpenAiCompatibleClient.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/OpenAiCompatibleClient.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/OpenAiCompatibleClient.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/OperationStatusService.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/OperationStatusService.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/OperationStatusService.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/OperationStatusService.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/OrdsMetadataService.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/OrdsMetadataService.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/OrdsMetadataService.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/OrdsMetadataService.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/OrdsProbeService.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/OrdsProbeService.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/OrdsProbeService.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/OrdsProbeService.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/PermissionService.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/PermissionService.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/PermissionService.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/PermissionService.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/ProbeErrorClassifier.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/ProbeErrorClassifier.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/ProbeErrorClassifier.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/ProbeErrorClassifier.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/ProtectedObjectService.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/ProtectedObjectService.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/ProtectedObjectService.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/ProtectedObjectService.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/SchemaMetadataService.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/SchemaMetadataService.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/SchemaMetadataService.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/SchemaMetadataService.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/SecuritySqlScriptExplanationService.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/SecuritySqlScriptExplanationService.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/SecuritySqlScriptExplanationService.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/SecuritySqlScriptExplanationService.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/SecuritySqlScriptService.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/SecuritySqlScriptService.java similarity index 96% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/SecuritySqlScriptService.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/SecuritySqlScriptService.java index 1550c72..61dabdb 100644 --- a/src/main/java/com/cloudhandson/vpdbackoffice/service/SecuritySqlScriptService.java +++ b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/SecuritySqlScriptService.java @@ -17,7 +17,7 @@ import org.springframework.stereotype.Service; /** * Read-only catalogue of security deployment SQL bundled from the Git-tracked - * sql/adb directory. Script ids are an application whitelist: request input + * database/adb directory. Script ids are an application whitelist: request input * never becomes a filesystem or classpath path. */ @Service @@ -64,7 +64,7 @@ public class SecuritySqlScriptService { } private String readSource(String fileName) { - ClassPathResource resource = new ClassPathResource("sql/adb/" + fileName); + ClassPathResource resource = new ClassPathResource("database/adb/" + fileName); try (InputStream input = resource.getInputStream()) { return new String(input.readAllBytes(), StandardCharsets.UTF_8); } catch (IOException exception) { diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/SelectAiAgentOrdsService.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/SelectAiAgentOrdsService.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/SelectAiAgentOrdsService.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/SelectAiAgentOrdsService.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/SelectAiService.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/SelectAiService.java similarity index 88% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/SelectAiService.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/SelectAiService.java index ef5c239..453bb4c 100644 --- a/src/main/java/com/cloudhandson/vpdbackoffice/service/SelectAiService.java +++ b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/SelectAiService.java @@ -7,6 +7,9 @@ import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import java.math.BigDecimal; import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; import java.sql.CallableStatement; import java.sql.Connection; import java.sql.DriverManager; @@ -63,7 +66,13 @@ public class SelectAiService { } String generatedSql = generate( - selectAi, vpdAwarePrompt(normalizedPrompt, principal)); + selectAi, + vpdAwarePrompt( + normalizedPrompt, + principal, + loadQueryContract(selectAi.queryContractFile()) + ) + ); String normalizedSql = validateReadOnlySql(generatedSql); QueryExecution execution = executeReadOnly( selectAi, normalizedSql, bearerToken.trim(), principal); @@ -94,8 +103,12 @@ public class SelectAiService { return normalized; } - private String vpdAwarePrompt(String prompt, HmmMcpPrincipal principal) { - return """ + private String vpdAwarePrompt( + String prompt, + HmmMcpPrincipal principal, + String queryContract + ) { + String basePrompt = """ Oracle SQL 생성 규칙: - 프로필에 승인된 HMM HR 객체만 사용하세요. - 단일 읽기 전용 SELECT 또는 WITH 문을 생성하세요. @@ -104,6 +117,37 @@ public class SelectAiService { 사용자 질문: %s """.formatted(principal.employeeCode(), prompt); + if (queryContract == null || queryContract.isBlank()) { + return basePrompt; + } + return basePrompt + """ + + 배포별 질의 계약(JSON): + 아래 계약 중 사용자 질문에 해당하는 항목만 적용하세요. 필수 필드, 계산, + 시간 기준, 누락 레코드 의미와 금지 fallback을 그대로 지키세요. + """ + queryContract; + } + + private String loadQueryContract(String configuredPath) { + if (configuredPath == null || configuredPath.isBlank()) { + return ""; + } + try { + Path path = Path.of(configuredPath.trim()).toAbsolutePath().normalize(); + if (!Files.isRegularFile(path)) { + throw new AppException("Select AI 질의 계약 파일을 찾을 수 없습니다: " + path); + } + long size = Files.size(path); + if (size < 2 || size > 128_000) { + throw new AppException("Select AI 질의 계약 파일 크기가 허용 범위를 벗어났습니다."); + } + return Files.readString(path, StandardCharsets.UTF_8); + } catch (AppException exception) { + throw exception; + } catch (Exception exception) { + throw new AppException("Select AI 질의 계약 파일을 읽지 못했습니다: " + + safeMessage(exception)); + } } private String generate( diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/SettingService.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/SettingService.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/SettingService.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/SettingService.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/StructuredDataService.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/StructuredDataService.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/StructuredDataService.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/StructuredDataService.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/TokenGenerator.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/TokenGenerator.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/TokenGenerator.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/TokenGenerator.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/TokenHasher.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/TokenHasher.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/TokenHasher.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/TokenHasher.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/UserService.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/UserService.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/UserService.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/UserService.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/VectorChunker.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/VectorChunker.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/VectorChunker.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/VectorChunker.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/VectorKnowledgeService.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/VectorKnowledgeService.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/VectorKnowledgeService.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/VectorKnowledgeService.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/VpdPolicyService.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/VpdPolicyService.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/VpdPolicyService.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/VpdPolicyService.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/VpdTokenAccessDeniedException.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/VpdTokenAccessDeniedException.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/service/VpdTokenAccessDeniedException.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/VpdTokenAccessDeniedException.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/web/AppExceptionHandler.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/AppExceptionHandler.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/web/AppExceptionHandler.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/AppExceptionHandler.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/web/DashboardController.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/DashboardController.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/web/DashboardController.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/DashboardController.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/web/EffectiveMatrixController.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/EffectiveMatrixController.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/web/EffectiveMatrixController.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/EffectiveMatrixController.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/web/GroupController.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/GroupController.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/web/GroupController.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/GroupController.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/web/LoginController.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/LoginController.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/web/LoginController.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/LoginController.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/web/MaskingRuleController.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/MaskingRuleController.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/web/MaskingRuleController.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/MaskingRuleController.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/web/McpChatbotController.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/McpChatbotController.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/web/McpChatbotController.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/McpChatbotController.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/web/McpClientDemoController.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/McpClientDemoController.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/web/McpClientDemoController.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/McpClientDemoController.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/web/McpReasoningController.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/McpReasoningController.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/web/McpReasoningController.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/McpReasoningController.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/web/McpSseController.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/McpSseController.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/web/McpSseController.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/McpSseController.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/web/OperationStatusController.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/OperationStatusController.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/web/OperationStatusController.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/OperationStatusController.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/web/OrdsHandlerController.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/OrdsHandlerController.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/web/OrdsHandlerController.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/OrdsHandlerController.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/web/PermissionController.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/PermissionController.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/web/PermissionController.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/PermissionController.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/web/ProbeController.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/ProbeController.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/web/ProbeController.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/ProbeController.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/web/ProductModelAdvice.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/ProductModelAdvice.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/web/ProductModelAdvice.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/ProductModelAdvice.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/web/ProtectedObjectController.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/ProtectedObjectController.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/web/ProtectedObjectController.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/ProtectedObjectController.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/web/RoleController.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/RoleController.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/web/RoleController.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/RoleController.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/web/RuntimeErrorMessage.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/RuntimeErrorMessage.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/web/RuntimeErrorMessage.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/RuntimeErrorMessage.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/web/RuntimeErrorMessages.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/RuntimeErrorMessages.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/web/RuntimeErrorMessages.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/RuntimeErrorMessages.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/web/SchemaMetadataController.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/SchemaMetadataController.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/web/SchemaMetadataController.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/SchemaMetadataController.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/web/SecurityModelAdvice.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/SecurityModelAdvice.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/web/SecurityModelAdvice.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/SecurityModelAdvice.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/web/SecuritySqlScriptController.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/SecuritySqlScriptController.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/web/SecuritySqlScriptController.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/SecuritySqlScriptController.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/web/SettingController.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/SettingController.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/web/SettingController.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/SettingController.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/web/StructuredDataController.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/StructuredDataController.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/web/StructuredDataController.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/StructuredDataController.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/web/TokenController.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/TokenController.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/web/TokenController.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/TokenController.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/web/UserController.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/UserController.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/web/UserController.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/UserController.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/web/UserMaskingRuleController.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/UserMaskingRuleController.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/web/UserMaskingRuleController.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/UserMaskingRuleController.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/web/VectorKnowledgeController.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/VectorKnowledgeController.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/web/VectorKnowledgeController.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/VectorKnowledgeController.java diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/web/VpdPolicyController.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/VpdPolicyController.java similarity index 100% rename from src/main/java/com/cloudhandson/vpdbackoffice/web/VpdPolicyController.java rename to vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/web/VpdPolicyController.java diff --git a/src/main/resources/application.yml b/vpd-backoffice/src/main/resources/application.yml similarity index 96% rename from src/main/resources/application.yml rename to vpd-backoffice/src/main/resources/application.yml index 16f5d20..6824731 100644 --- a/src/main/resources/application.yml +++ b/vpd-backoffice/src/main/resources/application.yml @@ -78,6 +78,8 @@ backoffice: runtime-db-url: ${BACKOFFICE_SELECT_AI_RUNTIME_DB_URL:} runtime-db-username: ${BACKOFFICE_SELECT_AI_RUNTIME_DB_USERNAME:} runtime-db-password: ${BACKOFFICE_SELECT_AI_RUNTIME_DB_PASSWORD:} + # Customer/project query semantics are external JSON, never Java constants. + query-contract-file: ${BACKOFFICE_SELECT_AI_QUERY_CONTRACT_FILE:} catalog: owner: ${BACKOFFICE_CATALOG_OWNER:} objects: ${BACKOFFICE_CATALOG_OBJECTS:} diff --git a/src/main/resources/mapper/AuditMapper.xml b/vpd-backoffice/src/main/resources/mapper/AuditMapper.xml similarity index 100% rename from src/main/resources/mapper/AuditMapper.xml rename to vpd-backoffice/src/main/resources/mapper/AuditMapper.xml diff --git a/src/main/resources/mapper/BearerTokenMapper.xml b/vpd-backoffice/src/main/resources/mapper/BearerTokenMapper.xml similarity index 100% rename from src/main/resources/mapper/BearerTokenMapper.xml rename to vpd-backoffice/src/main/resources/mapper/BearerTokenMapper.xml diff --git a/src/main/resources/mapper/GroupMapper.xml b/vpd-backoffice/src/main/resources/mapper/GroupMapper.xml similarity index 100% rename from src/main/resources/mapper/GroupMapper.xml rename to vpd-backoffice/src/main/resources/mapper/GroupMapper.xml diff --git a/src/main/resources/mapper/MaskingRuleMapper.xml b/vpd-backoffice/src/main/resources/mapper/MaskingRuleMapper.xml similarity index 100% rename from src/main/resources/mapper/MaskingRuleMapper.xml rename to vpd-backoffice/src/main/resources/mapper/MaskingRuleMapper.xml diff --git a/src/main/resources/mapper/OperationStatusMapper.xml b/vpd-backoffice/src/main/resources/mapper/OperationStatusMapper.xml similarity index 100% rename from src/main/resources/mapper/OperationStatusMapper.xml rename to vpd-backoffice/src/main/resources/mapper/OperationStatusMapper.xml diff --git a/src/main/resources/mapper/OrdsMetadataMapper.xml b/vpd-backoffice/src/main/resources/mapper/OrdsMetadataMapper.xml similarity index 100% rename from src/main/resources/mapper/OrdsMetadataMapper.xml rename to vpd-backoffice/src/main/resources/mapper/OrdsMetadataMapper.xml diff --git a/src/main/resources/mapper/PermissionMapper.xml b/vpd-backoffice/src/main/resources/mapper/PermissionMapper.xml similarity index 100% rename from src/main/resources/mapper/PermissionMapper.xml rename to vpd-backoffice/src/main/resources/mapper/PermissionMapper.xml diff --git a/src/main/resources/mapper/ProtectedObjectMapper.xml b/vpd-backoffice/src/main/resources/mapper/ProtectedObjectMapper.xml similarity index 100% rename from src/main/resources/mapper/ProtectedObjectMapper.xml rename to vpd-backoffice/src/main/resources/mapper/ProtectedObjectMapper.xml diff --git a/src/main/resources/mapper/SettingMapper.xml b/vpd-backoffice/src/main/resources/mapper/SettingMapper.xml similarity index 100% rename from src/main/resources/mapper/SettingMapper.xml rename to vpd-backoffice/src/main/resources/mapper/SettingMapper.xml diff --git a/src/main/resources/mapper/StakeholderMapper.xml b/vpd-backoffice/src/main/resources/mapper/StakeholderMapper.xml similarity index 100% rename from src/main/resources/mapper/StakeholderMapper.xml rename to vpd-backoffice/src/main/resources/mapper/StakeholderMapper.xml diff --git a/src/main/resources/mapper/UserMapper.xml b/vpd-backoffice/src/main/resources/mapper/UserMapper.xml similarity index 100% rename from src/main/resources/mapper/UserMapper.xml rename to vpd-backoffice/src/main/resources/mapper/UserMapper.xml diff --git a/src/main/resources/mapper/VpdPolicyMapper.xml b/vpd-backoffice/src/main/resources/mapper/VpdPolicyMapper.xml similarity index 100% rename from src/main/resources/mapper/VpdPolicyMapper.xml rename to vpd-backoffice/src/main/resources/mapper/VpdPolicyMapper.xml diff --git a/src/main/resources/static/css/app.css b/vpd-backoffice/src/main/resources/static/css/app.css similarity index 100% rename from src/main/resources/static/css/app.css rename to vpd-backoffice/src/main/resources/static/css/app.css diff --git a/src/main/resources/static/js/app.js b/vpd-backoffice/src/main/resources/static/js/app.js similarity index 100% rename from src/main/resources/static/js/app.js rename to vpd-backoffice/src/main/resources/static/js/app.js diff --git a/src/main/resources/templates/dashboard.html b/vpd-backoffice/src/main/resources/templates/dashboard.html similarity index 100% rename from src/main/resources/templates/dashboard.html rename to vpd-backoffice/src/main/resources/templates/dashboard.html diff --git a/src/main/resources/templates/effective-matrix.html b/vpd-backoffice/src/main/resources/templates/effective-matrix.html similarity index 100% rename from src/main/resources/templates/effective-matrix.html rename to vpd-backoffice/src/main/resources/templates/effective-matrix.html diff --git a/src/main/resources/templates/error.html b/vpd-backoffice/src/main/resources/templates/error.html similarity index 100% rename from src/main/resources/templates/error.html rename to vpd-backoffice/src/main/resources/templates/error.html diff --git a/src/main/resources/templates/fragments/layout.html b/vpd-backoffice/src/main/resources/templates/fragments/layout.html similarity index 100% rename from src/main/resources/templates/fragments/layout.html rename to vpd-backoffice/src/main/resources/templates/fragments/layout.html diff --git a/src/main/resources/templates/fragments/mcp-chatbot-result.html b/vpd-backoffice/src/main/resources/templates/fragments/mcp-chatbot-result.html similarity index 100% rename from src/main/resources/templates/fragments/mcp-chatbot-result.html rename to vpd-backoffice/src/main/resources/templates/fragments/mcp-chatbot-result.html diff --git a/src/main/resources/templates/fragments/mcp-client-demo-result.html b/vpd-backoffice/src/main/resources/templates/fragments/mcp-client-demo-result.html similarity index 100% rename from src/main/resources/templates/fragments/mcp-client-demo-result.html rename to vpd-backoffice/src/main/resources/templates/fragments/mcp-client-demo-result.html diff --git a/src/main/resources/templates/fragments/mcp-reasoning-result.html b/vpd-backoffice/src/main/resources/templates/fragments/mcp-reasoning-result.html similarity index 100% rename from src/main/resources/templates/fragments/mcp-reasoning-result.html rename to vpd-backoffice/src/main/resources/templates/fragments/mcp-reasoning-result.html diff --git a/src/main/resources/templates/fragments/ords-handler-source.html b/vpd-backoffice/src/main/resources/templates/fragments/ords-handler-source.html similarity index 100% rename from src/main/resources/templates/fragments/ords-handler-source.html rename to vpd-backoffice/src/main/resources/templates/fragments/ords-handler-source.html diff --git a/src/main/resources/templates/fragments/probe-result.html b/vpd-backoffice/src/main/resources/templates/fragments/probe-result.html similarity index 100% rename from src/main/resources/templates/fragments/probe-result.html rename to vpd-backoffice/src/main/resources/templates/fragments/probe-result.html diff --git a/src/main/resources/templates/fragments/security-sql-script-explanation.html b/vpd-backoffice/src/main/resources/templates/fragments/security-sql-script-explanation.html similarity index 100% rename from src/main/resources/templates/fragments/security-sql-script-explanation.html rename to vpd-backoffice/src/main/resources/templates/fragments/security-sql-script-explanation.html diff --git a/src/main/resources/templates/fragments/vector-search-result.html b/vpd-backoffice/src/main/resources/templates/fragments/vector-search-result.html similarity index 100% rename from src/main/resources/templates/fragments/vector-search-result.html rename to vpd-backoffice/src/main/resources/templates/fragments/vector-search-result.html diff --git a/src/main/resources/templates/fragments/vpd-function-source.html b/vpd-backoffice/src/main/resources/templates/fragments/vpd-function-source.html similarity index 100% rename from src/main/resources/templates/fragments/vpd-function-source.html rename to vpd-backoffice/src/main/resources/templates/fragments/vpd-function-source.html diff --git a/src/main/resources/templates/fragments/vpd-object-filter-detail.html b/vpd-backoffice/src/main/resources/templates/fragments/vpd-object-filter-detail.html similarity index 100% rename from src/main/resources/templates/fragments/vpd-object-filter-detail.html rename to vpd-backoffice/src/main/resources/templates/fragments/vpd-object-filter-detail.html diff --git a/src/main/resources/templates/fragments/vpd-policy-detail.html b/vpd-backoffice/src/main/resources/templates/fragments/vpd-policy-detail.html similarity index 100% rename from src/main/resources/templates/fragments/vpd-policy-detail.html rename to vpd-backoffice/src/main/resources/templates/fragments/vpd-policy-detail.html diff --git a/src/main/resources/templates/fragments/vpd-policy-explanation.html b/vpd-backoffice/src/main/resources/templates/fragments/vpd-policy-explanation.html similarity index 100% rename from src/main/resources/templates/fragments/vpd-policy-explanation.html rename to vpd-backoffice/src/main/resources/templates/fragments/vpd-policy-explanation.html diff --git a/src/main/resources/templates/groups.html b/vpd-backoffice/src/main/resources/templates/groups.html similarity index 100% rename from src/main/resources/templates/groups.html rename to vpd-backoffice/src/main/resources/templates/groups.html diff --git a/src/main/resources/templates/login.html b/vpd-backoffice/src/main/resources/templates/login.html similarity index 100% rename from src/main/resources/templates/login.html rename to vpd-backoffice/src/main/resources/templates/login.html diff --git a/src/main/resources/templates/masking-rules.html b/vpd-backoffice/src/main/resources/templates/masking-rules.html similarity index 100% rename from src/main/resources/templates/masking-rules.html rename to vpd-backoffice/src/main/resources/templates/masking-rules.html diff --git a/src/main/resources/templates/mcp-chatbot.html b/vpd-backoffice/src/main/resources/templates/mcp-chatbot.html similarity index 100% rename from src/main/resources/templates/mcp-chatbot.html rename to vpd-backoffice/src/main/resources/templates/mcp-chatbot.html diff --git a/src/main/resources/templates/mcp-client-demo.html b/vpd-backoffice/src/main/resources/templates/mcp-client-demo.html similarity index 100% rename from src/main/resources/templates/mcp-client-demo.html rename to vpd-backoffice/src/main/resources/templates/mcp-client-demo.html diff --git a/src/main/resources/templates/mcp-reasoning.html b/vpd-backoffice/src/main/resources/templates/mcp-reasoning.html similarity index 100% rename from src/main/resources/templates/mcp-reasoning.html rename to vpd-backoffice/src/main/resources/templates/mcp-reasoning.html diff --git a/src/main/resources/templates/mcp-sse.html b/vpd-backoffice/src/main/resources/templates/mcp-sse.html similarity index 100% rename from src/main/resources/templates/mcp-sse.html rename to vpd-backoffice/src/main/resources/templates/mcp-sse.html diff --git a/src/main/resources/templates/objects.html b/vpd-backoffice/src/main/resources/templates/objects.html similarity index 100% rename from src/main/resources/templates/objects.html rename to vpd-backoffice/src/main/resources/templates/objects.html diff --git a/src/main/resources/templates/operation-status.html b/vpd-backoffice/src/main/resources/templates/operation-status.html similarity index 100% rename from src/main/resources/templates/operation-status.html rename to vpd-backoffice/src/main/resources/templates/operation-status.html diff --git a/src/main/resources/templates/ords-handlers.html b/vpd-backoffice/src/main/resources/templates/ords-handlers.html similarity index 100% rename from src/main/resources/templates/ords-handlers.html rename to vpd-backoffice/src/main/resources/templates/ords-handlers.html diff --git a/src/main/resources/templates/permissions.html b/vpd-backoffice/src/main/resources/templates/permissions.html similarity index 100% rename from src/main/resources/templates/permissions.html rename to vpd-backoffice/src/main/resources/templates/permissions.html diff --git a/src/main/resources/templates/probe.html b/vpd-backoffice/src/main/resources/templates/probe.html similarity index 100% rename from src/main/resources/templates/probe.html rename to vpd-backoffice/src/main/resources/templates/probe.html diff --git a/src/main/resources/templates/roles.html b/vpd-backoffice/src/main/resources/templates/roles.html similarity index 100% rename from src/main/resources/templates/roles.html rename to vpd-backoffice/src/main/resources/templates/roles.html diff --git a/src/main/resources/templates/schema-metadata.html b/vpd-backoffice/src/main/resources/templates/schema-metadata.html similarity index 100% rename from src/main/resources/templates/schema-metadata.html rename to vpd-backoffice/src/main/resources/templates/schema-metadata.html diff --git a/src/main/resources/templates/security-sql-scripts.html b/vpd-backoffice/src/main/resources/templates/security-sql-scripts.html similarity index 92% rename from src/main/resources/templates/security-sql-scripts.html rename to vpd-backoffice/src/main/resources/templates/security-sql-scripts.html index 9524494..01157fa 100644 --- a/src/main/resources/templates/security-sql-scripts.html +++ b/vpd-backoffice/src/main/resources/templates/security-sql-scripts.html @@ -51,7 +51,7 @@ 62_kb_aso_masking_backoffice_metadata.sql -

Git source: sql/adb/62_kb_aso_masking_backoffice_metadata.sql. 실제 DB 배포본은 행 접근 필터 구조 및 DB 배포 이력과 함께 확인하세요.

+

Git source: database/adb/62_kb_aso_masking_backoffice_metadata.sql. 실제 DB 배포본은 행 접근 필터 구조 및 DB 배포 이력과 함께 확인하세요.

diff --git a/src/main/resources/templates/settings-database.html b/vpd-backoffice/src/main/resources/templates/settings-database.html similarity index 99% rename from src/main/resources/templates/settings-database.html rename to vpd-backoffice/src/main/resources/templates/settings-database.html index c36ab57..3ea34fe 100644 --- a/src/main/resources/templates/settings-database.html +++ b/vpd-backoffice/src/main/resources/templates/settings-database.html @@ -58,7 +58,7 @@

실행 전 SQL 확인

앱에서 실행하는 지원 DDL
CREATE TABLE ...
-
sqlcl로 실행할 VPD/ORDS runtime 순서
@sql/adb/...
+
sqlcl로 실행할 VPD/ORDS runtime 순서
@database/adb/...
diff --git a/src/main/resources/templates/settings.html b/vpd-backoffice/src/main/resources/templates/settings.html similarity index 100% rename from src/main/resources/templates/settings.html rename to vpd-backoffice/src/main/resources/templates/settings.html diff --git a/src/main/resources/templates/structured-data.html b/vpd-backoffice/src/main/resources/templates/structured-data.html similarity index 100% rename from src/main/resources/templates/structured-data.html rename to vpd-backoffice/src/main/resources/templates/structured-data.html diff --git a/src/main/resources/templates/tokens.html b/vpd-backoffice/src/main/resources/templates/tokens.html similarity index 100% rename from src/main/resources/templates/tokens.html rename to vpd-backoffice/src/main/resources/templates/tokens.html diff --git a/src/main/resources/templates/user-masking-rules.html b/vpd-backoffice/src/main/resources/templates/user-masking-rules.html similarity index 100% rename from src/main/resources/templates/user-masking-rules.html rename to vpd-backoffice/src/main/resources/templates/user-masking-rules.html diff --git a/src/main/resources/templates/users.html b/vpd-backoffice/src/main/resources/templates/users.html similarity index 100% rename from src/main/resources/templates/users.html rename to vpd-backoffice/src/main/resources/templates/users.html diff --git a/src/main/resources/templates/vector-knowledge.html b/vpd-backoffice/src/main/resources/templates/vector-knowledge.html similarity index 100% rename from src/main/resources/templates/vector-knowledge.html rename to vpd-backoffice/src/main/resources/templates/vector-knowledge.html diff --git a/src/main/resources/templates/vpd-filter-policies.html b/vpd-backoffice/src/main/resources/templates/vpd-filter-policies.html similarity index 100% rename from src/main/resources/templates/vpd-filter-policies.html rename to vpd-backoffice/src/main/resources/templates/vpd-filter-policies.html diff --git a/src/main/resources/templates/vpd-filter-runtime.html b/vpd-backoffice/src/main/resources/templates/vpd-filter-runtime.html similarity index 100% rename from src/main/resources/templates/vpd-filter-runtime.html rename to vpd-backoffice/src/main/resources/templates/vpd-filter-runtime.html diff --git a/src/main/resources/templates/vpd-policies.html b/vpd-backoffice/src/main/resources/templates/vpd-policies.html similarity index 100% rename from src/main/resources/templates/vpd-policies.html rename to vpd-backoffice/src/main/resources/templates/vpd-policies.html diff --git a/src/test/java/com/cloudhandson/vpdbackoffice/config/TransportSecurityTest.java b/vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/config/TransportSecurityTest.java similarity index 100% rename from src/test/java/com/cloudhandson/vpdbackoffice/config/TransportSecurityTest.java rename to vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/config/TransportSecurityTest.java diff --git a/src/test/java/com/cloudhandson/vpdbackoffice/domain/masking/MaskingTemplateTest.java b/vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/domain/masking/MaskingTemplateTest.java similarity index 100% rename from src/test/java/com/cloudhandson/vpdbackoffice/domain/masking/MaskingTemplateTest.java rename to vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/domain/masking/MaskingTemplateTest.java diff --git a/src/test/java/com/cloudhandson/vpdbackoffice/domain/permission/PermissionViewTest.java b/vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/domain/permission/PermissionViewTest.java similarity index 100% rename from src/test/java/com/cloudhandson/vpdbackoffice/domain/permission/PermissionViewTest.java rename to vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/domain/permission/PermissionViewTest.java diff --git a/src/test/java/com/cloudhandson/vpdbackoffice/domain/probe/ProbeResultTest.java b/vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/domain/probe/ProbeResultTest.java similarity index 100% rename from src/test/java/com/cloudhandson/vpdbackoffice/domain/probe/ProbeResultTest.java rename to vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/domain/probe/ProbeResultTest.java diff --git a/src/test/java/com/cloudhandson/vpdbackoffice/domain/protectedobject/ProtectedColumnTest.java b/vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/domain/protectedobject/ProtectedColumnTest.java similarity index 100% rename from src/test/java/com/cloudhandson/vpdbackoffice/domain/protectedobject/ProtectedColumnTest.java rename to vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/domain/protectedobject/ProtectedColumnTest.java diff --git a/src/test/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdPolicyFormOptionsTest.java b/vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdPolicyFormOptionsTest.java similarity index 100% rename from src/test/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdPolicyFormOptionsTest.java rename to vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdPolicyFormOptionsTest.java diff --git a/src/test/java/com/cloudhandson/vpdbackoffice/service/EffectiveMatrixServiceTest.java b/vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/service/EffectiveMatrixServiceTest.java similarity index 100% rename from src/test/java/com/cloudhandson/vpdbackoffice/service/EffectiveMatrixServiceTest.java rename to vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/service/EffectiveMatrixServiceTest.java diff --git a/src/test/java/com/cloudhandson/vpdbackoffice/service/ExternalAuthorizationChangeNotifierTest.java b/vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/service/ExternalAuthorizationChangeNotifierTest.java similarity index 100% rename from src/test/java/com/cloudhandson/vpdbackoffice/service/ExternalAuthorizationChangeNotifierTest.java rename to vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/service/ExternalAuthorizationChangeNotifierTest.java diff --git a/src/test/java/com/cloudhandson/vpdbackoffice/service/HmmRuntimeSettingsInitializerTest.java b/vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/service/HmmRuntimeSettingsInitializerTest.java similarity index 100% rename from src/test/java/com/cloudhandson/vpdbackoffice/service/HmmRuntimeSettingsInitializerTest.java rename to vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/service/HmmRuntimeSettingsInitializerTest.java diff --git a/src/test/java/com/cloudhandson/vpdbackoffice/service/JdbcHmmAiAgentToolRunnerTest.java b/vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/service/JdbcHmmAiAgentToolRunnerTest.java similarity index 100% rename from src/test/java/com/cloudhandson/vpdbackoffice/service/JdbcHmmAiAgentToolRunnerTest.java rename to vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/service/JdbcHmmAiAgentToolRunnerTest.java diff --git a/src/test/java/com/cloudhandson/vpdbackoffice/service/MaskingPolicySynchronizerTest.java b/vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/service/MaskingPolicySynchronizerTest.java similarity index 100% rename from src/test/java/com/cloudhandson/vpdbackoffice/service/MaskingPolicySynchronizerTest.java rename to vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/service/MaskingPolicySynchronizerTest.java diff --git a/src/test/java/com/cloudhandson/vpdbackoffice/service/McpSseServiceTest.java b/vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/service/McpSseServiceTest.java similarity index 100% rename from src/test/java/com/cloudhandson/vpdbackoffice/service/McpSseServiceTest.java rename to vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/service/McpSseServiceTest.java diff --git a/src/test/java/com/cloudhandson/vpdbackoffice/service/OrdsMetadataServiceTest.java b/vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/service/OrdsMetadataServiceTest.java similarity index 100% rename from src/test/java/com/cloudhandson/vpdbackoffice/service/OrdsMetadataServiceTest.java rename to vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/service/OrdsMetadataServiceTest.java diff --git a/src/test/java/com/cloudhandson/vpdbackoffice/service/OrdsProbeServiceTest.java b/vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/service/OrdsProbeServiceTest.java similarity index 100% rename from src/test/java/com/cloudhandson/vpdbackoffice/service/OrdsProbeServiceTest.java rename to vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/service/OrdsProbeServiceTest.java diff --git a/src/test/java/com/cloudhandson/vpdbackoffice/service/PermissionServiceTest.java b/vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/service/PermissionServiceTest.java similarity index 100% rename from src/test/java/com/cloudhandson/vpdbackoffice/service/PermissionServiceTest.java rename to vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/service/PermissionServiceTest.java diff --git a/src/test/java/com/cloudhandson/vpdbackoffice/service/ProbeErrorClassifierTest.java b/vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/service/ProbeErrorClassifierTest.java similarity index 100% rename from src/test/java/com/cloudhandson/vpdbackoffice/service/ProbeErrorClassifierTest.java rename to vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/service/ProbeErrorClassifierTest.java diff --git a/src/test/java/com/cloudhandson/vpdbackoffice/service/SchemaMetadataServiceTest.java b/vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/service/SchemaMetadataServiceTest.java similarity index 100% rename from src/test/java/com/cloudhandson/vpdbackoffice/service/SchemaMetadataServiceTest.java rename to vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/service/SchemaMetadataServiceTest.java diff --git a/src/test/java/com/cloudhandson/vpdbackoffice/service/SecuritySqlScriptServiceTest.java b/vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/service/SecuritySqlScriptServiceTest.java similarity index 100% rename from src/test/java/com/cloudhandson/vpdbackoffice/service/SecuritySqlScriptServiceTest.java rename to vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/service/SecuritySqlScriptServiceTest.java diff --git a/src/test/java/com/cloudhandson/vpdbackoffice/service/SelectAiServiceTest.java b/vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/service/SelectAiServiceTest.java similarity index 100% rename from src/test/java/com/cloudhandson/vpdbackoffice/service/SelectAiServiceTest.java rename to vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/service/SelectAiServiceTest.java diff --git a/src/test/java/com/cloudhandson/vpdbackoffice/service/StructuredDataServiceTest.java b/vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/service/StructuredDataServiceTest.java similarity index 100% rename from src/test/java/com/cloudhandson/vpdbackoffice/service/StructuredDataServiceTest.java rename to vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/service/StructuredDataServiceTest.java diff --git a/src/test/java/com/cloudhandson/vpdbackoffice/service/TokenGeneratorTest.java b/vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/service/TokenGeneratorTest.java similarity index 100% rename from src/test/java/com/cloudhandson/vpdbackoffice/service/TokenGeneratorTest.java rename to vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/service/TokenGeneratorTest.java diff --git a/src/test/java/com/cloudhandson/vpdbackoffice/service/TokenHasherTest.java b/vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/service/TokenHasherTest.java similarity index 100% rename from src/test/java/com/cloudhandson/vpdbackoffice/service/TokenHasherTest.java rename to vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/service/TokenHasherTest.java diff --git a/src/test/java/com/cloudhandson/vpdbackoffice/service/VectorChunkerTest.java b/vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/service/VectorChunkerTest.java similarity index 100% rename from src/test/java/com/cloudhandson/vpdbackoffice/service/VectorChunkerTest.java rename to vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/service/VectorChunkerTest.java diff --git a/src/test/java/com/cloudhandson/vpdbackoffice/service/VectorKnowledgeServiceTest.java b/vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/service/VectorKnowledgeServiceTest.java similarity index 100% rename from src/test/java/com/cloudhandson/vpdbackoffice/service/VectorKnowledgeServiceTest.java rename to vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/service/VectorKnowledgeServiceTest.java diff --git a/src/test/java/com/cloudhandson/vpdbackoffice/service/VpdPolicyServiceTest.java b/vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/service/VpdPolicyServiceTest.java similarity index 100% rename from src/test/java/com/cloudhandson/vpdbackoffice/service/VpdPolicyServiceTest.java rename to vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/service/VpdPolicyServiceTest.java diff --git a/src/test/java/com/cloudhandson/vpdbackoffice/web/EffectiveMatrixTemplateRenderTest.java b/vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/web/EffectiveMatrixTemplateRenderTest.java similarity index 100% rename from src/test/java/com/cloudhandson/vpdbackoffice/web/EffectiveMatrixTemplateRenderTest.java rename to vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/web/EffectiveMatrixTemplateRenderTest.java diff --git a/src/test/java/com/cloudhandson/vpdbackoffice/web/GuidedFlowTemplateTest.java b/vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/web/GuidedFlowTemplateTest.java similarity index 96% rename from src/test/java/com/cloudhandson/vpdbackoffice/web/GuidedFlowTemplateTest.java rename to vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/web/GuidedFlowTemplateTest.java index 303be54..df876dd 100644 --- a/src/test/java/com/cloudhandson/vpdbackoffice/web/GuidedFlowTemplateTest.java +++ b/vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/web/GuidedFlowTemplateTest.java @@ -296,7 +296,7 @@ class GuidedFlowTemplateTest { @Test void probeResultSeparatesDbExecutionEvidenceFromReconstructedSql() throws IOException { String result = template("fragments/probe-result.html"); - String vectorSql = Files.readString(Path.of("sql/adb/29_agent_ords_vector_search_ords.sql")); + String vectorSql = Files.readString(Path.of("../database/adb/29_agent_ords_vector_search_ords.sql")); assertThat(result) .contains("DB 감사 실행 증적") @@ -317,7 +317,7 @@ class GuidedFlowTemplateTest { @Test void vectorDefaultRolesAreSeededAsWhitelistAllRules() throws IOException { - String setup = Files.readString(Path.of("sql/adb/32_dds_vector_tag_setup.sql")); + String setup = Files.readString(Path.of("../database/adb/32_dds_vector_tag_setup.sql")); assertThat(setup) .contains("ensure_vector_all_permission(10)") @@ -330,7 +330,7 @@ class GuidedFlowTemplateTest { @Test void defaultVerificationUsersUseHumanNamesInsteadOfRoleLikeAgentNames() throws IOException { - String seed = Files.readString(Path.of("sql/adb/17_agent_ords_security_local_vpd_setup.sql")); + String seed = Files.readString(Path.of("../database/adb/17_agent_ords_security_local_vpd_setup.sql")); String users = template("users.html"); String result = template("fragments/probe-result.html"); @@ -344,7 +344,7 @@ class GuidedFlowTemplateTest { @Test void vectorOrdsHandlerReadsBodyStreamOnlyOnce() throws IOException { - String sql = Files.readString(Path.of("sql/adb/29_agent_ords_vector_search_ords.sql")); + String sql = Files.readString(Path.of("../database/adb/29_agent_ords_vector_search_ords.sql")); assertThat(sql) .contains("v_body_text CLOB") @@ -358,8 +358,8 @@ class GuidedFlowTemplateTest { @Test void existingOrdsSetupCanReturnTheVpdPredicateTrace() throws IOException { - String setup = Files.readString(Path.of("sql/adb/22_agent_ords_security_ords_handler_setup.sql")); - String grant = Files.readString(Path.of("sql/adb/33_agent_ords_sql_trace_grant.sql")); + String setup = Files.readString(Path.of("../database/adb/22_agent_ords_security_ords_handler_setup.sql")); + String grant = Files.readString(Path.of("../database/adb/33_agent_ords_sql_trace_grant.sql")); assertThat(setup) .contains("admin.cb_agent_doc_vpd_filter('ADMIN', 'CB_V_SEARCH_DOCUMENTS')") diff --git a/src/test/java/com/cloudhandson/vpdbackoffice/web/MaskingRuleTemplateRenderTest.java b/vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/web/MaskingRuleTemplateRenderTest.java similarity index 100% rename from src/test/java/com/cloudhandson/vpdbackoffice/web/MaskingRuleTemplateRenderTest.java rename to vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/web/MaskingRuleTemplateRenderTest.java diff --git a/src/test/java/com/cloudhandson/vpdbackoffice/web/PermissionTemplateRenderTest.java b/vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/web/PermissionTemplateRenderTest.java similarity index 100% rename from src/test/java/com/cloudhandson/vpdbackoffice/web/PermissionTemplateRenderTest.java rename to vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/web/PermissionTemplateRenderTest.java diff --git a/src/test/java/com/cloudhandson/vpdbackoffice/web/ProbeResultTemplateRenderTest.java b/vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/web/ProbeResultTemplateRenderTest.java similarity index 100% rename from src/test/java/com/cloudhandson/vpdbackoffice/web/ProbeResultTemplateRenderTest.java rename to vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/web/ProbeResultTemplateRenderTest.java diff --git a/src/test/java/com/cloudhandson/vpdbackoffice/web/SettingsTemplateRenderTest.java b/vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/web/SettingsTemplateRenderTest.java similarity index 100% rename from src/test/java/com/cloudhandson/vpdbackoffice/web/SettingsTemplateRenderTest.java rename to vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/web/SettingsTemplateRenderTest.java diff --git a/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/vpd-backoffice/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker similarity index 100% rename from src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker rename to vpd-backoffice/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker