feat(stage1-part4): 사실원장 yaml 신작 (전략서 방식, 구 v3와 무관) + 자산 배포

- stage_1_part_4_v.8.yml: F0 code 리듀서(배포게이트 13·봉인 3종·13인자 호출)
  → F1 flash-lite 예외 재정판정(amount 한정) → F2 code 최종 게이트/작성자
  (보존 등식 5종 재계수·닫힌 29키·handoff FINALIZED), 완전 직렬 DAG
- 신설 자산: fact_ledger_compiler(.py/.txt 미러)·fact_ledger_policy.v1·스키마 3종,
  게이트 사이드카 gates.json ×25 배포
- legal_effect_structures.schema: signal_manifest_transaction_id required 승격
- runtime_manifest 434→465항(전수 MISMATCH 0), 인계면 45→55행(C-7 폐쇄),
  stage_chain Part 4 구간 갱신, 조립본 동기화(파일만)
- 독립 검증 2회 왕복: 1차 FAIL(U4 3건·MINOR 2) 수정 후 재검증 PASS·결함 0
- 전략서·구축 요약서(§7.4 정정 및 SG-01 재봉인 레시피 포함) 수록

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-23 21:51:50 +09:00
co-authored by Claude Fable 5
parent 85fca071c0
commit e6f514be76
45 changed files with 12800 additions and 144 deletions
@@ -0,0 +1,86 @@
{
"schema_version": "stage1_s2_domain_gates.v1",
"domain_id": "E-00",
"legacy_gate_source": {
"path": "S_0_GPT/legacy/part4/deterministic_gates_contract.json",
"sha256": "2b1b35bf1f28ba8f48401fffc86cebf5addad53570b9f8ab159f32c801d39752"
},
"rules": [
{
"gate_id": "E00-G01",
"evaluation_mode": "deterministic",
"input_fields": [
"bo_seed_candidates",
"review_items",
"extensions.domain_payload.payload"
],
"condition": {
"any_review_code_present": [
"E00_CONSERVATION_FAILURE",
"E00_MEMBERSHIP_CONFLICT_REVIEW"
]
},
"emitted_codes": [
"E00_CONSERVATION_FAILURE",
"E00_MEMBERSHIP_CONFLICT_REVIEW"
],
"writer_effect": {
"status_when_triggered": "READY_WITH_REVIEW",
"writes_fact_ledger": true,
"final_drafting_block": true
},
"silent_repair_forbidden": true
},
{
"gate_id": "E00-G02",
"evaluation_mode": "deterministic",
"input_fields": [
"bo_seed_candidates",
"review_items",
"extensions.domain_payload.payload"
],
"condition": {
"any_review_code_present": [
"UNROUTED_ISSUE_REVIEW",
"E00_FALSE_UNROUTED_REVIEW"
]
},
"emitted_codes": [
"UNROUTED_ISSUE_REVIEW",
"E00_FALSE_UNROUTED_REVIEW"
],
"writer_effect": {
"status_when_triggered": "READY_WITH_REVIEW",
"writes_fact_ledger": true,
"final_drafting_block": false
},
"silent_repair_forbidden": true
},
{
"gate_id": "E00-G03",
"evaluation_mode": "deterministic",
"input_fields": [
"bo_seed_candidates",
"review_items",
"extensions.domain_payload.payload"
],
"condition": {
"any_review_code_present": [
"E00_SILENT_FALLBACK_FORBIDDEN"
]
},
"emitted_codes": [
"E00_SILENT_FALLBACK_FORBIDDEN"
],
"writer_effect": {
"status_when_triggered": "READY_WITH_REVIEW",
"writes_fact_ledger": true,
"final_drafting_block": true
},
"silent_repair_forbidden": true
}
],
"legacy_equivalence_projections": [],
"global_status_rule": "any review code yields READY_WITH_REVIEW; hard input or contract failure yields FAILED/BLOCKED",
"closure_status": "IMPLEMENTED_LOCAL_DEFINITION"
}
@@ -0,0 +1 @@
{"domain_id":"E-01","gates":[{"condition":"required element/evidence slot이 누락·부분충족","emitted_review_codes":["E01_REQUIRED_ELEMENT_GAP_REVIEW"],"gate_id":"E01-G01","implementation_type":"deterministic_predicate_spec","input_contracts":["domain_seed_output.v3","E-01.domain_extension.v1","E-01.effect_projection.v1","stage1_domain_signals.v2"],"row_effect":{"must_consider":true},"unknown_or_missing_policy":"fail_open_to_review","writer_effect":{"blocks_final_drafting":false,"status_on_fire":"READY_WITH_REVIEW","writes_fact_ledger":true}},{"condition":"당사자·객체·행위·효력·인과·권리귀속의 핵심 원천이 상충","emitted_review_codes":["E01_DECLARATION_IDENTITY_CONFLICT_REVIEW","E01_VALIDITY_OR_CANCELLATION_CONFLICT_REVIEW","E01_AGENCY_OR_RATIFICATION_CONFLICT_REVIEW"],"gate_id":"E01-G02","implementation_type":"deterministic_predicate_spec","input_contracts":["domain_seed_output.v3","E-01.domain_extension.v1","E-01.effect_projection.v1","stage1_domain_signals.v2"],"row_effect":{"must_consider":true},"unknown_or_missing_policy":"fail_open_to_review","writer_effect":{"blocks_final_drafting":false,"status_on_fire":"READY_WITH_REVIEW","writes_fact_ledger":true}},{"condition":"법률행위·침해·유통·권리행사 기준일 또는 적용법 version이 누락·상충","emitted_review_codes":["E01_LAW_VERSION_OR_PERIOD_INPUT_REVIEW"],"gate_id":"E01-G03","implementation_type":"deterministic_predicate_spec","input_contracts":["domain_seed_output.v3","E-01.domain_extension.v1","E-01.effect_projection.v1","stage1_domain_signals.v2"],"row_effect":{"must_consider":true},"unknown_or_missing_policy":"fail_open_to_review","writer_effect":{"blocks_final_drafting":true,"status_on_fire":"READY_WITH_REVIEW","writes_fact_ledger":true}},{"condition":"필요 특별법 profile·전문 leaf·민사/공법/제외 트랙 경계가 닫히지 않음","emitted_review_codes":["E01_SPECIAL_LAW_OR_TRACK_BOUNDARY_REVIEW","SPECIAL_LAW_PROFILE_REQUIRED_REVIEW","TRACK_BOUNDARY_REVIEW","PUBLIC_LAW_NEXUS_REVIEW"],"gate_id":"E01-G04","implementation_type":"deterministic_predicate_spec","input_contracts":["domain_seed_output.v3","E-01.domain_extension.v1","E-01.effect_projection.v1","stage1_domain_signals.v2"],"row_effect":{"must_consider":true},"unknown_or_missing_policy":"fail_open_to_review","writer_effect":{"blocks_final_drafting":true,"status_on_fire":"READY_WITH_REVIEW","writes_fact_ledger":true}},{"condition":"계산 operand가 partial 또는 blocked인데 산식·단일값·법정수치를 확정하려 함","emitted_review_codes":["E01_CALCULATION_OPERAND_PARTIAL_REVIEW","E01_CALCULATION_OPERAND_BLOCKED_REVIEW","MISSING_REQUIRED_OPERAND","CALCULATION_PREEMPTION_BLOCKED"],"gate_id":"E01-G05","implementation_type":"deterministic_predicate_spec","input_contracts":["domain_seed_output.v3","E-01.domain_extension.v1","E-01.effect_projection.v1","stage1_domain_signals.v2"],"row_effect":{"must_consider":true},"unknown_or_missing_policy":"fail_open_to_review","writer_effect":{"blocks_final_drafting":true,"status_on_fire":"READY_WITH_REVIEW","writes_fact_ledger":true}},{"condition":"meeting-only 진술을 증거로 승격하거나 최종 법률결론·청구금액·소장문구를 생성하려 함","emitted_review_codes":["E01_MEETING_ONLY_SUPPORT_REVIEW","FINAL_LEGAL_CONCLUSION_BLOCKED"],"gate_id":"E01-G06","implementation_type":"deterministic_predicate_spec","input_contracts":["domain_seed_output.v3","E-01.domain_extension.v1","E-01.effect_projection.v1","stage1_domain_signals.v2"],"row_effect":{"must_consider":true},"unknown_or_missing_policy":"fail_open_to_review","writer_effect":{"blocks_final_drafting":true,"status_on_fire":"READY_WITH_REVIEW","writes_fact_ledger":true}}],"global_rules":{"fact_ledger_write_continues_with_review":true,"final_legal_conclusion_forbidden":true,"meeting_only_evidence_promotion_forbidden":true,"missing_input_is_not_false":true,"review_count_may_not_decrease_without_resolution_ref":true,"runtime_case_name_lookup_forbidden":true,"statutory_numeric_constant_hardcoding_forbidden":true},"schema_version":"stage1_domain_gates.v1","status":"ACTIVE"}
@@ -0,0 +1,85 @@
{
"schema_version": "s2_domain_gates.v1",
"domain_id": "E-02",
"rules": [
{
"gate_id": "E02-G01",
"review_code": "E02_REQUIRED_ELEMENT_GAP_REVIEW",
"evaluation": "deterministic",
"condition": "관련 필수 요소·증거 상태가 E02_REQUIRED_ELEMENT_GAP_REVIEW 규칙을 충족",
"source_membership_required": true,
"on_true": {
"preserve_seed": true,
"writer_status": "READY_WITH_REVIEW",
"blocks_final_drafting": false
}
},
{
"gate_id": "E02-G02",
"review_code": "E02_AMOUNT_OR_DUE_DATE_CONFLICT",
"evaluation": "deterministic",
"condition": "관련 필수 요소·증거 상태가 E02_AMOUNT_OR_DUE_DATE_CONFLICT 규칙을 충족",
"source_membership_required": true,
"on_true": {
"preserve_seed": true,
"writer_status": "READY_WITH_REVIEW",
"blocks_final_drafting": false
}
},
{
"gate_id": "E02-G03",
"review_code": "E02_PAYMENT_STATUS_CONFLICT",
"evaluation": "deterministic",
"condition": "관련 필수 요소·증거 상태가 E02_PAYMENT_STATUS_CONFLICT 규칙을 충족",
"source_membership_required": true,
"on_true": {
"preserve_seed": true,
"writer_status": "READY_WITH_REVIEW",
"blocks_final_drafting": false
}
},
{
"gate_id": "E02-G04",
"review_code": "E02_SET_OFF_REVIEW",
"evaluation": "deterministic",
"condition": "관련 필수 요소·증거 상태가 E02_SET_OFF_REVIEW 규칙을 충족",
"source_membership_required": true,
"on_true": {
"preserve_seed": true,
"writer_status": "READY_WITH_REVIEW",
"blocks_final_drafting": false
}
},
{
"gate_id": "E02-G05",
"review_code": "E02_LIMITATION_REVIEW",
"evaluation": "deterministic",
"condition": "관련 필수 요소·증거 상태가 E02_LIMITATION_REVIEW 규칙을 충족",
"source_membership_required": true,
"on_true": {
"preserve_seed": true,
"writer_status": "READY_WITH_REVIEW",
"blocks_final_drafting": false
}
},
{
"gate_id": "E02-G06",
"review_code": "CALCULATION_PREEMPTION_BLOCKED",
"evaluation": "deterministic",
"condition": "관련 필수 요소·증거 상태가 CALCULATION_PREEMPTION_BLOCKED 규칙을 충족",
"source_membership_required": true,
"on_true": {
"preserve_seed": true,
"writer_status": "READY_WITH_REVIEW",
"blocks_final_drafting": true
}
}
],
"global_rules": {
"review_count_non_decrease": true,
"silent_drop_forbidden": true,
"unknown_source_rejected": true
},
"legacy_gate_projection": [],
"closure_status": "CLOSED"
}
@@ -0,0 +1,61 @@
{
"schema_version": "s2_domain_gates.v1",
"domain_id": "E-03",
"rules": [
{
"gate_id": "E03-G01",
"review_code": "E03_PARTY_LINK_GAP_REVIEW",
"evaluation": "deterministic",
"condition": "관련 필수 요소·증거 상태가 E03_PARTY_LINK_GAP_REVIEW 규칙을 충족",
"source_membership_required": true,
"on_true": {
"preserve_seed": true,
"writer_status": "READY_WITH_REVIEW",
"blocks_final_drafting": false
}
},
{
"gate_id": "E03-G02",
"review_code": "E03_SUCCESSION_SCOPE_CONFLICT",
"evaluation": "deterministic",
"condition": "관련 필수 요소·증거 상태가 E03_SUCCESSION_SCOPE_CONFLICT 규칙을 충족",
"source_membership_required": true,
"on_true": {
"preserve_seed": true,
"writer_status": "READY_WITH_REVIEW",
"blocks_final_drafting": false
}
},
{
"gate_id": "E03-G03",
"review_code": "E03_NOTICE_EFFECT_REVIEW",
"evaluation": "deterministic",
"condition": "관련 필수 요소·증거 상태가 E03_NOTICE_EFFECT_REVIEW 규칙을 충족",
"source_membership_required": true,
"on_true": {
"preserve_seed": true,
"writer_status": "READY_WITH_REVIEW",
"blocks_final_drafting": false
}
},
{
"gate_id": "E03-G04",
"review_code": "E03_MULTIPARTY_ALLOCATION_REVIEW",
"evaluation": "deterministic",
"condition": "관련 필수 요소·증거 상태가 E03_MULTIPARTY_ALLOCATION_REVIEW 규칙을 충족",
"source_membership_required": true,
"on_true": {
"preserve_seed": true,
"writer_status": "READY_WITH_REVIEW",
"blocks_final_drafting": false
}
}
],
"global_rules": {
"review_count_non_decrease": true,
"silent_drop_forbidden": true,
"unknown_source_rejected": true
},
"legacy_gate_projection": [],
"closure_status": "CLOSED"
}
@@ -0,0 +1,106 @@
{
"schema_version": "stage1_domain_gates.v1",
"domain_id": "E-04",
"status": "ACTIVE",
"legacy_contract_refs": [
"S_0_GPT/legacy/part4/deterministic_gates_contract.json",
"S_0_GPT/legacy/part4/structure_projection_contract.json"
],
"gates": [
{
"gate_id": "E04-G01",
"implementation_type": "deterministic_predicate_spec",
"input_contracts": [
"domain_seed_output.v3",
"E-04.domain_extension.v2",
"E-04.effect_projection.v1"
],
"condition": "필수 이익·손실·대응관계·법률상 원인 슬롯 중 하나라도 누락",
"emitted_review_codes": [
"E04_REQUIRED_ELEMENT_GAP_REVIEW"
],
"row_effect": {
"must_consider": true
},
"writer_effect": {
"status_on_fire": "READY_WITH_REVIEW",
"blocks_final_drafting": false,
"writes_fact_ledger": true
},
"unknown_or_missing_policy": "fail_open_to_review"
},
{
"gate_id": "E04-G02",
"implementation_type": "deterministic_predicate_spec",
"input_contracts": [
"domain_seed_output.v3",
"E-04.domain_extension.v2",
"E-04.effect_projection.v1"
],
"condition": "기간·면적·임료 또는 가치기준 후보가 상충",
"emitted_review_codes": [
"E04_USE_VALUE_CONFLICT"
],
"row_effect": {
"must_consider": true
},
"writer_effect": {
"status_on_fire": "READY_WITH_REVIEW",
"blocks_final_drafting": false,
"writes_fact_ledger": true
},
"unknown_or_missing_policy": "fail_open_to_review"
},
{
"gate_id": "E04-G03",
"implementation_type": "deterministic_predicate_spec",
"input_contracts": [
"domain_seed_output.v3",
"E-04.domain_extension.v2",
"E-04.effect_projection.v1"
],
"condition": "법률상 원인·공제·상계·불법원인급여 후보가 존재하나 확정할 수 없음",
"emitted_review_codes": [
"E04_CAUSE_OR_GAIN_CONFLICT",
"E04_DEFENSE_OR_OFFSET_REVIEW"
],
"row_effect": {
"must_consider": true
},
"writer_effect": {
"status_on_fire": "READY_WITH_REVIEW",
"blocks_final_drafting": false,
"writes_fact_ledger": true
},
"unknown_or_missing_policy": "fail_open_to_review"
},
{
"gate_id": "E04-G04",
"implementation_type": "deterministic_predicate_spec",
"input_contracts": [
"domain_seed_output.v3",
"E-04.domain_extension.v2",
"E-04.effect_projection.v1"
],
"condition": "Stage 1 사실 후보를 반환의무·액수의 최종 결론으로 변환하려 함",
"emitted_review_codes": [
"FINAL_LEGAL_CONCLUSION_BLOCKED"
],
"row_effect": {
"must_consider": true
},
"writer_effect": {
"status_on_fire": "READY_WITH_REVIEW",
"blocks_final_drafting": true,
"writes_fact_ledger": true
},
"unknown_or_missing_policy": "fail_open_to_review"
}
],
"global_rules": {
"review_count_may_not_decrease_without_resolution_ref": true,
"missing_input_is_not_false": true,
"final_legal_conclusion_forbidden": true,
"fact_ledger_write_continues_with_review": true
}
}
@@ -0,0 +1,163 @@
{
"schema_version": "stage1_domain_gates.v1",
"domain_id": "E-05",
"status": "ACTIVE",
"gates": [
{
"gate_id": "E05-G01",
"implementation_type": "deterministic_predicate_spec",
"input_contracts": [
"domain_seed_output.v3",
"E-05.domain_extension.v1",
"E-05.effect_projection.v1",
"stage1_domain_signals.v2"
],
"condition": "행위·귀책·침해이익·손해·인과관계의 required slot 중 하나 이상이 증거로 채워지지 않음",
"emitted_review_codes": [
"E05_REQUIRED_ELEMENT_GAP_REVIEW",
"E05_DAMAGE_SOURCE_GAP_REVIEW",
"E05_CAUSATION_SOURCE_GAP_REVIEW"
],
"row_effect": {
"must_consider": true
},
"writer_effect": {
"status_on_fire": "READY_WITH_REVIEW",
"blocks_final_drafting": false,
"writes_fact_ledger": true
},
"unknown_or_missing_policy": "fail_open_to_review"
},
{
"gate_id": "E05-G02",
"implementation_type": "deterministic_predicate_spec",
"input_contracts": [
"domain_seed_output.v3",
"E-05.domain_extension.v1",
"E-05.effect_projection.v1",
"stage1_domain_signals.v2"
],
"condition": "행위자·귀책·손해기전·인과관계 또는 손해범위에 상충 원천이 존재함",
"emitted_review_codes": [
"E05_CONDUCT_OR_FAULT_CONFLICT_REVIEW",
"E05_CAUSATION_OR_DAMAGE_CONFLICT_REVIEW"
],
"row_effect": {
"must_consider": true
},
"writer_effect": {
"status_on_fire": "READY_WITH_REVIEW",
"blocks_final_drafting": false,
"writes_fact_ledger": true
},
"unknown_or_missing_policy": "fail_open_to_review"
},
{
"gate_id": "E05-G03",
"implementation_type": "deterministic_predicate_spec",
"input_contracts": [
"domain_seed_output.v3",
"E-05.domain_extension.v1",
"E-05.effect_projection.v1",
"stage1_domain_signals.v2"
],
"condition": "침해·손해발생·인지·권리행사 기준일이 누락되거나 상충함",
"emitted_review_codes": [
"E05_LIMITATION_INPUT_GAP_REVIEW",
"E05_LIMITATION_DATE_CONFLICT_REVIEW",
"E05_LIMITATION_REVIEW"
],
"row_effect": {
"must_consider": true
},
"writer_effect": {
"status_on_fire": "READY_WITH_REVIEW",
"blocks_final_drafting": true,
"writes_fact_ledger": true
},
"unknown_or_missing_policy": "fail_open_to_review"
},
{
"gate_id": "E05-G04",
"implementation_type": "deterministic_predicate_spec",
"input_contracts": [
"domain_seed_output.v3",
"E-05.domain_extension.v1",
"E-05.effect_projection.v1",
"stage1_domain_signals.v2"
],
"condition": "전문사건 또는 공법·트랙 경계 단서가 있으나 필요한 profile·전문 leaf가 부착되지 않음",
"emitted_review_codes": [
"SPECIAL_LAW_PROFILE_REQUIRED_REVIEW",
"PUBLIC_LAW_NEXUS_REVIEW",
"TRACK_BOUNDARY_REVIEW"
],
"row_effect": {
"must_consider": true
},
"writer_effect": {
"status_on_fire": "READY_WITH_REVIEW",
"blocks_final_drafting": true,
"writes_fact_ledger": true
},
"unknown_or_missing_policy": "fail_open_to_review"
},
{
"gate_id": "E05-G05",
"implementation_type": "deterministic_predicate_spec",
"input_contracts": [
"domain_seed_output.v3",
"E-05.domain_extension.v1",
"E-05.effect_projection.v1",
"stage1_domain_signals.v2"
],
"condition": "CE-01·CE-03·CE-05 operand가 partial 또는 blocked인데 산식·단일값을 만들려 함",
"emitted_review_codes": [
"E05_CALCULATION_OPERAND_PARTIAL_REVIEW",
"E05_CALCULATION_OPERAND_BLOCKED_REVIEW",
"CALCULATION_PREEMPTION_BLOCKED"
],
"row_effect": {
"must_consider": true
},
"writer_effect": {
"status_on_fire": "READY_WITH_REVIEW",
"blocks_final_drafting": true,
"writes_fact_ledger": true
},
"unknown_or_missing_policy": "fail_open_to_review"
},
{
"gate_id": "E05-G06",
"implementation_type": "deterministic_predicate_spec",
"input_contracts": [
"domain_seed_output.v3",
"E-05.domain_extension.v1",
"E-05.effect_projection.v1",
"stage1_domain_signals.v2"
],
"condition": "meeting-only 진술을 증거로 승격하거나 책임·손해·시효의 최종 결론을 출력하려 함",
"emitted_review_codes": [
"E05_MEETING_ONLY_SUPPORT_REVIEW",
"FINAL_LEGAL_CONCLUSION_BLOCKED"
],
"row_effect": {
"must_consider": true
},
"writer_effect": {
"status_on_fire": "READY_WITH_REVIEW",
"blocks_final_drafting": true,
"writes_fact_ledger": true
},
"unknown_or_missing_policy": "fail_open_to_review"
}
],
"global_rules": {
"review_count_may_not_decrease_without_resolution_ref": true,
"missing_input_is_not_false": true,
"final_legal_conclusion_forbidden": true,
"meeting_only_evidence_promotion_forbidden": true,
"fact_ledger_write_continues_with_review": true,
"statutory_numeric_constant_hardcoding_forbidden": true
}
}
@@ -0,0 +1 @@
{"domain_id":"E-06","gates":[{"condition":"required element/evidence slot이 누락·부분충족","emitted_review_codes":["E06_REQUIRED_ELEMENT_GAP_REVIEW"],"gate_id":"E06-G01","implementation_type":"deterministic_predicate_spec","input_contracts":["domain_seed_output.v3","E-06.domain_extension.v1","E-06.effect_projection.v1","stage1_domain_signals.v2"],"row_effect":{"must_consider":true},"unknown_or_missing_policy":"fail_open_to_review","writer_effect":{"blocks_final_drafting":false,"status_on_fire":"READY_WITH_REVIEW","writes_fact_ledger":true}},{"condition":"당사자·객체·행위·효력·인과·권리귀속의 핵심 원천이 상충","emitted_review_codes":["E06_PROFESSIONAL_STANDARD_OR_BREACH_CONFLICT_REVIEW","E06_CAUSATION_OR_DAMAGE_CONFLICT_REVIEW","E06_DISCLOSURE_OR_PRODUCT_DEFECT_CONFLICT_REVIEW"],"gate_id":"E06-G02","implementation_type":"deterministic_predicate_spec","input_contracts":["domain_seed_output.v3","E-06.domain_extension.v1","E-06.effect_projection.v1","stage1_domain_signals.v2"],"row_effect":{"must_consider":true},"unknown_or_missing_policy":"fail_open_to_review","writer_effect":{"blocks_final_drafting":false,"status_on_fire":"READY_WITH_REVIEW","writes_fact_ledger":true}},{"condition":"법률행위·침해·유통·권리행사 기준일 또는 적용법 version이 누락·상충","emitted_review_codes":["E06_LAW_VERSION_OR_PERIOD_INPUT_REVIEW"],"gate_id":"E06-G03","implementation_type":"deterministic_predicate_spec","input_contracts":["domain_seed_output.v3","E-06.domain_extension.v1","E-06.effect_projection.v1","stage1_domain_signals.v2"],"row_effect":{"must_consider":true},"unknown_or_missing_policy":"fail_open_to_review","writer_effect":{"blocks_final_drafting":true,"status_on_fire":"READY_WITH_REVIEW","writes_fact_ledger":true}},{"condition":"필요 특별법 profile·전문 leaf·민사/공법/제외 트랙 경계가 닫히지 않음","emitted_review_codes":["E06_SPECIAL_LAW_OR_TRACK_BOUNDARY_REVIEW","SPECIAL_LAW_PROFILE_REQUIRED_REVIEW","TRACK_BOUNDARY_REVIEW","PUBLIC_LAW_NEXUS_REVIEW"],"gate_id":"E06-G04","implementation_type":"deterministic_predicate_spec","input_contracts":["domain_seed_output.v3","E-06.domain_extension.v1","E-06.effect_projection.v1","stage1_domain_signals.v2"],"row_effect":{"must_consider":true},"unknown_or_missing_policy":"fail_open_to_review","writer_effect":{"blocks_final_drafting":true,"status_on_fire":"READY_WITH_REVIEW","writes_fact_ledger":true}},{"condition":"계산 operand가 partial 또는 blocked인데 산식·단일값·법정수치를 확정하려 함","emitted_review_codes":["E06_CALCULATION_OPERAND_PARTIAL_REVIEW","E06_CALCULATION_OPERAND_BLOCKED_REVIEW","MISSING_REQUIRED_OPERAND","CALCULATION_PREEMPTION_BLOCKED"],"gate_id":"E06-G05","implementation_type":"deterministic_predicate_spec","input_contracts":["domain_seed_output.v3","E-06.domain_extension.v1","E-06.effect_projection.v1","stage1_domain_signals.v2"],"row_effect":{"must_consider":true},"unknown_or_missing_policy":"fail_open_to_review","writer_effect":{"blocks_final_drafting":true,"status_on_fire":"READY_WITH_REVIEW","writes_fact_ledger":true}},{"condition":"meeting-only 진술을 증거로 승격하거나 최종 법률결론·청구금액·소장문구를 생성하려 함","emitted_review_codes":["E06_MEETING_ONLY_SUPPORT_REVIEW","FINAL_LEGAL_CONCLUSION_BLOCKED"],"gate_id":"E06-G06","implementation_type":"deterministic_predicate_spec","input_contracts":["domain_seed_output.v3","E-06.domain_extension.v1","E-06.effect_projection.v1","stage1_domain_signals.v2"],"row_effect":{"must_consider":true},"unknown_or_missing_policy":"fail_open_to_review","writer_effect":{"blocks_final_drafting":true,"status_on_fire":"READY_WITH_REVIEW","writes_fact_ledger":true}}],"global_rules":{"fact_ledger_write_continues_with_review":true,"final_legal_conclusion_forbidden":true,"meeting_only_evidence_promotion_forbidden":true,"missing_input_is_not_false":true,"review_count_may_not_decrease_without_resolution_ref":true,"runtime_case_name_lookup_forbidden":true,"statutory_numeric_constant_hardcoding_forbidden":true},"schema_version":"stage1_domain_gates.v1","status":"ACTIVE"}
@@ -0,0 +1,146 @@
{
"schema_version": "s3_domain_gates.v1",
"domain_id": "E-07",
"rules": [
{
"gate_id": "E07-G01",
"review_code": "E07_REQUIRED_ELEMENT_GAP_REVIEW",
"evaluation": "deterministic",
"condition": "증거슬롯·source membership·routing 또는 계산 operand가 E07_REQUIRED_ELEMENT_GAP_REVIEW 조건을 충족",
"source_membership_required": true,
"on_true": {
"preserve_seed": true,
"writer_status": "READY_WITH_REVIEW",
"blocks_final_drafting": false
}
},
{
"gate_id": "E07-G02",
"review_code": "E07_SCOPE_AMOUNT_CONFLICT",
"evaluation": "deterministic",
"condition": "증거슬롯·source membership·routing 또는 계산 operand가 E07_SCOPE_AMOUNT_CONFLICT 조건을 충족",
"source_membership_required": true,
"on_true": {
"preserve_seed": true,
"writer_status": "READY_WITH_REVIEW",
"blocks_final_drafting": false
}
},
{
"gate_id": "E07-G03",
"review_code": "E07_PROGRESS_COMPLETION_CONFLICT",
"evaluation": "deterministic",
"condition": "증거슬롯·source membership·routing 또는 계산 operand가 E07_PROGRESS_COMPLETION_CONFLICT 조건을 충족",
"source_membership_required": true,
"on_true": {
"preserve_seed": true,
"writer_status": "READY_WITH_REVIEW",
"blocks_final_drafting": false
}
},
{
"gate_id": "E07-G04",
"review_code": "E07_DEFECT_CAUSATION_COST_CONFLICT",
"evaluation": "deterministic",
"condition": "증거슬롯·source membership·routing 또는 계산 operand가 E07_DEFECT_CAUSATION_COST_CONFLICT 조건을 충족",
"source_membership_required": true,
"on_true": {
"preserve_seed": true,
"writer_status": "READY_WITH_REVIEW",
"blocks_final_drafting": false
}
},
{
"gate_id": "E07-G05",
"review_code": "E07_ADDITIONAL_WORK_AUTHORIZATION_REVIEW",
"evaluation": "deterministic",
"condition": "증거슬롯·source membership·routing 또는 계산 operand가 E07_ADDITIONAL_WORK_AUTHORIZATION_REVIEW 조건을 충족",
"source_membership_required": true,
"on_true": {
"preserve_seed": true,
"writer_status": "READY_WITH_REVIEW",
"blocks_final_drafting": false
}
},
{
"gate_id": "E07-G06",
"review_code": "E07_BOUNDARY_OVERLAP_REVIEW",
"evaluation": "deterministic",
"condition": "증거슬롯·source membership·routing 또는 계산 operand가 E07_BOUNDARY_OVERLAP_REVIEW 조건을 충족",
"source_membership_required": true,
"on_true": {
"preserve_seed": true,
"writer_status": "READY_WITH_REVIEW",
"blocks_final_drafting": false
}
},
{
"gate_id": "E07-G07",
"review_code": "E07_MEETING_ONLY_MONITOR_REVIEW",
"evaluation": "deterministic",
"condition": "증거슬롯·source membership·routing 또는 계산 operand가 E07_MEETING_ONLY_MONITOR_REVIEW 조건을 충족",
"source_membership_required": true,
"on_true": {
"preserve_seed": true,
"writer_status": "READY_WITH_REVIEW",
"blocks_final_drafting": false
}
},
{
"gate_id": "E07-G08",
"review_code": "E07_SPECIAL_LAW_PROFILE_REQUIRED_REVIEW",
"evaluation": "deterministic",
"condition": "증거슬롯·source membership·routing 또는 계산 operand가 E07_SPECIAL_LAW_PROFILE_REQUIRED_REVIEW 조건을 충족",
"source_membership_required": true,
"on_true": {
"preserve_seed": true,
"writer_status": "READY_WITH_REVIEW",
"blocks_final_drafting": false
}
},
{
"gate_id": "E07-G09",
"review_code": "E07_CALCULATION_PARTIAL_REVIEW",
"evaluation": "deterministic",
"condition": "증거슬롯·source membership·routing 또는 계산 operand가 E07_CALCULATION_PARTIAL_REVIEW 조건을 충족",
"source_membership_required": true,
"on_true": {
"preserve_seed": true,
"writer_status": "READY_WITH_REVIEW",
"blocks_final_drafting": false
}
},
{
"gate_id": "E07-G10",
"review_code": "E07_SOURCE_MEMBERSHIP_REVIEW",
"evaluation": "deterministic",
"condition": "증거슬롯·source membership·routing 또는 계산 operand가 E07_SOURCE_MEMBERSHIP_REVIEW 조건을 충족",
"source_membership_required": true,
"on_true": {
"preserve_seed": true,
"writer_status": "READY_WITH_REVIEW",
"blocks_final_drafting": false
}
},
{
"gate_id": "E07-G11",
"review_code": "CALCULATION_PREEMPTION_BLOCKED",
"evaluation": "deterministic",
"condition": "증거슬롯·source membership·routing 또는 계산 operand가 CALCULATION_PREEMPTION_BLOCKED 조건을 충족",
"source_membership_required": true,
"on_true": {
"preserve_seed": true,
"writer_status": "BLOCKED_PENDING_OPERANDS",
"blocks_final_drafting": true
}
}
],
"global_rules": {
"review_count_non_decrease": true,
"silent_drop_forbidden": true,
"unknown_source_rejected": true,
"meeting_only_cannot_promote_to_active": true,
"calculation_formula_hardcoding_forbidden": true
},
"closure_status": "CLOSED"
}
@@ -0,0 +1,146 @@
{
"schema_version": "s3_domain_gates.v1",
"domain_id": "E-08",
"rules": [
{
"gate_id": "E08-G01",
"review_code": "E08_REQUIRED_ELEMENT_GAP_REVIEW",
"evaluation": "deterministic",
"condition": "증거슬롯·source membership·routing 또는 계산 operand가 E08_REQUIRED_ELEMENT_GAP_REVIEW 조건을 충족",
"source_membership_required": true,
"on_true": {
"preserve_seed": true,
"writer_status": "READY_WITH_REVIEW",
"blocks_final_drafting": false
}
},
{
"gate_id": "E08-G02",
"review_code": "E08_TERM_RENEWAL_CONFLICT",
"evaluation": "deterministic",
"condition": "증거슬롯·source membership·routing 또는 계산 operand가 E08_TERM_RENEWAL_CONFLICT 조건을 충족",
"source_membership_required": true,
"on_true": {
"preserve_seed": true,
"writer_status": "READY_WITH_REVIEW",
"blocks_final_drafting": false
}
},
{
"gate_id": "E08-G03",
"review_code": "E08_DEPOSIT_DEDUCTION_CONFLICT",
"evaluation": "deterministic",
"condition": "증거슬롯·source membership·routing 또는 계산 operand가 E08_DEPOSIT_DEDUCTION_CONFLICT 조건을 충족",
"source_membership_required": true,
"on_true": {
"preserve_seed": true,
"writer_status": "READY_WITH_REVIEW",
"blocks_final_drafting": false
}
},
{
"gate_id": "E08-G04",
"review_code": "E08_POSSESSION_DELIVERY_CONFLICT",
"evaluation": "deterministic",
"condition": "증거슬롯·source membership·routing 또는 계산 operand가 E08_POSSESSION_DELIVERY_CONFLICT 조건을 충족",
"source_membership_required": true,
"on_true": {
"preserve_seed": true,
"writer_status": "READY_WITH_REVIEW",
"blocks_final_drafting": false
}
},
{
"gate_id": "E08-G05",
"review_code": "E08_CONCURRENT_PERFORMANCE_REVIEW",
"evaluation": "deterministic",
"condition": "증거슬롯·source membership·routing 또는 계산 operand가 E08_CONCURRENT_PERFORMANCE_REVIEW 조건을 충족",
"source_membership_required": true,
"on_true": {
"preserve_seed": true,
"writer_status": "READY_WITH_REVIEW",
"blocks_final_drafting": false
}
},
{
"gate_id": "E08-G06",
"review_code": "E08_SPECIAL_LAW_PROFILE_REQUIRED_REVIEW",
"evaluation": "deterministic",
"condition": "증거슬롯·source membership·routing 또는 계산 operand가 E08_SPECIAL_LAW_PROFILE_REQUIRED_REVIEW 조건을 충족",
"source_membership_required": true,
"on_true": {
"preserve_seed": true,
"writer_status": "READY_WITH_REVIEW",
"blocks_final_drafting": false
}
},
{
"gate_id": "E08-G07",
"review_code": "E08_BOUNDARY_OVERLAP_REVIEW",
"evaluation": "deterministic",
"condition": "증거슬롯·source membership·routing 또는 계산 operand가 E08_BOUNDARY_OVERLAP_REVIEW 조건을 충족",
"source_membership_required": true,
"on_true": {
"preserve_seed": true,
"writer_status": "READY_WITH_REVIEW",
"blocks_final_drafting": false
}
},
{
"gate_id": "E08-G08",
"review_code": "E08_MEETING_ONLY_MONITOR_REVIEW",
"evaluation": "deterministic",
"condition": "증거슬롯·source membership·routing 또는 계산 operand가 E08_MEETING_ONLY_MONITOR_REVIEW 조건을 충족",
"source_membership_required": true,
"on_true": {
"preserve_seed": true,
"writer_status": "READY_WITH_REVIEW",
"blocks_final_drafting": false
}
},
{
"gate_id": "E08-G09",
"review_code": "E08_CALCULATION_PARTIAL_REVIEW",
"evaluation": "deterministic",
"condition": "증거슬롯·source membership·routing 또는 계산 operand가 E08_CALCULATION_PARTIAL_REVIEW 조건을 충족",
"source_membership_required": true,
"on_true": {
"preserve_seed": true,
"writer_status": "READY_WITH_REVIEW",
"blocks_final_drafting": false
}
},
{
"gate_id": "E08-G10",
"review_code": "E08_SOURCE_MEMBERSHIP_REVIEW",
"evaluation": "deterministic",
"condition": "증거슬롯·source membership·routing 또는 계산 operand가 E08_SOURCE_MEMBERSHIP_REVIEW 조건을 충족",
"source_membership_required": true,
"on_true": {
"preserve_seed": true,
"writer_status": "READY_WITH_REVIEW",
"blocks_final_drafting": false
}
},
{
"gate_id": "E08-G11",
"review_code": "CALCULATION_PREEMPTION_BLOCKED",
"evaluation": "deterministic",
"condition": "증거슬롯·source membership·routing 또는 계산 operand가 CALCULATION_PREEMPTION_BLOCKED 조건을 충족",
"source_membership_required": true,
"on_true": {
"preserve_seed": true,
"writer_status": "BLOCKED_PENDING_OPERANDS",
"blocks_final_drafting": true
}
}
],
"global_rules": {
"review_count_non_decrease": true,
"silent_drop_forbidden": true,
"unknown_source_rejected": true,
"meeting_only_cannot_promote_to_active": true,
"calculation_formula_hardcoding_forbidden": true
},
"closure_status": "CLOSED"
}
@@ -0,0 +1,61 @@
{
"schema_version": "s2_domain_gates.v1",
"domain_id": "E-09",
"rules": [
{
"gate_id": "E09-G01",
"review_code": "E09_CAUSE_REGISTRY_MISMATCH_REVIEW",
"evaluation": "deterministic",
"condition": "관련 필수 요소·증거 상태가 E09_CAUSE_REGISTRY_MISMATCH_REVIEW 규칙을 충족",
"source_membership_required": true,
"on_true": {
"preserve_seed": true,
"writer_status": "READY_WITH_REVIEW",
"blocks_final_drafting": false
}
},
{
"gate_id": "E09-G02",
"review_code": "E09_REGISTRY_SEQUENCE_CONFLICT",
"evaluation": "deterministic",
"condition": "관련 필수 요소·증거 상태가 E09_REGISTRY_SEQUENCE_CONFLICT 규칙을 충족",
"source_membership_required": true,
"on_true": {
"preserve_seed": true,
"writer_status": "READY_WITH_REVIEW",
"blocks_final_drafting": false
}
},
{
"gate_id": "E09-G03",
"review_code": "E09_THIRD_PARTY_CONSENT_REVIEW",
"evaluation": "deterministic",
"condition": "관련 필수 요소·증거 상태가 E09_THIRD_PARTY_CONSENT_REVIEW 규칙을 충족",
"source_membership_required": true,
"on_true": {
"preserve_seed": true,
"writer_status": "READY_WITH_REVIEW",
"blocks_final_drafting": false
}
},
{
"gate_id": "E09-G04",
"review_code": "E09_FRACTION_UNRESOLVED_REVIEW",
"evaluation": "deterministic",
"condition": "관련 필수 요소·증거 상태가 E09_FRACTION_UNRESOLVED_REVIEW 규칙을 충족",
"source_membership_required": true,
"on_true": {
"preserve_seed": true,
"writer_status": "READY_WITH_REVIEW",
"blocks_final_drafting": false
}
}
],
"global_rules": {
"review_count_non_decrease": true,
"silent_drop_forbidden": true,
"unknown_source_rejected": true
},
"legacy_gate_projection": [],
"closure_status": "CLOSED"
}
@@ -0,0 +1,88 @@
{
"schema_version": "s2_domain_gates.v1",
"domain_id": "E-10",
"rules": [
{
"gate_id": "E10-G01",
"review_code": "E10_SECURED_BALANCE_CONFLICT",
"evaluation": "deterministic",
"condition": "관련 필수 요소·증거 상태가 E10_SECURED_BALANCE_CONFLICT 규칙을 충족",
"source_membership_required": true,
"on_true": {
"preserve_seed": true,
"writer_status": "READY_WITH_REVIEW",
"blocks_final_drafting": false
}
},
{
"gate_id": "E10-G02",
"review_code": "E10_REGISTRY_SEQUENCE_CONFLICT",
"evaluation": "deterministic",
"condition": "관련 필수 요소·증거 상태가 E10_REGISTRY_SEQUENCE_CONFLICT 규칙을 충족",
"source_membership_required": true,
"on_true": {
"preserve_seed": true,
"writer_status": "READY_WITH_REVIEW",
"blocks_final_drafting": false
}
},
{
"gate_id": "E10-G03",
"review_code": "E10_AUCTION_DISTRIBUTION_REVIEW",
"evaluation": "deterministic",
"condition": "관련 필수 요소·증거 상태가 E10_AUCTION_DISTRIBUTION_REVIEW 규칙을 충족",
"source_membership_required": true,
"on_true": {
"preserve_seed": true,
"writer_status": "READY_WITH_REVIEW",
"blocks_final_drafting": false
}
},
{
"gate_id": "E10-G04",
"review_code": "E10_LIEN_CAUSE_POSSESSION_REVIEW",
"evaluation": "deterministic",
"condition": "관련 필수 요소·증거 상태가 E10_LIEN_CAUSE_POSSESSION_REVIEW 규칙을 충족",
"source_membership_required": true,
"on_true": {
"preserve_seed": true,
"writer_status": "READY_WITH_REVIEW",
"blocks_final_drafting": false
}
},
{
"gate_id": "E10-G05",
"review_code": "POSSESSION_CAUSE_CONFLICT",
"evaluation": "deterministic",
"condition": "관련 필수 요소·증거 상태가 POSSESSION_CAUSE_CONFLICT 규칙을 충족",
"source_membership_required": true,
"on_true": {
"preserve_seed": true,
"writer_status": "READY_WITH_REVIEW",
"blocks_final_drafting": true
}
},
{
"gate_id": "E10-G06",
"review_code": "LIEN_ASSERTED_AFTER_EXTINCTION_NOTICE_REVIEW",
"evaluation": "deterministic",
"condition": "관련 필수 요소·증거 상태가 LIEN_ASSERTED_AFTER_EXTINCTION_NOTICE_REVIEW 규칙을 충족",
"source_membership_required": true,
"on_true": {
"preserve_seed": true,
"writer_status": "READY_WITH_REVIEW",
"blocks_final_drafting": false
}
}
],
"global_rules": {
"review_count_non_decrease": true,
"silent_drop_forbidden": true,
"unknown_source_rejected": true
},
"legacy_gate_projection": [
"LEGACY-GATE-01",
"LEGACY-GATE-03"
],
"closure_status": "CLOSED"
}
@@ -0,0 +1,107 @@
{
"schema_version": "stage1_domain_gates.v1",
"domain_id": "E-11",
"status": "ACTIVE",
"legacy_contract_refs": [
"S_0_GPT/legacy/part4/deterministic_gates_contract.json",
"S_0_GPT/legacy/part4/structure_projection_contract.json"
],
"gates": [
{
"gate_id": "E11-G01",
"implementation_type": "deterministic_predicate_spec",
"input_contracts": [
"domain_seed_output.v3",
"E-11.domain_extension.v2",
"E-11.effect_projection.v1"
],
"condition": "동일 asset_ref와 중첩 기간에 양립 불가능한 점유원인 쌍이 존재",
"emitted_review_codes": [
"POSSESSION_CAUSE_CONFLICT",
"LEGAL_CAUSE_STATE_UNKNOWN_REVIEW"
],
"row_effect": {
"must_consider": true
},
"writer_effect": {
"status_on_fire": "READY_WITH_REVIEW",
"blocks_final_drafting": false,
"writes_fact_ledger": true
},
"unknown_or_missing_policy": "fail_open_to_review"
},
{
"gate_id": "E11-G02",
"implementation_type": "deterministic_predicate_spec",
"input_contracts": [
"domain_seed_output.v3",
"E-11.domain_extension.v2",
"E-11.effect_projection.v1"
],
"condition": "인도·철거·방해제거 대상의 표시·범위·수량이 서로 충돌",
"emitted_review_codes": [
"E11_OBJECT_OR_SCOPE_CONFLICT",
"E11_ASSET_BINDING_REVIEW"
],
"row_effect": {
"must_consider": true
},
"writer_effect": {
"status_on_fire": "READY_WITH_REVIEW",
"blocks_final_drafting": true,
"writes_fact_ledger": true
},
"unknown_or_missing_policy": "fail_open_to_review"
},
{
"gate_id": "E11-G03",
"implementation_type": "deterministic_predicate_spec",
"input_contracts": [
"domain_seed_output.v3",
"E-11.domain_extension.v2",
"E-11.effect_projection.v1"
],
"condition": "권리관계 또는 현재 점유 상태의 필수 슬롯이 누락",
"emitted_review_codes": [
"E11_REQUIRED_ELEMENT_GAP_REVIEW"
],
"row_effect": {
"must_consider": true
},
"writer_effect": {
"status_on_fire": "READY_WITH_REVIEW",
"blocks_final_drafting": false,
"writes_fact_ledger": true
},
"unknown_or_missing_policy": "fail_open_to_review"
},
{
"gate_id": "E11-G04",
"implementation_type": "deterministic_predicate_spec",
"input_contracts": [
"domain_seed_output.v3",
"E-11.domain_extension.v2",
"E-11.effect_projection.v1"
],
"condition": "점유 권원 또는 반환·방해배제 의무를 Stage 1에서 확정하려 함",
"emitted_review_codes": [
"FINAL_LEGAL_CONCLUSION_BLOCKED"
],
"row_effect": {
"must_consider": true
},
"writer_effect": {
"status_on_fire": "READY_WITH_REVIEW",
"blocks_final_drafting": true,
"writes_fact_ledger": true
},
"unknown_or_missing_policy": "fail_open_to_review"
}
],
"global_rules": {
"review_count_may_not_decrease_without_resolution_ref": true,
"missing_input_is_not_false": true,
"final_legal_conclusion_forbidden": true,
"fact_ledger_write_continues_with_review": true
}
}
@@ -0,0 +1 @@
{"domain_id":"E-12","gates":[{"condition":"required element/evidence slot이 누락·부분충족","emitted_review_codes":["E12_REQUIRED_ELEMENT_GAP_REVIEW"],"gate_id":"E12-G01","implementation_type":"deterministic_predicate_spec","input_contracts":["domain_seed_output.v3","E-12.domain_extension.v1","E-12.effect_projection.v1","stage1_domain_signals.v2"],"row_effect":{"must_consider":true},"unknown_or_missing_policy":"fail_open_to_review","writer_effect":{"blocks_final_drafting":false,"status_on_fire":"READY_WITH_REVIEW","writes_fact_ledger":true}},{"condition":"당사자·객체·행위·효력·인과·권리귀속의 핵심 원천이 상충","emitted_review_codes":["E12_TITLE_SHARE_OR_OBJECT_CONFLICT_REVIEW","E12_GOVERNANCE_OR_RESOLUTION_CONFLICT_REVIEW","E12_BOUNDARY_OR_PARTITION_CONFLICT_REVIEW"],"gate_id":"E12-G02","implementation_type":"deterministic_predicate_spec","input_contracts":["domain_seed_output.v3","E-12.domain_extension.v1","E-12.effect_projection.v1","stage1_domain_signals.v2"],"row_effect":{"must_consider":true},"unknown_or_missing_policy":"fail_open_to_review","writer_effect":{"blocks_final_drafting":false,"status_on_fire":"READY_WITH_REVIEW","writes_fact_ledger":true}},{"condition":"법률행위·침해·유통·권리행사 기준일 또는 적용법 version이 누락·상충","emitted_review_codes":["E12_LAW_VERSION_OR_PERIOD_INPUT_REVIEW"],"gate_id":"E12-G03","implementation_type":"deterministic_predicate_spec","input_contracts":["domain_seed_output.v3","E-12.domain_extension.v1","E-12.effect_projection.v1","stage1_domain_signals.v2"],"row_effect":{"must_consider":true},"unknown_or_missing_policy":"fail_open_to_review","writer_effect":{"blocks_final_drafting":true,"status_on_fire":"READY_WITH_REVIEW","writes_fact_ledger":true}},{"condition":"필요 특별법 profile·전문 leaf·민사/공법/제외 트랙 경계가 닫히지 않음","emitted_review_codes":["E12_SPECIAL_LAW_OR_TRACK_BOUNDARY_REVIEW","SPECIAL_LAW_PROFILE_REQUIRED_REVIEW","TRACK_BOUNDARY_REVIEW","PUBLIC_LAW_NEXUS_REVIEW"],"gate_id":"E12-G04","implementation_type":"deterministic_predicate_spec","input_contracts":["domain_seed_output.v3","E-12.domain_extension.v1","E-12.effect_projection.v1","stage1_domain_signals.v2"],"row_effect":{"must_consider":true},"unknown_or_missing_policy":"fail_open_to_review","writer_effect":{"blocks_final_drafting":true,"status_on_fire":"READY_WITH_REVIEW","writes_fact_ledger":true}},{"condition":"계산 operand가 partial 또는 blocked인데 산식·단일값·법정수치를 확정하려 함","emitted_review_codes":["E12_CALCULATION_OPERAND_PARTIAL_REVIEW","E12_CALCULATION_OPERAND_BLOCKED_REVIEW","MISSING_REQUIRED_OPERAND","CALCULATION_PREEMPTION_BLOCKED"],"gate_id":"E12-G05","implementation_type":"deterministic_predicate_spec","input_contracts":["domain_seed_output.v3","E-12.domain_extension.v1","E-12.effect_projection.v1","stage1_domain_signals.v2"],"row_effect":{"must_consider":true},"unknown_or_missing_policy":"fail_open_to_review","writer_effect":{"blocks_final_drafting":true,"status_on_fire":"READY_WITH_REVIEW","writes_fact_ledger":true}},{"condition":"meeting-only 진술을 증거로 승격하거나 최종 법률결론·청구금액·소장문구를 생성하려 함","emitted_review_codes":["E12_MEETING_ONLY_SUPPORT_REVIEW","FINAL_LEGAL_CONCLUSION_BLOCKED"],"gate_id":"E12-G06","implementation_type":"deterministic_predicate_spec","input_contracts":["domain_seed_output.v3","E-12.domain_extension.v1","E-12.effect_projection.v1","stage1_domain_signals.v2"],"row_effect":{"must_consider":true},"unknown_or_missing_policy":"fail_open_to_review","writer_effect":{"blocks_final_drafting":true,"status_on_fire":"READY_WITH_REVIEW","writes_fact_ledger":true}}],"global_rules":{"fact_ledger_write_continues_with_review":true,"final_legal_conclusion_forbidden":true,"meeting_only_evidence_promotion_forbidden":true,"missing_input_is_not_false":true,"review_count_may_not_decrease_without_resolution_ref":true,"runtime_case_name_lookup_forbidden":true,"statutory_numeric_constant_hardcoding_forbidden":true},"schema_version":"stage1_domain_gates.v1","status":"ACTIVE"}
@@ -0,0 +1,130 @@
{
"schema_version": "stage1_domain_gates.v1",
"domain_id": "E-13",
"status": "ACTIVE",
"legacy_contract_refs": [
"S_0_GPT/legacy/part4/deterministic_gates_contract.json",
"S_0_GPT/legacy/part4/structure_projection_contract.json"
],
"gates": [
{
"gate_id": "E13-G01",
"implementation_type": "deterministic_predicate_spec",
"input_contracts": [
"domain_seed_output.v3",
"E-13.domain_extension.v2",
"E-13.effect_projection.v1"
],
"condition": "피보전채권·채무자 행위·책임재산 핵심 슬롯 누락",
"emitted_review_codes": [
"E13_REQUIRED_ELEMENT_GAP_REVIEW",
"E13_PRESERVED_CLAIM_GAP_REVIEW",
"E13_BALANCE_SHEET_INCOMPLETE_REVIEW"
],
"row_effect": {
"must_consider": true
},
"writer_effect": {
"status_on_fire": "READY_WITH_REVIEW",
"blocks_final_drafting": false,
"writes_fact_ledger": true
},
"unknown_or_missing_policy": "fail_open_to_review"
},
{
"gate_id": "E13-G02",
"implementation_type": "deterministic_predicate_spec",
"input_contracts": [
"domain_seed_output.v3",
"E-13.domain_extension.v2",
"E-13.effect_projection.v1"
],
"condition": "처분 기준일의 자산·부채·선순위권·가치 후보가 상충",
"emitted_review_codes": [
"E13_BALANCE_SHEET_CONFLICT"
],
"row_effect": {
"must_consider": true
},
"writer_effect": {
"status_on_fire": "READY_WITH_REVIEW",
"blocks_final_drafting": false,
"writes_fact_ledger": true
},
"unknown_or_missing_policy": "fail_open_to_review"
},
{
"gate_id": "E13-G03",
"implementation_type": "deterministic_predicate_spec",
"input_contracts": [
"domain_seed_output.v3",
"E-13.domain_extension.v2",
"E-13.effect_projection.v1"
],
"condition": "대가·정상거래·수익자 또는 전득자 인식 자료가 상충",
"emitted_review_codes": [
"E13_CONSIDERATION_OR_GOOD_FAITH_CONFLICT"
],
"row_effect": {
"must_consider": true
},
"writer_effect": {
"status_on_fire": "READY_WITH_REVIEW",
"blocks_final_drafting": false,
"writes_fact_ledger": true
},
"unknown_or_missing_policy": "fail_open_to_review"
},
{
"gate_id": "E13-G04",
"implementation_type": "deterministic_predicate_spec",
"input_contracts": [
"domain_seed_output.v3",
"E-13.domain_extension.v2",
"E-13.effect_projection.v1"
],
"condition": "행위일·원인 인지일·제소일 후보가 불완전하거나 충돌",
"emitted_review_codes": [
"E13_TIME_BAR_DATE_CONFLICT"
],
"row_effect": {
"must_consider": true
},
"writer_effect": {
"status_on_fire": "READY_WITH_REVIEW",
"blocks_final_drafting": true,
"writes_fact_ledger": true
},
"unknown_or_missing_policy": "fail_open_to_review"
},
{
"gate_id": "E13-G05",
"implementation_type": "deterministic_predicate_spec",
"input_contracts": [
"domain_seed_output.v3",
"E-13.domain_extension.v2",
"E-13.effect_projection.v1"
],
"condition": "채권자대위·채권자취소 또는 원물반환·가액배상을 근거 없이 병합하거나 확정",
"emitted_review_codes": [
"E13_THEORY_TRACK_MERGE_BLOCKED",
"FINAL_LEGAL_CONCLUSION_BLOCKED"
],
"row_effect": {
"must_consider": true
},
"writer_effect": {
"status_on_fire": "READY_WITH_REVIEW",
"blocks_final_drafting": true,
"writes_fact_ledger": true
},
"unknown_or_missing_policy": "fail_open_to_review"
}
],
"global_rules": {
"review_count_may_not_decrease_without_resolution_ref": true,
"missing_input_is_not_false": true,
"final_legal_conclusion_forbidden": true,
"fact_ledger_write_continues_with_review": true
}
}
@@ -0,0 +1,91 @@
{
"schema_version": "stage1_s3_domain_gates.v1",
"domain_id": "E-14",
"rules": [
{
"gate_id": "E14-G01",
"evaluation_mode": "deterministic",
"condition": {
"required_slots_missing_or_conflicting": [
"e14.el01",
"e14.el02",
"e14.el03"
]
},
"emitted_codes": [
"E14_REQUIRED_ELEMENT_GAP_REVIEW",
"E14_ORDER_SERVICE_FINALITY_REVIEW",
"E14_ATTACHED_CLAIM_IDENTITY_REVIEW"
],
"writer_effect": {
"status_when_triggered": "READY_WITH_REVIEW",
"writes_fact_ledger": true,
"final_drafting_block": true
},
"silent_repair_forbidden": true
},
{
"gate_id": "E14-G02",
"evaluation_mode": "deterministic",
"condition": {
"boundary_or_overlap_detected": [
"E-02",
"E-09",
"E-10",
"E-13",
"X3"
]
},
"emitted_codes": [
"E14_EXECUTION_OVERLAP_REVIEW",
"E14_PROCEDURE_ONLY_REVIEW",
"TRACK_BOUNDARY_REVIEW"
],
"writer_effect": {
"status_when_triggered": "READY_WITH_REVIEW",
"writes_fact_ledger": true,
"final_drafting_block": true
},
"silent_repair_forbidden": true
},
{
"gate_id": "E14-G03",
"evaluation_mode": "deterministic",
"condition": {
"calculation_operand_state_in": [
"partial",
"blocked"
]
},
"emitted_codes": [
"MISSING_REQUIRED_OPERAND",
"CALCULATION_BLOCKED_REVIEW"
],
"writer_effect": {
"status_when_triggered": "READY_WITH_REVIEW",
"writes_fact_ledger": true,
"final_drafting_block": true
},
"silent_repair_forbidden": true
},
{
"gate_id": "E14-G04",
"evaluation_mode": "deterministic",
"condition": {
"procedure_only_or_final_conclusion_attempted": true
},
"emitted_codes": [
"E14_PROCEDURE_ONLY_REVIEW",
"TRACK_BOUNDARY_REVIEW"
],
"writer_effect": {
"status_when_triggered": "READY_WITH_REVIEW",
"writes_fact_ledger": true,
"final_drafting_block": true
},
"silent_repair_forbidden": true
}
],
"global_status_rule": "unknown or conflicting material is preserved; no worker may finalize an application, enforcement result, labor status, liability, or amount",
"closure_status": "IMPLEMENTED_LOCAL_DEFINITION"
}
@@ -0,0 +1,94 @@
{
"schema_version": "stage1_s2_domain_gates.v1",
"domain_id": "E-15",
"legacy_gate_source": {
"path": "S_0_GPT/legacy/part4/deterministic_gates_contract.json",
"sha256": "2b1b35bf1f28ba8f48401fffc86cebf5addad53570b9f8ab159f32c801d39752"
},
"rules": [
{
"gate_id": "E15-G01",
"evaluation_mode": "deterministic",
"input_fields": [
"bo_seed_candidates",
"review_items",
"extensions.domain_payload.payload"
],
"condition": {
"any_review_code_present": [
"E15_REQUIRED_ELEMENT_GAP_REVIEW",
"E15_HEIR_SCOPE_CONFLICT_REVIEW",
"E15_ASSET_SCOPE_GAP_REVIEW"
]
},
"emitted_codes": [
"E15_REQUIRED_ELEMENT_GAP_REVIEW",
"E15_HEIR_SCOPE_CONFLICT_REVIEW",
"E15_ASSET_SCOPE_GAP_REVIEW"
],
"writer_effect": {
"status_when_triggered": "READY_WITH_REVIEW",
"writes_fact_ledger": true,
"final_drafting_block": true
},
"silent_repair_forbidden": true
},
{
"gate_id": "E15-G02",
"evaluation_mode": "deterministic",
"input_fields": [
"bo_seed_candidates",
"review_items",
"extensions.domain_payload.payload"
],
"condition": {
"any_review_code_present": [
"E15_LIFECYCLE_CONFLICT_REVIEW",
"E15_SUCCESSION_STATUS_DEFENSE_REVIEW",
"E15_WILL_RESERVED_SHARE_REVIEW"
]
},
"emitted_codes": [
"E15_LIFECYCLE_CONFLICT_REVIEW",
"E15_SUCCESSION_STATUS_DEFENSE_REVIEW",
"E15_WILL_RESERVED_SHARE_REVIEW"
],
"writer_effect": {
"status_when_triggered": "READY_WITH_REVIEW",
"writes_fact_ledger": true,
"final_drafting_block": false
},
"silent_repair_forbidden": true
},
{
"gate_id": "E15-G03",
"evaluation_mode": "deterministic",
"input_fields": [
"bo_seed_candidates",
"review_items",
"extensions.domain_payload.payload"
],
"condition": {
"any_review_code_present": [
"E15_LAW_VERSION_REVIEW",
"MISSING_REQUIRED_OPERAND",
"TRACK_BOUNDARY_REVIEW"
]
},
"emitted_codes": [
"E15_LAW_VERSION_REVIEW",
"MISSING_REQUIRED_OPERAND",
"TRACK_BOUNDARY_REVIEW"
],
"writer_effect": {
"status_when_triggered": "READY_WITH_REVIEW",
"writes_fact_ledger": true,
"final_drafting_block": true
},
"silent_repair_forbidden": true
}
],
"legacy_equivalence_projections": [],
"global_status_rule": "any review code yields READY_WITH_REVIEW; hard input or contract failure yields FAILED/BLOCKED",
"closure_status": "IMPLEMENTED_LOCAL_DEFINITION"
}
@@ -0,0 +1 @@
{"domain_id":"E-16","gates":[{"condition":"required element/evidence slot이 누락·부분충족","emitted_review_codes":["E16_REQUIRED_ELEMENT_GAP_REVIEW"],"gate_id":"E16-G01","implementation_type":"deterministic_predicate_spec","input_contracts":["domain_seed_output.v3","E-16.domain_extension.v1","E-16.effect_projection.v1","stage1_domain_signals.v2"],"row_effect":{"must_consider":true},"unknown_or_missing_policy":"fail_open_to_review","writer_effect":{"blocks_final_drafting":false,"status_on_fire":"READY_WITH_REVIEW","writes_fact_ledger":true}},{"condition":"당사자·객체·행위·효력·인과·권리귀속의 핵심 원천이 상충","emitted_review_codes":["E16_INSTRUMENT_FORM_OR_COMPLETION_CONFLICT_REVIEW","E16_ENDORSEMENT_OR_HOLDER_CONFLICT_REVIEW","E16_PRESENTMENT_RECOURSE_OR_CAUSE_CONFLICT_REVIEW"],"gate_id":"E16-G02","implementation_type":"deterministic_predicate_spec","input_contracts":["domain_seed_output.v3","E-16.domain_extension.v1","E-16.effect_projection.v1","stage1_domain_signals.v2"],"row_effect":{"must_consider":true},"unknown_or_missing_policy":"fail_open_to_review","writer_effect":{"blocks_final_drafting":false,"status_on_fire":"READY_WITH_REVIEW","writes_fact_ledger":true}},{"condition":"법률행위·침해·유통·권리행사 기준일 또는 적용법 version이 누락·상충","emitted_review_codes":["E16_LAW_VERSION_OR_PERIOD_INPUT_REVIEW"],"gate_id":"E16-G03","implementation_type":"deterministic_predicate_spec","input_contracts":["domain_seed_output.v3","E-16.domain_extension.v1","E-16.effect_projection.v1","stage1_domain_signals.v2"],"row_effect":{"must_consider":true},"unknown_or_missing_policy":"fail_open_to_review","writer_effect":{"blocks_final_drafting":true,"status_on_fire":"READY_WITH_REVIEW","writes_fact_ledger":true}},{"condition":"필요 특별법 profile·전문 leaf·민사/공법/제외 트랙 경계가 닫히지 않음","emitted_review_codes":["E16_SPECIAL_LAW_OR_TRACK_BOUNDARY_REVIEW","SPECIAL_LAW_PROFILE_REQUIRED_REVIEW","TRACK_BOUNDARY_REVIEW","PUBLIC_LAW_NEXUS_REVIEW"],"gate_id":"E16-G04","implementation_type":"deterministic_predicate_spec","input_contracts":["domain_seed_output.v3","E-16.domain_extension.v1","E-16.effect_projection.v1","stage1_domain_signals.v2"],"row_effect":{"must_consider":true},"unknown_or_missing_policy":"fail_open_to_review","writer_effect":{"blocks_final_drafting":true,"status_on_fire":"READY_WITH_REVIEW","writes_fact_ledger":true}},{"condition":"계산 operand가 partial 또는 blocked인데 산식·단일값·법정수치를 확정하려 함","emitted_review_codes":["E16_CALCULATION_OPERAND_PARTIAL_REVIEW","E16_CALCULATION_OPERAND_BLOCKED_REVIEW","MISSING_REQUIRED_OPERAND","CALCULATION_PREEMPTION_BLOCKED"],"gate_id":"E16-G05","implementation_type":"deterministic_predicate_spec","input_contracts":["domain_seed_output.v3","E-16.domain_extension.v1","E-16.effect_projection.v1","stage1_domain_signals.v2"],"row_effect":{"must_consider":true},"unknown_or_missing_policy":"fail_open_to_review","writer_effect":{"blocks_final_drafting":true,"status_on_fire":"READY_WITH_REVIEW","writes_fact_ledger":true}},{"condition":"meeting-only 진술을 증거로 승격하거나 최종 법률결론·청구금액·소장문구를 생성하려 함","emitted_review_codes":["E16_MEETING_ONLY_SUPPORT_REVIEW","FINAL_LEGAL_CONCLUSION_BLOCKED"],"gate_id":"E16-G06","implementation_type":"deterministic_predicate_spec","input_contracts":["domain_seed_output.v3","E-16.domain_extension.v1","E-16.effect_projection.v1","stage1_domain_signals.v2"],"row_effect":{"must_consider":true},"unknown_or_missing_policy":"fail_open_to_review","writer_effect":{"blocks_final_drafting":true,"status_on_fire":"READY_WITH_REVIEW","writes_fact_ledger":true}}],"global_rules":{"fact_ledger_write_continues_with_review":true,"final_legal_conclusion_forbidden":true,"meeting_only_evidence_promotion_forbidden":true,"missing_input_is_not_false":true,"review_count_may_not_decrease_without_resolution_ref":true,"runtime_case_name_lookup_forbidden":true,"statutory_numeric_constant_hardcoding_forbidden":true},"schema_version":"stage1_domain_gates.v1","status":"ACTIVE"}
@@ -0,0 +1,88 @@
{
"schema_version": "stage1_s3_domain_gates.v1",
"domain_id": "E-17",
"rules": [
{
"gate_id": "E17-G01",
"evaluation_mode": "deterministic",
"condition": {
"required_slots_missing_or_conflicting": [
"e17.el01",
"e17.el02",
"e17.el03"
]
},
"emitted_codes": [
"E17_REQUIRED_ELEMENT_GAP_REVIEW",
"E17_WORKER_STATUS_CONFLICT_REVIEW",
"E17_WAGE_ITEM_CONFLICT_REVIEW"
],
"writer_effect": {
"status_when_triggered": "READY_WITH_REVIEW",
"writes_fact_ledger": true,
"final_drafting_block": true
},
"silent_repair_forbidden": true
},
{
"gate_id": "E17-G02",
"evaluation_mode": "deterministic",
"condition": {
"boundary_or_overlap_detected": [
"E-05",
"X3"
]
},
"emitted_codes": [
"E17_INDUSTRIAL_ACCIDENT_OVERLAP_REVIEW",
"E17_PROCEDURE_BOUNDARY_REVIEW",
"TRACK_BOUNDARY_REVIEW"
],
"writer_effect": {
"status_when_triggered": "READY_WITH_REVIEW",
"writes_fact_ledger": true,
"final_drafting_block": true
},
"silent_repair_forbidden": true
},
{
"gate_id": "E17-G03",
"evaluation_mode": "deterministic",
"condition": {
"calculation_operand_state_in": [
"partial",
"blocked"
]
},
"emitted_codes": [
"MISSING_REQUIRED_OPERAND",
"CALCULATION_BLOCKED_REVIEW"
],
"writer_effect": {
"status_when_triggered": "READY_WITH_REVIEW",
"writes_fact_ledger": true,
"final_drafting_block": true
},
"silent_repair_forbidden": true
},
{
"gate_id": "E17-G04",
"evaluation_mode": "deterministic",
"condition": {
"procedure_only_or_final_conclusion_attempted": true
},
"emitted_codes": [
"E17_PROCEDURE_BOUNDARY_REVIEW",
"TRACK_BOUNDARY_REVIEW"
],
"writer_effect": {
"status_when_triggered": "READY_WITH_REVIEW",
"writes_fact_ledger": true,
"final_drafting_block": true
},
"silent_repair_forbidden": true
}
],
"global_status_rule": "unknown or conflicting material is preserved; no worker may finalize an application, enforcement result, labor status, liability, or amount",
"closure_status": "IMPLEMENTED_LOCAL_DEFINITION"
}
@@ -0,0 +1 @@
{"closure_status":"IMPLEMENTED_LOCAL_DEFINITION","domain_id":"E-18","global_status_rule":"unknown, conflicting, meeting-only and missing-law material is preserved; no Stage 1 worker may finalize entitlement, liability, validity or amount","rules":[{"condition":{"required_slots_missing_or_conflicting":["e18.el01","e18.el02","e18.el03"]},"emitted_codes":["E18_ENTITY_CONSTITUTION_REVIEW","E18_RESOLUTION_EXISTENCE_VALIDITY_REVIEW","E18_ACTION_PERIOD_REVIEW"],"evaluation_mode":"deterministic","gate_id":"E18-G01","silent_repair_forbidden":true,"writer_effect":{"final_drafting_block":true,"status_when_triggered":"READY_WITH_REVIEW","writes_fact_ledger":true}},{"condition":{"boundary_or_overlap_detected":["E-01","E-02","E-04","E-09","X3"]},"emitted_codes":["E18_RELIEF_POSTURE_REVIEW","TRACK_BOUNDARY_REVIEW"],"evaluation_mode":"deterministic","gate_id":"E18-G02","silent_repair_forbidden":true,"writer_effect":{"final_drafting_block":true,"status_when_triggered":"READY_WITH_REVIEW","writes_fact_ledger":true}},{"condition":{"calculation_operand_state_in":["partial","blocked","deferred"]},"emitted_codes":["MISSING_REQUIRED_OPERAND","CALCULATION_BLOCKED_REVIEW","DEFERRED_CALCULATION_TRACK_REVIEW"],"evaluation_mode":"deterministic","gate_id":"E18-G03","silent_repair_forbidden":true,"writer_effect":{"final_drafting_block":true,"status_when_triggered":"READY_WITH_REVIEW","writes_fact_ledger":true}},{"condition":{"final_conclusion_or_missing_profile_attempted":true},"emitted_codes":["E18_ENTITY_CONSTITUTION_REVIEW","TRACK_BOUNDARY_REVIEW"],"evaluation_mode":"deterministic","gate_id":"E18-G04","silent_repair_forbidden":true,"writer_effect":{"final_drafting_block":true,"status_when_triggered":"READY_WITH_REVIEW","writes_fact_ledger":true}}],"schema_version":"stage1_s6_domain_gates.v1"}
@@ -0,0 +1 @@
{"closure_status":"IMPLEMENTED_LOCAL_DEFINITION","domain_id":"E-19","global_status_rule":"unknown, conflicting, meeting-only and missing-law material is preserved; no Stage 1 worker may finalize entitlement, liability, validity or amount","rules":[{"condition":{"required_slots_missing_or_conflicting":["e19.el01","e19.el02","e19.el03"]},"emitted_codes":["E19_POLICY_VERSION_REVIEW","E19_COVERAGE_EXCLUSION_REVIEW","E19_DISCLOSURE_NOTICE_REVIEW"],"evaluation_mode":"deterministic","gate_id":"E19-G01","silent_repair_forbidden":true,"writer_effect":{"final_drafting_block":true,"status_when_triggered":"READY_WITH_REVIEW","writes_fact_ledger":true}},{"condition":{"boundary_or_overlap_detected":["E-01","E-05","E-12","E-17","X1","X3"]},"emitted_codes":["E19_SPECIAL_TERMS_INSURANCE_BUSINESS_REVIEW","TRACK_BOUNDARY_REVIEW"],"evaluation_mode":"deterministic","gate_id":"E19-G02","silent_repair_forbidden":true,"writer_effect":{"final_drafting_block":true,"status_when_triggered":"READY_WITH_REVIEW","writes_fact_ledger":true}},{"condition":{"calculation_operand_state_in":["partial","blocked","deferred"]},"emitted_codes":["MISSING_REQUIRED_OPERAND","CALCULATION_BLOCKED_REVIEW"],"evaluation_mode":"deterministic","gate_id":"E19-G03","silent_repair_forbidden":true,"writer_effect":{"final_drafting_block":true,"status_when_triggered":"READY_WITH_REVIEW","writes_fact_ledger":true}},{"condition":{"final_conclusion_or_missing_profile_attempted":true},"emitted_codes":["E19_POLICY_VERSION_REVIEW","TRACK_BOUNDARY_REVIEW"],"evaluation_mode":"deterministic","gate_id":"E19-G04","silent_repair_forbidden":true,"writer_effect":{"final_drafting_block":true,"status_when_triggered":"READY_WITH_REVIEW","writes_fact_ledger":true}}],"schema_version":"stage1_s6_domain_gates.v1"}
@@ -0,0 +1 @@
{"closure_status":"IMPLEMENTED_LOCAL_DEFINITION","domain_id":"E-20","global_status_rule":"unknown, conflicting, meeting-only and missing-law material is preserved; no Stage 1 worker may finalize entitlement, liability, validity or amount","rules":[{"condition":{"required_slots_missing_or_conflicting":["e20.el01","e20.el02","e20.el03"]},"emitted_codes":["E20_RIGHT_IDENTITY_VALIDITY_REVIEW","E20_OWNERSHIP_LICENSE_REVIEW","E20_SCOPE_COMPARISON_REVIEW"],"evaluation_mode":"deterministic","gate_id":"E20-G01","silent_repair_forbidden":true,"writer_effect":{"final_drafting_block":true,"status_when_triggered":"READY_WITH_REVIEW","writes_fact_ledger":true}},{"condition":{"boundary_or_overlap_detected":["E-01","E-05","E-12","E-16","X3"]},"emitted_codes":["E20_OVERLAP_REVIEW","TRACK_BOUNDARY_REVIEW"],"evaluation_mode":"deterministic","gate_id":"E20-G02","silent_repair_forbidden":true,"writer_effect":{"final_drafting_block":true,"status_when_triggered":"READY_WITH_REVIEW","writes_fact_ledger":true}},{"condition":{"calculation_operand_state_in":["partial","blocked","deferred"]},"emitted_codes":["MISSING_REQUIRED_OPERAND","CALCULATION_BLOCKED_REVIEW","DEFERRED_CALCULATION_TRACK_REVIEW"],"evaluation_mode":"deterministic","gate_id":"E20-G03","silent_repair_forbidden":true,"writer_effect":{"final_drafting_block":true,"status_when_triggered":"READY_WITH_REVIEW","writes_fact_ledger":true}},{"condition":{"final_conclusion_or_missing_profile_attempted":true},"emitted_codes":["E20_SPECIAL_LAW_PROFILE_REQUIRED","TRACK_BOUNDARY_REVIEW"],"evaluation_mode":"deterministic","gate_id":"E20-G04","silent_repair_forbidden":true,"writer_effect":{"final_drafting_block":true,"status_when_triggered":"READY_WITH_REVIEW","writes_fact_ledger":true}}],"schema_version":"stage1_s6_domain_gates.v1"}
@@ -0,0 +1 @@
{"closure_status":"IMPLEMENTED_LOCAL_DEFINITION","domain_id":"E-21","global_status_rule":"unknown, conflicting, meeting-only and missing-law material is preserved; no Stage 1 worker may finalize entitlement, liability, validity or amount","rules":[{"condition":{"required_slots_missing_or_conflicting":["e21.el01","e21.el02","e21.el03"]},"emitted_codes":["E21_PUBLICATION_VERSION_REVIEW","E21_IDENTIFIABILITY_REVIEW","E21_FACT_OPINION_TRUTH_REVIEW"],"evaluation_mode":"deterministic","gate_id":"E21-G01","silent_repair_forbidden":true,"writer_effect":{"final_drafting_block":true,"status_when_triggered":"READY_WITH_REVIEW","writes_fact_ledger":true}},{"condition":{"boundary_or_overlap_detected":["E-01","E-05","E-12","E-20","X1","X3"]},"emitted_codes":["E21_SPECIAL_LAW_PROFILE_REQUIRED","TRACK_BOUNDARY_REVIEW"],"evaluation_mode":"deterministic","gate_id":"E21-G02","silent_repair_forbidden":true,"writer_effect":{"final_drafting_block":true,"status_when_triggered":"READY_WITH_REVIEW","writes_fact_ledger":true}},{"condition":{"calculation_operand_state_in":["partial","blocked","deferred"]},"emitted_codes":["MISSING_REQUIRED_OPERAND","CALCULATION_BLOCKED_REVIEW"],"evaluation_mode":"deterministic","gate_id":"E21-G03","silent_repair_forbidden":true,"writer_effect":{"final_drafting_block":true,"status_when_triggered":"READY_WITH_REVIEW","writes_fact_ledger":true}},{"condition":{"final_conclusion_or_missing_profile_attempted":true},"emitted_codes":["E21_SOURCE_CONFLICT_REVIEW","TRACK_BOUNDARY_REVIEW"],"evaluation_mode":"deterministic","gate_id":"E21-G04","silent_repair_forbidden":true,"writer_effect":{"final_drafting_block":true,"status_when_triggered":"READY_WITH_REVIEW","writes_fact_ledger":true}}],"schema_version":"stage1_s6_domain_gates.v1"}
@@ -0,0 +1,114 @@
{
"schema_version": "stage1_s2_domain_gates.v1",
"domain_id": "X1",
"legacy_gate_source": {
"path": "S_0_GPT/legacy/part4/deterministic_gates_contract.json",
"sha256": "2b1b35bf1f28ba8f48401fffc86cebf5addad53570b9f8ab159f32c801d39752"
},
"rules": [
{
"gate_id": "X1-G01",
"evaluation_mode": "deterministic",
"input_fields": [
"bo_seed_candidates",
"review_items",
"extensions.domain_payload.payload"
],
"condition": {
"any_review_code_present": [
"X1_REQUIRED_ELEMENT_GAP_REVIEW",
"X1_ARRIVAL_UNPROVEN_REVIEW",
"NOTICE_RECEIPT_UNVERIFIED",
"BLOCK_FINAL_DRAFTING_NOTICE_RECEIPT_MISSING"
]
},
"emitted_codes": [
"X1_REQUIRED_ELEMENT_GAP_REVIEW",
"X1_ARRIVAL_UNPROVEN_REVIEW",
"NOTICE_RECEIPT_UNVERIFIED",
"BLOCK_FINAL_DRAFTING_NOTICE_RECEIPT_MISSING"
],
"writer_effect": {
"status_when_triggered": "READY_WITH_REVIEW",
"writes_fact_ledger": true,
"final_drafting_block": true
},
"silent_repair_forbidden": true
},
{
"gate_id": "X1-G02",
"evaluation_mode": "deterministic",
"input_fields": [
"bo_seed_candidates",
"review_items",
"extensions.domain_payload.payload"
],
"condition": {
"any_review_code_present": [
"X1_LIFECYCLE_DATE_CONFLICT",
"X1_RECIPIENT_OR_CONTENT_CONFLICT",
"NOTICE_RECEIPT_EVIDENCE_WEAK_REVIEW"
]
},
"emitted_codes": [
"X1_LIFECYCLE_DATE_CONFLICT",
"X1_RECIPIENT_OR_CONTENT_CONFLICT",
"NOTICE_RECEIPT_EVIDENCE_WEAK_REVIEW"
],
"writer_effect": {
"status_when_triggered": "READY_WITH_REVIEW",
"writes_fact_ledger": true,
"final_drafting_block": false
},
"silent_repair_forbidden": true
},
{
"gate_id": "X1-G03",
"evaluation_mode": "deterministic",
"input_fields": [
"bo_seed_candidates",
"review_items",
"extensions.domain_payload.payload"
],
"condition": {
"any_review_code_present": [
"CALCULATION_PREEMPTION_BLOCKED"
]
},
"emitted_codes": [
"CALCULATION_PREEMPTION_BLOCKED"
],
"writer_effect": {
"status_when_triggered": "READY_WITH_REVIEW",
"writes_fact_ledger": true,
"final_drafting_block": true
},
"silent_repair_forbidden": true
}
],
"legacy_equivalence_projections": [
{
"legacy_gate_id": "LEGACY-GATE-02",
"successor_gate_id": "X1-G01",
"branch": "receipt_missing",
"condition": "requires_receipt_for_effect_candidate=true and arrival_or_received_candidate is null",
"emitted_codes": [
"NOTICE_RECEIPT_UNVERIFIED",
"BLOCK_FINAL_DRAFTING_NOTICE_RECEIPT_MISSING"
],
"equivalence": "preserved"
},
{
"legacy_gate_id": "LEGACY-GATE-02",
"successor_gate_id": "X1-G02",
"branch": "receipt_evidence_weak",
"condition": "arrival_or_received_candidate is not null and receipt_evidence_refs empty and recipient_admission_candidate null",
"emitted_codes": [
"NOTICE_RECEIPT_EVIDENCE_WEAK_REVIEW"
],
"equivalence": "preserved"
}
],
"global_status_rule": "any review code yields READY_WITH_REVIEW; hard input or contract failure yields FAILED/BLOCKED",
"closure_status": "IMPLEMENTED_LOCAL_DEFINITION"
}
@@ -0,0 +1,128 @@
{
"schema_version": "stage1_domain_gates.v1",
"domain_id": "X2",
"status": "ACTIVE",
"legacy_contract_refs": [
"S_0_GPT/legacy/part4/deterministic_gates_contract.json",
"S_0_GPT/legacy/part4/structure_projection_contract.json"
],
"gates": [
{
"gate_id": "X2-G01",
"implementation_type": "deterministic_predicate_spec",
"input_contracts": [
"domain_seed_output.v3",
"X2.domain_extension.v2",
"X2.effect_projection.v1"
],
"condition": "선택된 자산 원천에 canonical asset identifier 또는 표시가 없음",
"emitted_review_codes": [
"X2_ASSET_IDENTITY_GAP_REVIEW"
],
"row_effect": {
"must_consider": true
},
"writer_effect": {
"status_on_fire": "READY_WITH_REVIEW",
"blocks_final_drafting": true,
"writes_fact_ledger": true
},
"unknown_or_missing_policy": "fail_open_to_review"
},
{
"gate_id": "X2-G02",
"implementation_type": "deterministic_predicate_spec",
"input_contracts": [
"domain_seed_output.v3",
"X2.domain_extension.v2",
"X2.effect_projection.v1"
],
"condition": "alias·지번·동호수·면적·지분이 복수 자산에 상충 연결",
"emitted_review_codes": [
"X2_ASSET_IDENTITY_CONFLICT",
"X2_SHARE_OR_AREA_CONFLICT"
],
"row_effect": {
"must_consider": true
},
"writer_effect": {
"status_on_fire": "READY_WITH_REVIEW",
"blocks_final_drafting": true,
"writes_fact_ledger": true
},
"unknown_or_missing_policy": "fail_open_to_review"
},
{
"gate_id": "X2-G03",
"implementation_type": "deterministic_predicate_spec",
"input_contracts": [
"domain_seed_output.v3",
"X2.domain_extension.v2",
"X2.effect_projection.v1"
],
"condition": "동일 기준시점의 소유·점유·사용·수익 또는 등기 lineage가 충돌",
"emitted_review_codes": [
"X2_STATE_OR_LINEAGE_CONFLICT"
],
"row_effect": {
"must_consider": true
},
"writer_effect": {
"status_on_fire": "READY_WITH_REVIEW",
"blocks_final_drafting": false,
"writes_fact_ledger": true
},
"unknown_or_missing_policy": "fail_open_to_review"
},
{
"gate_id": "X2-G04",
"implementation_type": "deterministic_predicate_spec",
"input_contracts": [
"domain_seed_output.v3",
"X2.domain_extension.v2",
"X2.effect_projection.v1"
],
"condition": "평가값이 대상 자산 또는 기준시점과 결합되지 않음",
"emitted_review_codes": [
"X2_VALUATION_BINDING_REVIEW"
],
"row_effect": {
"must_consider": true
},
"writer_effect": {
"status_on_fire": "READY_WITH_REVIEW",
"blocks_final_drafting": false,
"writes_fact_ledger": true
},
"unknown_or_missing_policy": "fail_open_to_review"
},
{
"gate_id": "X2-G05",
"implementation_type": "deterministic_predicate_spec",
"input_contracts": [
"domain_seed_output.v3",
"X2.domain_extension.v2",
"X2.effect_projection.v1"
],
"condition": "자산 동일성·권리귀속을 사실 연결을 넘어 최종 확정하려 함",
"emitted_review_codes": [
"FINAL_LEGAL_CONCLUSION_BLOCKED"
],
"row_effect": {
"must_consider": true
},
"writer_effect": {
"status_on_fire": "READY_WITH_REVIEW",
"blocks_final_drafting": true,
"writes_fact_ledger": true
},
"unknown_or_missing_policy": "fail_open_to_review"
}
],
"global_rules": {
"review_count_may_not_decrease_without_resolution_ref": true,
"missing_input_is_not_false": true,
"final_legal_conclusion_forbidden": true,
"fact_ledger_write_continues_with_review": true
}
}
@@ -0,0 +1,115 @@
{
"schema_version": "stage1_s2_domain_gates.v1",
"domain_id": "X3",
"legacy_gate_source": {
"path": "S_0_GPT/legacy/part4/deterministic_gates_contract.json",
"sha256": "2b1b35bf1f28ba8f48401fffc86cebf5addad53570b9f8ab159f32c801d39752"
},
"rules": [
{
"gate_id": "X3-G01",
"evaluation_mode": "deterministic",
"input_fields": [
"bo_seed_candidates",
"review_items",
"extensions.domain_payload.payload"
],
"condition": {
"any_review_code_present": [
"X3_PARTY_STANDING_GAP_REVIEW",
"X3_PARTY_STANDING_CONFLICT_REVIEW",
"X3_CAPACITY_AUTHORITY_REVIEW"
]
},
"emitted_codes": [
"X3_PARTY_STANDING_GAP_REVIEW",
"X3_PARTY_STANDING_CONFLICT_REVIEW",
"X3_CAPACITY_AUTHORITY_REVIEW"
],
"writer_effect": {
"status_when_triggered": "READY_WITH_REVIEW",
"writes_fact_ledger": true,
"final_drafting_block": true
},
"silent_repair_forbidden": true
},
{
"gate_id": "X3-G02",
"evaluation_mode": "deterministic",
"input_fields": [
"bo_seed_candidates",
"review_items",
"extensions.domain_payload.payload"
],
"condition": {
"any_review_code_present": [
"X3_RELIEF_FORM_GAP_REVIEW",
"X3_EXTRAORDINARY_REMEDY_REVIEW"
]
},
"emitted_codes": [
"X3_RELIEF_FORM_GAP_REVIEW",
"X3_EXTRAORDINARY_REMEDY_REVIEW"
],
"writer_effect": {
"status_when_triggered": "READY_WITH_REVIEW",
"writes_fact_ledger": true,
"final_drafting_block": true
},
"silent_repair_forbidden": true
},
{
"gate_id": "X3-G03",
"evaluation_mode": "deterministic",
"input_fields": [
"bo_seed_candidates",
"review_items",
"extensions.domain_payload.payload"
],
"condition": {
"any_review_code_present": [
"TRACK_BOUNDARY_REVIEW",
"PUBLIC_LAW_NEXUS_REVIEW"
]
},
"emitted_codes": [
"TRACK_BOUNDARY_REVIEW",
"PUBLIC_LAW_NEXUS_REVIEW"
],
"writer_effect": {
"status_when_triggered": "READY_WITH_REVIEW",
"writes_fact_ledger": true,
"final_drafting_block": true
},
"silent_repair_forbidden": true
},
{
"gate_id": "X3-G04",
"evaluation_mode": "deterministic",
"input_fields": [
"bo_seed_candidates",
"review_items",
"extensions.domain_payload.payload"
],
"condition": {
"any_review_code_present": [
"MISSING_REQUIRED_OPERAND",
"CALCULATION_PREEMPTION_BLOCKED"
]
},
"emitted_codes": [
"MISSING_REQUIRED_OPERAND",
"CALCULATION_PREEMPTION_BLOCKED"
],
"writer_effect": {
"status_when_triggered": "READY_WITH_REVIEW",
"writes_fact_ledger": true,
"final_drafting_block": true
},
"silent_repair_forbidden": true
}
],
"legacy_equivalence_projections": [],
"global_status_rule": "any review code yields READY_WITH_REVIEW; hard input or contract failure yields FAILED/BLOCKED",
"closure_status": "IMPLEMENTED_LOCAL_DEFINITION"
}
@@ -0,0 +1,93 @@
{
"$id": "https://schemas.liti-agent.local/stage1/part4/fact_exception_pack.schema.json",
"title": "stage1_fact_exception_pack.v1",
"description": "Part 4 F0 -> F1 예외 pack. 판정 부류는 정책 f1_exception_types 가 선언하며 unreachable_in_v8 부류가 실리면 PART4_UNREACHABLE_EXCEPTION_TYPE 경성 실패다.",
"type": "object",
"additionalProperties": false,
"required": [
"fact_exception_pack"
],
"properties": {
"fact_exception_pack": {
"type": "object",
"additionalProperties": false,
"required": [
"schema_version",
"has_exceptions",
"exception_count",
"exceptions",
"budget"
],
"properties": {
"schema_version": {
"const": "stage1_fact_exception_pack.v1"
},
"has_exceptions": {
"type": "boolean"
},
"exception_count": {
"type": "integer",
"minimum": 0
},
"exceptions": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"exception_id",
"exception_type",
"fact_id",
"source_bo_id",
"conflict_flags",
"compact_payload"
],
"properties": {
"exception_id": {
"type": "string",
"pattern": "^FX-[0-9]{3}$"
},
"exception_type": {
"type": "string"
},
"fact_id": {
"type": "string",
"pattern": "^F-[0-9]{3}$"
},
"source_bo_id": {
"type": "string",
"minLength": 1
},
"conflict_flags": {
"type": "array",
"items": {
"type": "string",
"pattern": "^[A-Z][A-Z0-9_]{2,127}$"
}
},
"compact_payload": {
"type": "object"
},
"oversize_review": {
"type": "boolean"
}
}
}
},
"budget": {
"type": "object",
"additionalProperties": false,
"required": [
"pack_item_char_limit"
],
"properties": {
"pack_item_char_limit": {
"type": "integer",
"minimum": 1
}
}
}
}
}
}
}
@@ -0,0 +1,406 @@
{
"$id": "https://schemas.liti-agent.local/stage1/part4/fact_ledger_base.schema.json",
"title": "stage1_fact_ledger_base.v1",
"description": "Part 4 최종 산출 — 래핑 없는 배열. 행 29키 = 구 27(순서·이름 불변) + domain_effects·calculation_requests(required 에 올리지 않는다 — Stage 2 호환 D-P4-4). 열거를 하드코딩하지 않는다 — 도메인 ID·검토 코드는 패턴만 강제한다.",
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"fact_id",
"source_bo_id",
"type",
"date",
"parties",
"object_spec",
"amount",
"action",
"evidence_refs",
"credibility",
"legal_centrality",
"proof_strength",
"legal_effect_roles",
"claim_chain_ref",
"linked_structures",
"must_not_drop_in_claim_types",
"must_consider",
"state_context",
"money_claim_effect",
"commercial_successor_effect",
"actio_roles",
"linked_actio_structures",
"downstream_module_candidates",
"skeleton_validation_codes",
"derived_fact",
"derivation_basis",
"legal_calculation_object"
],
"properties": {
"fact_id": {
"type": "string",
"pattern": "^F-[0-9]{3}$"
},
"source_bo_id": {
"type": "string",
"minLength": 1
},
"type": {
"type": [
"string",
"null"
]
},
"date": {
"type": [
"string",
"null"
]
},
"parties": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"role",
"value",
"source"
],
"properties": {
"role": {
"type": "string"
},
"value": {
"type": [
"string",
"null"
]
},
"source": {
"type": "string"
}
}
}
},
"object_spec": {
"type": [
"string",
"null"
]
},
"amount": {
"type": [
"string",
"null"
]
},
"action": {
"type": [
"string",
"null"
]
},
"evidence_refs": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"credibility": {
"enum": [
"high",
"medium",
"low"
]
},
"legal_centrality": {
"enum": [
"critical",
"high",
"medium",
"low"
]
},
"proof_strength": {
"enum": [
"documentary",
"high",
"medium",
"meeting_only_core",
"low"
]
},
"legal_effect_roles": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"claim_chain_ref": {
"type": "object",
"additionalProperties": false,
"required": [
"claim_group_ids",
"liability_group_ids",
"signal_count"
],
"properties": {
"claim_group_ids": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"liability_group_ids": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"signal_count": {
"type": "integer",
"minimum": 0
}
}
},
"linked_structures": {
"type": "object",
"additionalProperties": false,
"required": [
"structure_ids"
],
"properties": {
"structure_ids": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
}
}
},
"must_not_drop_in_claim_types": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"must_consider": {
"type": "boolean"
},
"state_context": {
"type": "object",
"additionalProperties": false,
"required": [
"bo_type",
"domain_ids"
],
"properties": {
"bo_type": {
"type": [
"string",
"null"
]
},
"domain_ids": {
"type": "array",
"items": {
"type": "string",
"pattern": "^(?:(?:EC|E)-[A-Z0-9]{2,8}|X[A-Z0-9]{1,8})$"
},
"uniqueItems": true
}
}
},
"money_claim_effect": {
"type": "object",
"additionalProperties": false,
"required": [
"enabled",
"structure_ids"
],
"properties": {
"enabled": {
"type": "boolean"
},
"structure_ids": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
}
}
},
"commercial_successor_effect": {
"type": "object",
"additionalProperties": false,
"required": [
"enabled",
"structure_ids"
],
"properties": {
"enabled": {
"type": "boolean"
},
"structure_ids": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
}
}
},
"actio_roles": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"linked_actio_structures": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"downstream_module_candidates": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"skeleton_validation_codes": {
"type": "array",
"items": {
"type": "string",
"pattern": "^[A-Z][A-Z0-9_]{2,127}$"
}
},
"derived_fact": {
"type": [
"string",
"null"
]
},
"derivation_basis": {
"type": "string"
},
"legal_calculation_object": {
"type": [
"string",
"null"
]
},
"domain_effects": {
"type": "object",
"additionalProperties": {
"type": "object",
"additionalProperties": false,
"required": [
"module",
"role_projection",
"structure_ids",
"type_ids",
"review_codes"
],
"properties": {
"module": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"role_projection": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"structure_ids": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"type_ids": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"review_codes": {
"type": "array",
"items": {
"type": "string",
"pattern": "^[A-Z][A-Z0-9_]{2,127}$"
},
"uniqueItems": true
}
}
}
},
"calculation_requests": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"calculation_id"
],
"properties": {
"calculation_id": {
"type": [
"string",
"null"
]
},
"request_id": {
"type": [
"string",
"null"
]
},
"operand_state": {
"enum": [
"ready",
"partial",
"blocked",
"deferred",
null
]
},
"missing_operands": {
"type": "array",
"items": {
"type": "string"
}
},
"law_version_refs": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"provenance": {
"type": "object"
}
}
}
}
}
}
}
@@ -0,0 +1,471 @@
{
"$id": "https://schemas.liti-agent.local/stage1/part4/fact_ledger_candidate_bundle.schema.json",
"title": "stage1_fact_ledger_candidate_bundle.v1",
"description": "Part 4 F0 산출 — 후보 행 묶음. domain_effects·calculation_requests 를 아는 것이 F0 의미 검사(PART4_LEDGER_SCHEMA_STALE)의 대상이다.",
"type": "object",
"additionalProperties": false,
"required": [
"fact_ledger_candidate_bundle"
],
"properties": {
"fact_ledger_candidate_bundle": {
"type": "object",
"additionalProperties": false,
"required": [
"schema_version",
"status",
"rows",
"gate_report",
"counts",
"signal_manifest_transaction_id",
"created_by"
],
"properties": {
"schema_version": {
"const": "stage1_fact_ledger_candidate_bundle.v1"
},
"status": {
"enum": [
"READY",
"READY_WITH_REVIEW"
]
},
"rows": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"fact_id",
"source_bo_id",
"type",
"date",
"parties",
"object_spec",
"amount",
"action",
"evidence_refs",
"credibility",
"legal_centrality",
"proof_strength",
"legal_effect_roles",
"claim_chain_ref",
"linked_structures",
"must_not_drop_in_claim_types",
"must_consider",
"state_context",
"money_claim_effect",
"commercial_successor_effect",
"actio_roles",
"linked_actio_structures",
"downstream_module_candidates",
"skeleton_validation_codes",
"derived_fact",
"derivation_basis",
"legal_calculation_object"
],
"properties": {
"fact_id": {
"type": "string",
"pattern": "^F-[0-9]{3}$"
},
"source_bo_id": {
"type": "string",
"minLength": 1
},
"type": {
"type": [
"string",
"null"
]
},
"date": {
"type": [
"string",
"null"
]
},
"parties": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"role",
"value",
"source"
],
"properties": {
"role": {
"type": "string"
},
"value": {
"type": [
"string",
"null"
]
},
"source": {
"type": "string"
}
}
}
},
"object_spec": {
"type": [
"string",
"null"
]
},
"amount": {
"type": [
"string",
"null"
]
},
"action": {
"type": [
"string",
"null"
]
},
"evidence_refs": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"credibility": {
"enum": [
"high",
"medium",
"low"
]
},
"legal_centrality": {
"enum": [
"critical",
"high",
"medium",
"low"
]
},
"proof_strength": {
"enum": [
"documentary",
"high",
"medium",
"meeting_only_core",
"low"
]
},
"legal_effect_roles": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"claim_chain_ref": {
"type": "object",
"additionalProperties": false,
"required": [
"claim_group_ids",
"liability_group_ids",
"signal_count"
],
"properties": {
"claim_group_ids": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"liability_group_ids": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"signal_count": {
"type": "integer",
"minimum": 0
}
}
},
"linked_structures": {
"type": "object",
"additionalProperties": false,
"required": [
"structure_ids"
],
"properties": {
"structure_ids": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
}
}
},
"must_not_drop_in_claim_types": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"must_consider": {
"type": "boolean"
},
"state_context": {
"type": "object",
"additionalProperties": false,
"required": [
"bo_type",
"domain_ids"
],
"properties": {
"bo_type": {
"type": [
"string",
"null"
]
},
"domain_ids": {
"type": "array",
"items": {
"type": "string",
"pattern": "^(?:(?:EC|E)-[A-Z0-9]{2,8}|X[A-Z0-9]{1,8})$"
},
"uniqueItems": true
}
}
},
"money_claim_effect": {
"type": "object",
"additionalProperties": false,
"required": [
"enabled",
"structure_ids"
],
"properties": {
"enabled": {
"type": "boolean"
},
"structure_ids": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
}
}
},
"commercial_successor_effect": {
"type": "object",
"additionalProperties": false,
"required": [
"enabled",
"structure_ids"
],
"properties": {
"enabled": {
"type": "boolean"
},
"structure_ids": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
}
}
},
"actio_roles": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"linked_actio_structures": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"downstream_module_candidates": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"skeleton_validation_codes": {
"type": "array",
"items": {
"type": "string",
"pattern": "^[A-Z][A-Z0-9_]{2,127}$"
}
},
"derived_fact": {
"type": [
"string",
"null"
]
},
"derivation_basis": {
"type": "string"
},
"legal_calculation_object": {
"type": [
"string",
"null"
]
},
"domain_effects": {
"type": "object",
"additionalProperties": {
"type": "object",
"additionalProperties": false,
"required": [
"module",
"role_projection",
"structure_ids",
"type_ids",
"review_codes"
],
"properties": {
"module": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"role_projection": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"structure_ids": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"type_ids": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"review_codes": {
"type": "array",
"items": {
"type": "string",
"pattern": "^[A-Z][A-Z0-9_]{2,127}$"
},
"uniqueItems": true
}
}
}
},
"calculation_requests": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"calculation_id"
],
"properties": {
"calculation_id": {
"type": [
"string",
"null"
]
},
"request_id": {
"type": [
"string",
"null"
]
},
"operand_state": {
"enum": [
"ready",
"partial",
"blocked",
"deferred",
null
]
},
"missing_operands": {
"type": "array",
"items": {
"type": "string"
}
},
"law_version_refs": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"provenance": {
"type": "object"
}
}
}
}
}
}
},
"gate_report": {
"type": "object"
},
"counts": {
"type": "object"
},
"signal_manifest_transaction_id": {
"type": [
"string",
"null"
]
},
"les_transaction_seal": {
"enum": [
"MATCHED",
"UNSEALED_REVIEW"
]
},
"expected_runnable_domain_ids": {
"type": "array",
"items": {
"type": "string",
"pattern": "^(?:(?:EC|E)-[A-Z0-9]{2,8}|X[A-Z0-9]{1,8})$"
},
"uniqueItems": true
},
"alias_resolution": {
"type": "object"
},
"created_by": {
"type": "string"
}
}
}
}
}
@@ -4,128 +4,333 @@
"description": "Part 3 L2 최종 산출 — 법률효과 구조와 역인덱스 셋(by_domain_id · by_module · by_bo_id). by_claim_form 은 만들지 않는다(claim-form 어휘가 registry 에 없다 — D-4 라). 열거를 하드코딩하지 않는다.", "description": "Part 3 L2 최종 산출 — 법률효과 구조와 역인덱스 셋(by_domain_id · by_module · by_bo_id). by_claim_form 은 만들지 않는다(claim-form 어휘가 registry 에 없다 — D-4 라). 열거를 하드코딩하지 않는다.",
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"required": ["schema_version", "status", "expected_runnable_domain_ids", "theta", "required": [
"structure_records", "structure_index", "quality_gate", "created_by"], "schema_version",
"status",
"expected_runnable_domain_ids",
"theta",
"structure_records",
"structure_index",
"quality_gate",
"created_by",
"signal_manifest_transaction_id"
],
"properties": { "properties": {
"schema_version": {"const": "stage1_legal_effect_structures.v1"}, "schema_version": {
"status": {"enum": ["READY", "READY_WITH_REVIEW"]}, "const": "stage1_legal_effect_structures.v1"
"registry_version": {"type": ["string", "null"]}, },
"registry_index_sha256": {"type": "string", "pattern": "^[a-f0-9]{64}$"}, "status": {
"activation_manifest_sha256": {"type": "string", "pattern": "^[a-f0-9]{64}$"}, "enum": [
"signal_manifest_transaction_id": {"type": "string", "pattern": "^S5TX-[a-f0-9]{20}$"}, "READY",
"READY_WITH_REVIEW"
]
},
"registry_version": {
"type": [
"string",
"null"
]
},
"registry_index_sha256": {
"type": "string",
"pattern": "^[a-f0-9]{64}$"
},
"activation_manifest_sha256": {
"type": "string",
"pattern": "^[a-f0-9]{64}$"
},
"signal_manifest_transaction_id": {
"type": "string",
"pattern": "^S5TX-[a-f0-9]{20}$"
},
"expected_runnable_domain_ids": { "expected_runnable_domain_ids": {
"type": "array", "type": "array",
"items": {"type": "string", "pattern": "^(?:(?:EC|E)-[A-Z0-9]{2,8}|X[A-Z0-9]{1,8})$"} "items": {
"type": "string",
"pattern": "^(?:(?:EC|E)-[A-Z0-9]{2,8}|X[A-Z0-9]{1,8})$"
}
}, },
"theta": { "theta": {
"type": "array", "type": "array",
"items": {"type": "string", "pattern": "^[a-z][a-z0-9_]{1,127}$"} "items": {
"type": "string",
"pattern": "^[a-z][a-z0-9_]{1,127}$"
}
}, },
"structure_records": { "structure_records": {
"type": "array", "type": "array",
"items": { "items": {
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"required": ["structure_id", "domain_id", "type_id", "priority_rank", "order_key", "required": [
"module", "role_projection", "admission_policy", "routes", "route_count", "structure_id",
"evidence_refs", "meeting_clause_refs", "calculation_request_refs", "domain_id",
"source_bo_ids", "review_codes", "review_required"], "type_id",
"priority_rank",
"order_key",
"module",
"role_projection",
"admission_policy",
"routes",
"route_count",
"evidence_refs",
"meeting_clause_refs",
"calculation_request_refs",
"source_bo_ids",
"review_codes",
"review_required"
],
"properties": { "properties": {
"structure_id": {"type": "string", "pattern": "^LES-[A-Za-z0-9._:-]{2,64}-[a-z][a-z0-9_]{1,127}$"}, "structure_id": {
"domain_id": {"type": "string", "pattern": "^(?:(?:EC|E)-[A-Z0-9]{2,8}|X[A-Z0-9]{1,8})$"}, "type": "string",
"type_id": {"type": "string", "pattern": "^[a-z][a-z0-9_]{1,127}$"}, "pattern": "^LES-[A-Za-z0-9._:-]{2,64}-[a-z][a-z0-9_]{1,127}$"
"priority_rank": {"type": ["integer", "null"]}, },
"order_key": {"type": "array", "minItems": 3, "maxItems": 3}, "domain_id": {
"module": {"type": ["string", "null"]}, "type": "string",
"role_projection": {"type": ["string", "null"]}, "pattern": "^(?:(?:EC|E)-[A-Z0-9]{2,8}|X[A-Z0-9]{1,8})$"
"admission_policy": {"type": "string", "minLength": 1}, },
"admission_rule": {"type": "string"}, "type_id": {
"registered_declared": {"type": "boolean"}, "type": "string",
"extension_key": {"type": "string"}, "pattern": "^[a-z][a-z0-9_]{1,127}$"
},
"priority_rank": {
"type": [
"integer",
"null"
]
},
"order_key": {
"type": "array",
"minItems": 3,
"maxItems": 3
},
"module": {
"type": [
"string",
"null"
]
},
"role_projection": {
"type": [
"string",
"null"
]
},
"admission_policy": {
"type": "string",
"minLength": 1
},
"admission_rule": {
"type": "string"
},
"registered_declared": {
"type": "boolean"
},
"extension_key": {
"type": "string"
},
"routes": { "routes": {
"type": "array", "type": "array",
"items": { "items": {
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"required": ["signal_id", "source_seed_id", "registered", "status"], "required": [
"signal_id",
"source_seed_id",
"registered",
"status"
],
"properties": { "properties": {
"signal_id": {"type": ["string", "null"]}, "signal_id": {
"source_seed_id": {"type": ["string", "null"]}, "type": [
"registered": {"type": ["boolean", "null"]}, "string",
"status": {"type": ["string", "null"]} "null"
]
},
"source_seed_id": {
"type": [
"string",
"null"
]
},
"registered": {
"type": [
"boolean",
"null"
]
},
"status": {
"type": [
"string",
"null"
]
}
} }
} }
}, },
"route_count": {"type": "integer", "minimum": 0}, "route_count": {
"evidence_refs": {"type": "array", "items": {"type": "string"}}, "type": "integer",
"meeting_clause_refs": {"type": "array", "items": {"type": "string"}}, "minimum": 0
"calculation_request_refs": {"type": "array", "items": {"type": "string"}}, },
"source_bo_ids": {"type": "array", "items": {"type": "string"}}, "evidence_refs": {
"type": "array",
"items": {
"type": "string"
}
},
"meeting_clause_refs": {
"type": "array",
"items": {
"type": "string"
}
},
"calculation_request_refs": {
"type": "array",
"items": {
"type": "string"
}
},
"source_bo_ids": {
"type": "array",
"items": {
"type": "string"
}
},
"review_codes": { "review_codes": {
"type": "array", "type": "array",
"items": {"type": "string", "pattern": "^[A-Z][A-Z0-9_]{2,127}$"} "items": {
"type": "string",
"pattern": "^[A-Z][A-Z0-9_]{2,127}$"
}
}, },
"review_required": {"type": "boolean"} "review_required": {
"type": "boolean"
}
} }
} }
}, },
"structure_index": { "structure_index": {
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"required": ["by_domain_id", "by_module", "by_bo_id"], "required": [
"by_domain_id",
"by_module",
"by_bo_id"
],
"properties": { "properties": {
"by_domain_id": { "by_domain_id": {
"type": "object", "type": "object",
"additionalProperties": {"type": "array", "items": {"type": "string"}} "additionalProperties": {
"type": "array",
"items": {
"type": "string"
}
}
}, },
"by_module": { "by_module": {
"type": "object", "type": "object",
"additionalProperties": {"type": "array", "items": {"type": "string"}} "additionalProperties": {
"type": "array",
"items": {
"type": "string"
}
}
}, },
"by_bo_id": { "by_bo_id": {
"type": "object", "type": "object",
"additionalProperties": {"type": "array", "items": {"type": "string"}} "additionalProperties": {
"type": "array",
"items": {
"type": "string"
}
}
} }
} }
}, },
"quality_gate": { "quality_gate": {
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"required": ["declaration_conservation", "route_conservation", "bo_conservation", "required": [
"review_queue"], "declaration_conservation",
"route_conservation",
"bo_conservation",
"review_queue"
],
"properties": { "properties": {
"declaration_conservation": { "declaration_conservation": {
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"required": ["structure_records", "declared_total_from_config", "conservation_pass"], "required": [
"structure_records",
"declared_total_from_config",
"conservation_pass"
],
"properties": { "properties": {
"structure_records": {"type": "integer", "minimum": 0}, "structure_records": {
"declared_total_from_config": {"type": "integer", "minimum": 0}, "type": "integer",
"conservation_pass": {"type": "boolean"} "minimum": 0
},
"declared_total_from_config": {
"type": "integer",
"minimum": 0
},
"conservation_pass": {
"type": "boolean"
}
} }
}, },
"route_conservation": { "route_conservation": {
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"required": ["routes_attached", "routes_unattached", "sg13_route_total_from_source", "required": [
"conservation_pass"], "routes_attached",
"routes_unattached",
"sg13_route_total_from_source",
"conservation_pass"
],
"properties": { "properties": {
"routes_attached": {"type": "integer", "minimum": 0}, "routes_attached": {
"routes_unattached": {"type": "integer", "minimum": 0}, "type": "integer",
"sg13_route_total_from_source": {"type": "integer", "minimum": 0}, "minimum": 0
"conservation_pass": {"type": "boolean"} },
"routes_unattached": {
"type": "integer",
"minimum": 0
},
"sg13_route_total_from_source": {
"type": "integer",
"minimum": 0
},
"conservation_pass": {
"type": "boolean"
}
} }
}, },
"bo_conservation": { "bo_conservation": {
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"required": ["bo_attached", "bo_unattached", "bo_total_from_source", "required": [
"conservation_pass"], "bo_attached",
"bo_unattached",
"bo_total_from_source",
"conservation_pass"
],
"properties": { "properties": {
"bo_attached": {"type": "integer", "minimum": 0}, "bo_attached": {
"bo_unattached": {"type": "integer", "minimum": 0}, "type": "integer",
"bo_total_from_source": {"type": "integer", "minimum": 0}, "minimum": 0
"conservation_pass": {"type": "boolean"} },
"bo_unattached": {
"type": "integer",
"minimum": 0
},
"bo_total_from_source": {
"type": "integer",
"minimum": 0
},
"conservation_pass": {
"type": "boolean"
}
} }
}, },
"review_queue": { "review_queue": {
@@ -133,18 +338,44 @@
"items": { "items": {
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"required": ["review_code", "severity"], "required": [
"review_code",
"severity"
],
"properties": { "properties": {
"review_code": {"type": "string", "pattern": "^[A-Z][A-Z0-9_]{2,127}$"}, "review_code": {
"severity": {"enum": ["SOFT_WARNING", "HARD_WARNING"]}, "type": "string",
"domain_id": {"type": ["string", "null"]}, "pattern": "^[A-Z][A-Z0-9_]{2,127}$"
"structure_id": {"type": ["string", "null"]}, },
"count": {"type": "integer", "minimum": 1} "severity": {
"enum": [
"SOFT_WARNING",
"HARD_WARNING"
]
},
"domain_id": {
"type": [
"string",
"null"
]
},
"structure_id": {
"type": [
"string",
"null"
]
},
"count": {
"type": "integer",
"minimum": 1
}
} }
} }
} }
} }
}, },
"created_by": {"const": "Task_C_LE_L2_final_structure_index_writer"} "created_by": {
"const": "Task_C_LE_L2_final_structure_index_writer"
}
} }
} }
@@ -824,6 +824,10 @@
"path": "contracts/signals/s5_execution_contract.v2.json", "path": "contracts/signals/s5_execution_contract.v2.json",
"sha256": "311ac8721ec297bc86e54c83f0866d61629bd99f15746f86bbbff32914d35e02" "sha256": "311ac8721ec297bc86e54c83f0866d61629bd99f15746f86bbbff32914d35e02"
}, },
{
"path": "domains/E-00/gates.json",
"sha256": "665499a6eaa2466846633e26c3736379091b52ca7514c8ffc14c742728c84057"
},
{ {
"path": "domains/E-00/module_role_projection.json", "path": "domains/E-00/module_role_projection.json",
"sha256": "278ecdd4ac6c004f9994d6357ef439d089456a5e65bed0f74f49170ba3315932" "sha256": "278ecdd4ac6c004f9994d6357ef439d089456a5e65bed0f74f49170ba3315932"
@@ -832,6 +836,10 @@
"path": "domains/E-00/structure_types.json", "path": "domains/E-00/structure_types.json",
"sha256": "ae4a94fdbb0e2a8e0273a259e38c28774a3f2f24985878286f18a9ae05cbabaa" "sha256": "ae4a94fdbb0e2a8e0273a259e38c28774a3f2f24985878286f18a9ae05cbabaa"
}, },
{
"path": "domains/E-01/gates.json",
"sha256": "d3b5acefeaf2fd42cffabad18e1caa82dc5930c4d9638ac9f9caca20e58c7194"
},
{ {
"path": "domains/E-01/module_role_projection.json", "path": "domains/E-01/module_role_projection.json",
"sha256": "cb263ca339f7b749a51b57fa12d915fda3644f09e648d15a84202651a23a84fb" "sha256": "cb263ca339f7b749a51b57fa12d915fda3644f09e648d15a84202651a23a84fb"
@@ -840,6 +848,10 @@
"path": "domains/E-01/structure_types.json", "path": "domains/E-01/structure_types.json",
"sha256": "f2c423a0346a347292b3ca33d3a9f33a82d540e83cbb5f79bb8dae7161c68b26" "sha256": "f2c423a0346a347292b3ca33d3a9f33a82d540e83cbb5f79bb8dae7161c68b26"
}, },
{
"path": "domains/E-02/gates.json",
"sha256": "fe1bbd285fd0d6fd6116ea8cd4ff9196514be84a64e8891a30fa063d0283eff7"
},
{ {
"path": "domains/E-02/module_role_projection.json", "path": "domains/E-02/module_role_projection.json",
"sha256": "e21465cf806fb080d75f89526f9c142010c392ee87454a5a19248b9a80eb7c28" "sha256": "e21465cf806fb080d75f89526f9c142010c392ee87454a5a19248b9a80eb7c28"
@@ -848,6 +860,10 @@
"path": "domains/E-02/structure_types.json", "path": "domains/E-02/structure_types.json",
"sha256": "e470dd3c96f2f9b8bfa6c2c97b05259281005a84b6eea24c38d322abdf16ad8e" "sha256": "e470dd3c96f2f9b8bfa6c2c97b05259281005a84b6eea24c38d322abdf16ad8e"
}, },
{
"path": "domains/E-03/gates.json",
"sha256": "c1c7fb4b8685592a1bf1e09e78050944ccbafe25ec04164f6f1685b2a2e67820"
},
{ {
"path": "domains/E-03/module_role_projection.json", "path": "domains/E-03/module_role_projection.json",
"sha256": "f693146dfb3ba2ac5fa6a6221c6b2dc7e32084acb06823b1f46f423a56546704" "sha256": "f693146dfb3ba2ac5fa6a6221c6b2dc7e32084acb06823b1f46f423a56546704"
@@ -856,6 +872,10 @@
"path": "domains/E-03/structure_types.json", "path": "domains/E-03/structure_types.json",
"sha256": "70c579a03c41226e6383fe7b4aa5e60f4b0f5a723db30f6292dd2a0bde4d17bb" "sha256": "70c579a03c41226e6383fe7b4aa5e60f4b0f5a723db30f6292dd2a0bde4d17bb"
}, },
{
"path": "domains/E-04/gates.json",
"sha256": "190bd66c8d1df9d547879fb302b104fddca309f9983ee512932b9668cca9afaa"
},
{ {
"path": "domains/E-04/module_role_projection.json", "path": "domains/E-04/module_role_projection.json",
"sha256": "4c4bd473a12ad1dfcc8c9392503cd057017385a21287a89cb7ea147f3cf7d2d6" "sha256": "4c4bd473a12ad1dfcc8c9392503cd057017385a21287a89cb7ea147f3cf7d2d6"
@@ -864,6 +884,10 @@
"path": "domains/E-04/structure_types.json", "path": "domains/E-04/structure_types.json",
"sha256": "e8b15e9e5a39a488bf9ef660d69c7bc2db832248eb33788ba2ae54a9b5200e73" "sha256": "e8b15e9e5a39a488bf9ef660d69c7bc2db832248eb33788ba2ae54a9b5200e73"
}, },
{
"path": "domains/E-05/gates.json",
"sha256": "118d3b69eeb89b961a8a0bcdb1e011addbec8d24d5279faf0edfe39c907617f8"
},
{ {
"path": "domains/E-05/module_role_projection.json", "path": "domains/E-05/module_role_projection.json",
"sha256": "7fabfa3893a138dccb4e5bc797444f5f999c0e51ec6b0c8fbe0211eff3f03a6c" "sha256": "7fabfa3893a138dccb4e5bc797444f5f999c0e51ec6b0c8fbe0211eff3f03a6c"
@@ -872,6 +896,10 @@
"path": "domains/E-05/structure_types.json", "path": "domains/E-05/structure_types.json",
"sha256": "22707fbce849991c099a08885cac929d1ba842aa896945f9921c3214e4c701d0" "sha256": "22707fbce849991c099a08885cac929d1ba842aa896945f9921c3214e4c701d0"
}, },
{
"path": "domains/E-06/gates.json",
"sha256": "1f847a976bbcc37e37ed0daf5d141f7d611ed10823b7874e6ad7e6c7ce746926"
},
{ {
"path": "domains/E-06/module_role_projection.json", "path": "domains/E-06/module_role_projection.json",
"sha256": "805b73b2b15cb720b62cd83455de85a67bda434255b69d588eb98f2527f88b71" "sha256": "805b73b2b15cb720b62cd83455de85a67bda434255b69d588eb98f2527f88b71"
@@ -880,6 +908,10 @@
"path": "domains/E-06/structure_types.json", "path": "domains/E-06/structure_types.json",
"sha256": "a6520f72813caeaf72d62fd63eca10a1f46fd4fb3ffd97f97f222f37c53e8663" "sha256": "a6520f72813caeaf72d62fd63eca10a1f46fd4fb3ffd97f97f222f37c53e8663"
}, },
{
"path": "domains/E-07/gates.json",
"sha256": "9d6ed4dba2cf63de8688b4c047d01771bbb2d8cdab1674b9ea584ed3d570df32"
},
{ {
"path": "domains/E-07/module_role_projection.json", "path": "domains/E-07/module_role_projection.json",
"sha256": "9920ed415f9768beb517100e97fa7d2eb862ca90ca83f802ea460f8f6bbf2445" "sha256": "9920ed415f9768beb517100e97fa7d2eb862ca90ca83f802ea460f8f6bbf2445"
@@ -888,6 +920,10 @@
"path": "domains/E-07/structure_types.json", "path": "domains/E-07/structure_types.json",
"sha256": "fd4946e24cacc3bcc74c7fee5ce7fca99d3e5f2339fe3a9d698c54e2626a6c01" "sha256": "fd4946e24cacc3bcc74c7fee5ce7fca99d3e5f2339fe3a9d698c54e2626a6c01"
}, },
{
"path": "domains/E-08/gates.json",
"sha256": "b567e5a1862adbce09420dca71ee7cc32f8771f3efb8f2428f6110d3d396b488"
},
{ {
"path": "domains/E-08/module_role_projection.json", "path": "domains/E-08/module_role_projection.json",
"sha256": "4a8c284b5d3bbf8ca224e9f08398e6f563b00cec6de72f5128acc4cd8425eced" "sha256": "4a8c284b5d3bbf8ca224e9f08398e6f563b00cec6de72f5128acc4cd8425eced"
@@ -896,6 +932,10 @@
"path": "domains/E-08/structure_types.json", "path": "domains/E-08/structure_types.json",
"sha256": "a7ee6782c6541d0c6f6f6107a1d3615c2e7003a979741f2afc9d5d686a59e51d" "sha256": "a7ee6782c6541d0c6f6f6107a1d3615c2e7003a979741f2afc9d5d686a59e51d"
}, },
{
"path": "domains/E-09/gates.json",
"sha256": "8570e2704bdb75225bdb8391a89525a0d121b828d7fecafdc8ebb72c7754e6bf"
},
{ {
"path": "domains/E-09/module_role_projection.json", "path": "domains/E-09/module_role_projection.json",
"sha256": "ab93656808a832cfd73a6d75ed28d75e50e6cc66c67d59d765fcb8bbf638205f" "sha256": "ab93656808a832cfd73a6d75ed28d75e50e6cc66c67d59d765fcb8bbf638205f"
@@ -904,6 +944,10 @@
"path": "domains/E-09/structure_types.json", "path": "domains/E-09/structure_types.json",
"sha256": "c374b8b912679cc765b152c58ecd8320898f947b799e44ca509aa622669d7f4c" "sha256": "c374b8b912679cc765b152c58ecd8320898f947b799e44ca509aa622669d7f4c"
}, },
{
"path": "domains/E-10/gates.json",
"sha256": "31bf82ccc8b543a6e0a4a17275f18a42900bec6161cce41dbdf88100659773cf"
},
{ {
"path": "domains/E-10/module_role_projection.json", "path": "domains/E-10/module_role_projection.json",
"sha256": "80f760d9d8516cc81ebe503a2967418c43e11f8b8410231081111483dfa6477d" "sha256": "80f760d9d8516cc81ebe503a2967418c43e11f8b8410231081111483dfa6477d"
@@ -912,6 +956,10 @@
"path": "domains/E-10/structure_types.json", "path": "domains/E-10/structure_types.json",
"sha256": "07be97d40e422e14e570574c4e1da875307fe487c39e2ba9862ffd4b1623b748" "sha256": "07be97d40e422e14e570574c4e1da875307fe487c39e2ba9862ffd4b1623b748"
}, },
{
"path": "domains/E-11/gates.json",
"sha256": "a63a84e674e8b0f5919304000dde92f4786fe2fc3eab1214cc4f1b9fbc93fc09"
},
{ {
"path": "domains/E-11/module_role_projection.json", "path": "domains/E-11/module_role_projection.json",
"sha256": "0a55ce91b1845d64d085710110aec22bfcea66967ab5fce3a4acf5179faf65f1" "sha256": "0a55ce91b1845d64d085710110aec22bfcea66967ab5fce3a4acf5179faf65f1"
@@ -920,6 +968,10 @@
"path": "domains/E-11/structure_types.json", "path": "domains/E-11/structure_types.json",
"sha256": "6e565bab389f59866a0eafa8b7b7ff89f90ef2363ced3d9a4240100d3b3c3b90" "sha256": "6e565bab389f59866a0eafa8b7b7ff89f90ef2363ced3d9a4240100d3b3c3b90"
}, },
{
"path": "domains/E-12/gates.json",
"sha256": "b3013940aa8d496272378f76ddab7f3695f1bbc68a9773dfebf7a567ba693c42"
},
{ {
"path": "domains/E-12/module_role_projection.json", "path": "domains/E-12/module_role_projection.json",
"sha256": "207ef7d4a1300c12aa54cb1defa1110a6a9d18850d0fc4e144baebe6ff2b3c27" "sha256": "207ef7d4a1300c12aa54cb1defa1110a6a9d18850d0fc4e144baebe6ff2b3c27"
@@ -928,6 +980,10 @@
"path": "domains/E-12/structure_types.json", "path": "domains/E-12/structure_types.json",
"sha256": "ef97f4b0ed05637165bfb211fc9be0cb3bcf0523e6b5a645c007e8dc2825bea9" "sha256": "ef97f4b0ed05637165bfb211fc9be0cb3bcf0523e6b5a645c007e8dc2825bea9"
}, },
{
"path": "domains/E-13/gates.json",
"sha256": "3a9d36fe9c8de15853c6b9ff8d342f6e3d3b30431a4e5fdddb3bf34b13cd9aa9"
},
{ {
"path": "domains/E-13/module_role_projection.json", "path": "domains/E-13/module_role_projection.json",
"sha256": "412df6800be576d7f789bc7fc2045091eb9c49fd346e442ba42d6f29300f7313" "sha256": "412df6800be576d7f789bc7fc2045091eb9c49fd346e442ba42d6f29300f7313"
@@ -936,6 +992,10 @@
"path": "domains/E-13/structure_types.json", "path": "domains/E-13/structure_types.json",
"sha256": "cc18073ca443e98a34e139c08d11121ceb7d4ecd366ad3b3d5cb65ddb53e36f7" "sha256": "cc18073ca443e98a34e139c08d11121ceb7d4ecd366ad3b3d5cb65ddb53e36f7"
}, },
{
"path": "domains/E-14/gates.json",
"sha256": "36a08d9c88a6878fe0474b59411e8496a4b9a5674edcaf5e5f290cf75c047934"
},
{ {
"path": "domains/E-14/module_role_projection.json", "path": "domains/E-14/module_role_projection.json",
"sha256": "cead5634615b6b417390b37a948a050ea89cba37485bb79617bd1296c06829a5" "sha256": "cead5634615b6b417390b37a948a050ea89cba37485bb79617bd1296c06829a5"
@@ -944,6 +1004,10 @@
"path": "domains/E-14/structure_types.json", "path": "domains/E-14/structure_types.json",
"sha256": "5b0025a720a7b9f1ad7c74ce8d79f0969b282690abc0e3171e28dbf41933153b" "sha256": "5b0025a720a7b9f1ad7c74ce8d79f0969b282690abc0e3171e28dbf41933153b"
}, },
{
"path": "domains/E-15/gates.json",
"sha256": "059c800b75b7afa8836a9eeb1352fe99116d216f858374eb1cc847ec0c298eae"
},
{ {
"path": "domains/E-15/module_role_projection.json", "path": "domains/E-15/module_role_projection.json",
"sha256": "873f921bbbc86fc07a0c1ecfcbcacee67b1d9592b2efd7319290a10e8c1ba018" "sha256": "873f921bbbc86fc07a0c1ecfcbcacee67b1d9592b2efd7319290a10e8c1ba018"
@@ -952,6 +1016,10 @@
"path": "domains/E-15/structure_types.json", "path": "domains/E-15/structure_types.json",
"sha256": "784ceabd17c6de6d95a30f52dd5fd4036946fbedc556f2bad536225057a0f5fe" "sha256": "784ceabd17c6de6d95a30f52dd5fd4036946fbedc556f2bad536225057a0f5fe"
}, },
{
"path": "domains/E-16/gates.json",
"sha256": "eba2368cb418aa574b84b6e088ff73e60869802385505996b731cdeaeb4bc7bc"
},
{ {
"path": "domains/E-16/module_role_projection.json", "path": "domains/E-16/module_role_projection.json",
"sha256": "24ad172b32d4942be6f4031c582f928d3c6feb6e442027e3ee02700519744b11" "sha256": "24ad172b32d4942be6f4031c582f928d3c6feb6e442027e3ee02700519744b11"
@@ -960,6 +1028,10 @@
"path": "domains/E-16/structure_types.json", "path": "domains/E-16/structure_types.json",
"sha256": "b0bb30602ec424d35359c1a676ccca2bb2eda4a676997753da44d1dbbcc1f0b3" "sha256": "b0bb30602ec424d35359c1a676ccca2bb2eda4a676997753da44d1dbbcc1f0b3"
}, },
{
"path": "domains/E-17/gates.json",
"sha256": "b517109ee6df850682ada51c16a3c686680b72e50fe3e30b98486c84ca0cae8d"
},
{ {
"path": "domains/E-17/module_role_projection.json", "path": "domains/E-17/module_role_projection.json",
"sha256": "498e2320ad04a907680b8588ee1b1b9ee7487e131bece96a601979d60796f5a4" "sha256": "498e2320ad04a907680b8588ee1b1b9ee7487e131bece96a601979d60796f5a4"
@@ -968,6 +1040,10 @@
"path": "domains/E-17/structure_types.json", "path": "domains/E-17/structure_types.json",
"sha256": "d306dbdf583453d51f643eb8398920efb1bb5bfa82b3a600caa79f45f790b2eb" "sha256": "d306dbdf583453d51f643eb8398920efb1bb5bfa82b3a600caa79f45f790b2eb"
}, },
{
"path": "domains/E-18/gates.json",
"sha256": "c28bd022f7a873f2c48c679ff005749f8128f0eae69a41bbfa320f8b474b6742"
},
{ {
"path": "domains/E-18/module_role_projection.json", "path": "domains/E-18/module_role_projection.json",
"sha256": "b0699a793cdc52e6f74e9df3254f403858e83080be9e402bf3c7dae3cd8ffd0e" "sha256": "b0699a793cdc52e6f74e9df3254f403858e83080be9e402bf3c7dae3cd8ffd0e"
@@ -976,6 +1052,10 @@
"path": "domains/E-18/structure_types.json", "path": "domains/E-18/structure_types.json",
"sha256": "ce4eae3a54bfe7ac31f16b657cc0a891580e025f7bfecb1812c015e7fd0c2500" "sha256": "ce4eae3a54bfe7ac31f16b657cc0a891580e025f7bfecb1812c015e7fd0c2500"
}, },
{
"path": "domains/E-19/gates.json",
"sha256": "24b6ebcdf38678663890cc960877bf86124582531ede6ae751741fd3002c3704"
},
{ {
"path": "domains/E-19/module_role_projection.json", "path": "domains/E-19/module_role_projection.json",
"sha256": "00950a8c76d4367ccd6dc000476c810df33e49b07fce124545e350530b008b99" "sha256": "00950a8c76d4367ccd6dc000476c810df33e49b07fce124545e350530b008b99"
@@ -984,6 +1064,10 @@
"path": "domains/E-19/structure_types.json", "path": "domains/E-19/structure_types.json",
"sha256": "58b9503558bcab3fb7ae76532659d54694208d813197e97bac148d50795feb22" "sha256": "58b9503558bcab3fb7ae76532659d54694208d813197e97bac148d50795feb22"
}, },
{
"path": "domains/E-20/gates.json",
"sha256": "40314fa600094cb73634d7bef81804df35e6e713b5b11bd5e300a84add75a00f"
},
{ {
"path": "domains/E-20/module_role_projection.json", "path": "domains/E-20/module_role_projection.json",
"sha256": "7aef9706b50161693005ee61fc576a1562929fdf20830a3187fdda2a94fae8e3" "sha256": "7aef9706b50161693005ee61fc576a1562929fdf20830a3187fdda2a94fae8e3"
@@ -992,6 +1076,10 @@
"path": "domains/E-20/structure_types.json", "path": "domains/E-20/structure_types.json",
"sha256": "6766546fd61d3f46f0d0b71bc3c41d5c63a525954b2568d8003c9f5e90234074" "sha256": "6766546fd61d3f46f0d0b71bc3c41d5c63a525954b2568d8003c9f5e90234074"
}, },
{
"path": "domains/E-21/gates.json",
"sha256": "c3fc58291924b41cb7a22b97ffa5f4f573fab9ff39d3ffddd49587469f51cdd3"
},
{ {
"path": "domains/E-21/module_role_projection.json", "path": "domains/E-21/module_role_projection.json",
"sha256": "de6b0f29ad8100744a1a4fe8393a44a8a921d604b5244f832256450cb547722a" "sha256": "de6b0f29ad8100744a1a4fe8393a44a8a921d604b5244f832256450cb547722a"
@@ -1000,6 +1088,10 @@
"path": "domains/E-21/structure_types.json", "path": "domains/E-21/structure_types.json",
"sha256": "b027ff04f21cc4a86c8285e2cde6f8fd23e0ff3a3aeb3fa61a9bd10142814e36" "sha256": "b027ff04f21cc4a86c8285e2cde6f8fd23e0ff3a3aeb3fa61a9bd10142814e36"
}, },
{
"path": "domains/X1/gates.json",
"sha256": "cd68a09a8e1c99fd2c0ee236bd7754664161d4cab5385cf83df347eaefea13ee"
},
{ {
"path": "domains/X1/module_role_projection.json", "path": "domains/X1/module_role_projection.json",
"sha256": "1bceacd99f408384fec9547262017d8c857dbdb58ae906446c45ce8f1e322920" "sha256": "1bceacd99f408384fec9547262017d8c857dbdb58ae906446c45ce8f1e322920"
@@ -1008,6 +1100,10 @@
"path": "domains/X1/structure_types.json", "path": "domains/X1/structure_types.json",
"sha256": "3344a27beb548c7d0b083b6e442a8f70ca89ce02d22614fda83203d3ff53e7e9" "sha256": "3344a27beb548c7d0b083b6e442a8f70ca89ce02d22614fda83203d3ff53e7e9"
}, },
{
"path": "domains/X2/gates.json",
"sha256": "bf9d6fff3c80e0eb31359387bfd231137587a21f5429e5fc5e0e863ebe34c89e"
},
{ {
"path": "domains/X2/module_role_projection.json", "path": "domains/X2/module_role_projection.json",
"sha256": "9282422d06035a78b592c110c197c1afa58e78294566eecd2666f0c61820cd5f" "sha256": "9282422d06035a78b592c110c197c1afa58e78294566eecd2666f0c61820cd5f"
@@ -1016,6 +1112,10 @@
"path": "domains/X2/structure_types.json", "path": "domains/X2/structure_types.json",
"sha256": "f51c0404bab1c5f4506012460ba02faf2cee6c958d49ee353095366defacd2ec" "sha256": "f51c0404bab1c5f4506012460ba02faf2cee6c958d49ee353095366defacd2ec"
}, },
{
"path": "domains/X3/gates.json",
"sha256": "a3319e942736e35f399c25fdf8b00ef4ee35a44fcb669f7c1e350a2856eaeb98"
},
{ {
"path": "domains/X3/module_role_projection.json", "path": "domains/X3/module_role_projection.json",
"sha256": "93d3709c7d01132b82f0445d18eab7e666ea8d7fe2afe9b308bf87af5477be10" "sha256": "93d3709c7d01132b82f0445d18eab7e666ea8d7fe2afe9b308bf87af5477be10"
@@ -1292,6 +1392,10 @@
"path": "platform/reference_catalogs/domain_ids.json", "path": "platform/reference_catalogs/domain_ids.json",
"sha256": "4740d98e6a31bd0e7d9f792189712a8af1d0a87ec0bbd181deba1e34edb08355" "sha256": "4740d98e6a31bd0e7d9f792189712a8af1d0a87ec0bbd181deba1e34edb08355"
}, },
{
"path": "platform/reference_catalogs/profile_ids.json",
"sha256": "84f3538229df73e2d7ca58eb32da3d9b38ab1037edb67d4ac43e31e51c20aea3"
},
{ {
"path": "platform/router/s3_sg01_router_config.json", "path": "platform/router/s3_sg01_router_config.json",
"sha256": "4913b119f5ee08f96b52e10bd1f8376326564f1ab4742909fd2d0f07ded4efc6" "sha256": "4913b119f5ee08f96b52e10bd1f8376326564f1ab4742909fd2d0f07ded4efc6"
@@ -1320,9 +1424,21 @@
"path": "platform/schemas/domain_slice.schema.v2.json", "path": "platform/schemas/domain_slice.schema.v2.json",
"sha256": "212a405088e7cf7ba2c65528a1c716938c946df7fe3bae3256b613051ed31aa3" "sha256": "212a405088e7cf7ba2c65528a1c716938c946df7fe3bae3256b613051ed31aa3"
}, },
{
"path": "platform/schemas/fact_exception_pack.schema.json",
"sha256": "4eba7e51ed46a99e3704bc2333169749f4a16935de26a8c8027c1cac98ea58cf"
},
{
"path": "platform/schemas/fact_ledger_base.schema.json",
"sha256": "b3f0e79ecb4c2f720f3e07e89154aadbd2327e4129cc703569fb5635240d2fe8"
},
{
"path": "platform/schemas/fact_ledger_candidate_bundle.schema.json",
"sha256": "4e481504fb795b2be510680a8fa88124f5763a8124462f7870be4125ed9a7730"
},
{ {
"path": "platform/schemas/legal_effect_structures.schema.json", "path": "platform/schemas/legal_effect_structures.schema.json",
"sha256": "e2879c7a6d7f163c0729e8b863c5ef79b1094482eace78e6b8e1c7d69ce201e3" "sha256": "fc962e8ae39f9bede64ba017297eded6413689204a065e00c3b3bdca8f1854df"
}, },
{ {
"path": "platform/schemas/structure_seed_bundle.schema.json", "path": "platform/schemas/structure_seed_bundle.schema.json",
@@ -1640,17 +1756,29 @@
"path": "stage1_runtime/domain_slice_compiler.txt", "path": "stage1_runtime/domain_slice_compiler.txt",
"sha256": "c865eb281e201c5400d7b0d922b8d70f1df1d7de2001e66e3274fc5550a72526" "sha256": "c865eb281e201c5400d7b0d922b8d70f1df1d7de2001e66e3274fc5550a72526"
}, },
{
"path": "stage1_runtime/fact_ledger_compiler.py",
"sha256": "f6c4b476f4974104cd57accd559dcd91b31c80391077846dc3920d443c59a996"
},
{
"path": "stage1_runtime/fact_ledger_compiler.txt",
"sha256": "f6c4b476f4974104cd57accd559dcd91b31c80391077846dc3920d443c59a996"
},
{
"path": "stage1_runtime/fact_ledger_policy.v1.json",
"sha256": "fea0cea48d862c6c76774d6fee8ef84401a1b9fd753e86ccca76df32259523f7"
},
{ {
"path": "stage1_runtime/prompt_compiler.py", "path": "stage1_runtime/prompt_compiler.py",
"sha256": "6922aa5274cea35a98f927839c1db2ea631df467e499963dc35d456dbadac153" "sha256": "bfd80e90577d1f8be715cd581f8280f422dd8ba7cb526030cd9eaf7cb8151e30"
}, },
{ {
"path": "stage1_runtime/prompt_compiler.txt", "path": "stage1_runtime/prompt_compiler.txt",
"sha256": "6922aa5274cea35a98f927839c1db2ea631df467e499963dc35d456dbadac153" "sha256": "bfd80e90577d1f8be715cd581f8280f422dd8ba7cb526030cd9eaf7cb8151e30"
}, },
{ {
"path": "stage1_runtime/prompt_composition_policy.json", "path": "stage1_runtime/prompt_composition_policy.json",
"sha256": "06dbc4de60ab30525c2d4172f201e0b692210d9ff0a6c621d5c1caeb403f4e74" "sha256": "8d3f003ecc5e53eb7112115126e325b77292a27b0a542a10c8cecfe6c6bbfdea"
}, },
{ {
"path": "stage1_runtime/registry_loader.py", "path": "stage1_runtime/registry_loader.py",
@@ -1684,6 +1812,10 @@
"path": "stage1_runtime/schema_subset_validator.txt", "path": "stage1_runtime/schema_subset_validator.txt",
"sha256": "18d470ff8dc775ce4b7da490e1f503913b821f7d95ad3d22ad0b8061d18aa377" "sha256": "18d470ff8dc775ce4b7da490e1f503913b821f7d95ad3d22ad0b8061d18aa377"
}, },
{
"path": "stage1_runtime/seed_admission_policy.v1.json",
"sha256": "af048b2d1c9d01c7943658046dc3e8e9f3caee0901635dc344095982a7e05ba4"
},
{ {
"path": "stage1_runtime/stage_a_context_builder.py", "path": "stage1_runtime/stage_a_context_builder.py",
"sha256": "68d984f9d565034c73721dc97c5aab0f9cdb8a8b46e1bf13eabd8c136ec4bcc0" "sha256": "68d984f9d565034c73721dc97c5aab0f9cdb8a8b46e1bf13eabd8c136ec4bcc0"
@@ -1730,6 +1862,6 @@
} }
], ],
"program_release_status": "STAGE1_NOT_RELEASE_READY", "program_release_status": "STAGE1_NOT_RELEASE_READY",
"runtime_artifact_count": 432, "runtime_artifact_count": 465,
"schema_version": "stage1_runtime_manifest.v1" "schema_version": "stage1_runtime_manifest.v1"
} }
@@ -0,0 +1,910 @@
#!/usr/bin/env python3
"""Compile v.8 Fact Ledger candidate rows from BO, LES, signals, envelopes, and gate sidecars.
순수 함수 모듈 — I/O·시각·난수 없음. 규칙 선언은 fact_ledger_policy.v1.json 이 유일 정본이며
이 모듈은 그것을 읽어 dispatch 만 한다. gate_id·27필드·registry type_id 리터럴을 코드에 두지
않는다(정적 검사 11·12). 사건 종류 이름을 어디에도 두지 않는다(137종 원칙).
공개 API
normalize_gate_rules(sidecar_doc, policy) 17축 방언 정규화 리더
resolve_alias_table(alias_declarations, sidecars, policy)
compile_fact_ledger_candidates(...) 조립 본체 (아래 시그니처)
"""
from __future__ import annotations
import json
import re
from typing import Any
_WS_RE = re.compile(r"\s+")
_CODE_RE = re.compile(r"^[A-Z][A-Z0-9_]{2,127}$")
_DOMAIN_RE = re.compile(r"^(?:(?:EC|E)-[A-Z0-9]{2,8}|X[A-Z0-9]{1,8})$")
# ---------------------------------------------------------------------------
# 공용 헬퍼
# ---------------------------------------------------------------------------
def _dict(value: Any) -> dict:
return value if isinstance(value, dict) else {}
def _list(value: Any) -> list:
return value if isinstance(value, list) else []
def _strings(value: Any) -> list[str]:
return [item for item in _list(value) if isinstance(item, str)]
def _nonblank(value: Any) -> bool:
return value not in (None, "", [], {})
def _text(value: Any, limit: int) -> str | None:
"""키 오름차순 JSON -> 공백열 단일 공백 -> limit 절단 -> 빈 문자열이면 None."""
if value is None:
return None
if isinstance(value, str):
rendered = value
else:
rendered = json.dumps(value, ensure_ascii=False, sort_keys=True)
rendered = _WS_RE.sub(" ", rendered).strip()
if not rendered:
return None
return rendered[:limit]
def _first_value(*values: Any) -> Any:
for value in values:
if _nonblank(value):
return value
return None
def _first_key(record: dict, keys: list[str]) -> Any:
for key in keys:
if _nonblank(record.get(key)):
return record[key]
return None
def _dedupe_keep_order(items: list[str]) -> list[str]:
seen: set[str] = set()
out: list[str] = []
for item in items:
if item not in seen:
seen.add(item)
out.append(item)
return out
# ---------------------------------------------------------------------------
# F0-0c — BO 압축·정규화 (구본 _compact_bo 등가 계층. (가) 9필드 전부의 전제)
# ---------------------------------------------------------------------------
def compact_bo(bo: dict, policy: dict) -> dict:
norm = _dict(policy.get("bo_normalization"))
key_map = _dict(norm.get("key_map"))
keep = _strings(norm.get("domain_payload_keep_keys"))
out: dict[str, Any] = {}
for key, value in bo.items():
out[key_map.get(key, key)] = value
payload = _dict(_dict(bo.get("extensions")).get("domain_payload"))
out["domain_payload"] = {k: payload[k] for k in keep
if k in payload and _nonblank(payload[k])}
out["core"] = _dict(bo.get("core_field_base"))
return out
# ---------------------------------------------------------------------------
# (다) — 증거력 산정 (정책 evidence_scoring_da_2 의 5갈래 · 순서 그대로)
# ---------------------------------------------------------------------------
def _flatten_evidence(document: Any, depth: int = 3) -> list[dict]:
rows: list[dict] = []
def visit(node: Any, level: int) -> None:
if level > depth:
return
if isinstance(node, dict):
if any(k in node for k in ("evidence_index", "id", "evidence_id",
"eid", "index", "doc_id")):
rows.append(node)
return
for value in node.values():
visit(value, level + 1)
elif isinstance(node, list):
for item in node:
visit(item, level + 1)
visit(document, 0)
return rows
def _evidence_id(record: dict) -> str | None:
value = _first_key(record, ["evidence_index", "id", "evidence_id",
"eid", "index", "doc_id"])
return value if isinstance(value, str) else None
def _excerpt_from_indexed(record: dict) -> str:
facts = _strings(record.get("key_facts"))
if facts:
return " ".join(facts)
parts = _strings(record.get("key_dates")) + _strings(record.get("key_amounts"))
if parts:
return " ".join(parts)
for value in record.values():
if isinstance(value, str) and value.strip():
return value
return ""
def build_evidence_authority(evidence_document: Any) -> dict[str, dict]:
authority: dict[str, dict] = {}
for record in _flatten_evidence(evidence_document):
eid = _evidence_id(record)
if not eid:
continue
authority[eid] = {
"evidence_index": eid,
"title": _first_key(record, ["title", "Title", "name", "doc_title"]),
"excerpt": _excerpt_from_indexed(record),
"priority": _first_key(record, ["priority", "Priority", "weight"]),
"authentication": _first_key(record, ["authentication", "auth",
"source_authentication"]),
"source": "evidence_indexed.json",
}
return authority
def score_evidence(evidence_refs: list[str], authority: dict[str, dict],
policy: dict, reviews: list[dict],
fact_id: str) -> tuple[str, str, int]:
"""(credibility, proof_strength, 절단여유 최소값) — 정책 5갈래 순서 그대로."""
scoring = _dict(policy.get("evidence_scoring_da_2"))
markers = _strings(scoring.get("markers_documentary"))
rows: list[dict] = []
for ref in evidence_refs:
row = authority.get(ref)
if row is None:
# 우주 밖 참조 — stub 도 비어 있지 않은 증거 행으로 계수된다(정책 승계).
reviews.append({"review_code": "EVIDENCE_REF_NOT_IN_EVIDENCE_UNIVERSE",
"severity": "SOFT_WARNING", "fact_id": fact_id,
"detail": ref})
row = {"evidence_index": ref, "missing": True}
rows.append(row)
if not rows:
return "low", "low", -1
pieces: list[str] = []
slack = 10 ** 9
for row in rows:
rendered = _WS_RE.sub(" ", json.dumps(row, ensure_ascii=False,
sort_keys=True)).strip()
slack = min(slack, 300 - len(rendered))
pieces.append(rendered[:300])
text = " ".join(pieces)
if any(marker in text for marker in markers):
return "high", "documentary", slack
if len(rows) >= 2:
return "high", "high", slack
if ("meeting" in text) or ("회의" in text) or ("면담" in text):
return "medium", "meeting_only_core", slack
return "medium", "medium", slack
# ---------------------------------------------------------------------------
# F0-0 — 17축 사이드카 방언 정규화 리더
# ---------------------------------------------------------------------------
def normalize_gate_rules(sidecar_doc: Any, policy: dict) -> dict:
"""사이드카 한 파일을 정규화한다. {rules: {gate_id: norm}, file_meta, warnings}."""
axes = _dict(_dict(policy.get("gate_execution")).get("normalization_axes_17"))
array_keys = _strings(axes.get("rule_array_keys")) or ["rules", "gates"]
code_keys = _strings(axes.get("emitted_code_keys"))
doc = _dict(sidecar_doc)
raw_rules: list[dict] = []
array_key = None
for key in array_keys:
if isinstance(doc.get(key), list):
array_key = key
raw_rules = [r for r in doc[key] if isinstance(r, dict)]
break
warnings: list[dict] = []
norm_rules: dict[str, dict] = {}
for rule in raw_rules:
gate_id = rule.get("gate_id")
if not isinstance(gate_id, str):
warnings.append({"code": "GATE_RULE_WITHOUT_ID", "detail": str(rule)[:120]})
continue
writer = _dict(rule.get("writer_effect"))
row_eff = _dict(rule.get("row_effect"))
on_true = _dict(rule.get("on_true"))
condition = rule.get("condition")
cond_kind = "dict" if isinstance(condition, dict) else "str"
cond_key = next(iter(condition), None) if cond_kind == "dict" else None
cond_value = condition.get(cond_key) if cond_kind == "dict" else condition
emitted: list[str] = []
for key in code_keys:
value = rule.get(key)
if isinstance(value, str):
emitted.append(value)
else:
emitted.extend(_strings(value))
blocks = None
for container, key in (("writer_effect", "final_drafting_block"),
("writer_effect", "blocks_final_drafting"),
("on_true", "blocks_final_drafting")):
holder = writer if container == "writer_effect" else on_true
if key in holder:
blocks = bool(holder[key])
break
status_fire = (writer.get("status_when_triggered")
or writer.get("status_on_fire")
or on_true.get("writer_status"))
evaluation = (rule.get("evaluation_mode") or rule.get("implementation_type")
or rule.get("evaluation"))
norm_rules[gate_id] = {
"gate_id": gate_id,
"condition_kind": cond_kind,
"condition_key": cond_key,
"condition_value": cond_value,
"emitted_codes": _dedupe_keep_order(emitted),
"blocks_final_drafting": blocks,
"status_when_triggered": status_fire,
"row_must_consider": bool(row_eff.get("must_consider")),
"preserve_seed": bool(on_true.get("preserve_seed")),
"unknown_or_missing_policy": rule.get("unknown_or_missing_policy"),
"evaluation": evaluation,
"writes_fact_ledger": writer.get("writes_fact_ledger"),
"silent_repair_forbidden": rule.get("silent_repair_forbidden"),
"source_membership_required": rule.get("source_membership_required"),
"input_declaration": rule.get("input_fields") or rule.get("input_contracts"),
}
if doc.get("global_status_rule") is not None:
warnings.append({"code": "SIDECAR_GLOBAL_STATUS_RULE_IGNORED",
"detail": str(doc.get("global_status_rule"))[:120]})
return {"rules": norm_rules,
"file_meta": {"array_key": array_key,
"schema_version": doc.get("schema_version"),
"status_key": ("closure_status" if "closure_status" in doc
else ("status" if "status" in doc else None))},
"warnings": warnings}
# ---------------------------------------------------------------------------
# F0-0b — 별칭 해소 (D-P4-7 — 전수 자기선언 대조 · 침묵 탈락 금지)
# ---------------------------------------------------------------------------
def resolve_alias_table(alias_declarations: Any, structure_type_sidecars: dict,
policy: dict) -> dict:
root = _dict(alias_declarations)
root = _dict(root.get("extension_payload_key_declarations", root))
table = {k: _strings(v) for k, v in _dict(root.get("legacy_alias_targets")).items()}
derived_mismatches = _list(root.get("legacy_declaration_mismatches"))
abbrev = _dict(_dict(policy.get("legacy_alias")).get("abbreviation_map"))
self_decl: dict[str, set] = {}
for domain_id, doc in sorted(_dict(structure_type_sidecars).items()):
doc = _dict(doc)
rows = None
for key in ("types", "structure_types", "records", "entries"):
if isinstance(doc.get(key), list):
rows = doc[key]
break
for row in rows or []:
if not isinstance(row, dict):
continue
value = row.get("legacy_source") or row.get("legacy_source_domain")
if not isinstance(value, str):
continue
head = value.split("_")[0]
if head not in abbrev:
continue # 'Part3 fallback' 등 비 B* 값은 선언이 아니다
self_decl.setdefault(abbrev[head], set()).add(domain_id)
reviews: list[dict] = []
mismatch_map: dict[str, dict] = {}
for legacy in sorted(set(table) | set(self_decl)):
declared = self_decl.get(legacy, set())
aliased = set(table.get(legacy, []))
missing = sorted(declared - aliased)
excess = sorted(aliased - declared)
if missing:
mismatch_map[legacy] = {"missing": missing, "excess": excess}
reviews.append({"review_code": "LEGACY_ALIAS_SELF_MISMATCH_REVIEW",
"severity": "SOFT_WARNING", "legacy": legacy,
"missing_domain_ids": missing,
"excess_domain_ids": excess})
derived_missing = sorted({str(_dict(m).get("domain_id"))
for m in derived_mismatches if _dict(m).get("domain_id")})
actual_missing = sorted({d for m in mismatch_map.values() for d in m["missing"]})
if derived_missing != actual_missing:
reviews.append({"review_code": "LEGACY_ALIAS_DECLARATION_INCOMPLETE_REVIEW",
"severity": "SOFT_WARNING",
"derived_list": derived_missing,
"actual_missing": actual_missing})
return {"table": table, "self_declarations": {k: sorted(v) for k, v in self_decl.items()},
"mismatches": mismatch_map, "reviews": reviews}
# ---------------------------------------------------------------------------
# 조립 본체
# ---------------------------------------------------------------------------
def compile_fact_ledger_candidates(
bo_document: Any,
les_document: Any,
signal_documents: dict,
domain_envelopes: dict,
p2_handoff_document: Any,
evidence_document: Any,
registry_index: Any,
domain_configs: dict,
gate_sidecars: dict,
structure_type_sidecars: dict,
activation_document: Any,
alias_declarations: Any,
policy: dict) -> dict:
reviews: list[dict] = []
audit: dict[str, Any] = {}
activation_root = _dict(activation_document)
activation_root = _dict(activation_root.get("domain_activation_manifest",
activation_root))
expected_runnable = _strings(activation_root.get("expected_runnable_domain_ids"))
# ---- F0-0 리더 · F0-0b 별칭 ----------------------------------------
normalized: dict[str, dict] = {}
dialect_counts: dict[str, Any] = {"array_key": {}, "files": 0}
reader_warnings: list[dict] = []
for domain_id, sidecar in sorted(_dict(gate_sidecars).items()):
result = normalize_gate_rules(sidecar, policy)
normalized[domain_id] = result
dialect_counts["files"] += 1
ak = result["file_meta"]["array_key"]
dialect_counts["array_key"][ak] = dialect_counts["array_key"].get(ak, 0) + 1
reader_warnings.extend(result["warnings"])
audit["sidecar_dialects"] = dialect_counts
audit["reader_warnings"] = reader_warnings
alias = resolve_alias_table(alias_declarations, structure_type_sidecars, policy)
reviews.extend(alias["reviews"])
# ---- LES 적재 -------------------------------------------------------
les_root = _dict(les_document)
structure_records = [r for r in _list(les_root.get("structure_records"))
if isinstance(r, dict)]
record_by_id = {r.get("structure_id"): r for r in structure_records
if isinstance(r.get("structure_id"), str)}
index = _dict(les_root.get("structure_index"))
by_bo_id = {k: _strings(v) for k, v in _dict(index.get("by_bo_id")).items()}
# ---- SG 적재 --------------------------------------------------------
def signal_records(sg: str) -> list[dict]:
doc = _dict(_dict(signal_documents).get(sg))
return [r for r in _list(doc.get("records")) if isinstance(r, dict)]
sg11_records = signal_records("SG-11")
sg11_by_signal_id = {r.get("signal_id"): r for r in sg11_records
if isinstance(r.get("signal_id"), str)}
sg04_by_domain: dict[str, list[str]] = {}
for record in signal_records("SG-04"):
for domain_id in _strings(record.get("domain_ids")):
if isinstance(record.get("signal_id"), str):
sg04_by_domain.setdefault(domain_id, []).append(record["signal_id"])
# ---- F0-7b — 게이트 입력 정규화 ------------------------------------
handoff_root = _dict(p2_handoff_document)
handoff_root = _dict(handoff_root.get("stage1_part2_review_handoff", handoff_root))
handoff_items = [r for r in _list(handoff_root.get("review_items"))
if isinstance(r, dict)]
p2_rules = _dict(policy.get("p2_handoff_rules"))
code_pool: dict[str, list[str]] = {}
gate_input_counts = {"envelope_review_items": 0, "handoff_codes": 0,
"handoff_not_a_code": 0, "handoff_domain_unresolved": 0,
"slot_entries": 0, "operand_states": 0}
for domain_id in expected_runnable:
env = _dict(_dict(domain_envelopes).get(domain_id))
env = _dict(env.get("domain_signal_envelope", env))
for item in _list(env.get("review_items")):
code = _dict(item).get("review_code") or _dict(item).get("code")
if isinstance(code, str) and _CODE_RE.match(code):
code_pool.setdefault(domain_id, []).append(code)
gate_input_counts["envelope_review_items"] += 1
for item in handoff_items:
raw_code = item.get("source_review_code")
if not (isinstance(raw_code, str) and _CODE_RE.match(raw_code)):
if raw_code is not None:
gate_input_counts["handoff_not_a_code"] += 1
reviews.append({"review_code": "P2_HANDOFF_CODE_NOT_A_REVIEW_CODE_REVIEW",
"severity": "SOFT_WARNING",
"detail": str(raw_code)[:80]})
continue
source_domain = item.get("source_domain")
targets: list[str] = []
if isinstance(source_domain, str):
if _DOMAIN_RE.match(source_domain):
targets = [source_domain]
elif source_domain in alias["table"]:
targets = alias["table"][source_domain]
if not targets:
gate_input_counts["handoff_domain_unresolved"] += 1
reviews.append({"review_code": "P2_HANDOFF_DOMAIN_UNRESOLVED_REVIEW",
"severity": "SOFT_WARNING",
"detail": str(source_domain)[:80]})
continue
for domain_id in targets:
code_pool.setdefault(domain_id, []).append(raw_code)
gate_input_counts["handoff_codes"] += 1
slot_status: dict[str, list[dict]] = {}
for domain_id in expected_runnable:
env = _dict(_dict(domain_envelopes).get(domain_id))
env = _dict(env.get("domain_signal_envelope", env))
entries = [e for e in _list(env.get("evidence_slot_status"))
if isinstance(e, dict)]
if entries:
slot_status[domain_id] = entries
gate_input_counts["slot_entries"] += len(entries)
operand_by_domain: dict[str, list[str]] = {}
for record in sg11_records:
details = _dict(record.get("details"))
state = record.get("operand_state") or details.get("operand_state")
if not isinstance(state, str):
continue
for domain_id in (_strings(record.get("domain_ids"))
or _strings(details.get("domain_ids"))):
operand_by_domain.setdefault(domain_id, []).append(state)
gate_input_counts["operand_states"] += 1
audit["gate_input_counts"] = gate_input_counts
# ---- F0-1 ~ F0-6b — 행 생성 ----------------------------------------
bo_records = [b for b in _list(bo_document) if isinstance(b, dict)]
bo_sorted = sorted(bo_records, key=lambda b: str(b.get("BO_ID") or ""))
authority = build_evidence_authority(evidence_document)
calc_types = set(_strings(policy.get("legal_calculation_object_types")))
na_defaults = _dict(policy.get("field_synthesis_na_15"))
rows: list[dict] = []
attach_stats = {"attached_bo": 0, "unattached_bo": 0}
used_structure_ids: set[str] = set()
used_sg11: set[str] = set()
slack_min = 10 ** 9
for idx, bo in enumerate(bo_sorted, 1):
compact = compact_bo(bo, policy)
core = compact["core"]
fact_id = "F-%03d" % idx
bo_id = str(bo.get("BO_ID") or "")
# (가) 9필드
parties = []
if _nonblank(core.get("Performer")):
parties.append({"role": "actor", "value": _text(core.get("Performer"), 160),
"source": "BO.core_field_base"})
if _nonblank(core.get("Subject")):
parties.append({"role": "counterparty",
"value": _text(core.get("Subject"), 160),
"source": "BO.core_field_base"})
amount = _text(_first_value(compact.get("amount"), core.get("Amount")), 240)
action = _first_value(compact.get("action"), core.get("Action"))
evidence_refs = _strings(bo.get("source_evidence_indexes"))
# F0-3 — LES 조인
structure_ids = sorted(set(by_bo_id.get(bo_id, [])))
attached = [record_by_id[s] for s in structure_ids if s in record_by_id]
if attached:
attach_stats["attached_bo"] += 1
else:
attach_stats["unattached_bo"] += 1
reviews.append({"review_code": "FACT_ROW_UNATTACHED_REVIEW",
"severity": "SOFT_WARNING", "fact_id": fact_id,
"detail": bo_id})
used_structure_ids.update(s for s in structure_ids if s in record_by_id)
domain_effects: dict[str, dict] = {}
for record in attached:
domain_id = record.get("domain_id")
if not isinstance(domain_id, str):
continue
slot = domain_effects.setdefault(domain_id, {
"module": set(), "role_projection": set(), "structure_ids": set(),
"type_ids": set(), "review_codes": set()})
if isinstance(record.get("module"), str):
slot["module"].add(record["module"])
if isinstance(record.get("role_projection"), str):
slot["role_projection"].add(record["role_projection"])
if isinstance(record.get("structure_id"), str):
slot["structure_ids"].add(record["structure_id"])
if isinstance(record.get("type_id"), str):
slot["type_ids"].add(record["type_id"])
slot["review_codes"].update(
c for c in _strings(record.get("review_codes")) if _CODE_RE.match(c))
domain_effects = {d: {k: sorted(v) for k, v in slot.items()}
for d, slot in sorted(domain_effects.items())}
modules = sorted({m for slot in domain_effects.values() for m in slot["module"]})
roles = sorted({r for slot in domain_effects.values()
for r in slot["role_projection"]})
type_ids = sorted({t for slot in domain_effects.values()
for t in slot["type_ids"]})
# F0-5 — SG-11 조인 + operand_state 승계 (재산정 금지)
calculation_requests: list[dict] = []
route_refs: list[str] = []
for record in attached:
for ref in _strings(record.get("calculation_request_refs")):
route_refs.append(ref)
sg = sg11_by_signal_id.get(ref)
if sg is None:
calculation_requests.append({"calculation_id": None,
"request_id": ref,
"provenance": {"signal_id": ref,
"dangling": True}})
reviews.append({"review_code": "CALCULATION_REF_DANGLING_REVIEW",
"severity": "SOFT_WARNING", "fact_id": fact_id,
"detail": ref})
continue
used_sg11.add(ref)
details = _dict(sg.get("details"))
calc_id = details.get("calculation_id")
if not isinstance(calc_id, str) or not calc_id:
calc_id = ref
reviews.append({"review_code": "CALCULATION_ID_SUBSTITUTED_REVIEW",
"severity": "SOFT_WARNING", "fact_id": fact_id,
"detail": ref})
law_refs = sorted(set(
_strings(sg.get("law_version_refs"))
+ _strings(details.get("law_version_refs"))
+ [ref2 for d in _strings(sg.get("domain_ids"))
for ref2 in sg04_by_domain.get(d, [])]))
calculation_requests.append({
"calculation_id": calc_id,
"request_id": ref,
"operand_state": (sg.get("operand_state")
or details.get("operand_state")),
"missing_operands": _strings(details.get("missing_operands")),
"law_version_refs": law_refs,
"provenance": {"signal_id": ref,
"structure_ids": sorted(
s for s in structure_ids
if ref in _strings(
_dict(record_by_id.get(s)).get(
"calculation_request_refs")))},
})
# (다) 증거력
credibility, proof_strength, slack = score_evidence(
evidence_refs, authority, policy, reviews, fact_id)
slack_min = min(slack_min, slack)
# (나) 15필드
object_spec = _text(core.get("Object"), 160)
calc_obj_candidates = sorted(calc_types & set(type_ids))
legal_calculation_object = calc_obj_candidates[0] if calc_obj_candidates else None
alias_vocab = _dict(policy.get("alias_module_vocabulary"))
mod_actio = alias_vocab.get("actio_fields")
mod_money = alias_vocab.get("money_claim_effect")
mod_successor = alias_vocab.get("commercial_successor_effect")
actio_ids = sorted({s for slot in domain_effects.values()
for s in slot["structure_ids"]
if mod_actio in slot["module"]})
actio_roles = sorted({r for slot in domain_effects.values()
if mod_actio in slot["module"]
for r in slot["role_projection"]})
money_ids = sorted({s for slot in domain_effects.values()
if mod_money in slot["module"]
for s in slot["structure_ids"]})
successor_ids = sorted({s for slot in domain_effects.values()
if mod_successor in slot["module"]
for s in slot["structure_ids"]})
row = {
"fact_id": fact_id,
"source_bo_id": bo_id,
"type": compact.get("bo_type") if isinstance(compact.get("bo_type"), str) else None,
"date": _first_value(core.get("BehaviorTime"), compact.get("date")),
"parties": parties,
"object_spec": object_spec,
"amount": amount,
"action": action if isinstance(action, str) else _text(action, 240),
"evidence_refs": evidence_refs,
"credibility": credibility,
"legal_centrality": "high" if attached else "medium",
"proof_strength": proof_strength,
"legal_effect_roles": roles,
"claim_chain_ref": {"claim_group_ids": [], "liability_group_ids": [],
"signal_count": len(route_refs)},
"linked_structures": {"structure_ids": structure_ids},
"must_not_drop_in_claim_types": modules,
"must_consider": bool(attached),
"state_context": {"bo_type": compact.get("bo_type")
if isinstance(compact.get("bo_type"), str) else None,
"domain_ids": sorted(domain_effects)},
"money_claim_effect": {"enabled": bool(money_ids),
"structure_ids": money_ids},
"commercial_successor_effect": {"enabled": bool(successor_ids),
"structure_ids": successor_ids},
"actio_roles": actio_roles,
"linked_actio_structures": actio_ids,
"downstream_module_candidates": modules,
"skeleton_validation_codes": [],
"derived_fact": action if isinstance(action, str) else _text(action, 240),
"derivation_basis": ("structure_ids=" + ",".join(structure_ids)
if structure_ids else "bo_only"),
"legal_calculation_object": legal_calculation_object,
"domain_effects": domain_effects,
"calculation_requests": calculation_requests,
}
rows.append(row)
audit["evidence_truncation_slack_min"] = (None if slack_min == 10 ** 9 else slack_min)
# ---- F0-8 — 게이트 합집합 실행 (§4.4 그대로) ------------------------
gate_exec = _dict(policy.get("gate_execution"))
implemented = set(_strings(gate_exec.get("implemented_condition_keys")))
default_missing_policy = str(gate_exec.get("unknown_or_missing_policy_default")
or "fail_open_to_review")
declared_gates: dict[str, dict] = {}
for domain_id in expected_runnable:
config = _dict(_dict(domain_configs).get(domain_id))
config_root = _dict(config.get("domain_config", config))
for gate in _list(config_root.get("gates")):
gate = _dict(gate)
gate_id = gate.get("gate_id")
if isinstance(gate_id, str):
declared_gates[gate_id] = {"domain_id": domain_id, "declared": gate}
all_rules: dict[str, dict] = {}
for domain_id, result in normalized.items():
for gate_id, rule in result["rules"].items():
all_rules[gate_id] = {"domain_id": domain_id, "rule": rule}
gate_report: list[dict] = []
fired_by_domain: dict[str, list[dict]] = {}
for gate_id in sorted(declared_gates):
info = declared_gates[gate_id]
domain_id = info["domain_id"]
declared = info["declared"]
registry_codes = [c for c in _strings(declared.get("review_codes"))
if _CODE_RE.match(c)]
entry: dict[str, Any] = {"gate_id": gate_id, "domain_id": domain_id,
"fired": False, "input_state": "n/a"}
rule_info = all_rules.get(gate_id)
if rule_info is None:
entry["disposition"] = "GATE_SPEC_NOT_DEPLOYED_REVIEW"
reviews.append({"review_code": "GATE_SPEC_NOT_DEPLOYED_REVIEW",
"severity": "SOFT_WARNING", "gate_id": gate_id})
gate_report.append(entry)
continue
rule = rule_info["rule"]
fired = False
if rule["condition_kind"] == "dict":
key = rule["condition_key"]
if key in implemented:
entry["disposition"] = "implemented"
value = rule["condition_value"]
if key == "any_review_code_present":
pool = code_pool.get(domain_id)
if pool is None and domain_id not in _dict(domain_envelopes):
entry["input_state"] = "input_missing"
entry["missing_policy"] = (
rule["unknown_or_missing_policy"] or default_missing_policy)
else:
entry["input_state"] = "supplied"
fired = bool(set(_strings(value)) & set(pool or []))
elif key == "required_slots_missing_or_conflicting":
entries = slot_status.get(domain_id)
if entries is None and domain_id not in _dict(domain_envelopes):
entry["input_state"] = "input_missing"
entry["missing_policy"] = (
rule["unknown_or_missing_policy"] or default_missing_policy)
else:
entry["input_state"] = "supplied"
wanted = set(_strings(value))
fired = any(
(e.get("slot_id") in wanted)
and (e.get("status") in ("missing", "conflicted"))
for e in entries or [])
elif key == "calculation_operand_state_in":
states = operand_by_domain.get(domain_id)
entry["input_state"] = ("supplied" if states else "input_missing")
fired = bool(set(_strings(value)) & set(states or []))
else:
entry["disposition"] = "GATE_CONDITION_KEY_UNSUPPORTED_REVIEW"
entry["condition_key"] = key
reviews.append({"review_code": "GATE_CONDITION_KEY_UNSUPPORTED_REVIEW",
"severity": "SOFT_WARNING", "gate_id": gate_id,
"detail": str(key)})
else:
entry["disposition"] = "GATE_PREDICATE_SPEC_UNIMPLEMENTED_REVIEW"
entry["predicate_spec"] = _text(rule["condition_value"], 200)
reviews.append({"review_code": "GATE_PREDICATE_SPEC_UNIMPLEMENTED_REVIEW",
"severity": "SOFT_WARNING", "gate_id": gate_id,
"detail": _text(rule["condition_value"], 160)})
# 차단 표지 대조 — registry ↔ 정규화된 사이드카.
declared_blocks = declared.get("blocks_final_drafting")
if (rule["blocks_final_drafting"] is not None
and declared_blocks is not None
and bool(declared_blocks) != rule["blocks_final_drafting"]):
reviews.append({"review_code": "GATE_BLOCK_FLAG_MISMATCH_REVIEW",
"severity": "SOFT_WARNING", "gate_id": gate_id})
entry["block_flag_mismatch"] = True
emitted = _dedupe_keep_order(rule["emitted_codes"] + registry_codes)
excess = [c for c in rule["emitted_codes"] if c not in registry_codes]
if excess:
entry["codes_not_in_registry"] = excess
reviews.append({"review_code": "GATE_EMITTED_CODE_NOT_IN_REGISTRY",
"severity": "SOFT_WARNING", "gate_id": gate_id,
"detail": ",".join(excess)[:160]})
entry["fired"] = fired
entry["blocks_final_drafting"] = (rule["blocks_final_drafting"]
if rule["blocks_final_drafting"] is not None
else bool(declared_blocks))
if fired:
projection_rows = [r["fact_id"] for r in rows
if domain_id in r["domain_effects"]]
entry["projected_row_count"] = len(projection_rows)
fired_by_domain.setdefault(domain_id, []).append({
"gate_id": gate_id, "codes": emitted,
"row_must_consider": rule["row_must_consider"],
"preserve_seed": rule["preserve_seed"],
"blocks_final_drafting": entry["blocks_final_drafting"],
"status_when_triggered": rule["status_when_triggered"],
"projected_rows": projection_rows,
})
gate_report.append(entry)
orphan_rules = sorted(set(all_rules) - set(declared_gates))
for gate_id in orphan_rules:
reviews.append({"review_code": "GATE_RULE_NOT_IN_REGISTRY",
"severity": "SOFT_WARNING", "gate_id": gate_id})
# ---- 행 투영 3분기 + F0-8b 스켈레톤 --------------------------------
skeleton_order = _strings(_dict(policy.get("skeleton_checks")).get("order"))
for row in rows:
codes: list[str] = []
if not _nonblank(row["derived_fact"]):
codes.append(skeleton_order[0])
if not row["evidence_refs"]:
codes.append(skeleton_order[1])
if not row["linked_structures"]["structure_ids"]:
codes.append(skeleton_order[2])
gate_codes: list[str] = []
for domain_id in sorted(row["domain_effects"]):
for firing in sorted(fired_by_domain.get(domain_id, []),
key=lambda f: f["gate_id"]):
gate_codes.extend(firing["codes"])
if firing["row_must_consider"]:
row["must_consider"] = row["must_consider"] or True
row["skeleton_validation_codes"] = _dedupe_keep_order(codes + gate_codes)
# ---- STRUCTURE_UNATTACHED_REVIEW -----------------------------------
unattached_structures = sorted(set(record_by_id) - used_structure_ids)
for structure_id in unattached_structures:
reviews.append({"review_code": "STRUCTURE_UNATTACHED_REVIEW",
"severity": "SOFT_WARNING", "detail": structure_id})
# ---- F0-9 — 예외 pack ----------------------------------------------
pack_policy = _dict(policy.get("exception_pack"))
char_limit = int(pack_policy.get("pack_item_char_limit") or 2000)
exceptions: list[dict] = []
for row in rows:
if row["legal_calculation_object"] is None:
continue
bo = next((b for b in bo_sorted
if str(b.get("BO_ID") or "") == row["source_bo_id"]), {})
core = _dict(bo.get("core_field_base"))
amount_candidates = [v for v in (bo.get("amount"), core.get("Amount"))
if _nonblank(v)]
if len(amount_candidates) >= 2:
payload = {"amount_candidates": [_text(v, 240) for v in amount_candidates],
"object_spec": row["object_spec"],
"calculation_request_ids": sorted(
str(c.get("calculation_id")) for c in
row["calculation_requests"] if c.get("calculation_id"))}
item = {"exception_id": "FX-%03d" % (len(exceptions) + 1),
"exception_type": "legal_calculation_required",
"fact_id": row["fact_id"],
"source_bo_id": row["source_bo_id"],
"conflict_flags": ["CALCULATION_BASIS_CONFLICT"],
"compact_payload": payload}
rendered = json.dumps(item, ensure_ascii=False)
if len(rendered) > char_limit:
item["oversize_review"] = True
reviews.append({"review_code": "CALCULATION_BASIS_INCOMPLETE_REVIEW",
"severity": "SOFT_WARNING",
"fact_id": row["fact_id"],
"detail": "pack item oversize %d>%d" % (len(rendered),
char_limit)})
exceptions.append(item)
elif (not _nonblank(row["amount"])) or (not _nonblank(row["object_spec"])):
reviews.append({"review_code": "CALCULATION_BASIS_INCOMPLETE_REVIEW",
"severity": "SOFT_WARNING", "fact_id": row["fact_id"],
"detail": "amount_or_object_spec_blank"})
unreachable = {name for name, meta in
_dict(policy.get("f1_exception_types")).items()
if _dict(meta).get("unreachable_in_v8")}
for item in exceptions:
if item["exception_type"] in unreachable:
raise ValueError(json.dumps({
"reason_code": "PART4_UNREACHABLE_EXCEPTION_TYPE",
"exception_type": item["exception_type"]}, ensure_ascii=False))
# ---- F0-10 — 보존 등식 계수 ----------------------------------------
effect_left = sum(len(r["domain_effects"]) for r in rows)
effect_right = 0
calc_right = 0
for bo in bo_sorted:
bo_id = str(bo.get("BO_ID") or "")
attached = [record_by_id[s] for s in by_bo_id.get(bo_id, [])
if s in record_by_id]
effect_right += len({r.get("domain_id") for r in attached
if isinstance(r.get("domain_id"), str)})
calc_right += len({ref for r in attached
for ref in _strings(r.get("calculation_request_refs"))})
calc_left = sum(len(r["calculation_requests"]) for r in rows)
sg11_total = len(sg11_records)
sg11_unused = sg11_total - len(used_sg11)
equations = {
"row": {"left": len(rows), "right": len(bo_sorted),
"holds": len(rows) == len(bo_sorted)},
"attachment_coverage": {
"attached": attach_stats["attached_bo"],
"unattached": attach_stats["unattached_bo"],
"total": len(bo_sorted),
"holds": (attach_stats["attached_bo"] + attach_stats["unattached_bo"]
== len(bo_sorted))},
"effect": {"left": effect_left, "right": effect_right,
"holds": effect_left == effect_right},
"calc_row": {"left": calc_left, "right": calc_right,
"holds": calc_left == calc_right},
"calc_source": {"attached_distinct": len(used_sg11),
"unattached_distinct": sg11_unused,
"total": sg11_total,
"holds": len(used_sg11) + sg11_unused == sg11_total},
}
# ---- 상태·집계 ------------------------------------------------------
les_review_codes = set(_strings(policy.get("les_inherited_review_codes")))
les_review_present = any(
(set(slot["review_codes"]) & les_review_codes)
for row in rows for slot in row["domain_effects"].values()) or any(
_dict(r).get("review_required") for r in structure_records)
gate_fired_any = any(f for firings in fired_by_domain.values() for f in firings)
status = ("READY_WITH_REVIEW"
if (reviews or exceptions or les_review_present or gate_fired_any)
else "READY")
counts = {
"rows": len(rows),
"bo_records": len(bo_sorted),
"structure_records": len(structure_records),
"gates_declared": len(declared_gates),
"gates_implemented": sum(1 for e in gate_report
if e.get("disposition") == "implemented"),
"gates_visualized_str": sum(
1 for e in gate_report
if e.get("disposition") == "GATE_PREDICATE_SPEC_UNIMPLEMENTED_REVIEW"),
"gates_visualized_unsupported": sum(
1 for e in gate_report
if e.get("disposition") == "GATE_CONDITION_KEY_UNSUPPORTED_REVIEW"),
"gates_fired": sum(1 for e in gate_report if e.get("fired")),
"orphan_rules": len(orphan_rules),
"review_items": len(reviews),
"exceptions": len(exceptions),
"conservation": equations,
}
bundle = {
"schema_version": "stage1_fact_ledger_candidate_bundle.v1",
"status": status,
"rows": rows,
"gate_report": {"gates": gate_report,
"fired_by_domain": {d: f for d, f in
sorted(fired_by_domain.items())},
"orphan_rule_ids": orphan_rules},
"counts": counts,
"signal_manifest_transaction_id": None,
"expected_runnable_domain_ids": expected_runnable,
"alias_resolution": {"table": alias["table"],
"self_declarations": alias["self_declarations"],
"mismatches": alias["mismatches"]},
"created_by": "fact_ledger_compiler",
}
pack = {
"schema_version": "stage1_fact_exception_pack.v1",
"has_exceptions": bool(exceptions),
"exception_count": len(exceptions),
"exceptions": exceptions,
"budget": {"pack_item_char_limit": char_limit},
}
audit["unattached_structure_ids"] = unattached_structures
audit["equations"] = equations
return {"fact_ledger_candidate_bundle": bundle,
"fact_exception_pack": pack,
"review_items": reviews,
"audit": audit}
@@ -0,0 +1,910 @@
#!/usr/bin/env python3
"""Compile v.8 Fact Ledger candidate rows from BO, LES, signals, envelopes, and gate sidecars.
순수 함수 모듈 — I/O·시각·난수 없음. 규칙 선언은 fact_ledger_policy.v1.json 이 유일 정본이며
이 모듈은 그것을 읽어 dispatch 만 한다. gate_id·27필드·registry type_id 리터럴을 코드에 두지
않는다(정적 검사 11·12). 사건 종류 이름을 어디에도 두지 않는다(137종 원칙).
공개 API
normalize_gate_rules(sidecar_doc, policy) 17축 방언 정규화 리더
resolve_alias_table(alias_declarations, sidecars, policy)
compile_fact_ledger_candidates(...) 조립 본체 (아래 시그니처)
"""
from __future__ import annotations
import json
import re
from typing import Any
_WS_RE = re.compile(r"\s+")
_CODE_RE = re.compile(r"^[A-Z][A-Z0-9_]{2,127}$")
_DOMAIN_RE = re.compile(r"^(?:(?:EC|E)-[A-Z0-9]{2,8}|X[A-Z0-9]{1,8})$")
# ---------------------------------------------------------------------------
# 공용 헬퍼
# ---------------------------------------------------------------------------
def _dict(value: Any) -> dict:
return value if isinstance(value, dict) else {}
def _list(value: Any) -> list:
return value if isinstance(value, list) else []
def _strings(value: Any) -> list[str]:
return [item for item in _list(value) if isinstance(item, str)]
def _nonblank(value: Any) -> bool:
return value not in (None, "", [], {})
def _text(value: Any, limit: int) -> str | None:
"""키 오름차순 JSON -> 공백열 단일 공백 -> limit 절단 -> 빈 문자열이면 None."""
if value is None:
return None
if isinstance(value, str):
rendered = value
else:
rendered = json.dumps(value, ensure_ascii=False, sort_keys=True)
rendered = _WS_RE.sub(" ", rendered).strip()
if not rendered:
return None
return rendered[:limit]
def _first_value(*values: Any) -> Any:
for value in values:
if _nonblank(value):
return value
return None
def _first_key(record: dict, keys: list[str]) -> Any:
for key in keys:
if _nonblank(record.get(key)):
return record[key]
return None
def _dedupe_keep_order(items: list[str]) -> list[str]:
seen: set[str] = set()
out: list[str] = []
for item in items:
if item not in seen:
seen.add(item)
out.append(item)
return out
# ---------------------------------------------------------------------------
# F0-0c — BO 압축·정규화 (구본 _compact_bo 등가 계층. (가) 9필드 전부의 전제)
# ---------------------------------------------------------------------------
def compact_bo(bo: dict, policy: dict) -> dict:
norm = _dict(policy.get("bo_normalization"))
key_map = _dict(norm.get("key_map"))
keep = _strings(norm.get("domain_payload_keep_keys"))
out: dict[str, Any] = {}
for key, value in bo.items():
out[key_map.get(key, key)] = value
payload = _dict(_dict(bo.get("extensions")).get("domain_payload"))
out["domain_payload"] = {k: payload[k] for k in keep
if k in payload and _nonblank(payload[k])}
out["core"] = _dict(bo.get("core_field_base"))
return out
# ---------------------------------------------------------------------------
# (다) — 증거력 산정 (정책 evidence_scoring_da_2 의 5갈래 · 순서 그대로)
# ---------------------------------------------------------------------------
def _flatten_evidence(document: Any, depth: int = 3) -> list[dict]:
rows: list[dict] = []
def visit(node: Any, level: int) -> None:
if level > depth:
return
if isinstance(node, dict):
if any(k in node for k in ("evidence_index", "id", "evidence_id",
"eid", "index", "doc_id")):
rows.append(node)
return
for value in node.values():
visit(value, level + 1)
elif isinstance(node, list):
for item in node:
visit(item, level + 1)
visit(document, 0)
return rows
def _evidence_id(record: dict) -> str | None:
value = _first_key(record, ["evidence_index", "id", "evidence_id",
"eid", "index", "doc_id"])
return value if isinstance(value, str) else None
def _excerpt_from_indexed(record: dict) -> str:
facts = _strings(record.get("key_facts"))
if facts:
return " ".join(facts)
parts = _strings(record.get("key_dates")) + _strings(record.get("key_amounts"))
if parts:
return " ".join(parts)
for value in record.values():
if isinstance(value, str) and value.strip():
return value
return ""
def build_evidence_authority(evidence_document: Any) -> dict[str, dict]:
authority: dict[str, dict] = {}
for record in _flatten_evidence(evidence_document):
eid = _evidence_id(record)
if not eid:
continue
authority[eid] = {
"evidence_index": eid,
"title": _first_key(record, ["title", "Title", "name", "doc_title"]),
"excerpt": _excerpt_from_indexed(record),
"priority": _first_key(record, ["priority", "Priority", "weight"]),
"authentication": _first_key(record, ["authentication", "auth",
"source_authentication"]),
"source": "evidence_indexed.json",
}
return authority
def score_evidence(evidence_refs: list[str], authority: dict[str, dict],
policy: dict, reviews: list[dict],
fact_id: str) -> tuple[str, str, int]:
"""(credibility, proof_strength, 절단여유 최소값) — 정책 5갈래 순서 그대로."""
scoring = _dict(policy.get("evidence_scoring_da_2"))
markers = _strings(scoring.get("markers_documentary"))
rows: list[dict] = []
for ref in evidence_refs:
row = authority.get(ref)
if row is None:
# 우주 밖 참조 — stub 도 비어 있지 않은 증거 행으로 계수된다(정책 승계).
reviews.append({"review_code": "EVIDENCE_REF_NOT_IN_EVIDENCE_UNIVERSE",
"severity": "SOFT_WARNING", "fact_id": fact_id,
"detail": ref})
row = {"evidence_index": ref, "missing": True}
rows.append(row)
if not rows:
return "low", "low", -1
pieces: list[str] = []
slack = 10 ** 9
for row in rows:
rendered = _WS_RE.sub(" ", json.dumps(row, ensure_ascii=False,
sort_keys=True)).strip()
slack = min(slack, 300 - len(rendered))
pieces.append(rendered[:300])
text = " ".join(pieces)
if any(marker in text for marker in markers):
return "high", "documentary", slack
if len(rows) >= 2:
return "high", "high", slack
if ("meeting" in text) or ("회의" in text) or ("면담" in text):
return "medium", "meeting_only_core", slack
return "medium", "medium", slack
# ---------------------------------------------------------------------------
# F0-0 — 17축 사이드카 방언 정규화 리더
# ---------------------------------------------------------------------------
def normalize_gate_rules(sidecar_doc: Any, policy: dict) -> dict:
"""사이드카 한 파일을 정규화한다. {rules: {gate_id: norm}, file_meta, warnings}."""
axes = _dict(_dict(policy.get("gate_execution")).get("normalization_axes_17"))
array_keys = _strings(axes.get("rule_array_keys")) or ["rules", "gates"]
code_keys = _strings(axes.get("emitted_code_keys"))
doc = _dict(sidecar_doc)
raw_rules: list[dict] = []
array_key = None
for key in array_keys:
if isinstance(doc.get(key), list):
array_key = key
raw_rules = [r for r in doc[key] if isinstance(r, dict)]
break
warnings: list[dict] = []
norm_rules: dict[str, dict] = {}
for rule in raw_rules:
gate_id = rule.get("gate_id")
if not isinstance(gate_id, str):
warnings.append({"code": "GATE_RULE_WITHOUT_ID", "detail": str(rule)[:120]})
continue
writer = _dict(rule.get("writer_effect"))
row_eff = _dict(rule.get("row_effect"))
on_true = _dict(rule.get("on_true"))
condition = rule.get("condition")
cond_kind = "dict" if isinstance(condition, dict) else "str"
cond_key = next(iter(condition), None) if cond_kind == "dict" else None
cond_value = condition.get(cond_key) if cond_kind == "dict" else condition
emitted: list[str] = []
for key in code_keys:
value = rule.get(key)
if isinstance(value, str):
emitted.append(value)
else:
emitted.extend(_strings(value))
blocks = None
for container, key in (("writer_effect", "final_drafting_block"),
("writer_effect", "blocks_final_drafting"),
("on_true", "blocks_final_drafting")):
holder = writer if container == "writer_effect" else on_true
if key in holder:
blocks = bool(holder[key])
break
status_fire = (writer.get("status_when_triggered")
or writer.get("status_on_fire")
or on_true.get("writer_status"))
evaluation = (rule.get("evaluation_mode") or rule.get("implementation_type")
or rule.get("evaluation"))
norm_rules[gate_id] = {
"gate_id": gate_id,
"condition_kind": cond_kind,
"condition_key": cond_key,
"condition_value": cond_value,
"emitted_codes": _dedupe_keep_order(emitted),
"blocks_final_drafting": blocks,
"status_when_triggered": status_fire,
"row_must_consider": bool(row_eff.get("must_consider")),
"preserve_seed": bool(on_true.get("preserve_seed")),
"unknown_or_missing_policy": rule.get("unknown_or_missing_policy"),
"evaluation": evaluation,
"writes_fact_ledger": writer.get("writes_fact_ledger"),
"silent_repair_forbidden": rule.get("silent_repair_forbidden"),
"source_membership_required": rule.get("source_membership_required"),
"input_declaration": rule.get("input_fields") or rule.get("input_contracts"),
}
if doc.get("global_status_rule") is not None:
warnings.append({"code": "SIDECAR_GLOBAL_STATUS_RULE_IGNORED",
"detail": str(doc.get("global_status_rule"))[:120]})
return {"rules": norm_rules,
"file_meta": {"array_key": array_key,
"schema_version": doc.get("schema_version"),
"status_key": ("closure_status" if "closure_status" in doc
else ("status" if "status" in doc else None))},
"warnings": warnings}
# ---------------------------------------------------------------------------
# F0-0b — 별칭 해소 (D-P4-7 — 전수 자기선언 대조 · 침묵 탈락 금지)
# ---------------------------------------------------------------------------
def resolve_alias_table(alias_declarations: Any, structure_type_sidecars: dict,
policy: dict) -> dict:
root = _dict(alias_declarations)
root = _dict(root.get("extension_payload_key_declarations", root))
table = {k: _strings(v) for k, v in _dict(root.get("legacy_alias_targets")).items()}
derived_mismatches = _list(root.get("legacy_declaration_mismatches"))
abbrev = _dict(_dict(policy.get("legacy_alias")).get("abbreviation_map"))
self_decl: dict[str, set] = {}
for domain_id, doc in sorted(_dict(structure_type_sidecars).items()):
doc = _dict(doc)
rows = None
for key in ("types", "structure_types", "records", "entries"):
if isinstance(doc.get(key), list):
rows = doc[key]
break
for row in rows or []:
if not isinstance(row, dict):
continue
value = row.get("legacy_source") or row.get("legacy_source_domain")
if not isinstance(value, str):
continue
head = value.split("_")[0]
if head not in abbrev:
continue # 'Part3 fallback' 등 비 B* 값은 선언이 아니다
self_decl.setdefault(abbrev[head], set()).add(domain_id)
reviews: list[dict] = []
mismatch_map: dict[str, dict] = {}
for legacy in sorted(set(table) | set(self_decl)):
declared = self_decl.get(legacy, set())
aliased = set(table.get(legacy, []))
missing = sorted(declared - aliased)
excess = sorted(aliased - declared)
if missing:
mismatch_map[legacy] = {"missing": missing, "excess": excess}
reviews.append({"review_code": "LEGACY_ALIAS_SELF_MISMATCH_REVIEW",
"severity": "SOFT_WARNING", "legacy": legacy,
"missing_domain_ids": missing,
"excess_domain_ids": excess})
derived_missing = sorted({str(_dict(m).get("domain_id"))
for m in derived_mismatches if _dict(m).get("domain_id")})
actual_missing = sorted({d for m in mismatch_map.values() for d in m["missing"]})
if derived_missing != actual_missing:
reviews.append({"review_code": "LEGACY_ALIAS_DECLARATION_INCOMPLETE_REVIEW",
"severity": "SOFT_WARNING",
"derived_list": derived_missing,
"actual_missing": actual_missing})
return {"table": table, "self_declarations": {k: sorted(v) for k, v in self_decl.items()},
"mismatches": mismatch_map, "reviews": reviews}
# ---------------------------------------------------------------------------
# 조립 본체
# ---------------------------------------------------------------------------
def compile_fact_ledger_candidates(
bo_document: Any,
les_document: Any,
signal_documents: dict,
domain_envelopes: dict,
p2_handoff_document: Any,
evidence_document: Any,
registry_index: Any,
domain_configs: dict,
gate_sidecars: dict,
structure_type_sidecars: dict,
activation_document: Any,
alias_declarations: Any,
policy: dict) -> dict:
reviews: list[dict] = []
audit: dict[str, Any] = {}
activation_root = _dict(activation_document)
activation_root = _dict(activation_root.get("domain_activation_manifest",
activation_root))
expected_runnable = _strings(activation_root.get("expected_runnable_domain_ids"))
# ---- F0-0 리더 · F0-0b 별칭 ----------------------------------------
normalized: dict[str, dict] = {}
dialect_counts: dict[str, Any] = {"array_key": {}, "files": 0}
reader_warnings: list[dict] = []
for domain_id, sidecar in sorted(_dict(gate_sidecars).items()):
result = normalize_gate_rules(sidecar, policy)
normalized[domain_id] = result
dialect_counts["files"] += 1
ak = result["file_meta"]["array_key"]
dialect_counts["array_key"][ak] = dialect_counts["array_key"].get(ak, 0) + 1
reader_warnings.extend(result["warnings"])
audit["sidecar_dialects"] = dialect_counts
audit["reader_warnings"] = reader_warnings
alias = resolve_alias_table(alias_declarations, structure_type_sidecars, policy)
reviews.extend(alias["reviews"])
# ---- LES 적재 -------------------------------------------------------
les_root = _dict(les_document)
structure_records = [r for r in _list(les_root.get("structure_records"))
if isinstance(r, dict)]
record_by_id = {r.get("structure_id"): r for r in structure_records
if isinstance(r.get("structure_id"), str)}
index = _dict(les_root.get("structure_index"))
by_bo_id = {k: _strings(v) for k, v in _dict(index.get("by_bo_id")).items()}
# ---- SG 적재 --------------------------------------------------------
def signal_records(sg: str) -> list[dict]:
doc = _dict(_dict(signal_documents).get(sg))
return [r for r in _list(doc.get("records")) if isinstance(r, dict)]
sg11_records = signal_records("SG-11")
sg11_by_signal_id = {r.get("signal_id"): r for r in sg11_records
if isinstance(r.get("signal_id"), str)}
sg04_by_domain: dict[str, list[str]] = {}
for record in signal_records("SG-04"):
for domain_id in _strings(record.get("domain_ids")):
if isinstance(record.get("signal_id"), str):
sg04_by_domain.setdefault(domain_id, []).append(record["signal_id"])
# ---- F0-7b — 게이트 입력 정규화 ------------------------------------
handoff_root = _dict(p2_handoff_document)
handoff_root = _dict(handoff_root.get("stage1_part2_review_handoff", handoff_root))
handoff_items = [r for r in _list(handoff_root.get("review_items"))
if isinstance(r, dict)]
p2_rules = _dict(policy.get("p2_handoff_rules"))
code_pool: dict[str, list[str]] = {}
gate_input_counts = {"envelope_review_items": 0, "handoff_codes": 0,
"handoff_not_a_code": 0, "handoff_domain_unresolved": 0,
"slot_entries": 0, "operand_states": 0}
for domain_id in expected_runnable:
env = _dict(_dict(domain_envelopes).get(domain_id))
env = _dict(env.get("domain_signal_envelope", env))
for item in _list(env.get("review_items")):
code = _dict(item).get("review_code") or _dict(item).get("code")
if isinstance(code, str) and _CODE_RE.match(code):
code_pool.setdefault(domain_id, []).append(code)
gate_input_counts["envelope_review_items"] += 1
for item in handoff_items:
raw_code = item.get("source_review_code")
if not (isinstance(raw_code, str) and _CODE_RE.match(raw_code)):
if raw_code is not None:
gate_input_counts["handoff_not_a_code"] += 1
reviews.append({"review_code": "P2_HANDOFF_CODE_NOT_A_REVIEW_CODE_REVIEW",
"severity": "SOFT_WARNING",
"detail": str(raw_code)[:80]})
continue
source_domain = item.get("source_domain")
targets: list[str] = []
if isinstance(source_domain, str):
if _DOMAIN_RE.match(source_domain):
targets = [source_domain]
elif source_domain in alias["table"]:
targets = alias["table"][source_domain]
if not targets:
gate_input_counts["handoff_domain_unresolved"] += 1
reviews.append({"review_code": "P2_HANDOFF_DOMAIN_UNRESOLVED_REVIEW",
"severity": "SOFT_WARNING",
"detail": str(source_domain)[:80]})
continue
for domain_id in targets:
code_pool.setdefault(domain_id, []).append(raw_code)
gate_input_counts["handoff_codes"] += 1
slot_status: dict[str, list[dict]] = {}
for domain_id in expected_runnable:
env = _dict(_dict(domain_envelopes).get(domain_id))
env = _dict(env.get("domain_signal_envelope", env))
entries = [e for e in _list(env.get("evidence_slot_status"))
if isinstance(e, dict)]
if entries:
slot_status[domain_id] = entries
gate_input_counts["slot_entries"] += len(entries)
operand_by_domain: dict[str, list[str]] = {}
for record in sg11_records:
details = _dict(record.get("details"))
state = record.get("operand_state") or details.get("operand_state")
if not isinstance(state, str):
continue
for domain_id in (_strings(record.get("domain_ids"))
or _strings(details.get("domain_ids"))):
operand_by_domain.setdefault(domain_id, []).append(state)
gate_input_counts["operand_states"] += 1
audit["gate_input_counts"] = gate_input_counts
# ---- F0-1 ~ F0-6b — 행 생성 ----------------------------------------
bo_records = [b for b in _list(bo_document) if isinstance(b, dict)]
bo_sorted = sorted(bo_records, key=lambda b: str(b.get("BO_ID") or ""))
authority = build_evidence_authority(evidence_document)
calc_types = set(_strings(policy.get("legal_calculation_object_types")))
na_defaults = _dict(policy.get("field_synthesis_na_15"))
rows: list[dict] = []
attach_stats = {"attached_bo": 0, "unattached_bo": 0}
used_structure_ids: set[str] = set()
used_sg11: set[str] = set()
slack_min = 10 ** 9
for idx, bo in enumerate(bo_sorted, 1):
compact = compact_bo(bo, policy)
core = compact["core"]
fact_id = "F-%03d" % idx
bo_id = str(bo.get("BO_ID") or "")
# (가) 9필드
parties = []
if _nonblank(core.get("Performer")):
parties.append({"role": "actor", "value": _text(core.get("Performer"), 160),
"source": "BO.core_field_base"})
if _nonblank(core.get("Subject")):
parties.append({"role": "counterparty",
"value": _text(core.get("Subject"), 160),
"source": "BO.core_field_base"})
amount = _text(_first_value(compact.get("amount"), core.get("Amount")), 240)
action = _first_value(compact.get("action"), core.get("Action"))
evidence_refs = _strings(bo.get("source_evidence_indexes"))
# F0-3 — LES 조인
structure_ids = sorted(set(by_bo_id.get(bo_id, [])))
attached = [record_by_id[s] for s in structure_ids if s in record_by_id]
if attached:
attach_stats["attached_bo"] += 1
else:
attach_stats["unattached_bo"] += 1
reviews.append({"review_code": "FACT_ROW_UNATTACHED_REVIEW",
"severity": "SOFT_WARNING", "fact_id": fact_id,
"detail": bo_id})
used_structure_ids.update(s for s in structure_ids if s in record_by_id)
domain_effects: dict[str, dict] = {}
for record in attached:
domain_id = record.get("domain_id")
if not isinstance(domain_id, str):
continue
slot = domain_effects.setdefault(domain_id, {
"module": set(), "role_projection": set(), "structure_ids": set(),
"type_ids": set(), "review_codes": set()})
if isinstance(record.get("module"), str):
slot["module"].add(record["module"])
if isinstance(record.get("role_projection"), str):
slot["role_projection"].add(record["role_projection"])
if isinstance(record.get("structure_id"), str):
slot["structure_ids"].add(record["structure_id"])
if isinstance(record.get("type_id"), str):
slot["type_ids"].add(record["type_id"])
slot["review_codes"].update(
c for c in _strings(record.get("review_codes")) if _CODE_RE.match(c))
domain_effects = {d: {k: sorted(v) for k, v in slot.items()}
for d, slot in sorted(domain_effects.items())}
modules = sorted({m for slot in domain_effects.values() for m in slot["module"]})
roles = sorted({r for slot in domain_effects.values()
for r in slot["role_projection"]})
type_ids = sorted({t for slot in domain_effects.values()
for t in slot["type_ids"]})
# F0-5 — SG-11 조인 + operand_state 승계 (재산정 금지)
calculation_requests: list[dict] = []
route_refs: list[str] = []
for record in attached:
for ref in _strings(record.get("calculation_request_refs")):
route_refs.append(ref)
sg = sg11_by_signal_id.get(ref)
if sg is None:
calculation_requests.append({"calculation_id": None,
"request_id": ref,
"provenance": {"signal_id": ref,
"dangling": True}})
reviews.append({"review_code": "CALCULATION_REF_DANGLING_REVIEW",
"severity": "SOFT_WARNING", "fact_id": fact_id,
"detail": ref})
continue
used_sg11.add(ref)
details = _dict(sg.get("details"))
calc_id = details.get("calculation_id")
if not isinstance(calc_id, str) or not calc_id:
calc_id = ref
reviews.append({"review_code": "CALCULATION_ID_SUBSTITUTED_REVIEW",
"severity": "SOFT_WARNING", "fact_id": fact_id,
"detail": ref})
law_refs = sorted(set(
_strings(sg.get("law_version_refs"))
+ _strings(details.get("law_version_refs"))
+ [ref2 for d in _strings(sg.get("domain_ids"))
for ref2 in sg04_by_domain.get(d, [])]))
calculation_requests.append({
"calculation_id": calc_id,
"request_id": ref,
"operand_state": (sg.get("operand_state")
or details.get("operand_state")),
"missing_operands": _strings(details.get("missing_operands")),
"law_version_refs": law_refs,
"provenance": {"signal_id": ref,
"structure_ids": sorted(
s for s in structure_ids
if ref in _strings(
_dict(record_by_id.get(s)).get(
"calculation_request_refs")))},
})
# (다) 증거력
credibility, proof_strength, slack = score_evidence(
evidence_refs, authority, policy, reviews, fact_id)
slack_min = min(slack_min, slack)
# (나) 15필드
object_spec = _text(core.get("Object"), 160)
calc_obj_candidates = sorted(calc_types & set(type_ids))
legal_calculation_object = calc_obj_candidates[0] if calc_obj_candidates else None
alias_vocab = _dict(policy.get("alias_module_vocabulary"))
mod_actio = alias_vocab.get("actio_fields")
mod_money = alias_vocab.get("money_claim_effect")
mod_successor = alias_vocab.get("commercial_successor_effect")
actio_ids = sorted({s for slot in domain_effects.values()
for s in slot["structure_ids"]
if mod_actio in slot["module"]})
actio_roles = sorted({r for slot in domain_effects.values()
if mod_actio in slot["module"]
for r in slot["role_projection"]})
money_ids = sorted({s for slot in domain_effects.values()
if mod_money in slot["module"]
for s in slot["structure_ids"]})
successor_ids = sorted({s for slot in domain_effects.values()
if mod_successor in slot["module"]
for s in slot["structure_ids"]})
row = {
"fact_id": fact_id,
"source_bo_id": bo_id,
"type": compact.get("bo_type") if isinstance(compact.get("bo_type"), str) else None,
"date": _first_value(core.get("BehaviorTime"), compact.get("date")),
"parties": parties,
"object_spec": object_spec,
"amount": amount,
"action": action if isinstance(action, str) else _text(action, 240),
"evidence_refs": evidence_refs,
"credibility": credibility,
"legal_centrality": "high" if attached else "medium",
"proof_strength": proof_strength,
"legal_effect_roles": roles,
"claim_chain_ref": {"claim_group_ids": [], "liability_group_ids": [],
"signal_count": len(route_refs)},
"linked_structures": {"structure_ids": structure_ids},
"must_not_drop_in_claim_types": modules,
"must_consider": bool(attached),
"state_context": {"bo_type": compact.get("bo_type")
if isinstance(compact.get("bo_type"), str) else None,
"domain_ids": sorted(domain_effects)},
"money_claim_effect": {"enabled": bool(money_ids),
"structure_ids": money_ids},
"commercial_successor_effect": {"enabled": bool(successor_ids),
"structure_ids": successor_ids},
"actio_roles": actio_roles,
"linked_actio_structures": actio_ids,
"downstream_module_candidates": modules,
"skeleton_validation_codes": [],
"derived_fact": action if isinstance(action, str) else _text(action, 240),
"derivation_basis": ("structure_ids=" + ",".join(structure_ids)
if structure_ids else "bo_only"),
"legal_calculation_object": legal_calculation_object,
"domain_effects": domain_effects,
"calculation_requests": calculation_requests,
}
rows.append(row)
audit["evidence_truncation_slack_min"] = (None if slack_min == 10 ** 9 else slack_min)
# ---- F0-8 — 게이트 합집합 실행 (§4.4 그대로) ------------------------
gate_exec = _dict(policy.get("gate_execution"))
implemented = set(_strings(gate_exec.get("implemented_condition_keys")))
default_missing_policy = str(gate_exec.get("unknown_or_missing_policy_default")
or "fail_open_to_review")
declared_gates: dict[str, dict] = {}
for domain_id in expected_runnable:
config = _dict(_dict(domain_configs).get(domain_id))
config_root = _dict(config.get("domain_config", config))
for gate in _list(config_root.get("gates")):
gate = _dict(gate)
gate_id = gate.get("gate_id")
if isinstance(gate_id, str):
declared_gates[gate_id] = {"domain_id": domain_id, "declared": gate}
all_rules: dict[str, dict] = {}
for domain_id, result in normalized.items():
for gate_id, rule in result["rules"].items():
all_rules[gate_id] = {"domain_id": domain_id, "rule": rule}
gate_report: list[dict] = []
fired_by_domain: dict[str, list[dict]] = {}
for gate_id in sorted(declared_gates):
info = declared_gates[gate_id]
domain_id = info["domain_id"]
declared = info["declared"]
registry_codes = [c for c in _strings(declared.get("review_codes"))
if _CODE_RE.match(c)]
entry: dict[str, Any] = {"gate_id": gate_id, "domain_id": domain_id,
"fired": False, "input_state": "n/a"}
rule_info = all_rules.get(gate_id)
if rule_info is None:
entry["disposition"] = "GATE_SPEC_NOT_DEPLOYED_REVIEW"
reviews.append({"review_code": "GATE_SPEC_NOT_DEPLOYED_REVIEW",
"severity": "SOFT_WARNING", "gate_id": gate_id})
gate_report.append(entry)
continue
rule = rule_info["rule"]
fired = False
if rule["condition_kind"] == "dict":
key = rule["condition_key"]
if key in implemented:
entry["disposition"] = "implemented"
value = rule["condition_value"]
if key == "any_review_code_present":
pool = code_pool.get(domain_id)
if pool is None and domain_id not in _dict(domain_envelopes):
entry["input_state"] = "input_missing"
entry["missing_policy"] = (
rule["unknown_or_missing_policy"] or default_missing_policy)
else:
entry["input_state"] = "supplied"
fired = bool(set(_strings(value)) & set(pool or []))
elif key == "required_slots_missing_or_conflicting":
entries = slot_status.get(domain_id)
if entries is None and domain_id not in _dict(domain_envelopes):
entry["input_state"] = "input_missing"
entry["missing_policy"] = (
rule["unknown_or_missing_policy"] or default_missing_policy)
else:
entry["input_state"] = "supplied"
wanted = set(_strings(value))
fired = any(
(e.get("slot_id") in wanted)
and (e.get("status") in ("missing", "conflicted"))
for e in entries or [])
elif key == "calculation_operand_state_in":
states = operand_by_domain.get(domain_id)
entry["input_state"] = ("supplied" if states else "input_missing")
fired = bool(set(_strings(value)) & set(states or []))
else:
entry["disposition"] = "GATE_CONDITION_KEY_UNSUPPORTED_REVIEW"
entry["condition_key"] = key
reviews.append({"review_code": "GATE_CONDITION_KEY_UNSUPPORTED_REVIEW",
"severity": "SOFT_WARNING", "gate_id": gate_id,
"detail": str(key)})
else:
entry["disposition"] = "GATE_PREDICATE_SPEC_UNIMPLEMENTED_REVIEW"
entry["predicate_spec"] = _text(rule["condition_value"], 200)
reviews.append({"review_code": "GATE_PREDICATE_SPEC_UNIMPLEMENTED_REVIEW",
"severity": "SOFT_WARNING", "gate_id": gate_id,
"detail": _text(rule["condition_value"], 160)})
# 차단 표지 대조 — registry ↔ 정규화된 사이드카.
declared_blocks = declared.get("blocks_final_drafting")
if (rule["blocks_final_drafting"] is not None
and declared_blocks is not None
and bool(declared_blocks) != rule["blocks_final_drafting"]):
reviews.append({"review_code": "GATE_BLOCK_FLAG_MISMATCH_REVIEW",
"severity": "SOFT_WARNING", "gate_id": gate_id})
entry["block_flag_mismatch"] = True
emitted = _dedupe_keep_order(rule["emitted_codes"] + registry_codes)
excess = [c for c in rule["emitted_codes"] if c not in registry_codes]
if excess:
entry["codes_not_in_registry"] = excess
reviews.append({"review_code": "GATE_EMITTED_CODE_NOT_IN_REGISTRY",
"severity": "SOFT_WARNING", "gate_id": gate_id,
"detail": ",".join(excess)[:160]})
entry["fired"] = fired
entry["blocks_final_drafting"] = (rule["blocks_final_drafting"]
if rule["blocks_final_drafting"] is not None
else bool(declared_blocks))
if fired:
projection_rows = [r["fact_id"] for r in rows
if domain_id in r["domain_effects"]]
entry["projected_row_count"] = len(projection_rows)
fired_by_domain.setdefault(domain_id, []).append({
"gate_id": gate_id, "codes": emitted,
"row_must_consider": rule["row_must_consider"],
"preserve_seed": rule["preserve_seed"],
"blocks_final_drafting": entry["blocks_final_drafting"],
"status_when_triggered": rule["status_when_triggered"],
"projected_rows": projection_rows,
})
gate_report.append(entry)
orphan_rules = sorted(set(all_rules) - set(declared_gates))
for gate_id in orphan_rules:
reviews.append({"review_code": "GATE_RULE_NOT_IN_REGISTRY",
"severity": "SOFT_WARNING", "gate_id": gate_id})
# ---- 행 투영 3분기 + F0-8b 스켈레톤 --------------------------------
skeleton_order = _strings(_dict(policy.get("skeleton_checks")).get("order"))
for row in rows:
codes: list[str] = []
if not _nonblank(row["derived_fact"]):
codes.append(skeleton_order[0])
if not row["evidence_refs"]:
codes.append(skeleton_order[1])
if not row["linked_structures"]["structure_ids"]:
codes.append(skeleton_order[2])
gate_codes: list[str] = []
for domain_id in sorted(row["domain_effects"]):
for firing in sorted(fired_by_domain.get(domain_id, []),
key=lambda f: f["gate_id"]):
gate_codes.extend(firing["codes"])
if firing["row_must_consider"]:
row["must_consider"] = row["must_consider"] or True
row["skeleton_validation_codes"] = _dedupe_keep_order(codes + gate_codes)
# ---- STRUCTURE_UNATTACHED_REVIEW -----------------------------------
unattached_structures = sorted(set(record_by_id) - used_structure_ids)
for structure_id in unattached_structures:
reviews.append({"review_code": "STRUCTURE_UNATTACHED_REVIEW",
"severity": "SOFT_WARNING", "detail": structure_id})
# ---- F0-9 — 예외 pack ----------------------------------------------
pack_policy = _dict(policy.get("exception_pack"))
char_limit = int(pack_policy.get("pack_item_char_limit") or 2000)
exceptions: list[dict] = []
for row in rows:
if row["legal_calculation_object"] is None:
continue
bo = next((b for b in bo_sorted
if str(b.get("BO_ID") or "") == row["source_bo_id"]), {})
core = _dict(bo.get("core_field_base"))
amount_candidates = [v for v in (bo.get("amount"), core.get("Amount"))
if _nonblank(v)]
if len(amount_candidates) >= 2:
payload = {"amount_candidates": [_text(v, 240) for v in amount_candidates],
"object_spec": row["object_spec"],
"calculation_request_ids": sorted(
str(c.get("calculation_id")) for c in
row["calculation_requests"] if c.get("calculation_id"))}
item = {"exception_id": "FX-%03d" % (len(exceptions) + 1),
"exception_type": "legal_calculation_required",
"fact_id": row["fact_id"],
"source_bo_id": row["source_bo_id"],
"conflict_flags": ["CALCULATION_BASIS_CONFLICT"],
"compact_payload": payload}
rendered = json.dumps(item, ensure_ascii=False)
if len(rendered) > char_limit:
item["oversize_review"] = True
reviews.append({"review_code": "CALCULATION_BASIS_INCOMPLETE_REVIEW",
"severity": "SOFT_WARNING",
"fact_id": row["fact_id"],
"detail": "pack item oversize %d>%d" % (len(rendered),
char_limit)})
exceptions.append(item)
elif (not _nonblank(row["amount"])) or (not _nonblank(row["object_spec"])):
reviews.append({"review_code": "CALCULATION_BASIS_INCOMPLETE_REVIEW",
"severity": "SOFT_WARNING", "fact_id": row["fact_id"],
"detail": "amount_or_object_spec_blank"})
unreachable = {name for name, meta in
_dict(policy.get("f1_exception_types")).items()
if _dict(meta).get("unreachable_in_v8")}
for item in exceptions:
if item["exception_type"] in unreachable:
raise ValueError(json.dumps({
"reason_code": "PART4_UNREACHABLE_EXCEPTION_TYPE",
"exception_type": item["exception_type"]}, ensure_ascii=False))
# ---- F0-10 — 보존 등식 계수 ----------------------------------------
effect_left = sum(len(r["domain_effects"]) for r in rows)
effect_right = 0
calc_right = 0
for bo in bo_sorted:
bo_id = str(bo.get("BO_ID") or "")
attached = [record_by_id[s] for s in by_bo_id.get(bo_id, [])
if s in record_by_id]
effect_right += len({r.get("domain_id") for r in attached
if isinstance(r.get("domain_id"), str)})
calc_right += len({ref for r in attached
for ref in _strings(r.get("calculation_request_refs"))})
calc_left = sum(len(r["calculation_requests"]) for r in rows)
sg11_total = len(sg11_records)
sg11_unused = sg11_total - len(used_sg11)
equations = {
"row": {"left": len(rows), "right": len(bo_sorted),
"holds": len(rows) == len(bo_sorted)},
"attachment_coverage": {
"attached": attach_stats["attached_bo"],
"unattached": attach_stats["unattached_bo"],
"total": len(bo_sorted),
"holds": (attach_stats["attached_bo"] + attach_stats["unattached_bo"]
== len(bo_sorted))},
"effect": {"left": effect_left, "right": effect_right,
"holds": effect_left == effect_right},
"calc_row": {"left": calc_left, "right": calc_right,
"holds": calc_left == calc_right},
"calc_source": {"attached_distinct": len(used_sg11),
"unattached_distinct": sg11_unused,
"total": sg11_total,
"holds": len(used_sg11) + sg11_unused == sg11_total},
}
# ---- 상태·집계 ------------------------------------------------------
les_review_codes = set(_strings(policy.get("les_inherited_review_codes")))
les_review_present = any(
(set(slot["review_codes"]) & les_review_codes)
for row in rows for slot in row["domain_effects"].values()) or any(
_dict(r).get("review_required") for r in structure_records)
gate_fired_any = any(f for firings in fired_by_domain.values() for f in firings)
status = ("READY_WITH_REVIEW"
if (reviews or exceptions or les_review_present or gate_fired_any)
else "READY")
counts = {
"rows": len(rows),
"bo_records": len(bo_sorted),
"structure_records": len(structure_records),
"gates_declared": len(declared_gates),
"gates_implemented": sum(1 for e in gate_report
if e.get("disposition") == "implemented"),
"gates_visualized_str": sum(
1 for e in gate_report
if e.get("disposition") == "GATE_PREDICATE_SPEC_UNIMPLEMENTED_REVIEW"),
"gates_visualized_unsupported": sum(
1 for e in gate_report
if e.get("disposition") == "GATE_CONDITION_KEY_UNSUPPORTED_REVIEW"),
"gates_fired": sum(1 for e in gate_report if e.get("fired")),
"orphan_rules": len(orphan_rules),
"review_items": len(reviews),
"exceptions": len(exceptions),
"conservation": equations,
}
bundle = {
"schema_version": "stage1_fact_ledger_candidate_bundle.v1",
"status": status,
"rows": rows,
"gate_report": {"gates": gate_report,
"fired_by_domain": {d: f for d, f in
sorted(fired_by_domain.items())},
"orphan_rule_ids": orphan_rules},
"counts": counts,
"signal_manifest_transaction_id": None,
"expected_runnable_domain_ids": expected_runnable,
"alias_resolution": {"table": alias["table"],
"self_declarations": alias["self_declarations"],
"mismatches": alias["mismatches"]},
"created_by": "fact_ledger_compiler",
}
pack = {
"schema_version": "stage1_fact_exception_pack.v1",
"has_exceptions": bool(exceptions),
"exception_count": len(exceptions),
"exceptions": exceptions,
"budget": {"pack_item_char_limit": char_limit},
}
audit["unattached_structure_ids"] = unattached_structures
audit["equations"] = equations
return {"fact_ledger_candidate_bundle": bundle,
"fact_exception_pack": pack,
"review_items": reviews,
"audit": audit}
@@ -0,0 +1,401 @@
{
"schema_version": "stage1_fact_ledger_policy.v1",
"status": "ACTIVE",
"purpose": "Part 4 Fact Ledger 의 규칙 선언 정본. 27필드 목록·순서·합성 규칙, 증거력 산정, 게이트 방언 정규화와 구현 범위, 별칭 파생, 보존 등식, 루트 래핑을 코드 밖에 둔다. 코드는 이 선언을 읽어 dispatch 만 한다. registry 가 말하지 않는 값은 만들지 않는다 — 137종 사건 이름을 어디에도 두지 않는다.",
"authority": {
"strategy": "stage_1_part_4_개정전략서.md",
"row_baseline": "선행구축/S_2_GPT_v2/fixtures/legacy_equivalence/run_{1,2}/fact_ledger/Fact_Ledger_base.json (59행 x 27키 실측)",
"gate_sidecars": "domains/<id>/gates.json x25 (조립본 바이트 동일 복사 · 127규칙 · registry 121 전건 커버)",
"case_kind_names_forbidden": true
},
"final_fields_27": [
"fact_id",
"source_bo_id",
"type",
"date",
"parties",
"object_spec",
"amount",
"action",
"evidence_refs",
"credibility",
"legal_centrality",
"proof_strength",
"legal_effect_roles",
"claim_chain_ref",
"linked_structures",
"must_not_drop_in_claim_types",
"must_consider",
"state_context",
"money_claim_effect",
"commercial_successor_effect",
"actio_roles",
"linked_actio_structures",
"downstream_module_candidates",
"skeleton_validation_codes",
"derived_fact",
"derivation_basis",
"legal_calculation_object"
],
"new_fields_2": [
"domain_effects",
"calculation_requests"
],
"new_fields_note": "29키 = 구 27 + 신규 2. 신규 2키는 required 로 올리지 않는다(D-P4-4 · Stage 2 호환). domain_effects 의 module·role_projection 은 정렬된 배열이다 — 한 도메인이 복수 module 을 선언할 수 있어 스칼라로 두면 정보가 소실된다(D-P4-e).",
"fact_id_format": "F-{idx:03d}",
"fact_id_rule": "BO_ID 사전순 1-기반 연속. 연속성은 F2 가 경성 단언한다.",
"row_cardinality": "BO 1건 = 1행",
"bo_normalization": {
"key_map": {
"BOType": "bo_type",
"Action": "action",
"BO_ID": "bo_id"
},
"domain_payload_keep_keys": [
"money_claim_details",
"possession_state",
"lien_state",
"notice_lifecycle",
"asset_alias",
"fractional_effect_events"
],
"nonblank_only": true,
"note": "구본 _compact_bo(369-374행) 등가 계층. 정규화 없이 원본 대문자 키를 소문자로 찾으면 전건 None 이 된다 — (가) 9필드 전부의 전제."
},
"field_synthesis_ga_9": {
"note": "(가) BO 유래 9필드 — 골든 59행 전건 값등가 요구. 우변을 바꾸면 정상 구현에서 G-r 이 깨진다.",
"fact_id": "F-{idx:03d} (BO_ID 사전순 1-기반)",
"source_bo_id": "BO_ID 그대로",
"type": "compact.bo_type 단독 (LES types[0] 폴백 금지)",
"date": "first_value(core_field_base.BehaviorTime, bo.date) — bo.date 는 BO 18키에 없어 항상 None. 골든은 BehaviorTime 0/59 라 전건 null",
"parties": "core_field_base 단독 — Performer -> role: actor · Subject -> role: counterparty · 값은 text(party, 160) · source: 'BO.core_field_base'. 구 signal 경로 폐지",
"amount": "text(first_value(bo.amount, core_field_base.Amount), 240) or None — 키 오름차순 JSON(ensure_ascii=False) -> 공백열 단일 공백 정규화 -> 240자 절단 -> 빈 문자열이면 None",
"action": "first_value(compact.action, core_field_base.Action) — 정규화 계층 통과 후의 소문자 키",
"evidence_refs": "BO source_evidence_indexes 단독 — LES 증거는 합집합하지 않는다(합집합하면 v.8 에서 깨진다). LES 증거는 domain_effects 쪽에만 싣는다",
"derived_fact": "action 복제 단독 — 구본 4항 서술 분기는 삭제 확정(deprecation 절)"
},
"field_synthesis_na_15": {
"note": "(나) LES 유래 15필드 — 골든 등가를 요구하지 않는다(EXPECTED_DELTA-04). v.8 LES 부착에서 결정론으로 재산출한다. 부착 0 이면 아래 기본값이 그대로 남는다.",
"object_spec": {
"rule": "core_field_base.Object 단독 · 없으면 null · 폴백 없음",
"decision": "D-P4-d=(가) — LES 폴백 대상(asset_cluster_ids)이 Part 3 v.8 에서 소멸했으므로 BO 단독 원천으로 확정한다"
},
"legal_centrality": "부착 있으면 high · 없으면 medium",
"legal_effect_roles": "부착 레코드 role_projection 정렬 합집합 · 기본 []",
"claim_chain_ref": "{claim_group_ids: [], liability_group_ids: [], signal_count: 부착 route signal 수} — 구 그룹 ID 는 v.8 에 존재하지 않으므로 상시 빈 배열",
"linked_structures": "{structure_ids: 정렬 합집합} · 기본 {structure_ids: []}",
"must_not_drop_in_claim_types": "부착 레코드 module 정렬 합집합 · 기본 []",
"must_consider": "1차 = 부착 존재 여부(bool) · 게이트 행 투영이 OR 로 갱신한다 — (나)∩(라) 겸용 필드",
"state_context": "{bo_type, domain_ids: domain_effects 키 정렬} · 기본 {bo_type, domain_ids: []}",
"money_claim_effect": "{enabled: module 'money_claim' 부착 여부, structure_ids: 해당 정렬 목록}",
"commercial_successor_effect": "{enabled: module 'commercial_successor' 부착 여부, structure_ids: 해당 정렬 목록}",
"actio_roles": "module 'actio' 부착 레코드의 role_projection 정렬 합집합 · 기본 []",
"linked_actio_structures": "module 'actio' 부착 레코드의 structure_id 정렬 목록 · 기본 []",
"downstream_module_candidates": "must_not_drop_in_claim_types 와 같은 파생(골든 59/59 동일값 실측 승계)",
"derivation_basis": "부착 있으면 'structure_ids=' + 콤마결합 · 없으면 'bo_only'",
"legal_calculation_object": "부착 type_id ∩ legal_calculation_object_types 의 사전순 최소 · 없으면 null"
},
"legal_calculation_object_types": [
"fraudulent_transfer",
"land_use_gain",
"money_claim",
"secured_debt",
"valuation"
],
"legal_calculation_object_note": "구본 677행 5-원소 집합 승계(D-P4-1 값 이관). calculation_bindings 비공백 도메인으로 대체하면 항등식으로 퇴화한다(라운드 12 실측 — 25도메인 x type_id 56종 전건).",
"evidence_scoring_da_2": {
"note": "(다) credibility·proof_strength — 골든 59행 전건 값등가 요구. 갈래 순서를 바꾸면 재현이 깨진다(문서 초안 4갈래 = 29/59 실측).",
"authority_row_keys": {
"evidence_index": "id 키 6종(evidence_index·id·evidence_id·eid·index·doc_id) 첫 적중",
"title": "first_key(title, Title, name, doc_title)",
"excerpt": "key_facts -> key_dates/key_amounts -> 대표 문자열 폴백",
"priority": "first_key(priority, Priority, weight)",
"authentication": "first_key(authentication, auth, source_authentication)",
"source": "고정 문자열 'evidence_indexed.json'"
},
"flatten_depth": 3,
"universe_missing_stub": "{evidence_index: eid, missing: true} — stub 도 비어 있지 않은 증거 행으로 계수한다(구본 1136행 승계)",
"text_rule": "행마다 json.dumps(sort_keys=True, ensure_ascii=False) -> 공백열 단일 공백 -> 300자 절단 -> ' '.join 으로 합쳐 1회 대조(합친 뒤 절단이 아니다). sort_keys 에서 title 이 최후미라 절단이 마커를 먼저 지운다 — 절단 여유를 영수증에 적는다",
"markers_documentary": [
"등기",
"계약",
"판결",
"공문",
"금융",
"송금",
"계좌",
"registry",
"contract",
"judgment"
],
"branches_in_order": [
{
"order": 1,
"condition": "증거 행 0건",
"credibility": "low",
"proof_strength": "low"
},
{
"order": 2,
"condition": "마커 10종 중 하나라도 text 에 존재",
"credibility": "high",
"proof_strength": "documentary"
},
{
"order": 3,
"condition": "증거 행 2건 이상",
"credibility": "high",
"proof_strength": "high"
},
{
"order": 4,
"condition": "text 에 meeting 또는 회의 또는 면담",
"credibility": "medium",
"proof_strength": "meeting_only_core"
},
{
"order": 5,
"condition": "그 외 전부",
"credibility": "medium",
"proof_strength": "medium"
}
]
},
"skeleton_checks": {
"order": [
"EMPTY_DERIVED_FACT",
"NO_EVIDENCE_REFS",
"NO_LINKED_LEGAL_EFFECT_STRUCTURE"
],
"note": "스켈레톤 3코드를 이 고정 순서로 먼저, 그 뒤 게이트 코드를 gate_id 오름차순으로 append, 중복은 첫 출현 위치 보존 dedupe. G-n(바이트 동일)·G-r 이 순서에 의존한다."
},
"gate_execution": {
"implemented_condition_keys": [
"any_review_code_present",
"required_slots_missing_or_conflicting",
"calculation_operand_state_in"
],
"visualized_condition_keys": {
"boundary_or_overlap_detected": "GATE_CONDITION_KEY_UNSUPPORTED_REVIEW — 정의처(domain_extension.schema · Stage2_handoff_contract)가 배포 트리에 없다(D-P4-c=(가) · source_refs 배포 선언 0회 실측)"
},
"str_condition_disposition": "GATE_PREDICATE_SPEC_UNIMPLEMENTED_REVIEW — 술어 명세를 코드로 옮기는 것은 판정식 창작이다(D-P4-b=(가))",
"orphan_rule_disposition": "GATE_RULE_NOT_IN_REGISTRY",
"unknown_or_missing_policy_default": "fail_open_to_review",
"block_semantics": "blocks_final_drafting: true 는 Stage 1 기록을 막지 않는다. 행은 기록되고 final_writer_status 가 READY_WITH_REVIEW 가 되며 차단 표지는 Stage 2 로 전달된다.",
"row_projection": {
"target_rows": "발화 게이트의 도메인 d 에 대해 R(d) = { row | d ∈ row.domain_effects 키 }",
"codes": "R(d) 전 행의 skeleton_validation_codes 에 합집합 부착(순서 규칙은 skeleton_checks)",
"must_consider": "row_effect.must_consider 보유 규칙 발화 시 R(d) 전 행 OR 갱신(기존 True 미소실)",
"preserve_seed": "on_true.preserve_seed 는 행 필드가 아니라 writer report 게이트별 발화 목록에만 기록",
"empty_target": "R(d) 공집합이면 행에 닿지 않고 writer report 에만 계수(침묵 아님)"
},
"normalization_axes_17": {
"rule_array_keys": [
"rules",
"gates"
],
"emitted_code_keys": [
"emitted_codes",
"emitted_review_codes",
"review_code"
],
"effect_containers": [
"writer_effect",
"row_effect",
"on_true"
],
"block_flag_paths": [
"writer_effect.final_drafting_block",
"writer_effect.blocks_final_drafting",
"on_true.blocks_final_drafting"
],
"status_when_triggered_paths": [
"writer_effect.status_when_triggered",
"writer_effect.status_on_fire",
"on_true.writer_status"
],
"evaluation_keys": [
"evaluation_mode",
"implementation_type",
"evaluation"
],
"row_effect_keys": [
"row_effect.must_consider",
"on_true.preserve_seed"
],
"file_status_keys": [
"closure_status",
"status"
],
"input_declaration_keys": [
"input_fields",
"input_contracts"
],
"optional_flags": [
"writes_fact_ledger",
"unknown_or_missing_policy",
"silent_repair_forbidden",
"source_membership_required"
],
"global_status_rule_note": "사이드카 global_status_rule 3방언은 채택하지 않는다 — 경고로만 기록하고 전역 규칙은 아래 global_status_rule 하나를 쓴다"
},
"emitted_code_registry_check_surface": "gates[].review_codes (게이트 단위) — 도메인 어휘 면을 쓰면 실측 초과 2건(E18-G03·E20-G03)이 사라진다"
},
"legacy_alias": {
"source": "routing/extension_payload_key_declarations.v1.json legacy_alias_targets",
"self_declaration_scan": "배포 domains/<id>/structure_types.json 전수 — 배열 키 방언(types·structure_types·entries) 정규화 · legacy_source 약칭(B1~B5)을 완전명으로 정규화 · B* 형이 아닌 값(예: 'Part3 fallback')은 선언이 아니다",
"mismatch_disposition": "누락 도메인은 해소 결과에 추가하지 않고 LEGACY_ALIAS_SELF_MISMATCH_REVIEW 로 누락 도메인 ID·영향 항목 수를 계수한다. 자산의 legacy_declaration_mismatches 는 파생 목록이므로 대조용으로만 쓰고 불일치 시 LEGACY_ALIAS_DECLARATION_INCOMPLETE_REVIEW 를 계수한다.",
"abbreviation_map": {
"B1": "B1_Money_Successor",
"B2": "B2_Secured_Registry",
"B3": "B3_Land_Valuation",
"B4": "B4_Actio",
"B5": "B5_Succession_Notice_Lien_Asset_Defense"
}
},
"p2_handoff_rules": {
"code_key": "source_review_code — issue_type 은 enum 밖 값을 뭉갠다",
"code_pattern": "^[A-Z][A-Z0-9_]{2,127}$",
"not_a_code_disposition": "P2_HANDOFF_CODE_NOT_A_REVIEW_CODE_REVIEW",
"domain_attribution": "source_domain 을 별칭 해소표로 registry 도메인 ID 로 옮긴 뒤 해당 도메인 게이트에만 공급한다(전역 병합 금지 — 교차 오발화)",
"domain_unresolved_disposition": "P2_HANDOFF_DOMAIN_UNRESOLVED_REVIEW — 어느 게이트에도 공급하지 않는다"
},
"calculation_join": {
"join_key": "signal_id (details.request_id 는 보장되지 않는 키)",
"operand_state": "Part 2 산출을 재산정 없이 승계한다(D-P4-6). deferred 는 registry 선언값이다.",
"calculation_id_missing": "signal_id 로 대체하고 CALCULATION_ID_SUBSTITUTED_REVIEW 계수 — 버리지 않는다",
"dangling_ref": "calculation_id 없는 stub 항목으로 적재(버리면 등식 ④가 정상 실행에서 발화) · CALCULATION_REF_DANGLING_REVIEW 계수",
"law_version_refs": "SG-11 레코드 law_version_refs ∪ details.law_version_refs ∪ 해당 도메인 SG-04 레코드 signal_id — 버전 확정 판단 금지(SG-04 는 참조만)"
},
"conservation_equations_5": {
"row": "len(rows) == len(BO.json) -> PART4_ROW_CONSERVATION_BROKEN",
"attachment_coverage": "부착 BO 수 + 미부착 BO 수 == len(BO.json) -> PART4_BO_ATTACHMENT_COVERAGE_BROKEN",
"effect": "Σ_bo |domain_effects 키| == Σ_bo |by_bo_id 레코드 distinct domain_id| -> PART4_EFFECT_CONSERVATION_BROKEN",
"calc_row": "Σ_bo |calculation_requests| == Σ_bo |by_bo_id 레코드 distinct calculation_request_refs| -> PART4_CALC_CONSERVATION_BROKEN",
"calc_source": "부착 distinct SG-11 + 미부착 distinct == SG-11 레코드 총수 -> PART4_CALC_SOURCE_CONSERVATION_BROKEN",
"note": "④와 ⑤는 차원이 다르다 — 컴파일러가 SG-11 signal_id 를 도메인 전 레코드에 복사하므로 행 항목 수 ≠ 원천 레코드 수. by_bo_id == {} 는 정상 입력이다(골든 경로 부착 0/59)."
},
"unattached_review": {
"bo": "FACT_ROW_UNATTACHED_REVIEW",
"structure": "STRUCTURE_UNATTACHED_REVIEW",
"note": "보존 등식은 손실을 세지 흡수를 세지 않는다 — 침묵 금지를 등식 위에 따로 건다"
},
"exception_pack": {
"surviving_type": "legal_calculation_required",
"review_code": "CALCULATION_BASIS_INCOMPLETE_REVIEW",
"trigger": "구본 866·899-925행 승계 — calculation_required AND amount 후보 2개 이상(bo.amount·core.Amount 양쪽 비공백)이면 pack 적재 + conflict_flags [CALCULATION_BASIS_CONFLICT] · elif amount 또는 object_spec 공백이면 pack 미적재 + CALCULATION_BASIS_INCOMPLETE_REVIEW 검토(if/elif 배타)",
"exception_id_format": "FX-{n:03d}",
"pack_item_char_limit": 2000,
"oversize_disposition": "자동 차단이 아니라 exception_id 유지한 채 review",
"unreachable_in_v8": {
"unresolved_asset_cluster_projection": "asset_cluster 계열이 Part 3 v.8 에서 소멸(P2·P3·LES 스키마·컴파일러 0회)",
"unresolved_structure_role_projection": "registry structure_types 73/73 이 module·role_projection 전건 선언(결손 0)"
},
"unreachable_disposition": "pack 에 실리면 PART4_UNREACHABLE_EXCEPTION_TYPE 경성 실패"
},
"adjudication": {
"accepted_status": [
"READY",
"READY_NO_EXCEPTIONS",
"READY_WITH_BLOCKS"
],
"allowed_patch_fields": [
"amount"
],
"note": "허용 필드 밖 수정은 PART4_PATCH_REJECTED. F1 의 유일 생존 부류가 계산 근거 다중성 판정이므로 패치 가능 필드는 amount 하나다."
},
"global_status_rule": "blocked item · rejected patch · deterministic review · LES 승계 검토(review_codes 2종 ∪ review_required 플래그) · gate review code 중 하나라도 있으면 READY_WITH_REVIEW, 아니면 READY (동결 계약 승계 — 사이드카 3방언 값은 경고로만 기록)",
"les_inherited_review_codes": [
"UNREGISTERED_EFFECT_TYPE_REVIEW",
"PART3_REGISTRY_SIDECAR_MISMATCH"
],
"root_wrapping_table": {
"signals/signal_manifest.json": null,
"routing/domain_activation_manifest.json": "domain_activation_manifest",
"BO.json": "(array)",
"legal_effect_structures.json": null,
"signals/domain_signals/<domain_id>.json": "domain_signal_envelope",
"quality_gates/stage1_part2_review_handoff.json": null,
"evidence_indexed.json": null,
"stage1_tmp/fact_ledger/fact_ledger_candidate_bundle.json": "fact_ledger_candidate_bundle",
"stage1_tmp/fact_ledger/fact_exception_pack.json": "fact_exception_pack",
"stage1_tmp/fact_ledger/fact_adjudication.json": "fact_exception_adjudication",
"Fact_Ledger_base.json": "(array)",
"stage1_tmp/fact_ledger/fact_ledger_writer_report.json": null,
"quality_gates/stage1_part4_review_handoff.json": "stage1_part4_review_handoff",
"validation_assets/routing/part4_receipt.json": null,
"signals/governing_law_version_signals.json": null,
"signals/timeline_notice_condition_signals.json": null,
"signals/asset_right_state_signals.json": null,
"signals/evidence_proof_conflict_signals.json": null,
"signals/calculation_requirements.json": null
},
"review_codes_soft": [
"GATE_SPEC_NOT_DEPLOYED_REVIEW",
"GATE_CONDITION_KEY_UNSUPPORTED_REVIEW",
"GATE_PREDICATE_SPEC_UNIMPLEMENTED_REVIEW",
"GATE_RULE_NOT_IN_REGISTRY",
"GATE_EMITTED_CODE_NOT_IN_REGISTRY",
"GATE_BLOCK_FLAG_MISMATCH_REVIEW",
"FACT_ROW_UNATTACHED_REVIEW",
"STRUCTURE_UNATTACHED_REVIEW",
"CALCULATION_REF_DANGLING_REVIEW",
"CALCULATION_ID_SUBSTITUTED_REVIEW",
"CALCULATION_BASIS_INCOMPLETE_REVIEW",
"LEGACY_ALIAS_SELF_MISMATCH_REVIEW",
"LEGACY_ALIAS_DECLARATION_INCOMPLETE_REVIEW",
"P2_HANDOFF_CODE_NOT_A_REVIEW_CODE_REVIEW",
"P2_HANDOFF_DOMAIN_UNRESOLVED_REVIEW",
"EVIDENCE_REF_NOT_IN_EVIDENCE_UNIVERSE",
"EMPTY_DERIVED_FACT",
"NO_EVIDENCE_REFS",
"NO_LINKED_LEGAL_EFFECT_STRUCTURE"
],
"hard_failure_codes": [
"PART4_ASSET_DEPLOYMENT_INCOMPLETE",
"MODULE_MIRROR_UNREGISTERED",
"MODULE_MIRROR_HASH_MISMATCH",
"PART4_POLICY_INVALID",
"PART4_LEDGER_SCHEMA_STALE",
"PART4_COMPILER_STALE",
"PART4_SIGNAL_SEAL_FAILED",
"PART4_REGISTRY_INDEX_SEAL_FAILED",
"PART4_LES_TRANSACTION_MISMATCH",
"PART4_ROW_CONSERVATION_BROKEN",
"PART4_BO_ATTACHMENT_COVERAGE_BROKEN",
"PART4_EFFECT_CONSERVATION_BROKEN",
"PART4_CALC_CONSERVATION_BROKEN",
"PART4_CALC_SOURCE_CONSERVATION_BROKEN",
"PART4_COVERAGE_BROKEN",
"PART4_PATCH_REJECTED",
"PART4_BO_CONSERVATION_BROKEN",
"PART4_FACT_ID_SEQUENCE_BROKEN",
"PART4_ADJUDICATION_MISSING",
"PART4_DOMAIN_EFFECT_NOT_ACTIVE",
"PART4_LEDGER_SCHEMA_INVALID",
"PART4_UNREACHABLE_EXCEPTION_TYPE",
"PART4_WRITE_REREAD_MISMATCH"
],
"deprecation": {
"old_4_fields_alias": "money_claim_effect·commercial_successor_effect·actio_roles·linked_actio_structures 는 v.8 한 버전 동안 domain_effects 로부터의 결정론 별칭이다. v.9 에서 제거 예정(D-P4-a=(가)).",
"derived_fact_4part_branch": "구본의 date·actor·obj·action 4항 서술 분기는 삭제 확정 — object_spec 참조가 끊겨 조건이 영구 거짓이 되는 사문이다.",
"old_signal_3_root_names": "actio_case_signals·case_liability_signals·legal_effect_signals 뿌리 이름을 읽지 않는다(C-7 폐쇄)."
},
"f1_exception_types": {
"legal_calculation_required": {
"unreachable_in_v8": false
},
"unresolved_asset_cluster_projection": {
"unreachable_in_v8": true
},
"unresolved_structure_role_projection": {
"unreachable_in_v8": true
}
},
"alias_module_vocabulary": {
"money_claim_effect": "money_claim",
"commercial_successor_effect": "commercial_successor",
"actio_fields": "actio",
"note": "구 4필드 별칭이 참조하는 module 어휘. 코드에 리터럴을 두지 않는다(정적 검사 11) — registry structure_types[].module 이 원천이고 이 선언은 그중 별칭 파생이 쓰는 세 값을 고정한다."
}
}
@@ -0,0 +1,65 @@
# Stage 1 — Part 4 YAML 신규 작성 요약서
- 작성일: 2026-08-23
- 정본 명세: `stage_1_part_4_개정전략서.md` (구 `Stage_1_Part_4_Claude_v3.yml`과 무관한 신작)
- 검증: 독립 sub-agent 2회 왕복 — 1차 FAIL(MAJOR 1·MINOR 2) → 전건 수정 → **재검증 PASS · DEFECTS 없음**
## 1. 최종 산출물
| 산출물 | 위치 | sha256(16) · 크기 |
|---|---|---|
| Part 4 YAML | `ver_8_yaml_candidates/stage_1_part_4_v.8.yml` | `fc60011042f1159a` · 69,004 B |
| 사실원장 컴파일러 | `Default_Agent/stage1_runtime/fact_ledger_compiler.py` (+`.txt` 바이트 동일 미러) | `f6c4b476f4974104` · 45,335 B |
| 사실원장 정책 | `Default_Agent/stage1_runtime/fact_ledger_policy.v1.json` | `fea0cea48d862c6c` · 21,350 B |
| 원장 스키마 | `Default_Agent/platform/schemas/fact_ledger_base.schema.json` | `b3f0e79ecb4c2f72` · 7,415 B |
| 후보 번들 스키마 | `Default_Agent/platform/schemas/fact_ledger_candidate_bundle.schema.json` | `4e481504fb795b2b` · 10,105 B |
| 예외 팩 스키마 | `Default_Agent/platform/schemas/fact_exception_pack.schema.json` | `4eba7e51ed46a99e` · 2,087 B |
| 게이트 사이드카 | `Default_Agent/domains/<id>/gates.json` ×25 (조립본 바이트 동일 복사, 81,430 B) | — |
| 매니페스트 | `Default_Agent/runtime_manifest.json` 434→465항 (+31 = 신설 6 + 사이드카 25) | `8964593a64a9b1bc` |
| 인계면 | `선행구축/Default_Agent_Stage_1/handoffs/stage1_part_interface.v1.json` 45→55행, `stage1_stage_chain.v1.json` Part 4 구간 | — |
| LES 스키마 개정 | `Default_Agent/platform/schemas/legal_effect_structures.schema.json` — `signal_manifest_transaction_id` required 승격 (§8-4b) | — |
신설 자산 6종 + 개정 LES 스키마·정책은 조립본(`선행구축/Default_Agent_Stage_1/`)에 파일 동기화 완료(빌더 등록 없음 — §9-11 규율).
## 2. YAML 구조 (task 3종, 완전 직렬 DAG)
- **F0 `Task_C_FL_F0_fact_ledger_reducer`** (code-executor, timeout 300): 배포 게이트 13자산 → 모듈 5종 물질화 → 정책/스키마 능력 확인 → 봉인 3종(signal_manifest tx+파일 sha · SG-01 registry sha+config_sha 사슬 · LES 트랜잭션 2갈래) → 입력 적재(BO 경성, P2 handoff·evidence는 `read_json(상수)` + try/except 부재 계수) → `compile_fact_ledger_candidates(13인자)` → 번들·팩·handoff(OPEN)·영수증(실측값) 기록.
- **F1 `Task_C_FL_F1_fact_exception_adjudicator`** (LLM gemini-3.1-flash-lite / reasoning low / max_iterations 1): 예외 팩만 읽고 `fact_adjudication.json` 기록 (SELECT_AMOUNT | BLOCK_REVIEW, patch_field는 "amount" 한정).
- **F2 `Task_C_FL_F2_final_fact_ledger_gate_and_writer`** (code, timeout 240): 재정판정 적용(수용 status·허용 패치 필드는 정책에서 읽음) → domain_effects ⊆ 활성 도메인 검사(등식보다 먼저 — G-g 순서) → 보존 등식 5종 원본 재계수 → BO 보존·fact_id 연속성·닫힌 스키마 → `Fact_Ledger_base.json`(맨몸 배열) + writer 보고서 + handoff FINALIZED.
행 계약: BO 1:1, `F-{idx:03d}` 연속, 29키 = 구 27키(이름·순서 보존) + `domain_effects`·`calculation_requests`(required 제외). 게이트 실행: 사이드카 25종 127규칙, 17축 방언 정규화, dict 구현 3부류(25건) 실행 · str 90+경계 6 시각화 · 고아 6 review — 코드에 gate_id/모듈 어휘 리터럴 0(정책이 유일 출처).
## 3. 검증 실측 (재검증 PASS 시점)
- 인터페이스 검사기 4-yaml 전수(part 1~4, 정본 선언표 55행): `status: PASS` · `finding_count: 0` ×4.
- 골든 재현: (가)9+(다)2 = 11필드 × 59행 불일치 0 · (라) 차분 EXPECTED_DELTA-03∪-05 정확 일치 · 결정성 2회 바이트 동일 · 보존 등식 5/5.
- 26-A 전수: 규칙 121 / 사이드카 25 / str 90 / 경계 6 / 고아 6. 발화 양·음성 테스트 전건 통과.
- F2 파괴 시나리오 8종: 실패 코드 전건 정확 발화, 정상 amount 패치 반영.
- F0 스텁 전체 실행: 배포 게이트 13 · 봉인 3종 통과 · 4파일 기록 · 2회 바이트 동일.
- 부재 의미론: evidence+P2 handoff 부재 → 경고 2건 계수 후 59행 완주. adjudication 부재+예외 0 → 진행, 예외 1+부재 → `PART4_ADJUDICATION_MISSING`.
- 매니페스트 465항 전수 재계산: match 188 · MISMATCH 0 · absent 277(절대경로 참조 자산).
## 4. 검증 1차 FAIL → 수정 내역
| # | 결함 | 수정 |
|---|---|---|
| D1 (MAJOR) | 소비 선언 3건이 간접 래퍼(`try_read_json`)로 읽혀 검사기에 비가시(U4 ×3). 내 "finding 0" 주장도 존재하지 않는 출력 키를 읽은 허위였음 | 3건 모두 `read_json(경로상수)` 직접 호출 + try/except로 부재 의미론 보존. 검사기 재실행은 실재 키(`finding_count`·`counts_by_code`)로 판독 |
| D2 (MINOR) | LES 스키마 required에 `signal_manifest_transaction_id` 누락(§8-4b 미이행) | required 승격, 매니페스트 해시 갱신, 조립본 동기화 |
| D4 (관찰) | F2가 수용 status 리터럴 중복 보유, 정책 root_wrapping_table에 SG 5종 누락 | F2는 `policy.adjudication.accepted_status` 참조로 전환, SG 5종 진입행 추가 |
| D3 (문서) | 아래 §5 | 본 요약서에 정정 기록 |
## 5. 전략서 §7.4 정정 (D3)
전략서 §7.4의 "골든-S2 봉인 사슬이 현 배포본에서 그대로 통과한다"는 서술은 **현재 트리에서 거짓**이다. 골든-S2의 SG-01 사본이 담은 `registry_index_sha256`(`a47a76aa…`)은 현 배포 `domains/_registry_index.json` 실측 sha(`9f177ebf…`)와 다르다. 산출 코드는 봉인을 올바르게 검사했기 때문에 이 불일치에 걸린 것이며, 결함이 아니다.
**골든-S2 픽스처로 F0를 돌릴 때의 재봉인 레시피**: SG-01 사본의 `registry_index_sha256` 필드 **하나만** 현 배포 `domains/_registry_index.json`의 실측 sha(`9f177ebf…`)로 치환한다. 다른 필드는 무변경 — config_sha256 사슬은 현 registry entries에서 읽으므로 추가 보정이 불필요하다. (검증자가 이 방식으로 봉인 셋 통과를 2회 재현.)
## 6. 미측정·이월 항목
1. **실주행**: code-executor·localdocs 실환경 실행 미실시(서버에 신설 31자산 + 매니페스트 465 배포 필요).
2. **F1 실 LLM 품질**(§9-14): flash-lite 재정판정 품질은 스텁으로만 검증.
3. **게이트 실발화**: dict 3부류의 실사건 발화는 §9-3·5·6·13 해소 후 관측 가능.
4. **(나) 15필드 재산출 기준선**(G-r2/26-B): 골든 LES가 비어 있어 값 등가 미검증(현재는 공 LES에서 규칙 실행만 확인).
5. **G-w2** 비자명 등식 경로, **SUPP-calc6** 행 단위 승계 완결형.
6. F2 블록의 `try_read_json` 정의는 사문 헬퍼로 잔존(무해) — v.9 정리 후보.
7. 관례 수립: 검사기류 출력 소비 시 키 존재 단언(`assert "finding_count" in r`)을 선행할 것 — 1차 MAJOR의 뿌리였던 "부재 키 기본값을 성공으로 읽는" 유형 차단.
@@ -1 +1,87 @@
{"enforcement":{"declared":true,"note":"강제와 선언은 다르다. 이 파일이 선언을 맡는다.","runtime_guard":"Task_C_BO_A0 가 봉인 세 해시를 대조하므로 Part 1 미수행 시 SEAL_FAILED 로 멈춘다."},"interface_table":"handoffs/stage1_part_interface.v1.json","purpose":"Part 1~4 의 실행 순서와 인계 파일을 선언한다. 지금은 각 통합본의 prevs/nexts 가 비어 있어 순서가 A0 의 파일 부재 실패로 사실상 강제될 뿐 선언되어 있지 않다.","schema_version":"stage1_stage_chain.v1","stages":[{"order":1,"part":"Stage_1.Part_1","produces":["routing/domain_activation_manifest.json","quality_gates/stage1_part1_soft_gate_handoff.json","routing/domain_screening.json","evidence_indexed.json","evidence_event_candidates.json"],"requires":["client_meeting.md","evidence_all.json"],"yaml":"stage_1_part_1_v.8.yml"},{"order":2,"part":"Stage_1.Part_2","produces":["BO.json","signals/signal_manifest.json","actio_case_signals.json","case_liability_signals.json","legal_effect_signals.json","stage1_tmp/task_c_bo/postb_compiled_bundle_compact.json"],"requires":["routing/domain_activation_manifest.json","quality_gates/stage1_part1_soft_gate_handoff.json","routing/domain_screening.json","evidence_indexed.json","evidence_event_candidates.json"],"yaml":"stage_1_part_2_v.8.yml"},{"note":"정본 signal 집합(downstream_read_sets.part3_L0) 이관 완료. L1 없음 — 2 task 직렬.","order":3,"part":"Stage_1.Part_3","produces":["legal_effect_structures.json","stage1_tmp/task_le/structure_seed_bundle.json","quality_gates/stage1_part3_review_handoff.json","validation_assets/routing/part3_receipt.json"],"requires":["BO.json","signals/signal_manifest.json","routing/domain_activation_manifest.json"],"yaml":"stage_1_part_3_v.8.yml"},{"note":"같음.","order":4,"part":"Stage_1.Part_4","produces":[],"requires":["BO.json","signals/signal_manifest.json"],"yaml":"Stage_1_Part_4_Claude_v3.yml"}],"status":"ACTIVE"} {
"enforcement": {
"declared": true,
"note": "강제와 선언은 다르다. 이 파일이 선언을 맡는다.",
"runtime_guard": "Task_C_BO_A0 가 봉인 세 해시를 대조하므로 Part 1 미수행 시 SEAL_FAILED 로 멈춘다."
},
"interface_table": "handoffs/stage1_part_interface.v1.json",
"purpose": "Part 1~4 의 실행 순서와 인계 파일을 선언한다. 지금은 각 통합본의 prevs/nexts 가 비어 있어 순서가 A0 의 파일 부재 실패로 사실상 강제될 뿐 선언되어 있지 않다.",
"schema_version": "stage1_stage_chain.v1",
"stages": [
{
"order": 1,
"part": "Stage_1.Part_1",
"produces": [
"routing/domain_activation_manifest.json",
"quality_gates/stage1_part1_soft_gate_handoff.json",
"routing/domain_screening.json",
"evidence_indexed.json",
"evidence_event_candidates.json"
],
"requires": [
"client_meeting.md",
"evidence_all.json"
],
"yaml": "stage_1_part_1_v.8.yml"
},
{
"order": 2,
"part": "Stage_1.Part_2",
"produces": [
"BO.json",
"signals/signal_manifest.json",
"actio_case_signals.json",
"case_liability_signals.json",
"legal_effect_signals.json",
"stage1_tmp/task_c_bo/postb_compiled_bundle_compact.json"
],
"requires": [
"routing/domain_activation_manifest.json",
"quality_gates/stage1_part1_soft_gate_handoff.json",
"routing/domain_screening.json",
"evidence_indexed.json",
"evidence_event_candidates.json"
],
"yaml": "stage_1_part_2_v.8.yml"
},
{
"note": "정본 signal 집합(downstream_read_sets.part3_L0) 이관 완료. L1 없음 — 2 task 직렬.",
"order": 3,
"part": "Stage_1.Part_3",
"produces": [
"legal_effect_structures.json",
"stage1_tmp/task_le/structure_seed_bundle.json",
"quality_gates/stage1_part3_review_handoff.json",
"validation_assets/routing/part3_receipt.json"
],
"requires": [
"BO.json",
"signals/signal_manifest.json",
"routing/domain_activation_manifest.json"
],
"yaml": "stage_1_part_3_v.8.yml"
},
{
"note": "같음.",
"order": 4,
"part": "Stage_1.Part_4",
"produces": [
"Fact_Ledger_base.json",
"stage1_tmp/fact_ledger/fact_ledger_writer_report.json",
"quality_gates/stage1_part4_review_handoff.json",
"validation_assets/routing/part4_receipt.json"
],
"requires": [
"BO.json",
"signals/signal_manifest.json",
"legal_effect_structures.json",
"routing/domain_activation_manifest.json",
"evidence_indexed.json",
"signals/domain_signals/<domain_id>.json",
"quality_gates/stage1_part2_review_handoff.json"
],
"yaml": "stage_1_part_4_v.8.yml"
}
],
"status": "ACTIVE"
}
@@ -0,0 +1,93 @@
{
"$id": "https://schemas.liti-agent.local/stage1/part4/fact_exception_pack.schema.json",
"title": "stage1_fact_exception_pack.v1",
"description": "Part 4 F0 -> F1 예외 pack. 판정 부류는 정책 f1_exception_types 가 선언하며 unreachable_in_v8 부류가 실리면 PART4_UNREACHABLE_EXCEPTION_TYPE 경성 실패다.",
"type": "object",
"additionalProperties": false,
"required": [
"fact_exception_pack"
],
"properties": {
"fact_exception_pack": {
"type": "object",
"additionalProperties": false,
"required": [
"schema_version",
"has_exceptions",
"exception_count",
"exceptions",
"budget"
],
"properties": {
"schema_version": {
"const": "stage1_fact_exception_pack.v1"
},
"has_exceptions": {
"type": "boolean"
},
"exception_count": {
"type": "integer",
"minimum": 0
},
"exceptions": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"exception_id",
"exception_type",
"fact_id",
"source_bo_id",
"conflict_flags",
"compact_payload"
],
"properties": {
"exception_id": {
"type": "string",
"pattern": "^FX-[0-9]{3}$"
},
"exception_type": {
"type": "string"
},
"fact_id": {
"type": "string",
"pattern": "^F-[0-9]{3}$"
},
"source_bo_id": {
"type": "string",
"minLength": 1
},
"conflict_flags": {
"type": "array",
"items": {
"type": "string",
"pattern": "^[A-Z][A-Z0-9_]{2,127}$"
}
},
"compact_payload": {
"type": "object"
},
"oversize_review": {
"type": "boolean"
}
}
}
},
"budget": {
"type": "object",
"additionalProperties": false,
"required": [
"pack_item_char_limit"
],
"properties": {
"pack_item_char_limit": {
"type": "integer",
"minimum": 1
}
}
}
}
}
}
}
@@ -0,0 +1,406 @@
{
"$id": "https://schemas.liti-agent.local/stage1/part4/fact_ledger_base.schema.json",
"title": "stage1_fact_ledger_base.v1",
"description": "Part 4 최종 산출 — 래핑 없는 배열. 행 29키 = 구 27(순서·이름 불변) + domain_effects·calculation_requests(required 에 올리지 않는다 — Stage 2 호환 D-P4-4). 열거를 하드코딩하지 않는다 — 도메인 ID·검토 코드는 패턴만 강제한다.",
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"fact_id",
"source_bo_id",
"type",
"date",
"parties",
"object_spec",
"amount",
"action",
"evidence_refs",
"credibility",
"legal_centrality",
"proof_strength",
"legal_effect_roles",
"claim_chain_ref",
"linked_structures",
"must_not_drop_in_claim_types",
"must_consider",
"state_context",
"money_claim_effect",
"commercial_successor_effect",
"actio_roles",
"linked_actio_structures",
"downstream_module_candidates",
"skeleton_validation_codes",
"derived_fact",
"derivation_basis",
"legal_calculation_object"
],
"properties": {
"fact_id": {
"type": "string",
"pattern": "^F-[0-9]{3}$"
},
"source_bo_id": {
"type": "string",
"minLength": 1
},
"type": {
"type": [
"string",
"null"
]
},
"date": {
"type": [
"string",
"null"
]
},
"parties": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"role",
"value",
"source"
],
"properties": {
"role": {
"type": "string"
},
"value": {
"type": [
"string",
"null"
]
},
"source": {
"type": "string"
}
}
}
},
"object_spec": {
"type": [
"string",
"null"
]
},
"amount": {
"type": [
"string",
"null"
]
},
"action": {
"type": [
"string",
"null"
]
},
"evidence_refs": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"credibility": {
"enum": [
"high",
"medium",
"low"
]
},
"legal_centrality": {
"enum": [
"critical",
"high",
"medium",
"low"
]
},
"proof_strength": {
"enum": [
"documentary",
"high",
"medium",
"meeting_only_core",
"low"
]
},
"legal_effect_roles": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"claim_chain_ref": {
"type": "object",
"additionalProperties": false,
"required": [
"claim_group_ids",
"liability_group_ids",
"signal_count"
],
"properties": {
"claim_group_ids": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"liability_group_ids": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"signal_count": {
"type": "integer",
"minimum": 0
}
}
},
"linked_structures": {
"type": "object",
"additionalProperties": false,
"required": [
"structure_ids"
],
"properties": {
"structure_ids": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
}
}
},
"must_not_drop_in_claim_types": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"must_consider": {
"type": "boolean"
},
"state_context": {
"type": "object",
"additionalProperties": false,
"required": [
"bo_type",
"domain_ids"
],
"properties": {
"bo_type": {
"type": [
"string",
"null"
]
},
"domain_ids": {
"type": "array",
"items": {
"type": "string",
"pattern": "^(?:(?:EC|E)-[A-Z0-9]{2,8}|X[A-Z0-9]{1,8})$"
},
"uniqueItems": true
}
}
},
"money_claim_effect": {
"type": "object",
"additionalProperties": false,
"required": [
"enabled",
"structure_ids"
],
"properties": {
"enabled": {
"type": "boolean"
},
"structure_ids": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
}
}
},
"commercial_successor_effect": {
"type": "object",
"additionalProperties": false,
"required": [
"enabled",
"structure_ids"
],
"properties": {
"enabled": {
"type": "boolean"
},
"structure_ids": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
}
}
},
"actio_roles": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"linked_actio_structures": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"downstream_module_candidates": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"skeleton_validation_codes": {
"type": "array",
"items": {
"type": "string",
"pattern": "^[A-Z][A-Z0-9_]{2,127}$"
}
},
"derived_fact": {
"type": [
"string",
"null"
]
},
"derivation_basis": {
"type": "string"
},
"legal_calculation_object": {
"type": [
"string",
"null"
]
},
"domain_effects": {
"type": "object",
"additionalProperties": {
"type": "object",
"additionalProperties": false,
"required": [
"module",
"role_projection",
"structure_ids",
"type_ids",
"review_codes"
],
"properties": {
"module": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"role_projection": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"structure_ids": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"type_ids": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"review_codes": {
"type": "array",
"items": {
"type": "string",
"pattern": "^[A-Z][A-Z0-9_]{2,127}$"
},
"uniqueItems": true
}
}
}
},
"calculation_requests": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"calculation_id"
],
"properties": {
"calculation_id": {
"type": [
"string",
"null"
]
},
"request_id": {
"type": [
"string",
"null"
]
},
"operand_state": {
"enum": [
"ready",
"partial",
"blocked",
"deferred",
null
]
},
"missing_operands": {
"type": "array",
"items": {
"type": "string"
}
},
"law_version_refs": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"provenance": {
"type": "object"
}
}
}
}
}
}
}
@@ -0,0 +1,471 @@
{
"$id": "https://schemas.liti-agent.local/stage1/part4/fact_ledger_candidate_bundle.schema.json",
"title": "stage1_fact_ledger_candidate_bundle.v1",
"description": "Part 4 F0 산출 — 후보 행 묶음. domain_effects·calculation_requests 를 아는 것이 F0 의미 검사(PART4_LEDGER_SCHEMA_STALE)의 대상이다.",
"type": "object",
"additionalProperties": false,
"required": [
"fact_ledger_candidate_bundle"
],
"properties": {
"fact_ledger_candidate_bundle": {
"type": "object",
"additionalProperties": false,
"required": [
"schema_version",
"status",
"rows",
"gate_report",
"counts",
"signal_manifest_transaction_id",
"created_by"
],
"properties": {
"schema_version": {
"const": "stage1_fact_ledger_candidate_bundle.v1"
},
"status": {
"enum": [
"READY",
"READY_WITH_REVIEW"
]
},
"rows": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"fact_id",
"source_bo_id",
"type",
"date",
"parties",
"object_spec",
"amount",
"action",
"evidence_refs",
"credibility",
"legal_centrality",
"proof_strength",
"legal_effect_roles",
"claim_chain_ref",
"linked_structures",
"must_not_drop_in_claim_types",
"must_consider",
"state_context",
"money_claim_effect",
"commercial_successor_effect",
"actio_roles",
"linked_actio_structures",
"downstream_module_candidates",
"skeleton_validation_codes",
"derived_fact",
"derivation_basis",
"legal_calculation_object"
],
"properties": {
"fact_id": {
"type": "string",
"pattern": "^F-[0-9]{3}$"
},
"source_bo_id": {
"type": "string",
"minLength": 1
},
"type": {
"type": [
"string",
"null"
]
},
"date": {
"type": [
"string",
"null"
]
},
"parties": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"role",
"value",
"source"
],
"properties": {
"role": {
"type": "string"
},
"value": {
"type": [
"string",
"null"
]
},
"source": {
"type": "string"
}
}
}
},
"object_spec": {
"type": [
"string",
"null"
]
},
"amount": {
"type": [
"string",
"null"
]
},
"action": {
"type": [
"string",
"null"
]
},
"evidence_refs": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"credibility": {
"enum": [
"high",
"medium",
"low"
]
},
"legal_centrality": {
"enum": [
"critical",
"high",
"medium",
"low"
]
},
"proof_strength": {
"enum": [
"documentary",
"high",
"medium",
"meeting_only_core",
"low"
]
},
"legal_effect_roles": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"claim_chain_ref": {
"type": "object",
"additionalProperties": false,
"required": [
"claim_group_ids",
"liability_group_ids",
"signal_count"
],
"properties": {
"claim_group_ids": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"liability_group_ids": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"signal_count": {
"type": "integer",
"minimum": 0
}
}
},
"linked_structures": {
"type": "object",
"additionalProperties": false,
"required": [
"structure_ids"
],
"properties": {
"structure_ids": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
}
}
},
"must_not_drop_in_claim_types": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"must_consider": {
"type": "boolean"
},
"state_context": {
"type": "object",
"additionalProperties": false,
"required": [
"bo_type",
"domain_ids"
],
"properties": {
"bo_type": {
"type": [
"string",
"null"
]
},
"domain_ids": {
"type": "array",
"items": {
"type": "string",
"pattern": "^(?:(?:EC|E)-[A-Z0-9]{2,8}|X[A-Z0-9]{1,8})$"
},
"uniqueItems": true
}
}
},
"money_claim_effect": {
"type": "object",
"additionalProperties": false,
"required": [
"enabled",
"structure_ids"
],
"properties": {
"enabled": {
"type": "boolean"
},
"structure_ids": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
}
}
},
"commercial_successor_effect": {
"type": "object",
"additionalProperties": false,
"required": [
"enabled",
"structure_ids"
],
"properties": {
"enabled": {
"type": "boolean"
},
"structure_ids": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
}
}
},
"actio_roles": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"linked_actio_structures": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"downstream_module_candidates": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"skeleton_validation_codes": {
"type": "array",
"items": {
"type": "string",
"pattern": "^[A-Z][A-Z0-9_]{2,127}$"
}
},
"derived_fact": {
"type": [
"string",
"null"
]
},
"derivation_basis": {
"type": "string"
},
"legal_calculation_object": {
"type": [
"string",
"null"
]
},
"domain_effects": {
"type": "object",
"additionalProperties": {
"type": "object",
"additionalProperties": false,
"required": [
"module",
"role_projection",
"structure_ids",
"type_ids",
"review_codes"
],
"properties": {
"module": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"role_projection": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"structure_ids": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"type_ids": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"review_codes": {
"type": "array",
"items": {
"type": "string",
"pattern": "^[A-Z][A-Z0-9_]{2,127}$"
},
"uniqueItems": true
}
}
}
},
"calculation_requests": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"calculation_id"
],
"properties": {
"calculation_id": {
"type": [
"string",
"null"
]
},
"request_id": {
"type": [
"string",
"null"
]
},
"operand_state": {
"enum": [
"ready",
"partial",
"blocked",
"deferred",
null
]
},
"missing_operands": {
"type": "array",
"items": {
"type": "string"
}
},
"law_version_refs": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"provenance": {
"type": "object"
}
}
}
}
}
}
},
"gate_report": {
"type": "object"
},
"counts": {
"type": "object"
},
"signal_manifest_transaction_id": {
"type": [
"string",
"null"
]
},
"les_transaction_seal": {
"enum": [
"MATCHED",
"UNSEALED_REVIEW"
]
},
"expected_runnable_domain_ids": {
"type": "array",
"items": {
"type": "string",
"pattern": "^(?:(?:EC|E)-[A-Z0-9]{2,8}|X[A-Z0-9]{1,8})$"
},
"uniqueItems": true
},
"alias_resolution": {
"type": "object"
},
"created_by": {
"type": "string"
}
}
}
}
}
@@ -0,0 +1,381 @@
{
"$id": "https://schemas.liti-agent.local/stage1/part3/legal_effect_structures.schema.json",
"title": "stage1_legal_effect_structures.v1",
"description": "Part 3 L2 최종 산출 — 법률효과 구조와 역인덱스 셋(by_domain_id · by_module · by_bo_id). by_claim_form 은 만들지 않는다(claim-form 어휘가 registry 에 없다 — D-4 라). 열거를 하드코딩하지 않는다.",
"type": "object",
"additionalProperties": false,
"required": [
"schema_version",
"status",
"expected_runnable_domain_ids",
"theta",
"structure_records",
"structure_index",
"quality_gate",
"created_by",
"signal_manifest_transaction_id"
],
"properties": {
"schema_version": {
"const": "stage1_legal_effect_structures.v1"
},
"status": {
"enum": [
"READY",
"READY_WITH_REVIEW"
]
},
"registry_version": {
"type": [
"string",
"null"
]
},
"registry_index_sha256": {
"type": "string",
"pattern": "^[a-f0-9]{64}$"
},
"activation_manifest_sha256": {
"type": "string",
"pattern": "^[a-f0-9]{64}$"
},
"signal_manifest_transaction_id": {
"type": "string",
"pattern": "^S5TX-[a-f0-9]{20}$"
},
"expected_runnable_domain_ids": {
"type": "array",
"items": {
"type": "string",
"pattern": "^(?:(?:EC|E)-[A-Z0-9]{2,8}|X[A-Z0-9]{1,8})$"
}
},
"theta": {
"type": "array",
"items": {
"type": "string",
"pattern": "^[a-z][a-z0-9_]{1,127}$"
}
},
"structure_records": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"structure_id",
"domain_id",
"type_id",
"priority_rank",
"order_key",
"module",
"role_projection",
"admission_policy",
"routes",
"route_count",
"evidence_refs",
"meeting_clause_refs",
"calculation_request_refs",
"source_bo_ids",
"review_codes",
"review_required"
],
"properties": {
"structure_id": {
"type": "string",
"pattern": "^LES-[A-Za-z0-9._:-]{2,64}-[a-z][a-z0-9_]{1,127}$"
},
"domain_id": {
"type": "string",
"pattern": "^(?:(?:EC|E)-[A-Z0-9]{2,8}|X[A-Z0-9]{1,8})$"
},
"type_id": {
"type": "string",
"pattern": "^[a-z][a-z0-9_]{1,127}$"
},
"priority_rank": {
"type": [
"integer",
"null"
]
},
"order_key": {
"type": "array",
"minItems": 3,
"maxItems": 3
},
"module": {
"type": [
"string",
"null"
]
},
"role_projection": {
"type": [
"string",
"null"
]
},
"admission_policy": {
"type": "string",
"minLength": 1
},
"admission_rule": {
"type": "string"
},
"registered_declared": {
"type": "boolean"
},
"extension_key": {
"type": "string"
},
"routes": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"signal_id",
"source_seed_id",
"registered",
"status"
],
"properties": {
"signal_id": {
"type": [
"string",
"null"
]
},
"source_seed_id": {
"type": [
"string",
"null"
]
},
"registered": {
"type": [
"boolean",
"null"
]
},
"status": {
"type": [
"string",
"null"
]
}
}
}
},
"route_count": {
"type": "integer",
"minimum": 0
},
"evidence_refs": {
"type": "array",
"items": {
"type": "string"
}
},
"meeting_clause_refs": {
"type": "array",
"items": {
"type": "string"
}
},
"calculation_request_refs": {
"type": "array",
"items": {
"type": "string"
}
},
"source_bo_ids": {
"type": "array",
"items": {
"type": "string"
}
},
"review_codes": {
"type": "array",
"items": {
"type": "string",
"pattern": "^[A-Z][A-Z0-9_]{2,127}$"
}
},
"review_required": {
"type": "boolean"
}
}
}
},
"structure_index": {
"type": "object",
"additionalProperties": false,
"required": [
"by_domain_id",
"by_module",
"by_bo_id"
],
"properties": {
"by_domain_id": {
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"type": "string"
}
}
},
"by_module": {
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"type": "string"
}
}
},
"by_bo_id": {
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
},
"quality_gate": {
"type": "object",
"additionalProperties": false,
"required": [
"declaration_conservation",
"route_conservation",
"bo_conservation",
"review_queue"
],
"properties": {
"declaration_conservation": {
"type": "object",
"additionalProperties": false,
"required": [
"structure_records",
"declared_total_from_config",
"conservation_pass"
],
"properties": {
"structure_records": {
"type": "integer",
"minimum": 0
},
"declared_total_from_config": {
"type": "integer",
"minimum": 0
},
"conservation_pass": {
"type": "boolean"
}
}
},
"route_conservation": {
"type": "object",
"additionalProperties": false,
"required": [
"routes_attached",
"routes_unattached",
"sg13_route_total_from_source",
"conservation_pass"
],
"properties": {
"routes_attached": {
"type": "integer",
"minimum": 0
},
"routes_unattached": {
"type": "integer",
"minimum": 0
},
"sg13_route_total_from_source": {
"type": "integer",
"minimum": 0
},
"conservation_pass": {
"type": "boolean"
}
}
},
"bo_conservation": {
"type": "object",
"additionalProperties": false,
"required": [
"bo_attached",
"bo_unattached",
"bo_total_from_source",
"conservation_pass"
],
"properties": {
"bo_attached": {
"type": "integer",
"minimum": 0
},
"bo_unattached": {
"type": "integer",
"minimum": 0
},
"bo_total_from_source": {
"type": "integer",
"minimum": 0
},
"conservation_pass": {
"type": "boolean"
}
}
},
"review_queue": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"review_code",
"severity"
],
"properties": {
"review_code": {
"type": "string",
"pattern": "^[A-Z][A-Z0-9_]{2,127}$"
},
"severity": {
"enum": [
"SOFT_WARNING",
"HARD_WARNING"
]
},
"domain_id": {
"type": [
"string",
"null"
]
},
"structure_id": {
"type": [
"string",
"null"
]
},
"count": {
"type": "integer",
"minimum": 1
}
}
}
}
}
},
"created_by": {
"const": "Task_C_LE_L2_final_structure_index_writer"
}
}
}
@@ -0,0 +1,910 @@
#!/usr/bin/env python3
"""Compile v.8 Fact Ledger candidate rows from BO, LES, signals, envelopes, and gate sidecars.
순수 함수 모듈 — I/O·시각·난수 없음. 규칙 선언은 fact_ledger_policy.v1.json 이 유일 정본이며
이 모듈은 그것을 읽어 dispatch 만 한다. gate_id·27필드·registry type_id 리터럴을 코드에 두지
않는다(정적 검사 11·12). 사건 종류 이름을 어디에도 두지 않는다(137종 원칙).
공개 API
normalize_gate_rules(sidecar_doc, policy) 17축 방언 정규화 리더
resolve_alias_table(alias_declarations, sidecars, policy)
compile_fact_ledger_candidates(...) 조립 본체 (아래 시그니처)
"""
from __future__ import annotations
import json
import re
from typing import Any
_WS_RE = re.compile(r"\s+")
_CODE_RE = re.compile(r"^[A-Z][A-Z0-9_]{2,127}$")
_DOMAIN_RE = re.compile(r"^(?:(?:EC|E)-[A-Z0-9]{2,8}|X[A-Z0-9]{1,8})$")
# ---------------------------------------------------------------------------
# 공용 헬퍼
# ---------------------------------------------------------------------------
def _dict(value: Any) -> dict:
return value if isinstance(value, dict) else {}
def _list(value: Any) -> list:
return value if isinstance(value, list) else []
def _strings(value: Any) -> list[str]:
return [item for item in _list(value) if isinstance(item, str)]
def _nonblank(value: Any) -> bool:
return value not in (None, "", [], {})
def _text(value: Any, limit: int) -> str | None:
"""키 오름차순 JSON -> 공백열 단일 공백 -> limit 절단 -> 빈 문자열이면 None."""
if value is None:
return None
if isinstance(value, str):
rendered = value
else:
rendered = json.dumps(value, ensure_ascii=False, sort_keys=True)
rendered = _WS_RE.sub(" ", rendered).strip()
if not rendered:
return None
return rendered[:limit]
def _first_value(*values: Any) -> Any:
for value in values:
if _nonblank(value):
return value
return None
def _first_key(record: dict, keys: list[str]) -> Any:
for key in keys:
if _nonblank(record.get(key)):
return record[key]
return None
def _dedupe_keep_order(items: list[str]) -> list[str]:
seen: set[str] = set()
out: list[str] = []
for item in items:
if item not in seen:
seen.add(item)
out.append(item)
return out
# ---------------------------------------------------------------------------
# F0-0c — BO 압축·정규화 (구본 _compact_bo 등가 계층. (가) 9필드 전부의 전제)
# ---------------------------------------------------------------------------
def compact_bo(bo: dict, policy: dict) -> dict:
norm = _dict(policy.get("bo_normalization"))
key_map = _dict(norm.get("key_map"))
keep = _strings(norm.get("domain_payload_keep_keys"))
out: dict[str, Any] = {}
for key, value in bo.items():
out[key_map.get(key, key)] = value
payload = _dict(_dict(bo.get("extensions")).get("domain_payload"))
out["domain_payload"] = {k: payload[k] for k in keep
if k in payload and _nonblank(payload[k])}
out["core"] = _dict(bo.get("core_field_base"))
return out
# ---------------------------------------------------------------------------
# (다) — 증거력 산정 (정책 evidence_scoring_da_2 의 5갈래 · 순서 그대로)
# ---------------------------------------------------------------------------
def _flatten_evidence(document: Any, depth: int = 3) -> list[dict]:
rows: list[dict] = []
def visit(node: Any, level: int) -> None:
if level > depth:
return
if isinstance(node, dict):
if any(k in node for k in ("evidence_index", "id", "evidence_id",
"eid", "index", "doc_id")):
rows.append(node)
return
for value in node.values():
visit(value, level + 1)
elif isinstance(node, list):
for item in node:
visit(item, level + 1)
visit(document, 0)
return rows
def _evidence_id(record: dict) -> str | None:
value = _first_key(record, ["evidence_index", "id", "evidence_id",
"eid", "index", "doc_id"])
return value if isinstance(value, str) else None
def _excerpt_from_indexed(record: dict) -> str:
facts = _strings(record.get("key_facts"))
if facts:
return " ".join(facts)
parts = _strings(record.get("key_dates")) + _strings(record.get("key_amounts"))
if parts:
return " ".join(parts)
for value in record.values():
if isinstance(value, str) and value.strip():
return value
return ""
def build_evidence_authority(evidence_document: Any) -> dict[str, dict]:
authority: dict[str, dict] = {}
for record in _flatten_evidence(evidence_document):
eid = _evidence_id(record)
if not eid:
continue
authority[eid] = {
"evidence_index": eid,
"title": _first_key(record, ["title", "Title", "name", "doc_title"]),
"excerpt": _excerpt_from_indexed(record),
"priority": _first_key(record, ["priority", "Priority", "weight"]),
"authentication": _first_key(record, ["authentication", "auth",
"source_authentication"]),
"source": "evidence_indexed.json",
}
return authority
def score_evidence(evidence_refs: list[str], authority: dict[str, dict],
policy: dict, reviews: list[dict],
fact_id: str) -> tuple[str, str, int]:
"""(credibility, proof_strength, 절단여유 최소값) — 정책 5갈래 순서 그대로."""
scoring = _dict(policy.get("evidence_scoring_da_2"))
markers = _strings(scoring.get("markers_documentary"))
rows: list[dict] = []
for ref in evidence_refs:
row = authority.get(ref)
if row is None:
# 우주 밖 참조 — stub 도 비어 있지 않은 증거 행으로 계수된다(정책 승계).
reviews.append({"review_code": "EVIDENCE_REF_NOT_IN_EVIDENCE_UNIVERSE",
"severity": "SOFT_WARNING", "fact_id": fact_id,
"detail": ref})
row = {"evidence_index": ref, "missing": True}
rows.append(row)
if not rows:
return "low", "low", -1
pieces: list[str] = []
slack = 10 ** 9
for row in rows:
rendered = _WS_RE.sub(" ", json.dumps(row, ensure_ascii=False,
sort_keys=True)).strip()
slack = min(slack, 300 - len(rendered))
pieces.append(rendered[:300])
text = " ".join(pieces)
if any(marker in text for marker in markers):
return "high", "documentary", slack
if len(rows) >= 2:
return "high", "high", slack
if ("meeting" in text) or ("회의" in text) or ("면담" in text):
return "medium", "meeting_only_core", slack
return "medium", "medium", slack
# ---------------------------------------------------------------------------
# F0-0 — 17축 사이드카 방언 정규화 리더
# ---------------------------------------------------------------------------
def normalize_gate_rules(sidecar_doc: Any, policy: dict) -> dict:
"""사이드카 한 파일을 정규화한다. {rules: {gate_id: norm}, file_meta, warnings}."""
axes = _dict(_dict(policy.get("gate_execution")).get("normalization_axes_17"))
array_keys = _strings(axes.get("rule_array_keys")) or ["rules", "gates"]
code_keys = _strings(axes.get("emitted_code_keys"))
doc = _dict(sidecar_doc)
raw_rules: list[dict] = []
array_key = None
for key in array_keys:
if isinstance(doc.get(key), list):
array_key = key
raw_rules = [r for r in doc[key] if isinstance(r, dict)]
break
warnings: list[dict] = []
norm_rules: dict[str, dict] = {}
for rule in raw_rules:
gate_id = rule.get("gate_id")
if not isinstance(gate_id, str):
warnings.append({"code": "GATE_RULE_WITHOUT_ID", "detail": str(rule)[:120]})
continue
writer = _dict(rule.get("writer_effect"))
row_eff = _dict(rule.get("row_effect"))
on_true = _dict(rule.get("on_true"))
condition = rule.get("condition")
cond_kind = "dict" if isinstance(condition, dict) else "str"
cond_key = next(iter(condition), None) if cond_kind == "dict" else None
cond_value = condition.get(cond_key) if cond_kind == "dict" else condition
emitted: list[str] = []
for key in code_keys:
value = rule.get(key)
if isinstance(value, str):
emitted.append(value)
else:
emitted.extend(_strings(value))
blocks = None
for container, key in (("writer_effect", "final_drafting_block"),
("writer_effect", "blocks_final_drafting"),
("on_true", "blocks_final_drafting")):
holder = writer if container == "writer_effect" else on_true
if key in holder:
blocks = bool(holder[key])
break
status_fire = (writer.get("status_when_triggered")
or writer.get("status_on_fire")
or on_true.get("writer_status"))
evaluation = (rule.get("evaluation_mode") or rule.get("implementation_type")
or rule.get("evaluation"))
norm_rules[gate_id] = {
"gate_id": gate_id,
"condition_kind": cond_kind,
"condition_key": cond_key,
"condition_value": cond_value,
"emitted_codes": _dedupe_keep_order(emitted),
"blocks_final_drafting": blocks,
"status_when_triggered": status_fire,
"row_must_consider": bool(row_eff.get("must_consider")),
"preserve_seed": bool(on_true.get("preserve_seed")),
"unknown_or_missing_policy": rule.get("unknown_or_missing_policy"),
"evaluation": evaluation,
"writes_fact_ledger": writer.get("writes_fact_ledger"),
"silent_repair_forbidden": rule.get("silent_repair_forbidden"),
"source_membership_required": rule.get("source_membership_required"),
"input_declaration": rule.get("input_fields") or rule.get("input_contracts"),
}
if doc.get("global_status_rule") is not None:
warnings.append({"code": "SIDECAR_GLOBAL_STATUS_RULE_IGNORED",
"detail": str(doc.get("global_status_rule"))[:120]})
return {"rules": norm_rules,
"file_meta": {"array_key": array_key,
"schema_version": doc.get("schema_version"),
"status_key": ("closure_status" if "closure_status" in doc
else ("status" if "status" in doc else None))},
"warnings": warnings}
# ---------------------------------------------------------------------------
# F0-0b — 별칭 해소 (D-P4-7 — 전수 자기선언 대조 · 침묵 탈락 금지)
# ---------------------------------------------------------------------------
def resolve_alias_table(alias_declarations: Any, structure_type_sidecars: dict,
policy: dict) -> dict:
root = _dict(alias_declarations)
root = _dict(root.get("extension_payload_key_declarations", root))
table = {k: _strings(v) for k, v in _dict(root.get("legacy_alias_targets")).items()}
derived_mismatches = _list(root.get("legacy_declaration_mismatches"))
abbrev = _dict(_dict(policy.get("legacy_alias")).get("abbreviation_map"))
self_decl: dict[str, set] = {}
for domain_id, doc in sorted(_dict(structure_type_sidecars).items()):
doc = _dict(doc)
rows = None
for key in ("types", "structure_types", "records", "entries"):
if isinstance(doc.get(key), list):
rows = doc[key]
break
for row in rows or []:
if not isinstance(row, dict):
continue
value = row.get("legacy_source") or row.get("legacy_source_domain")
if not isinstance(value, str):
continue
head = value.split("_")[0]
if head not in abbrev:
continue # 'Part3 fallback' 등 비 B* 값은 선언이 아니다
self_decl.setdefault(abbrev[head], set()).add(domain_id)
reviews: list[dict] = []
mismatch_map: dict[str, dict] = {}
for legacy in sorted(set(table) | set(self_decl)):
declared = self_decl.get(legacy, set())
aliased = set(table.get(legacy, []))
missing = sorted(declared - aliased)
excess = sorted(aliased - declared)
if missing:
mismatch_map[legacy] = {"missing": missing, "excess": excess}
reviews.append({"review_code": "LEGACY_ALIAS_SELF_MISMATCH_REVIEW",
"severity": "SOFT_WARNING", "legacy": legacy,
"missing_domain_ids": missing,
"excess_domain_ids": excess})
derived_missing = sorted({str(_dict(m).get("domain_id"))
for m in derived_mismatches if _dict(m).get("domain_id")})
actual_missing = sorted({d for m in mismatch_map.values() for d in m["missing"]})
if derived_missing != actual_missing:
reviews.append({"review_code": "LEGACY_ALIAS_DECLARATION_INCOMPLETE_REVIEW",
"severity": "SOFT_WARNING",
"derived_list": derived_missing,
"actual_missing": actual_missing})
return {"table": table, "self_declarations": {k: sorted(v) for k, v in self_decl.items()},
"mismatches": mismatch_map, "reviews": reviews}
# ---------------------------------------------------------------------------
# 조립 본체
# ---------------------------------------------------------------------------
def compile_fact_ledger_candidates(
bo_document: Any,
les_document: Any,
signal_documents: dict,
domain_envelopes: dict,
p2_handoff_document: Any,
evidence_document: Any,
registry_index: Any,
domain_configs: dict,
gate_sidecars: dict,
structure_type_sidecars: dict,
activation_document: Any,
alias_declarations: Any,
policy: dict) -> dict:
reviews: list[dict] = []
audit: dict[str, Any] = {}
activation_root = _dict(activation_document)
activation_root = _dict(activation_root.get("domain_activation_manifest",
activation_root))
expected_runnable = _strings(activation_root.get("expected_runnable_domain_ids"))
# ---- F0-0 리더 · F0-0b 별칭 ----------------------------------------
normalized: dict[str, dict] = {}
dialect_counts: dict[str, Any] = {"array_key": {}, "files": 0}
reader_warnings: list[dict] = []
for domain_id, sidecar in sorted(_dict(gate_sidecars).items()):
result = normalize_gate_rules(sidecar, policy)
normalized[domain_id] = result
dialect_counts["files"] += 1
ak = result["file_meta"]["array_key"]
dialect_counts["array_key"][ak] = dialect_counts["array_key"].get(ak, 0) + 1
reader_warnings.extend(result["warnings"])
audit["sidecar_dialects"] = dialect_counts
audit["reader_warnings"] = reader_warnings
alias = resolve_alias_table(alias_declarations, structure_type_sidecars, policy)
reviews.extend(alias["reviews"])
# ---- LES 적재 -------------------------------------------------------
les_root = _dict(les_document)
structure_records = [r for r in _list(les_root.get("structure_records"))
if isinstance(r, dict)]
record_by_id = {r.get("structure_id"): r for r in structure_records
if isinstance(r.get("structure_id"), str)}
index = _dict(les_root.get("structure_index"))
by_bo_id = {k: _strings(v) for k, v in _dict(index.get("by_bo_id")).items()}
# ---- SG 적재 --------------------------------------------------------
def signal_records(sg: str) -> list[dict]:
doc = _dict(_dict(signal_documents).get(sg))
return [r for r in _list(doc.get("records")) if isinstance(r, dict)]
sg11_records = signal_records("SG-11")
sg11_by_signal_id = {r.get("signal_id"): r for r in sg11_records
if isinstance(r.get("signal_id"), str)}
sg04_by_domain: dict[str, list[str]] = {}
for record in signal_records("SG-04"):
for domain_id in _strings(record.get("domain_ids")):
if isinstance(record.get("signal_id"), str):
sg04_by_domain.setdefault(domain_id, []).append(record["signal_id"])
# ---- F0-7b — 게이트 입력 정규화 ------------------------------------
handoff_root = _dict(p2_handoff_document)
handoff_root = _dict(handoff_root.get("stage1_part2_review_handoff", handoff_root))
handoff_items = [r for r in _list(handoff_root.get("review_items"))
if isinstance(r, dict)]
p2_rules = _dict(policy.get("p2_handoff_rules"))
code_pool: dict[str, list[str]] = {}
gate_input_counts = {"envelope_review_items": 0, "handoff_codes": 0,
"handoff_not_a_code": 0, "handoff_domain_unresolved": 0,
"slot_entries": 0, "operand_states": 0}
for domain_id in expected_runnable:
env = _dict(_dict(domain_envelopes).get(domain_id))
env = _dict(env.get("domain_signal_envelope", env))
for item in _list(env.get("review_items")):
code = _dict(item).get("review_code") or _dict(item).get("code")
if isinstance(code, str) and _CODE_RE.match(code):
code_pool.setdefault(domain_id, []).append(code)
gate_input_counts["envelope_review_items"] += 1
for item in handoff_items:
raw_code = item.get("source_review_code")
if not (isinstance(raw_code, str) and _CODE_RE.match(raw_code)):
if raw_code is not None:
gate_input_counts["handoff_not_a_code"] += 1
reviews.append({"review_code": "P2_HANDOFF_CODE_NOT_A_REVIEW_CODE_REVIEW",
"severity": "SOFT_WARNING",
"detail": str(raw_code)[:80]})
continue
source_domain = item.get("source_domain")
targets: list[str] = []
if isinstance(source_domain, str):
if _DOMAIN_RE.match(source_domain):
targets = [source_domain]
elif source_domain in alias["table"]:
targets = alias["table"][source_domain]
if not targets:
gate_input_counts["handoff_domain_unresolved"] += 1
reviews.append({"review_code": "P2_HANDOFF_DOMAIN_UNRESOLVED_REVIEW",
"severity": "SOFT_WARNING",
"detail": str(source_domain)[:80]})
continue
for domain_id in targets:
code_pool.setdefault(domain_id, []).append(raw_code)
gate_input_counts["handoff_codes"] += 1
slot_status: dict[str, list[dict]] = {}
for domain_id in expected_runnable:
env = _dict(_dict(domain_envelopes).get(domain_id))
env = _dict(env.get("domain_signal_envelope", env))
entries = [e for e in _list(env.get("evidence_slot_status"))
if isinstance(e, dict)]
if entries:
slot_status[domain_id] = entries
gate_input_counts["slot_entries"] += len(entries)
operand_by_domain: dict[str, list[str]] = {}
for record in sg11_records:
details = _dict(record.get("details"))
state = record.get("operand_state") or details.get("operand_state")
if not isinstance(state, str):
continue
for domain_id in (_strings(record.get("domain_ids"))
or _strings(details.get("domain_ids"))):
operand_by_domain.setdefault(domain_id, []).append(state)
gate_input_counts["operand_states"] += 1
audit["gate_input_counts"] = gate_input_counts
# ---- F0-1 ~ F0-6b — 행 생성 ----------------------------------------
bo_records = [b for b in _list(bo_document) if isinstance(b, dict)]
bo_sorted = sorted(bo_records, key=lambda b: str(b.get("BO_ID") or ""))
authority = build_evidence_authority(evidence_document)
calc_types = set(_strings(policy.get("legal_calculation_object_types")))
na_defaults = _dict(policy.get("field_synthesis_na_15"))
rows: list[dict] = []
attach_stats = {"attached_bo": 0, "unattached_bo": 0}
used_structure_ids: set[str] = set()
used_sg11: set[str] = set()
slack_min = 10 ** 9
for idx, bo in enumerate(bo_sorted, 1):
compact = compact_bo(bo, policy)
core = compact["core"]
fact_id = "F-%03d" % idx
bo_id = str(bo.get("BO_ID") or "")
# (가) 9필드
parties = []
if _nonblank(core.get("Performer")):
parties.append({"role": "actor", "value": _text(core.get("Performer"), 160),
"source": "BO.core_field_base"})
if _nonblank(core.get("Subject")):
parties.append({"role": "counterparty",
"value": _text(core.get("Subject"), 160),
"source": "BO.core_field_base"})
amount = _text(_first_value(compact.get("amount"), core.get("Amount")), 240)
action = _first_value(compact.get("action"), core.get("Action"))
evidence_refs = _strings(bo.get("source_evidence_indexes"))
# F0-3 — LES 조인
structure_ids = sorted(set(by_bo_id.get(bo_id, [])))
attached = [record_by_id[s] for s in structure_ids if s in record_by_id]
if attached:
attach_stats["attached_bo"] += 1
else:
attach_stats["unattached_bo"] += 1
reviews.append({"review_code": "FACT_ROW_UNATTACHED_REVIEW",
"severity": "SOFT_WARNING", "fact_id": fact_id,
"detail": bo_id})
used_structure_ids.update(s for s in structure_ids if s in record_by_id)
domain_effects: dict[str, dict] = {}
for record in attached:
domain_id = record.get("domain_id")
if not isinstance(domain_id, str):
continue
slot = domain_effects.setdefault(domain_id, {
"module": set(), "role_projection": set(), "structure_ids": set(),
"type_ids": set(), "review_codes": set()})
if isinstance(record.get("module"), str):
slot["module"].add(record["module"])
if isinstance(record.get("role_projection"), str):
slot["role_projection"].add(record["role_projection"])
if isinstance(record.get("structure_id"), str):
slot["structure_ids"].add(record["structure_id"])
if isinstance(record.get("type_id"), str):
slot["type_ids"].add(record["type_id"])
slot["review_codes"].update(
c for c in _strings(record.get("review_codes")) if _CODE_RE.match(c))
domain_effects = {d: {k: sorted(v) for k, v in slot.items()}
for d, slot in sorted(domain_effects.items())}
modules = sorted({m for slot in domain_effects.values() for m in slot["module"]})
roles = sorted({r for slot in domain_effects.values()
for r in slot["role_projection"]})
type_ids = sorted({t for slot in domain_effects.values()
for t in slot["type_ids"]})
# F0-5 — SG-11 조인 + operand_state 승계 (재산정 금지)
calculation_requests: list[dict] = []
route_refs: list[str] = []
for record in attached:
for ref in _strings(record.get("calculation_request_refs")):
route_refs.append(ref)
sg = sg11_by_signal_id.get(ref)
if sg is None:
calculation_requests.append({"calculation_id": None,
"request_id": ref,
"provenance": {"signal_id": ref,
"dangling": True}})
reviews.append({"review_code": "CALCULATION_REF_DANGLING_REVIEW",
"severity": "SOFT_WARNING", "fact_id": fact_id,
"detail": ref})
continue
used_sg11.add(ref)
details = _dict(sg.get("details"))
calc_id = details.get("calculation_id")
if not isinstance(calc_id, str) or not calc_id:
calc_id = ref
reviews.append({"review_code": "CALCULATION_ID_SUBSTITUTED_REVIEW",
"severity": "SOFT_WARNING", "fact_id": fact_id,
"detail": ref})
law_refs = sorted(set(
_strings(sg.get("law_version_refs"))
+ _strings(details.get("law_version_refs"))
+ [ref2 for d in _strings(sg.get("domain_ids"))
for ref2 in sg04_by_domain.get(d, [])]))
calculation_requests.append({
"calculation_id": calc_id,
"request_id": ref,
"operand_state": (sg.get("operand_state")
or details.get("operand_state")),
"missing_operands": _strings(details.get("missing_operands")),
"law_version_refs": law_refs,
"provenance": {"signal_id": ref,
"structure_ids": sorted(
s for s in structure_ids
if ref in _strings(
_dict(record_by_id.get(s)).get(
"calculation_request_refs")))},
})
# (다) 증거력
credibility, proof_strength, slack = score_evidence(
evidence_refs, authority, policy, reviews, fact_id)
slack_min = min(slack_min, slack)
# (나) 15필드
object_spec = _text(core.get("Object"), 160)
calc_obj_candidates = sorted(calc_types & set(type_ids))
legal_calculation_object = calc_obj_candidates[0] if calc_obj_candidates else None
alias_vocab = _dict(policy.get("alias_module_vocabulary"))
mod_actio = alias_vocab.get("actio_fields")
mod_money = alias_vocab.get("money_claim_effect")
mod_successor = alias_vocab.get("commercial_successor_effect")
actio_ids = sorted({s for slot in domain_effects.values()
for s in slot["structure_ids"]
if mod_actio in slot["module"]})
actio_roles = sorted({r for slot in domain_effects.values()
if mod_actio in slot["module"]
for r in slot["role_projection"]})
money_ids = sorted({s for slot in domain_effects.values()
if mod_money in slot["module"]
for s in slot["structure_ids"]})
successor_ids = sorted({s for slot in domain_effects.values()
if mod_successor in slot["module"]
for s in slot["structure_ids"]})
row = {
"fact_id": fact_id,
"source_bo_id": bo_id,
"type": compact.get("bo_type") if isinstance(compact.get("bo_type"), str) else None,
"date": _first_value(core.get("BehaviorTime"), compact.get("date")),
"parties": parties,
"object_spec": object_spec,
"amount": amount,
"action": action if isinstance(action, str) else _text(action, 240),
"evidence_refs": evidence_refs,
"credibility": credibility,
"legal_centrality": "high" if attached else "medium",
"proof_strength": proof_strength,
"legal_effect_roles": roles,
"claim_chain_ref": {"claim_group_ids": [], "liability_group_ids": [],
"signal_count": len(route_refs)},
"linked_structures": {"structure_ids": structure_ids},
"must_not_drop_in_claim_types": modules,
"must_consider": bool(attached),
"state_context": {"bo_type": compact.get("bo_type")
if isinstance(compact.get("bo_type"), str) else None,
"domain_ids": sorted(domain_effects)},
"money_claim_effect": {"enabled": bool(money_ids),
"structure_ids": money_ids},
"commercial_successor_effect": {"enabled": bool(successor_ids),
"structure_ids": successor_ids},
"actio_roles": actio_roles,
"linked_actio_structures": actio_ids,
"downstream_module_candidates": modules,
"skeleton_validation_codes": [],
"derived_fact": action if isinstance(action, str) else _text(action, 240),
"derivation_basis": ("structure_ids=" + ",".join(structure_ids)
if structure_ids else "bo_only"),
"legal_calculation_object": legal_calculation_object,
"domain_effects": domain_effects,
"calculation_requests": calculation_requests,
}
rows.append(row)
audit["evidence_truncation_slack_min"] = (None if slack_min == 10 ** 9 else slack_min)
# ---- F0-8 — 게이트 합집합 실행 (§4.4 그대로) ------------------------
gate_exec = _dict(policy.get("gate_execution"))
implemented = set(_strings(gate_exec.get("implemented_condition_keys")))
default_missing_policy = str(gate_exec.get("unknown_or_missing_policy_default")
or "fail_open_to_review")
declared_gates: dict[str, dict] = {}
for domain_id in expected_runnable:
config = _dict(_dict(domain_configs).get(domain_id))
config_root = _dict(config.get("domain_config", config))
for gate in _list(config_root.get("gates")):
gate = _dict(gate)
gate_id = gate.get("gate_id")
if isinstance(gate_id, str):
declared_gates[gate_id] = {"domain_id": domain_id, "declared": gate}
all_rules: dict[str, dict] = {}
for domain_id, result in normalized.items():
for gate_id, rule in result["rules"].items():
all_rules[gate_id] = {"domain_id": domain_id, "rule": rule}
gate_report: list[dict] = []
fired_by_domain: dict[str, list[dict]] = {}
for gate_id in sorted(declared_gates):
info = declared_gates[gate_id]
domain_id = info["domain_id"]
declared = info["declared"]
registry_codes = [c for c in _strings(declared.get("review_codes"))
if _CODE_RE.match(c)]
entry: dict[str, Any] = {"gate_id": gate_id, "domain_id": domain_id,
"fired": False, "input_state": "n/a"}
rule_info = all_rules.get(gate_id)
if rule_info is None:
entry["disposition"] = "GATE_SPEC_NOT_DEPLOYED_REVIEW"
reviews.append({"review_code": "GATE_SPEC_NOT_DEPLOYED_REVIEW",
"severity": "SOFT_WARNING", "gate_id": gate_id})
gate_report.append(entry)
continue
rule = rule_info["rule"]
fired = False
if rule["condition_kind"] == "dict":
key = rule["condition_key"]
if key in implemented:
entry["disposition"] = "implemented"
value = rule["condition_value"]
if key == "any_review_code_present":
pool = code_pool.get(domain_id)
if pool is None and domain_id not in _dict(domain_envelopes):
entry["input_state"] = "input_missing"
entry["missing_policy"] = (
rule["unknown_or_missing_policy"] or default_missing_policy)
else:
entry["input_state"] = "supplied"
fired = bool(set(_strings(value)) & set(pool or []))
elif key == "required_slots_missing_or_conflicting":
entries = slot_status.get(domain_id)
if entries is None and domain_id not in _dict(domain_envelopes):
entry["input_state"] = "input_missing"
entry["missing_policy"] = (
rule["unknown_or_missing_policy"] or default_missing_policy)
else:
entry["input_state"] = "supplied"
wanted = set(_strings(value))
fired = any(
(e.get("slot_id") in wanted)
and (e.get("status") in ("missing", "conflicted"))
for e in entries or [])
elif key == "calculation_operand_state_in":
states = operand_by_domain.get(domain_id)
entry["input_state"] = ("supplied" if states else "input_missing")
fired = bool(set(_strings(value)) & set(states or []))
else:
entry["disposition"] = "GATE_CONDITION_KEY_UNSUPPORTED_REVIEW"
entry["condition_key"] = key
reviews.append({"review_code": "GATE_CONDITION_KEY_UNSUPPORTED_REVIEW",
"severity": "SOFT_WARNING", "gate_id": gate_id,
"detail": str(key)})
else:
entry["disposition"] = "GATE_PREDICATE_SPEC_UNIMPLEMENTED_REVIEW"
entry["predicate_spec"] = _text(rule["condition_value"], 200)
reviews.append({"review_code": "GATE_PREDICATE_SPEC_UNIMPLEMENTED_REVIEW",
"severity": "SOFT_WARNING", "gate_id": gate_id,
"detail": _text(rule["condition_value"], 160)})
# 차단 표지 대조 — registry ↔ 정규화된 사이드카.
declared_blocks = declared.get("blocks_final_drafting")
if (rule["blocks_final_drafting"] is not None
and declared_blocks is not None
and bool(declared_blocks) != rule["blocks_final_drafting"]):
reviews.append({"review_code": "GATE_BLOCK_FLAG_MISMATCH_REVIEW",
"severity": "SOFT_WARNING", "gate_id": gate_id})
entry["block_flag_mismatch"] = True
emitted = _dedupe_keep_order(rule["emitted_codes"] + registry_codes)
excess = [c for c in rule["emitted_codes"] if c not in registry_codes]
if excess:
entry["codes_not_in_registry"] = excess
reviews.append({"review_code": "GATE_EMITTED_CODE_NOT_IN_REGISTRY",
"severity": "SOFT_WARNING", "gate_id": gate_id,
"detail": ",".join(excess)[:160]})
entry["fired"] = fired
entry["blocks_final_drafting"] = (rule["blocks_final_drafting"]
if rule["blocks_final_drafting"] is not None
else bool(declared_blocks))
if fired:
projection_rows = [r["fact_id"] for r in rows
if domain_id in r["domain_effects"]]
entry["projected_row_count"] = len(projection_rows)
fired_by_domain.setdefault(domain_id, []).append({
"gate_id": gate_id, "codes": emitted,
"row_must_consider": rule["row_must_consider"],
"preserve_seed": rule["preserve_seed"],
"blocks_final_drafting": entry["blocks_final_drafting"],
"status_when_triggered": rule["status_when_triggered"],
"projected_rows": projection_rows,
})
gate_report.append(entry)
orphan_rules = sorted(set(all_rules) - set(declared_gates))
for gate_id in orphan_rules:
reviews.append({"review_code": "GATE_RULE_NOT_IN_REGISTRY",
"severity": "SOFT_WARNING", "gate_id": gate_id})
# ---- 행 투영 3분기 + F0-8b 스켈레톤 --------------------------------
skeleton_order = _strings(_dict(policy.get("skeleton_checks")).get("order"))
for row in rows:
codes: list[str] = []
if not _nonblank(row["derived_fact"]):
codes.append(skeleton_order[0])
if not row["evidence_refs"]:
codes.append(skeleton_order[1])
if not row["linked_structures"]["structure_ids"]:
codes.append(skeleton_order[2])
gate_codes: list[str] = []
for domain_id in sorted(row["domain_effects"]):
for firing in sorted(fired_by_domain.get(domain_id, []),
key=lambda f: f["gate_id"]):
gate_codes.extend(firing["codes"])
if firing["row_must_consider"]:
row["must_consider"] = row["must_consider"] or True
row["skeleton_validation_codes"] = _dedupe_keep_order(codes + gate_codes)
# ---- STRUCTURE_UNATTACHED_REVIEW -----------------------------------
unattached_structures = sorted(set(record_by_id) - used_structure_ids)
for structure_id in unattached_structures:
reviews.append({"review_code": "STRUCTURE_UNATTACHED_REVIEW",
"severity": "SOFT_WARNING", "detail": structure_id})
# ---- F0-9 — 예외 pack ----------------------------------------------
pack_policy = _dict(policy.get("exception_pack"))
char_limit = int(pack_policy.get("pack_item_char_limit") or 2000)
exceptions: list[dict] = []
for row in rows:
if row["legal_calculation_object"] is None:
continue
bo = next((b for b in bo_sorted
if str(b.get("BO_ID") or "") == row["source_bo_id"]), {})
core = _dict(bo.get("core_field_base"))
amount_candidates = [v for v in (bo.get("amount"), core.get("Amount"))
if _nonblank(v)]
if len(amount_candidates) >= 2:
payload = {"amount_candidates": [_text(v, 240) for v in amount_candidates],
"object_spec": row["object_spec"],
"calculation_request_ids": sorted(
str(c.get("calculation_id")) for c in
row["calculation_requests"] if c.get("calculation_id"))}
item = {"exception_id": "FX-%03d" % (len(exceptions) + 1),
"exception_type": "legal_calculation_required",
"fact_id": row["fact_id"],
"source_bo_id": row["source_bo_id"],
"conflict_flags": ["CALCULATION_BASIS_CONFLICT"],
"compact_payload": payload}
rendered = json.dumps(item, ensure_ascii=False)
if len(rendered) > char_limit:
item["oversize_review"] = True
reviews.append({"review_code": "CALCULATION_BASIS_INCOMPLETE_REVIEW",
"severity": "SOFT_WARNING",
"fact_id": row["fact_id"],
"detail": "pack item oversize %d>%d" % (len(rendered),
char_limit)})
exceptions.append(item)
elif (not _nonblank(row["amount"])) or (not _nonblank(row["object_spec"])):
reviews.append({"review_code": "CALCULATION_BASIS_INCOMPLETE_REVIEW",
"severity": "SOFT_WARNING", "fact_id": row["fact_id"],
"detail": "amount_or_object_spec_blank"})
unreachable = {name for name, meta in
_dict(policy.get("f1_exception_types")).items()
if _dict(meta).get("unreachable_in_v8")}
for item in exceptions:
if item["exception_type"] in unreachable:
raise ValueError(json.dumps({
"reason_code": "PART4_UNREACHABLE_EXCEPTION_TYPE",
"exception_type": item["exception_type"]}, ensure_ascii=False))
# ---- F0-10 — 보존 등식 계수 ----------------------------------------
effect_left = sum(len(r["domain_effects"]) for r in rows)
effect_right = 0
calc_right = 0
for bo in bo_sorted:
bo_id = str(bo.get("BO_ID") or "")
attached = [record_by_id[s] for s in by_bo_id.get(bo_id, [])
if s in record_by_id]
effect_right += len({r.get("domain_id") for r in attached
if isinstance(r.get("domain_id"), str)})
calc_right += len({ref for r in attached
for ref in _strings(r.get("calculation_request_refs"))})
calc_left = sum(len(r["calculation_requests"]) for r in rows)
sg11_total = len(sg11_records)
sg11_unused = sg11_total - len(used_sg11)
equations = {
"row": {"left": len(rows), "right": len(bo_sorted),
"holds": len(rows) == len(bo_sorted)},
"attachment_coverage": {
"attached": attach_stats["attached_bo"],
"unattached": attach_stats["unattached_bo"],
"total": len(bo_sorted),
"holds": (attach_stats["attached_bo"] + attach_stats["unattached_bo"]
== len(bo_sorted))},
"effect": {"left": effect_left, "right": effect_right,
"holds": effect_left == effect_right},
"calc_row": {"left": calc_left, "right": calc_right,
"holds": calc_left == calc_right},
"calc_source": {"attached_distinct": len(used_sg11),
"unattached_distinct": sg11_unused,
"total": sg11_total,
"holds": len(used_sg11) + sg11_unused == sg11_total},
}
# ---- 상태·집계 ------------------------------------------------------
les_review_codes = set(_strings(policy.get("les_inherited_review_codes")))
les_review_present = any(
(set(slot["review_codes"]) & les_review_codes)
for row in rows for slot in row["domain_effects"].values()) or any(
_dict(r).get("review_required") for r in structure_records)
gate_fired_any = any(f for firings in fired_by_domain.values() for f in firings)
status = ("READY_WITH_REVIEW"
if (reviews or exceptions or les_review_present or gate_fired_any)
else "READY")
counts = {
"rows": len(rows),
"bo_records": len(bo_sorted),
"structure_records": len(structure_records),
"gates_declared": len(declared_gates),
"gates_implemented": sum(1 for e in gate_report
if e.get("disposition") == "implemented"),
"gates_visualized_str": sum(
1 for e in gate_report
if e.get("disposition") == "GATE_PREDICATE_SPEC_UNIMPLEMENTED_REVIEW"),
"gates_visualized_unsupported": sum(
1 for e in gate_report
if e.get("disposition") == "GATE_CONDITION_KEY_UNSUPPORTED_REVIEW"),
"gates_fired": sum(1 for e in gate_report if e.get("fired")),
"orphan_rules": len(orphan_rules),
"review_items": len(reviews),
"exceptions": len(exceptions),
"conservation": equations,
}
bundle = {
"schema_version": "stage1_fact_ledger_candidate_bundle.v1",
"status": status,
"rows": rows,
"gate_report": {"gates": gate_report,
"fired_by_domain": {d: f for d, f in
sorted(fired_by_domain.items())},
"orphan_rule_ids": orphan_rules},
"counts": counts,
"signal_manifest_transaction_id": None,
"expected_runnable_domain_ids": expected_runnable,
"alias_resolution": {"table": alias["table"],
"self_declarations": alias["self_declarations"],
"mismatches": alias["mismatches"]},
"created_by": "fact_ledger_compiler",
}
pack = {
"schema_version": "stage1_fact_exception_pack.v1",
"has_exceptions": bool(exceptions),
"exception_count": len(exceptions),
"exceptions": exceptions,
"budget": {"pack_item_char_limit": char_limit},
}
audit["unattached_structure_ids"] = unattached_structures
audit["equations"] = equations
return {"fact_ledger_candidate_bundle": bundle,
"fact_exception_pack": pack,
"review_items": reviews,
"audit": audit}
@@ -0,0 +1,910 @@
#!/usr/bin/env python3
"""Compile v.8 Fact Ledger candidate rows from BO, LES, signals, envelopes, and gate sidecars.
순수 함수 모듈 — I/O·시각·난수 없음. 규칙 선언은 fact_ledger_policy.v1.json 이 유일 정본이며
이 모듈은 그것을 읽어 dispatch 만 한다. gate_id·27필드·registry type_id 리터럴을 코드에 두지
않는다(정적 검사 11·12). 사건 종류 이름을 어디에도 두지 않는다(137종 원칙).
공개 API
normalize_gate_rules(sidecar_doc, policy) 17축 방언 정규화 리더
resolve_alias_table(alias_declarations, sidecars, policy)
compile_fact_ledger_candidates(...) 조립 본체 (아래 시그니처)
"""
from __future__ import annotations
import json
import re
from typing import Any
_WS_RE = re.compile(r"\s+")
_CODE_RE = re.compile(r"^[A-Z][A-Z0-9_]{2,127}$")
_DOMAIN_RE = re.compile(r"^(?:(?:EC|E)-[A-Z0-9]{2,8}|X[A-Z0-9]{1,8})$")
# ---------------------------------------------------------------------------
# 공용 헬퍼
# ---------------------------------------------------------------------------
def _dict(value: Any) -> dict:
return value if isinstance(value, dict) else {}
def _list(value: Any) -> list:
return value if isinstance(value, list) else []
def _strings(value: Any) -> list[str]:
return [item for item in _list(value) if isinstance(item, str)]
def _nonblank(value: Any) -> bool:
return value not in (None, "", [], {})
def _text(value: Any, limit: int) -> str | None:
"""키 오름차순 JSON -> 공백열 단일 공백 -> limit 절단 -> 빈 문자열이면 None."""
if value is None:
return None
if isinstance(value, str):
rendered = value
else:
rendered = json.dumps(value, ensure_ascii=False, sort_keys=True)
rendered = _WS_RE.sub(" ", rendered).strip()
if not rendered:
return None
return rendered[:limit]
def _first_value(*values: Any) -> Any:
for value in values:
if _nonblank(value):
return value
return None
def _first_key(record: dict, keys: list[str]) -> Any:
for key in keys:
if _nonblank(record.get(key)):
return record[key]
return None
def _dedupe_keep_order(items: list[str]) -> list[str]:
seen: set[str] = set()
out: list[str] = []
for item in items:
if item not in seen:
seen.add(item)
out.append(item)
return out
# ---------------------------------------------------------------------------
# F0-0c — BO 압축·정규화 (구본 _compact_bo 등가 계층. (가) 9필드 전부의 전제)
# ---------------------------------------------------------------------------
def compact_bo(bo: dict, policy: dict) -> dict:
norm = _dict(policy.get("bo_normalization"))
key_map = _dict(norm.get("key_map"))
keep = _strings(norm.get("domain_payload_keep_keys"))
out: dict[str, Any] = {}
for key, value in bo.items():
out[key_map.get(key, key)] = value
payload = _dict(_dict(bo.get("extensions")).get("domain_payload"))
out["domain_payload"] = {k: payload[k] for k in keep
if k in payload and _nonblank(payload[k])}
out["core"] = _dict(bo.get("core_field_base"))
return out
# ---------------------------------------------------------------------------
# (다) — 증거력 산정 (정책 evidence_scoring_da_2 의 5갈래 · 순서 그대로)
# ---------------------------------------------------------------------------
def _flatten_evidence(document: Any, depth: int = 3) -> list[dict]:
rows: list[dict] = []
def visit(node: Any, level: int) -> None:
if level > depth:
return
if isinstance(node, dict):
if any(k in node for k in ("evidence_index", "id", "evidence_id",
"eid", "index", "doc_id")):
rows.append(node)
return
for value in node.values():
visit(value, level + 1)
elif isinstance(node, list):
for item in node:
visit(item, level + 1)
visit(document, 0)
return rows
def _evidence_id(record: dict) -> str | None:
value = _first_key(record, ["evidence_index", "id", "evidence_id",
"eid", "index", "doc_id"])
return value if isinstance(value, str) else None
def _excerpt_from_indexed(record: dict) -> str:
facts = _strings(record.get("key_facts"))
if facts:
return " ".join(facts)
parts = _strings(record.get("key_dates")) + _strings(record.get("key_amounts"))
if parts:
return " ".join(parts)
for value in record.values():
if isinstance(value, str) and value.strip():
return value
return ""
def build_evidence_authority(evidence_document: Any) -> dict[str, dict]:
authority: dict[str, dict] = {}
for record in _flatten_evidence(evidence_document):
eid = _evidence_id(record)
if not eid:
continue
authority[eid] = {
"evidence_index": eid,
"title": _first_key(record, ["title", "Title", "name", "doc_title"]),
"excerpt": _excerpt_from_indexed(record),
"priority": _first_key(record, ["priority", "Priority", "weight"]),
"authentication": _first_key(record, ["authentication", "auth",
"source_authentication"]),
"source": "evidence_indexed.json",
}
return authority
def score_evidence(evidence_refs: list[str], authority: dict[str, dict],
policy: dict, reviews: list[dict],
fact_id: str) -> tuple[str, str, int]:
"""(credibility, proof_strength, 절단여유 최소값) — 정책 5갈래 순서 그대로."""
scoring = _dict(policy.get("evidence_scoring_da_2"))
markers = _strings(scoring.get("markers_documentary"))
rows: list[dict] = []
for ref in evidence_refs:
row = authority.get(ref)
if row is None:
# 우주 밖 참조 — stub 도 비어 있지 않은 증거 행으로 계수된다(정책 승계).
reviews.append({"review_code": "EVIDENCE_REF_NOT_IN_EVIDENCE_UNIVERSE",
"severity": "SOFT_WARNING", "fact_id": fact_id,
"detail": ref})
row = {"evidence_index": ref, "missing": True}
rows.append(row)
if not rows:
return "low", "low", -1
pieces: list[str] = []
slack = 10 ** 9
for row in rows:
rendered = _WS_RE.sub(" ", json.dumps(row, ensure_ascii=False,
sort_keys=True)).strip()
slack = min(slack, 300 - len(rendered))
pieces.append(rendered[:300])
text = " ".join(pieces)
if any(marker in text for marker in markers):
return "high", "documentary", slack
if len(rows) >= 2:
return "high", "high", slack
if ("meeting" in text) or ("회의" in text) or ("면담" in text):
return "medium", "meeting_only_core", slack
return "medium", "medium", slack
# ---------------------------------------------------------------------------
# F0-0 — 17축 사이드카 방언 정규화 리더
# ---------------------------------------------------------------------------
def normalize_gate_rules(sidecar_doc: Any, policy: dict) -> dict:
"""사이드카 한 파일을 정규화한다. {rules: {gate_id: norm}, file_meta, warnings}."""
axes = _dict(_dict(policy.get("gate_execution")).get("normalization_axes_17"))
array_keys = _strings(axes.get("rule_array_keys")) or ["rules", "gates"]
code_keys = _strings(axes.get("emitted_code_keys"))
doc = _dict(sidecar_doc)
raw_rules: list[dict] = []
array_key = None
for key in array_keys:
if isinstance(doc.get(key), list):
array_key = key
raw_rules = [r for r in doc[key] if isinstance(r, dict)]
break
warnings: list[dict] = []
norm_rules: dict[str, dict] = {}
for rule in raw_rules:
gate_id = rule.get("gate_id")
if not isinstance(gate_id, str):
warnings.append({"code": "GATE_RULE_WITHOUT_ID", "detail": str(rule)[:120]})
continue
writer = _dict(rule.get("writer_effect"))
row_eff = _dict(rule.get("row_effect"))
on_true = _dict(rule.get("on_true"))
condition = rule.get("condition")
cond_kind = "dict" if isinstance(condition, dict) else "str"
cond_key = next(iter(condition), None) if cond_kind == "dict" else None
cond_value = condition.get(cond_key) if cond_kind == "dict" else condition
emitted: list[str] = []
for key in code_keys:
value = rule.get(key)
if isinstance(value, str):
emitted.append(value)
else:
emitted.extend(_strings(value))
blocks = None
for container, key in (("writer_effect", "final_drafting_block"),
("writer_effect", "blocks_final_drafting"),
("on_true", "blocks_final_drafting")):
holder = writer if container == "writer_effect" else on_true
if key in holder:
blocks = bool(holder[key])
break
status_fire = (writer.get("status_when_triggered")
or writer.get("status_on_fire")
or on_true.get("writer_status"))
evaluation = (rule.get("evaluation_mode") or rule.get("implementation_type")
or rule.get("evaluation"))
norm_rules[gate_id] = {
"gate_id": gate_id,
"condition_kind": cond_kind,
"condition_key": cond_key,
"condition_value": cond_value,
"emitted_codes": _dedupe_keep_order(emitted),
"blocks_final_drafting": blocks,
"status_when_triggered": status_fire,
"row_must_consider": bool(row_eff.get("must_consider")),
"preserve_seed": bool(on_true.get("preserve_seed")),
"unknown_or_missing_policy": rule.get("unknown_or_missing_policy"),
"evaluation": evaluation,
"writes_fact_ledger": writer.get("writes_fact_ledger"),
"silent_repair_forbidden": rule.get("silent_repair_forbidden"),
"source_membership_required": rule.get("source_membership_required"),
"input_declaration": rule.get("input_fields") or rule.get("input_contracts"),
}
if doc.get("global_status_rule") is not None:
warnings.append({"code": "SIDECAR_GLOBAL_STATUS_RULE_IGNORED",
"detail": str(doc.get("global_status_rule"))[:120]})
return {"rules": norm_rules,
"file_meta": {"array_key": array_key,
"schema_version": doc.get("schema_version"),
"status_key": ("closure_status" if "closure_status" in doc
else ("status" if "status" in doc else None))},
"warnings": warnings}
# ---------------------------------------------------------------------------
# F0-0b — 별칭 해소 (D-P4-7 — 전수 자기선언 대조 · 침묵 탈락 금지)
# ---------------------------------------------------------------------------
def resolve_alias_table(alias_declarations: Any, structure_type_sidecars: dict,
policy: dict) -> dict:
root = _dict(alias_declarations)
root = _dict(root.get("extension_payload_key_declarations", root))
table = {k: _strings(v) for k, v in _dict(root.get("legacy_alias_targets")).items()}
derived_mismatches = _list(root.get("legacy_declaration_mismatches"))
abbrev = _dict(_dict(policy.get("legacy_alias")).get("abbreviation_map"))
self_decl: dict[str, set] = {}
for domain_id, doc in sorted(_dict(structure_type_sidecars).items()):
doc = _dict(doc)
rows = None
for key in ("types", "structure_types", "records", "entries"):
if isinstance(doc.get(key), list):
rows = doc[key]
break
for row in rows or []:
if not isinstance(row, dict):
continue
value = row.get("legacy_source") or row.get("legacy_source_domain")
if not isinstance(value, str):
continue
head = value.split("_")[0]
if head not in abbrev:
continue # 'Part3 fallback' 등 비 B* 값은 선언이 아니다
self_decl.setdefault(abbrev[head], set()).add(domain_id)
reviews: list[dict] = []
mismatch_map: dict[str, dict] = {}
for legacy in sorted(set(table) | set(self_decl)):
declared = self_decl.get(legacy, set())
aliased = set(table.get(legacy, []))
missing = sorted(declared - aliased)
excess = sorted(aliased - declared)
if missing:
mismatch_map[legacy] = {"missing": missing, "excess": excess}
reviews.append({"review_code": "LEGACY_ALIAS_SELF_MISMATCH_REVIEW",
"severity": "SOFT_WARNING", "legacy": legacy,
"missing_domain_ids": missing,
"excess_domain_ids": excess})
derived_missing = sorted({str(_dict(m).get("domain_id"))
for m in derived_mismatches if _dict(m).get("domain_id")})
actual_missing = sorted({d for m in mismatch_map.values() for d in m["missing"]})
if derived_missing != actual_missing:
reviews.append({"review_code": "LEGACY_ALIAS_DECLARATION_INCOMPLETE_REVIEW",
"severity": "SOFT_WARNING",
"derived_list": derived_missing,
"actual_missing": actual_missing})
return {"table": table, "self_declarations": {k: sorted(v) for k, v in self_decl.items()},
"mismatches": mismatch_map, "reviews": reviews}
# ---------------------------------------------------------------------------
# 조립 본체
# ---------------------------------------------------------------------------
def compile_fact_ledger_candidates(
bo_document: Any,
les_document: Any,
signal_documents: dict,
domain_envelopes: dict,
p2_handoff_document: Any,
evidence_document: Any,
registry_index: Any,
domain_configs: dict,
gate_sidecars: dict,
structure_type_sidecars: dict,
activation_document: Any,
alias_declarations: Any,
policy: dict) -> dict:
reviews: list[dict] = []
audit: dict[str, Any] = {}
activation_root = _dict(activation_document)
activation_root = _dict(activation_root.get("domain_activation_manifest",
activation_root))
expected_runnable = _strings(activation_root.get("expected_runnable_domain_ids"))
# ---- F0-0 리더 · F0-0b 별칭 ----------------------------------------
normalized: dict[str, dict] = {}
dialect_counts: dict[str, Any] = {"array_key": {}, "files": 0}
reader_warnings: list[dict] = []
for domain_id, sidecar in sorted(_dict(gate_sidecars).items()):
result = normalize_gate_rules(sidecar, policy)
normalized[domain_id] = result
dialect_counts["files"] += 1
ak = result["file_meta"]["array_key"]
dialect_counts["array_key"][ak] = dialect_counts["array_key"].get(ak, 0) + 1
reader_warnings.extend(result["warnings"])
audit["sidecar_dialects"] = dialect_counts
audit["reader_warnings"] = reader_warnings
alias = resolve_alias_table(alias_declarations, structure_type_sidecars, policy)
reviews.extend(alias["reviews"])
# ---- LES 적재 -------------------------------------------------------
les_root = _dict(les_document)
structure_records = [r for r in _list(les_root.get("structure_records"))
if isinstance(r, dict)]
record_by_id = {r.get("structure_id"): r for r in structure_records
if isinstance(r.get("structure_id"), str)}
index = _dict(les_root.get("structure_index"))
by_bo_id = {k: _strings(v) for k, v in _dict(index.get("by_bo_id")).items()}
# ---- SG 적재 --------------------------------------------------------
def signal_records(sg: str) -> list[dict]:
doc = _dict(_dict(signal_documents).get(sg))
return [r for r in _list(doc.get("records")) if isinstance(r, dict)]
sg11_records = signal_records("SG-11")
sg11_by_signal_id = {r.get("signal_id"): r for r in sg11_records
if isinstance(r.get("signal_id"), str)}
sg04_by_domain: dict[str, list[str]] = {}
for record in signal_records("SG-04"):
for domain_id in _strings(record.get("domain_ids")):
if isinstance(record.get("signal_id"), str):
sg04_by_domain.setdefault(domain_id, []).append(record["signal_id"])
# ---- F0-7b — 게이트 입력 정규화 ------------------------------------
handoff_root = _dict(p2_handoff_document)
handoff_root = _dict(handoff_root.get("stage1_part2_review_handoff", handoff_root))
handoff_items = [r for r in _list(handoff_root.get("review_items"))
if isinstance(r, dict)]
p2_rules = _dict(policy.get("p2_handoff_rules"))
code_pool: dict[str, list[str]] = {}
gate_input_counts = {"envelope_review_items": 0, "handoff_codes": 0,
"handoff_not_a_code": 0, "handoff_domain_unresolved": 0,
"slot_entries": 0, "operand_states": 0}
for domain_id in expected_runnable:
env = _dict(_dict(domain_envelopes).get(domain_id))
env = _dict(env.get("domain_signal_envelope", env))
for item in _list(env.get("review_items")):
code = _dict(item).get("review_code") or _dict(item).get("code")
if isinstance(code, str) and _CODE_RE.match(code):
code_pool.setdefault(domain_id, []).append(code)
gate_input_counts["envelope_review_items"] += 1
for item in handoff_items:
raw_code = item.get("source_review_code")
if not (isinstance(raw_code, str) and _CODE_RE.match(raw_code)):
if raw_code is not None:
gate_input_counts["handoff_not_a_code"] += 1
reviews.append({"review_code": "P2_HANDOFF_CODE_NOT_A_REVIEW_CODE_REVIEW",
"severity": "SOFT_WARNING",
"detail": str(raw_code)[:80]})
continue
source_domain = item.get("source_domain")
targets: list[str] = []
if isinstance(source_domain, str):
if _DOMAIN_RE.match(source_domain):
targets = [source_domain]
elif source_domain in alias["table"]:
targets = alias["table"][source_domain]
if not targets:
gate_input_counts["handoff_domain_unresolved"] += 1
reviews.append({"review_code": "P2_HANDOFF_DOMAIN_UNRESOLVED_REVIEW",
"severity": "SOFT_WARNING",
"detail": str(source_domain)[:80]})
continue
for domain_id in targets:
code_pool.setdefault(domain_id, []).append(raw_code)
gate_input_counts["handoff_codes"] += 1
slot_status: dict[str, list[dict]] = {}
for domain_id in expected_runnable:
env = _dict(_dict(domain_envelopes).get(domain_id))
env = _dict(env.get("domain_signal_envelope", env))
entries = [e for e in _list(env.get("evidence_slot_status"))
if isinstance(e, dict)]
if entries:
slot_status[domain_id] = entries
gate_input_counts["slot_entries"] += len(entries)
operand_by_domain: dict[str, list[str]] = {}
for record in sg11_records:
details = _dict(record.get("details"))
state = record.get("operand_state") or details.get("operand_state")
if not isinstance(state, str):
continue
for domain_id in (_strings(record.get("domain_ids"))
or _strings(details.get("domain_ids"))):
operand_by_domain.setdefault(domain_id, []).append(state)
gate_input_counts["operand_states"] += 1
audit["gate_input_counts"] = gate_input_counts
# ---- F0-1 ~ F0-6b — 행 생성 ----------------------------------------
bo_records = [b for b in _list(bo_document) if isinstance(b, dict)]
bo_sorted = sorted(bo_records, key=lambda b: str(b.get("BO_ID") or ""))
authority = build_evidence_authority(evidence_document)
calc_types = set(_strings(policy.get("legal_calculation_object_types")))
na_defaults = _dict(policy.get("field_synthesis_na_15"))
rows: list[dict] = []
attach_stats = {"attached_bo": 0, "unattached_bo": 0}
used_structure_ids: set[str] = set()
used_sg11: set[str] = set()
slack_min = 10 ** 9
for idx, bo in enumerate(bo_sorted, 1):
compact = compact_bo(bo, policy)
core = compact["core"]
fact_id = "F-%03d" % idx
bo_id = str(bo.get("BO_ID") or "")
# (가) 9필드
parties = []
if _nonblank(core.get("Performer")):
parties.append({"role": "actor", "value": _text(core.get("Performer"), 160),
"source": "BO.core_field_base"})
if _nonblank(core.get("Subject")):
parties.append({"role": "counterparty",
"value": _text(core.get("Subject"), 160),
"source": "BO.core_field_base"})
amount = _text(_first_value(compact.get("amount"), core.get("Amount")), 240)
action = _first_value(compact.get("action"), core.get("Action"))
evidence_refs = _strings(bo.get("source_evidence_indexes"))
# F0-3 — LES 조인
structure_ids = sorted(set(by_bo_id.get(bo_id, [])))
attached = [record_by_id[s] for s in structure_ids if s in record_by_id]
if attached:
attach_stats["attached_bo"] += 1
else:
attach_stats["unattached_bo"] += 1
reviews.append({"review_code": "FACT_ROW_UNATTACHED_REVIEW",
"severity": "SOFT_WARNING", "fact_id": fact_id,
"detail": bo_id})
used_structure_ids.update(s for s in structure_ids if s in record_by_id)
domain_effects: dict[str, dict] = {}
for record in attached:
domain_id = record.get("domain_id")
if not isinstance(domain_id, str):
continue
slot = domain_effects.setdefault(domain_id, {
"module": set(), "role_projection": set(), "structure_ids": set(),
"type_ids": set(), "review_codes": set()})
if isinstance(record.get("module"), str):
slot["module"].add(record["module"])
if isinstance(record.get("role_projection"), str):
slot["role_projection"].add(record["role_projection"])
if isinstance(record.get("structure_id"), str):
slot["structure_ids"].add(record["structure_id"])
if isinstance(record.get("type_id"), str):
slot["type_ids"].add(record["type_id"])
slot["review_codes"].update(
c for c in _strings(record.get("review_codes")) if _CODE_RE.match(c))
domain_effects = {d: {k: sorted(v) for k, v in slot.items()}
for d, slot in sorted(domain_effects.items())}
modules = sorted({m for slot in domain_effects.values() for m in slot["module"]})
roles = sorted({r for slot in domain_effects.values()
for r in slot["role_projection"]})
type_ids = sorted({t for slot in domain_effects.values()
for t in slot["type_ids"]})
# F0-5 — SG-11 조인 + operand_state 승계 (재산정 금지)
calculation_requests: list[dict] = []
route_refs: list[str] = []
for record in attached:
for ref in _strings(record.get("calculation_request_refs")):
route_refs.append(ref)
sg = sg11_by_signal_id.get(ref)
if sg is None:
calculation_requests.append({"calculation_id": None,
"request_id": ref,
"provenance": {"signal_id": ref,
"dangling": True}})
reviews.append({"review_code": "CALCULATION_REF_DANGLING_REVIEW",
"severity": "SOFT_WARNING", "fact_id": fact_id,
"detail": ref})
continue
used_sg11.add(ref)
details = _dict(sg.get("details"))
calc_id = details.get("calculation_id")
if not isinstance(calc_id, str) or not calc_id:
calc_id = ref
reviews.append({"review_code": "CALCULATION_ID_SUBSTITUTED_REVIEW",
"severity": "SOFT_WARNING", "fact_id": fact_id,
"detail": ref})
law_refs = sorted(set(
_strings(sg.get("law_version_refs"))
+ _strings(details.get("law_version_refs"))
+ [ref2 for d in _strings(sg.get("domain_ids"))
for ref2 in sg04_by_domain.get(d, [])]))
calculation_requests.append({
"calculation_id": calc_id,
"request_id": ref,
"operand_state": (sg.get("operand_state")
or details.get("operand_state")),
"missing_operands": _strings(details.get("missing_operands")),
"law_version_refs": law_refs,
"provenance": {"signal_id": ref,
"structure_ids": sorted(
s for s in structure_ids
if ref in _strings(
_dict(record_by_id.get(s)).get(
"calculation_request_refs")))},
})
# (다) 증거력
credibility, proof_strength, slack = score_evidence(
evidence_refs, authority, policy, reviews, fact_id)
slack_min = min(slack_min, slack)
# (나) 15필드
object_spec = _text(core.get("Object"), 160)
calc_obj_candidates = sorted(calc_types & set(type_ids))
legal_calculation_object = calc_obj_candidates[0] if calc_obj_candidates else None
alias_vocab = _dict(policy.get("alias_module_vocabulary"))
mod_actio = alias_vocab.get("actio_fields")
mod_money = alias_vocab.get("money_claim_effect")
mod_successor = alias_vocab.get("commercial_successor_effect")
actio_ids = sorted({s for slot in domain_effects.values()
for s in slot["structure_ids"]
if mod_actio in slot["module"]})
actio_roles = sorted({r for slot in domain_effects.values()
if mod_actio in slot["module"]
for r in slot["role_projection"]})
money_ids = sorted({s for slot in domain_effects.values()
if mod_money in slot["module"]
for s in slot["structure_ids"]})
successor_ids = sorted({s for slot in domain_effects.values()
if mod_successor in slot["module"]
for s in slot["structure_ids"]})
row = {
"fact_id": fact_id,
"source_bo_id": bo_id,
"type": compact.get("bo_type") if isinstance(compact.get("bo_type"), str) else None,
"date": _first_value(core.get("BehaviorTime"), compact.get("date")),
"parties": parties,
"object_spec": object_spec,
"amount": amount,
"action": action if isinstance(action, str) else _text(action, 240),
"evidence_refs": evidence_refs,
"credibility": credibility,
"legal_centrality": "high" if attached else "medium",
"proof_strength": proof_strength,
"legal_effect_roles": roles,
"claim_chain_ref": {"claim_group_ids": [], "liability_group_ids": [],
"signal_count": len(route_refs)},
"linked_structures": {"structure_ids": structure_ids},
"must_not_drop_in_claim_types": modules,
"must_consider": bool(attached),
"state_context": {"bo_type": compact.get("bo_type")
if isinstance(compact.get("bo_type"), str) else None,
"domain_ids": sorted(domain_effects)},
"money_claim_effect": {"enabled": bool(money_ids),
"structure_ids": money_ids},
"commercial_successor_effect": {"enabled": bool(successor_ids),
"structure_ids": successor_ids},
"actio_roles": actio_roles,
"linked_actio_structures": actio_ids,
"downstream_module_candidates": modules,
"skeleton_validation_codes": [],
"derived_fact": action if isinstance(action, str) else _text(action, 240),
"derivation_basis": ("structure_ids=" + ",".join(structure_ids)
if structure_ids else "bo_only"),
"legal_calculation_object": legal_calculation_object,
"domain_effects": domain_effects,
"calculation_requests": calculation_requests,
}
rows.append(row)
audit["evidence_truncation_slack_min"] = (None if slack_min == 10 ** 9 else slack_min)
# ---- F0-8 — 게이트 합집합 실행 (§4.4 그대로) ------------------------
gate_exec = _dict(policy.get("gate_execution"))
implemented = set(_strings(gate_exec.get("implemented_condition_keys")))
default_missing_policy = str(gate_exec.get("unknown_or_missing_policy_default")
or "fail_open_to_review")
declared_gates: dict[str, dict] = {}
for domain_id in expected_runnable:
config = _dict(_dict(domain_configs).get(domain_id))
config_root = _dict(config.get("domain_config", config))
for gate in _list(config_root.get("gates")):
gate = _dict(gate)
gate_id = gate.get("gate_id")
if isinstance(gate_id, str):
declared_gates[gate_id] = {"domain_id": domain_id, "declared": gate}
all_rules: dict[str, dict] = {}
for domain_id, result in normalized.items():
for gate_id, rule in result["rules"].items():
all_rules[gate_id] = {"domain_id": domain_id, "rule": rule}
gate_report: list[dict] = []
fired_by_domain: dict[str, list[dict]] = {}
for gate_id in sorted(declared_gates):
info = declared_gates[gate_id]
domain_id = info["domain_id"]
declared = info["declared"]
registry_codes = [c for c in _strings(declared.get("review_codes"))
if _CODE_RE.match(c)]
entry: dict[str, Any] = {"gate_id": gate_id, "domain_id": domain_id,
"fired": False, "input_state": "n/a"}
rule_info = all_rules.get(gate_id)
if rule_info is None:
entry["disposition"] = "GATE_SPEC_NOT_DEPLOYED_REVIEW"
reviews.append({"review_code": "GATE_SPEC_NOT_DEPLOYED_REVIEW",
"severity": "SOFT_WARNING", "gate_id": gate_id})
gate_report.append(entry)
continue
rule = rule_info["rule"]
fired = False
if rule["condition_kind"] == "dict":
key = rule["condition_key"]
if key in implemented:
entry["disposition"] = "implemented"
value = rule["condition_value"]
if key == "any_review_code_present":
pool = code_pool.get(domain_id)
if pool is None and domain_id not in _dict(domain_envelopes):
entry["input_state"] = "input_missing"
entry["missing_policy"] = (
rule["unknown_or_missing_policy"] or default_missing_policy)
else:
entry["input_state"] = "supplied"
fired = bool(set(_strings(value)) & set(pool or []))
elif key == "required_slots_missing_or_conflicting":
entries = slot_status.get(domain_id)
if entries is None and domain_id not in _dict(domain_envelopes):
entry["input_state"] = "input_missing"
entry["missing_policy"] = (
rule["unknown_or_missing_policy"] or default_missing_policy)
else:
entry["input_state"] = "supplied"
wanted = set(_strings(value))
fired = any(
(e.get("slot_id") in wanted)
and (e.get("status") in ("missing", "conflicted"))
for e in entries or [])
elif key == "calculation_operand_state_in":
states = operand_by_domain.get(domain_id)
entry["input_state"] = ("supplied" if states else "input_missing")
fired = bool(set(_strings(value)) & set(states or []))
else:
entry["disposition"] = "GATE_CONDITION_KEY_UNSUPPORTED_REVIEW"
entry["condition_key"] = key
reviews.append({"review_code": "GATE_CONDITION_KEY_UNSUPPORTED_REVIEW",
"severity": "SOFT_WARNING", "gate_id": gate_id,
"detail": str(key)})
else:
entry["disposition"] = "GATE_PREDICATE_SPEC_UNIMPLEMENTED_REVIEW"
entry["predicate_spec"] = _text(rule["condition_value"], 200)
reviews.append({"review_code": "GATE_PREDICATE_SPEC_UNIMPLEMENTED_REVIEW",
"severity": "SOFT_WARNING", "gate_id": gate_id,
"detail": _text(rule["condition_value"], 160)})
# 차단 표지 대조 — registry ↔ 정규화된 사이드카.
declared_blocks = declared.get("blocks_final_drafting")
if (rule["blocks_final_drafting"] is not None
and declared_blocks is not None
and bool(declared_blocks) != rule["blocks_final_drafting"]):
reviews.append({"review_code": "GATE_BLOCK_FLAG_MISMATCH_REVIEW",
"severity": "SOFT_WARNING", "gate_id": gate_id})
entry["block_flag_mismatch"] = True
emitted = _dedupe_keep_order(rule["emitted_codes"] + registry_codes)
excess = [c for c in rule["emitted_codes"] if c not in registry_codes]
if excess:
entry["codes_not_in_registry"] = excess
reviews.append({"review_code": "GATE_EMITTED_CODE_NOT_IN_REGISTRY",
"severity": "SOFT_WARNING", "gate_id": gate_id,
"detail": ",".join(excess)[:160]})
entry["fired"] = fired
entry["blocks_final_drafting"] = (rule["blocks_final_drafting"]
if rule["blocks_final_drafting"] is not None
else bool(declared_blocks))
if fired:
projection_rows = [r["fact_id"] for r in rows
if domain_id in r["domain_effects"]]
entry["projected_row_count"] = len(projection_rows)
fired_by_domain.setdefault(domain_id, []).append({
"gate_id": gate_id, "codes": emitted,
"row_must_consider": rule["row_must_consider"],
"preserve_seed": rule["preserve_seed"],
"blocks_final_drafting": entry["blocks_final_drafting"],
"status_when_triggered": rule["status_when_triggered"],
"projected_rows": projection_rows,
})
gate_report.append(entry)
orphan_rules = sorted(set(all_rules) - set(declared_gates))
for gate_id in orphan_rules:
reviews.append({"review_code": "GATE_RULE_NOT_IN_REGISTRY",
"severity": "SOFT_WARNING", "gate_id": gate_id})
# ---- 행 투영 3분기 + F0-8b 스켈레톤 --------------------------------
skeleton_order = _strings(_dict(policy.get("skeleton_checks")).get("order"))
for row in rows:
codes: list[str] = []
if not _nonblank(row["derived_fact"]):
codes.append(skeleton_order[0])
if not row["evidence_refs"]:
codes.append(skeleton_order[1])
if not row["linked_structures"]["structure_ids"]:
codes.append(skeleton_order[2])
gate_codes: list[str] = []
for domain_id in sorted(row["domain_effects"]):
for firing in sorted(fired_by_domain.get(domain_id, []),
key=lambda f: f["gate_id"]):
gate_codes.extend(firing["codes"])
if firing["row_must_consider"]:
row["must_consider"] = row["must_consider"] or True
row["skeleton_validation_codes"] = _dedupe_keep_order(codes + gate_codes)
# ---- STRUCTURE_UNATTACHED_REVIEW -----------------------------------
unattached_structures = sorted(set(record_by_id) - used_structure_ids)
for structure_id in unattached_structures:
reviews.append({"review_code": "STRUCTURE_UNATTACHED_REVIEW",
"severity": "SOFT_WARNING", "detail": structure_id})
# ---- F0-9 — 예외 pack ----------------------------------------------
pack_policy = _dict(policy.get("exception_pack"))
char_limit = int(pack_policy.get("pack_item_char_limit") or 2000)
exceptions: list[dict] = []
for row in rows:
if row["legal_calculation_object"] is None:
continue
bo = next((b for b in bo_sorted
if str(b.get("BO_ID") or "") == row["source_bo_id"]), {})
core = _dict(bo.get("core_field_base"))
amount_candidates = [v for v in (bo.get("amount"), core.get("Amount"))
if _nonblank(v)]
if len(amount_candidates) >= 2:
payload = {"amount_candidates": [_text(v, 240) for v in amount_candidates],
"object_spec": row["object_spec"],
"calculation_request_ids": sorted(
str(c.get("calculation_id")) for c in
row["calculation_requests"] if c.get("calculation_id"))}
item = {"exception_id": "FX-%03d" % (len(exceptions) + 1),
"exception_type": "legal_calculation_required",
"fact_id": row["fact_id"],
"source_bo_id": row["source_bo_id"],
"conflict_flags": ["CALCULATION_BASIS_CONFLICT"],
"compact_payload": payload}
rendered = json.dumps(item, ensure_ascii=False)
if len(rendered) > char_limit:
item["oversize_review"] = True
reviews.append({"review_code": "CALCULATION_BASIS_INCOMPLETE_REVIEW",
"severity": "SOFT_WARNING",
"fact_id": row["fact_id"],
"detail": "pack item oversize %d>%d" % (len(rendered),
char_limit)})
exceptions.append(item)
elif (not _nonblank(row["amount"])) or (not _nonblank(row["object_spec"])):
reviews.append({"review_code": "CALCULATION_BASIS_INCOMPLETE_REVIEW",
"severity": "SOFT_WARNING", "fact_id": row["fact_id"],
"detail": "amount_or_object_spec_blank"})
unreachable = {name for name, meta in
_dict(policy.get("f1_exception_types")).items()
if _dict(meta).get("unreachable_in_v8")}
for item in exceptions:
if item["exception_type"] in unreachable:
raise ValueError(json.dumps({
"reason_code": "PART4_UNREACHABLE_EXCEPTION_TYPE",
"exception_type": item["exception_type"]}, ensure_ascii=False))
# ---- F0-10 — 보존 등식 계수 ----------------------------------------
effect_left = sum(len(r["domain_effects"]) for r in rows)
effect_right = 0
calc_right = 0
for bo in bo_sorted:
bo_id = str(bo.get("BO_ID") or "")
attached = [record_by_id[s] for s in by_bo_id.get(bo_id, [])
if s in record_by_id]
effect_right += len({r.get("domain_id") for r in attached
if isinstance(r.get("domain_id"), str)})
calc_right += len({ref for r in attached
for ref in _strings(r.get("calculation_request_refs"))})
calc_left = sum(len(r["calculation_requests"]) for r in rows)
sg11_total = len(sg11_records)
sg11_unused = sg11_total - len(used_sg11)
equations = {
"row": {"left": len(rows), "right": len(bo_sorted),
"holds": len(rows) == len(bo_sorted)},
"attachment_coverage": {
"attached": attach_stats["attached_bo"],
"unattached": attach_stats["unattached_bo"],
"total": len(bo_sorted),
"holds": (attach_stats["attached_bo"] + attach_stats["unattached_bo"]
== len(bo_sorted))},
"effect": {"left": effect_left, "right": effect_right,
"holds": effect_left == effect_right},
"calc_row": {"left": calc_left, "right": calc_right,
"holds": calc_left == calc_right},
"calc_source": {"attached_distinct": len(used_sg11),
"unattached_distinct": sg11_unused,
"total": sg11_total,
"holds": len(used_sg11) + sg11_unused == sg11_total},
}
# ---- 상태·집계 ------------------------------------------------------
les_review_codes = set(_strings(policy.get("les_inherited_review_codes")))
les_review_present = any(
(set(slot["review_codes"]) & les_review_codes)
for row in rows for slot in row["domain_effects"].values()) or any(
_dict(r).get("review_required") for r in structure_records)
gate_fired_any = any(f for firings in fired_by_domain.values() for f in firings)
status = ("READY_WITH_REVIEW"
if (reviews or exceptions or les_review_present or gate_fired_any)
else "READY")
counts = {
"rows": len(rows),
"bo_records": len(bo_sorted),
"structure_records": len(structure_records),
"gates_declared": len(declared_gates),
"gates_implemented": sum(1 for e in gate_report
if e.get("disposition") == "implemented"),
"gates_visualized_str": sum(
1 for e in gate_report
if e.get("disposition") == "GATE_PREDICATE_SPEC_UNIMPLEMENTED_REVIEW"),
"gates_visualized_unsupported": sum(
1 for e in gate_report
if e.get("disposition") == "GATE_CONDITION_KEY_UNSUPPORTED_REVIEW"),
"gates_fired": sum(1 for e in gate_report if e.get("fired")),
"orphan_rules": len(orphan_rules),
"review_items": len(reviews),
"exceptions": len(exceptions),
"conservation": equations,
}
bundle = {
"schema_version": "stage1_fact_ledger_candidate_bundle.v1",
"status": status,
"rows": rows,
"gate_report": {"gates": gate_report,
"fired_by_domain": {d: f for d, f in
sorted(fired_by_domain.items())},
"orphan_rule_ids": orphan_rules},
"counts": counts,
"signal_manifest_transaction_id": None,
"expected_runnable_domain_ids": expected_runnable,
"alias_resolution": {"table": alias["table"],
"self_declarations": alias["self_declarations"],
"mismatches": alias["mismatches"]},
"created_by": "fact_ledger_compiler",
}
pack = {
"schema_version": "stage1_fact_exception_pack.v1",
"has_exceptions": bool(exceptions),
"exception_count": len(exceptions),
"exceptions": exceptions,
"budget": {"pack_item_char_limit": char_limit},
}
audit["unattached_structure_ids"] = unattached_structures
audit["equations"] = equations
return {"fact_ledger_candidate_bundle": bundle,
"fact_exception_pack": pack,
"review_items": reviews,
"audit": audit}
@@ -0,0 +1,401 @@
{
"schema_version": "stage1_fact_ledger_policy.v1",
"status": "ACTIVE",
"purpose": "Part 4 Fact Ledger 의 규칙 선언 정본. 27필드 목록·순서·합성 규칙, 증거력 산정, 게이트 방언 정규화와 구현 범위, 별칭 파생, 보존 등식, 루트 래핑을 코드 밖에 둔다. 코드는 이 선언을 읽어 dispatch 만 한다. registry 가 말하지 않는 값은 만들지 않는다 — 137종 사건 이름을 어디에도 두지 않는다.",
"authority": {
"strategy": "stage_1_part_4_개정전략서.md",
"row_baseline": "선행구축/S_2_GPT_v2/fixtures/legacy_equivalence/run_{1,2}/fact_ledger/Fact_Ledger_base.json (59행 x 27키 실측)",
"gate_sidecars": "domains/<id>/gates.json x25 (조립본 바이트 동일 복사 · 127규칙 · registry 121 전건 커버)",
"case_kind_names_forbidden": true
},
"final_fields_27": [
"fact_id",
"source_bo_id",
"type",
"date",
"parties",
"object_spec",
"amount",
"action",
"evidence_refs",
"credibility",
"legal_centrality",
"proof_strength",
"legal_effect_roles",
"claim_chain_ref",
"linked_structures",
"must_not_drop_in_claim_types",
"must_consider",
"state_context",
"money_claim_effect",
"commercial_successor_effect",
"actio_roles",
"linked_actio_structures",
"downstream_module_candidates",
"skeleton_validation_codes",
"derived_fact",
"derivation_basis",
"legal_calculation_object"
],
"new_fields_2": [
"domain_effects",
"calculation_requests"
],
"new_fields_note": "29키 = 구 27 + 신규 2. 신규 2키는 required 로 올리지 않는다(D-P4-4 · Stage 2 호환). domain_effects 의 module·role_projection 은 정렬된 배열이다 — 한 도메인이 복수 module 을 선언할 수 있어 스칼라로 두면 정보가 소실된다(D-P4-e).",
"fact_id_format": "F-{idx:03d}",
"fact_id_rule": "BO_ID 사전순 1-기반 연속. 연속성은 F2 가 경성 단언한다.",
"row_cardinality": "BO 1건 = 1행",
"bo_normalization": {
"key_map": {
"BOType": "bo_type",
"Action": "action",
"BO_ID": "bo_id"
},
"domain_payload_keep_keys": [
"money_claim_details",
"possession_state",
"lien_state",
"notice_lifecycle",
"asset_alias",
"fractional_effect_events"
],
"nonblank_only": true,
"note": "구본 _compact_bo(369-374행) 등가 계층. 정규화 없이 원본 대문자 키를 소문자로 찾으면 전건 None 이 된다 — (가) 9필드 전부의 전제."
},
"field_synthesis_ga_9": {
"note": "(가) BO 유래 9필드 — 골든 59행 전건 값등가 요구. 우변을 바꾸면 정상 구현에서 G-r 이 깨진다.",
"fact_id": "F-{idx:03d} (BO_ID 사전순 1-기반)",
"source_bo_id": "BO_ID 그대로",
"type": "compact.bo_type 단독 (LES types[0] 폴백 금지)",
"date": "first_value(core_field_base.BehaviorTime, bo.date) — bo.date 는 BO 18키에 없어 항상 None. 골든은 BehaviorTime 0/59 라 전건 null",
"parties": "core_field_base 단독 — Performer -> role: actor · Subject -> role: counterparty · 값은 text(party, 160) · source: 'BO.core_field_base'. 구 signal 경로 폐지",
"amount": "text(first_value(bo.amount, core_field_base.Amount), 240) or None — 키 오름차순 JSON(ensure_ascii=False) -> 공백열 단일 공백 정규화 -> 240자 절단 -> 빈 문자열이면 None",
"action": "first_value(compact.action, core_field_base.Action) — 정규화 계층 통과 후의 소문자 키",
"evidence_refs": "BO source_evidence_indexes 단독 — LES 증거는 합집합하지 않는다(합집합하면 v.8 에서 깨진다). LES 증거는 domain_effects 쪽에만 싣는다",
"derived_fact": "action 복제 단독 — 구본 4항 서술 분기는 삭제 확정(deprecation 절)"
},
"field_synthesis_na_15": {
"note": "(나) LES 유래 15필드 — 골든 등가를 요구하지 않는다(EXPECTED_DELTA-04). v.8 LES 부착에서 결정론으로 재산출한다. 부착 0 이면 아래 기본값이 그대로 남는다.",
"object_spec": {
"rule": "core_field_base.Object 단독 · 없으면 null · 폴백 없음",
"decision": "D-P4-d=(가) — LES 폴백 대상(asset_cluster_ids)이 Part 3 v.8 에서 소멸했으므로 BO 단독 원천으로 확정한다"
},
"legal_centrality": "부착 있으면 high · 없으면 medium",
"legal_effect_roles": "부착 레코드 role_projection 정렬 합집합 · 기본 []",
"claim_chain_ref": "{claim_group_ids: [], liability_group_ids: [], signal_count: 부착 route signal 수} — 구 그룹 ID 는 v.8 에 존재하지 않으므로 상시 빈 배열",
"linked_structures": "{structure_ids: 정렬 합집합} · 기본 {structure_ids: []}",
"must_not_drop_in_claim_types": "부착 레코드 module 정렬 합집합 · 기본 []",
"must_consider": "1차 = 부착 존재 여부(bool) · 게이트 행 투영이 OR 로 갱신한다 — (나)∩(라) 겸용 필드",
"state_context": "{bo_type, domain_ids: domain_effects 키 정렬} · 기본 {bo_type, domain_ids: []}",
"money_claim_effect": "{enabled: module 'money_claim' 부착 여부, structure_ids: 해당 정렬 목록}",
"commercial_successor_effect": "{enabled: module 'commercial_successor' 부착 여부, structure_ids: 해당 정렬 목록}",
"actio_roles": "module 'actio' 부착 레코드의 role_projection 정렬 합집합 · 기본 []",
"linked_actio_structures": "module 'actio' 부착 레코드의 structure_id 정렬 목록 · 기본 []",
"downstream_module_candidates": "must_not_drop_in_claim_types 와 같은 파생(골든 59/59 동일값 실측 승계)",
"derivation_basis": "부착 있으면 'structure_ids=' + 콤마결합 · 없으면 'bo_only'",
"legal_calculation_object": "부착 type_id ∩ legal_calculation_object_types 의 사전순 최소 · 없으면 null"
},
"legal_calculation_object_types": [
"fraudulent_transfer",
"land_use_gain",
"money_claim",
"secured_debt",
"valuation"
],
"legal_calculation_object_note": "구본 677행 5-원소 집합 승계(D-P4-1 값 이관). calculation_bindings 비공백 도메인으로 대체하면 항등식으로 퇴화한다(라운드 12 실측 — 25도메인 x type_id 56종 전건).",
"evidence_scoring_da_2": {
"note": "(다) credibility·proof_strength — 골든 59행 전건 값등가 요구. 갈래 순서를 바꾸면 재현이 깨진다(문서 초안 4갈래 = 29/59 실측).",
"authority_row_keys": {
"evidence_index": "id 키 6종(evidence_index·id·evidence_id·eid·index·doc_id) 첫 적중",
"title": "first_key(title, Title, name, doc_title)",
"excerpt": "key_facts -> key_dates/key_amounts -> 대표 문자열 폴백",
"priority": "first_key(priority, Priority, weight)",
"authentication": "first_key(authentication, auth, source_authentication)",
"source": "고정 문자열 'evidence_indexed.json'"
},
"flatten_depth": 3,
"universe_missing_stub": "{evidence_index: eid, missing: true} — stub 도 비어 있지 않은 증거 행으로 계수한다(구본 1136행 승계)",
"text_rule": "행마다 json.dumps(sort_keys=True, ensure_ascii=False) -> 공백열 단일 공백 -> 300자 절단 -> ' '.join 으로 합쳐 1회 대조(합친 뒤 절단이 아니다). sort_keys 에서 title 이 최후미라 절단이 마커를 먼저 지운다 — 절단 여유를 영수증에 적는다",
"markers_documentary": [
"등기",
"계약",
"판결",
"공문",
"금융",
"송금",
"계좌",
"registry",
"contract",
"judgment"
],
"branches_in_order": [
{
"order": 1,
"condition": "증거 행 0건",
"credibility": "low",
"proof_strength": "low"
},
{
"order": 2,
"condition": "마커 10종 중 하나라도 text 에 존재",
"credibility": "high",
"proof_strength": "documentary"
},
{
"order": 3,
"condition": "증거 행 2건 이상",
"credibility": "high",
"proof_strength": "high"
},
{
"order": 4,
"condition": "text 에 meeting 또는 회의 또는 면담",
"credibility": "medium",
"proof_strength": "meeting_only_core"
},
{
"order": 5,
"condition": "그 외 전부",
"credibility": "medium",
"proof_strength": "medium"
}
]
},
"skeleton_checks": {
"order": [
"EMPTY_DERIVED_FACT",
"NO_EVIDENCE_REFS",
"NO_LINKED_LEGAL_EFFECT_STRUCTURE"
],
"note": "스켈레톤 3코드를 이 고정 순서로 먼저, 그 뒤 게이트 코드를 gate_id 오름차순으로 append, 중복은 첫 출현 위치 보존 dedupe. G-n(바이트 동일)·G-r 이 순서에 의존한다."
},
"gate_execution": {
"implemented_condition_keys": [
"any_review_code_present",
"required_slots_missing_or_conflicting",
"calculation_operand_state_in"
],
"visualized_condition_keys": {
"boundary_or_overlap_detected": "GATE_CONDITION_KEY_UNSUPPORTED_REVIEW — 정의처(domain_extension.schema · Stage2_handoff_contract)가 배포 트리에 없다(D-P4-c=(가) · source_refs 배포 선언 0회 실측)"
},
"str_condition_disposition": "GATE_PREDICATE_SPEC_UNIMPLEMENTED_REVIEW — 술어 명세를 코드로 옮기는 것은 판정식 창작이다(D-P4-b=(가))",
"orphan_rule_disposition": "GATE_RULE_NOT_IN_REGISTRY",
"unknown_or_missing_policy_default": "fail_open_to_review",
"block_semantics": "blocks_final_drafting: true 는 Stage 1 기록을 막지 않는다. 행은 기록되고 final_writer_status 가 READY_WITH_REVIEW 가 되며 차단 표지는 Stage 2 로 전달된다.",
"row_projection": {
"target_rows": "발화 게이트의 도메인 d 에 대해 R(d) = { row | d ∈ row.domain_effects 키 }",
"codes": "R(d) 전 행의 skeleton_validation_codes 에 합집합 부착(순서 규칙은 skeleton_checks)",
"must_consider": "row_effect.must_consider 보유 규칙 발화 시 R(d) 전 행 OR 갱신(기존 True 미소실)",
"preserve_seed": "on_true.preserve_seed 는 행 필드가 아니라 writer report 게이트별 발화 목록에만 기록",
"empty_target": "R(d) 공집합이면 행에 닿지 않고 writer report 에만 계수(침묵 아님)"
},
"normalization_axes_17": {
"rule_array_keys": [
"rules",
"gates"
],
"emitted_code_keys": [
"emitted_codes",
"emitted_review_codes",
"review_code"
],
"effect_containers": [
"writer_effect",
"row_effect",
"on_true"
],
"block_flag_paths": [
"writer_effect.final_drafting_block",
"writer_effect.blocks_final_drafting",
"on_true.blocks_final_drafting"
],
"status_when_triggered_paths": [
"writer_effect.status_when_triggered",
"writer_effect.status_on_fire",
"on_true.writer_status"
],
"evaluation_keys": [
"evaluation_mode",
"implementation_type",
"evaluation"
],
"row_effect_keys": [
"row_effect.must_consider",
"on_true.preserve_seed"
],
"file_status_keys": [
"closure_status",
"status"
],
"input_declaration_keys": [
"input_fields",
"input_contracts"
],
"optional_flags": [
"writes_fact_ledger",
"unknown_or_missing_policy",
"silent_repair_forbidden",
"source_membership_required"
],
"global_status_rule_note": "사이드카 global_status_rule 3방언은 채택하지 않는다 — 경고로만 기록하고 전역 규칙은 아래 global_status_rule 하나를 쓴다"
},
"emitted_code_registry_check_surface": "gates[].review_codes (게이트 단위) — 도메인 어휘 면을 쓰면 실측 초과 2건(E18-G03·E20-G03)이 사라진다"
},
"legacy_alias": {
"source": "routing/extension_payload_key_declarations.v1.json legacy_alias_targets",
"self_declaration_scan": "배포 domains/<id>/structure_types.json 전수 — 배열 키 방언(types·structure_types·entries) 정규화 · legacy_source 약칭(B1~B5)을 완전명으로 정규화 · B* 형이 아닌 값(예: 'Part3 fallback')은 선언이 아니다",
"mismatch_disposition": "누락 도메인은 해소 결과에 추가하지 않고 LEGACY_ALIAS_SELF_MISMATCH_REVIEW 로 누락 도메인 ID·영향 항목 수를 계수한다. 자산의 legacy_declaration_mismatches 는 파생 목록이므로 대조용으로만 쓰고 불일치 시 LEGACY_ALIAS_DECLARATION_INCOMPLETE_REVIEW 를 계수한다.",
"abbreviation_map": {
"B1": "B1_Money_Successor",
"B2": "B2_Secured_Registry",
"B3": "B3_Land_Valuation",
"B4": "B4_Actio",
"B5": "B5_Succession_Notice_Lien_Asset_Defense"
}
},
"p2_handoff_rules": {
"code_key": "source_review_code — issue_type 은 enum 밖 값을 뭉갠다",
"code_pattern": "^[A-Z][A-Z0-9_]{2,127}$",
"not_a_code_disposition": "P2_HANDOFF_CODE_NOT_A_REVIEW_CODE_REVIEW",
"domain_attribution": "source_domain 을 별칭 해소표로 registry 도메인 ID 로 옮긴 뒤 해당 도메인 게이트에만 공급한다(전역 병합 금지 — 교차 오발화)",
"domain_unresolved_disposition": "P2_HANDOFF_DOMAIN_UNRESOLVED_REVIEW — 어느 게이트에도 공급하지 않는다"
},
"calculation_join": {
"join_key": "signal_id (details.request_id 는 보장되지 않는 키)",
"operand_state": "Part 2 산출을 재산정 없이 승계한다(D-P4-6). deferred 는 registry 선언값이다.",
"calculation_id_missing": "signal_id 로 대체하고 CALCULATION_ID_SUBSTITUTED_REVIEW 계수 — 버리지 않는다",
"dangling_ref": "calculation_id 없는 stub 항목으로 적재(버리면 등식 ④가 정상 실행에서 발화) · CALCULATION_REF_DANGLING_REVIEW 계수",
"law_version_refs": "SG-11 레코드 law_version_refs ∪ details.law_version_refs ∪ 해당 도메인 SG-04 레코드 signal_id — 버전 확정 판단 금지(SG-04 는 참조만)"
},
"conservation_equations_5": {
"row": "len(rows) == len(BO.json) -> PART4_ROW_CONSERVATION_BROKEN",
"attachment_coverage": "부착 BO 수 + 미부착 BO 수 == len(BO.json) -> PART4_BO_ATTACHMENT_COVERAGE_BROKEN",
"effect": "Σ_bo |domain_effects 키| == Σ_bo |by_bo_id 레코드 distinct domain_id| -> PART4_EFFECT_CONSERVATION_BROKEN",
"calc_row": "Σ_bo |calculation_requests| == Σ_bo |by_bo_id 레코드 distinct calculation_request_refs| -> PART4_CALC_CONSERVATION_BROKEN",
"calc_source": "부착 distinct SG-11 + 미부착 distinct == SG-11 레코드 총수 -> PART4_CALC_SOURCE_CONSERVATION_BROKEN",
"note": "④와 ⑤는 차원이 다르다 — 컴파일러가 SG-11 signal_id 를 도메인 전 레코드에 복사하므로 행 항목 수 ≠ 원천 레코드 수. by_bo_id == {} 는 정상 입력이다(골든 경로 부착 0/59)."
},
"unattached_review": {
"bo": "FACT_ROW_UNATTACHED_REVIEW",
"structure": "STRUCTURE_UNATTACHED_REVIEW",
"note": "보존 등식은 손실을 세지 흡수를 세지 않는다 — 침묵 금지를 등식 위에 따로 건다"
},
"exception_pack": {
"surviving_type": "legal_calculation_required",
"review_code": "CALCULATION_BASIS_INCOMPLETE_REVIEW",
"trigger": "구본 866·899-925행 승계 — calculation_required AND amount 후보 2개 이상(bo.amount·core.Amount 양쪽 비공백)이면 pack 적재 + conflict_flags [CALCULATION_BASIS_CONFLICT] · elif amount 또는 object_spec 공백이면 pack 미적재 + CALCULATION_BASIS_INCOMPLETE_REVIEW 검토(if/elif 배타)",
"exception_id_format": "FX-{n:03d}",
"pack_item_char_limit": 2000,
"oversize_disposition": "자동 차단이 아니라 exception_id 유지한 채 review",
"unreachable_in_v8": {
"unresolved_asset_cluster_projection": "asset_cluster 계열이 Part 3 v.8 에서 소멸(P2·P3·LES 스키마·컴파일러 0회)",
"unresolved_structure_role_projection": "registry structure_types 73/73 이 module·role_projection 전건 선언(결손 0)"
},
"unreachable_disposition": "pack 에 실리면 PART4_UNREACHABLE_EXCEPTION_TYPE 경성 실패"
},
"adjudication": {
"accepted_status": [
"READY",
"READY_NO_EXCEPTIONS",
"READY_WITH_BLOCKS"
],
"allowed_patch_fields": [
"amount"
],
"note": "허용 필드 밖 수정은 PART4_PATCH_REJECTED. F1 의 유일 생존 부류가 계산 근거 다중성 판정이므로 패치 가능 필드는 amount 하나다."
},
"global_status_rule": "blocked item · rejected patch · deterministic review · LES 승계 검토(review_codes 2종 ∪ review_required 플래그) · gate review code 중 하나라도 있으면 READY_WITH_REVIEW, 아니면 READY (동결 계약 승계 — 사이드카 3방언 값은 경고로만 기록)",
"les_inherited_review_codes": [
"UNREGISTERED_EFFECT_TYPE_REVIEW",
"PART3_REGISTRY_SIDECAR_MISMATCH"
],
"root_wrapping_table": {
"signals/signal_manifest.json": null,
"routing/domain_activation_manifest.json": "domain_activation_manifest",
"BO.json": "(array)",
"legal_effect_structures.json": null,
"signals/domain_signals/<domain_id>.json": "domain_signal_envelope",
"quality_gates/stage1_part2_review_handoff.json": null,
"evidence_indexed.json": null,
"stage1_tmp/fact_ledger/fact_ledger_candidate_bundle.json": "fact_ledger_candidate_bundle",
"stage1_tmp/fact_ledger/fact_exception_pack.json": "fact_exception_pack",
"stage1_tmp/fact_ledger/fact_adjudication.json": "fact_exception_adjudication",
"Fact_Ledger_base.json": "(array)",
"stage1_tmp/fact_ledger/fact_ledger_writer_report.json": null,
"quality_gates/stage1_part4_review_handoff.json": "stage1_part4_review_handoff",
"validation_assets/routing/part4_receipt.json": null,
"signals/governing_law_version_signals.json": null,
"signals/timeline_notice_condition_signals.json": null,
"signals/asset_right_state_signals.json": null,
"signals/evidence_proof_conflict_signals.json": null,
"signals/calculation_requirements.json": null
},
"review_codes_soft": [
"GATE_SPEC_NOT_DEPLOYED_REVIEW",
"GATE_CONDITION_KEY_UNSUPPORTED_REVIEW",
"GATE_PREDICATE_SPEC_UNIMPLEMENTED_REVIEW",
"GATE_RULE_NOT_IN_REGISTRY",
"GATE_EMITTED_CODE_NOT_IN_REGISTRY",
"GATE_BLOCK_FLAG_MISMATCH_REVIEW",
"FACT_ROW_UNATTACHED_REVIEW",
"STRUCTURE_UNATTACHED_REVIEW",
"CALCULATION_REF_DANGLING_REVIEW",
"CALCULATION_ID_SUBSTITUTED_REVIEW",
"CALCULATION_BASIS_INCOMPLETE_REVIEW",
"LEGACY_ALIAS_SELF_MISMATCH_REVIEW",
"LEGACY_ALIAS_DECLARATION_INCOMPLETE_REVIEW",
"P2_HANDOFF_CODE_NOT_A_REVIEW_CODE_REVIEW",
"P2_HANDOFF_DOMAIN_UNRESOLVED_REVIEW",
"EVIDENCE_REF_NOT_IN_EVIDENCE_UNIVERSE",
"EMPTY_DERIVED_FACT",
"NO_EVIDENCE_REFS",
"NO_LINKED_LEGAL_EFFECT_STRUCTURE"
],
"hard_failure_codes": [
"PART4_ASSET_DEPLOYMENT_INCOMPLETE",
"MODULE_MIRROR_UNREGISTERED",
"MODULE_MIRROR_HASH_MISMATCH",
"PART4_POLICY_INVALID",
"PART4_LEDGER_SCHEMA_STALE",
"PART4_COMPILER_STALE",
"PART4_SIGNAL_SEAL_FAILED",
"PART4_REGISTRY_INDEX_SEAL_FAILED",
"PART4_LES_TRANSACTION_MISMATCH",
"PART4_ROW_CONSERVATION_BROKEN",
"PART4_BO_ATTACHMENT_COVERAGE_BROKEN",
"PART4_EFFECT_CONSERVATION_BROKEN",
"PART4_CALC_CONSERVATION_BROKEN",
"PART4_CALC_SOURCE_CONSERVATION_BROKEN",
"PART4_COVERAGE_BROKEN",
"PART4_PATCH_REJECTED",
"PART4_BO_CONSERVATION_BROKEN",
"PART4_FACT_ID_SEQUENCE_BROKEN",
"PART4_ADJUDICATION_MISSING",
"PART4_DOMAIN_EFFECT_NOT_ACTIVE",
"PART4_LEDGER_SCHEMA_INVALID",
"PART4_UNREACHABLE_EXCEPTION_TYPE",
"PART4_WRITE_REREAD_MISMATCH"
],
"deprecation": {
"old_4_fields_alias": "money_claim_effect·commercial_successor_effect·actio_roles·linked_actio_structures 는 v.8 한 버전 동안 domain_effects 로부터의 결정론 별칭이다. v.9 에서 제거 예정(D-P4-a=(가)).",
"derived_fact_4part_branch": "구본의 date·actor·obj·action 4항 서술 분기는 삭제 확정 — object_spec 참조가 끊겨 조건이 영구 거짓이 되는 사문이다.",
"old_signal_3_root_names": "actio_case_signals·case_liability_signals·legal_effect_signals 뿌리 이름을 읽지 않는다(C-7 폐쇄)."
},
"f1_exception_types": {
"legal_calculation_required": {
"unreachable_in_v8": false
},
"unresolved_asset_cluster_projection": {
"unreachable_in_v8": true
},
"unresolved_structure_role_projection": {
"unreachable_in_v8": true
}
},
"alias_module_vocabulary": {
"money_claim_effect": "money_claim",
"commercial_successor_effect": "commercial_successor",
"actio_fields": "actio",
"note": "구 4필드 별칭이 참조하는 module 어휘. 코드에 리터럴을 두지 않는다(정적 검사 11) — registry structure_types[].module 이 원천이고 이 선언은 그중 별칭 파생이 쓰는 세 값을 고정한다."
}
}