V10.505 新增市場情報 writer review decision gate

This commit is contained in:
OoO
2026-05-31 18:19:28 +08:00
parent 58ac16781c
commit b347aa44b9
14 changed files with 1703 additions and 228 deletions

View File

@@ -108,6 +108,9 @@ from services.market_intel.mcp_fetch_candidate_queue_writer_review_handoff impor
from services.market_intel.mcp_fetch_candidate_queue_writer_review_inventory import (
build_mcp_fetch_candidate_queue_writer_review_inventory_preview,
)
from services.market_intel.mcp_fetch_candidate_queue_writer_review_decision import (
build_mcp_fetch_candidate_queue_writer_review_decision_preview,
)
from services.market_intel.mcp_manual_fetch_handoff import (
build_mcp_manual_fetch_handoff_preview,
)
@@ -287,8 +290,17 @@ PRODUCTION_SMOKE_TARGETS = (
+ ("/api/market_intel/mcp_fetch_candidate_queue_writer_review_inventory",)
+ PRODUCTION_SMOKE_TARGETS[-1:]
)
PRODUCTION_SMOKE_TARGETS = (
PRODUCTION_SMOKE_TARGETS[:-1]
+ ("/api/market_intel/mcp_fetch_candidate_queue_writer_review_decision",)
+ PRODUCTION_SMOKE_TARGETS[-1:]
)
def _run_review_preview_safe(payload, mode):
return bool(payload["mode"] == mode and all(not payload.get(key) for key in BLOCKED_RUN_REVIEW_KEYS))
def build_deployment_readiness_preview(*, service, market_intel_tables, schema_smoke_builder):
"""建立市場情報推版準備狀態;不執行 git、部署或遠端操作。"""
status = service.get_runtime_status()
@@ -381,6 +393,11 @@ def build_deployment_readiness_preview(*, service, market_intel_tables, schema_s
phase=service.phase,
)
)
mcp_fetch_candidate_queue_writer_review_decision = (
build_mcp_fetch_candidate_queue_writer_review_decision_preview(
phase=service.phase,
)
)
scheduler_plan = service.build_scheduler_plan()
manual_sample_plan = service.build_manual_sample_plan()
manual_sample_acceptance = service.build_manual_sample_acceptance()
@@ -1325,6 +1342,76 @@ def build_deployment_readiness_preview(*, service, market_intel_tables, schema_s
"candidate_review_state_updated"
]
),
"mcp_fetch_candidate_queue_writer_review_decision_preview_safe": bool(
mcp_fetch_candidate_queue_writer_review_decision["mode"]
== "mcp_fetch_candidate_queue_writer_review_decision_preview"
and not mcp_fetch_candidate_queue_writer_review_decision[
"payload_persisted"
]
and not mcp_fetch_candidate_queue_writer_review_decision[
"review_decision_persisted"
]
and not mcp_fetch_candidate_queue_writer_review_decision[
"candidate_queue_review_decision_persisted"
]
and not mcp_fetch_candidate_queue_writer_review_decision[
"candidate_queue_review_decision_file_written"
]
and not mcp_fetch_candidate_queue_writer_review_decision[
"decision_record_written"
]
and not mcp_fetch_candidate_queue_writer_review_decision[
"decision_record_file_written"
]
and not mcp_fetch_candidate_queue_writer_review_decision[
"package_artifact_created"
]
and not mcp_fetch_candidate_queue_writer_review_decision[
"api_writes_file"
]
and not mcp_fetch_candidate_queue_writer_review_decision[
"api_executes_cli"
]
and not mcp_fetch_candidate_queue_writer_review_decision[
"api_reads_approval_token"
]
and not mcp_fetch_candidate_queue_writer_review_decision[
"api_opens_database_connection"
]
and not mcp_fetch_candidate_queue_writer_review_decision[
"api_writes_database"
]
and not mcp_fetch_candidate_queue_writer_review_decision[
"api_updates_review_state"
]
and not mcp_fetch_candidate_queue_writer_review_decision[
"api_writes_decision_record"
]
and not mcp_fetch_candidate_queue_writer_review_decision[
"database_write_executed"
]
and not mcp_fetch_candidate_queue_writer_review_decision[
"database_commit_executed"
]
and not mcp_fetch_candidate_queue_writer_review_decision[
"database_connection_opened"
]
and not mcp_fetch_candidate_queue_writer_review_decision[
"cli_executed"
]
and not mcp_fetch_candidate_queue_writer_review_decision[
"file_written"
]
and not mcp_fetch_candidate_queue_writer_review_decision[
"scheduler_attached"
]
and not mcp_fetch_candidate_queue_writer_review_decision[
"candidate_queue_persisted"
]
and not mcp_fetch_candidate_queue_writer_review_decision[
"candidate_review_state_updated"
]
),
"candidate_queue_writer_postwrite_smoke_planned_safe": bool(
candidate_queue_writer_postwrite_smoke["mode"]
== "candidate_queue_writer_postwrite_smoke_planned"
@@ -1653,6 +1740,7 @@ def build_deployment_readiness_preview(*, service, market_intel_tables, schema_s
"mcp_fetch_candidate_queue_writer_post_closeout_inventory_review": mcp_fetch_candidate_queue_writer_post_closeout_inventory_review,
"mcp_fetch_candidate_queue_writer_review_handoff": mcp_fetch_candidate_queue_writer_review_handoff,
"mcp_fetch_candidate_queue_writer_review_inventory": mcp_fetch_candidate_queue_writer_review_inventory,
"mcp_fetch_candidate_queue_writer_review_decision": mcp_fetch_candidate_queue_writer_review_decision,
"scheduler_plan": scheduler_plan,
"manual_sample_plan": manual_sample_plan,
"manual_sample_acceptance": manual_sample_acceptance,

View File

@@ -0,0 +1,498 @@
"""市場情報 MCP fetch candidate queue writer review decision。
本模組只審核 review inventory 後的人工候選隊列決策摘要;
API/UI 不讀 approval token、不執行 CLI、不開 DB、不寫 queue、不做 inventory query、
不更新 review_state、不寫 match result、不掛 scheduler。
"""
from services.market_intel.mcp_fetch_candidate_queue_writer_preflight import TARGET_TABLE
from services.market_intel.mcp_fetch_candidate_queue_writer_run_readiness import (
ARTIFACT_PREFIX,
)
from services.market_intel.mcp_fetch_candidate_queue_writer_post_closeout_inventory_review import (
FORBIDDEN_SECRET_KEYS,
SAFE_SECRET_METADATA_KEYS,
_as_dict,
_as_list,
_blocked_side_effects,
_contains_forbidden_key,
_safe_int,
_safe_path,
_safe_text,
)
from services.market_intel.mcp_fetch_candidate_queue_writer_review_inventory import (
build_mcp_fetch_candidate_queue_writer_review_inventory_preview,
)
from services.market_intel.mcp_fetch_candidate_queue_writer_review_decision_gates import (
ALLOWED_REVIEW_DECISIONS,
build_review_decision_gates,
)
from services.market_intel.mcp_fetch_candidate_queue_writer_review_decision_sample import (
build_sample_writer_review_decision_package,
)
_REVIEW_DECISION_BLOCKED_SIDE_EFFECT_KEYS = (
"allow_api_candidate_review_update",
"allow_api_execution",
"allow_api_file_write",
"allow_api_inventory_query",
"allow_api_match_write",
"allow_api_queue_insert",
"allow_api_queue_write",
"allow_cli_execution",
"allow_database_write",
"api_candidate_review_update_executed",
"api_created_review_decision_file",
"api_created_review_queue",
"api_executed_candidate_review",
"api_inventory_query_executed",
"api_review_decision_persisted",
"api_review_state_update_executed",
"api_updates_review_state",
"api_writes_match_result",
"api_writes_review_queue",
"candidate_queue_review_decision_file_written",
"candidate_queue_review_decision_persisted",
"candidate_queue_review_started_by_api",
"candidate_review_file_written",
"candidate_review_queue_written",
"decision_record_written",
"identity_match_written",
"manual_review_record_written",
"market_product_match_written",
"match_result_written",
"queue_review_persisted",
"review_decision_file_written",
"review_decision_persisted",
"review_queue_file_written",
"review_queue_row_written",
"review_state_update_executed",
)
def _blocked_review_decision_side_effects(payload):
found = list(_blocked_side_effects(payload))
def visit(value, path):
if isinstance(value, dict):
for key, item in value.items():
normalized_key = str(key).lower()
key_path = f"{path}.{key}" if path else key
if (
normalized_key in _REVIEW_DECISION_BLOCKED_SIDE_EFFECT_KEYS
and bool(item)
):
found.append(key_path)
visit(item, key_path)
elif isinstance(value, list):
for index, item in enumerate(value):
visit(item, f"{path}[{index}]")
visit(payload, "")
return sorted(set(found))
def _inventory_from_inputs(inventory_package, inventory_result, phase):
if isinstance(inventory_result, dict) and inventory_result:
return inventory_result
inventory_package = _as_dict(inventory_package)
return build_mcp_fetch_candidate_queue_writer_review_inventory_preview(
writer_review_handoff_package=(
inventory_package.get("writer_review_handoff_package")
or inventory_package.get("candidate_queue_writer_review_handoff")
or inventory_package.get("writer_review_handoff")
or inventory_package.get("review_handoff_package")
or inventory_package.get("review_handoff")
or {}
),
writer_review_handoff_result=(
inventory_package.get("writer_review_handoff_result")
or inventory_package.get(
"mcp_fetch_candidate_queue_writer_review_handoff"
)
),
operator_review_inventory=(
inventory_package.get("operator_review_inventory")
or inventory_package.get("candidate_queue_review_inventory")
or inventory_package.get("writer_review_inventory")
or inventory_package.get("review_inventory_payload")
or inventory_package.get("inventory_payload")
or inventory_package.get("inventory")
),
phase=phase,
)
def _inventory_summary(inventory_result):
inventory_result = _as_dict(inventory_result)
inventory = _as_dict(inventory_result.get("operator_review_inventory_summary"))
side_effects_clear = bool(
not inventory_result.get("payload_persisted")
and not inventory_result.get("review_inventory_persisted")
and not inventory_result.get("writer_review_inventory_persisted")
and not inventory_result.get("candidate_queue_review_inventory_persisted")
and not inventory_result.get("candidate_queue_review_inventory_file_written")
and not inventory_result.get("review_inventory_file_written")
and not inventory_result.get("inventory_file_written")
and not inventory_result.get("review_queue_file_written")
and not inventory_result.get("candidate_review_file_written")
and not inventory_result.get("package_artifact_created")
and not inventory_result.get("network_request_allowed")
and not inventory_result.get("api_executes_cli")
and not inventory_result.get("api_reads_approval_token")
and not inventory_result.get("api_opens_database_connection")
and not inventory_result.get("api_writes_database")
and not inventory_result.get("api_writes_file")
and not inventory_result.get("api_uses_external_network")
and not inventory_result.get("api_inventory_query_executed")
and not inventory_result.get("api_candidate_review_update_executed")
and not inventory_result.get("api_updates_review_state")
and not inventory_result.get("review_inventory_query_executed_by_api")
and not inventory_result.get("database_write_executed")
and not inventory_result.get("database_commit_executed")
and not inventory_result.get("cli_executed")
and not inventory_result.get("file_written")
and not inventory_result.get("scheduler_attached")
and not inventory_result.get("candidate_queue_created")
and not inventory_result.get("candidate_queue_persisted")
and not inventory_result.get("candidate_review_state_updated")
)
return {
"mode": inventory_result.get("mode"),
"accepted": bool(
inventory_result.get(
"mcp_fetch_candidate_queue_writer_review_inventory_accepted"
)
),
"ready_for_candidate_queue_review_decision": bool(
inventory_result.get("ready_for_candidate_queue_review_decision")
),
"review_inventory_id": _safe_text(
inventory.get("review_inventory_id"), 160
),
"handoff_id": _safe_text(inventory.get("handoff_id"), 160),
"inventory_review_id": _safe_text(inventory.get("inventory_review_id"), 160),
"closeout_id": _safe_text(inventory.get("closeout_id"), 160),
"run_package_id": _safe_text(inventory.get("run_package_id"), 160),
"receipt_id": _safe_text(inventory.get("receipt_id"), 160),
"target_table": _safe_text(inventory.get("target_table"), 160),
"payload_row_count": _safe_int(inventory.get("payload_row_count")),
"expected_payload_row_count": _safe_int(
inventory.get("expected_payload_row_count")
or inventory.get("payload_row_count")
),
"found_row_count": _safe_int(inventory.get("found_row_count")),
"dedupe_key_count": _safe_int(inventory.get("dedupe_key_count")),
"sample_dedupe_keys": [
_safe_text(item, 160)
for item in _as_list(inventory.get("sample_dedupe_keys"))
if _safe_text(item, 160)
],
"review_state": _safe_text(inventory.get("review_state"), 80),
"review_state_breakdown": _as_dict(inventory.get("review_state_breakdown")),
"review_queue_artifact_path": _safe_text(
inventory.get("review_queue_artifact_path")
),
"review_inventory_artifact_path": _safe_text(
inventory.get("review_inventory_artifact_path")
),
"read_only_query_result_path": _safe_text(
inventory.get("read_only_query_result_path")
),
"side_effects_clear": side_effects_clear,
"blocked_reasons": inventory_result.get("blocked_reasons", []),
}
def _decision_row_summary(row):
row = _as_dict(row)
return {
"dedupe_key": _safe_text(row.get("dedupe_key"), 160),
"current_review_state": _safe_text(row.get("current_review_state"), 80),
"proposed_review_decision": _safe_text(
row.get("proposed_review_decision"), 80
),
"decision_bucket": _safe_text(row.get("decision_bucket"), 120),
"evidence_lane": _safe_text(row.get("evidence_lane"), 120),
"evidence_ref": _safe_text(row.get("evidence_ref")),
"evidence_ref_safe": _safe_path(
row.get("evidence_ref"),
prefixes=(ARTIFACT_PREFIX,),
suffixes=(".json",),
),
"decision_notes_present": bool(_safe_text(row.get("decision_notes"))),
"hard_veto_present": bool(row.get("hard_veto_present")),
"stronger_existing_match_conflict": bool(
row.get("stronger_existing_match_conflict")
),
"false_positive_guard_passed": bool(row.get("false_positive_guard_passed")),
"variant_conflict_checked": bool(row.get("variant_conflict_checked")),
"overwrite_protection_checked": bool(
row.get("overwrite_protection_checked")
),
"exact_identity_confirmed": bool(row.get("exact_identity_confirmed")),
}
def _operator_review_decision_summary(operator_decision):
operator_decision = _as_dict(operator_decision)
confirmations = _as_dict(operator_decision.get("operator_confirmations"))
rows = [_decision_row_summary(row) for row in _as_list(
operator_decision.get("decision_rows")
)]
expected_dedupe_keys = [
_safe_text(item, 160)
for item in _as_list(operator_decision.get("expected_dedupe_keys"))
if _safe_text(item, 160)
]
return {
"provided_keys": sorted(operator_decision.keys()),
"review_decision_id": _safe_text(
operator_decision.get("review_decision_id"), 160
),
"review_inventory_id": _safe_text(
operator_decision.get("review_inventory_id"), 160
),
"handoff_id": _safe_text(operator_decision.get("handoff_id"), 160),
"inventory_review_id": _safe_text(
operator_decision.get("inventory_review_id"), 160
),
"closeout_id": _safe_text(operator_decision.get("closeout_id"), 160),
"run_package_id": _safe_text(operator_decision.get("run_package_id"), 160),
"receipt_id": _safe_text(operator_decision.get("receipt_id"), 160),
"target_table": _safe_text(operator_decision.get("target_table"), 160),
"decision_scope": _safe_text(operator_decision.get("decision_scope"), 120),
"decision_mode": _safe_text(operator_decision.get("decision_mode"), 120),
"expected_current_review_state": _safe_text(
operator_decision.get("expected_current_review_state"), 80
),
"decision_row_count": _safe_int(operator_decision.get("decision_row_count")),
"expected_decision_row_count": _safe_int(
operator_decision.get("expected_decision_row_count")
or operator_decision.get("decision_row_count")
),
"expected_dedupe_keys": expected_dedupe_keys,
"decision_rows": rows,
"review_queue_artifact_path": _safe_text(
operator_decision.get("review_queue_artifact_path")
),
"review_inventory_artifact_path": _safe_text(
operator_decision.get("review_inventory_artifact_path")
),
"review_decision_artifact_path": _safe_text(
operator_decision.get("review_decision_artifact_path")
),
"review_queue_artifact_path_safe": _safe_path(
operator_decision.get("review_queue_artifact_path"),
prefixes=(ARTIFACT_PREFIX,),
suffixes=(".json",),
),
"review_inventory_artifact_path_safe": _safe_path(
operator_decision.get("review_inventory_artifact_path"),
prefixes=(ARTIFACT_PREFIX,),
suffixes=(".json",),
),
"review_decision_artifact_path_safe": _safe_path(
operator_decision.get("review_decision_artifact_path"),
prefixes=(ARTIFACT_PREFIX,),
suffixes=(".json",),
),
"review_inventory_checked": bool(
confirmations.get("review_inventory_checked")
),
"candidate_queue_review_only": bool(
confirmations.get("candidate_queue_review_only")
),
"human_decision_only": bool(confirmations.get("human_decision_only")),
"manual_record_required": bool(confirmations.get("manual_record_required")),
"no_approval_token_payload": bool(
confirmations.get("no_approval_token_payload")
),
"no_api_cli_execution": bool(confirmations.get("no_api_cli_execution")),
"no_api_database_write": bool(confirmations.get("no_api_database_write")),
"no_api_inventory_query": bool(confirmations.get("no_api_inventory_query")),
"no_api_review_state_update": bool(
confirmations.get("no_api_review_state_update")
),
"no_api_queue_insert": bool(confirmations.get("no_api_queue_insert")),
"no_api_file_write": bool(confirmations.get("no_api_file_write")),
"no_scheduler_attach": bool(confirmations.get("no_scheduler_attach")),
"stronger_existing_match_guard_preserved": bool(
confirmations.get("stronger_existing_match_guard_preserved")
),
"false_positive_guard_completed": bool(
confirmations.get("false_positive_guard_completed")
),
"variant_sensitive_review_completed": bool(
confirmations.get("variant_sensitive_review_completed")
),
"hard_veto_respected": bool(confirmations.get("hard_veto_respected")),
"api_execution_allowed": bool(operator_decision.get("api_execution_allowed")),
"real_write_allowed_by_api": bool(
operator_decision.get("real_write_allowed_by_api")
),
"api_candidate_review_allowed": bool(
operator_decision.get("api_candidate_review_allowed")
),
"api_updates_review_state": bool(
operator_decision.get("api_updates_review_state")
),
"api_writes_match_result": bool(
operator_decision.get("api_writes_match_result")
),
"secret_or_token_submitted_to_api": _contains_forbidden_key(
operator_decision,
FORBIDDEN_SECRET_KEYS,
safe_keys=SAFE_SECRET_METADATA_KEYS,
),
"blocked_side_effects": _blocked_review_decision_side_effects(
operator_decision
),
}
def build_mcp_fetch_candidate_queue_writer_review_decision_preview(
*,
writer_review_inventory_package=None,
writer_review_inventory_result=None,
operator_review_decision=None,
phase=None,
):
"""建立 writer review decision不執行查詢、寫檔或寫入。"""
writer_review_inventory_package = _as_dict(writer_review_inventory_package)
inventory_result_received = bool(
isinstance(writer_review_inventory_result, dict)
and writer_review_inventory_result
)
decision_valid_object = (
isinstance(operator_review_decision, dict)
if operator_review_decision is not None
else True
)
decision_payload = _as_dict(operator_review_decision)
inventory_received = bool(
writer_review_inventory_package or inventory_result_received
)
inventory_result = (
_inventory_from_inputs(
writer_review_inventory_package,
writer_review_inventory_result,
phase,
)
if inventory_received
else {}
)
payload_received = bool(
inventory_received or decision_payload or operator_review_decision is not None
)
decision_received = bool(decision_payload)
inventory = _inventory_summary(inventory_result)
decision = _operator_review_decision_summary(decision_payload)
gates = build_review_decision_gates(
inventory_received=inventory_received,
decision_received=decision_received and decision_valid_object,
inventory=inventory,
decision=decision,
)
blocked_reasons = [gate["key"] for gate in gates if not gate["passed"]]
if not decision_valid_object:
blocked_reasons.append("operator_review_decision_payload_valid_object")
accepted = bool(payload_received and not blocked_reasons)
return {
"mode": (
"mcp_fetch_candidate_queue_writer_review_decision"
if payload_received
else "mcp_fetch_candidate_queue_writer_review_decision_preview"
),
"phase": phase,
"writer_review_decision_payload_received": payload_received,
"review_decision_payload_received": payload_received,
"writer_review_inventory_received": inventory_received,
"operator_review_decision_received": decision_received,
"operator_review_decision_valid_object": decision_valid_object,
"writer_review_inventory_accepted": inventory["accepted"],
"mcp_fetch_candidate_queue_writer_review_decision_accepted": accepted,
"candidate_queue_writer_review_decision_ready": accepted,
"ready_for_candidate_queue_human_review": accepted,
"ready_for_candidate_queue_review_decision_approval": accepted,
"ready_for_candidate_queue_review_state_writer": False,
"ready_for_candidate_queue_review_decision_writer_preflight": False,
"ready_for_api_database_write": False,
"ready_for_real_write": False,
"ready_for_scheduler_attach": False,
"ready_for_candidate_queue_review_api_update": False,
"ready_for_api_review_state_update": False,
"ready_for_api_match_write": False,
"network_request_allowed": False,
"api_executes_cli": False,
"api_reads_approval_token": False,
"api_opens_database_connection": False,
"api_writes_database": False,
"api_writes_file": False,
"api_uses_external_network": False,
"api_inventory_query_executed": False,
"api_candidate_review_update_executed": False,
"api_updates_review_state": False,
"api_writes_decision_record": False,
"api_writes_match_result": False,
"review_decision_query_executed_by_api": False,
"payload_row_count": decision["decision_row_count"],
"gate_count": len(gates),
"passed_gate_count": sum(1 for gate in gates if gate["passed"]),
"blocked_reasons": blocked_reasons,
"gates": gates,
"writer_review_inventory_summary": inventory,
"operator_review_decision_summary": decision,
"decision_rows": decision["decision_rows"],
"decision_contract": {
"expected_current_state": "needs_review",
"allowed_next_states": list(ALLOWED_REVIEW_DECISIONS),
"manual_decision_required": True,
"next_gate": "candidate_queue_review_decision_approval",
"decision_scope": "candidate_queue_review_decision",
"forbidden_api_actions": [
"update_review_state",
"write_match_result",
"write_decision_record",
"insert_missing_queue_row",
"attach_scheduler",
],
},
"sample_writer_review_decision_package": (
build_sample_writer_review_decision_package()
),
"next_operator_steps": [
"Decision 通過後,只代表可進入人工 approval / writer preflight 設計",
"API/UI 仍不得自動寫 review_state、寫 match result、補 queue row 或讀 token",
"matched 決策必須保留 exact identity、variant、false-positive 與 overwrite guard",
],
"payload_persisted": False,
"review_decision_persisted": False,
"writer_review_decision_persisted": False,
"candidate_queue_review_decision_persisted": False,
"candidate_queue_review_decision_file_written": False,
"review_decision_file_written": False,
"decision_record_written": False,
"decision_record_file_written": False,
"review_queue_file_written": False,
"candidate_review_file_written": False,
"match_result_written": False,
"market_product_match_written": False,
"package_artifact_created": False,
"database_connection_opened": False,
"database_session_created": False,
"database_commit_executed": False,
"database_write_executed": False,
"external_network_executed": False,
"cli_executed": False,
"file_written": False,
"writes_executed": False,
"would_write_database": False,
"scheduler_attached": False,
"candidate_queue_created": False,
"candidate_queue_persisted": False,
"candidate_review_state_updated": False,
}

View File

@@ -0,0 +1,241 @@
"""Gate checks for market intel candidate queue review decision."""
from services.market_intel.mcp_fetch_candidate_queue_writer_preflight import TARGET_TABLE
ALLOWED_REVIEW_DECISIONS = (
"matched",
"low_score",
"identity_veto",
"unit_comparable",
"fresh_search_required",
"deferred",
)
def _sample_keys_covered_by_expected(inventory, decision):
sample_keys = set(inventory["sample_dedupe_keys"])
expected_keys = set(decision["expected_dedupe_keys"])
return bool(sample_keys and sample_keys.issubset(expected_keys))
def _row_keys_match_expected(decision):
row_keys = [row["dedupe_key"] for row in decision["decision_rows"]]
expected_keys = decision["expected_dedupe_keys"]
return bool(
row_keys
and len(row_keys) == len(set(row_keys))
and set(row_keys) == set(expected_keys)
)
def _all_rows_have_allowed_decisions(decision):
return bool(
decision["decision_rows"]
and all(
row["proposed_review_decision"] in ALLOWED_REVIEW_DECISIONS
for row in decision["decision_rows"]
)
)
def _all_rows_have_manual_evidence(decision):
return bool(
decision["decision_rows"]
and all(
row["decision_notes_present"] and row["evidence_ref_safe"]
for row in decision["decision_rows"]
)
)
def _matched_rows_keep_identity_guards(decision):
matched_rows = [
row
for row in decision["decision_rows"]
if row["proposed_review_decision"] == "matched"
]
if not matched_rows:
return True
return all(
row["evidence_lane"] == "exact_identity"
and row["exact_identity_confirmed"]
and not row["hard_veto_present"]
and not row["stronger_existing_match_conflict"]
and row["false_positive_guard_passed"]
and row["variant_conflict_checked"]
and row["overwrite_protection_checked"]
for row in matched_rows
)
def build_review_decision_gates(*, inventory_received, decision_received, inventory, decision):
operator_confirmed_boundaries = bool(
decision["review_inventory_checked"]
and decision["candidate_queue_review_only"]
and decision["human_decision_only"]
and decision["manual_record_required"]
and decision["no_approval_token_payload"]
and decision["no_api_cli_execution"]
and decision["no_api_database_write"]
and decision["no_api_inventory_query"]
and decision["no_api_review_state_update"]
and decision["no_api_queue_insert"]
and decision["no_api_file_write"]
and decision["no_scheduler_attach"]
and decision["stronger_existing_match_guard_preserved"]
and decision["false_positive_guard_completed"]
and decision["variant_sensitive_review_completed"]
and decision["hard_veto_respected"]
)
return [
{
"key": "candidate_queue_review_inventory_payload_or_result_received",
"label": "已提供 review inventory package 或已審核結果",
"passed": inventory_received,
},
{
"key": "candidate_queue_review_inventory_accepted",
"label": "review inventory gate 必須已通過",
"passed": inventory["accepted"],
},
{
"key": "candidate_queue_review_inventory_ready_for_decision",
"label": "inventory 必須只放行到 candidate queue review decision",
"passed": inventory["ready_for_candidate_queue_review_decision"],
},
{
"key": "candidate_queue_review_inventory_side_effect_free",
"label": "inventory 未顯示 API 執行、寫 DB、查 DB、更新 review_state 或掛 scheduler",
"passed": inventory["side_effects_clear"],
},
{
"key": "candidate_queue_review_decision_payload_received",
"label": "已提供 operator candidate queue review decision 摘要",
"passed": decision_received,
},
{
"key": "candidate_queue_review_decision_identity_recorded",
"label": "decision 必須記錄 review_decision_id 與上游 identity",
"passed": bool(
decision["review_decision_id"]
and decision["review_inventory_id"]
and decision["handoff_id"]
and decision["inventory_review_id"]
and decision["closeout_id"]
and decision["run_package_id"]
and decision["receipt_id"]
),
},
{
"key": "candidate_queue_review_decision_identity_matches_inventory",
"label": "decision identity 必須對齊 review inventory",
"passed": bool(
decision["review_inventory_id"] == inventory["review_inventory_id"]
and decision["handoff_id"] == inventory["handoff_id"]
and decision["inventory_review_id"] == inventory["inventory_review_id"]
and decision["closeout_id"] == inventory["closeout_id"]
and decision["run_package_id"] == inventory["run_package_id"]
and decision["receipt_id"] == inventory["receipt_id"]
),
},
{
"key": "candidate_queue_review_decision_target_table_safe",
"label": "target table 必須是 market_alert_review_queue",
"passed": decision["target_table"] == TARGET_TABLE,
},
{
"key": "candidate_queue_review_decision_scope_safe",
"label": "decision 僅能是 human review only",
"passed": bool(
decision["decision_scope"] == "candidate_queue_review_decision"
and decision["decision_mode"] == "human_review_only"
and decision["expected_current_review_state"] == "needs_review"
),
},
{
"key": "candidate_queue_review_decision_row_count_matches_inventory",
"label": "decision row count 必須對齊 inventory dedupe count",
"passed": bool(
decision["expected_decision_row_count"]
and decision["decision_row_count"]
== decision["expected_decision_row_count"]
== inventory["dedupe_key_count"]
== inventory["found_row_count"]
and len(decision["decision_rows"]) == inventory["dedupe_key_count"]
),
},
{
"key": "candidate_queue_review_decision_dedupe_keys_match_inventory",
"label": "decision dedupe keys 必須覆蓋 inventory keys 且逐列對齊",
"passed": bool(
_sample_keys_covered_by_expected(inventory, decision)
and _row_keys_match_expected(decision)
),
},
{
"key": "candidate_queue_review_decision_rows_still_needs_review",
"label": "所有 decision rows 的目前狀態必須仍是 needs_review",
"passed": bool(
decision["decision_rows"]
and all(
row["current_review_state"] == "needs_review"
for row in decision["decision_rows"]
)
),
},
{
"key": "candidate_queue_review_decision_values_allowed",
"label": "人工決策只能使用允許的狀態集合",
"passed": _all_rows_have_allowed_decisions(decision),
},
{
"key": "candidate_queue_review_decision_row_evidence_complete",
"label": "每列決策必須保留 evidence ref 與 notes",
"passed": _all_rows_have_manual_evidence(decision),
},
{
"key": "candidate_queue_review_decision_matched_rows_keep_identity_guards",
"label": "matched rows 必須保留 exact identity、false-positive、variant 與 overwrite guard",
"passed": _matched_rows_keep_identity_guards(decision),
},
{
"key": "candidate_queue_review_decision_artifact_paths_safe",
"label": "review queue、inventory 與 decision artifact paths 必須安全並對齊",
"passed": bool(
decision["review_queue_artifact_path_safe"]
and decision["review_inventory_artifact_path_safe"]
and decision["review_decision_artifact_path_safe"]
and decision["review_queue_artifact_path"]
== inventory["review_queue_artifact_path"]
and decision["review_inventory_artifact_path"]
== inventory["review_inventory_artifact_path"]
),
},
{
"key": "candidate_queue_review_decision_operator_boundaries_confirmed",
"label": "操作員確認 API 未執行 CLI/DB/query/file/review_state/scheduler且 guardrail 已完成",
"passed": operator_confirmed_boundaries,
},
{
"key": "candidate_queue_review_decision_no_api_execution_or_real_write",
"label": "decision payload 不得允許 API execution、real write、review update 或 match write",
"passed": bool(
not decision["api_execution_allowed"]
and not decision["real_write_allowed_by_api"]
and not decision["api_candidate_review_allowed"]
and not decision["api_updates_review_state"]
and not decision["api_writes_match_result"]
),
},
{
"key": "candidate_queue_review_decision_no_secret_or_token_key",
"label": "decision payload 不得包含 secret、cookie、password 或 token key",
"passed": not decision["secret_or_token_submitted_to_api"],
},
{
"key": "candidate_queue_review_decision_side_effect_free",
"label": "decision payload 不得要求 API 寫檔、執行、查 DB、寫 DB、補 queue、寫 match 或掛 scheduler",
"passed": not decision["blocked_side_effects"],
},
]

View File

@@ -0,0 +1,118 @@
"""Sample payload for the market intel candidate queue review decision gate."""
from copy import deepcopy
from services.market_intel.mcp_fetch_candidate_queue_writer_preflight import TARGET_TABLE
from services.market_intel.mcp_fetch_candidate_queue_writer_run_readiness import (
ARTIFACT_PREFIX,
)
from services.market_intel.mcp_fetch_candidate_queue_writer_review_inventory import (
build_mcp_fetch_candidate_queue_writer_review_inventory_preview,
)
_SAMPLE_WRITER_REVIEW_DECISION_PACKAGE = None
def build_sample_writer_review_decision_package():
global _SAMPLE_WRITER_REVIEW_DECISION_PACKAGE
if _SAMPLE_WRITER_REVIEW_DECISION_PACKAGE is not None:
return deepcopy(_SAMPLE_WRITER_REVIEW_DECISION_PACKAGE)
inventory_preview = (
build_mcp_fetch_candidate_queue_writer_review_inventory_preview()
)
inventory_package = inventory_preview["sample_writer_review_inventory_package"]
inventory_result = build_mcp_fetch_candidate_queue_writer_review_inventory_preview(
writer_review_handoff_package=inventory_package[
"writer_review_handoff_package"
],
writer_review_handoff_result=inventory_package[
"writer_review_handoff_result"
],
operator_review_inventory=inventory_package["operator_review_inventory"],
)
inventory = inventory_result["operator_review_inventory_summary"]
dedupe_keys = inventory["sample_dedupe_keys"]
decision_rows = []
for index, dedupe_key in enumerate(dedupe_keys):
proposed_decision = "matched" if index == 0 else "low_score"
decision_rows.append(
{
"dedupe_key": dedupe_key,
"current_review_state": "needs_review",
"proposed_review_decision": proposed_decision,
"decision_bucket": "recoverable_low_score",
"evidence_lane": "exact_identity",
"evidence_ref": (
ARTIFACT_PREFIX
+ f"candidate-queue-review-decision-evidence-{index + 1}.json"
),
"decision_notes": (
"same candidate lane checked; keep human audit trail"
),
"hard_veto_present": False,
"stronger_existing_match_conflict": False,
"false_positive_guard_passed": True,
"variant_conflict_checked": True,
"overwrite_protection_checked": True,
"exact_identity_confirmed": proposed_decision == "matched",
}
)
operator_review_decision = {
"review_decision_id": (
"market-intel-candidate-writer-review-decision-sample"
),
"review_inventory_id": inventory["review_inventory_id"],
"handoff_id": inventory["handoff_id"],
"inventory_review_id": inventory["inventory_review_id"],
"closeout_id": inventory["closeout_id"],
"run_package_id": inventory["run_package_id"],
"receipt_id": inventory["receipt_id"],
"target_table": TARGET_TABLE,
"decision_scope": "candidate_queue_review_decision",
"decision_mode": "human_review_only",
"expected_current_review_state": "needs_review",
"decision_row_count": len(decision_rows),
"expected_decision_row_count": inventory["dedupe_key_count"],
"expected_dedupe_keys": dedupe_keys,
"review_queue_artifact_path": inventory["review_queue_artifact_path"],
"review_inventory_artifact_path": (
inventory["review_inventory_artifact_path"]
),
"review_decision_artifact_path": (
ARTIFACT_PREFIX
+ "candidate-queue-review-decision-sample.json"
),
"decision_rows": decision_rows,
"operator_confirmations": {
"review_inventory_checked": True,
"candidate_queue_review_only": True,
"human_decision_only": True,
"manual_record_required": True,
"no_approval_token_payload": True,
"no_api_cli_execution": True,
"no_api_database_write": True,
"no_api_inventory_query": True,
"no_api_review_state_update": True,
"no_api_queue_insert": True,
"no_api_file_write": True,
"no_scheduler_attach": True,
"stronger_existing_match_guard_preserved": True,
"false_positive_guard_completed": True,
"variant_sensitive_review_completed": True,
"hard_veto_respected": True,
},
"api_execution_allowed": False,
"real_write_allowed_by_api": False,
"api_candidate_review_allowed": False,
"api_updates_review_state": False,
"api_writes_match_result": False,
}
_SAMPLE_WRITER_REVIEW_DECISION_PACKAGE = {
"writer_review_inventory_package": inventory_package,
"writer_review_inventory_result": inventory_result,
"operator_review_decision": operator_review_decision,
}
return deepcopy(_SAMPLE_WRITER_REVIEW_DECISION_PACKAGE)

View File

@@ -1,3 +1,3 @@
"""市場情報 rollout phase 單一來源。"""
MARKET_INTEL_PHASE = "phase_136_market_intel_mcp_fetch_candidate_queue_writer_review_inventory"
MARKET_INTEL_PHASE = "phase_137_market_intel_mcp_fetch_candidate_queue_writer_review_decision"