唯讀盤點。證據路徑相對 monorepo 根
~/Projects/Jedicogy/module/jedi-python-package/或主專案根~/Projects/Billows/Audit-Manager/compliance-manager-be/(標「主專案」)。 DB 事實查自 DEV192.168.50.188:25432 / guidant_ai_dev(唯讀 SELECT)。
Q1 它是什麼
「一個專案裡有哪些任務、任務被誰留言、綁了什麼東西、進度怎麼樣」——專案主檔 CRUD + 通用任務層(留言/任務綁設備組織問卷/匯入匯出/批次完成/執行進度/儀表板聚合),業務插件(稽核、問卷、檢測)插在它上面。
README 與程式碼大致一致,但有兩處不符:
jedi-task-platform/README.md:35 宣稱機械判準是「平台層 grep 不到 poam / audit_round / assessment_plan / ssp」,且說掃全部內容不只 import。實際 grep 到兩處 ssp:jedi_task_platform/infra/model/project.py:85-88(living_ssp_id 欄位+ comment 明寫 oscal.system_security_plans.id)與 jedi_task_platform/domain/entity/project_query_entity.py:18。守衛測試只掃 jedi_task_platform/task/ 子樹(tests/test_task_plugin_contract.py),jedi_task_platform/(project 那半)不在守衛範圍內——所以 README 說的「平台層」其實只指 task/ 子目錄,project 主檔那半帶著 OSCAL 錨點欄位而守衛掃不到。task/ 子樹本身也不是零產品詞:task/app/service/task_execution_service.py 有 17 處 detection(detection_orchestration_service、_auto_dispatch_detection_scans、get_detection_jobs_without_execution、回傳鍵 detection_dispatched_count),守衛的黑名單只含 poam/audit_round/assessment_plan/ssp,detection 不在其中。Q2 資料
自己擁有的表(5 張):
| 表 | 存什麼 | 關鍵欄位 |
|---|---|---|
compliance.projects |
專案主檔(infra/model/project.py:16) |
uid / name / status / module_frame_id / living_ssp_id / owner_id / deleted_at |
compliance.job_execution_comments |
任務留言(task/infra/model/job_execution_comment.py:32) |
job_execution_id(軟參照)/ author_id(FK users)/ author_nickname 快照 / content |
compliance.job_execution_devices |
任務↔︎設備綁定(job_execution_device.py:29) |
job_execution_id(軟參照)/ device_id(FK devices CASCADE) |
compliance.job_execution_org_units |
任務↔︎組織綁定(job_execution_org_unit.py:29) |
job_execution_id(軟參照)/ org_unit_id(FK org_units CASCADE) |
compliance.job_execution_surveys |
任務↔︎問卷綁定(job_execution_survey.py:29) |
job_execution_id(軟參照)/ survey_id(FK survey.surveys CASCADE) |
projects 表有 living_ssp_id(產品欄位):infra/model/project.py:85-89,型別 BigInteger,comment 逐字寫「專案 living SSP(oscal.system_security_plans.id, soft-ref)— 專案唯一 OSCAL 錨點」。DEV 實查 compliance.projects 確有此欄(information_schema.columns 回:module_frame_id / living_ssp_id / owner_id / deleted_at 四欄都在主表)。另外 compliance.project_extensions 表在 DEV 已不存在(to_regclass 回空),四欄已收回主表(CM-1449/CM-1474)。同檔 line 29 還有 Index("ix_projects_living_ssp_id", ...) ——不只是欄位,還為 SSP 反查建了索引。
對別人的表的參照:
(a) 真 FK(DEV pg_constraint 實查確認,非只是 ORM 宣告):
job_execution_comments.author_id → public.users(ON DELETE SET NULL)job_execution_devices.device_id → public.devices(CASCADE)job_execution_org_units.org_unit_id → public.org_units(CASCADE)job_execution_surveys.survey_id → survey.surveys(CASCADE)(b) ORM relationship 跨包:目前 0 條。四張綁定表的 relationship 全被拔(CM-1493 拔 survey/device、CM-1512 拔 users/org_units),檔內留紅字禁止加回(job_execution_comment.py:84-92、job_execution_org_unit.py:55-62)。
(c) 軟參照:四張綁定表的 job_execution_id → compliance.job_executions.id(flow-engine 的表)。DEV 實查四張表確實只有 1 條 FK 各自(devices/org_units/users/surveys 那條),指向 job_executions 的 FK 已不存在——CM-1490 的 migration 確實生效。另 projects.living_ssp_id → oscal.system_security_plans.id 亦為軟參照(DEV 實查 compliance.projects 零 FK 出邊)。
(d) raw SQL 直打別人的表:無(套件內 text( 零處;危險的跨 schema SQL 留在主專案,經 ITaskExecutionQuery port 回問)。
表名產品詞彙:表名本身乾淨(projects / job_execution_*),欄位名不乾淨(living_ssp_id)。
Q3 Port
需要宿主提供(task/domain/ports.py,5 張,全部 ABC):
| Port | 簽名 | 用在哪 |
|---|---|---|
ILicenseGuard (line 21) |
require(resource_type) -> Callable / viewer_licensed_modules() -> Optional[frozenset] |
route decorator、任務型別過濾 |
IProjectRoleGuard (line 40) |
assert_role(participant_domain_service, project_id, user_id, allowed_roles, error_code) / assert_role_fallback(role_service, project_id, allowed_roles, group_id, control_id, error_code) |
task/common/guard.py |
IIdentityGuard (line 62) |
viewer_is_super_admin() -> bool |
超管判定 |
ITaskExecutionQuery (line 70) |
4 支:get_assigned_todo_prep_jobs / activate_jobs / get_detection_jobs_without_execution / get_prep_job_progress |
task/app/service/task_execution_service.py:34 |
ITaskExistenceQuery (line 102) |
get_workflow_execution_id(job_uid) / get_internal_id(job_uid) |
task/app/service/job_comment_service.py:18、task/infra/repository/flow_control_job_comment_repo_impl.py:35 |
提供給別人的入口:jedi_task_platform.task.plugin.{TaskAdapters, TaskServices, attach, register};jedi_task_platform.domain.service.project_domain_service.ProjectDomainService;jedi_task_platform.infra.model.project.Project(ORM,被 compliance-audit 直接 import 6 處);jedi_task_platform.task.domain.task_type_registry.{TaskTypeSpec, register_job_type}(申報制入口,survey 用)。
宣告但零使用的 port:無(五張都有消費端)。
Q4 消費者
(a) 主專案(from jedi_task_platform 共 104 處,含 test)——集中在:app/flow_control/service(15)、infra/flow_control/repository(14)、di_containers/flow_control/flow_control_containers.py(7)、app/project/service(5)、infra|domain|app/associations/*(16)、infra/readmodel/audit(3)、di_containers/project|associations(6)。import 內容以 infra.model.project.Project、domain.service.project_domain_service、task.plugin、task.domain.code.flow_control_job_type_enum 為主。
(b) 其他 jedi 套件:
Project:infra/repository/flow_control_control_group_repo_impl.py:20、flow_control_task_setup_repo_impl.py:17、flow_control_control_repo_impl.py:23、project_extension_repo_impl.py:4、infra/mapper/flow_control_project_mapper.py:2、infra/mapper/project_extension_mapper.py:2;7 處 import service/entity/enum)app/service/task_assignee_service.py:10、app/service/project_participant_service.py:6,皆 ProjectDomainService)app/task_type_declaration.py:35,TaskTypeSpec;且有守衛測試 jedi-survey/tests/unittest/test_plugin_contract.py:119 焊死「核心路徑對 task_platform import 必須 0,唯一白名單是這支」)注意方向:task-platform 反向 import jedi-participant 3 處(見 Q8)。
Q5 執行期形狀
grc_projects / prefix /api/1.0/grc(task/api/__init__.py:39-40, 70-79):/job-executions/<uid>/comments、/job/comment/<uid>、/dashboard/summary、/project/<uid>/ap/<ap_uid>/jobs/{export,import,import/validate,import/confirm}、/project/<uid>/jobs/batch-complete、/project/<uid>/start-task-execution、/project/<uid>/task-execution/progress。threading.Thread / scheduler / APScheduler 零處)。註:README 提到的「每天 03:10 UTC 孤兒清理」實作在主專案(core/scheduler.py:374-431 + app/flow_control/service/job_binding_orphan_cleanup_service.py:64),不在套件內。flask.send_file(task/api/routes/job_import_route.py:9,37)。無 SMTP / HTTP / S3 / subprocess / socket。os.getenv / os.environ 全套件 0 處)。job_executions 表在同一個庫(軟參照跨表讀)。Q6 通用性
(a) 不能直接用。(b) 寫死的 Guidant 產品知識:
infra/model/project.py:85-89 living_ssp_id 欄位 + :29 對它建索引(OSCAL 概念)task/app/service/task_execution_service.py:43,51,102-129 detection_orchestration_service / _auto_dispatch_detection_scans(檢測工具業務)task/api/__init__.py:73-76 URL 路徑硬帶 /ap/<ap_uid>(AP=assessment plan,稽核詞;檔頭 line 16-20 自承「路徑是 D5 凍結的對外契約,平台層當不透明識別子」)task/domain/code/flow_control_job_type_enum.py:41-42 枚舉成員 SURVEY / DETECTION_TOOL 硬編碼(檔頭自承是歷史落庫值,新型別走申報制)GRC_(task/common/error_code.py:41-51,5 個成員,值凍結為 FE i18n key)common/code/status_code.py:5 ProjectStatus docstring 寫「稽核專案執行狀態」FlowControlJobType / FlowControlJobCommentRepoImpl(flow_control = 分家前的稽核套件名)Q7 插件完整度
| 項 | 有/無 | 證據 |
|---|---|---|
| ① api 層隨包 | ✅ 10 條 | task/api/__init__.py:54-79 |
② register(app, adapters, config, schema_extensions, mount_api) + attach(bp, adapters) |
✅ | task/plugin.py;簽名有測試焊死 tests/test_task_plugin_contract.py:45-49 |
| ③ migrations 隨包 | ❌ 0 支 | find 無 migrations/ |
| ④ DI container 預設 | ❌ | 套件內 0 個 container 檔(刻意:存 provider 名冊由宿主注入) |
| ⑤ 獨立 harness | ✅ | harness/task_dev_app.py(README:89-94 宣稱實打 /dashboard/summary 回 401) |
| ⑥ 接入 README | ✅ | README.md:56-94 |
Q8 糾纏對象
與 jedi-participant(最強):
task/infra/repository/flow_control_job_comment_repo_impl.py:18(from jedi_participant.common import user_directory——留言者身分批次換人)、task/app/service/task_execution_service.py:18(ProjectParticipantDomainService)、task/common/guard.py(assert_project_role 委派)。這是「平台包 import 業務/人員包」的方向,與 pyproject.toml:50 宣告的 jedi-participant>=0.0.1 一致,且 README:119 只說「已不再依賴任何業務套件」——participant 被歸類為非業務。ProjectDomainService)。兩包互相 import(雙向循環依賴,pyproject 也是雙向宣告:jedi-task-platform/pyproject.toml:50 依賴 participant,jedi-participant/pyproject.toml:31 依賴 task-platform)。project_id DEV 實查 100% 對得上 compliance.projects.id(project_participants 752 列中 716 列 join 得到 projects,0 列 join 得到 workflow_executions;task_assignees 12479 列全部對得上 projects),亦即「participant 的表在語意上是 projects 的子表,只是沒建 FK」。task_execution_service._check_manager()(task/app/service/task_execution_service.py:53-60)在同一 @transaction 內先查 task-platform 的 Project、再查 participant 的 ProjectParticipant 判角色——每一支寫入端點都走這條。與 jedi-flow-engine(次強,但已用 port 隔開):四張綁定表的 job_execution_id 全指向 flow-engine 的 compliance.job_executions,原本是 CASCADE FK(CM-1490 拆成軟參照)。README:121-126 自承「四張綁定表全部對 flow-engine 的 job_executions 建了 CASCADE 外鍵——依 D17 律①『想建 FK = 該合併疆界』,這是合體的最強理由」。拆 FK 後責任轉給應用層:FlowControlJobRepoImpl.delete_job() 顯式刪四張表 + 主專案的每日孤兒清理排程。程式碼層 import 已歸零(ITaskExistenceQuery port,task/domain/ports.py:102-147)。
與 jedi-compliance-audit:單向、方向正確(CA → TP 13 處,TP → CA 0 處,三處守衛焊死)。但 CA 有 6 處直接 import TP 的 ORM model Project 而非走 service——那是「稽核插件的 repo 直接查平台的表」,project_extension_repo_impl.py:14-43 甚至用 Project 當自己的 BaseRepositoryImpl model 並 UPDATE compliance.projects(add() 與 update_owner(),line 90-125)——稽核插件在寫平台的主檔表。
看起來像但其實不是一件事:
TaskTypeSpec),且 survey 側有守衛測試證明核心路徑零依賴。TP 側 job_execution_surveys 只留 survey_id 軟參照。這條是「任務可以綁問卷」的最鬆耦合形態。devices / org_units / users 有真 FK,但那是「宿主的身分/資產表」不是套件疆界;program 層 import 已零(CM-1512)。Q1 它是什麼
「誰被指派到哪一層」——專案/控制群組/控制項/任務四層的人員指派紀錄與角色(manager/reviewer/auditor/viewer)繼承。
README 與程式碼有一處明顯過時:harness/dev_app.py:11 仍寫「README §5 已知限制①:六支 ORM model 直接 relationship 到 jedi_iam 的…」,但實際六支 model 的 jedi_iam relationship 已全部拔除(CM-1484,見 infra/model/*.py 各檔的紅字註解),套件對 jedi_iam 的 import 實查 0 處。反之 pyproject.toml:26-31 誠實記載仍待償的兩條(flow-engine 的 JobExecution、task-platform 的 ProjectDomainService),與程式碼相符。
Q2 資料
自己擁有的表(6 張,全在 compliance schema,migration jedi_participant/migrations/001-participant-tables.sql):
| 表 | 存什麼 | 關鍵欄位(皆為複合主鍵) |
|---|---|---|
project_participants |
專案層參與者 | (project_id, user_id) + role |
process_participants |
流程層參與者 | (project_id, process_id, user_id) + role |
project_group_participants |
專案×群組 | (project_id, group_id, user_id) + role |
project_control_participants |
專案×群組×控制項 | (project_id, group_id, control_id, user_id) + role |
control_group_participants |
控制項群組 | (project_id, group_id, user_id) + role |
task_assignees |
任務指派 | (project_id, control_id, task_id, user_id) + task_uid / task_template_id / is_approver / role |
對別人的表的參照:
(a) 真 FK:六張表全部 0 條。DEV pg_constraint 實查逐表確認 fk_count = 0(六張全 0)。migration 檔頭 line 16-19 亦明記「本套件的表沒有 RLS、也沒有 tenant_id / org_unit_id」。
→ 回答派工單的問題:participant 六張表「全部沒有」FK 掛 task-platform 的表(不是「全部有」)。它們的 project_id 在資料上確實指 compliance.projects.id(見下),但約束層是空的。
(b) ORM relationship 跨包:1 條——infra/model/project_participant.py:6,23-30:
from jedi_flow_engine.infra.models.workflow_execution import WorkflowExecution
project = relationship("WorkflowExecution", primaryjoin=WorkflowExecution.id == ProjectParticipant.project_id, viewonly=True)
這條 relationship 把 project_id 對到 flow-engine 的 workflow_executions.id。DEV 實查證明這條是壞的:752 列 project_participants 中,join compliance.projects 得 716 列、join compliance.workflow_executions 得 0 列(projects.id 值域 40–306,workflow_executions.id 值域 1558–13258,兩者完全不重疊)。全 codebase(套件 + 主專案)grep .project.uid / participant 的 .project 讀取端 0 處——這是一條沒有讀者的死 relationship,且語意上指錯表。
(c) 軟參照:project_id → compliance.projects.id(真實對應,見上)、user_id → public.users.id、group_id / control_id → 稽核插件的控制項概念(無實體表對應)、task_assignees.task_id → compliance.job_executions.id(DEV 實查 12479 列中 12470 列 join 得到)。
(d) raw SQL:套件內 0 處。
表名產品詞彙:control_group_participants / project_control_participants 的 control 是稽核控制項詞彙(NIST/CMMC control),task_assignees.task_template_id 亦然。程式層另有兩處洩漏:app/service/project_participant_service.py:35,44,126(project_assessment_plan_mapping_service 建構子參數+ _append_user_to_ssp_metadata_parties 方法,該方法 line 131-132 是 FR-038 2A 留下的 return 空殼 stub)、common/participant_enum.py:7 註解提及 common/authz/ssp.py。
Q3 Port
domain/ports.py 宣告 6 張(全部 Protocol + runtime_checkable):
| Port | 簽名 | 用在哪 | 缺線行為 |
|---|---|---|---|
IUserDirectory (line 33) |
get_user(uid) / get_users_by_ids(ids) / get_org_units_by_ids(ids) / get_users_by_login_names(names) |
common/user_directory.py 模組級 configure;六支 service 建構子 |
拒絕掛載(plugin _assert_wiring) |
IProjectDirectory (line 84) |
get_by_uid(project_uid) -> Any |
零使用 | 文件宣稱「拒絕掛載」 |
IProjectRoleGuard (line 91) |
assert_manager(role_service, project_id, group_id, control_id) |
common/guard.py:27 |
拒絕掛載 |
IAuditLogger (line 112) |
emit(*, event_code, message, **extra) |
未以此名注入(實際走 ParticipantConfig.audit_event_codes dict) |
降級不記錄 + WARNING |
IJobNotifier (line 119) |
notify_user_todo_job(job_execution_id, assignees) |
app/service/task_assignee_service.py:34(參數名叫 workflow_execution_service) |
降級不通知 |
IJobEnrichment (line 126) |
count_attachment_evidence_by_job_ids / get_completed_survey_job_ids |
task_assignee_service.py:40-41(參數名 job_evidence_domain_service / task_survey_domain_service) |
降級回 0/False |
宣告了但套件內零使用的 port:IProjectDirectory(樣板殘留)。全 monorepo + 主專案 grep IProjectDirectory 只有 domain/ports.py:21(文件表格)與 :84(宣告)兩處,plugin.py 的 _assert_wiring(line 176-186)只檢查 auth_required / project_role_guard / user_directory 三項,不含 IProjectDirectory;ParticipantAdapters(line 118-122)根本沒有 project_directory 欄位。文件表格說「不注入拒絕掛載」是不成立的。
→ 回答派工單的問題:participant → task-platform 的兩處 import 是 app/service/task_assignee_service.py:10 與 app/service/project_participant_service.py:6,兩處都 import ProjectDomainService(具體類別,非 port)。兩處都繞過了已定義的 IProjectDirectory port——port 定義得好好的(get_by_uid),而 project_participant_service.py:188 的 self.project_domain_service.get_by_uid(project_uid) 正好就是那張 port 的簽名,卻直接 import 具體套件的 domain service。task_assignee_service.py:36 的 project_domain_service: ProjectDomainService = None 更只是型別註記(DI 注入、預設 None),拔 import 改鴨子型別即可——與 task-platform 對 jedi_iam.UserDomainService 的處置(CM-1512)同形,但這裡沒做。
另一個宿主接線缺口(DEV 實況):主專案 api/participant/__init__.py:75-90 的 _UserDirectoryAdapter 只轉發 2 支方法(get_user / get_users_by_ids),而底層 infra/participant/user_directory_adapter.py:43-93 有 4 支(另有 get_org_units_by_ids / get_users_by_login_names,CM-1512 加的)。套件側 common/user_directory.py:80-87,107-114 對「adapter 未實作該方法」的處置是 getattr(...) is None → 回空 dict + WARNING。消費端 jedi-survey infra/repository/identity_enrichment.py:62,129 正好呼叫這兩支——問卷的部門名與審計 nickname 走的是降級路徑(唯一接線點只有 api/participant/__init__.py:116 一處,無其他 configure)。未在 log 中驗證是否真的觸發(見「未查證事項」)。
提供給別人的入口:jedi_participant.plugin.{register, create_blueprint, ParticipantAdapters, ParticipantConfig, ParticipantServices}、jedi_participant.common.guard.assert_project_manager(canonical,survey 與 CA 都在用)、jedi_participant.common.user_directory(模組級名冊,canonical,TP/survey/CA 都在用)、jedi_participant.common.participant_enum.ParticipantRole、domain.entity.task_assignee_query_entity.TaskAssigneeQueryEntity、domain.service.*_domain_service、app.dto.*、api.serializers.*。
Q4 消費者
(a) 主專案(98 處):di_containers/flow_engine/project_participant_containers.py(18)、infra/flow_control/repository(9)、app/flow_control/service(8)、app/flow_engine/service|dto(12)、infra/readmodel/audit(5)、infra/flow_engine/{repository,models,mapper}(10)、app/associations/service(4)、api/flow_engine/serializers(3)、api/participant/__init__.py(1,插件接線)等。
(b) 其他套件:jedi-compliance-audit → 8 處(app/dto/{control_group,control,project}_dto.py 借 ParticipantMenuDTO / ProjectParticipantDTO、api/serializers/{task_setup,project}.py 借 serializer、app/service/review_service.py:11 借 ParticipantRole、planning_readiness_checker.py:29 借 query entity、infra/repository/flow_control_task_setup_repo_impl.py:130 借 user_directory);jedi-survey → 8 處;jedi-task-platform → 3 處(見上)。
方向注意:participant 被三支套件依賴,自己又依賴 flow-engine + task-platform,與 task-platform 形成雙向循環。
Q5 執行期形狀
api/__init__.py:80-116,另有 9 條註解掉的死路徑,FR-038 DEAD-V1),blueprint 名 participant / prefix /api/1.0:/project-participants、/project-participants/menu、/project-participant、/task-assignees、/task-assignees/batch、/task-assignee、/project-control-participants、/project-control-participants/menu、/project-control-participant、/control-group-participant。IJobNotifier port 交給宿主)。os.getenv 0 處。JobExecutionService 與 task-platform 的 ProjectDomainService(兩者都是具體類別 import,不是 port),所以目前不行。Q6 通用性
(a) 不能直接用(要先裝 flow-engine + task-platform)。(b) 寫死的產品知識:
common/participant_enum.py:13-18 ParticipantRole 四角(manager/reviewer/auditor/viewer)——auditor 是稽核詞,且 docstring 寫「系統送『待稽核判定』給他(Phase 3)」control_group_participants / project_control_participants(control = 合規控制項)app/service/project_participant_service.py:125-132 _append_user_to_ssp_metadata_parties()(OSCAL SSP parties 概念;FR-038 2A 後成 return 空殼,但方法與呼叫點 line 116-121 都還在,外包了一層 savepoint() + try/except 保護一個什麼都不做的函式)common/error_code.py 八個碼中四個帶 GRC_ 前綴(值凍結為 FE i18n key)common/event_code.py 申報 TASK_ASSIGNED / TASK_REASSIGNED / TASK_UNASSIGNED 三個事件名,數值由宿主給(api/participant/__init__.py:99-107)——這條設計是乾淨的Q7 插件完整度
| 項 | 有/無 | 證據 |
|---|---|---|
| ① api 層隨包 | ✅ 10 條 live | api/__init__.py:39-116 |
② register(app, adapters, config, schema_extensions, mount_api) + create_blueprint() |
✅ | plugin.py:238-261 / 202-235 |
| ③ migrations 隨包 | ✅ 1 支(六張表建表,全冪等) | jedi_participant/migrations/001-participant-tables.sql;plugin.iter_migrations() (line 44) 只提供不執行 |
| ④ DI container 預設 | ❌ 0 個 | 走 provider 名冊 |
| ⑤ 獨立 harness | ✅ | harness/dev_app.py(但檔頭 line 11 描述已過時) |
| ⑥ 接入 README | ✅ | README.md(177 行) |
Q8 糾纏對象
與 jedi-task-platform(最強):
ProjectDomainService,繞過自家 IProjectDirectory port);TP → participant 3 處(user_directory、ProjectParticipantDomainService、guard 委派)。pyproject 亦雙向宣告依賴。project_id 100% 指向 compliance.projects.id(task_assignees 12479/12479),只是約束層留白(0 FK)。task_assignee_service.add_task_assignee()(app/service/task_assignee_service.py:145-178)一個 @transaction 內:查 flow-engine 的 job_execution → 寫 participant 的 task_assignees → 呼叫 notify_user_todo_job;而 TP 的 task_execution_service._check_manager() 一個 @transaction 內先查 TP Project 再查 participant ProjectParticipant。兩邊互相在對方的交易裡出現。common/guard.py 的 assert_project_manager 被 survey(app/common/task_survey_guard.py:24)與 CA(間接)使用,而它的實作由宿主 common/authz/project.py 注入——三包共用一支宿主守門。與 jedi-flow-engine:1 條 ORM relationship(死的、指錯表,見 Q2b)+ task_assignee_service.py:8-9,88,110,154,212,242 六處呼叫 JobExecutionService.get_job_execution()(每次派工/查詢都要先問「這個 task_uid 是哪個 job」)。資料上 task_assignees.task_id 對得上 job_executions.id(12470/12479)但無 FK。
看起來像但其實不是一件事:
TaskAssigneeQueryEntity + guard 做守門判定(8 處),是「問卷要知道誰被指派」的單向讀取;participant 對 survey 的認識只有 IJobEnrichment port 的一支方法(get_completed_survey_job_ids,未注入即降級)。IUserDirectory。是本批中疆界隔離做得最徹底的一條。決策者已裁:flow-engine 不併入 task-platform。以下只擺事實。
Q1 它是什麼
BPMN 2.0 流程引擎——解析 BPMN XML、建流程執行實例(含主/子流程樹)、推進流程節點、記錄任務執行與元素變數。
README 與程式碼一致且異常誠實:README.md:9-22 開宗明義「本套件目前沒有 api 層,register() 會拿到一個空的 blueprint,這是刻意的」;:173-185 的「驗收邊界(誠實聲明)」表格逐項標 ✅/❌,明寫「拔掉測試 ❌ 做不到——沒有 route 就沒有可驗,不用假端點充數」。實查全部屬實。唯一小落差:README:142 說 models 有「9 張表」、:117 說「本套件 5 張表全在 compliance schema」——現況是 5 張(infra/models/__init__.py:19-31 列 5 個,另四張 hi_* 歷程表已於 CM-1499 退役,README.md:120-122 有記載但 142 行的「9 張」沒改)。
Q2 資料
自己擁有的表(5 張,全在 compliance):
| 表 | 存什麼 | 關鍵欄位 |
|---|---|---|
workflow_templates |
BPMN 範本快照(版本/父子樹/JSON)(infra/models/workflow_template.py:12) |
parent_template_id(自 FK CASCADE)/ template_id / tenant_id / org_unit_id |
workflow_templates_trans |
範本翻譯(workflow_template_trans.py:11) |
workflow_template_id(FK CASCADE)/ locale |
workflow_executions |
流程執行實例,支援主/子流程巢狀(workflow_execution.py:19) |
main_workflow_execution_id / parent_id(皆自 FK)/ template_id(FK)/ status |
job_executions |
工作節點執行紀錄(job_execution.py:16) |
main_workflow_execution_id / workflow_execution_id(皆 FK)/ template_id(FK)/ type / status |
element_variables |
流程/工作的變數值(element_variable.py:12) |
workflow_id(FK CASCADE)/ element_id |
對別人的表的參照:
(a) 真 FK 出邊(跨疆界):DEV 實查 workflow_executions / job_executions / workflow_templates 三張各有 tenant_id → tenants 與 org_unit_id → org_units 兩條 FK(來自 TenantScopedMixinModel,屬宿主身分/租戶表)。其餘 FK 全在套件內部。
(b) ORM relationship 跨包:0 條。
(c) 軟參照:無(套件不存別人的 id)。
(d) raw SQL 直打別人的表:無。
別人指向它的 FK(DEV 實查,這是耦合的核心事實):
compliance.job_evidences.{job_execution_id, workflow_execution_id, main_workflow_execution_id} → job_executions / workflow_executions(主專案的表)compliance.job_execution_device_mapping.{job_execution_id, workflow_execution_id}、compliance.project_job_execution_device_mapping.job_execution_id(主專案 associations)public.workflow_execution_control_mapping.workflow_execution_id(主專案)表名產品詞彙:乾淨(workflow_/job_/element_,全通用流程詞)。全套件產品詞 grep 只中 1 處,且是英文技術用語誤中(common/utils/bpmn_topology_validator.py:39 註解 Reverse detection)。
Q3 Port
plugin.py 宣告 2 張 Protocol:ISettingsReader.get(key, default)(line 114-121)、INotifier.send(*, to, subject, body)(line 124-133)。兩張皆零使用,且 docstring 自己明說「本套件目前零使用」「插槽開齊是為了日後簽名不用改,不是現在有東西被 port 化了」(README.md:87-104 同)。這是誠實標註的樣板殘留,不是隱藏債。
提供給別人的入口:jedi_flow_engine.infra.models.{JobExecution, WorkflowExecution, WorkflowTemplate, ElementVariable}(ORM,被跨包直接 import)、common.enum.job_code.{JobType, JobStatus} / workflow_code.WorkflowStatus、common.utils.bpmn_uilts.BpmnUtils(BPMN 解析核心)、common.utils.bpmn_topology_validator、domain.entity.*QueryEntity、domain.service.*DomainService、app.service.{workflow_template_service, workflow_execution_service, job_execution_service}、app.dto.*、plugin.{register, create_blueprint}。
Q4 消費者(誰呼叫它)
(a) 主專案(122 處,本批最高):app/flow_engine/service(30,含 workflow_execution_service.py 一支就 17 條 import)、infra/flow_control/repository(16)、di_containers/flow_engine/workflow_excution_containers.py(11)、app/module_frame/service(9)、app/flow_control/service(6)、app/cloud_integration/service(6)、api/flow_engine/routes(4)、infra/flow_engine/models(4)、infra/readmodel/tasks(3)、app/oscal/service(2)等。
⚠️ jedi_flow_engine.plugin 在主專案 0 處被呼叫(grep 全 codebase 無 jedi_flow_engine.plugin / from jedi_flow_engine import plugin)——主專案是把它當純 library 用,register() 從未被執行。連帶事實:plugin.py:84-97 的 set_repo_providers(...) 是 import 期副作用,而 app/service/workflow_execution_service.py:25 的 get_repos() 依賴它;主專案既然從不 import plugin,就從不會呼叫套件自己的 WorkflowExecutionService——實查確認主專案 0 處 import jedi_flow_engine.app.service.workflow_execution_service(用的是主專案自己那支 6,375 行的同名 service)。套件的 WorkflowExecutionService 在本產品是死碼路徑。
(b) 其他套件:
infra/repository/{flow_control_control_repo_impl.py:21-22, flow_control_control_group_repo_impl.py:17-18, flow_control_task_setup_repo_impl.py:14-16, oscal_audit_query.py:12} import ORM JobExecution / WorkflowTemplate + JobType/JobStatus enum;app/service/planning_readiness_checker.py:24-25 用 BpmnUtils + query entityapp/service/detection_result_handler.py:30-31、detection_orchestration_service.py:34-36(JobStatus / ErrorCode / JobExecutionQueryEntity)infra/model/project_participant.py:6(死 relationship)、app/service/task_assignee_service.py:8-9infra/models/task_survey.py:2-3(ORM relationship,見 survey 段)ITaskExistenceQuery port 償清)→ 回答派工單的問題「誰呼叫它」:主專案 app/flow_engine/ 與 app/flow_control/ 是最大宗(宿主自己的 flow_control 那 6,375 行);task-platform 不再呼叫(已 port 化);survey 只有 ORM relationship 不呼叫 service;另外 jedi-detection 也是消費者(5 處),這支不在本批但屬同一張依賴圖。
workflow_executions 表的 ORM model 在套件與主專案各有一個嗎:是,但不是兩個 class 定義同一張表——是繼承 + extend_existing。
jedi_flow_engine/infra/models/workflow_execution.py:18-20 class WorkflowExecution(BaseModel, TenantScopedMixinModel),__tablename__ = "workflow_executions", schema complianceinfra/flow_engine/models/ext_workflow_execution.py:18-20 class ExtWorkflowExecution(BaseWorkflowExecution)(繼承套件那支),__tablename__ = "workflow_executions", __table_args__ = {"extend_existing": True, "schema": "compliance"}main_workflow_execution / parent / sub_workflows / variables,line 22-61,覆寫 lazy 策略),新增三個跨包 relationship 到 participant 的表(line 125-163:project_participants / process_participants / task_assignees,全 viewonly=True + foreign() 手動 primaryjoin)+ 兩個 hybrid_property 聚合(sub_workflows_count / sub_workflows_completion_percentage,line 64-123)。Q5 執行期形狀
plugin.create_blueprint() line 207-229 建的 blueprint 沒有任何 add_resource,且有測試焊死 tests/unittest/test_plugin_contract.py)。→ 派工單問「附錄說 0 條」——確認為真。common/utils/bpmn_generator.py 用 defusedxml.ElementTree.parse,pyproject.toml:30 有宣告)與 xmltodict。套件內另有三份 .bpmn 範例檔(common/utils/{camunda8測試,網路設備風險評估,資安健診}.bpmn)——產品用的 BPMN 範本檔案跟著套件走。os.getenv / os.environ 0 處(README:96 宣稱,實查屬實)。register() 從未被呼叫、真正的流程推進邏輯(6,375 行)在主專案。它現況是純 library。Q6 通用性
(a) 接近可以直接用——本批中通用性最高的一支。(b) 硬編碼的 Guidant 產品知識:幾乎沒有。
README.md:108-118)是技術性的:ENABLE_MULTI_TENANT=true 必須在 import model 前設好;tenants / org_units 兩張表要在 SQLAlchemy metadata 裡(TenantScopedMixinModel 的 FK 在 mapper 設定期就要解析)——這是 jedi-common 的租戶 mixin 帶來的,不是 flow-engine 自己的產品知識common/enum/error_code.py 9 個成員,前綴非 GRC_.bpmn 範例檔帶產品情境(資安健診/網路設備風險評估),但只是檔案不是邏輯Q7 插件完整度
| 項 | 有/無 | 證據 |
|---|---|---|
| ① api 層隨包 | ❌ 0 條(刻意,有測試焊死) | plugin.py:37-43、README.md:9-22, 183 |
② register(app, adapters, config, schema_extensions, mount_api) |
✅ | plugin.py:232-261 |
| ③ migrations 隨包 | ❌ 0 支(README:163-169 明說「本套件不隨包 migration,九張表的建表腳本在主專案 scripts/sql/」) |
— |
| ④ DI container 預設 | ❌ 0 個(有 app/wiring/ 的 provider 注入縫隙,由 plugin.py import 期接線) |
app/wiring/__init__.py:43-60 |
| ⑤ 獨立 harness | ✅ 本批唯一有 docker-compose 的 | harness/dev_app.py + harness/docker-compose.yml(port 5498) |
| ⑥ 接入 README | ✅ | README.md(185 行) |
Q8 糾纏對象
與 jedi-task-platform:曾經是四條 CASCADE FK(TP 的四張綁定表 → job_executions),CM-1490 拆成軟參照、CM-1492 拆掉最後兩條 import。現況:程式碼 0 依賴、DB 0 FK,只剩「四張表的 job_execution_id 欄位在語意上指這裡」+ 主專案要跑每日孤兒清理排程來補 DB 不再保證的完整性。TP 的 README:121-126 仍留著「這是合體的最強理由」那段(決策者已裁不併)。
與主專案 app/flow_engine/ + app/flow_control/(最強,但對象是宿主不是套件):19 條 route 中只有 1 條吃套件的 service(api/flow_engine/__init__.py:41 /flow-engine/process-definition/<uid>),其餘 18 條吃主專案 6,375 行的 app/flow_engine/。主專案的 ExtWorkflowExecution 用繼承 + extend_existing 覆寫並擴充套件的 ORM model。「引擎在套件、驅動引擎的業務在宿主」是這支最大的分裂。
與 jedi-compliance-audit:CA 的 4 支 repo 直接 session.query(JobExecution)(flow_control_control_repo_impl.py:22 等)與 raw SQL JOIN compliance.job_executions(flow_control_task_setup_repo_impl.py 實查)——稽核插件直接查流程引擎的表,沒有 port。
看起來像但其實不是一件事:
infra/models/task_survey.py:2-3 兩條 ORM relationship(main_project / process → WorkflowExecution、task → JobExecution,全 viewonly),無 service 呼叫、無 FK(DEV 實查 survey.task_surveys 只有 pkey + uid unique,零 FK)。ProjectParticipant.project → WorkflowExecution relationship 是死的且指錯表(見 participant Q2b),實際耦合只有 JobExecutionService.get_job_execution() 六處呼叫。Q1 它是什麼
「設計一份問卷、發給人填、記錄他填了什麼與改過什麼」——資料夾/問卷/頁/題/顯示條件/版本快照(設計層)+ 任務問卷/答案/答案歷史與還原/作答狀態機/多人即時共編(作答層)。
README 與程式碼一致,且 README.md:136-172 的「誠實聲明(待償項)」逐條屬實:7.1 說「只能裝在有 jedi-iam / jedi-participant / jedi-flow-engine / jedi-device 的宿主上」——實查四支 import 都在;7.2 說「沒有 harness」——實查 jedi-survey/harness/ 目錄不存在。
Q2 資料
自己擁有的表(14 張,全在 survey schema):
| 表 | 存什麼 | 關鍵欄位 |
|---|---|---|
surveys |
問卷主檔(infra/models/survey.py:18) |
folder_id(FK CASCADE)/ version_no / 發布狀態 / tenant_id |
surveys_trans |
問卷翻譯 | survey_id(FK CASCADE) |
survey_folders |
資料夾樹(survey_folder.py:18) |
parent_id(自 FK) |
survey_folders_trans |
資料夾翻譯 | survey_folder_id(FK CASCADE) |
survey_pages |
問卷頁(樹狀)(survey_page.py:15) |
survey_id(FK CASCADE)/ parent_id(自 FK) |
survey_pages_trans |
頁翻譯 | survey_page_id(FK CASCADE) |
survey_questions |
題目(樹狀)(survey_question.py:18) |
page_id(FK CASCADE)/ parent_id(自 FK)/ 顯示條件 |
survey_questions_trans |
題翻譯 | survey_question_id(FK CASCADE) |
survey_discussions |
問卷討論(survey_discussion.py:15) |
survey_id(FK CASCADE) |
question_answers |
答案(question_answer.py:16) |
question_id(FK → survey_questions)/ task_survey_id / answer |
question_answer_histories |
作答歷史(question_answer_history.py:15) |
使用者 login_name(審計)/ 時間 |
question_answer_history_details |
歷史明細(question_answer_history_detail.py:18) |
history_id(FK)/ question_id(FK → survey_questions) |
task_surveys |
任務↔︎問卷(作答實例)(task_survey.py:19) |
main_project_id / process_id / task_id / survey_id / device_id / department_id / status / job_evidence_id / inherited_from_round_id |
task_survey_ref_items |
任務問卷參考項目(task_survey_ref_item.py:10) |
— |
對別人的表的參照:
(a) 真 FK:0 條跨疆界。DEV 實查 survey.task_surveys 的 constraint 只有 task_surveys_pkey + task_surveys_uid_key,零 FK。question_answers / question_answer_history_details 的 FK 全指向 survey.survey_questions(自家表,正是 README:15-24 說的「兩條真實外鍵橫跨原本的套件/主專案邊界,所以合併不抽兩包」)。
(b) ORM relationship 跨包:4 條,全在 infra/models/task_survey.py:
main_project → jedi_flow_engine.WorkflowExecution(primaryjoin WorkflowExecution.id == TaskSurvey.main_project_id,viewonly)+ main_project_uid association_proxy (line 114)process → WorkflowExecution(TaskSurvey.process_id)+ process_uid (line 124)task → jedi_flow_engine.JobExecution(TaskSurvey.task_id)+ task_uid (line 134)device → jedi_device.Device(TaskSurvey.device_id)+ device_uid / device_name (line 144-145)task_survey.py:1-3→ 回答派工單的問題「task_surveys 對 flow-engine 與 device 的 ORM relationship 在哪」:全部集中在 jedi_survey/infra/models/task_survey.py:106-145(四條 relationship + 六個 association_proxy)。沒有任何 FK 撐它們(DEV 實查),純靠 SQLAlchemy 的 primaryjoin + viewonly=True + foreign() 縫起來。相對地,同檔 line 147-152 的部門那條已於 CM-1512 拔除改走名冊 port,紅字禁止加回——同一個檔裡,身分那條償了、業務那三條沒償。
(c) 軟參照:task_surveys.job_evidence_id → compliance.job_evidences.id(task_survey.py:83-87 comment 明寫 soft ref);task_surveys.inherited_from_round_id → compliance.project_audit_rounds.id(task_survey.py:89-93,comment 逐字:「承襲來源輪次ID(soft ref → compliance.project_audit_rounds.id,無 FK);覆核輪自母輪 clone 的問卷帶母輪 id,本輪原生建立為 NULL(FR-053.1)」);department_id → public.org_units.id。
→ 回答派工單的問題「inherited_from_round_id 欄位」:這是問卷疆界裡的稽核輪次概念——欄位存在 survey schema 的表上,指向 compliance-audit 擁有的 project_audit_rounds。套件內只有讀取端(infra/mapper/task_survey_mapper.py:46 讀出、domain/entities/task_survey_entity.py:36,57 放進 entity),沒有任何寫入端。真正的寫入者在主專案:infra/flow_control/repository/reverify_clone_query.py:128-160(clone_task_surveys() 用 raw SQL INSERT INTO survey.task_surveys (... inherited_from_round_id ...) 帶母輪 id)——主專案的稽核覆核流程用 raw SQL 直寫問卷套件的表。
(d) raw SQL 直打別人的表:套件內無(反向:主專案用 raw SQL 直寫 survey 的表,見上)。
表名產品詞彙:表名乾淨(survey_* / question_* / task_survey*)。欄位名不乾淨:inherited_from_round_id(round = 稽核輪次)、job_evidence_id(稽核證據)。
Q3 Port
domain/ports.py 宣告 3 張 ABC + 1 個型別別名:
| Port | 簽名 | 缺線行為 |
|---|---|---|
INotifier (line 34) |
get_channel_config(channel) / send_mail_notification(*a,**k) / send_discord_notification / send_telegram_notification |
降級不發通知 |
IPresenceStore (line 50) |
members(room_key) / join(room_key, user) / leave(room_key, user) |
降級:共編在線名單為空 |
IAuditNicknameEnricher (line 63) |
enrich_objs(items) -> list |
降級:少 *_name 欄位 |
ConfigReader (line 89,型別別名 Callable[[str,str],Any]) |
(group, key) -> 值或 None |
降級回內建預設 |
刻意不自建的 port:專案角色守門直接復用 jedi_participant.common.guard(domain/ports.py:13-18 明說「再開一張同義的 port 等於同一件事兩套真相」);license 走自家 api/guard.py:32-56 的 configure() / require_license(缺接線在掛載當下拋 RuntimeError)。
宣告了但零使用的 port:無,但 IConfigReader 有歷史錯誤已修正並記錄(domain/ports.py:72-93:原本是 ABC 宣告 .get(group,key),唯一消費端卻當裸 callable 呼叫,宿主又從未接線 → 三層各說各話,2026-09-01 arc-review I-8 收斂成 Callable 別名)。這是本批中對「port 樣板殘留」處理得最誠實的一處。
提供給別人的入口:jedi_survey.plugin.{register, create_blueprints, SurveyAdapters, SurveyConfig, SurveyServices}、app.task_type_declaration.survey_task_type_spec()(申報書)。其他套件 import survey:0 處(monorepo 全掃)。
Q4 消費者
(a) 主專案(65 處):di_containers/survey/survey_containers.py(18)、di_containers/task_survey/question_answer_containers.py(15)、di_containers/task_survey/task_survey_containers.py(6)、infra/flow_control/repository(4)、app/flow_engine/service(4)、app/flow_control/service(4)、infra/readmodel/tasks(2)、core/app_factory.py:473(socketio 模式的 library 註冊)、api/{survey,task_survey,flow_control}/__init__.py(3)。另有 52 支 re-export shim(README:161-172)讓既有 import 零改動。
(b) 其他 jedi 套件:0 處。survey 是本批中唯一「沒有任何套件依賴它」的一支。
Q5 執行期形狀
api/__init__.py:65-93 設計層 20 條、:121-135 作答層 12 條),兩個 blueprint(survey prefix /api/1.0、task-survey;README.md:95-97 說明分兩個是刻意的,合併會讓 endpoint 改名而 url_for 靜默匹配 0 條)。app/service/task_survey_service.py:526,537,543 三處 threading.Thread(...).start() 發通知(mail / telegram / discord),fire-and-forget 無 join、無錯誤回收。app/handler/fill_survey_socketio_handler.py(共編),繼承 jedi_iam.middleware.socketio_auth.AuthenticatedNamespace(line 8);api/routes/question_answer_history_route.py:60 直接讀 flask.current_app.extensions['socketio'] 發 emit。os.getenv("SYSTEM_NAME") / os.getenv("SYSTEM_URL")(task_survey_service.py:468-469,本批唯一直接讀 env 的套件);Excel 讀寫(api/routes/task_survey_route.py:11-12,21,92-107 openpyxl + pandas ExcelWriter、survey_route.py:24 pandas);send_file 下載;通知走 port 由宿主寄。IPresenceStore port(宿主 infra/survey/adapters.py 的 RedisPresenceStoreAdapter)。AuthenticatedNamespace。Q6 通用性
(a) 不能直接用(要先裝 iam / participant / flow-engine / device 四支)。(b) 寫死的產品知識:
infra/models/task_survey.py:89-93 inherited_from_round_id(稽核輪次)infra/models/task_survey.py:83-87 job_evidence_id(稽核證據)app/task_type_declaration.py:43 SURVEY_JOB_TYPE = "survey" 落庫值、:46 SURVEY_REQUIRED_MODULES = ("survey",)(Guidant 授權模組粒度)common/enum/error_code.py:34 個碼,其中 HostAuthzErrorCode 是宿主碼表的逐字複本(task_survey_guard.py:25 在用)SurveyConfig(audit_logger_name="app.task_survey") 必須傳宿主的 logger 名,不傳事件從 public.system_logs 整批消失且無錯誤(README.md:126-132)common/utils/common_util.py 等處的作答狀態機五態(未填寫/編輯中/待審核/補充說明/完成)是產品定義Q7 插件完整度
| 項 | 有/無 | 證據 |
|---|---|---|
| ① api 層隨包 | ✅ 32 條 / 2 blueprint | api/__init__.py |
② register(app, adapters, config, schema_extensions, mount_api, declare_task_type) |
✅(六參數,比別支多 declare_task_type) |
plugin.py:312;create_blueprints() line 273 |
| ③ migrations 隨包 | ❌ 0 支 | — |
| ④ DI container 預設 | ❌ 0 個 | — |
| ⑤ 獨立 harness | ❌ 無(harness/ 目錄不存在,README:147-150 誠實聲明「跨四套件相依,架 harness 需要先斷 relationship,不假裝有」) |
— |
| ⑥ 接入 README | ✅ | README.md(220 行) |
Q8 糾纏對象
與 jedi-participant(7-8 處 import,全部是「借判定資料」)——派工單問「survey 對 participant 的 7 處 import 是拿什麼」,實查 7 條真 import 語句(另 3 處是 docstring 提及):
| # | 位置 | 拿什麼 | 用途 |
|---|---|---|---|
| 1 | plugin.py:241 |
common.guard as participant_guard |
register() 把宿主的 IProjectRoleGuard 轉接進 participant 的模組級 guard |
| 2 | infra/repository/identity_enrichment.py:44 |
common.user_directory |
批次填部門名(get_org_units_by_ids, line 62)+ 審計 nickname(get_users_by_login_names, line 129) |
| 3 | app/common/task_survey_guard.py:24 |
common.guard.assert_project_manager |
manager 代填 override |
| 4 | app/common/task_survey_guard.py:27 |
common.participant_enum.ParticipantRole |
判 role == MANAGER |
| 5 | app/common/task_survey_guard.py:28 |
domain.entity.task_assignee_query_entity.TaskAssigneeQueryEntity |
用 (task_id, user_id) 查「這人是不是這個 task 的 assignee」 |
| 6 | app/service/task_survey_service.py:15 |
同上 TaskAssigneeQueryEntity |
通知對象反查 |
| 7 | app/service/task_survey_service.py:16 |
domain.service.task_assignee_domain_service.TaskAssigneeDomainService |
同上(DI 注入的實體型別) |
歸納:全部是「問 participant:這個 task 派給誰了 / 這人是不是 manager」——填答守門與通知對象兩件事。無寫入、無 ORM relationship、無 FK。這是單向讀取式耦合,方向 survey → participant。
與 jedi-flow-engine / jedi-device:4 條 ORM relationship(Q2b),全 viewonly、無 FK、無 service 呼叫。只為了填 main_project_uid / process_uid / task_uid / device_uid / device_name 五個顯示欄位(association_proxy),與 CM-1512 已償還的部門那條性質完全相同——同檔內一條償了另三條沒償。
與 jedi-compliance-audit(間接,透過主專案 raw SQL):task_surveys.inherited_from_round_id 指向 CA 的 project_audit_rounds;寫入者是主專案 infra/flow_control/repository/reverify_clone_query.py:128-160 的 raw SQL。survey 與 CA 程式碼層 0 互相 import,但資料層有一條稽核概念的軟參照,且由第三方(宿主)寫入。
看起來像但其實不是一件事:
app/task_type_declaration.py:35),且核心路徑零依賴有 AST 守衛測試 + 突變驗證(tests/unittest/test_plugin_contract.py:119-141)。TP 側 job_execution_surveys.survey_id 有真 FK 指 survey.surveys(CASCADE),但那是 TP 的表指過來,survey 不知道它存在。這是本批中疆界最清楚的一組。Q1 它是什麼
「一次合規稽核從規劃到結案的整條流程」——稽核輪次 7 態狀態機、評估計畫(AP)、評估結果(AR)判定與發現、改善計畫(POA&M)、SSP 參照文件、覆核輪繼承、審閱標記、稽核人員儀表板、任務設置(走 OSCAL catalog 解析出 AO 樹)。
README 與程式碼一致,且是本批中最誠實的一份——README.md:69-157 整節「誠實聲明」逐條列 16 支留守檔、4 支 D-4 讀寫混合 query、readmodel 全目錄不搬、錯誤碼是複製不是共用、ORM 仍認識其他套件實體。實查全部屬實。唯一要補充的是 README 沒說的:api/routes/{audit_route,poam_route}.py 兩支檔的 8 個 Resource class 完全沒有被 mount_routes() 掛載(見 Q5)。
→ 回答派工單的問題「它是稽核業務還是通用平台」:明確是稽核業務。判準三條:① pyproject.toml:4 自稱「合規稽核插件…插在 jedi-task-platform 上」;② 表名/欄位名/entity 名/error code 全帶產品詞(project_audit_rounds / poams / ssp_reference_documents / round_stage_transitions / FlowControlArControlEntity / 231 個 GRC_* 碼);③ 它依賴 7 支 jedi 套件(本批最多)而無任何套件依賴它(monorepo 全掃 0 處)——它是依賴圖的葉節點,典型的最上層業務插件。
Q2 資料
自己擁有的表(9 張,跨 compliance 與 oscal 兩個 schema):
| 表 | schema | 存什麼 | 關鍵欄位 |
|---|---|---|---|
project_audit_rounds |
compliance | 稽核輪次主檔,7 態狀態機(infra/model/project_audit_round.py:23) |
project_id(soft) / round_no / round_type(initial/close-out/surveillance) / status / parent_round_id / assessment_plan_id / ssp_id / ar_result_id / poam_id |
round_stage_transitions |
compliance | 階段歷程時間軸(round_stage_transition.py:13) |
— |
round_rollback_supersessions |
compliance | 回退 supersession 標記(round_rollback_supersession.py:13) |
— |
review_marks |
compliance | 審閱標記(review_mark.py:9,extend_existing=True) |
— |
poams |
compliance | 改善計畫(poam_model.py:11) |
uid / assessment_plan_id / ar_finding_id / control_identifier / ao_uid / status / tenant_id / remediation_plan / due_date / assignee_uid |
ap_docx_parse_jobs |
oscal | AP docx 解析工作紀錄(ap_docx_parse_job.py:14) |
— |
ar_xlsx_parse_jobs |
oscal | AR xlsx 解析工作紀錄(ar_xlsx_parse_job.py:14) |
— |
ssp_reference_documents |
oscal | SSP 引用文件(ssp_reference_document.py:19) |
file_id(FK → upload_files) |
ssp_reference_document_mappings |
oscal | 引用文件對照(ssp_reference_document_mapping.py:19) |
reference_document_id(FK CASCADE) |
poams 表在它與 oscal-v2 各定義一次,是同一張表嗎?→ 不是。是兩張不同 schema 的同名表。(DEV 實查)
jedi-compliance-audit 的 PoamModel |
jedi-oscal-v2 的 OscalPoam |
|
|---|---|---|
| 檔案 | jedi_compliance_audit/infra/model/poam_model.py:10-25 |
jedi_oscal_v2/infra/model/poam/oscal_poam.py:27-32 |
__tablename__ |
"poams" |
"poams" |
__table_args__ schema |
compliance |
oscal |
| DEV 實查存在 | ✅ compliance.poams |
✅ oscal.poams |
| DEV 欄位 | id(int) / uid(varchar) / assessment_plan_id / ar_finding_id / control_identifier / ao_uid / status / closed_at / tenant_id / org_unit_id / created_* / remediation_plan / due_date / assignee_uid(17 欄) | id(bigint) / uuid(uuid) / metadata_id / status / import_ssp_id / system_id / system_id_identifier_type / local_definitions(jsonb) / created_*(12 欄) |
| DEV 筆數 | 92 | 9 |
| 被指向的 FK | 0 條 | 4 條(oscal.{assessment_findings, assessment_observations, assessment_risks, poam_items}.poam_id) |
| 語意 | GRC 產品的「一條待改善項」(一個 AR finding 一條) | OSCAL 標準的 POA&M 文件 root(一份文件) |
兩張表schema 不同、欄位幾乎不重疊、粒度不同(項 vs 文件)、資料量差 10 倍——是兩個不同概念剛好撞名,不是同一張表被定義兩次。project_audit_rounds.poam_id(project_audit_round.py:36)comment 明寫指向 oscal.poams.id,也就是 CA 自己同時用到兩張。
對別人的表的參照:
(a) 真 FK:ssp_reference_documents.file_id → public.upload_files.id(DEV 實查,跨疆界指向宿主的檔案表);ssp_reference_document_mappings.reference_document_id → 自家表。project_audit_rounds 的四條 FK(DEV 實查)→ oscal.{assessment_plans, ar_results, system_security_plans} + 自 FK parent_round_id——這四條是真 FK,指向 jedi-oscal-v2 擁有的表(雖然 model docstring line 19-20 說「跨/同 schema soft-ref…比照慣例不建 ORM relationship」,DB 層實際有 FK 約束)。
(b) ORM relationship 跨包:ssp_reference_document.py:43 relationship("UploadFile")(字串引用宿主的 model);ssp_reference_document_mapping.py:40 自家表。
(c) 軟參照:project_audit_rounds.project_id → compliance.projects.id(TP 的表,無 FK)、flow_template_snapshot_uid / workflow_execution_uid → flow-engine;poams.assessment_plan_id / ar_finding_id / ao_uid → oscal-v2。
(d) raw SQL 直打別人的表(實查 grep -oE "FROM|JOIN <schema>.<table>"):
infra/repository/flow_control_review_repo_impl.py:FROM compliance.projects(TP 的表)、FROM/JOIN oscal.catalog_controls、oscal.catalog_control_parts、JOIN oscal.profile_imports、JOIN oscal.system_security_plans(皆 oscal-v2 的表)infra/repository/flow_control_task_setup_repo_impl.py:FROM/JOIN compliance.projects(TP)、JOIN compliance.job_executions、FROM compliance.workflow_templates、JOIN compliance.workflow_executions(皆 flow-engine 的表)、FROM compliance.task_assignees(participant 的表,line 133-135)、FROM oscal.assessment_plansinfra/repository/wf_control_mapping_lookup_query.py:53:FROM public.workflow_execution_control_mapping(主專案的表)JOIN public.users 那條已於 CM-1512 改成「查自家 task_assignees + 名冊 port」(flow_control_task_setup_repo_impl.py:126-142),但改成的做法是改去 raw SQL 查 participant 的表——身分那條償了,人員指派那條沒償。表名產品詞彙:全部帶產品詞(audit_round / poam / ssp / review / ar_xlsx / ap_docx)——這是它的疆界,不是洩漏。
Q3 Port
domain/ports.py 宣告 3 張 ABC:ILicenseGuard(21) / IProjectRoleGuard(40) / IIdentityGuard(62)。
→ 回答派工單的問題「三張 guard 與 task-platform 那份是否逐字相同」——diff 實測:程式碼逐字相同,只有 4 行 docstring 文字不同:
line 4: 「平台層不認識…」 vs 「本套件不認識…」
line 9: :func:`jedi_task_platform.task.plugin._assert_wiring` vs :func:`jedi_compliance_audit.plugin._assert_wiring`
line 11: 「…讀寫別人專案的任務資料」 vs 「…讀寫別人專案的稽核資料」
line 32: 「平台層用到 "project"」 vs 「本套件用到 "project" / "audit"」
三個 class 名、五個 method 簽名(require / viewer_licensed_modules / assert_role / assert_role_fallback / viewer_is_super_admin)、參數名、預設值、型別註記完全一致。這是同一份 port 定義的兩份複本——兩包各自複製一份而非共用,且 CA 依賴 TP(pyproject.toml:44),技術上可以 import TP 那份。
宣告了但零使用的 port:無(三張都在 plugin._assert_wiring 與 common/guard.py 內使用)。
提供給別人的入口:jedi_compliance_audit.plugin.{register, create_blueprint, attach, FlowControlAdapters, FlowControlServices}、app.service.{audit_round_app_service, assessment_result_app_service, poam_app_service, ap_docx_import_app_service, ar_import_app_service}、api.serializers.*。其他 jedi 套件 import 它:0 處。
Q4 消費者
(a) 主專案(129 處,本批最高):di_containers/flow_control/flow_control_containers.py(43)、infra/flow_control/repository(9)、app/oscal/service(7)、app/flow_control/service(6)、api/project/routes(5,ar_import_route.py / audit_round_route.py / ap_docx_import_route.py)、di_containers/{oscal,module_frame}(6)、app/module_frame/service(3)、api/flow_control/{__init__,routes,serializers}(4)、api/flow_engine/routes/stage_rollback_route.py:19(1)、infra/oscal/clone(2)等;另 test 20+ 處。
(b) 其他 jedi 套件:0 處。
它自己依賴 7 支 jedi 套件(pyproject.toml:42-48):iam / flow-engine / task-platform / participant / survey / device / oscal-v2。實查 import:oscal-v2 41 處、flow-engine 10 處、task-platform 13 處、participant 8 處、iam 5 處、survey 0 處、device 0 處(後兩支是 pyproject 虛掛,程式碼零 import)。
→ 回答派工單的問題「它直接 import oscal-v2 的 infra *_repo_impl 幾處」——30 處(grep -c "jedi_oscal_v2.infra.repository"),分佈在 4 個檔:
app/service/assessment_result_app_service.py:16 處(line 36-64:ap_repo / ap_reviewed_controls / ap_assessment_subjects / ap_tasks / ap_assessment_activities / ap_task_subjects / ar_finding / ar_finding_risk / ar_observation / ar_results / ar_risk / ar_remediation / catalog_control_part / catalog_control / profile_import / ssp)app/service/poam_app_service.py:7 處(line 24-30:ar_finding / ar_finding_risk / ar_observation / ar_remediation / ar_risk / poam_item / poam_milestone)app/service/audit_round_app_service.py:3 處(line 26-30:ap_repo / profile_import / ssp)infra/repository/flow_control_task_setup_repo_impl.py:4 處(line 51-62,函式內延後 import:ssp_repo / profile_import_repo / catalog_group_repo / catalog_control_part_repo) (另 11 處是 jedi_oscal_v2 的非 infra import:domain entity / domain service / app service / common enum)這是雙重違規:① 跨套件;② app service 層直接 import infra 層的 repo 實作(主專案 CLAUDE.md「app 層禁止直接 import ORM model 做查詢…所有 DB 操作必須透過 domain service → repository interface」)。23 處在 app service 內。
Q5 執行期形狀
api/__init__.py:66-79,tests/urls_frozen.txt 5 行焊死),掛宿主的 grc_projects blueprint / prefix /api/1.0/grc:/project/<uid>/current-ssp-uid、/project/<pu>/ap/<ap>/task-setup/tree、/project/<pu>/ap/<ap>/control/<cu>/review、/project/<pu>/ap/<ap>/control/<cu>/ao/<ao>/review、/audits/my/list。api/routes/audit_route.py(6 個:ArControlList / ArControlDetail / ArVerdict / ArFindingCreate / ArFindingDetail / ArRoundList)與 api/routes/poam_route.py(2 個:PoamList / PoamDetail)完全沒有出現在 mount_routes(),主專案 api/flow_control/__init__.py:230-237 有對應的 # api.add_resource(...) 註解行(FR-038 DEAD-V1,2026-06-18 停用)。也就是說兩支檔的 route class 隨包搬過來了,但沒有任何地方掛它們。app/service/ap_docx_import_app_service.py:21,90-99(tempfile.NamedTemporaryFile + BytesIO 收 docx 上傳)、app/service/ar_import_app_service.py:21(同,收 xlsx);文件解析 app/service/ap_report_parser/registry.py:14,26(import docx)、ar_report_parser/registry.py:8,17(import openpyxl)。無 SMTP / HTTP / S3 / subprocess / socket。pyproject.toml 未宣告 python-docx 與 openpyxl(grep "docx|openpyxl|pandas" pyproject.toml 無結果),但套件源碼直接 import docx / import openpyxl ——漏宣告的直接依賴(現在能跑是靠宿主裝了)。同一份 pyproject 卻列了程式碼零 import 的 jedi-survey 與 jedi-device(虛掛)。os.getenv 0 處。Q6 通用性
(a) 完全不能用於非稽核產品,且不應該被期待可以——它就是稽核業務本身。(b) 產品知識不是「洩漏」而是「內容」:7 態輪次狀態機(common/round_guard.py:1-13)、AP/AR/POA&M/SSP/AO 全套 OSCAL 概念、231 個 GRC_* error code(common/error_code.py,README:135-142 說明是主專案 common/code/flow_control_error_code.py 的逐字複製,套件實際用 50 個,兩份漂移由 tests/test_error_code_contract.py 焊死)、CMMC 專屬 parser(app/service/ap_report_parser/airasia_cmmc_l1_v1.py、ar_report_parser/airasia_cmmc_l1_ar_v1.py、ar_framework_profile/cmmc_l1.py——客戶名 airasia 進了檔名)。
Q7 插件完整度
| 項 | 有/無 | 證據 |
|---|---|---|
| ① api 層隨包 | ✅ 5 條 live(+8 個未掛載的死 class) | api/__init__.py:52-79 |
② register(app, adapters, config, schema_extensions, mount_api) + attach(bp, adapters) + create_blueprint() |
✅ | plugin.py:215, 174, 199 |
| ③ migrations 隨包 | ❌ 0 支 | — |
| ④ DI container 預設 | ❌ 0 個(宿主 di_containers/flow_control/flow_control_containers.py 43 處 import 撐起整包接線) |
— |
| ⑤ 獨立 harness | ✅ | harness/dev_app.py(檔頭 line 8-11:假 guard + 假 service,實打一支端點;不連 DB) |
| ⑥ 接入 README | ✅ | README.md(156 行,含最完整的誠實聲明) |
Q8 糾纏對象
與 jedi-oscal-v2(最強,遠超其他):41 處 import,其中 30 處直取 infra 層 *_repo_impl,23 處在 app service 內(違反自家分層)。assessment_result_app_service.py 一支就 import 了 oscal-v2 的 16 支 repo impl——它實際上是「用 oscal-v2 的 repo 拼出 AR 匯入流程」的組裝器。project_audit_rounds 對 oscal.{assessment_plans, ar_results, system_security_plans} 有 3 條真 FK(DEV 實查)。raw SQL 亦直 JOIN oscal.catalog_controls / catalog_control_parts / profile_imports / system_security_plans。一邊沒有另一邊就沒意義:CA 的核心(AP/AR/POA&M/AO 樹)全部是 OSCAL 文件模型的產品化包裝。
與 jedi-task-platform:13 處 import,其中 6 處直接 import ORM Project。最深的一條是 infra/repository/project_extension_repo_impl.py:14-43——它以 TP 的 Project 為 BaseRepositoryImpl 的 model,add()(line 90-111)與 update_owner()(line 113-127)UPDATE compliance.projects 的 module_frame_id / living_ssp_id / owner_id / deleted_at 四欄。也就是稽核插件在寫平台的主檔表,而那四欄正是 TP 的 projects 表上的產品欄位(Q2)。這是「同一交易寫兩邊」的直接證據:project_start_app_service 啟動專案時先由 TP 的 ProjectService 建列、再由 CA 這支補四欄。
與 jedi-flow-engine:10 處,其中 4 處直接 session.query(JobExecution) / import WorkflowTemplate ORM,raw SQL 亦 JOIN compliance.job_executions / workflow_executions / workflow_templates。project_audit_rounds.workflow_execution_uid 軟參照。
與 jedi-participant:8 處,全部是「借 DTO / serializer / enum / query entity / 名冊」——app/dto/{control_group,control,project}_dto.py 直接把 participant 的 ParticipantMenuDTO / ProjectParticipantDTO 當自己 DTO 的欄位型別,api/serializers/{task_setup,project}.py 直接 import participant 的 marshmallow schema。API 契約層跨套件共用。另 flow_control_task_setup_repo_impl.py:133-135 raw SQL 直查 compliance.task_assignees。
看起來像但其實不是一件事:
pyproject.toml:46 宣告依賴,程式碼 0 處 import(虛掛)。CA 與 survey 唯一的交會是 task_surveys.inherited_from_round_id 這個軟參照欄位,而寫入者是主專案的 raw SQL,兩包互不相識。pyproject.toml:47 宣告,程式碼 0 處 import(虛掛)。UserDomainService / UserQueryEntity),實體由宿主 DI 注入。infra 層 JOIN 已於 CM-1512 償清。這是正當的身分疆界取用。| ↓import→ | task-platform | participant | flow-engine | survey | compliance-audit | oscal-v2 | iam | device |
|---|---|---|---|---|---|---|---|---|
| task-platform | — | 3 | 0 (CM-1492 償) | 0 | 0(守衛焊死) | 0 | 0 (CM-1512 償) | 0 |
| participant | 2 | — | 3 | 0 | 0 | 0 | 0 (CM-1484 償) | 0 |
| flow-engine | 0 | 0 | — | 0 | 0 | 0 | 0 | 0 |
| survey | 1(申報書) | 7 | 2 | — | 0 | 0 | 7 | 1 |
| compliance-audit | 13 | 8 | 10 | 0(虛掛) | — | 41(30 直取 infra repo) | 5 | 0(虛掛) |
| 主專案(宿主) | 104 | 98 | 122 | 65 | 129 | — | — | — |
| jedi-detection | 0 | 0 | 5 | 0 | 0 | — | — | — |
🔴 唯一的循環:task-platform ⇄ participant(TP→P 3 處、P→TP 2 處;pyproject 亦雙向宣告)。
guidant_ai_dev pg_constraint 實查)| 來源表.欄位 | → 目標表 | 誰擁有來源 | 誰擁有目標 | ondelete |
|---|---|---|---|---|
compliance.job_execution_comments.author_id |
public.users |
task-platform | 宿主/iam | SET NULL |
compliance.job_execution_devices.device_id |
public.devices |
task-platform | device | CASCADE |
compliance.job_execution_org_units.org_unit_id |
public.org_units |
task-platform | 宿主/iam | CASCADE |
compliance.job_execution_surveys.survey_id |
survey.surveys |
task-platform | survey | CASCADE |
compliance.project_audit_rounds.assessment_plan_id |
oscal.assessment_plans |
compliance-audit | oscal-v2 | NO ACTION |
compliance.project_audit_rounds.ssp_id |
oscal.system_security_plans |
compliance-audit | oscal-v2 | NO ACTION |
compliance.project_audit_rounds.ar_result_id |
oscal.ar_results |
compliance-audit | oscal-v2 | NO ACTION |
oscal.ssp_reference_documents.file_id |
public.upload_files |
compliance-audit | 宿主/file-upload | NO ACTION |
compliance.{workflow_executions,job_executions,workflow_templates}.{tenant_id,org_unit_id} |
tenants / org_units |
flow-engine | 宿主/iam | NO ACTION |
compliance.job_evidences.{job_execution_id,workflow_execution_id,main_workflow_execution_id} |
flow-engine 兩表 | 主專案 | flow-engine | — |
public.workflow_execution_control_mapping.workflow_execution_id |
compliance.workflow_executions |
主專案 | flow-engine | — |
零 FK 的表(重要對照):participant 六張全 0;survey.task_surveys 0;task-platform 四張綁定表指向 job_executions 的 FK 已拆(CM-1490)。
| 來源 | → 目標 | 證據 |
|---|---|---|
participant 六張表 .project_id |
compliance.projects.id(task-platform) |
DEV:project_participants 716/752、task_assignees 12479/12479 對得上;對 workflow_executions 0 列 |
task_assignees.task_id |
compliance.job_executions.id(flow-engine) |
DEV:12470/12479 |
task-platform 四張綁定表 .job_execution_id |
compliance.job_executions.id |
原為 CASCADE FK,CM-1490 拆;代價轉為主專案每日 03:10 UTC 孤兒清理排程(core/scheduler.py:374-431) |
compliance.projects.living_ssp_id |
oscal.system_security_plans.id |
infra/model/project.py:85-89 + ix_projects_living_ssp_id 索引 |
survey.task_surveys.inherited_from_round_id |
compliance.project_audit_rounds.id(CA) |
task_survey.py:89-93;寫入者是主專案 raw SQL reverify_clone_query.py:128-160 |
compliance.project_audit_rounds.project_id |
compliance.projects.id(TP) |
project_audit_round.py:27 |
| 提供者 | Port / 入口 | 消費者 | 缺線行為 |
|---|---|---|---|
| task-platform | ILicenseGuard/IProjectRoleGuard/IIdentityGuard |
宿主注入 | 拒絕掛載 |
| task-platform | ITaskExecutionQuery / ITaskExistenceQuery |
宿主注入(實作留主專案) | 前者必填、後者降級 |
| task-platform | TaskTypeSpec / register_job_type() 申報制 |
survey(申報書)、主專案 app/detection_tools/task_type_declaration.py |
選配 |
| participant | common.user_directory(canonical 名冊) |
task-platform / survey / compliance-audit 三包 + 宿主 | 降級回空 + WARNING |
| participant | common.guard.assert_project_manager(canonical 守門) |
survey(task_survey_guard.py:24)+ 宿主 authz |
拒絕掛載 |
| participant | IUserDirectory / IProjectRoleGuard(要宿主給) |
宿主 api/participant/__init__.py:112-117 |
拒絕掛載 |
| participant | IProjectDirectory |
零消費者(樣板殘留) | 文件說拒絕掛載,實際 _assert_wiring 不檢查 |
| participant | IAuditLogger/IJobNotifier/IJobEnrichment |
宿主(以別的參數名注入) | 降級 |
| flow-engine | ISettingsReader / INotifier |
零消費者(誠實標註的樣板殘留) | — |
| survey | INotifier/IPresenceStore/IAuditNicknameEnricher/ConfigReader |
宿主 | 全降級 |
| survey | license 走 api/guard.configure() |
宿主 | 拒絕掛載 |
| compliance-audit | ILicenseGuard/IProjectRoleGuard/IIdentityGuard(與 TP 逐字相同的第二份) |
宿主 | 拒絕掛載 |
ILicenseGuard / IProjectRoleGuard / IIdentityGuard 在 task-platform task/domain/ports.py:21-67 與 compliance-audit domain/ports.py:21-67 各存在一份,diff 實測程式碼逐字相同、僅 4 行 docstring 措辭不同。CA 依賴 TP,技術上可 import TP 那份而非複製。(第三份在宿主 common/authz/,那是實作端。)
| api 隨包 | register() | migrations | DI container | harness | README | |
|---|---|---|---|---|---|---|
| task-platform | ✅ 10 條 | ✅ +attach |
❌ 0 | ❌ | ✅ | ✅ |
| participant | ✅ 10 條 live(9 條註解死路徑) | ✅ | ✅ 1 支 | ❌ | ✅(檔頭過時) | ✅ |
| flow-engine | ❌ 0 條(刻意,測試焊死) | ✅ | ❌ 0(明說不隨包) | ❌(有 app/wiring 縫隙) |
✅ +docker-compose | ✅ |
| survey | ✅ 32 條 / 2 bp | ✅ 6 參數 | ❌ 0 | ❌ | ❌ 無(誠實聲明) | ✅ |
| compliance-audit | ✅ 5 條 live(+8 個未掛載的死 class) | ✅ +attach |
❌ 0 | ❌ | ✅(不連 DB) | ✅ |
| 套件源碼行數 | error code 數 | 自有表數 | 依賴 jedi 套件數(實際 import) | |
|---|---|---|---|---|
| task-platform | 3,447 | 5 +3 | 5 | 1(participant) |
| participant | 5,310 | 8 | 6 | 2(flow-engine, task-platform) |
| flow-engine | 6,728 | 9 | 5 | 0 |
| survey | 11,315 | 34 | 14 | 4(participant, flow-engine, iam, device) |
| compliance-audit | 13,604 | 231(複製,用 50) | 9 | 5(oscal-v2, task-platform, flow-engine, participant, iam;另 2 支虛掛) |
ProjectParticipant.project relationship 指錯表且無讀者(infra/model/project_participant.py:23-30 指 WorkflowExecution.id,DEV 實查 0 列對得上;全 codebase 0 處讀取)。_UserDirectoryAdapter 只轉發 4 支中的 2 支(主專案 api/participant/__init__.py:75-90 vs infra/participant/user_directory_adapter.py:43-93),survey 的 get_org_units_by_ids / get_users_by_login_names 走的是「adapter 未實作 → 回空 + WARNING」的降級路徑。唯一接線點無其他 configure。jedi_flow_engine.plugin 在主專案 0 處被 import,register() 從未執行;連帶套件自己的 WorkflowExecutionService(依賴 plugin.py import 期的 set_repo_providers)在本產品是死碼路徑,主專案用的是自己那支 6,375 行的同名 service。pyproject.toml 漏宣告 python-docx / openpyxl(源碼直接 import),同時虛掛 jedi-survey / jedi-device(源碼 0 import)。api/routes/{audit_route,poam_route}.py)。task/ 子樹,jedi_task_platform/(project 那半)的 living_ssp_id 掃不到;且守衛黑名單不含 detection,而 task/app/service/task_execution_service.py 有 17 處 detection。log/app.log 未見「名冊 adapter 未實作」的 WARNING(grep 無結果),但該 log 只有 604KB 且不知涵蓋時間範圍與是否曾走到相關程式路徑,故無法據此判定第 2 點是否真的在跑時觸發——只確認了程式碼層的方法數落差。jedi_participant/migrations/001-participant-tables.sql 與 DEV 實際 schema 是否仍一致(只確認六張表都存在且 0 FK)。grep -c + 檔案分佈統計;「23 處在 app service 層」是按檔名歸類,未逐行確認每一處都在 class body 內。