feat(stage2): implement S2_20 relief planner
- add one inline deterministic MCP task and sealed planning assets - reseal S2_00/S2_10 release hashes for the S2_20 handoff - keep live and legal admission explicitly pending
This commit is contained in:
+1
-1
@@ -192,7 +192,7 @@ Agent:
|
||||
# literal placeholders in the offline parity mirror and its unit tests.
|
||||
INLINE_USER_HASH = "{{__user_hash__}}"
|
||||
INLINE_WORKSPACE_HASH = "{{__workspace_hash__}}"
|
||||
EXPECTED_STAGE2_RELEASE_SHA256 = "0f21af3ddca538d9b6a5853dac23222c0b632d0e573de371a8de2709e691699d"
|
||||
EXPECTED_STAGE2_RELEASE_SHA256 = "70ef317cc0c557e44619b6cd6b4ad567e02e73271a36a0bbc93b748ae3f34f92"
|
||||
INLINE_REQUEST_PATH = "stage2_control/s2_00_request.json"
|
||||
INLINE_STAGE2_ASSET_ROOT = "Default_Agent/Stage_2_Clean"
|
||||
INLINE_STAGE2_RELEASE_PATH = (
|
||||
|
||||
+3975
File diff suppressed because it is too large
Load Diff
+65
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"schema_version": "stage2_corpus_build_receipt.v1",
|
||||
"receipt_id": "S2-CORPUS-BUILD-PENDING",
|
||||
"build_status": "PENDING_SOURCE_AND_OPERATOR_BUILD",
|
||||
"build_executed": false,
|
||||
"created_at": null,
|
||||
"collection_contract_status": "PENDING_CORPUS_BUILD",
|
||||
"collection_schema_ref": "corpus/weaviate_collection.schema.json",
|
||||
"collection_schema_sha256": "f29b287e99d29dbd6d3008d1ad68e3960f9cc2c72459f04316f0311cd3e9a47d",
|
||||
"collection_name": null,
|
||||
"tenant": null,
|
||||
"snapshot_id": null,
|
||||
"snapshot_sha256": null,
|
||||
"source_document_count": 0,
|
||||
"chunk_count": 0,
|
||||
"approved_case_type_ids": [],
|
||||
"components": [
|
||||
{
|
||||
"component_id": "CHUNKER",
|
||||
"status": "PENDING",
|
||||
"configuration_digest": null,
|
||||
"evidence_refs": []
|
||||
},
|
||||
{
|
||||
"component_id": "EMBEDDING",
|
||||
"status": "PENDING",
|
||||
"configuration_digest": null,
|
||||
"evidence_refs": []
|
||||
},
|
||||
{
|
||||
"component_id": "RERANKER",
|
||||
"status": "PENDING",
|
||||
"configuration_digest": null,
|
||||
"evidence_refs": []
|
||||
},
|
||||
{
|
||||
"component_id": "INDEX",
|
||||
"status": "PENDING",
|
||||
"configuration_digest": null,
|
||||
"evidence_refs": []
|
||||
},
|
||||
{
|
||||
"component_id": "CROSSWALK",
|
||||
"status": "PENDING",
|
||||
"configuration_digest": null,
|
||||
"evidence_refs": []
|
||||
}
|
||||
],
|
||||
"legal_review_status": "PENDING_KOREAN_LAWYER_REVIEW",
|
||||
"live_mcp_admission_status": "PENDING_LIVE_MCP_ADMISSION",
|
||||
"production_eligible": false,
|
||||
"unresolved_reason_codes": [
|
||||
"CORPUS_BUILD_NOT_EXECUTED",
|
||||
"LEGAL_REVIEW_NOT_COMPLETED",
|
||||
"LIVE_MCP_ADMISSION_NOT_COMPLETED",
|
||||
"RAW_CORPUS_NOT_PROVIDED"
|
||||
],
|
||||
"guards": {
|
||||
"raw_corpus_runtime_ingest_forbidden": true,
|
||||
"directory_scan_forbidden": true,
|
||||
"filter_relaxation_forbidden": true,
|
||||
"embedded_instruction_execution_forbidden": true,
|
||||
"unapproved_chunk_use_forbidden": true
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"schema_version":"stage2_corpus_chunk_manifest_header.v1","record_type":"MANIFEST_STATUS","manifest_id":"S2-CORPUS-CHUNK-MANIFEST-PENDING","status":"PENDING_SOURCE_AND_OPERATOR_BUILD","execution_eligible":false,"source_document_count":0,"chunk_count":0,"snapshot_id":null,"snapshot_sha256":null,"unresolved_reason_codes":["RAW_CORPUS_NOT_PROVIDED","HEADING_AWARE_CHUNK_BUILD_NOT_EXECUTED","CHUNK_HASHES_NOT_BOUND","CROSSWALK_NOT_BUILT","KOREAN_LAWYER_REVIEW_NOT_COMPLETED"],"guards":{"placeholder_chunk_row_forbidden":true,"unapproved_chunk_runtime_use_forbidden":true,"embedded_instruction_execution_forbidden":true}}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"schema_version": "stage2_corpus_source_manifest.v1",
|
||||
"manifest_id": "S2-CORPUS-SOURCE-MANIFEST-PENDING",
|
||||
"status": "PENDING_SOURCE_AND_LEGAL_REVIEW",
|
||||
"execution_eligible": false,
|
||||
"path_base": "corpus/raw",
|
||||
"source_document_count": 0,
|
||||
"approved_source_document_count": 0,
|
||||
"source_rows": [],
|
||||
"unresolved_reason_codes": [
|
||||
"RAW_CORPUS_NOT_PROVIDED",
|
||||
"SOURCE_HASHES_NOT_BOUND",
|
||||
"CASE_TYPE_SUB_RULE_CROSSWALK_NOT_BUILT",
|
||||
"AUTHORITY_REVIEW_NOT_COMPLETED",
|
||||
"KOREAN_LAWYER_REVIEW_NOT_COMPLETED"
|
||||
],
|
||||
"guards": {
|
||||
"placeholder_source_row_forbidden": true,
|
||||
"unapproved_source_runtime_use_forbidden": true,
|
||||
"nearest_case_type_fallback_forbidden": true,
|
||||
"raw_directory_scan_at_runtime_forbidden": true,
|
||||
"embedded_instruction_execution_forbidden": true
|
||||
}
|
||||
}
|
||||
+533
@@ -0,0 +1,533 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://schemas.liti-agent.local/stage2/corpus/weaviate_collection.schema.v1.json",
|
||||
"title": "Stage 2 requirement-fact Weaviate collection and retrieval contracts",
|
||||
"description": "Closed metadata, filter, request, response, and build-receipt contracts. It does not assert that a live collection, tenant, credential, snapshot, embedding model, or legal approval exists.",
|
||||
"schema_version": "stage2_weaviate_collection_contract_bundle.v1",
|
||||
"oneOf": [
|
||||
{"$ref": "#/$defs/collection_contract"},
|
||||
{"$ref": "#/$defs/corpus_object_metadata"},
|
||||
{"$ref": "#/$defs/hybrid_search_request"},
|
||||
{"$ref": "#/$defs/hybrid_search_response"},
|
||||
{"$ref": "#/$defs/corpus_build_receipt"}
|
||||
],
|
||||
"$defs": {
|
||||
"sha256": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-f0-9]{64}$"
|
||||
},
|
||||
"pending_or_sha256": {
|
||||
"oneOf": [
|
||||
{"$ref": "#/$defs/sha256"},
|
||||
{"const": "PENDING_SEQUENTIAL_BIND"}
|
||||
]
|
||||
},
|
||||
"nonempty_string": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"nullable_nonempty_string": {
|
||||
"oneOf": [
|
||||
{"$ref": "#/$defs/nonempty_string"},
|
||||
{"type": "null"}
|
||||
]
|
||||
},
|
||||
"path_ref": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"pattern": "^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$))(?!.*[\\x00-\\x1f]).+$"
|
||||
},
|
||||
"string_set": {
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/$defs/nonempty_string"},
|
||||
"uniqueItems": true
|
||||
},
|
||||
"nonempty_string_set": {
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/$defs/nonempty_string"},
|
||||
"minItems": 1,
|
||||
"uniqueItems": true
|
||||
},
|
||||
"date_or_null": {
|
||||
"oneOf": [
|
||||
{"type": "string", "format": "date"},
|
||||
{"type": "null"}
|
||||
]
|
||||
},
|
||||
"collection_contract": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"schema_version",
|
||||
"contract_id",
|
||||
"transport",
|
||||
"endpoint",
|
||||
"protocol_version",
|
||||
"tool_name",
|
||||
"operation_mode",
|
||||
"collection_name",
|
||||
"tenant",
|
||||
"credential_ref",
|
||||
"object_metadata_schema_ref",
|
||||
"filter_schema_ref",
|
||||
"index_configuration_digest",
|
||||
"snapshot_id",
|
||||
"snapshot_sha256",
|
||||
"status"
|
||||
],
|
||||
"properties": {
|
||||
"schema_version": {"const": "stage2_weaviate_collection_contract.v1"},
|
||||
"contract_id": {"$ref": "#/$defs/nonempty_string"},
|
||||
"transport": {"const": "MCP_OVER_HTTP_JSON_RPC"},
|
||||
"endpoint": {"const": "https://weaviate.eroomai.com/mcp"},
|
||||
"protocol_version": {"const": "2025-03-26"},
|
||||
"tool_name": {"const": "search_hybrid"},
|
||||
"operation_mode": {"const": "READ_ONLY_FILTERED_HYBRID_SEARCH"},
|
||||
"collection_name": {"$ref": "#/$defs/nullable_nonempty_string"},
|
||||
"tenant": {"$ref": "#/$defs/nullable_nonempty_string"},
|
||||
"credential_ref": {"$ref": "#/$defs/nullable_nonempty_string"},
|
||||
"object_metadata_schema_ref": {
|
||||
"const": "corpus/weaviate_collection.schema.json#/$defs/corpus_object_metadata"
|
||||
},
|
||||
"filter_schema_ref": {
|
||||
"const": "corpus/weaviate_collection.schema.json#/$defs/search_filter"
|
||||
},
|
||||
"index_configuration_digest": {
|
||||
"oneOf": [
|
||||
{"$ref": "#/$defs/sha256"},
|
||||
{"type": "null"}
|
||||
]
|
||||
},
|
||||
"snapshot_id": {"$ref": "#/$defs/nullable_nonempty_string"},
|
||||
"snapshot_sha256": {
|
||||
"oneOf": [
|
||||
{"$ref": "#/$defs/sha256"},
|
||||
{"type": "null"}
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"enum": [
|
||||
"PENDING_CORPUS_BUILD",
|
||||
"BUILT_NOT_LIVE_ADMITTED",
|
||||
"LIVE_CANARY_ADMITTED",
|
||||
"PRODUCTION_ADMITTED"
|
||||
]
|
||||
}
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"status": {"const": "PENDING_CORPUS_BUILD"}
|
||||
},
|
||||
"required": ["status"]
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"collection_name": {"type": "null"},
|
||||
"tenant": {"type": "null"},
|
||||
"credential_ref": {"type": "null"},
|
||||
"index_configuration_digest": {"type": "null"},
|
||||
"snapshot_id": {"type": "null"},
|
||||
"snapshot_sha256": {"type": "null"}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"corpus_object_metadata": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"schema_version",
|
||||
"document_id",
|
||||
"document_sha256",
|
||||
"chunk_id",
|
||||
"chunk_sha256",
|
||||
"source_path",
|
||||
"source_locator",
|
||||
"case_type_ids",
|
||||
"sub_rule_ids",
|
||||
"rule_branch_keys",
|
||||
"corpus_scope_ids",
|
||||
"element_set_ids",
|
||||
"corpus_element_ids",
|
||||
"doctrine_roles",
|
||||
"jurisdiction",
|
||||
"effective_from",
|
||||
"effective_to",
|
||||
"authority_ids",
|
||||
"proposition_ids",
|
||||
"approval_status",
|
||||
"legal_reviewer_receipt_ref",
|
||||
"legal_reviewer_receipt_sha256"
|
||||
],
|
||||
"properties": {
|
||||
"schema_version": {"const": "stage2_requirement_fact_chunk_metadata.v1"},
|
||||
"document_id": {"$ref": "#/$defs/nonempty_string"},
|
||||
"document_sha256": {"$ref": "#/$defs/sha256"},
|
||||
"chunk_id": {"$ref": "#/$defs/nonempty_string"},
|
||||
"chunk_sha256": {"$ref": "#/$defs/sha256"},
|
||||
"source_path": {"$ref": "#/$defs/path_ref"},
|
||||
"source_locator": {"$ref": "#/$defs/nonempty_string"},
|
||||
"case_type_ids": {"$ref": "#/$defs/nonempty_string_set"},
|
||||
"sub_rule_ids": {"$ref": "#/$defs/string_set"},
|
||||
"rule_branch_keys": {"$ref": "#/$defs/string_set"},
|
||||
"corpus_scope_ids": {"$ref": "#/$defs/nonempty_string_set"},
|
||||
"element_set_ids": {"$ref": "#/$defs/nonempty_string_set"},
|
||||
"corpus_element_ids": {"$ref": "#/$defs/nonempty_string_set"},
|
||||
"doctrine_roles": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"enum": ["element", "defense", "rebuttal", "burden", "relief_consistency"]
|
||||
},
|
||||
"minItems": 1,
|
||||
"uniqueItems": true
|
||||
},
|
||||
"jurisdiction": {"const": "KR"},
|
||||
"effective_from": {"$ref": "#/$defs/date_or_null"},
|
||||
"effective_to": {"$ref": "#/$defs/date_or_null"},
|
||||
"authority_ids": {"$ref": "#/$defs/string_set"},
|
||||
"proposition_ids": {"$ref": "#/$defs/string_set"},
|
||||
"approval_status": {
|
||||
"enum": ["PENDING_LEGAL_REVIEW", "APPROVED", "REJECTED", "WITHDRAWN"]
|
||||
},
|
||||
"legal_reviewer_receipt_ref": {
|
||||
"oneOf": [
|
||||
{"$ref": "#/$defs/path_ref"},
|
||||
{"type": "null"}
|
||||
]
|
||||
},
|
||||
"legal_reviewer_receipt_sha256": {
|
||||
"oneOf": [
|
||||
{"$ref": "#/$defs/sha256"},
|
||||
{"type": "null"}
|
||||
]
|
||||
}
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"if": {
|
||||
"properties": {"approval_status": {"const": "APPROVED"}},
|
||||
"required": ["approval_status"]
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"legal_reviewer_receipt_ref": {"$ref": "#/$defs/path_ref"},
|
||||
"legal_reviewer_receipt_sha256": {"$ref": "#/$defs/sha256"}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"search_filter": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"case_type_id",
|
||||
"sub_rule_id",
|
||||
"rule_branch_key",
|
||||
"corpus_scope_id",
|
||||
"element_set_id",
|
||||
"doctrine_role",
|
||||
"jurisdiction",
|
||||
"snapshot_id",
|
||||
"approval_status",
|
||||
"effective_on"
|
||||
],
|
||||
"properties": {
|
||||
"case_type_id": {"$ref": "#/$defs/nonempty_string"},
|
||||
"sub_rule_id": {"$ref": "#/$defs/nonempty_string"},
|
||||
"rule_branch_key": {"$ref": "#/$defs/nonempty_string"},
|
||||
"corpus_scope_id": {"$ref": "#/$defs/nonempty_string"},
|
||||
"element_set_id": {"$ref": "#/$defs/nonempty_string"},
|
||||
"doctrine_role": {
|
||||
"enum": ["element", "defense", "rebuttal", "burden", "relief_consistency"]
|
||||
},
|
||||
"jurisdiction": {"const": "KR"},
|
||||
"snapshot_id": {"$ref": "#/$defs/nonempty_string"},
|
||||
"approval_status": {"const": "APPROVED"},
|
||||
"effective_on": {
|
||||
"type": "string",
|
||||
"format": "date"
|
||||
}
|
||||
}
|
||||
},
|
||||
"hybrid_search_request": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"schema_version",
|
||||
"request_id",
|
||||
"query_plan_id",
|
||||
"atomic_claim_id",
|
||||
"collection_name",
|
||||
"tenant",
|
||||
"query_text",
|
||||
"query_sha256",
|
||||
"filter",
|
||||
"alpha",
|
||||
"top_k",
|
||||
"embedding_configuration_digest",
|
||||
"reranker_configuration_digest",
|
||||
"index_configuration_digest",
|
||||
"snapshot_sha256"
|
||||
],
|
||||
"properties": {
|
||||
"schema_version": {"const": "stage2_weaviate_hybrid_search_request.v1"},
|
||||
"request_id": {"$ref": "#/$defs/nonempty_string"},
|
||||
"query_plan_id": {"$ref": "#/$defs/nonempty_string"},
|
||||
"atomic_claim_id": {"$ref": "#/$defs/nonempty_string"},
|
||||
"collection_name": {"$ref": "#/$defs/nonempty_string"},
|
||||
"tenant": {"$ref": "#/$defs/nonempty_string"},
|
||||
"query_text": {"$ref": "#/$defs/nonempty_string"},
|
||||
"query_sha256": {"$ref": "#/$defs/sha256"},
|
||||
"filter": {"$ref": "#/$defs/search_filter"},
|
||||
"alpha": {"type": "number", "minimum": 0, "maximum": 1},
|
||||
"top_k": {"type": "integer", "minimum": 1, "maximum": 100},
|
||||
"embedding_configuration_digest": {"$ref": "#/$defs/sha256"},
|
||||
"reranker_configuration_digest": {"$ref": "#/$defs/sha256"},
|
||||
"index_configuration_digest": {"$ref": "#/$defs/sha256"},
|
||||
"snapshot_sha256": {"$ref": "#/$defs/sha256"}
|
||||
}
|
||||
},
|
||||
"hybrid_hit": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"chunk_id",
|
||||
"chunk_sha256",
|
||||
"source_document_id",
|
||||
"source_document_sha256",
|
||||
"source_locator",
|
||||
"pre_rerank_rank",
|
||||
"hybrid_score",
|
||||
"post_rerank_rank",
|
||||
"reranker_score",
|
||||
"metadata"
|
||||
],
|
||||
"properties": {
|
||||
"chunk_id": {"$ref": "#/$defs/nonempty_string"},
|
||||
"chunk_sha256": {"$ref": "#/$defs/sha256"},
|
||||
"source_document_id": {"$ref": "#/$defs/nonempty_string"},
|
||||
"source_document_sha256": {"$ref": "#/$defs/sha256"},
|
||||
"source_locator": {"$ref": "#/$defs/nonempty_string"},
|
||||
"pre_rerank_rank": {"type": "integer", "minimum": 1},
|
||||
"hybrid_score": {"type": "number"},
|
||||
"post_rerank_rank": {"type": "integer", "minimum": 1},
|
||||
"reranker_score": {"type": ["number", "null"]},
|
||||
"metadata": {"$ref": "#/$defs/corpus_object_metadata"}
|
||||
}
|
||||
},
|
||||
"dropped_hit": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["chunk_id", "chunk_sha256", "drop_reason_code"],
|
||||
"properties": {
|
||||
"chunk_id": {"$ref": "#/$defs/nonempty_string"},
|
||||
"chunk_sha256": {"$ref": "#/$defs/sha256"},
|
||||
"drop_reason_code": {
|
||||
"enum": [
|
||||
"FILTER_MISMATCH",
|
||||
"UNAPPROVED_CHUNK",
|
||||
"HASH_MISMATCH",
|
||||
"SNAPSHOT_MISMATCH",
|
||||
"CROSSWALK_GAP",
|
||||
"DUPLICATE_CHUNK"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"hybrid_search_response": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"schema_version",
|
||||
"request_id",
|
||||
"request_sha256",
|
||||
"response_status",
|
||||
"raw_hit_count",
|
||||
"selected_hits",
|
||||
"dropped_hits",
|
||||
"deterministic_order_digest",
|
||||
"retry_count"
|
||||
],
|
||||
"properties": {
|
||||
"schema_version": {"const": "stage2_weaviate_hybrid_search_response.v1"},
|
||||
"request_id": {"$ref": "#/$defs/nonempty_string"},
|
||||
"request_sha256": {"$ref": "#/$defs/sha256"},
|
||||
"response_status": {
|
||||
"enum": ["COMPLETE", "NO_HIT", "TECHNICAL_INCOMPLETE"]
|
||||
},
|
||||
"raw_hit_count": {"type": "integer", "minimum": 0},
|
||||
"selected_hits": {
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/$defs/hybrid_hit"}
|
||||
},
|
||||
"dropped_hits": {
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/$defs/dropped_hit"}
|
||||
},
|
||||
"deterministic_order_digest": {
|
||||
"oneOf": [
|
||||
{"$ref": "#/$defs/sha256"},
|
||||
{"type": "null"}
|
||||
]
|
||||
},
|
||||
"retry_count": {"type": "integer", "minimum": 0, "maximum": 2}
|
||||
}
|
||||
},
|
||||
"build_component_status": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["component_id", "status", "configuration_digest", "evidence_refs"],
|
||||
"properties": {
|
||||
"component_id": {
|
||||
"enum": ["CHUNKER", "EMBEDDING", "RERANKER", "INDEX", "CROSSWALK"]
|
||||
},
|
||||
"status": {
|
||||
"enum": ["PENDING", "BUILT_NOT_ADMITTED", "ADMITTED"]
|
||||
},
|
||||
"configuration_digest": {
|
||||
"oneOf": [
|
||||
{"$ref": "#/$defs/sha256"},
|
||||
{"type": "null"}
|
||||
]
|
||||
},
|
||||
"evidence_refs": {"$ref": "#/$defs/string_set"}
|
||||
}
|
||||
},
|
||||
"corpus_build_receipt": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"schema_version",
|
||||
"receipt_id",
|
||||
"build_status",
|
||||
"build_executed",
|
||||
"created_at",
|
||||
"collection_contract_status",
|
||||
"collection_schema_ref",
|
||||
"collection_schema_sha256",
|
||||
"collection_name",
|
||||
"tenant",
|
||||
"snapshot_id",
|
||||
"snapshot_sha256",
|
||||
"source_document_count",
|
||||
"chunk_count",
|
||||
"approved_case_type_ids",
|
||||
"components",
|
||||
"legal_review_status",
|
||||
"live_mcp_admission_status",
|
||||
"production_eligible",
|
||||
"unresolved_reason_codes",
|
||||
"guards"
|
||||
],
|
||||
"properties": {
|
||||
"schema_version": {"const": "stage2_corpus_build_receipt.v1"},
|
||||
"receipt_id": {"$ref": "#/$defs/nonempty_string"},
|
||||
"build_status": {
|
||||
"enum": [
|
||||
"PENDING_SOURCE_AND_OPERATOR_BUILD",
|
||||
"BUILT_NOT_LEGALLY_REVIEWED",
|
||||
"BUILT_NOT_LIVE_ADMITTED",
|
||||
"ADMITTED"
|
||||
]
|
||||
},
|
||||
"build_executed": {"type": "boolean"},
|
||||
"created_at": {
|
||||
"oneOf": [
|
||||
{"type": "string", "format": "date-time"},
|
||||
{"type": "null"}
|
||||
]
|
||||
},
|
||||
"collection_contract_status": {
|
||||
"enum": ["PENDING_CORPUS_BUILD", "BUILT_NOT_LIVE_ADMITTED", "LIVE_CANARY_ADMITTED", "PRODUCTION_ADMITTED"]
|
||||
},
|
||||
"collection_schema_ref": {
|
||||
"const": "corpus/weaviate_collection.schema.json"
|
||||
},
|
||||
"collection_schema_sha256": {"$ref": "#/$defs/pending_or_sha256"},
|
||||
"collection_name": {"$ref": "#/$defs/nullable_nonempty_string"},
|
||||
"tenant": {"$ref": "#/$defs/nullable_nonempty_string"},
|
||||
"snapshot_id": {"$ref": "#/$defs/nullable_nonempty_string"},
|
||||
"snapshot_sha256": {
|
||||
"oneOf": [
|
||||
{"$ref": "#/$defs/sha256"},
|
||||
{"type": "null"}
|
||||
]
|
||||
},
|
||||
"source_document_count": {"type": "integer", "minimum": 0},
|
||||
"chunk_count": {"type": "integer", "minimum": 0},
|
||||
"approved_case_type_ids": {"$ref": "#/$defs/string_set"},
|
||||
"components": {
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/$defs/build_component_status"},
|
||||
"minItems": 5,
|
||||
"maxItems": 5
|
||||
},
|
||||
"legal_review_status": {
|
||||
"enum": ["PENDING_KOREAN_LAWYER_REVIEW", "APPROVED", "REJECTED"]
|
||||
},
|
||||
"live_mcp_admission_status": {
|
||||
"enum": ["PENDING_LIVE_MCP_ADMISSION", "LIVE_CANARY_ADMITTED", "PRODUCTION_ADMITTED"]
|
||||
},
|
||||
"production_eligible": {"type": "boolean"},
|
||||
"unresolved_reason_codes": {"$ref": "#/$defs/string_set"},
|
||||
"guards": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"raw_corpus_runtime_ingest_forbidden",
|
||||
"directory_scan_forbidden",
|
||||
"filter_relaxation_forbidden",
|
||||
"embedded_instruction_execution_forbidden",
|
||||
"unapproved_chunk_use_forbidden"
|
||||
],
|
||||
"properties": {
|
||||
"raw_corpus_runtime_ingest_forbidden": {"const": true},
|
||||
"directory_scan_forbidden": {"const": true},
|
||||
"filter_relaxation_forbidden": {"const": true},
|
||||
"embedded_instruction_execution_forbidden": {"const": true},
|
||||
"unapproved_chunk_use_forbidden": {"const": true}
|
||||
}
|
||||
}
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"build_status": {"const": "PENDING_SOURCE_AND_OPERATOR_BUILD"}
|
||||
},
|
||||
"required": ["build_status"]
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"build_executed": {"const": false},
|
||||
"created_at": {"type": "null"},
|
||||
"collection_contract_status": {"const": "PENDING_CORPUS_BUILD"},
|
||||
"collection_name": {"type": "null"},
|
||||
"tenant": {"type": "null"},
|
||||
"snapshot_id": {"type": "null"},
|
||||
"snapshot_sha256": {"type": "null"},
|
||||
"source_document_count": {"const": 0},
|
||||
"chunk_count": {"const": 0},
|
||||
"approved_case_type_ids": {"maxItems": 0},
|
||||
"legal_review_status": {"const": "PENDING_KOREAN_LAWYER_REVIEW"},
|
||||
"live_mcp_admission_status": {"const": "PENDING_LIVE_MCP_ADMISSION"},
|
||||
"production_eligible": {"const": false}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"x-runtime-boundary": {
|
||||
"mcp_endpoint": "https://weaviate.eroomai.com/mcp",
|
||||
"mcp_protocol_version": "2025-03-26",
|
||||
"tool_allowlist": ["search_hybrid"],
|
||||
"write_operations_allowed": false,
|
||||
"filter_relaxation_allowed": false,
|
||||
"live_admission_status": "PENDING_EXTERNAL_EVIDENCE"
|
||||
}
|
||||
}
|
||||
+176
-8
@@ -1,4 +1,7 @@
|
||||
schema_version: stage2_code_executor_binding.v2
|
||||
schema_version: stage2_code_executor_binding.v3
|
||||
binding_id: S2-BINDING-DETERMINISTIC-STAGES-V3
|
||||
stage_bindings:
|
||||
- stage_id: S2_00
|
||||
binding_id: S2-BINDING-S2_00-CODE-EXECUTOR-V2
|
||||
workflow_id: S2_00
|
||||
execution_class: NON-LLM-DETERMINISTIC
|
||||
@@ -7,7 +10,7 @@ active_runtime_authority: true
|
||||
agent_script_ref:
|
||||
asset_id: AGENT-S2_00-INLINE
|
||||
path: agent_scripts/Stage_2_S2_00.yml
|
||||
sha256: 04877f5459c8a579d793ed34ac936cc366a7e5b1c77a1230c93284036196fabe
|
||||
sha256: 45ca76de0c352745f8111ed036a001d053bdc9b43c1a72292d49d9b5f3bcd2c7
|
||||
schema_id: liti_agent_yaml.v1
|
||||
binding_status: BOUND
|
||||
|
||||
@@ -21,20 +24,20 @@ workflow_contract_ref:
|
||||
inline_code_receipt_ref:
|
||||
asset_id: RECEIPT-S2_00-INLINE-CODE
|
||||
path: manifest/s2_00_inline_code_receipt.json
|
||||
sha256: a0839fae2d30091ee3a6d41b96f324a530d0dc569cd23e0b7f6ad6a1445dd130
|
||||
sha256: 1a0044a0f1e0fec13a5d4870df6147b8c4c671d843d4b634b4249442fdcfdfb8
|
||||
schema_id: stage2_s2_00_inline_code_receipt.v2
|
||||
binding_status: BOUND
|
||||
|
||||
stage2_release_ref:
|
||||
asset_id: RELEASE-STAGE2-CLEAN
|
||||
path: manifest/stage2_release.json
|
||||
sha256: 0f21af3ddca538d9b6a5853dac23222c0b632d0e573de371a8de2709e691699d
|
||||
sha256: 70ef317cc0c557e44619b6cd6b4ad567e02e73271a36a0bbc93b748ae3f34f92
|
||||
schema_id: stage2_release.v2
|
||||
binding_status: BOUND
|
||||
|
||||
expected_release_sha256: 0f21af3ddca538d9b6a5853dac23222c0b632d0e573de371a8de2709e691699d
|
||||
agent_script_sha256: 04877f5459c8a579d793ed34ac936cc366a7e5b1c77a1230c93284036196fabe
|
||||
canonical_code_sha256: 92f13cf3ac113c68e646fcf3b8d6bf9b8a7f41962eef809af91ebf133d50bc99
|
||||
expected_release_sha256: 70ef317cc0c557e44619b6cd6b4ad567e02e73271a36a0bbc93b748ae3f34f92
|
||||
agent_script_sha256: 45ca76de0c352745f8111ed036a001d053bdc9b43c1a72292d49d9b5f3bcd2c7
|
||||
canonical_code_sha256: b2c56fde707664903158f7d73073fc4a4913fb21234df455bdf5fe2bd1e5fafa
|
||||
|
||||
mcp_server_id: code-executor
|
||||
tool_name: run_code
|
||||
@@ -70,6 +73,7 @@ localdocs_contract:
|
||||
- stage2_runs/by-binding/<run_binding_digest>/<published-artifact-path>
|
||||
write_root_rule: stage2_runs/by-binding/<run_binding_digest>/
|
||||
|
||||
external_mcp_contract: null
|
||||
egress_profile_id: S2_00_LOCALDOCS_ONLY_V1
|
||||
egress_profile_status: PENDING_LIVE_VERIFICATION
|
||||
|
||||
@@ -79,9 +83,173 @@ downstream_handoff_owner:
|
||||
agent_path: agent_scripts/Stage_2_S2_10.yml
|
||||
s2_10_agent_sha256: 0eed6f354d3539a58cc3953dd3a4eb30bb14ba515e2c90380df5830d404a8513
|
||||
llm_binding_path: deployment/stage2_s2_10_llm_binding.yml
|
||||
s2_10_llm_binding_sha256: b240212634c10017fa3b4d1d71fed7ab82cdb1c2da80d92ece29a6dadfb429f1
|
||||
s2_10_llm_binding_sha256: 90f23bbe5b0c91374e550e9c841f708f6df51d2692f626e4a05367d4dffd9f95
|
||||
owner_binding_status: HASH_BOUND_LIVE_ADMISSION_PENDING
|
||||
s2_00_override_allowed: false
|
||||
|
||||
live_admission_status: PENDING_SECRET_BINDING
|
||||
legacy_fallbacks: []
|
||||
|
||||
- stage_id: S2_20
|
||||
binding_id: S2-BINDING-S2_20-CODE-EXECUTOR-V1
|
||||
workflow_id: S2_20
|
||||
execution_class: NON-LLM-DETERMINISTIC
|
||||
active_runtime_authority: false
|
||||
|
||||
agent_script_ref:
|
||||
asset_id: AGENT-S2_20-INLINE
|
||||
path: agent_scripts/Stage_2_S2_20.yml
|
||||
sha256: 4d7642bb8b63dc37fc0ca7808676c816ba43623384b0f745f9f4af17f0f8553b
|
||||
schema_id: liti_agent_yaml.v1
|
||||
binding_status: BOUND
|
||||
|
||||
workflow_contract_ref:
|
||||
asset_id: WF-S2_20
|
||||
path: workflows/S2_20_canonical_relief_plan_reduce.yml
|
||||
sha256: da56152af502fe432cb2ebaa593a4e389b7f2cd4d0b8f3be29e49717cff0544a
|
||||
schema_id: stage2_workflow_contract.v1
|
||||
binding_status: BOUND
|
||||
|
||||
inline_code_receipt_ref:
|
||||
asset_id: RECEIPT-S2_20-INLINE-CODE
|
||||
path: manifest/s2_20_inline_code_receipt.json
|
||||
sha256: 3693ce7d99d64458f2dbf80f9535aad55ad1dec22ab95898f3c05b204afb801b
|
||||
schema_id: stage2_s2_20_inline_code_receipt.v1
|
||||
binding_status: BOUND
|
||||
|
||||
stage2_release_ref:
|
||||
asset_id: RELEASE-STAGE2-CLEAN
|
||||
path: manifest/stage2_release.json
|
||||
sha256: 70ef317cc0c557e44619b6cd6b4ad567e02e73271a36a0bbc93b748ae3f34f92
|
||||
schema_id: stage2_release.v2
|
||||
binding_status: BOUND
|
||||
|
||||
expected_release_sha256: 70ef317cc0c557e44619b6cd6b4ad567e02e73271a36a0bbc93b748ae3f34f92
|
||||
agent_script_sha256: 4d7642bb8b63dc37fc0ca7808676c816ba43623384b0f745f9f4af17f0f8553b
|
||||
canonical_code_sha256: 140a83b71aa1007dd275c91a0aa8c837bd2d72a41136ae5bfa0c007da8dd701e
|
||||
|
||||
mcp_server_id: code-executor
|
||||
tool_name: run_code
|
||||
language: python
|
||||
network: agent-network
|
||||
timeout_seconds: 300
|
||||
runtime_image_digest: PENDING_SEQUENTIAL_BIND
|
||||
runtime_image_status: PENDING_BACKEND_EVIDENCE
|
||||
|
||||
dependency_lock:
|
||||
requirements: "httpx==0.28.1"
|
||||
requirements_sha256: 5fadf5f6ea5bd1b141ea05745cb52449bdccde939c22e76231e7993c2afc91d0
|
||||
lock_status: LIVE_BACKEND_PENDING
|
||||
|
||||
localdocs_contract:
|
||||
endpoint: http://mcp-localdocs:8012/mcp
|
||||
user_id_template: "{{__user_hash__}}"
|
||||
workspace_id_template: "{{__workspace_hash__}}"
|
||||
tool_allowlist:
|
||||
- read_binary_doc
|
||||
- write_binary_file
|
||||
fixed_request_path: stage2_control/s2_20_request.json
|
||||
read_path_allowlist:
|
||||
- stage2_control/s2_20_request.json
|
||||
- stage2_control/host_cas/<run_binding_digest>/S2_20.json
|
||||
- Default_Agent/Stage_2_Clean/manifest/stage2_release.json
|
||||
- Default_Agent/Stage_2_Clean/manifest/module_manifest.json
|
||||
- Default_Agent/Stage_2_Clean/manifest/s2_10_release.json
|
||||
- Default_Agent/Stage_2_Clean/manifest/authority_release.json
|
||||
- Default_Agent/Stage_2_Clean/manifest/case_type_coverage.json
|
||||
- Default_Agent/Stage_2_Clean/manifest/stage2_deterministic_admission_receipt.json
|
||||
- Default_Agent/Stage_2_Clean/manifest/case_type_registry.yml
|
||||
- Default_Agent/Stage_2_Clean/manifest/case_type_rule_registry.yml
|
||||
- Default_Agent/Stage_2_Clean/rules/relief/<case_type_id>.md
|
||||
- Default_Agent/Stage_2_Clean/legal_review/<legal_review_receipt_id>.json
|
||||
- Default_Agent/Stage_2_Clean/manifest/corpus_release.json
|
||||
- Default_Agent/Stage_2_Clean/manifest/s2_20_inline_code_receipt.json
|
||||
- Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_20.yml
|
||||
- Default_Agent/Stage_2_Clean/deployment/stage2_code_executor_binding.yml
|
||||
- Default_Agent/Stage_2_Clean/deployment/stage2_s2_10_llm_binding.yml
|
||||
- Default_Agent/Stage_2_Clean/registry/binding/binding_signature_projection.yml
|
||||
- Default_Agent/Stage_2_Clean/registry/planning/option_disposition_policy.yml
|
||||
- Default_Agent/Stage_2_Clean/registry/party/party_set_rules.yml
|
||||
- Default_Agent/Stage_2_Clean/registry/drafting/case_type_relief_rules.yml
|
||||
- Default_Agent/Stage_2_Clean/registry/drafting/forbidden_relief_rules.yml
|
||||
- Default_Agent/Stage_2_Clean/registry/drafting/counter_performance_rules.yml
|
||||
- Default_Agent/Stage_2_Clean/registry/drafting/procedural_declaration_rules.yml
|
||||
- Default_Agent/Stage_2_Clean/registry/review/review_policy_registry.yml
|
||||
- Default_Agent/Stage_2_Clean/registry/authority/authority_registry.yml
|
||||
- Default_Agent/Stage_2_Clean/registry/corpus/requirement_fact_scope.yml
|
||||
- Default_Agent/Stage_2_Clean/registry/temporal/inheritance_reserved_share.yml
|
||||
- Default_Agent/Stage_2_Clean/registry/calculations/calculation_activation_registry.yml
|
||||
- Default_Agent/Stage_2_Clean/registry/calculations/CE-01_interest_delay.yml
|
||||
- Default_Agent/Stage_2_Clean/registry/calculations/CE-02_allocation_setoff_balance.yml
|
||||
- Default_Agent/Stage_2_Clean/registry/calculations/CE-03_limitation_deadline.yml
|
||||
- Default_Agent/Stage_2_Clean/registry/calculations/CE-04_valuation.yml
|
||||
- Default_Agent/Stage_2_Clean/registry/calculations/CE-05_personal_injury.yml
|
||||
- Default_Agent/Stage_2_Clean/registry/calculations/CE-06_construction_defect.yml
|
||||
- Default_Agent/Stage_2_Clean/registry/calculations/CE-07_lease_use_gain.yml
|
||||
- Default_Agent/Stage_2_Clean/registry/calculations/CE-08_inheritance_reserved_share.yml
|
||||
- Default_Agent/Stage_2_Clean/registry/calculations/CE-09_wage_severance.yml
|
||||
- Default_Agent/Stage_2_Clean/registry/calculations/CE-10_actio_insolvency_value.yml
|
||||
- Default_Agent/Stage_2_Clean/registry/calculations/CE-11_distribution_share_division.yml
|
||||
- Default_Agent/Stage_2_Clean/registry/calculations/CE-12_insurance.yml
|
||||
- Default_Agent/Stage_2_Clean/registry/calculations/CE-13_court_value_cost.yml
|
||||
- Default_Agent/Stage_2_Clean/registry/calculations/CE-R1_ip_damage.yml
|
||||
- Default_Agent/Stage_2_Clean/registry/calculations/CE-R2_org_liquidation.yml
|
||||
- Default_Agent/Stage_2_Clean/registry/calculations/CE-R3_transport_maritime.yml
|
||||
- Default_Agent/Stage_2_Clean/registry/calculations/CE-R4_financial_instrument.yml
|
||||
- Default_Agent/Stage_2_Clean/law_values/general_law_values.yml
|
||||
- Default_Agent/Stage_2_Clean/law_values/court_fee_values.yml
|
||||
- Default_Agent/Stage_2_Clean/schemas/relief_plan.schema.json
|
||||
- Default_Agent/Stage_2_Clean/schemas/binding_retrieval.schema.json
|
||||
- Default_Agent/Stage_2_Clean/schemas/calculation.schema.json
|
||||
- Default_Agent/Stage_2_Clean/corpus/weaviate_collection.schema.json
|
||||
- Default_Agent/Stage_2_Clean/schemas/ingress.schema.json
|
||||
- Default_Agent/Stage_2_Clean/schemas/context.schema.json
|
||||
- Default_Agent/Stage_2_Clean/schemas/s2_10.schema.json
|
||||
- Default_Agent/Stage_2_Clean/schemas/review_status.schema.json
|
||||
- Default_Agent/Stage_2_Clean/schemas/deployment.schema.json
|
||||
- Default_Agent/Stage_2_Clean/routing/actio_pauliana_route_registry.yml
|
||||
- Default_Agent/Stage_2_Clean/routing/actio_pauliana_mortgage_route.yml
|
||||
- Default_Agent/Stage_2_Clean/validators/actio_value_compensation_invariants.yml
|
||||
- Default_Agent/Stage_2_Clean/renderers/R01_money_payment.yml
|
||||
- Default_Agent/Stage_2_Clean/renderers/R02_delivery_possession.yml
|
||||
- Default_Agent/Stage_2_Clean/renderers/R03_declaration_registry.yml
|
||||
- Default_Agent/Stage_2_Clean/renderers/R04_special_nonmoney_performance.yml
|
||||
- Default_Agent/Stage_2_Clean/renderers/R05_declaratory.yml
|
||||
- Default_Agent/Stage_2_Clean/renderers/R06_constitutive_judgment_challenge.yml
|
||||
- stage2_runs/by-binding/<run_binding_digest>/ingress/ingress_status.json
|
||||
- stage2_runs/by-binding/<run_binding_digest>/context/case_context.json
|
||||
- stage2_runs/by-binding/<run_binding_digest>/context/evidence_inventory.json
|
||||
- stage2_runs/by-binding/<run_binding_digest>/context/object_registry.json
|
||||
- stage2_runs/by-binding/<run_binding_digest>/context/cluster_plan.json
|
||||
- stage2_runs/by-binding/<run_binding_digest>/context/party_and_title_context.json
|
||||
- stage2_runs/by-binding/<run_binding_digest>/context/slot_crosswalk.json
|
||||
- stage2_runs/by-binding/<run_binding_digest>/context/bundle_plan.json
|
||||
- stage2_runs/by-binding/<run_binding_digest>/review/issue_ledger.base.json
|
||||
- stage2_runs/by-binding/<run_binding_digest>/context/cluster_slices/<cluster_id>.json
|
||||
- stage2_runs/by-binding/<run_binding_digest>/map_s2_10/s2_10_publish_status.json
|
||||
- stage2_runs/by-binding/<run_binding_digest>/map_s2_10/domain_verdicts/<cluster_id>.json
|
||||
- stage2_runs/by-binding/<run_binding_digest>/map_s2_10/item_receipts/<cluster_id>.json
|
||||
- stage2_runs/by-binding/<run_binding_digest>/map_s2_10/issue_patches/<cluster_id>.json
|
||||
- stage2_runs/by-binding/<run_binding_digest>/map_s2_10/assumption_parts/<cluster_id>.json
|
||||
- stage2_runs/by-binding/<run_binding_digest>/map_s2_10/usage_parts/<cluster_id>.json
|
||||
- stage2_runs/by-binding/<run_binding_digest>/map_s2_10/wave_receipts/wave-<zero-based-ordinal>.json
|
||||
write_root_rule: stage2_runs/by-binding/<run_binding_digest>/
|
||||
|
||||
external_mcp_contract:
|
||||
endpoint: https://weaviate.eroomai.com/mcp
|
||||
protocol_version: "2025-03-26"
|
||||
tool_allowlist:
|
||||
- search_hybrid
|
||||
collection: PENDING_CORPUS_RELEASE_BIND
|
||||
tenant: PENDING_CORPUS_RELEASE_BIND
|
||||
credential_ref: agentbackend://stage2/s2_20/weaviate-readonly
|
||||
operation_mode: READ_ONLY_EXACT_FILTER
|
||||
admission_status: PENDING_LIVE_VERIFICATION
|
||||
|
||||
egress_profile_id: S2_20_LOCALDOCS_WEAVIATE_READONLY_V1
|
||||
egress_profile_status: PENDING_LIVE_VERIFICATION
|
||||
downstream_handoff_owner: null
|
||||
live_admission_status: PENDING_SECRET_BINDING
|
||||
legacy_fallbacks: []
|
||||
|
||||
legacy_fallbacks: []
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ fallback_allowed: false
|
||||
superseded_by:
|
||||
asset_id: BINDING-S2_00-CODE-EXECUTOR
|
||||
path: deployment/stage2_code_executor_binding.yml
|
||||
schema_id: stage2_code_executor_binding.v2
|
||||
schema_id: stage2_code_executor_binding.v3
|
||||
binding_status: EXTERNAL_TRUST_ROOT_PENDING_LIVE_ADMISSION
|
||||
retained_legacy_refs:
|
||||
- release_ops/stage2_loader.py
|
||||
|
||||
+1
-1
@@ -51,7 +51,7 @@ prompt_contract:
|
||||
selected_context_wrapper_sha256: c43c88cad59f2e9c88d944d4997551063e133207398dee0633af53e2bc5bcab6
|
||||
cluster_payload_wrapper_sha256: aaffb47bb21a975a13075901f3152d7a41b6e1661fa1626f42e678ba8abfdda8
|
||||
projection_receipt_path: manifest/s2_10_inline_prompt_projection_receipt.json
|
||||
projection_receipt_sha256: c0e388e56f52ee4fef2b825307ef41b204a80dd740cbc8f1562b30c3cd1d3696
|
||||
projection_receipt_sha256: 4c95f3b3884900d1701f955e109f8927b9c5d818aa5c97623dba567faf7f61e9
|
||||
canonical_source_paths:
|
||||
- prompts/P00_system_and_safety_contract.md
|
||||
- prompts/P10_legal_resolution_contract.md
|
||||
|
||||
+5
-5
@@ -16,16 +16,16 @@
|
||||
},
|
||||
"registry_dependency": {
|
||||
"path": "manifest/case_type_registry.yml",
|
||||
"sha256": null,
|
||||
"implementation_status": "NOT_IMPLEMENTED",
|
||||
"catalog_exact_equality_status": "UNVERIFIED"
|
||||
"sha256": "6462217f08ced21693e8cfc812182cfc1da593d52e719e5cd11ef30409ffd00e",
|
||||
"implementation_status": "STRUCTURAL_REGISTRY_IMPLEMENTED_LEGAL_CONTENT_PENDING",
|
||||
"catalog_exact_equality_status": "OFFLINE_EXACT_137_VERIFIED"
|
||||
},
|
||||
"summary": {
|
||||
"total_case_type_rows": 137,
|
||||
"named_catalog_entries": 134,
|
||||
"generic_source_rows": 3,
|
||||
"implementation_status": "NOT_IMPLEMENTED",
|
||||
"verification_status": "UNVERIFIED"
|
||||
"implementation_status": "STRUCTURAL_REGISTRY_IMPLEMENTED_LEGAL_CONTENT_PENDING",
|
||||
"verification_status": "OFFLINE_EXACT_137_VERIFIED"
|
||||
},
|
||||
"coverage_rows": [
|
||||
{
|
||||
|
||||
+2208
File diff suppressed because it is too large
Load Diff
+1519
File diff suppressed because it is too large
Load Diff
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1 +1 @@
|
||||
{"authoring":{"path":"Stage_2_S2_00_v.2.yml","sha256":"04877f5459c8a579d793ed34ac936cc366a7e5b1c77a1230c93284036196fabe","size_bytes":367573,"unique_key_parse":"PASS"},"authoring_rewritten":false,"build_kind":"OFFLINE_AUTHORING_PROJECTION","canonical_code":{"ast_status":"PASS","code_sha256":"92f13cf3ac113c68e646fcf3b8d6bf9b8a7f41962eef809af91ebf133d50bc99","code_size_bytes":283196,"compile_status":"PASS","encoding":"UTF-8","external_python_source_ref_count":0,"external_url_count":0,"extraction_transform":"NONE","forbidden_dynamic_call_count":0,"forbidden_import_count":0,"imports":["__future__","argparse","base64","binascii","collections","contextlib","dataclasses","hashlib","httpx","io","itertools","json","math","os","pathlib","re","shutil","stat","sys","tempfile","typing","unicodedata"],"placeholder_count":0,"plaintext_secret_count":0,"yaml_pointer":"/Agent/Stages/0/tasks/0/parameters/code"},"deployment_projection":{"byte_identical_to_authoring":true,"canonical_task_semantics_sha256":"4565b8ea46aac8eab0649e4bff5c17afdc3bc0aaebe77eb6301c3bdede612d0e","path":"Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00.yml","sha256":"04877f5459c8a579d793ed34ac936cc366a7e5b1c77a1230c93284036196fabe","size_bytes":367573},"full_code_mirrors":[{"byte_identical_to_canonical_code":true,"path":"Default_Agent/Stage_2_Clean/runtime/s2_00_ingress.py","sha256":"92f13cf3ac113c68e646fcf3b8d6bf9b8a7f41962eef809af91ebf133d50bc99","size_bytes":283196},{"byte_identical_to_canonical_code":true,"path":"Default_Agent/Stage_2_Clean/runtime/s2_00_ingress.txt","sha256":"92f13cf3ac113c68e646fcf3b8d6bf9b8a7f41962eef809af91ebf133d50bc99","size_bytes":283196}],"parity_status":"PASS","schema_version":"stage2_s2_00_inline_code_receipt.v2","source_of_truth":"Stage_2_S2_00_v.2.yml","task_contract":{"agent":{"name":"Stage_2_S2_00_v2","version":"1.2.0"},"mcp_servers":{"code-executor":{"type":"streamable-http","url":"https://code-executor.mcp.eroomai.com/mcp"},"localdocs":{"type":"streamable-http","url":"http://mcp-localdocs:8012/mcp"}},"stage":{"name":"S2_00","nexts":[],"prevs":[]},"task":{"code_sha256":"92f13cf3ac113c68e646fcf3b8d6bf9b8a7f41962eef809af91ebf133d50bc99","mcp":"code-executor","parameters":{"language":"python","network":"agent-network","requirements":"httpx==0.28.1","timeout":300},"task_name":"Task_S2_00_deterministic_ingress","tool_name":"run_code"},"task_procedure":{"IN":{"nexts":["Task_S2_00_deterministic_ingress"],"wait_until":[]},"OUT":{"nexts":[],"wait_until":["Task_S2_00_deterministic_ingress"]},"Task_S2_00_deterministic_ingress":{"nexts":["OUT"],"wait_until":["IN"]}}},"workflow_id":"S2_00"}
|
||||
{"authoring":{"path":"Stage_2_S2_00_v.2.yml","sha256":"45ca76de0c352745f8111ed036a001d053bdc9b43c1a72292d49d9b5f3bcd2c7","size_bytes":367573,"unique_key_parse":"PASS"},"authoring_rewritten":false,"build_kind":"OFFLINE_AUTHORING_PROJECTION","canonical_code":{"ast_status":"PASS","code_sha256":"b2c56fde707664903158f7d73073fc4a4913fb21234df455bdf5fe2bd1e5fafa","code_size_bytes":283196,"compile_status":"PASS","encoding":"UTF-8","external_python_source_ref_count":0,"external_url_count":0,"extraction_transform":"NONE","forbidden_dynamic_call_count":0,"forbidden_import_count":0,"imports":["__future__","argparse","base64","binascii","collections","contextlib","dataclasses","hashlib","httpx","io","itertools","json","math","os","pathlib","re","shutil","stat","sys","tempfile","typing","unicodedata"],"placeholder_count":0,"plaintext_secret_count":0,"yaml_pointer":"/Agent/Stages/0/tasks/0/parameters/code"},"deployment_projection":{"byte_identical_to_authoring":true,"canonical_task_semantics_sha256":"6905affae3adfb2e9b4f28c94f039011161c79ea88eebcd70c6cb623c597c0f4","path":"Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00.yml","sha256":"45ca76de0c352745f8111ed036a001d053bdc9b43c1a72292d49d9b5f3bcd2c7","size_bytes":367573},"full_code_mirrors":[{"byte_identical_to_canonical_code":true,"path":"Default_Agent/Stage_2_Clean/runtime/s2_00_ingress.py","sha256":"b2c56fde707664903158f7d73073fc4a4913fb21234df455bdf5fe2bd1e5fafa","size_bytes":283196},{"byte_identical_to_canonical_code":true,"path":"Default_Agent/Stage_2_Clean/runtime/s2_00_ingress.txt","sha256":"b2c56fde707664903158f7d73073fc4a4913fb21234df455bdf5fe2bd1e5fafa","size_bytes":283196}],"parity_status":"PASS","schema_version":"stage2_s2_00_inline_code_receipt.v2","source_of_truth":"Stage_2_S2_00_v.2.yml","task_contract":{"agent":{"name":"Stage_2_S2_00_v2","version":"1.2.0"},"mcp_servers":{"code-executor":{"type":"streamable-http","url":"https://code-executor.mcp.eroomai.com/mcp"},"localdocs":{"type":"streamable-http","url":"http://mcp-localdocs:8012/mcp"}},"stage":{"name":"S2_00","nexts":[],"prevs":[]},"task":{"code_sha256":"b2c56fde707664903158f7d73073fc4a4913fb21234df455bdf5fe2bd1e5fafa","mcp":"code-executor","parameters":{"language":"python","network":"agent-network","requirements":"httpx==0.28.1","timeout":300},"task_name":"Task_S2_00_deterministic_ingress","tool_name":"run_code"},"task_procedure":{"IN":{"nexts":["Task_S2_00_deterministic_ingress"],"wait_until":[]},"OUT":{"nexts":[],"wait_until":["Task_S2_00_deterministic_ingress"]},"Task_S2_00_deterministic_ingress":{"nexts":["OUT"],"wait_until":["IN"]}}},"workflow_id":"S2_00"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"agent_contract":{"agent_name":"Stage_2_S2_10","agent_version":"1.1.0","deterministic_task_count":0,"inline_common_prompt_sha256":"a3f001acfed764b38e34f12d72f13cdbcab59a2e44150dd1d71c5bf8ba1f7099","inline_static_prompt_sha256":"894c30bedaa5f230aed117e81ef4cc06cac832928a1e29cd3ee8804cfd4dba1f","item_tokens":["{{item.selected_legal_context_json}}","{{item.cluster_case_payload_json}}"],"llm_task_count":1,"prompt_roles":["system","system","system","user"],"reduce_task_count":0,"stage_name":"S2_10","task_name":"Task_S2_10_resolve_cluster","tool_task_count":0},"authoring":{"path":"Stage_2_S2_10_v.1.yml","sha256":"0eed6f354d3539a58cc3953dd3a4eb30bb14ba515e2c90380df5830d404a8513","size_bytes":30164},"binding":{"path":"deployment/stage2_s2_10_llm_binding.yml","sha256":"b240212634c10017fa3b4d1d71fed7ab82cdb1c2da80d92ece29a6dadfb429f1"},"child_release":{"path":"manifest/s2_10_release.json","release_digest":"7f6a9ada26c5b11525fd45c3a955fa491cd6e1f66ae491596d1505ba6a749c53","sha256":"b2574efbc61aadf5882b273c18918d62ca3151b95d697bb35e539abd8594698c"},"deployment":{"byte_parity":"PASS","path":"agent_scripts/Stage_2_S2_10.yml","sha256":"0eed6f354d3539a58cc3953dd3a4eb30bb14ba515e2c90380df5830d404a8513","size_bytes":30164},"external_admission_status":"PENDING","inline_prompt_receipt":{"path":"manifest/s2_10_inline_prompt_projection_receipt.json","sha256":"c0e388e56f52ee4fef2b825307ef41b204a80dd740cbc8f1562b30c3cd1d3696","size_bytes":2659},"receipt_digest":"1df0b4482dc5d828593df60d58676ac2a545614b910030589d3c85517043c4ca","receipt_status":"OFFLINE_AGENT_PROJECTION_VERIFIED","runtime_admission_effect":"NONE","schema":{"path":"schemas/s2_10.schema.json","sha256":"5233afde9ddb3c5868b9101816e8b3830a90b1399e3e5e89d9d7c7bf5beacaee","size_bytes":82703},"schema_version":"stage2_s2_10_agent_receipt.v2","workflow":{"path":"workflows/S2_10_domain_relief_resolution_map.yml","sha256":"f0fba48f1760cf4e233d7d698fd19090f96ffb89cabe4b2e2cae9af7c616be0f","size_bytes":15320}}
|
||||
{"agent_contract":{"agent_name":"Stage_2_S2_10","agent_version":"1.1.0","deterministic_task_count":0,"inline_common_prompt_sha256":"a3f001acfed764b38e34f12d72f13cdbcab59a2e44150dd1d71c5bf8ba1f7099","inline_static_prompt_sha256":"894c30bedaa5f230aed117e81ef4cc06cac832928a1e29cd3ee8804cfd4dba1f","item_tokens":["{{item.selected_legal_context_json}}","{{item.cluster_case_payload_json}}"],"llm_task_count":1,"prompt_roles":["system","system","system","user"],"reduce_task_count":0,"stage_name":"S2_10","task_name":"Task_S2_10_resolve_cluster","tool_task_count":0},"authoring":{"path":"Stage_2_S2_10_v.1.yml","sha256":"0eed6f354d3539a58cc3953dd3a4eb30bb14ba515e2c90380df5830d404a8513","size_bytes":30164},"binding":{"path":"deployment/stage2_s2_10_llm_binding.yml","sha256":"90f23bbe5b0c91374e550e9c841f708f6df51d2692f626e4a05367d4dffd9f95"},"child_release":{"path":"manifest/s2_10_release.json","release_digest":"1ad0ab3edd035d7ecafa3d148e9ae5fb7980326e4291a88caffcd04099845f5c","sha256":"284315158756d0391263dc808fcc3fff468b2e721d9d83ce7897b4666571a52f"},"deployment":{"byte_parity":"PASS","path":"agent_scripts/Stage_2_S2_10.yml","sha256":"0eed6f354d3539a58cc3953dd3a4eb30bb14ba515e2c90380df5830d404a8513","size_bytes":30164},"external_admission_status":"PENDING","inline_prompt_receipt":{"path":"manifest/s2_10_inline_prompt_projection_receipt.json","sha256":"4c95f3b3884900d1701f955e109f8927b9c5d818aa5c97623dba567faf7f61e9","size_bytes":2659},"receipt_digest":"7fb1d388ccab3bc281f93850d25515aac5f655f19f6eb29998c98ad1489b12b9","receipt_status":"OFFLINE_AGENT_PROJECTION_VERIFIED","runtime_admission_effect":"NONE","schema":{"path":"schemas/s2_10.schema.json","sha256":"5233afde9ddb3c5868b9101816e8b3830a90b1399e3e5e89d9d7c7bf5beacaee","size_bytes":82703},"schema_version":"stage2_s2_10_agent_receipt.v2","workflow":{"path":"workflows/S2_10_domain_relief_resolution_map.yml","sha256":"f0fba48f1760cf4e233d7d698fd19090f96ffb89cabe4b2e2cae9af7c616be0f","size_bytes":15320}}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"authoring_agent":{"path":"Stage_2_S2_10_v.1.yml","sha256":"0eed6f354d3539a58cc3953dd3a4eb30bb14ba515e2c90380df5830d404a8513","size_bytes":30164},"builder":{"path":"offline_build/build_s2_10_agent_projection.py","sha256":"f89acf6ddb307515006ed904865c05df1d38051bacb5f13e9f928142f8d72020","size_bytes":56552},"canonicalization_algorithm_id":"S2_10-NFC-LF-RTRIM-CLAUSE-PROJECTION-V1","deployment_agent":{"byte_parity":"PASS","path":"agent_scripts/Stage_2_S2_10.yml","sha256":"0eed6f354d3539a58cc3953dd3a4eb30bb14ba515e2c90380df5830d404a8513","size_bytes":30164},"inline_scalars":[{"raw_scalar_sha256":"a3f001acfed764b38e34f12d72f13cdbcab59a2e44150dd1d71c5bf8ba1f7099","role":"system","size_bytes":7184,"wrapper":"stage_2_common_cache_prefix","yaml_pointer":"/Agent/Stages/0/map_reduce/map/tasks/0/prompts/0/content"},{"raw_scalar_sha256":"894c30bedaa5f230aed117e81ef4cc06cac832928a1e29cd3ee8804cfd4dba1f","role":"system","size_bytes":12261,"wrapper":"s2_10_legal_resolution_static_prompt","yaml_pointer":"/Agent/Stages/0/map_reduce/map/tasks/0/prompts/1/content"}],"message_order":["INLINE_COMMON_SYSTEM","INLINE_STATIC_LEGAL_SYSTEM","SELECTED_CONTEXT_SYSTEM_DATA","CLUSTER_CASE_USER_DATA"],"raw_scalar_hash_algorithm_id":"SHA256-UTF8-PARSED-YAML-SCALAR-V1","receipt_digest":"56975bd356a6a8bcf6a72148be8075991c46511bce2ac18c8a2fd4c969ad425f","receipt_status":"OFFLINE_PROJECTION_VERIFIED","runtime_admission_effect":"NONE","schema_version":"stage2_s2_10_inline_prompt_projection_receipt.v1","source_projections":[{"inline_clause_projection_sha256":"df6e88cf889c739fbfc6829b711c315e4d14ba5dd2a8bb46e1f30ef88b3a7ed5","inline_wrapper":"stage_2_common_cache_prefix","parity":"PASS","source":{"path":"prompts/P00_system_and_safety_contract.md","sha256":"df6e88cf889c739fbfc6829b711c315e4d14ba5dd2a8bb46e1f30ef88b3a7ed5","size_bytes":7124},"source_clause_projection_sha256":"df6e88cf889c739fbfc6829b711c315e4d14ba5dd2a8bb46e1f30ef88b3a7ed5"},{"hybrid_supersession_required":true,"inline_clause_projection_sha256":"cafaab3991b8bd4ef3c95e55ce4c0649794c6eed9479d46d8c7758a4b823569b","inline_wrapper":"s2_10_legal_resolution_static_prompt","parity":"PASS","source":{"path":"prompts/P10_legal_resolution_contract.md","sha256":"cafaab3991b8bd4ef3c95e55ce4c0649794c6eed9479d46d8c7758a4b823569b","size_bytes":8712},"source_clause_projection_sha256":"cafaab3991b8bd4ef3c95e55ce4c0649794c6eed9479d46d8c7758a4b823569b"}],"validation":{"dynamic_item_tokens":["{{item.selected_legal_context_json}}","{{item.cluster_case_payload_json}}"],"normalized_clause_projection":"PASS","prompt_order":"PASS","static_item_token_count":0,"static_pii_scan":"PASS","wrapper_integrity":"PASS"}}
|
||||
{"authoring_agent":{"path":"Stage_2_S2_10_v.1.yml","sha256":"0eed6f354d3539a58cc3953dd3a4eb30bb14ba515e2c90380df5830d404a8513","size_bytes":30164},"builder":{"path":"offline_build/build_s2_10_agent_projection.py","sha256":"4359364816c8a58eb414bafae8ccf24fc3766dc611d15f1aa09e48c8fedc8f88","size_bytes":61930},"canonicalization_algorithm_id":"S2_10-NFC-LF-RTRIM-CLAUSE-PROJECTION-V1","deployment_agent":{"byte_parity":"PASS","path":"agent_scripts/Stage_2_S2_10.yml","sha256":"0eed6f354d3539a58cc3953dd3a4eb30bb14ba515e2c90380df5830d404a8513","size_bytes":30164},"inline_scalars":[{"raw_scalar_sha256":"a3f001acfed764b38e34f12d72f13cdbcab59a2e44150dd1d71c5bf8ba1f7099","role":"system","size_bytes":7184,"wrapper":"stage_2_common_cache_prefix","yaml_pointer":"/Agent/Stages/0/map_reduce/map/tasks/0/prompts/0/content"},{"raw_scalar_sha256":"894c30bedaa5f230aed117e81ef4cc06cac832928a1e29cd3ee8804cfd4dba1f","role":"system","size_bytes":12261,"wrapper":"s2_10_legal_resolution_static_prompt","yaml_pointer":"/Agent/Stages/0/map_reduce/map/tasks/0/prompts/1/content"}],"message_order":["INLINE_COMMON_SYSTEM","INLINE_STATIC_LEGAL_SYSTEM","SELECTED_CONTEXT_SYSTEM_DATA","CLUSTER_CASE_USER_DATA"],"raw_scalar_hash_algorithm_id":"SHA256-UTF8-PARSED-YAML-SCALAR-V1","receipt_digest":"bfefc18c44815e75dd26a71209e74436e1ec59d858ebeaf704bc6d6e8c97eed9","receipt_status":"OFFLINE_PROJECTION_VERIFIED","runtime_admission_effect":"NONE","schema_version":"stage2_s2_10_inline_prompt_projection_receipt.v1","source_projections":[{"inline_clause_projection_sha256":"df6e88cf889c739fbfc6829b711c315e4d14ba5dd2a8bb46e1f30ef88b3a7ed5","inline_wrapper":"stage_2_common_cache_prefix","parity":"PASS","source":{"path":"prompts/P00_system_and_safety_contract.md","sha256":"df6e88cf889c739fbfc6829b711c315e4d14ba5dd2a8bb46e1f30ef88b3a7ed5","size_bytes":7124},"source_clause_projection_sha256":"df6e88cf889c739fbfc6829b711c315e4d14ba5dd2a8bb46e1f30ef88b3a7ed5"},{"hybrid_supersession_required":true,"inline_clause_projection_sha256":"cafaab3991b8bd4ef3c95e55ce4c0649794c6eed9479d46d8c7758a4b823569b","inline_wrapper":"s2_10_legal_resolution_static_prompt","parity":"PASS","source":{"path":"prompts/P10_legal_resolution_contract.md","sha256":"cafaab3991b8bd4ef3c95e55ce4c0649794c6eed9479d46d8c7758a4b823569b","size_bytes":8712},"source_clause_projection_sha256":"cafaab3991b8bd4ef3c95e55ce4c0649794c6eed9479d46d8c7758a4b823569b"}],"validation":{"dynamic_item_tokens":["{{item.selected_legal_context_json}}","{{item.cluster_case_payload_json}}"],"normalized_clause_projection":"PASS","prompt_order":"PASS","static_item_token_count":0,"static_pii_scan":"PASS","wrapper_integrity":"PASS"}}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"binding_sha256":"b240212634c10017fa3b4d1d71fed7ab82cdb1c2da80d92ece29a6dadfb429f1","finding_ids":[],"inline_prompt_projection_sha256":"c0e388e56f52ee4fef2b825307ef41b204a80dd740cbc8f1562b30c3cd1d3696","receipt_digest":"53e1a0c3ba1d944fce9e2dfedb9c99bf4d37bb826add95a040e26ce44a06e8ec","review_id":"S2_10-HYBRID-LEGAL-REVIEW-PENDING","review_scope_digest":"bc5c06f51356ed69a41cd8ffd31ed168efbcecbacd191f9841532a00920e58ef","reviewer_role":"KOREAN_ATTORNEY","s2_10_release_sha256":"b2574efbc61aadf5882b273c18918d62ca3151b95d697bb35e539abd8594698c","schema_version":"stage2_s2_10_legal_review_receipt.v2","signature_ref":null,"signed_at":null,"signed_by":null,"status":"PENDING_KOREAN_LAWYER_REVIEW"}
|
||||
{"binding_sha256":"90f23bbe5b0c91374e550e9c841f708f6df51d2692f626e4a05367d4dffd9f95","finding_ids":[],"inline_prompt_projection_sha256":"4c95f3b3884900d1701f955e109f8927b9c5d818aa5c97623dba567faf7f61e9","receipt_digest":"de3f73d055a1c26c976f746911623f81373233dd50cd5781e7fa6ce462b89b96","review_id":"S2_10-HYBRID-LEGAL-REVIEW-PENDING","review_scope_digest":"1b327aba6aa5bdfd81bde57a76bc1d8255c512c88bace0579e2ccc98c300dfc1","reviewer_role":"KOREAN_ATTORNEY","s2_10_release_sha256":"284315158756d0391263dc808fcc3fff468b2e721d9d83ce7897b4666571a52f","schema_version":"stage2_s2_10_legal_review_receipt.v2","signature_ref":null,"signed_at":null,"signed_by":null,"status":"PENDING_KOREAN_LAWYER_REVIEW"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"benchmark_id":"S2_10-HYBRID-MODEL-BENCHMARK-PENDING","binding_sha256":"b240212634c10017fa3b4d1d71fed7ab82cdb1c2da80d92ece29a6dadfb429f1","cache_telemetry_observed":null,"endpoint":"responses","latency_p95_ms":null,"model":"gpt-5.6-sol","observed_fixture_refs":[],"reasoning_effort":"xhigh","receipt_digest":"acd2043fc18695b61280b099cd388229951ccd59da6dfde024cce58fc8795fdb","s2_10_release_sha256":"b2574efbc61aadf5882b273c18918d62ca3151b95d697bb35e539abd8594698c","schema_pass_rate":null,"schema_version":"stage2_s2_10_model_benchmark_receipt.v2","signature_ref":null,"signed_at":null,"signed_by":null,"status":"PENDING_MODEL_BENCHMARK","usage_telemetry_observed":null,"verbosity":"medium"}
|
||||
{"benchmark_id":"S2_10-HYBRID-MODEL-BENCHMARK-PENDING","binding_sha256":"90f23bbe5b0c91374e550e9c841f708f6df51d2692f626e4a05367d4dffd9f95","cache_telemetry_observed":null,"endpoint":"responses","latency_p95_ms":null,"model":"gpt-5.6-sol","observed_fixture_refs":[],"reasoning_effort":"xhigh","receipt_digest":"8d93041ecebb0d68965b40ac1270ecbc94ebed6ed3f8462af52d7a781127b794","s2_10_release_sha256":"284315158756d0391263dc808fcc3fff468b2e721d9d83ce7897b4666571a52f","schema_pass_rate":null,"schema_version":"stage2_s2_10_model_benchmark_receipt.v2","signature_ref":null,"signed_at":null,"signed_by":null,"status":"PENDING_MODEL_BENCHMARK","usage_telemetry_observed":null,"verbosity":"medium"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"adapter_contract_version":"S2_10_NATIVE_RESULT_ADAPTER_V2","binding_sha256":"b240212634c10017fa3b4d1d71fed7ab82cdb1c2da80d92ece29a6dadfb429f1","capabilities":[{"activation_binding_ref":null,"capability_id":"HOST_ATOMIC_SINGLE_FLIGHT_CAS_V1","failure_reason_codes":["PENDING_EXTERNAL_PLATFORM_BINDING"],"handler_digest":null,"handler_version":null,"implementation_ref":null,"live_evidence_refs":[],"status":"PENDING_EXTERNAL_PLATFORM_BINDING"},{"activation_binding_ref":null,"capability_id":"AGENTBACKEND_MAP_SOURCE_ITEMS_V1","failure_reason_codes":["PENDING_EXTERNAL_PLATFORM_BINDING"],"handler_digest":null,"handler_version":null,"implementation_ref":null,"live_evidence_refs":[],"status":"PENDING_EXTERNAL_PLATFORM_BINDING"},{"activation_binding_ref":null,"capability_id":"AGENTBACKEND_NO_REDUCE_RESULT_ADAPTER_V1","failure_reason_codes":["PENDING_EXTERNAL_PLATFORM_BINDING"],"handler_digest":null,"handler_version":null,"implementation_ref":null,"live_evidence_refs":[],"status":"PENDING_EXTERNAL_PLATFORM_BINDING"},{"activation_binding_ref":null,"capability_id":"S2_10_STRICT_SCHEMA_AND_REF_VALIDATOR_V1","failure_reason_codes":["PENDING_EXTERNAL_PLATFORM_BINDING"],"handler_digest":null,"handler_version":null,"implementation_ref":null,"live_evidence_refs":[],"status":"PENDING_EXTERNAL_PLATFORM_BINDING"},{"activation_binding_ref":null,"capability_id":"S2_10_DETERMINISTIC_ID_AND_IMMUTABLE_PERSIST_V1","failure_reason_codes":["PENDING_EXTERNAL_PLATFORM_BINDING"],"handler_digest":null,"handler_version":null,"implementation_ref":null,"live_evidence_refs":[],"status":"PENDING_EXTERNAL_PLATFORM_BINDING"},{"activation_binding_ref":null,"capability_id":"S2_10_ITEM_RETRY_CONTROLLER_V1","failure_reason_codes":["PENDING_EXTERNAL_PLATFORM_BINDING"],"handler_digest":null,"handler_version":null,"implementation_ref":null,"live_evidence_refs":[],"status":"PENDING_EXTERNAL_PLATFORM_BINDING"}],"overall_status":"PENDING_EXTERNAL_PLATFORM_BINDING","parent_stage2_release_sha256":"0f21af3ddca538d9b6a5853dac23222c0b632d0e573de371a8de2709e691699d","receipt_digest":"b45a1cf57d428bf63159e8de481c0a313a5457dadb667769ccdf290806d41062","receipt_id":"S2_10-HYBRID-PLATFORM-ADAPTER-PENDING","s2_10_release_sha256":"b2574efbc61aadf5882b273c18918d62ca3151b95d697bb35e539abd8594698c","schema_version":"stage2_s2_10_platform_adapter_receipt.v2","signature_ref":null,"signed_at":null,"signed_by":null}
|
||||
{"adapter_contract_version":"S2_10_NATIVE_RESULT_ADAPTER_V2","binding_sha256":"90f23bbe5b0c91374e550e9c841f708f6df51d2692f626e4a05367d4dffd9f95","capabilities":[{"activation_binding_ref":null,"capability_id":"HOST_ATOMIC_SINGLE_FLIGHT_CAS_V1","failure_reason_codes":["PENDING_EXTERNAL_PLATFORM_BINDING"],"handler_digest":null,"handler_version":null,"implementation_ref":null,"live_evidence_refs":[],"status":"PENDING_EXTERNAL_PLATFORM_BINDING"},{"activation_binding_ref":null,"capability_id":"AGENTBACKEND_MAP_SOURCE_ITEMS_V1","failure_reason_codes":["PENDING_EXTERNAL_PLATFORM_BINDING"],"handler_digest":null,"handler_version":null,"implementation_ref":null,"live_evidence_refs":[],"status":"PENDING_EXTERNAL_PLATFORM_BINDING"},{"activation_binding_ref":null,"capability_id":"AGENTBACKEND_NO_REDUCE_RESULT_ADAPTER_V1","failure_reason_codes":["PENDING_EXTERNAL_PLATFORM_BINDING"],"handler_digest":null,"handler_version":null,"implementation_ref":null,"live_evidence_refs":[],"status":"PENDING_EXTERNAL_PLATFORM_BINDING"},{"activation_binding_ref":null,"capability_id":"S2_10_STRICT_SCHEMA_AND_REF_VALIDATOR_V1","failure_reason_codes":["PENDING_EXTERNAL_PLATFORM_BINDING"],"handler_digest":null,"handler_version":null,"implementation_ref":null,"live_evidence_refs":[],"status":"PENDING_EXTERNAL_PLATFORM_BINDING"},{"activation_binding_ref":null,"capability_id":"S2_10_DETERMINISTIC_ID_AND_IMMUTABLE_PERSIST_V1","failure_reason_codes":["PENDING_EXTERNAL_PLATFORM_BINDING"],"handler_digest":null,"handler_version":null,"implementation_ref":null,"live_evidence_refs":[],"status":"PENDING_EXTERNAL_PLATFORM_BINDING"},{"activation_binding_ref":null,"capability_id":"S2_10_ITEM_RETRY_CONTROLLER_V1","failure_reason_codes":["PENDING_EXTERNAL_PLATFORM_BINDING"],"handler_digest":null,"handler_version":null,"implementation_ref":null,"live_evidence_refs":[],"status":"PENDING_EXTERNAL_PLATFORM_BINDING"}],"overall_status":"PENDING_EXTERNAL_PLATFORM_BINDING","parent_stage2_release_sha256":"70ef317cc0c557e44619b6cd6b4ad567e02e73271a36a0bbc93b748ae3f34f92","receipt_digest":"d11458da3cd0d5d85f202ec1ee275b95b8dbe34c588e7580bd21fef211fbd050","receipt_id":"S2_10-HYBRID-PLATFORM-ADAPTER-PENDING","s2_10_release_sha256":"284315158756d0391263dc808fcc3fff468b2e721d9d83ce7897b4666571a52f","schema_version":"stage2_s2_10_platform_adapter_receipt.v2","signature_ref":null,"signed_at":null,"signed_by":null}
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+1
@@ -0,0 +1 @@
|
||||
{"authoring":{"path":"Stage_2_S2_20.yml","sha256":"4d7642bb8b63dc37fc0ca7808676c816ba43623384b0f745f9f4af17f0f8553b","size_bytes":254364},"canonical_code":{"ast_status":"PASS","code_sha256":"140a83b71aa1007dd275c91a0aa8c837bd2d72a41136ae5bfa0c007da8dd701e","code_size_bytes":199933,"compile_status":"PASS","encoding":"UTF-8","endpoint_literals":["http://mcp-localdocs:8012/mcp","https://weaviate.eroomai.com/mcp"],"expected_parent_stage2_release_sha256":"70ef317cc0c557e44619b6cd6b4ad567e02e73271a36a0bbc93b748ae3f34f92","external_python_source_ref_count":0,"extraction_transform":"NONE","forbidden_dynamic_call_count":0,"forbidden_import_count":0,"imports":["__future__","base64","binascii","concurrent","contextlib","datetime","decimal","hashlib","httpx","io","itertools","json","pathlib","re","sys","typing","unicodedata"],"plaintext_secret_count":0,"yaml_pointer":"/Agent/Stages/0/tasks/0/parameters/code"},"deployment_projection":{"path":"Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_20.yml","sha256":"4d7642bb8b63dc37fc0ca7808676c816ba43623384b0f745f9f4af17f0f8553b","size_bytes":254364},"full_code_mirrors":["Default_Agent/Stage_2_Clean/runtime/s2_20_reduce.py","Default_Agent/Stage_2_Clean/runtime/s2_20_reduce.txt"],"parity_status":"PASS","schema_version":"stage2_s2_20_inline_code_receipt.v1","task_contract":{"agent":{"name":"Stage_2_S2_20","version":"1.0.0"},"authoring_rewritten":false,"canonical_task_semantics_sha256":"33367b7091a5d1bebe33c1159f00fa46f8766cee52106491d7d6c7e2c9d472b6","code_mirrors_byte_identical":true,"exactly_one_code_executor_run_code":true,"exactly_one_stage":true,"exactly_one_task":true,"expected_parent_stage2_release_sha256":"70ef317cc0c557e44619b6cd6b4ad567e02e73271a36a0bbc93b748ae3f34f92","internal_dag":"(C20||C21)->C25->C26->(C22||(C27->C28->C29))->C30->C35","mcp_servers":{"code-executor":{"type":"streamable-http","url":"https://code-executor.mcp.eroomai.com/mcp"},"localdocs":{"type":"streamable-http","url":"http://mcp-localdocs:8012/mcp"}},"projection_byte_identical_to_authoring":true,"stage":{"name":"S2_20","nexts":[],"prevs":[],"skip_confirm":true},"task":{"code_sha256":"140a83b71aa1007dd275c91a0aa8c837bd2d72a41136ae5bfa0c007da8dd701e","mcp":"code-executor","parameters":{"language":"python","network":"agent-network","requirements":"httpx==0.28.1","timeout":300},"task_name":"Task_S2_20_deterministic_relief_plan","tool_name":"run_code"},"task_procedure":{"IN":{"nexts":["Task_S2_20_deterministic_relief_plan"],"wait_until":[]},"OUT":{"nexts":[],"wait_until":["Task_S2_20_deterministic_relief_plan"]},"Task_S2_20_deterministic_relief_plan":{"nexts":["OUT"],"wait_until":["IN"]}}},"workflow_id":"S2_20"}
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
[
|
||||
"corpus/build_receipt.json",
|
||||
"corpus/chunk_manifest.jsonl",
|
||||
"corpus/source_manifest.json",
|
||||
"corpus/weaviate_collection.schema.json",
|
||||
"law_values/court_fee_values.yml",
|
||||
"law_values/general_law_values.yml",
|
||||
"manifest/authority_release.json",
|
||||
"manifest/case_type_coverage.json",
|
||||
"manifest/case_type_registry.yml",
|
||||
"manifest/case_type_rule_registry.yml",
|
||||
"manifest/corpus_release.json",
|
||||
"manifest/s2_20_parent_member_paths.json",
|
||||
"migration/actio_assets_receipt.json",
|
||||
"migration/legacy_asset_disposition.yml",
|
||||
"offline_build/build_authority_law_value_release.py",
|
||||
"offline_build/build_authority_law_value_release.txt",
|
||||
"offline_build/build_corpus_snapshot.py",
|
||||
"offline_build/build_corpus_snapshot.txt",
|
||||
"offline_build/build_release_manifests.py",
|
||||
"offline_build/build_release_manifests.txt",
|
||||
"offline_build/build_s2_20_inline_projection.py",
|
||||
"offline_build/build_s2_20_inline_projection.txt",
|
||||
"offline_build/compile_case_type_registry.py",
|
||||
"offline_build/compile_case_type_registry.txt",
|
||||
"offline_build/compile_relief_rule_projection.py",
|
||||
"offline_build/compile_relief_rule_projection.txt",
|
||||
"registry/binding/binding_signature_projection.yml",
|
||||
"registry/calculations/CE-01_interest_delay.yml",
|
||||
"registry/calculations/CE-02_allocation_setoff_balance.yml",
|
||||
"registry/calculations/CE-03_limitation_deadline.yml",
|
||||
"registry/calculations/CE-04_valuation.yml",
|
||||
"registry/calculations/CE-05_personal_injury.yml",
|
||||
"registry/calculations/CE-06_construction_defect.yml",
|
||||
"registry/calculations/CE-07_lease_use_gain.yml",
|
||||
"registry/calculations/CE-08_inheritance_reserved_share.yml",
|
||||
"registry/calculations/CE-09_wage_severance.yml",
|
||||
"registry/calculations/CE-10_actio_insolvency_value.yml",
|
||||
"registry/calculations/CE-11_distribution_share_division.yml",
|
||||
"registry/calculations/CE-12_insurance.yml",
|
||||
"registry/calculations/CE-13_court_value_cost.yml",
|
||||
"registry/calculations/CE-R1_ip_damage.yml",
|
||||
"registry/calculations/CE-R2_org_liquidation.yml",
|
||||
"registry/calculations/CE-R3_transport_maritime.yml",
|
||||
"registry/calculations/CE-R4_financial_instrument.yml",
|
||||
"registry/calculations/calculation_activation_registry.yml",
|
||||
"registry/corpus/requirement_fact_scope.yml",
|
||||
"registry/drafting/case_type_relief_rules.yml",
|
||||
"registry/drafting/counter_performance_rules.yml",
|
||||
"registry/drafting/forbidden_relief_rules.yml",
|
||||
"registry/drafting/procedural_declaration_rules.yml",
|
||||
"registry/party/party_set_rules.yml",
|
||||
"registry/planning/option_disposition_policy.yml",
|
||||
"registry/regression/finding_fixture_map.yml",
|
||||
"registry/review/review_policy_registry.yml",
|
||||
"registry/temporal/inheritance_reserved_share.yml",
|
||||
"release_ops/canary_rollback.py",
|
||||
"release_ops/canary_rollback.txt",
|
||||
"release_ops/release_validator.py",
|
||||
"release_ops/release_validator.txt",
|
||||
"renderers/R01_money_payment.yml",
|
||||
"renderers/R02_delivery_possession.yml",
|
||||
"renderers/R03_declaration_registry.yml",
|
||||
"renderers/R04_special_nonmoney_performance.yml",
|
||||
"renderers/R05_declaratory.yml",
|
||||
"renderers/R06_constitutive_judgment_challenge.yml",
|
||||
"routing/actio_pauliana_mortgage_route.yml",
|
||||
"routing/actio_pauliana_route_registry.yml",
|
||||
"runtime/authority/authority_preflight.py",
|
||||
"runtime/authority/authority_preflight.txt",
|
||||
"runtime/binding_signature_projection.py",
|
||||
"runtime/binding_signature_projection.txt",
|
||||
"runtime/c25_case_type_bind.py",
|
||||
"runtime/c25_case_type_bind.txt",
|
||||
"runtime/c26_rule_branch_bind.py",
|
||||
"runtime/c26_rule_branch_bind.txt",
|
||||
"runtime/c27_query_plan.py",
|
||||
"runtime/c27_query_plan.txt",
|
||||
"runtime/c28_weaviate_retrieve.py",
|
||||
"runtime/c28_weaviate_retrieve.txt",
|
||||
"runtime/c29_pack_verify.py",
|
||||
"runtime/c29_pack_verify.txt",
|
||||
"runtime/calculators/registry_engine.py",
|
||||
"runtime/calculators/registry_engine.txt",
|
||||
"schemas/binding_retrieval.schema.json",
|
||||
"schemas/calculation.schema.json",
|
||||
"schemas/deployment.schema.json",
|
||||
"schemas/domain_verdict.schema.json",
|
||||
"schemas/relief_plan.schema.json",
|
||||
"tests/fixtures/s2_20/actio_route_cap_matrix.json",
|
||||
"tests/fixtures/s2_20/calculation_temporal_boundaries.json",
|
||||
"tests/fixtures/s2_20/case_type_rule_binding_matrix.json",
|
||||
"tests/fixtures/s2_20/case_type_zero_multi_hash_mutations.json",
|
||||
"tests/fixtures/s2_20/minimal_supported_portfolio.json",
|
||||
"tests/fixtures/s2_20/mixed_option_dispositions.json",
|
||||
"tests/fixtures/s2_20/plan_publish_barrier_failure.json",
|
||||
"tests/fixtures/s2_20/regression_manifest.json",
|
||||
"tests/fixtures/s2_20/retrieval_replay_injection.json",
|
||||
"tests/s2_20/test_agent_and_inline_parity.py",
|
||||
"tests/s2_20/test_agent_and_inline_parity.txt",
|
||||
"tests/s2_20/test_calculation_and_reports.py",
|
||||
"tests/s2_20/test_calculation_and_reports.txt",
|
||||
"tests/s2_20/test_inline_output_schema.py",
|
||||
"tests/s2_20/test_inline_output_schema.txt",
|
||||
"tests/s2_20/test_offline_compilers.py",
|
||||
"tests/s2_20/test_offline_compilers.txt",
|
||||
"tests/s2_20/test_option_group_and_binding.py",
|
||||
"tests/s2_20/test_option_group_and_binding.txt",
|
||||
"tests/s2_20/test_plan_publish_and_release.py",
|
||||
"tests/s2_20/test_plan_publish_and_release.txt",
|
||||
"tests/s2_20/test_regression_manifest_closure.py",
|
||||
"tests/s2_20/test_regression_manifest_closure.txt",
|
||||
"tests/s2_20/test_retrieval_and_pack.py",
|
||||
"tests/s2_20/test_retrieval_and_pack.txt",
|
||||
"validators/actio_value_compensation_invariants.yml",
|
||||
"workflows/S2_20_canonical_relief_plan_reduce.yml"
|
||||
]
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"admission_status":"PENDING","backend_capability_receipt_sha256":"PENDING_SEQUENTIAL_BIND","executor_binding_sha256":"d1e16314e81faea5e6a8dd826e81b9ae01e0536923480ba4fb710a4959385a7c","parent_release_sha256":"70ef317cc0c557e44619b6cd6b4ad567e02e73271a36a0bbc93b748ae3f34f92","present_deterministic_stage_ids":["S2_00","S2_20"],"schema_version":"stage2_deterministic_admission_receipt.v1","signature":"PENDING_EXTERNAL_SIGNATURE","signature_verification_status":"PENDING_EXTERNAL_SIGNATURE","signed_payload_sha256":"PENDING_SEQUENTIAL_BIND","stage_receipts":[{"asset_id":"RECEIPT-S2_00-INLINE-CODE","binding_status":"BOUND","path":"manifest/s2_00_inline_code_receipt.json","schema_id":"stage2_s2_00_inline_code_receipt.v2","sha256":"1a0044a0f1e0fec13a5d4870df6147b8c4c671d843d4b634b4249442fdcfdfb8"},{"asset_id":"RECEIPT-S2_20-INLINE-CODE","binding_status":"BOUND","path":"manifest/s2_20_inline_code_receipt.json","schema_id":"stage2_s2_20_inline_code_receipt.v1","sha256":"3693ce7d99d64458f2dbf80f9535aad55ad1dec22ab95898f3c05b204afb801b"}]}
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+186
@@ -0,0 +1,186 @@
|
||||
{
|
||||
"schema_version": "stage2_actio_migration_receipt.v1",
|
||||
"receipt_id": "S2-ACTIO-MIGRATION-PENDING",
|
||||
"status": "PENDING_KOREAN_LAWYER_AND_AUTHORITY_REVIEW",
|
||||
"execution_eligible": false,
|
||||
"path_base": "Default_Agent/Stage_2_Clean",
|
||||
"source_root_ref": "../../사해행위취소소송관련기존자산",
|
||||
"expected_source_count": 10,
|
||||
"observed_source_count": 10,
|
||||
"completed_migration_count": 0,
|
||||
"rows": [
|
||||
{
|
||||
"source_asset_id": "ACTIO-MIGRATION-SOURCE-01",
|
||||
"raw_path": "../../사해행위취소소송관련기존자산/actio_pauliana_calc_v1.txt",
|
||||
"raw_basename_utf8_hex": "616374696f5f7061756c69616e615f63616c635f76312e747874",
|
||||
"nfc_display_name": "actio_pauliana_calc_v1.txt",
|
||||
"raw_sha256": "57dd4a10a982c4b344239f37dcdeab9a388f2dd77ba41bdf89698d2bf8d5ba27",
|
||||
"byte_length": 22913,
|
||||
"extracted_propositions": [],
|
||||
"official_authority_checks": [],
|
||||
"migration_disposition": null,
|
||||
"correction_or_drop_reason": "PENDING_PROPOSITION_EXTRACTION_AND_OFFICIAL_AUTHORITY_COMPARISON",
|
||||
"canonical_targets": [],
|
||||
"canonical_target_sha256s": [],
|
||||
"reviewer_id": null,
|
||||
"review_status": "PENDING_KOREAN_LAWYER_REVIEW"
|
||||
},
|
||||
{
|
||||
"source_asset_id": "ACTIO-MIGRATION-SOURCE-02",
|
||||
"raw_path": "../../사해행위취소소송관련기존자산/actio_pauliana_calc_v3_mini_v1.json",
|
||||
"raw_basename_utf8_hex": "616374696f5f7061756c69616e615f63616c635f76335f6d696e695f76312e6a736f6e",
|
||||
"nfc_display_name": "actio_pauliana_calc_v3_mini_v1.json",
|
||||
"raw_sha256": "551e0965de9061181beb9a019188fe54ced2b6ef74f979e2cd79eb4edadf1f09",
|
||||
"byte_length": 11803,
|
||||
"extracted_propositions": [],
|
||||
"official_authority_checks": [],
|
||||
"migration_disposition": null,
|
||||
"correction_or_drop_reason": "PENDING_PROPOSITION_EXTRACTION_AND_OFFICIAL_AUTHORITY_COMPARISON",
|
||||
"canonical_targets": [],
|
||||
"canonical_target_sha256s": [],
|
||||
"reviewer_id": null,
|
||||
"review_status": "PENDING_KOREAN_LAWYER_REVIEW"
|
||||
},
|
||||
{
|
||||
"source_asset_id": "ACTIO-MIGRATION-SOURCE-03",
|
||||
"raw_path": "../../사해행위취소소송관련기존자산/actio_pauliana_case_type_determination.csv",
|
||||
"raw_basename_utf8_hex": "616374696f5f7061756c69616e615f636173655f747970655f64657465726d696e6174696f6e2e637376",
|
||||
"nfc_display_name": "actio_pauliana_case_type_determination.csv",
|
||||
"raw_sha256": "fed50482bff95e8644c912d8e03ccbfe1ea74844a15cf25826fa2d8a681e3786",
|
||||
"byte_length": 1605,
|
||||
"extracted_propositions": [],
|
||||
"official_authority_checks": [],
|
||||
"migration_disposition": null,
|
||||
"correction_or_drop_reason": "PENDING_PROPOSITION_EXTRACTION_AND_OFFICIAL_AUTHORITY_COMPARISON",
|
||||
"canonical_targets": [],
|
||||
"canonical_target_sha256s": [],
|
||||
"reviewer_id": null,
|
||||
"review_status": "PENDING_KOREAN_LAWYER_REVIEW"
|
||||
},
|
||||
{
|
||||
"source_asset_id": "ACTIO-MIGRATION-SOURCE-04",
|
||||
"raw_path": "../../사해행위취소소송관련기존자산/actio_pauliana_mortgage_actio_calc_both_v1.txt",
|
||||
"raw_basename_utf8_hex": "616374696f5f7061756c69616e615f6d6f7274676167655f616374696f5f63616c635f626f74685f76312e747874",
|
||||
"nfc_display_name": "actio_pauliana_mortgage_actio_calc_both_v1.txt",
|
||||
"raw_sha256": "40405566257145b34c3e539861cf472aa5dc3c73f4f983136da58605eb352e83",
|
||||
"byte_length": 11138,
|
||||
"extracted_propositions": [],
|
||||
"official_authority_checks": [],
|
||||
"migration_disposition": null,
|
||||
"correction_or_drop_reason": "PENDING_PROPOSITION_EXTRACTION_AND_OFFICIAL_AUTHORITY_COMPARISON",
|
||||
"canonical_targets": [],
|
||||
"canonical_target_sha256s": [],
|
||||
"reviewer_id": null,
|
||||
"review_status": "PENDING_KOREAN_LAWYER_REVIEW"
|
||||
},
|
||||
{
|
||||
"source_asset_id": "ACTIO-MIGRATION-SOURCE-05",
|
||||
"raw_path": "../../사해행위취소소송관련기존자산/actio_pauliana_mortgage_v1.txt",
|
||||
"raw_basename_utf8_hex": "616374696f5f7061756c69616e615f6d6f7274676167655f76312e747874",
|
||||
"nfc_display_name": "actio_pauliana_mortgage_v1.txt",
|
||||
"raw_sha256": "91b8949dfed4bbe8a9553eef0775a39d1967e0c86432e6f8c13f6e8381befdb4",
|
||||
"byte_length": 21449,
|
||||
"extracted_propositions": [],
|
||||
"official_authority_checks": [],
|
||||
"migration_disposition": null,
|
||||
"correction_or_drop_reason": "PENDING_PROPOSITION_EXTRACTION_AND_OFFICIAL_AUTHORITY_COMPARISON",
|
||||
"canonical_targets": [],
|
||||
"canonical_target_sha256s": [],
|
||||
"reviewer_id": null,
|
||||
"review_status": "PENDING_KOREAN_LAWYER_REVIEW"
|
||||
},
|
||||
{
|
||||
"source_asset_id": "ACTIO-MIGRATION-SOURCE-06",
|
||||
"raw_path": "../../사해행위취소소송관련기존자산/mortgage_fraudulent_act_module_v1_mini_v1.json",
|
||||
"raw_basename_utf8_hex": "6d6f7274676167655f6672617564756c656e745f6163745f6d6f64756c655f76315f6d696e695f76312e6a736f6e",
|
||||
"nfc_display_name": "mortgage_fraudulent_act_module_v1_mini_v1.json",
|
||||
"raw_sha256": "ee5a29a871a82ec09ddf4e7cf5ea31be0e09314a178ba4de716bef427a0c218e",
|
||||
"byte_length": 12141,
|
||||
"extracted_propositions": [],
|
||||
"official_authority_checks": [],
|
||||
"migration_disposition": null,
|
||||
"correction_or_drop_reason": "PENDING_PROPOSITION_EXTRACTION_AND_OFFICIAL_AUTHORITY_COMPARISON",
|
||||
"canonical_targets": [],
|
||||
"canonical_target_sha256s": [],
|
||||
"reviewer_id": null,
|
||||
"review_status": "PENDING_KOREAN_LAWYER_REVIEW"
|
||||
},
|
||||
{
|
||||
"source_asset_id": "ACTIO-MIGRATION-SOURCE-07",
|
||||
"raw_path": "../../사해행위취소소송관련기존자산/부담부_부동산소유권이전_사해행위_가액배상_모듈.md",
|
||||
"raw_basename_utf8_hex": "e18487e185aee18483e185a1e186b7e18487e185ae5fe18487e185aee18483e185a9e186bce18489e185a1e186abe18489e185a9e1848be185b2e18480e185afe186abe1848be185b5e1848ce185a5e186ab5fe18489e185a1e18492e185a2e18492e185a2e186bce1848be185b15fe18480e185a1e1848be185a2e186a8e18487e185a2e18489e185a1e186bc5fe18486e185a9e18483e185b2e186af2e6d64",
|
||||
"nfc_display_name": "부담부_부동산소유권이전_사해행위_가액배상_모듈.md",
|
||||
"raw_sha256": "424b3d34b30e4c2822d79ab7ec6a3b0645213a743141b72da021365c3f8553ac",
|
||||
"byte_length": 15743,
|
||||
"extracted_propositions": [],
|
||||
"official_authority_checks": [],
|
||||
"migration_disposition": null,
|
||||
"correction_or_drop_reason": "PENDING_PROPOSITION_EXTRACTION_AND_OFFICIAL_AUTHORITY_COMPARISON",
|
||||
"canonical_targets": [],
|
||||
"canonical_target_sha256s": [],
|
||||
"reviewer_id": null,
|
||||
"review_status": "PENDING_KOREAN_LAWYER_REVIEW"
|
||||
},
|
||||
{
|
||||
"source_asset_id": "ACTIO-MIGRATION-SOURCE-08",
|
||||
"raw_path": "../../사해행위취소소송관련기존자산/사해행위취소_가액배상_최종화게이트_모듈.md",
|
||||
"raw_basename_utf8_hex": "e18489e185a1e18492e185a2e18492e185a2e186bce1848be185b1e1848ee185b1e18489e185a95fe18480e185a1e1848be185a2e186a8e18487e185a2e18489e185a1e186bc5fe1848ee185ace1848ce185a9e186bce18492e185aae18480e185a6e1848be185b5e18490e185b35fe18486e185a9e18483e185b2e186af2e6d64",
|
||||
"nfc_display_name": "사해행위취소_가액배상_최종화게이트_모듈.md",
|
||||
"raw_sha256": "794e7133e3b06d9e38314cc06aad5320faf5269870424e27fcdb2db1dff58478",
|
||||
"byte_length": 15011,
|
||||
"extracted_propositions": [],
|
||||
"official_authority_checks": [],
|
||||
"migration_disposition": null,
|
||||
"correction_or_drop_reason": "PENDING_PROPOSITION_EXTRACTION_AND_OFFICIAL_AUTHORITY_COMPARISON",
|
||||
"canonical_targets": [],
|
||||
"canonical_target_sha256s": [],
|
||||
"reviewer_id": null,
|
||||
"review_status": "PENDING_KOREAN_LAWYER_REVIEW"
|
||||
},
|
||||
{
|
||||
"source_asset_id": "ACTIO-MIGRATION-SOURCE-09",
|
||||
"raw_path": "../../사해행위취소소송관련기존자산/사해행위취소_피보전채권번들_검증_모듈.md",
|
||||
"raw_basename_utf8_hex": "e18489e185a1e18492e185a2e18492e185a2e186bce1848be185b1e1848ee185b1e18489e185a95fe18491e185b5e18487e185a9e1848ce185a5e186abe1848ee185a2e18480e185afe186abe18487e185a5e186abe18483e185b3e186af5fe18480e185a5e186b7e1848ce185b3e186bc5fe18486e185a9e18483e185b2e186af2e6d64",
|
||||
"nfc_display_name": "사해행위취소_피보전채권번들_검증_모듈.md",
|
||||
"raw_sha256": "7b91276b363c8a99d5c4cb2ce2a5154b7003ee2dc081fa8e4db83dd0e5c0275b",
|
||||
"byte_length": 13130,
|
||||
"extracted_propositions": [],
|
||||
"official_authority_checks": [],
|
||||
"migration_disposition": null,
|
||||
"correction_or_drop_reason": "PENDING_PROPOSITION_EXTRACTION_AND_OFFICIAL_AUTHORITY_COMPARISON",
|
||||
"canonical_targets": [],
|
||||
"canonical_target_sha256s": [],
|
||||
"reviewer_id": null,
|
||||
"review_status": "PENDING_KOREAN_LAWYER_REVIEW"
|
||||
},
|
||||
{
|
||||
"source_asset_id": "ACTIO-MIGRATION-SOURCE-10",
|
||||
"raw_path": "../../사해행위취소소송관련기존자산/사해행위취소_항변통합_모듈.md",
|
||||
"raw_basename_utf8_hex": "e18489e185a1e18492e185a2e18492e185a2e186bce1848be185b1e1848ee185b1e18489e185a95fe18492e185a1e186bce18487e185a7e186abe18490e185a9e186bce18492e185a1e186b85fe18486e185a9e18483e185b2e186af2e6d64",
|
||||
"nfc_display_name": "사해행위취소_항변통합_모듈.md",
|
||||
"raw_sha256": "be5e43c5b16758ae2f6e02d37d1735bfb871c46ab014bc81f3ae197463a569fc",
|
||||
"byte_length": 13211,
|
||||
"extracted_propositions": [],
|
||||
"official_authority_checks": [],
|
||||
"migration_disposition": null,
|
||||
"correction_or_drop_reason": "PENDING_PROPOSITION_EXTRACTION_AND_OFFICIAL_AUTHORITY_COMPARISON",
|
||||
"canonical_targets": [],
|
||||
"canonical_target_sha256s": [],
|
||||
"reviewer_id": null,
|
||||
"review_status": "PENDING_KOREAN_LAWYER_REVIEW"
|
||||
}
|
||||
],
|
||||
"unresolved_reason_codes": [
|
||||
"PROPOSITION_EXTRACTION_NOT_COMPLETED",
|
||||
"OFFICIAL_AUTHORITY_COMPARISON_NOT_COMPLETED",
|
||||
"CANONICAL_TARGETS_NOT_BOUND",
|
||||
"KOREAN_LAWYER_REVIEW_NOT_COMPLETED"
|
||||
],
|
||||
"guards": {
|
||||
"legacy_source_runtime_load_forbidden": true,
|
||||
"legacy_source_fallback_forbidden": true,
|
||||
"unverified_proposition_migration_forbidden": true,
|
||||
"source_hash_mutation_forbidden": true,
|
||||
"pending_receipt_is_not_approval": true
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"schema_version": "stage2_legacy_asset_disposition.v1",
|
||||
"registry_id": "S2-LEGACY-16-DISPOSITION-PENDING",
|
||||
"status": "PENDING_SOURCE_DISCOVERY_AND_REVIEW",
|
||||
"execution_eligible": false,
|
||||
"audit_is_runtime_dependency": false,
|
||||
"path_base": "Default_Agent/Stage_2_Clean",
|
||||
"expected_source_root_ref": "../../../../Default_Agent_updated/Modules_and_New_Rules",
|
||||
"expected_row_count": 16,
|
||||
"completed_disposition_count": 0,
|
||||
"allowed_final_dispositions": ["MIGRATE", "REBUILD", "DROP"],
|
||||
"rows": [
|
||||
{"legacy_asset_id":"LEGACY-01","expected_basename_nfc":"금전채권_이행기_시효_지연손해금_작성규칙.md","source_presence_status":"PENDING_SOURCE_DISCOVERY","raw_path":null,"raw_sha256":null,"disposition":null,"canonical_targets":[],"reason_code":"PENDING_SOURCE_AND_SEMANTIC_REVIEW","review_status":"PENDING"},
|
||||
{"legacy_asset_id":"LEGACY-02","expected_basename_nfc":"상호속용_영업양수인_책임_작성규칙.md","source_presence_status":"PENDING_SOURCE_DISCOVERY","raw_path":null,"raw_sha256":null,"disposition":null,"canonical_targets":[],"reason_code":"PENDING_SOURCE_AND_SEMANTIC_REVIEW","review_status":"PENDING"},
|
||||
{"legacy_asset_id":"LEGACY-03","expected_basename_nfc":"법정변제충당_담보채무_지분말소_모듈.md","source_presence_status":"PENDING_SOURCE_DISCOVERY","raw_path":null,"raw_sha256":null,"disposition":null,"canonical_targets":[],"reason_code":"PENDING_SOURCE_AND_SEMANTIC_REVIEW","review_status":"PENDING"},
|
||||
{"legacy_asset_id":"LEGACY-04","expected_basename_nfc":"담보권실행경매_공신력_항변_모듈.md","source_presence_status":"PENDING_SOURCE_DISCOVERY","raw_path":null,"raw_sha256":null,"disposition":null,"canonical_targets":[],"reason_code":"PENDING_SOURCE_AND_SEMANTIC_REVIEW","review_status":"PENDING"},
|
||||
{"legacy_asset_id":"LEGACY-05","expected_basename_nfc":"미등기건물_토지사용_부당이득_부진정연대_모듈.md","source_presence_status":"PENDING_SOURCE_DISCOVERY","raw_path":null,"raw_sha256":null,"disposition":null,"canonical_targets":[],"reason_code":"PENDING_SOURCE_AND_SEMANTIC_REVIEW","review_status":"PENDING"},
|
||||
{"legacy_asset_id":"LEGACY-06","expected_basename_nfc":"차임감정표_법률상_기준선택_모듈.md","source_presence_status":"PENDING_SOURCE_DISCOVERY","raw_path":null,"raw_sha256":null,"disposition":null,"canonical_targets":[],"reason_code":"PENDING_SOURCE_AND_SEMANTIC_REVIEW","review_status":"PENDING"},
|
||||
{"legacy_asset_id":"LEGACY-07","expected_basename_nfc":"임대차보증금_부당이득_배제검토_모듈.md","source_presence_status":"PENDING_SOURCE_DISCOVERY","raw_path":null,"raw_sha256":null,"disposition":null,"canonical_targets":[],"reason_code":"PENDING_SOURCE_AND_SEMANTIC_REVIEW","review_status":"PENDING"},
|
||||
{"legacy_asset_id":"LEGACY-08","expected_basename_nfc":"부담부_부동산소유권이전_사해행위_가액배상_모듈.md","source_presence_status":"PENDING_SOURCE_DISCOVERY","raw_path":null,"raw_sha256":null,"disposition":null,"canonical_targets":[],"reason_code":"PENDING_SOURCE_AND_SEMANTIC_REVIEW","review_status":"PENDING"},
|
||||
{"legacy_asset_id":"LEGACY-09","expected_basename_nfc":"사해행위취소_피보전채권번들_검증_모듈.md","source_presence_status":"PENDING_SOURCE_DISCOVERY","raw_path":null,"raw_sha256":null,"disposition":null,"canonical_targets":[],"reason_code":"PENDING_SOURCE_AND_SEMANTIC_REVIEW","review_status":"PENDING"},
|
||||
{"legacy_asset_id":"LEGACY-10","expected_basename_nfc":"사해행위취소_항변통합_모듈.md","source_presence_status":"PENDING_SOURCE_DISCOVERY","raw_path":null,"raw_sha256":null,"disposition":null,"canonical_targets":[],"reason_code":"PENDING_SOURCE_AND_SEMANTIC_REVIEW","review_status":"PENDING"},
|
||||
{"legacy_asset_id":"LEGACY-11","expected_basename_nfc":"사해행위취소_가액배상_최종화게이트_모듈.md","source_presence_status":"PENDING_SOURCE_DISCOVERY","raw_path":null,"raw_sha256":null,"disposition":null,"canonical_targets":[],"reason_code":"PENDING_SOURCE_AND_SEMANTIC_REVIEW","review_status":"PENDING"},
|
||||
{"legacy_asset_id":"LEGACY-12","expected_basename_nfc":"유치권소멸_건물인도_부당이득반환_모듈.md","source_presence_status":"PENDING_SOURCE_DISCOVERY","raw_path":null,"raw_sha256":null,"disposition":null,"canonical_targets":[],"reason_code":"PENDING_SOURCE_AND_SEMANTIC_REVIEW","review_status":"PENDING"},
|
||||
{"legacy_asset_id":"LEGACY-13","expected_basename_nfc":"상속포기_배우자단독상속_모듈.md","source_presence_status":"PENDING_SOURCE_DISCOVERY","raw_path":null,"raw_sha256":null,"disposition":null,"canonical_targets":[],"reason_code":"PENDING_SOURCE_AND_SEMANTIC_REVIEW","review_status":"PENDING"},
|
||||
{"legacy_asset_id":"LEGACY-14","expected_basename_nfc":"통지_도달_법률효과_모듈.md","source_presence_status":"PENDING_SOURCE_DISCOVERY","raw_path":null,"raw_sha256":null,"disposition":null,"canonical_targets":[],"reason_code":"PENDING_SOURCE_AND_SEMANTIC_REVIEW","review_status":"PENDING"},
|
||||
{"legacy_asset_id":"LEGACY-15","expected_basename_nfc":"별지목록_등기부_asset_alias_모듈.md","source_presence_status":"PENDING_SOURCE_DISCOVERY","raw_path":null,"raw_sha256":null,"disposition":null,"canonical_targets":[],"reason_code":"PENDING_SOURCE_AND_SEMANTIC_REVIEW","review_status":"PENDING"},
|
||||
{"legacy_asset_id":"LEGACY-16","expected_basename_nfc":"피고답변서_항변추출_모듈.md","source_presence_status":"PENDING_SOURCE_DISCOVERY","raw_path":null,"raw_sha256":null,"disposition":null,"canonical_targets":[],"reason_code":"PENDING_SOURCE_AND_SEMANTIC_REVIEW","review_status":"PENDING"}
|
||||
],
|
||||
"unresolved_reason_codes": ["LEGACY_SOURCE_ROOT_NOT_OBSERVED","DISPOSITION_AUDIT_NOT_COMPLETED","CANONICAL_TARGETS_NOT_BOUND","KOREAN_LAWYER_REVIEW_NOT_COMPLETED"],
|
||||
"guards": {"runtime_load_forbidden":true,"runtime_fallback_forbidden":true,"basename_only_merge_forbidden":true,"pending_row_is_not_disposition":true,"audit_is_non_blocking_until_unique_required_proposition_found":true}
|
||||
}
|
||||
+196
@@ -0,0 +1,196 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build or check the offline authority/law-value release inventory.
|
||||
|
||||
This program performs local, explicit-path hashing only. It never retrieves
|
||||
an authority, verifies a legal proposition, signs a release, or promotes a
|
||||
release beyond the honest ``DEV_UNAVAILABLE`` state.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
from typing import Any, Mapping, Sequence
|
||||
|
||||
|
||||
REGISTRY_REF = "registry/authority/authority_registry.yml"
|
||||
REGISTRY_SCHEMA_VERSION = "stage2.authority_registry.v1"
|
||||
SCHEMA_VERSION_PATTERN = re.compile(
|
||||
r'^schema_version:\s*["\']?([^"\'\s#]+)["\']?\s*(?:#.*)?$'
|
||||
)
|
||||
|
||||
|
||||
class AuthorityReleaseBuildError(ValueError):
|
||||
"""Raised when an explicit local release input is invalid."""
|
||||
|
||||
|
||||
def _output_bytes(payload: Mapping[str, Any]) -> bytes:
|
||||
return (json.dumps(payload, ensure_ascii=False, indent=2) + "\n").encode("utf-8")
|
||||
|
||||
|
||||
def _sha256(raw: bytes) -> str:
|
||||
return hashlib.sha256(raw).hexdigest()
|
||||
|
||||
|
||||
def _contained_file(root: Path, relative_path: str) -> Path:
|
||||
if not isinstance(relative_path, str) or not relative_path:
|
||||
raise AuthorityReleaseBuildError("AUTHORITY_ASSET_PATH_INVALID")
|
||||
root = root.resolve()
|
||||
target = (root / relative_path).resolve()
|
||||
if root not in target.parents or not target.is_file():
|
||||
raise AuthorityReleaseBuildError(f"AUTHORITY_ASSET_UNAVAILABLE:{relative_path}")
|
||||
return target
|
||||
|
||||
|
||||
def _registry_schema_version(raw: bytes) -> str:
|
||||
text = raw.decode("utf-8", errors="strict")
|
||||
for line in text.splitlines():
|
||||
match = SCHEMA_VERSION_PATTERN.fullmatch(line.strip())
|
||||
if match:
|
||||
return match.group(1)
|
||||
raise AuthorityReleaseBuildError("AUTHORITY_REGISTRY_SCHEMA_VERSION_MISSING")
|
||||
|
||||
|
||||
def _inventory_rows(root: Path, spec: Sequence[Mapping[str, Any]]) -> list[dict[str, Any]]:
|
||||
rows: list[dict[str, Any]] = []
|
||||
seen: set[str] = set()
|
||||
for item in spec:
|
||||
if not isinstance(item, Mapping):
|
||||
raise AuthorityReleaseBuildError("AUTHORITY_SPEC_ROW_INVALID")
|
||||
relative_path = item.get("path")
|
||||
if not isinstance(relative_path, str) or not relative_path:
|
||||
raise AuthorityReleaseBuildError("AUTHORITY_SPEC_PATH_INVALID")
|
||||
if relative_path in seen:
|
||||
raise AuthorityReleaseBuildError(f"AUTHORITY_SPEC_DUPLICATE_PATH:{relative_path}")
|
||||
seen.add(relative_path)
|
||||
target = _contained_file(root, relative_path)
|
||||
raw = target.read_bytes()
|
||||
approval_status = item.get("approval_status", "PENDING_LEGAL_CONTENT")
|
||||
if not isinstance(approval_status, str) or not approval_status:
|
||||
raise AuthorityReleaseBuildError(
|
||||
f"AUTHORITY_SPEC_APPROVAL_STATUS_INVALID:{relative_path}"
|
||||
)
|
||||
rows.append(
|
||||
{
|
||||
"path": relative_path,
|
||||
"sha256": _sha256(raw),
|
||||
"size_bytes": len(raw),
|
||||
"approval_status": approval_status,
|
||||
}
|
||||
)
|
||||
return sorted(rows, key=lambda row: row["path"])
|
||||
|
||||
|
||||
def build_release(root: Path, spec: Sequence[Mapping[str, Any]]) -> dict[str, Any]:
|
||||
"""Return the current canonical, unsigned pending release shape.
|
||||
|
||||
Explicit inventory rows are hashed and recorded as captures, but their
|
||||
mere presence cannot establish official-source provenance, legal review,
|
||||
a detached signature, or live admission. The result therefore remains a
|
||||
non-executable development release.
|
||||
"""
|
||||
|
||||
root = root.resolve()
|
||||
registry_path = _contained_file(root, REGISTRY_REF)
|
||||
registry_raw = registry_path.read_bytes()
|
||||
registry_schema_version = _registry_schema_version(registry_raw)
|
||||
if registry_schema_version != REGISTRY_SCHEMA_VERSION:
|
||||
raise AuthorityReleaseBuildError(
|
||||
f"AUTHORITY_REGISTRY_SCHEMA_VERSION_INVALID:{registry_schema_version}"
|
||||
)
|
||||
captures = _inventory_rows(root, spec)
|
||||
return {
|
||||
"schema_version": "stage2.authority_release.v1",
|
||||
"release_id": "AUTHORITY-RELEASE-DEV-UNAVAILABLE",
|
||||
"status": "DEV_UNAVAILABLE",
|
||||
"release_class": "DEV_FIXTURE_RELEASE",
|
||||
"sealed": False,
|
||||
"signed": False,
|
||||
"executable": False,
|
||||
"created_at": None,
|
||||
"as_of": None,
|
||||
"registry": {
|
||||
"path": REGISTRY_REF,
|
||||
"sha256": _sha256(registry_raw),
|
||||
"required_status": "VERIFIED",
|
||||
"schema_version": REGISTRY_SCHEMA_VERSION,
|
||||
},
|
||||
"captures": captures,
|
||||
"law_value_dependencies": [],
|
||||
"selected_common_authority_ids": [],
|
||||
"signature": None,
|
||||
"unresolved": [
|
||||
"OFFICIAL_AUTHORITY_NOT_CAPTURED",
|
||||
"AUTHORITY_REGISTRY_NOT_LEGALLY_VERIFIED",
|
||||
"RELEASE_HASH_SET_NOT_SEALED",
|
||||
"RELEASE_SIGNATURE_MISSING",
|
||||
],
|
||||
"guards": {
|
||||
"glob_selection_forbidden": True,
|
||||
"unlisted_capture_ingest_forbidden": True,
|
||||
"unverified_registry_row_use_forbidden": True,
|
||||
"production_or_canary_use_forbidden": True,
|
||||
},
|
||||
"hash_binding_status": "DEV_HASH_BOUND_UNSIGNED",
|
||||
}
|
||||
|
||||
|
||||
def write_or_check(output: Path, expected: bytes, *, check: bool) -> int:
|
||||
"""Write expected bytes, or perform read-only exact-byte drift detection."""
|
||||
|
||||
digest = _sha256(expected)
|
||||
if check:
|
||||
if not output.is_file():
|
||||
print(
|
||||
json.dumps(
|
||||
{"status": "DRIFT", "reason": "OUTPUT_MISSING", "output": str(output)},
|
||||
sort_keys=True,
|
||||
)
|
||||
)
|
||||
return 1
|
||||
actual = output.read_bytes()
|
||||
if actual != expected:
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"status": "DRIFT",
|
||||
"reason": "OUTPUT_BYTES_MISMATCH",
|
||||
"actual_sha256": _sha256(actual),
|
||||
"expected_sha256": digest,
|
||||
"output": str(output),
|
||||
},
|
||||
sort_keys=True,
|
||||
)
|
||||
)
|
||||
return 1
|
||||
print(json.dumps({"status": "PASS", "output_sha256": digest}, sort_keys=True))
|
||||
return 0
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
output.write_bytes(expected)
|
||||
print(json.dumps({"status": "BUILT", "output_sha256": digest}, sort_keys=True))
|
||||
return 0
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("root", type=Path)
|
||||
parser.add_argument("spec", type=Path, help="JSON list of {path, approval_status}")
|
||||
parser.add_argument("output", type=Path)
|
||||
parser.add_argument(
|
||||
"--check",
|
||||
action="store_true",
|
||||
help="read-only exact-byte drift check; never creates or modifies output",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
spec = json.loads(args.spec.read_text(encoding="utf-8"))
|
||||
if not isinstance(spec, list):
|
||||
raise AuthorityReleaseBuildError("AUTHORITY_SPEC_LIST_REQUIRED")
|
||||
payload = build_release(args.root, spec)
|
||||
return write_or_check(args.output, _output_bytes(payload), check=args.check)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
+196
@@ -0,0 +1,196 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build or check the offline authority/law-value release inventory.
|
||||
|
||||
This program performs local, explicit-path hashing only. It never retrieves
|
||||
an authority, verifies a legal proposition, signs a release, or promotes a
|
||||
release beyond the honest ``DEV_UNAVAILABLE`` state.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
from typing import Any, Mapping, Sequence
|
||||
|
||||
|
||||
REGISTRY_REF = "registry/authority/authority_registry.yml"
|
||||
REGISTRY_SCHEMA_VERSION = "stage2.authority_registry.v1"
|
||||
SCHEMA_VERSION_PATTERN = re.compile(
|
||||
r'^schema_version:\s*["\']?([^"\'\s#]+)["\']?\s*(?:#.*)?$'
|
||||
)
|
||||
|
||||
|
||||
class AuthorityReleaseBuildError(ValueError):
|
||||
"""Raised when an explicit local release input is invalid."""
|
||||
|
||||
|
||||
def _output_bytes(payload: Mapping[str, Any]) -> bytes:
|
||||
return (json.dumps(payload, ensure_ascii=False, indent=2) + "\n").encode("utf-8")
|
||||
|
||||
|
||||
def _sha256(raw: bytes) -> str:
|
||||
return hashlib.sha256(raw).hexdigest()
|
||||
|
||||
|
||||
def _contained_file(root: Path, relative_path: str) -> Path:
|
||||
if not isinstance(relative_path, str) or not relative_path:
|
||||
raise AuthorityReleaseBuildError("AUTHORITY_ASSET_PATH_INVALID")
|
||||
root = root.resolve()
|
||||
target = (root / relative_path).resolve()
|
||||
if root not in target.parents or not target.is_file():
|
||||
raise AuthorityReleaseBuildError(f"AUTHORITY_ASSET_UNAVAILABLE:{relative_path}")
|
||||
return target
|
||||
|
||||
|
||||
def _registry_schema_version(raw: bytes) -> str:
|
||||
text = raw.decode("utf-8", errors="strict")
|
||||
for line in text.splitlines():
|
||||
match = SCHEMA_VERSION_PATTERN.fullmatch(line.strip())
|
||||
if match:
|
||||
return match.group(1)
|
||||
raise AuthorityReleaseBuildError("AUTHORITY_REGISTRY_SCHEMA_VERSION_MISSING")
|
||||
|
||||
|
||||
def _inventory_rows(root: Path, spec: Sequence[Mapping[str, Any]]) -> list[dict[str, Any]]:
|
||||
rows: list[dict[str, Any]] = []
|
||||
seen: set[str] = set()
|
||||
for item in spec:
|
||||
if not isinstance(item, Mapping):
|
||||
raise AuthorityReleaseBuildError("AUTHORITY_SPEC_ROW_INVALID")
|
||||
relative_path = item.get("path")
|
||||
if not isinstance(relative_path, str) or not relative_path:
|
||||
raise AuthorityReleaseBuildError("AUTHORITY_SPEC_PATH_INVALID")
|
||||
if relative_path in seen:
|
||||
raise AuthorityReleaseBuildError(f"AUTHORITY_SPEC_DUPLICATE_PATH:{relative_path}")
|
||||
seen.add(relative_path)
|
||||
target = _contained_file(root, relative_path)
|
||||
raw = target.read_bytes()
|
||||
approval_status = item.get("approval_status", "PENDING_LEGAL_CONTENT")
|
||||
if not isinstance(approval_status, str) or not approval_status:
|
||||
raise AuthorityReleaseBuildError(
|
||||
f"AUTHORITY_SPEC_APPROVAL_STATUS_INVALID:{relative_path}"
|
||||
)
|
||||
rows.append(
|
||||
{
|
||||
"path": relative_path,
|
||||
"sha256": _sha256(raw),
|
||||
"size_bytes": len(raw),
|
||||
"approval_status": approval_status,
|
||||
}
|
||||
)
|
||||
return sorted(rows, key=lambda row: row["path"])
|
||||
|
||||
|
||||
def build_release(root: Path, spec: Sequence[Mapping[str, Any]]) -> dict[str, Any]:
|
||||
"""Return the current canonical, unsigned pending release shape.
|
||||
|
||||
Explicit inventory rows are hashed and recorded as captures, but their
|
||||
mere presence cannot establish official-source provenance, legal review,
|
||||
a detached signature, or live admission. The result therefore remains a
|
||||
non-executable development release.
|
||||
"""
|
||||
|
||||
root = root.resolve()
|
||||
registry_path = _contained_file(root, REGISTRY_REF)
|
||||
registry_raw = registry_path.read_bytes()
|
||||
registry_schema_version = _registry_schema_version(registry_raw)
|
||||
if registry_schema_version != REGISTRY_SCHEMA_VERSION:
|
||||
raise AuthorityReleaseBuildError(
|
||||
f"AUTHORITY_REGISTRY_SCHEMA_VERSION_INVALID:{registry_schema_version}"
|
||||
)
|
||||
captures = _inventory_rows(root, spec)
|
||||
return {
|
||||
"schema_version": "stage2.authority_release.v1",
|
||||
"release_id": "AUTHORITY-RELEASE-DEV-UNAVAILABLE",
|
||||
"status": "DEV_UNAVAILABLE",
|
||||
"release_class": "DEV_FIXTURE_RELEASE",
|
||||
"sealed": False,
|
||||
"signed": False,
|
||||
"executable": False,
|
||||
"created_at": None,
|
||||
"as_of": None,
|
||||
"registry": {
|
||||
"path": REGISTRY_REF,
|
||||
"sha256": _sha256(registry_raw),
|
||||
"required_status": "VERIFIED",
|
||||
"schema_version": REGISTRY_SCHEMA_VERSION,
|
||||
},
|
||||
"captures": captures,
|
||||
"law_value_dependencies": [],
|
||||
"selected_common_authority_ids": [],
|
||||
"signature": None,
|
||||
"unresolved": [
|
||||
"OFFICIAL_AUTHORITY_NOT_CAPTURED",
|
||||
"AUTHORITY_REGISTRY_NOT_LEGALLY_VERIFIED",
|
||||
"RELEASE_HASH_SET_NOT_SEALED",
|
||||
"RELEASE_SIGNATURE_MISSING",
|
||||
],
|
||||
"guards": {
|
||||
"glob_selection_forbidden": True,
|
||||
"unlisted_capture_ingest_forbidden": True,
|
||||
"unverified_registry_row_use_forbidden": True,
|
||||
"production_or_canary_use_forbidden": True,
|
||||
},
|
||||
"hash_binding_status": "DEV_HASH_BOUND_UNSIGNED",
|
||||
}
|
||||
|
||||
|
||||
def write_or_check(output: Path, expected: bytes, *, check: bool) -> int:
|
||||
"""Write expected bytes, or perform read-only exact-byte drift detection."""
|
||||
|
||||
digest = _sha256(expected)
|
||||
if check:
|
||||
if not output.is_file():
|
||||
print(
|
||||
json.dumps(
|
||||
{"status": "DRIFT", "reason": "OUTPUT_MISSING", "output": str(output)},
|
||||
sort_keys=True,
|
||||
)
|
||||
)
|
||||
return 1
|
||||
actual = output.read_bytes()
|
||||
if actual != expected:
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"status": "DRIFT",
|
||||
"reason": "OUTPUT_BYTES_MISMATCH",
|
||||
"actual_sha256": _sha256(actual),
|
||||
"expected_sha256": digest,
|
||||
"output": str(output),
|
||||
},
|
||||
sort_keys=True,
|
||||
)
|
||||
)
|
||||
return 1
|
||||
print(json.dumps({"status": "PASS", "output_sha256": digest}, sort_keys=True))
|
||||
return 0
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
output.write_bytes(expected)
|
||||
print(json.dumps({"status": "BUILT", "output_sha256": digest}, sort_keys=True))
|
||||
return 0
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("root", type=Path)
|
||||
parser.add_argument("spec", type=Path, help="JSON list of {path, approval_status}")
|
||||
parser.add_argument("output", type=Path)
|
||||
parser.add_argument(
|
||||
"--check",
|
||||
action="store_true",
|
||||
help="read-only exact-byte drift check; never creates or modifies output",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
spec = json.loads(args.spec.read_text(encoding="utf-8"))
|
||||
if not isinstance(spec, list):
|
||||
raise AuthorityReleaseBuildError("AUTHORITY_SPEC_LIST_REQUIRED")
|
||||
payload = build_release(args.root, spec)
|
||||
return write_or_check(args.output, _output_bytes(payload), check=args.check)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
+273
@@ -0,0 +1,273 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Create a deterministic, schema-conformant corpus build receipt."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
|
||||
DEPLOYMENT_ROOT = Path(__file__).resolve().parents[1]
|
||||
COLLECTION_SCHEMA_REF = "corpus/weaviate_collection.schema.json"
|
||||
HEADING = re.compile(r"^(#{1,6})\s+(.+?)\s*$", re.MULTILINE)
|
||||
|
||||
|
||||
def _canonical(value: Any) -> bytes:
|
||||
return (
|
||||
json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":")) + "\n"
|
||||
).encode("utf-8")
|
||||
|
||||
|
||||
def _output_bytes(value: Any) -> bytes:
|
||||
"""Serialize receipts in the repository's stable reviewable JSON form."""
|
||||
|
||||
return (json.dumps(value, ensure_ascii=False, indent=2) + "\n").encode("utf-8")
|
||||
|
||||
|
||||
def _digest(value: Any) -> str:
|
||||
return hashlib.sha256(_canonical(value)).hexdigest()
|
||||
|
||||
|
||||
def chunks(path: Path) -> list[dict[str, object]]:
|
||||
raw = path.read_bytes()
|
||||
text = raw.decode("utf-8", errors="strict")
|
||||
matches = list(HEADING.finditer(text))
|
||||
rows: list[dict[str, object]] = []
|
||||
for index, match in enumerate(matches):
|
||||
start = match.start()
|
||||
end = matches[index + 1].start() if index + 1 < len(matches) else len(text)
|
||||
content = text[start:end]
|
||||
rows.append(
|
||||
{
|
||||
"section_path": match.group(2),
|
||||
"char_start": start,
|
||||
"char_end": end,
|
||||
"chunk_sha256": hashlib.sha256(content.encode("utf-8")).hexdigest(),
|
||||
}
|
||||
)
|
||||
return rows or [
|
||||
{
|
||||
"section_path": "ROOT",
|
||||
"char_start": 0,
|
||||
"char_end": len(text),
|
||||
"chunk_sha256": hashlib.sha256(raw).hexdigest(),
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def build_receipt(
|
||||
raw_root: Path,
|
||||
*,
|
||||
deployment_root: Path = DEPLOYMENT_ROOT,
|
||||
collection_name: str | None = None,
|
||||
tenant: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Return the exact ``corpus_build_receipt`` schema contract.
|
||||
|
||||
Local chunking does not constitute Korean-lawyer approval or live MCP
|
||||
admission. Accordingly, even a nonempty local build remains ineligible for
|
||||
production and exposes those missing approvals explicitly.
|
||||
"""
|
||||
|
||||
source_rows: list[dict[str, Any]] = []
|
||||
chunk_rows: list[dict[str, Any]] = []
|
||||
for path in sorted(raw_root.glob("CT-*/*.md")):
|
||||
rel = path.relative_to(raw_root).as_posix()
|
||||
raw = path.read_bytes()
|
||||
source_id = "SRC-" + hashlib.sha256(rel.encode("utf-8")).hexdigest()[:24]
|
||||
source_rows.append(
|
||||
{
|
||||
"source_document_id": source_id,
|
||||
"source_path": rel,
|
||||
"source_document_sha256": hashlib.sha256(raw).hexdigest(),
|
||||
}
|
||||
)
|
||||
for ordinal, row in enumerate(chunks(path), 1):
|
||||
chunk_rows.append(
|
||||
{
|
||||
"chunk_id": f"{source_id}-CH-{ordinal:04d}",
|
||||
"source_document_id": source_id,
|
||||
**row,
|
||||
}
|
||||
)
|
||||
|
||||
collection_schema_path = deployment_root / COLLECTION_SCHEMA_REF
|
||||
if not collection_schema_path.is_file():
|
||||
raise FileNotFoundError(collection_schema_path)
|
||||
collection_schema_sha256 = hashlib.sha256(collection_schema_path.read_bytes()).hexdigest()
|
||||
if not source_rows:
|
||||
return {
|
||||
"schema_version": "stage2_corpus_build_receipt.v1",
|
||||
"receipt_id": "S2-CORPUS-BUILD-PENDING",
|
||||
"build_status": "PENDING_SOURCE_AND_OPERATOR_BUILD",
|
||||
"build_executed": False,
|
||||
"created_at": None,
|
||||
"collection_contract_status": "PENDING_CORPUS_BUILD",
|
||||
"collection_schema_ref": COLLECTION_SCHEMA_REF,
|
||||
"collection_schema_sha256": collection_schema_sha256,
|
||||
"collection_name": None,
|
||||
"tenant": None,
|
||||
"snapshot_id": None,
|
||||
"snapshot_sha256": None,
|
||||
"source_document_count": 0,
|
||||
"chunk_count": 0,
|
||||
"approved_case_type_ids": [],
|
||||
"components": [
|
||||
{
|
||||
"component_id": component_id,
|
||||
"status": "PENDING",
|
||||
"configuration_digest": None,
|
||||
"evidence_refs": [],
|
||||
}
|
||||
for component_id in ("CHUNKER", "EMBEDDING", "RERANKER", "INDEX", "CROSSWALK")
|
||||
],
|
||||
"legal_review_status": "PENDING_KOREAN_LAWYER_REVIEW",
|
||||
"live_mcp_admission_status": "PENDING_LIVE_MCP_ADMISSION",
|
||||
"production_eligible": False,
|
||||
"unresolved_reason_codes": [
|
||||
"CORPUS_BUILD_NOT_EXECUTED",
|
||||
"LEGAL_REVIEW_NOT_COMPLETED",
|
||||
"LIVE_MCP_ADMISSION_NOT_COMPLETED",
|
||||
"RAW_CORPUS_NOT_PROVIDED",
|
||||
],
|
||||
"guards": {
|
||||
"raw_corpus_runtime_ingest_forbidden": True,
|
||||
"directory_scan_forbidden": True,
|
||||
"filter_relaxation_forbidden": True,
|
||||
"embedded_instruction_execution_forbidden": True,
|
||||
"unapproved_chunk_use_forbidden": True,
|
||||
},
|
||||
}
|
||||
|
||||
snapshot_scope = {"source_rows": source_rows, "chunk_rows": chunk_rows}
|
||||
snapshot_sha256 = _digest(snapshot_scope)
|
||||
chunker_digest = _digest(
|
||||
{"heading_pattern": HEADING.pattern, "fallback_section": "ROOT", "algorithm": "SHA256"}
|
||||
)
|
||||
evidence_refs = sorted(row["source_path"] for row in source_rows)
|
||||
return {
|
||||
"schema_version": "stage2_corpus_build_receipt.v1",
|
||||
"receipt_id": f"S2-CORPUS-BUILD-{snapshot_sha256[:24]}",
|
||||
"build_status": "BUILT_NOT_LEGALLY_REVIEWED",
|
||||
"build_executed": True,
|
||||
"created_at": None,
|
||||
"collection_contract_status": "PENDING_CORPUS_BUILD",
|
||||
"collection_schema_ref": COLLECTION_SCHEMA_REF,
|
||||
"collection_schema_sha256": collection_schema_sha256,
|
||||
"collection_name": collection_name,
|
||||
"tenant": tenant,
|
||||
"snapshot_id": f"SNAP-{snapshot_sha256[:24]}",
|
||||
"snapshot_sha256": snapshot_sha256,
|
||||
"source_document_count": len(source_rows),
|
||||
"chunk_count": len(chunk_rows),
|
||||
"approved_case_type_ids": [],
|
||||
"components": [
|
||||
{
|
||||
"component_id": "CHUNKER",
|
||||
"status": "BUILT_NOT_ADMITTED",
|
||||
"configuration_digest": chunker_digest,
|
||||
"evidence_refs": evidence_refs,
|
||||
},
|
||||
*[
|
||||
{
|
||||
"component_id": component_id,
|
||||
"status": "PENDING",
|
||||
"configuration_digest": None,
|
||||
"evidence_refs": [],
|
||||
}
|
||||
for component_id in ("EMBEDDING", "RERANKER", "INDEX", "CROSSWALK")
|
||||
],
|
||||
],
|
||||
"legal_review_status": "PENDING_KOREAN_LAWYER_REVIEW",
|
||||
"live_mcp_admission_status": "PENDING_LIVE_MCP_ADMISSION",
|
||||
"production_eligible": False,
|
||||
"unresolved_reason_codes": [
|
||||
"LEGAL_REVIEW_NOT_COMPLETED",
|
||||
"LIVE_MCP_ADMISSION_NOT_COMPLETED",
|
||||
"EMBEDDING_NOT_BUILT",
|
||||
"RERANKER_NOT_BUILT",
|
||||
"INDEX_NOT_BUILT",
|
||||
"CROSSWALK_NOT_BUILT",
|
||||
],
|
||||
"guards": {
|
||||
"raw_corpus_runtime_ingest_forbidden": True,
|
||||
"directory_scan_forbidden": True,
|
||||
"filter_relaxation_forbidden": True,
|
||||
"embedded_instruction_execution_forbidden": True,
|
||||
"unapproved_chunk_use_forbidden": True,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def write_or_check(output: Path, expected: bytes, *, check: bool) -> int:
|
||||
"""Write expected bytes, or perform read-only exact-byte drift detection."""
|
||||
|
||||
expected_sha256 = hashlib.sha256(expected).hexdigest()
|
||||
if check:
|
||||
if not output.is_file():
|
||||
print(
|
||||
json.dumps(
|
||||
{"status": "DRIFT", "reason": "OUTPUT_MISSING", "output": str(output)},
|
||||
sort_keys=True,
|
||||
)
|
||||
)
|
||||
return 1
|
||||
actual = output.read_bytes()
|
||||
if actual != expected:
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"status": "DRIFT",
|
||||
"reason": "OUTPUT_BYTES_MISMATCH",
|
||||
"actual_sha256": hashlib.sha256(actual).hexdigest(),
|
||||
"expected_sha256": expected_sha256,
|
||||
"output": str(output),
|
||||
},
|
||||
sort_keys=True,
|
||||
)
|
||||
)
|
||||
return 1
|
||||
print(
|
||||
json.dumps(
|
||||
{"status": "PASS", "output_sha256": expected_sha256}, sort_keys=True
|
||||
)
|
||||
)
|
||||
return 0
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
output.write_bytes(expected)
|
||||
print(
|
||||
json.dumps(
|
||||
{"status": "BUILT", "output_sha256": expected_sha256}, sort_keys=True
|
||||
)
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("raw_root", type=Path)
|
||||
parser.add_argument("output", type=Path)
|
||||
parser.add_argument("--deployment-root", type=Path, default=DEPLOYMENT_ROOT)
|
||||
parser.add_argument("--collection-name")
|
||||
parser.add_argument("--tenant")
|
||||
parser.add_argument(
|
||||
"--check",
|
||||
action="store_true",
|
||||
help="read-only exact-byte drift check; never creates or modifies output",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
payload = build_receipt(
|
||||
args.raw_root,
|
||||
deployment_root=args.deployment_root,
|
||||
collection_name=args.collection_name,
|
||||
tenant=args.tenant,
|
||||
)
|
||||
return write_or_check(args.output, _output_bytes(payload), check=args.check)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
+273
@@ -0,0 +1,273 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Create a deterministic, schema-conformant corpus build receipt."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
|
||||
DEPLOYMENT_ROOT = Path(__file__).resolve().parents[1]
|
||||
COLLECTION_SCHEMA_REF = "corpus/weaviate_collection.schema.json"
|
||||
HEADING = re.compile(r"^(#{1,6})\s+(.+?)\s*$", re.MULTILINE)
|
||||
|
||||
|
||||
def _canonical(value: Any) -> bytes:
|
||||
return (
|
||||
json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":")) + "\n"
|
||||
).encode("utf-8")
|
||||
|
||||
|
||||
def _output_bytes(value: Any) -> bytes:
|
||||
"""Serialize receipts in the repository's stable reviewable JSON form."""
|
||||
|
||||
return (json.dumps(value, ensure_ascii=False, indent=2) + "\n").encode("utf-8")
|
||||
|
||||
|
||||
def _digest(value: Any) -> str:
|
||||
return hashlib.sha256(_canonical(value)).hexdigest()
|
||||
|
||||
|
||||
def chunks(path: Path) -> list[dict[str, object]]:
|
||||
raw = path.read_bytes()
|
||||
text = raw.decode("utf-8", errors="strict")
|
||||
matches = list(HEADING.finditer(text))
|
||||
rows: list[dict[str, object]] = []
|
||||
for index, match in enumerate(matches):
|
||||
start = match.start()
|
||||
end = matches[index + 1].start() if index + 1 < len(matches) else len(text)
|
||||
content = text[start:end]
|
||||
rows.append(
|
||||
{
|
||||
"section_path": match.group(2),
|
||||
"char_start": start,
|
||||
"char_end": end,
|
||||
"chunk_sha256": hashlib.sha256(content.encode("utf-8")).hexdigest(),
|
||||
}
|
||||
)
|
||||
return rows or [
|
||||
{
|
||||
"section_path": "ROOT",
|
||||
"char_start": 0,
|
||||
"char_end": len(text),
|
||||
"chunk_sha256": hashlib.sha256(raw).hexdigest(),
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def build_receipt(
|
||||
raw_root: Path,
|
||||
*,
|
||||
deployment_root: Path = DEPLOYMENT_ROOT,
|
||||
collection_name: str | None = None,
|
||||
tenant: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Return the exact ``corpus_build_receipt`` schema contract.
|
||||
|
||||
Local chunking does not constitute Korean-lawyer approval or live MCP
|
||||
admission. Accordingly, even a nonempty local build remains ineligible for
|
||||
production and exposes those missing approvals explicitly.
|
||||
"""
|
||||
|
||||
source_rows: list[dict[str, Any]] = []
|
||||
chunk_rows: list[dict[str, Any]] = []
|
||||
for path in sorted(raw_root.glob("CT-*/*.md")):
|
||||
rel = path.relative_to(raw_root).as_posix()
|
||||
raw = path.read_bytes()
|
||||
source_id = "SRC-" + hashlib.sha256(rel.encode("utf-8")).hexdigest()[:24]
|
||||
source_rows.append(
|
||||
{
|
||||
"source_document_id": source_id,
|
||||
"source_path": rel,
|
||||
"source_document_sha256": hashlib.sha256(raw).hexdigest(),
|
||||
}
|
||||
)
|
||||
for ordinal, row in enumerate(chunks(path), 1):
|
||||
chunk_rows.append(
|
||||
{
|
||||
"chunk_id": f"{source_id}-CH-{ordinal:04d}",
|
||||
"source_document_id": source_id,
|
||||
**row,
|
||||
}
|
||||
)
|
||||
|
||||
collection_schema_path = deployment_root / COLLECTION_SCHEMA_REF
|
||||
if not collection_schema_path.is_file():
|
||||
raise FileNotFoundError(collection_schema_path)
|
||||
collection_schema_sha256 = hashlib.sha256(collection_schema_path.read_bytes()).hexdigest()
|
||||
if not source_rows:
|
||||
return {
|
||||
"schema_version": "stage2_corpus_build_receipt.v1",
|
||||
"receipt_id": "S2-CORPUS-BUILD-PENDING",
|
||||
"build_status": "PENDING_SOURCE_AND_OPERATOR_BUILD",
|
||||
"build_executed": False,
|
||||
"created_at": None,
|
||||
"collection_contract_status": "PENDING_CORPUS_BUILD",
|
||||
"collection_schema_ref": COLLECTION_SCHEMA_REF,
|
||||
"collection_schema_sha256": collection_schema_sha256,
|
||||
"collection_name": None,
|
||||
"tenant": None,
|
||||
"snapshot_id": None,
|
||||
"snapshot_sha256": None,
|
||||
"source_document_count": 0,
|
||||
"chunk_count": 0,
|
||||
"approved_case_type_ids": [],
|
||||
"components": [
|
||||
{
|
||||
"component_id": component_id,
|
||||
"status": "PENDING",
|
||||
"configuration_digest": None,
|
||||
"evidence_refs": [],
|
||||
}
|
||||
for component_id in ("CHUNKER", "EMBEDDING", "RERANKER", "INDEX", "CROSSWALK")
|
||||
],
|
||||
"legal_review_status": "PENDING_KOREAN_LAWYER_REVIEW",
|
||||
"live_mcp_admission_status": "PENDING_LIVE_MCP_ADMISSION",
|
||||
"production_eligible": False,
|
||||
"unresolved_reason_codes": [
|
||||
"CORPUS_BUILD_NOT_EXECUTED",
|
||||
"LEGAL_REVIEW_NOT_COMPLETED",
|
||||
"LIVE_MCP_ADMISSION_NOT_COMPLETED",
|
||||
"RAW_CORPUS_NOT_PROVIDED",
|
||||
],
|
||||
"guards": {
|
||||
"raw_corpus_runtime_ingest_forbidden": True,
|
||||
"directory_scan_forbidden": True,
|
||||
"filter_relaxation_forbidden": True,
|
||||
"embedded_instruction_execution_forbidden": True,
|
||||
"unapproved_chunk_use_forbidden": True,
|
||||
},
|
||||
}
|
||||
|
||||
snapshot_scope = {"source_rows": source_rows, "chunk_rows": chunk_rows}
|
||||
snapshot_sha256 = _digest(snapshot_scope)
|
||||
chunker_digest = _digest(
|
||||
{"heading_pattern": HEADING.pattern, "fallback_section": "ROOT", "algorithm": "SHA256"}
|
||||
)
|
||||
evidence_refs = sorted(row["source_path"] for row in source_rows)
|
||||
return {
|
||||
"schema_version": "stage2_corpus_build_receipt.v1",
|
||||
"receipt_id": f"S2-CORPUS-BUILD-{snapshot_sha256[:24]}",
|
||||
"build_status": "BUILT_NOT_LEGALLY_REVIEWED",
|
||||
"build_executed": True,
|
||||
"created_at": None,
|
||||
"collection_contract_status": "PENDING_CORPUS_BUILD",
|
||||
"collection_schema_ref": COLLECTION_SCHEMA_REF,
|
||||
"collection_schema_sha256": collection_schema_sha256,
|
||||
"collection_name": collection_name,
|
||||
"tenant": tenant,
|
||||
"snapshot_id": f"SNAP-{snapshot_sha256[:24]}",
|
||||
"snapshot_sha256": snapshot_sha256,
|
||||
"source_document_count": len(source_rows),
|
||||
"chunk_count": len(chunk_rows),
|
||||
"approved_case_type_ids": [],
|
||||
"components": [
|
||||
{
|
||||
"component_id": "CHUNKER",
|
||||
"status": "BUILT_NOT_ADMITTED",
|
||||
"configuration_digest": chunker_digest,
|
||||
"evidence_refs": evidence_refs,
|
||||
},
|
||||
*[
|
||||
{
|
||||
"component_id": component_id,
|
||||
"status": "PENDING",
|
||||
"configuration_digest": None,
|
||||
"evidence_refs": [],
|
||||
}
|
||||
for component_id in ("EMBEDDING", "RERANKER", "INDEX", "CROSSWALK")
|
||||
],
|
||||
],
|
||||
"legal_review_status": "PENDING_KOREAN_LAWYER_REVIEW",
|
||||
"live_mcp_admission_status": "PENDING_LIVE_MCP_ADMISSION",
|
||||
"production_eligible": False,
|
||||
"unresolved_reason_codes": [
|
||||
"LEGAL_REVIEW_NOT_COMPLETED",
|
||||
"LIVE_MCP_ADMISSION_NOT_COMPLETED",
|
||||
"EMBEDDING_NOT_BUILT",
|
||||
"RERANKER_NOT_BUILT",
|
||||
"INDEX_NOT_BUILT",
|
||||
"CROSSWALK_NOT_BUILT",
|
||||
],
|
||||
"guards": {
|
||||
"raw_corpus_runtime_ingest_forbidden": True,
|
||||
"directory_scan_forbidden": True,
|
||||
"filter_relaxation_forbidden": True,
|
||||
"embedded_instruction_execution_forbidden": True,
|
||||
"unapproved_chunk_use_forbidden": True,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def write_or_check(output: Path, expected: bytes, *, check: bool) -> int:
|
||||
"""Write expected bytes, or perform read-only exact-byte drift detection."""
|
||||
|
||||
expected_sha256 = hashlib.sha256(expected).hexdigest()
|
||||
if check:
|
||||
if not output.is_file():
|
||||
print(
|
||||
json.dumps(
|
||||
{"status": "DRIFT", "reason": "OUTPUT_MISSING", "output": str(output)},
|
||||
sort_keys=True,
|
||||
)
|
||||
)
|
||||
return 1
|
||||
actual = output.read_bytes()
|
||||
if actual != expected:
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"status": "DRIFT",
|
||||
"reason": "OUTPUT_BYTES_MISMATCH",
|
||||
"actual_sha256": hashlib.sha256(actual).hexdigest(),
|
||||
"expected_sha256": expected_sha256,
|
||||
"output": str(output),
|
||||
},
|
||||
sort_keys=True,
|
||||
)
|
||||
)
|
||||
return 1
|
||||
print(
|
||||
json.dumps(
|
||||
{"status": "PASS", "output_sha256": expected_sha256}, sort_keys=True
|
||||
)
|
||||
)
|
||||
return 0
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
output.write_bytes(expected)
|
||||
print(
|
||||
json.dumps(
|
||||
{"status": "BUILT", "output_sha256": expected_sha256}, sort_keys=True
|
||||
)
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("raw_root", type=Path)
|
||||
parser.add_argument("output", type=Path)
|
||||
parser.add_argument("--deployment-root", type=Path, default=DEPLOYMENT_ROOT)
|
||||
parser.add_argument("--collection-name")
|
||||
parser.add_argument("--tenant")
|
||||
parser.add_argument(
|
||||
"--check",
|
||||
action="store_true",
|
||||
help="read-only exact-byte drift check; never creates or modifies output",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
payload = build_receipt(
|
||||
args.raw_root,
|
||||
deployment_root=args.deployment_root,
|
||||
collection_name=args.collection_name,
|
||||
tenant=args.tenant,
|
||||
)
|
||||
return write_or_check(args.output, _output_bytes(payload), check=args.check)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
+493
@@ -0,0 +1,493 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build the canonical Stage-2 module manifest and parent release.
|
||||
|
||||
The parent release is deliberately a *raw closure*. Assets that embed the
|
||||
parent release hash (Agent projections, inline-code mirrors/receipts, the
|
||||
shared executor binding and detached admission material) are kept outside the
|
||||
closure so that the release graph cannot become self-referential.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import copy
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path, PurePosixPath
|
||||
from typing import Any, Iterable
|
||||
|
||||
|
||||
MODULE_MANIFEST_SCHEMA = "stage2_module_manifest.v1"
|
||||
PARENT_RELEASE_SCHEMA = "stage2_release.v2"
|
||||
|
||||
# Downstream-of-parent, detached, and self-referential assets. They may appear
|
||||
# as an unhashed external trust-root pointer, but never in the parent closure.
|
||||
FORBIDDEN_PARENT_MEMBERS = frozenset(
|
||||
{
|
||||
"manifest/module_manifest.json",
|
||||
"manifest/stage2_release.json",
|
||||
"deployment/stage2_code_executor_binding.yml",
|
||||
"manifest/stage2_deterministic_admission_receipt.json",
|
||||
"agent_scripts/Stage_2_S2_00.yml",
|
||||
"runtime/s2_00_ingress.py",
|
||||
"runtime/s2_00_ingress.txt",
|
||||
"manifest/s2_00_inline_code_receipt.json",
|
||||
"agent_scripts/Stage_2_S2_20.yml",
|
||||
"runtime/s2_20_reduce.py",
|
||||
"runtime/s2_20_reduce.txt",
|
||||
"manifest/s2_20_inline_code_receipt.json",
|
||||
"manifest/s2_10_release.json",
|
||||
"manifest/s2_10_agent_receipt.json",
|
||||
"manifest/s2_10_platform_adapter_receipt.json",
|
||||
"manifest/s2_10_model_benchmark_receipt.json",
|
||||
"manifest/s2_10_legal_review_receipt.json",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class ReleaseBuildError(ValueError):
|
||||
"""Raised when a release input violates the canonical closure contract."""
|
||||
|
||||
|
||||
def _reject_duplicate_pairs(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
|
||||
result: dict[str, Any] = {}
|
||||
for key, value in pairs:
|
||||
if key in result:
|
||||
raise ReleaseBuildError(f"DUPLICATE_JSON_KEY:{key}")
|
||||
result[key] = value
|
||||
return result
|
||||
|
||||
|
||||
def _load_json(path: Path) -> Any:
|
||||
try:
|
||||
return json.loads(
|
||||
path.read_text(encoding="utf-8"),
|
||||
object_pairs_hook=_reject_duplicate_pairs,
|
||||
parse_constant=lambda token: (_ for _ in ()).throw(
|
||||
ReleaseBuildError(f"NON_FINITE_JSON_NUMBER:{token}")
|
||||
),
|
||||
)
|
||||
except UnicodeDecodeError as exc:
|
||||
raise ReleaseBuildError(f"UTF8_REQUIRED:{path.as_posix()}") from exc
|
||||
|
||||
|
||||
def _canonical_bytes(document: Any) -> bytes:
|
||||
return (
|
||||
json.dumps(
|
||||
document,
|
||||
ensure_ascii=False,
|
||||
sort_keys=True,
|
||||
separators=(",", ":"),
|
||||
allow_nan=False,
|
||||
)
|
||||
+ "\n"
|
||||
).encode("utf-8")
|
||||
|
||||
|
||||
def _document_digest(document: dict[str, Any], digest_field: str) -> str:
|
||||
payload = copy.deepcopy(document)
|
||||
payload.pop(digest_field, None)
|
||||
return hashlib.sha256(_canonical_bytes(payload)).hexdigest()
|
||||
|
||||
|
||||
def _sha256(raw: bytes) -> str:
|
||||
return hashlib.sha256(raw).hexdigest()
|
||||
|
||||
|
||||
def _relative_path(value: str) -> str:
|
||||
if not isinstance(value, str) or not value or "\\" in value:
|
||||
raise ReleaseBuildError(f"RELATIVE_POSIX_PATH_REQUIRED:{value!r}")
|
||||
pure = PurePosixPath(value)
|
||||
if pure.is_absolute() or any(part in {"", ".", ".."} for part in pure.parts):
|
||||
raise ReleaseBuildError(f"UNSAFE_RELATIVE_PATH:{value}")
|
||||
normalized = pure.as_posix()
|
||||
if any(segment in {"v.0", "v.1", "v.2", "v.3"} for segment in pure.parts):
|
||||
raise ReleaseBuildError(f"LEGACY_STAGE2_DEPENDENCY:{normalized}")
|
||||
return normalized
|
||||
|
||||
|
||||
def _physical_file(root: Path, relative: str) -> Path:
|
||||
relative = _relative_path(relative)
|
||||
root_real = root.resolve(strict=True)
|
||||
candidate = root / relative
|
||||
if candidate.is_symlink():
|
||||
raise ReleaseBuildError(f"MODULE_SYMLINK_FORBIDDEN:{relative}")
|
||||
try:
|
||||
resolved = candidate.resolve(strict=True)
|
||||
except FileNotFoundError as exc:
|
||||
raise ReleaseBuildError(f"MODULE_PATH_UNAVAILABLE:{relative}") from exc
|
||||
if root_real not in resolved.parents or not resolved.is_file():
|
||||
raise ReleaseBuildError(f"MODULE_PATH_UNAVAILABLE:{relative}")
|
||||
return resolved
|
||||
|
||||
|
||||
def _asset_bytes(root: Path, relative: str) -> bytes:
|
||||
return _physical_file(root, relative).read_bytes()
|
||||
|
||||
|
||||
def _mirror_for(root: Path, source_path: str) -> tuple[str, bytes] | None:
|
||||
if not source_path.endswith(".py"):
|
||||
return None
|
||||
mirror_path = source_path[:-3] + ".txt"
|
||||
candidate = root / mirror_path
|
||||
if not candidate.exists():
|
||||
raise ReleaseBuildError(f"PYTHON_DOCUMENTATION_MIRROR_MISSING:{source_path}")
|
||||
source_raw = _asset_bytes(root, source_path)
|
||||
mirror_raw = _asset_bytes(root, mirror_path)
|
||||
if source_raw != mirror_raw:
|
||||
raise ReleaseBuildError(f"PYTHON_DOCUMENTATION_MIRROR_MISMATCH:{source_path}")
|
||||
return mirror_path, mirror_raw
|
||||
|
||||
|
||||
def _generic_kind(relative: str) -> str:
|
||||
if relative.startswith("tests/"):
|
||||
return "TEST"
|
||||
if relative.startswith("schemas/"):
|
||||
return "JSON_SCHEMA"
|
||||
if relative.startswith("workflows/"):
|
||||
return "WORKFLOW"
|
||||
if relative.startswith("offline_build/") or relative.startswith("release_ops/"):
|
||||
return "BUILD_ONLY"
|
||||
if relative.startswith("runtime/"):
|
||||
return "RUNTIME_CORE"
|
||||
if relative.startswith("rules/"):
|
||||
return "LEGAL_RULE_SOURCE"
|
||||
if relative.startswith("manifest/"):
|
||||
return "MANIFEST"
|
||||
if relative.startswith("registry/"):
|
||||
return "DECLARATIVE_REGISTRY"
|
||||
return "S2_20_ASSET"
|
||||
|
||||
|
||||
def _generic_module_row(relative: str) -> dict[str, Any]:
|
||||
identity = hashlib.sha256(relative.encode("utf-8")).hexdigest()[:16].upper()
|
||||
return {
|
||||
"asset_version": "s2_20.1",
|
||||
"authority_ids": [],
|
||||
"consumed_schema_ids": [],
|
||||
"dependency_module_ids": [],
|
||||
"forbidden_contract_codes": ["LEGACY-STAGE2-V0-V3"],
|
||||
"implementation_status": "DEV_HASH_BOUND_OFFLINE_ONLY",
|
||||
"incompatible_module_ids": [],
|
||||
"inputs": [],
|
||||
"module_id": f"S2_20-ASSET-{identity}",
|
||||
"module_kind": _generic_kind(relative),
|
||||
"outputs": [],
|
||||
"owner": "Stage_2_S2_20_owner",
|
||||
"path": relative,
|
||||
"produced_schema_ids": [],
|
||||
"schema_version": "stage2_s2_20_generic_asset.v1",
|
||||
"scope_predicate": "workflow_id == S2_20",
|
||||
"semantic_review_status": "PENDING_LEGAL_AND_LIVE_ADMISSION",
|
||||
}
|
||||
|
||||
|
||||
def _refresh_module_row(root: Path, row: dict[str, Any]) -> dict[str, Any]:
|
||||
refreshed = copy.deepcopy(row)
|
||||
relative = _relative_path(refreshed.get("path"))
|
||||
if relative in FORBIDDEN_PARENT_MEMBERS:
|
||||
raise ReleaseBuildError(f"NON_CYCLIC_PARENT_VIOLATION:{relative}")
|
||||
raw = _asset_bytes(root, relative)
|
||||
refreshed["path"] = relative
|
||||
refreshed["sha256"] = _sha256(raw)
|
||||
refreshed["size_bytes"] = len(raw)
|
||||
mirror = _mirror_for(root, relative)
|
||||
if mirror is not None:
|
||||
mirror_path, mirror_raw = mirror
|
||||
refreshed["mirror_path"] = mirror_path
|
||||
refreshed["mirror_sha256"] = _sha256(mirror_raw)
|
||||
refreshed["mirror_size_bytes"] = len(mirror_raw)
|
||||
refreshed["mirror_byte_parity"] = True
|
||||
return refreshed
|
||||
|
||||
|
||||
def _documentation_mirror(row: dict[str, Any]) -> dict[str, Any] | None:
|
||||
source = row.get("path")
|
||||
mirror = row.get("mirror_path")
|
||||
if not isinstance(source, str) or not isinstance(mirror, str):
|
||||
return None
|
||||
return {
|
||||
"byte_identical": True,
|
||||
"mirror_path": mirror,
|
||||
"mirror_sha256": row["mirror_sha256"],
|
||||
"mirror_size_bytes": row["mirror_size_bytes"],
|
||||
"runtime_import_allowed": False,
|
||||
"source_path": source,
|
||||
"source_sha256": row["sha256"],
|
||||
"source_size_bytes": row["size_bytes"],
|
||||
}
|
||||
|
||||
|
||||
def build_module_manifest(
|
||||
root: Path,
|
||||
template: dict[str, Any],
|
||||
explicit_paths: Iterable[str],
|
||||
) -> dict[str, Any]:
|
||||
"""Preserve canonical rows, refresh physical bindings and append S2_20 rows."""
|
||||
|
||||
if template.get("schema_version") != MODULE_MANIFEST_SCHEMA:
|
||||
raise ReleaseBuildError("CANONICAL_MODULE_MANIFEST_V1_REQUIRED")
|
||||
existing = template.get("modules")
|
||||
if not isinstance(existing, list):
|
||||
raise ReleaseBuildError("CANONICAL_MODULES_ARRAY_REQUIRED")
|
||||
|
||||
normalized_explicit = [_relative_path(value) for value in explicit_paths]
|
||||
if len(normalized_explicit) != len(set(normalized_explicit)):
|
||||
raise ReleaseBuildError("PATH_LIST_MUST_BE_UNIQUE_ARRAY")
|
||||
forbidden = sorted(set(normalized_explicit) & FORBIDDEN_PARENT_MEMBERS)
|
||||
if forbidden:
|
||||
raise ReleaseBuildError(f"NON_CYCLIC_PARENT_VIOLATION:{forbidden}")
|
||||
|
||||
result = copy.deepcopy(template)
|
||||
refreshed_rows: list[dict[str, Any]] = []
|
||||
seen_ids: set[str] = set()
|
||||
seen_paths: set[str] = set()
|
||||
for value in existing:
|
||||
if not isinstance(value, dict):
|
||||
raise ReleaseBuildError("MODULE_ROW_OBJECT_REQUIRED")
|
||||
module_id = value.get("module_id")
|
||||
relative = _relative_path(value.get("path"))
|
||||
if not isinstance(module_id, str) or not module_id or module_id in seen_ids:
|
||||
raise ReleaseBuildError(f"MODULE_ID_INVALID_OR_DUPLICATE:{module_id!r}")
|
||||
if relative in seen_paths:
|
||||
raise ReleaseBuildError(f"MODULE_PATH_DUPLICATE:{relative}")
|
||||
seen_ids.add(module_id)
|
||||
seen_paths.add(relative)
|
||||
refreshed_rows.append(_refresh_module_row(root, value))
|
||||
|
||||
explicit_set = set(normalized_explicit)
|
||||
for relative in sorted(normalized_explicit):
|
||||
# A .txt copy is documentation for an explicitly bound .py source, not
|
||||
# a second executable module row.
|
||||
if relative.endswith(".txt"):
|
||||
source = relative[:-4] + ".py"
|
||||
if source in explicit_set or source in seen_paths:
|
||||
_mirror_for(root, source)
|
||||
continue
|
||||
if relative in seen_paths:
|
||||
continue
|
||||
row = _refresh_module_row(root, _generic_module_row(relative))
|
||||
if row["module_id"] in seen_ids:
|
||||
raise ReleaseBuildError(f"GENERATED_MODULE_ID_COLLISION:{row['module_id']}")
|
||||
seen_ids.add(row["module_id"])
|
||||
seen_paths.add(relative)
|
||||
refreshed_rows.append(row)
|
||||
|
||||
result["modules"] = refreshed_rows
|
||||
mirrors = [entry for row in refreshed_rows if (entry := _documentation_mirror(row))]
|
||||
mirror_sources = [str(entry["source_path"]) for entry in mirrors]
|
||||
if len(mirror_sources) != len(set(mirror_sources)):
|
||||
raise ReleaseBuildError("DOCUMENTATION_MIRROR_SOURCE_DUPLICATE")
|
||||
result["documentation_mirrors"] = sorted(
|
||||
mirrors, key=lambda entry: str(entry["source_path"])
|
||||
)
|
||||
|
||||
summary = result.get("closure_summary")
|
||||
if not isinstance(summary, dict):
|
||||
raise ReleaseBuildError("CLOSURE_SUMMARY_REQUIRED")
|
||||
summary.update(
|
||||
{
|
||||
"module_count": len(refreshed_rows),
|
||||
"documentation_mirror_count": len(mirrors),
|
||||
"test_module_count": sum(
|
||||
1 for row in refreshed_rows if row.get("module_kind") == "TEST"
|
||||
),
|
||||
"build_tool_module_count": sum(
|
||||
1 for row in refreshed_rows if row.get("module_kind") == "BUILD_TOOL"
|
||||
),
|
||||
"s2_20_generic_module_count": sum(
|
||||
1
|
||||
for row in refreshed_rows
|
||||
if row.get("schema_version") == "stage2_s2_20_generic_asset.v1"
|
||||
),
|
||||
"executable_agent_hash_included": False,
|
||||
"executor_binding_hash_included": False,
|
||||
"inline_runtime_code_hash_included": False,
|
||||
"self_hash_included": False,
|
||||
"stage2_release_hash_included": False,
|
||||
}
|
||||
)
|
||||
result["manifest_digest"] = _document_digest(result, "manifest_digest")
|
||||
return result
|
||||
|
||||
|
||||
def _refresh_parent_refs(
|
||||
value: Any,
|
||||
root: Path,
|
||||
raw_overrides: dict[str, bytes],
|
||||
location: str = "$",
|
||||
) -> None:
|
||||
if isinstance(value, list):
|
||||
for index, item in enumerate(value):
|
||||
_refresh_parent_refs(item, root, raw_overrides, f"{location}/{index}")
|
||||
return
|
||||
if not isinstance(value, dict):
|
||||
return
|
||||
|
||||
relative = value.get("path")
|
||||
if isinstance(relative, str) and relative and "<" not in relative:
|
||||
try:
|
||||
relative = _relative_path(relative)
|
||||
except ReleaseBuildError:
|
||||
relative = None
|
||||
if relative == "manifest/module_manifest.json":
|
||||
raw = raw_overrides.get(relative)
|
||||
if raw is None:
|
||||
raise ReleaseBuildError("MODULE_MANIFEST_RAW_OVERRIDE_REQUIRED")
|
||||
if "sha256" in value:
|
||||
value["sha256"] = _sha256(raw)
|
||||
if "size_bytes" in value:
|
||||
value["size_bytes"] = len(raw)
|
||||
elif relative in FORBIDDEN_PARENT_MEMBERS:
|
||||
if "sha256" in value or value.get("binding_status") == "BOUND":
|
||||
raise ReleaseBuildError(
|
||||
f"NON_CYCLIC_PARENT_HASH_REFERENCE:{location}:{relative}"
|
||||
)
|
||||
elif relative is not None and (relative in raw_overrides or (root / relative).is_file()):
|
||||
raw = raw_overrides.get(relative)
|
||||
if raw is None:
|
||||
raw = _asset_bytes(root, relative)
|
||||
if "sha256" in value:
|
||||
value["sha256"] = _sha256(raw)
|
||||
if "size_bytes" in value:
|
||||
value["size_bytes"] = len(raw)
|
||||
|
||||
for key, item in list(value.items()):
|
||||
_refresh_parent_refs(item, root, raw_overrides, f"{location}/{key}")
|
||||
|
||||
|
||||
def build_parent_release(
|
||||
root: Path,
|
||||
template: dict[str, Any],
|
||||
module_manifest_raw: bytes,
|
||||
) -> dict[str, Any]:
|
||||
"""Refresh safe parent refs and its self digest without changing admission."""
|
||||
|
||||
if template.get("schema_version") != PARENT_RELEASE_SCHEMA:
|
||||
raise ReleaseBuildError("PARENT_STAGE2_RELEASE_V2_REQUIRED")
|
||||
result = copy.deepcopy(template)
|
||||
immutable_admission = {
|
||||
key: copy.deepcopy(result.get(key))
|
||||
for key in (
|
||||
"release_class",
|
||||
"release_status",
|
||||
"authorization_status",
|
||||
"signature",
|
||||
"release_evidence",
|
||||
)
|
||||
}
|
||||
module_ref = result.get("module_manifest_ref")
|
||||
if not isinstance(module_ref, dict) or module_ref.get("path") != "manifest/module_manifest.json":
|
||||
raise ReleaseBuildError("PARENT_MODULE_MANIFEST_REF_REQUIRED")
|
||||
module_ref["sha256"] = _sha256(module_manifest_raw)
|
||||
if "size_bytes" in module_ref:
|
||||
module_ref["size_bytes"] = len(module_manifest_raw)
|
||||
|
||||
executor_ref = result.get("executor_binding_ref")
|
||||
if (
|
||||
not isinstance(executor_ref, dict)
|
||||
or executor_ref.get("path") != "deployment/stage2_code_executor_binding.yml"
|
||||
):
|
||||
raise ReleaseBuildError("PARENT_EXECUTOR_TRUST_ROOT_REF_REQUIRED")
|
||||
if "sha256" in executor_ref or executor_ref.get("binding_status") == "BOUND":
|
||||
raise ReleaseBuildError("PARENT_EXECUTOR_TRUST_ROOT_MUST_BE_UNHASHED")
|
||||
executor_ref["schema_id"] = "stage2_code_executor_binding.v3"
|
||||
|
||||
_refresh_parent_refs(
|
||||
result,
|
||||
root,
|
||||
{"manifest/module_manifest.json": module_manifest_raw},
|
||||
)
|
||||
bundle = result.get("bundle")
|
||||
if isinstance(bundle, dict):
|
||||
agent_ref = bundle.get("s2_10_agent_ref")
|
||||
binding_ref = bundle.get("s2_10_llm_binding_ref")
|
||||
if isinstance(agent_ref, dict) and isinstance(agent_ref.get("sha256"), str):
|
||||
bundle["s2_10_agent_sha256"] = agent_ref["sha256"]
|
||||
if isinstance(binding_ref, dict) and isinstance(binding_ref.get("sha256"), str):
|
||||
bundle["s2_10_llm_binding_sha256"] = binding_ref["sha256"]
|
||||
|
||||
for key, expected in immutable_admission.items():
|
||||
if result.get(key) != expected:
|
||||
raise ReleaseBuildError(f"ADMISSION_STATE_MUTATION_FORBIDDEN:{key}")
|
||||
result["release_digest"] = _document_digest(result, "release_digest")
|
||||
return result
|
||||
|
||||
|
||||
def build_release_package(
|
||||
root: Path,
|
||||
path_list: list[str],
|
||||
module_template: dict[str, Any],
|
||||
parent_template: dict[str, Any],
|
||||
) -> tuple[dict[str, Any], dict[str, Any]]:
|
||||
module_manifest = build_module_manifest(root, module_template, path_list)
|
||||
module_raw = _canonical_bytes(module_manifest)
|
||||
parent_release = build_parent_release(root, parent_template, module_raw)
|
||||
return module_manifest, parent_release
|
||||
|
||||
|
||||
def _write_bytes(path: Path, raw: bytes) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
temporary = path.with_name(f".{path.name}.tmp-{os.getpid()}")
|
||||
temporary.write_bytes(raw)
|
||||
os.replace(temporary, path)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Reseal canonical Stage-2 module and parent release manifests"
|
||||
)
|
||||
parser.add_argument("root", type=Path)
|
||||
parser.add_argument("path_list", type=Path, help="JSON array of relative S2_20 paths")
|
||||
parser.add_argument("output", type=Path, help="module_manifest.json output")
|
||||
parser.add_argument(
|
||||
"--module-template",
|
||||
type=Path,
|
||||
help="canonical module manifest template (default: ROOT/manifest/module_manifest.json)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--parent-template",
|
||||
type=Path,
|
||||
help="parent release template (default: ROOT/manifest/stage2_release.json)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--parent-output",
|
||||
type=Path,
|
||||
help="parent release output (default: OUTPUT sibling stage2_release.json)",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
path_list = _load_json(args.path_list)
|
||||
if not isinstance(path_list, list) or not all(isinstance(row, str) for row in path_list):
|
||||
raise ReleaseBuildError("PATH_LIST_MUST_BE_STRING_ARRAY")
|
||||
module_template_path = args.module_template or args.root / "manifest/module_manifest.json"
|
||||
parent_template_path = args.parent_template or args.root / "manifest/stage2_release.json"
|
||||
parent_output = args.parent_output or args.output.with_name("stage2_release.json")
|
||||
module_manifest, parent_release = build_release_package(
|
||||
args.root,
|
||||
path_list,
|
||||
_load_json(module_template_path),
|
||||
_load_json(parent_template_path),
|
||||
)
|
||||
_write_bytes(args.output, _canonical_bytes(module_manifest))
|
||||
_write_bytes(parent_output, _canonical_bytes(parent_release))
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"module_count": len(module_manifest["modules"]),
|
||||
"module_manifest_output": args.output.as_posix(),
|
||||
"module_manifest_sha256": _sha256(_canonical_bytes(module_manifest)),
|
||||
"parent_release_output": parent_output.as_posix(),
|
||||
"parent_release_sha256": _sha256(_canonical_bytes(parent_release)),
|
||||
"status": "PASS",
|
||||
},
|
||||
sort_keys=True,
|
||||
separators=(",", ":"),
|
||||
)
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
+493
@@ -0,0 +1,493 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build the canonical Stage-2 module manifest and parent release.
|
||||
|
||||
The parent release is deliberately a *raw closure*. Assets that embed the
|
||||
parent release hash (Agent projections, inline-code mirrors/receipts, the
|
||||
shared executor binding and detached admission material) are kept outside the
|
||||
closure so that the release graph cannot become self-referential.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import copy
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path, PurePosixPath
|
||||
from typing import Any, Iterable
|
||||
|
||||
|
||||
MODULE_MANIFEST_SCHEMA = "stage2_module_manifest.v1"
|
||||
PARENT_RELEASE_SCHEMA = "stage2_release.v2"
|
||||
|
||||
# Downstream-of-parent, detached, and self-referential assets. They may appear
|
||||
# as an unhashed external trust-root pointer, but never in the parent closure.
|
||||
FORBIDDEN_PARENT_MEMBERS = frozenset(
|
||||
{
|
||||
"manifest/module_manifest.json",
|
||||
"manifest/stage2_release.json",
|
||||
"deployment/stage2_code_executor_binding.yml",
|
||||
"manifest/stage2_deterministic_admission_receipt.json",
|
||||
"agent_scripts/Stage_2_S2_00.yml",
|
||||
"runtime/s2_00_ingress.py",
|
||||
"runtime/s2_00_ingress.txt",
|
||||
"manifest/s2_00_inline_code_receipt.json",
|
||||
"agent_scripts/Stage_2_S2_20.yml",
|
||||
"runtime/s2_20_reduce.py",
|
||||
"runtime/s2_20_reduce.txt",
|
||||
"manifest/s2_20_inline_code_receipt.json",
|
||||
"manifest/s2_10_release.json",
|
||||
"manifest/s2_10_agent_receipt.json",
|
||||
"manifest/s2_10_platform_adapter_receipt.json",
|
||||
"manifest/s2_10_model_benchmark_receipt.json",
|
||||
"manifest/s2_10_legal_review_receipt.json",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class ReleaseBuildError(ValueError):
|
||||
"""Raised when a release input violates the canonical closure contract."""
|
||||
|
||||
|
||||
def _reject_duplicate_pairs(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
|
||||
result: dict[str, Any] = {}
|
||||
for key, value in pairs:
|
||||
if key in result:
|
||||
raise ReleaseBuildError(f"DUPLICATE_JSON_KEY:{key}")
|
||||
result[key] = value
|
||||
return result
|
||||
|
||||
|
||||
def _load_json(path: Path) -> Any:
|
||||
try:
|
||||
return json.loads(
|
||||
path.read_text(encoding="utf-8"),
|
||||
object_pairs_hook=_reject_duplicate_pairs,
|
||||
parse_constant=lambda token: (_ for _ in ()).throw(
|
||||
ReleaseBuildError(f"NON_FINITE_JSON_NUMBER:{token}")
|
||||
),
|
||||
)
|
||||
except UnicodeDecodeError as exc:
|
||||
raise ReleaseBuildError(f"UTF8_REQUIRED:{path.as_posix()}") from exc
|
||||
|
||||
|
||||
def _canonical_bytes(document: Any) -> bytes:
|
||||
return (
|
||||
json.dumps(
|
||||
document,
|
||||
ensure_ascii=False,
|
||||
sort_keys=True,
|
||||
separators=(",", ":"),
|
||||
allow_nan=False,
|
||||
)
|
||||
+ "\n"
|
||||
).encode("utf-8")
|
||||
|
||||
|
||||
def _document_digest(document: dict[str, Any], digest_field: str) -> str:
|
||||
payload = copy.deepcopy(document)
|
||||
payload.pop(digest_field, None)
|
||||
return hashlib.sha256(_canonical_bytes(payload)).hexdigest()
|
||||
|
||||
|
||||
def _sha256(raw: bytes) -> str:
|
||||
return hashlib.sha256(raw).hexdigest()
|
||||
|
||||
|
||||
def _relative_path(value: str) -> str:
|
||||
if not isinstance(value, str) or not value or "\\" in value:
|
||||
raise ReleaseBuildError(f"RELATIVE_POSIX_PATH_REQUIRED:{value!r}")
|
||||
pure = PurePosixPath(value)
|
||||
if pure.is_absolute() or any(part in {"", ".", ".."} for part in pure.parts):
|
||||
raise ReleaseBuildError(f"UNSAFE_RELATIVE_PATH:{value}")
|
||||
normalized = pure.as_posix()
|
||||
if any(segment in {"v.0", "v.1", "v.2", "v.3"} for segment in pure.parts):
|
||||
raise ReleaseBuildError(f"LEGACY_STAGE2_DEPENDENCY:{normalized}")
|
||||
return normalized
|
||||
|
||||
|
||||
def _physical_file(root: Path, relative: str) -> Path:
|
||||
relative = _relative_path(relative)
|
||||
root_real = root.resolve(strict=True)
|
||||
candidate = root / relative
|
||||
if candidate.is_symlink():
|
||||
raise ReleaseBuildError(f"MODULE_SYMLINK_FORBIDDEN:{relative}")
|
||||
try:
|
||||
resolved = candidate.resolve(strict=True)
|
||||
except FileNotFoundError as exc:
|
||||
raise ReleaseBuildError(f"MODULE_PATH_UNAVAILABLE:{relative}") from exc
|
||||
if root_real not in resolved.parents or not resolved.is_file():
|
||||
raise ReleaseBuildError(f"MODULE_PATH_UNAVAILABLE:{relative}")
|
||||
return resolved
|
||||
|
||||
|
||||
def _asset_bytes(root: Path, relative: str) -> bytes:
|
||||
return _physical_file(root, relative).read_bytes()
|
||||
|
||||
|
||||
def _mirror_for(root: Path, source_path: str) -> tuple[str, bytes] | None:
|
||||
if not source_path.endswith(".py"):
|
||||
return None
|
||||
mirror_path = source_path[:-3] + ".txt"
|
||||
candidate = root / mirror_path
|
||||
if not candidate.exists():
|
||||
raise ReleaseBuildError(f"PYTHON_DOCUMENTATION_MIRROR_MISSING:{source_path}")
|
||||
source_raw = _asset_bytes(root, source_path)
|
||||
mirror_raw = _asset_bytes(root, mirror_path)
|
||||
if source_raw != mirror_raw:
|
||||
raise ReleaseBuildError(f"PYTHON_DOCUMENTATION_MIRROR_MISMATCH:{source_path}")
|
||||
return mirror_path, mirror_raw
|
||||
|
||||
|
||||
def _generic_kind(relative: str) -> str:
|
||||
if relative.startswith("tests/"):
|
||||
return "TEST"
|
||||
if relative.startswith("schemas/"):
|
||||
return "JSON_SCHEMA"
|
||||
if relative.startswith("workflows/"):
|
||||
return "WORKFLOW"
|
||||
if relative.startswith("offline_build/") or relative.startswith("release_ops/"):
|
||||
return "BUILD_ONLY"
|
||||
if relative.startswith("runtime/"):
|
||||
return "RUNTIME_CORE"
|
||||
if relative.startswith("rules/"):
|
||||
return "LEGAL_RULE_SOURCE"
|
||||
if relative.startswith("manifest/"):
|
||||
return "MANIFEST"
|
||||
if relative.startswith("registry/"):
|
||||
return "DECLARATIVE_REGISTRY"
|
||||
return "S2_20_ASSET"
|
||||
|
||||
|
||||
def _generic_module_row(relative: str) -> dict[str, Any]:
|
||||
identity = hashlib.sha256(relative.encode("utf-8")).hexdigest()[:16].upper()
|
||||
return {
|
||||
"asset_version": "s2_20.1",
|
||||
"authority_ids": [],
|
||||
"consumed_schema_ids": [],
|
||||
"dependency_module_ids": [],
|
||||
"forbidden_contract_codes": ["LEGACY-STAGE2-V0-V3"],
|
||||
"implementation_status": "DEV_HASH_BOUND_OFFLINE_ONLY",
|
||||
"incompatible_module_ids": [],
|
||||
"inputs": [],
|
||||
"module_id": f"S2_20-ASSET-{identity}",
|
||||
"module_kind": _generic_kind(relative),
|
||||
"outputs": [],
|
||||
"owner": "Stage_2_S2_20_owner",
|
||||
"path": relative,
|
||||
"produced_schema_ids": [],
|
||||
"schema_version": "stage2_s2_20_generic_asset.v1",
|
||||
"scope_predicate": "workflow_id == S2_20",
|
||||
"semantic_review_status": "PENDING_LEGAL_AND_LIVE_ADMISSION",
|
||||
}
|
||||
|
||||
|
||||
def _refresh_module_row(root: Path, row: dict[str, Any]) -> dict[str, Any]:
|
||||
refreshed = copy.deepcopy(row)
|
||||
relative = _relative_path(refreshed.get("path"))
|
||||
if relative in FORBIDDEN_PARENT_MEMBERS:
|
||||
raise ReleaseBuildError(f"NON_CYCLIC_PARENT_VIOLATION:{relative}")
|
||||
raw = _asset_bytes(root, relative)
|
||||
refreshed["path"] = relative
|
||||
refreshed["sha256"] = _sha256(raw)
|
||||
refreshed["size_bytes"] = len(raw)
|
||||
mirror = _mirror_for(root, relative)
|
||||
if mirror is not None:
|
||||
mirror_path, mirror_raw = mirror
|
||||
refreshed["mirror_path"] = mirror_path
|
||||
refreshed["mirror_sha256"] = _sha256(mirror_raw)
|
||||
refreshed["mirror_size_bytes"] = len(mirror_raw)
|
||||
refreshed["mirror_byte_parity"] = True
|
||||
return refreshed
|
||||
|
||||
|
||||
def _documentation_mirror(row: dict[str, Any]) -> dict[str, Any] | None:
|
||||
source = row.get("path")
|
||||
mirror = row.get("mirror_path")
|
||||
if not isinstance(source, str) or not isinstance(mirror, str):
|
||||
return None
|
||||
return {
|
||||
"byte_identical": True,
|
||||
"mirror_path": mirror,
|
||||
"mirror_sha256": row["mirror_sha256"],
|
||||
"mirror_size_bytes": row["mirror_size_bytes"],
|
||||
"runtime_import_allowed": False,
|
||||
"source_path": source,
|
||||
"source_sha256": row["sha256"],
|
||||
"source_size_bytes": row["size_bytes"],
|
||||
}
|
||||
|
||||
|
||||
def build_module_manifest(
|
||||
root: Path,
|
||||
template: dict[str, Any],
|
||||
explicit_paths: Iterable[str],
|
||||
) -> dict[str, Any]:
|
||||
"""Preserve canonical rows, refresh physical bindings and append S2_20 rows."""
|
||||
|
||||
if template.get("schema_version") != MODULE_MANIFEST_SCHEMA:
|
||||
raise ReleaseBuildError("CANONICAL_MODULE_MANIFEST_V1_REQUIRED")
|
||||
existing = template.get("modules")
|
||||
if not isinstance(existing, list):
|
||||
raise ReleaseBuildError("CANONICAL_MODULES_ARRAY_REQUIRED")
|
||||
|
||||
normalized_explicit = [_relative_path(value) for value in explicit_paths]
|
||||
if len(normalized_explicit) != len(set(normalized_explicit)):
|
||||
raise ReleaseBuildError("PATH_LIST_MUST_BE_UNIQUE_ARRAY")
|
||||
forbidden = sorted(set(normalized_explicit) & FORBIDDEN_PARENT_MEMBERS)
|
||||
if forbidden:
|
||||
raise ReleaseBuildError(f"NON_CYCLIC_PARENT_VIOLATION:{forbidden}")
|
||||
|
||||
result = copy.deepcopy(template)
|
||||
refreshed_rows: list[dict[str, Any]] = []
|
||||
seen_ids: set[str] = set()
|
||||
seen_paths: set[str] = set()
|
||||
for value in existing:
|
||||
if not isinstance(value, dict):
|
||||
raise ReleaseBuildError("MODULE_ROW_OBJECT_REQUIRED")
|
||||
module_id = value.get("module_id")
|
||||
relative = _relative_path(value.get("path"))
|
||||
if not isinstance(module_id, str) or not module_id or module_id in seen_ids:
|
||||
raise ReleaseBuildError(f"MODULE_ID_INVALID_OR_DUPLICATE:{module_id!r}")
|
||||
if relative in seen_paths:
|
||||
raise ReleaseBuildError(f"MODULE_PATH_DUPLICATE:{relative}")
|
||||
seen_ids.add(module_id)
|
||||
seen_paths.add(relative)
|
||||
refreshed_rows.append(_refresh_module_row(root, value))
|
||||
|
||||
explicit_set = set(normalized_explicit)
|
||||
for relative in sorted(normalized_explicit):
|
||||
# A .txt copy is documentation for an explicitly bound .py source, not
|
||||
# a second executable module row.
|
||||
if relative.endswith(".txt"):
|
||||
source = relative[:-4] + ".py"
|
||||
if source in explicit_set or source in seen_paths:
|
||||
_mirror_for(root, source)
|
||||
continue
|
||||
if relative in seen_paths:
|
||||
continue
|
||||
row = _refresh_module_row(root, _generic_module_row(relative))
|
||||
if row["module_id"] in seen_ids:
|
||||
raise ReleaseBuildError(f"GENERATED_MODULE_ID_COLLISION:{row['module_id']}")
|
||||
seen_ids.add(row["module_id"])
|
||||
seen_paths.add(relative)
|
||||
refreshed_rows.append(row)
|
||||
|
||||
result["modules"] = refreshed_rows
|
||||
mirrors = [entry for row in refreshed_rows if (entry := _documentation_mirror(row))]
|
||||
mirror_sources = [str(entry["source_path"]) for entry in mirrors]
|
||||
if len(mirror_sources) != len(set(mirror_sources)):
|
||||
raise ReleaseBuildError("DOCUMENTATION_MIRROR_SOURCE_DUPLICATE")
|
||||
result["documentation_mirrors"] = sorted(
|
||||
mirrors, key=lambda entry: str(entry["source_path"])
|
||||
)
|
||||
|
||||
summary = result.get("closure_summary")
|
||||
if not isinstance(summary, dict):
|
||||
raise ReleaseBuildError("CLOSURE_SUMMARY_REQUIRED")
|
||||
summary.update(
|
||||
{
|
||||
"module_count": len(refreshed_rows),
|
||||
"documentation_mirror_count": len(mirrors),
|
||||
"test_module_count": sum(
|
||||
1 for row in refreshed_rows if row.get("module_kind") == "TEST"
|
||||
),
|
||||
"build_tool_module_count": sum(
|
||||
1 for row in refreshed_rows if row.get("module_kind") == "BUILD_TOOL"
|
||||
),
|
||||
"s2_20_generic_module_count": sum(
|
||||
1
|
||||
for row in refreshed_rows
|
||||
if row.get("schema_version") == "stage2_s2_20_generic_asset.v1"
|
||||
),
|
||||
"executable_agent_hash_included": False,
|
||||
"executor_binding_hash_included": False,
|
||||
"inline_runtime_code_hash_included": False,
|
||||
"self_hash_included": False,
|
||||
"stage2_release_hash_included": False,
|
||||
}
|
||||
)
|
||||
result["manifest_digest"] = _document_digest(result, "manifest_digest")
|
||||
return result
|
||||
|
||||
|
||||
def _refresh_parent_refs(
|
||||
value: Any,
|
||||
root: Path,
|
||||
raw_overrides: dict[str, bytes],
|
||||
location: str = "$",
|
||||
) -> None:
|
||||
if isinstance(value, list):
|
||||
for index, item in enumerate(value):
|
||||
_refresh_parent_refs(item, root, raw_overrides, f"{location}/{index}")
|
||||
return
|
||||
if not isinstance(value, dict):
|
||||
return
|
||||
|
||||
relative = value.get("path")
|
||||
if isinstance(relative, str) and relative and "<" not in relative:
|
||||
try:
|
||||
relative = _relative_path(relative)
|
||||
except ReleaseBuildError:
|
||||
relative = None
|
||||
if relative == "manifest/module_manifest.json":
|
||||
raw = raw_overrides.get(relative)
|
||||
if raw is None:
|
||||
raise ReleaseBuildError("MODULE_MANIFEST_RAW_OVERRIDE_REQUIRED")
|
||||
if "sha256" in value:
|
||||
value["sha256"] = _sha256(raw)
|
||||
if "size_bytes" in value:
|
||||
value["size_bytes"] = len(raw)
|
||||
elif relative in FORBIDDEN_PARENT_MEMBERS:
|
||||
if "sha256" in value or value.get("binding_status") == "BOUND":
|
||||
raise ReleaseBuildError(
|
||||
f"NON_CYCLIC_PARENT_HASH_REFERENCE:{location}:{relative}"
|
||||
)
|
||||
elif relative is not None and (relative in raw_overrides or (root / relative).is_file()):
|
||||
raw = raw_overrides.get(relative)
|
||||
if raw is None:
|
||||
raw = _asset_bytes(root, relative)
|
||||
if "sha256" in value:
|
||||
value["sha256"] = _sha256(raw)
|
||||
if "size_bytes" in value:
|
||||
value["size_bytes"] = len(raw)
|
||||
|
||||
for key, item in list(value.items()):
|
||||
_refresh_parent_refs(item, root, raw_overrides, f"{location}/{key}")
|
||||
|
||||
|
||||
def build_parent_release(
|
||||
root: Path,
|
||||
template: dict[str, Any],
|
||||
module_manifest_raw: bytes,
|
||||
) -> dict[str, Any]:
|
||||
"""Refresh safe parent refs and its self digest without changing admission."""
|
||||
|
||||
if template.get("schema_version") != PARENT_RELEASE_SCHEMA:
|
||||
raise ReleaseBuildError("PARENT_STAGE2_RELEASE_V2_REQUIRED")
|
||||
result = copy.deepcopy(template)
|
||||
immutable_admission = {
|
||||
key: copy.deepcopy(result.get(key))
|
||||
for key in (
|
||||
"release_class",
|
||||
"release_status",
|
||||
"authorization_status",
|
||||
"signature",
|
||||
"release_evidence",
|
||||
)
|
||||
}
|
||||
module_ref = result.get("module_manifest_ref")
|
||||
if not isinstance(module_ref, dict) or module_ref.get("path") != "manifest/module_manifest.json":
|
||||
raise ReleaseBuildError("PARENT_MODULE_MANIFEST_REF_REQUIRED")
|
||||
module_ref["sha256"] = _sha256(module_manifest_raw)
|
||||
if "size_bytes" in module_ref:
|
||||
module_ref["size_bytes"] = len(module_manifest_raw)
|
||||
|
||||
executor_ref = result.get("executor_binding_ref")
|
||||
if (
|
||||
not isinstance(executor_ref, dict)
|
||||
or executor_ref.get("path") != "deployment/stage2_code_executor_binding.yml"
|
||||
):
|
||||
raise ReleaseBuildError("PARENT_EXECUTOR_TRUST_ROOT_REF_REQUIRED")
|
||||
if "sha256" in executor_ref or executor_ref.get("binding_status") == "BOUND":
|
||||
raise ReleaseBuildError("PARENT_EXECUTOR_TRUST_ROOT_MUST_BE_UNHASHED")
|
||||
executor_ref["schema_id"] = "stage2_code_executor_binding.v3"
|
||||
|
||||
_refresh_parent_refs(
|
||||
result,
|
||||
root,
|
||||
{"manifest/module_manifest.json": module_manifest_raw},
|
||||
)
|
||||
bundle = result.get("bundle")
|
||||
if isinstance(bundle, dict):
|
||||
agent_ref = bundle.get("s2_10_agent_ref")
|
||||
binding_ref = bundle.get("s2_10_llm_binding_ref")
|
||||
if isinstance(agent_ref, dict) and isinstance(agent_ref.get("sha256"), str):
|
||||
bundle["s2_10_agent_sha256"] = agent_ref["sha256"]
|
||||
if isinstance(binding_ref, dict) and isinstance(binding_ref.get("sha256"), str):
|
||||
bundle["s2_10_llm_binding_sha256"] = binding_ref["sha256"]
|
||||
|
||||
for key, expected in immutable_admission.items():
|
||||
if result.get(key) != expected:
|
||||
raise ReleaseBuildError(f"ADMISSION_STATE_MUTATION_FORBIDDEN:{key}")
|
||||
result["release_digest"] = _document_digest(result, "release_digest")
|
||||
return result
|
||||
|
||||
|
||||
def build_release_package(
|
||||
root: Path,
|
||||
path_list: list[str],
|
||||
module_template: dict[str, Any],
|
||||
parent_template: dict[str, Any],
|
||||
) -> tuple[dict[str, Any], dict[str, Any]]:
|
||||
module_manifest = build_module_manifest(root, module_template, path_list)
|
||||
module_raw = _canonical_bytes(module_manifest)
|
||||
parent_release = build_parent_release(root, parent_template, module_raw)
|
||||
return module_manifest, parent_release
|
||||
|
||||
|
||||
def _write_bytes(path: Path, raw: bytes) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
temporary = path.with_name(f".{path.name}.tmp-{os.getpid()}")
|
||||
temporary.write_bytes(raw)
|
||||
os.replace(temporary, path)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Reseal canonical Stage-2 module and parent release manifests"
|
||||
)
|
||||
parser.add_argument("root", type=Path)
|
||||
parser.add_argument("path_list", type=Path, help="JSON array of relative S2_20 paths")
|
||||
parser.add_argument("output", type=Path, help="module_manifest.json output")
|
||||
parser.add_argument(
|
||||
"--module-template",
|
||||
type=Path,
|
||||
help="canonical module manifest template (default: ROOT/manifest/module_manifest.json)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--parent-template",
|
||||
type=Path,
|
||||
help="parent release template (default: ROOT/manifest/stage2_release.json)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--parent-output",
|
||||
type=Path,
|
||||
help="parent release output (default: OUTPUT sibling stage2_release.json)",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
path_list = _load_json(args.path_list)
|
||||
if not isinstance(path_list, list) or not all(isinstance(row, str) for row in path_list):
|
||||
raise ReleaseBuildError("PATH_LIST_MUST_BE_STRING_ARRAY")
|
||||
module_template_path = args.module_template or args.root / "manifest/module_manifest.json"
|
||||
parent_template_path = args.parent_template or args.root / "manifest/stage2_release.json"
|
||||
parent_output = args.parent_output or args.output.with_name("stage2_release.json")
|
||||
module_manifest, parent_release = build_release_package(
|
||||
args.root,
|
||||
path_list,
|
||||
_load_json(module_template_path),
|
||||
_load_json(parent_template_path),
|
||||
)
|
||||
_write_bytes(args.output, _canonical_bytes(module_manifest))
|
||||
_write_bytes(parent_output, _canonical_bytes(parent_release))
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"module_count": len(module_manifest["modules"]),
|
||||
"module_manifest_output": args.output.as_posix(),
|
||||
"module_manifest_sha256": _sha256(_canonical_bytes(module_manifest)),
|
||||
"parent_release_output": parent_output.as_posix(),
|
||||
"parent_release_sha256": _sha256(_canonical_bytes(parent_release)),
|
||||
"status": "PASS",
|
||||
},
|
||||
sort_keys=True,
|
||||
separators=(",", ":"),
|
||||
)
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
+62
@@ -33,6 +33,7 @@ PROJECTION_PATH = DEPLOYMENT_ROOT / "agent_scripts" / "Stage_2_S2_00.yml"
|
||||
MIRROR_PY_PATH = DEPLOYMENT_ROOT / "runtime" / "s2_00_ingress.py"
|
||||
MIRROR_TXT_PATH = DEPLOYMENT_ROOT / "runtime" / "s2_00_ingress.txt"
|
||||
RECEIPT_PATH = DEPLOYMENT_ROOT / "manifest" / "s2_00_inline_code_receipt.json"
|
||||
BINDING_PATH = DEPLOYMENT_ROOT / "deployment" / "stage2_code_executor_binding.yml"
|
||||
|
||||
EXPECTED_TASK_NAME = "Task_S2_00_deterministic_ingress"
|
||||
EXPECTED_AGENT_NAME = "Stage_2_S2_00_v2"
|
||||
@@ -207,6 +208,66 @@ def load_authoring(path: Path | None = None) -> tuple[bytes, dict[str, Any]]:
|
||||
return raw, parse_authoring_bytes(raw, source=path.as_posix())
|
||||
|
||||
|
||||
def select_s2_00_binding(document: Mapping[str, Any]) -> dict[str, Any]:
|
||||
"""Select the unique S2_00 row from the shared deterministic-stage binding.
|
||||
|
||||
The v3 wrapper replaces the former singleton v2 document. This selector
|
||||
deliberately checks only migration/identity semantics; byte hashes are
|
||||
resealed after projection generation by the release pipeline.
|
||||
"""
|
||||
|
||||
if document.get("schema_version") != "stage2_code_executor_binding.v3":
|
||||
raise ProjectionError(
|
||||
"EXECUTOR_BINDING_SCHEMA_MISMATCH",
|
||||
repr(document.get("schema_version")),
|
||||
)
|
||||
rows = _require_list(document.get("stage_bindings"), "STAGE_BINDINGS_ARRAY_REQUIRED")
|
||||
stage_ids = [row.get("stage_id") for row in rows if isinstance(row, dict)]
|
||||
if len(stage_ids) != len(rows) or len(set(stage_ids)) != len(stage_ids):
|
||||
raise ProjectionError("STAGE_BINDING_ID_NOT_UNIQUE", repr(stage_ids))
|
||||
matches = [row for row in rows if row.get("stage_id") == "S2_00"]
|
||||
if len(matches) != 1:
|
||||
raise ProjectionError("S2_00_BINDING_EXACT_ONE_REQUIRED", str(len(matches)))
|
||||
row = _require_mapping(matches[0], "S2_00_BINDING_OBJECT_REQUIRED")
|
||||
expected = {
|
||||
"workflow_id": "S2_00",
|
||||
"execution_class": "NON-LLM-DETERMINISTIC",
|
||||
"mcp_server_id": "code-executor",
|
||||
"tool_name": "run_code",
|
||||
"language": "python",
|
||||
"network": "agent-network",
|
||||
}
|
||||
drift = {
|
||||
key: {"expected": value, "observed": row.get(key)}
|
||||
for key, value in expected.items()
|
||||
if row.get(key) != value
|
||||
}
|
||||
if row.get("active_runtime_authority") is not True:
|
||||
drift["active_runtime_authority"] = {
|
||||
"expected": True,
|
||||
"observed": row.get("active_runtime_authority"),
|
||||
}
|
||||
if row.get("external_mcp_contract") is not None:
|
||||
drift["external_mcp_contract"] = {
|
||||
"expected": None,
|
||||
"observed": row.get("external_mcp_contract"),
|
||||
}
|
||||
if drift:
|
||||
raise ProjectionError(
|
||||
"S2_00_BINDING_SEMANTICS_DRIFT",
|
||||
json.dumps(drift, ensure_ascii=False, sort_keys=True),
|
||||
)
|
||||
return row
|
||||
|
||||
|
||||
def load_s2_00_binding(path: Path | None = None) -> dict[str, Any]:
|
||||
path = BINDING_PATH if path is None else path
|
||||
if not path.is_file() or path.is_symlink():
|
||||
raise ProjectionError("EXECUTOR_BINDING_UNAVAILABLE", path.as_posix())
|
||||
wrapper = parse_authoring_bytes(path.read_bytes(), source=path.as_posix())
|
||||
return select_s2_00_binding(wrapper)
|
||||
|
||||
|
||||
def _require_mapping(value: Any, code: str) -> dict[str, Any]:
|
||||
if not isinstance(value, dict):
|
||||
raise ProjectionError(code, repr(value)[:200])
|
||||
@@ -738,6 +799,7 @@ def compare_outputs(outputs: Mapping[Path, bytes]) -> list[dict[str, Any]]:
|
||||
|
||||
|
||||
def run(*, check: bool) -> tuple[int, dict[str, Any]]:
|
||||
load_s2_00_binding()
|
||||
before, document = load_authoring()
|
||||
outputs, receipt = expected_outputs(before, document)
|
||||
if check:
|
||||
|
||||
+62
@@ -33,6 +33,7 @@ PROJECTION_PATH = DEPLOYMENT_ROOT / "agent_scripts" / "Stage_2_S2_00.yml"
|
||||
MIRROR_PY_PATH = DEPLOYMENT_ROOT / "runtime" / "s2_00_ingress.py"
|
||||
MIRROR_TXT_PATH = DEPLOYMENT_ROOT / "runtime" / "s2_00_ingress.txt"
|
||||
RECEIPT_PATH = DEPLOYMENT_ROOT / "manifest" / "s2_00_inline_code_receipt.json"
|
||||
BINDING_PATH = DEPLOYMENT_ROOT / "deployment" / "stage2_code_executor_binding.yml"
|
||||
|
||||
EXPECTED_TASK_NAME = "Task_S2_00_deterministic_ingress"
|
||||
EXPECTED_AGENT_NAME = "Stage_2_S2_00_v2"
|
||||
@@ -207,6 +208,66 @@ def load_authoring(path: Path | None = None) -> tuple[bytes, dict[str, Any]]:
|
||||
return raw, parse_authoring_bytes(raw, source=path.as_posix())
|
||||
|
||||
|
||||
def select_s2_00_binding(document: Mapping[str, Any]) -> dict[str, Any]:
|
||||
"""Select the unique S2_00 row from the shared deterministic-stage binding.
|
||||
|
||||
The v3 wrapper replaces the former singleton v2 document. This selector
|
||||
deliberately checks only migration/identity semantics; byte hashes are
|
||||
resealed after projection generation by the release pipeline.
|
||||
"""
|
||||
|
||||
if document.get("schema_version") != "stage2_code_executor_binding.v3":
|
||||
raise ProjectionError(
|
||||
"EXECUTOR_BINDING_SCHEMA_MISMATCH",
|
||||
repr(document.get("schema_version")),
|
||||
)
|
||||
rows = _require_list(document.get("stage_bindings"), "STAGE_BINDINGS_ARRAY_REQUIRED")
|
||||
stage_ids = [row.get("stage_id") for row in rows if isinstance(row, dict)]
|
||||
if len(stage_ids) != len(rows) or len(set(stage_ids)) != len(stage_ids):
|
||||
raise ProjectionError("STAGE_BINDING_ID_NOT_UNIQUE", repr(stage_ids))
|
||||
matches = [row for row in rows if row.get("stage_id") == "S2_00"]
|
||||
if len(matches) != 1:
|
||||
raise ProjectionError("S2_00_BINDING_EXACT_ONE_REQUIRED", str(len(matches)))
|
||||
row = _require_mapping(matches[0], "S2_00_BINDING_OBJECT_REQUIRED")
|
||||
expected = {
|
||||
"workflow_id": "S2_00",
|
||||
"execution_class": "NON-LLM-DETERMINISTIC",
|
||||
"mcp_server_id": "code-executor",
|
||||
"tool_name": "run_code",
|
||||
"language": "python",
|
||||
"network": "agent-network",
|
||||
}
|
||||
drift = {
|
||||
key: {"expected": value, "observed": row.get(key)}
|
||||
for key, value in expected.items()
|
||||
if row.get(key) != value
|
||||
}
|
||||
if row.get("active_runtime_authority") is not True:
|
||||
drift["active_runtime_authority"] = {
|
||||
"expected": True,
|
||||
"observed": row.get("active_runtime_authority"),
|
||||
}
|
||||
if row.get("external_mcp_contract") is not None:
|
||||
drift["external_mcp_contract"] = {
|
||||
"expected": None,
|
||||
"observed": row.get("external_mcp_contract"),
|
||||
}
|
||||
if drift:
|
||||
raise ProjectionError(
|
||||
"S2_00_BINDING_SEMANTICS_DRIFT",
|
||||
json.dumps(drift, ensure_ascii=False, sort_keys=True),
|
||||
)
|
||||
return row
|
||||
|
||||
|
||||
def load_s2_00_binding(path: Path | None = None) -> dict[str, Any]:
|
||||
path = BINDING_PATH if path is None else path
|
||||
if not path.is_file() or path.is_symlink():
|
||||
raise ProjectionError("EXECUTOR_BINDING_UNAVAILABLE", path.as_posix())
|
||||
wrapper = parse_authoring_bytes(path.read_bytes(), source=path.as_posix())
|
||||
return select_s2_00_binding(wrapper)
|
||||
|
||||
|
||||
def _require_mapping(value: Any, code: str) -> dict[str, Any]:
|
||||
if not isinstance(value, dict):
|
||||
raise ProjectionError(code, repr(value)[:200])
|
||||
@@ -738,6 +799,7 @@ def compare_outputs(outputs: Mapping[Path, bytes]) -> list[dict[str, Any]]:
|
||||
|
||||
|
||||
def run(*, check: bool) -> tuple[int, dict[str, Any]]:
|
||||
load_s2_00_binding()
|
||||
before, document = load_authoring()
|
||||
outputs, receipt = expected_outputs(before, document)
|
||||
if check:
|
||||
|
||||
+128
@@ -1265,15 +1265,143 @@ def check() -> dict[str, Any]:
|
||||
}
|
||||
|
||||
|
||||
def child_only_outputs() -> tuple[dict[Path, bytes], dict[str, Any]]:
|
||||
"""Reseal only the downstream S2_10 child chain against a frozen parent.
|
||||
|
||||
The canonical parent/module builder owns the raw parent closure. This mode
|
||||
therefore never rewrites ``module_manifest.json``, ``stage2_release.json``,
|
||||
the Agent projection, prompt receipt, or LLM binding.
|
||||
"""
|
||||
|
||||
authoring_raw = _require_file(AUTHORING_PATH)
|
||||
if _require_file(PROJECTION_PATH) != authoring_raw:
|
||||
raise BuildError("S2_10_AGENT_PROJECTION_DRIFT", _logical_path(PROJECTION_PATH))
|
||||
contract = extract_agent_contract(_load_yaml(authoring_raw, _logical_path(AUTHORING_PATH)))
|
||||
binding_raw = _require_file(BINDING_PATH)
|
||||
binding = _load_yaml(binding_raw, _logical_path(BINDING_PATH))
|
||||
inline_receipt_raw = _require_file(INLINE_RECEIPT_PATH)
|
||||
parent_raw = _require_file(PARENT_RELEASE_PATH)
|
||||
child = build_child_release(parent_raw, binding)
|
||||
child_raw = canonical_json_bytes(child)
|
||||
outputs = {
|
||||
CHILD_RELEASE_PATH: child_raw,
|
||||
AGENT_RECEIPT_PATH: canonical_json_bytes(
|
||||
build_agent_receipt(authoring_raw, binding_raw, child_raw, contract)
|
||||
),
|
||||
PLATFORM_RECEIPT_PATH: canonical_json_bytes(
|
||||
build_platform_receipt(
|
||||
parent_raw=parent_raw, binding_raw=binding_raw, child_raw=child_raw
|
||||
)
|
||||
),
|
||||
MODEL_RECEIPT_PATH: canonical_json_bytes(
|
||||
build_model_receipt(binding_raw=binding_raw, child_raw=child_raw)
|
||||
),
|
||||
LEGAL_RECEIPT_PATH: canonical_json_bytes(
|
||||
build_legal_receipt(
|
||||
binding_raw=binding_raw,
|
||||
child_raw=child_raw,
|
||||
inline_receipt_raw=inline_receipt_raw,
|
||||
)
|
||||
),
|
||||
}
|
||||
return outputs, {
|
||||
"child_release_sha256": sha256_bytes(child_raw),
|
||||
"parent_release_sha256": sha256_bytes(parent_raw),
|
||||
}
|
||||
|
||||
|
||||
def prerequisite_outputs() -> tuple[dict[Path, bytes], dict[str, Any]]:
|
||||
"""Build only the parent-owned S2_10 projection prerequisites."""
|
||||
|
||||
authoring_raw = _require_file(AUTHORING_PATH)
|
||||
contract = extract_agent_contract(_load_yaml(authoring_raw, _logical_path(AUTHORING_PATH)))
|
||||
inline_receipt = build_inline_receipt(authoring_raw, contract)
|
||||
inline_receipt_raw = canonical_json_bytes(inline_receipt)
|
||||
binding, producer_material = build_binding(authoring_raw, contract, inline_receipt_raw)
|
||||
return {
|
||||
PROJECTION_PATH: authoring_raw,
|
||||
INLINE_RECEIPT_PATH: inline_receipt_raw,
|
||||
BINDING_PATH: _yaml_bytes(binding),
|
||||
}, {
|
||||
"producer_contract_material": producer_material,
|
||||
"producer_contract_digest": binding["producer_contract"]["s2_10_producer_contract_digest"],
|
||||
}
|
||||
|
||||
|
||||
def prerequisites_only(*, verify: bool) -> dict[str, Any]:
|
||||
outputs, report = prerequisite_outputs()
|
||||
drift: list[dict[str, Any]] = []
|
||||
for path, expected in outputs.items():
|
||||
observed = path.read_bytes() if path.is_file() and not path.is_symlink() else None
|
||||
if observed != expected:
|
||||
drift.append(
|
||||
{
|
||||
"path": _logical_path(path),
|
||||
"expected_sha256": sha256_bytes(expected),
|
||||
"observed_sha256": sha256_bytes(observed) if observed is not None else None,
|
||||
}
|
||||
)
|
||||
if not verify:
|
||||
_atomic_write(path, expected)
|
||||
if verify and drift:
|
||||
raise BuildError("S2_10_PREREQUISITE_DRIFT", json.dumps(drift, ensure_ascii=False))
|
||||
return {
|
||||
"status": "S2_10_PREREQUISITE_CHECK_PASS" if verify else "S2_10_PREREQUISITES_REBUILT",
|
||||
"paths": [_logical_path(path) for path in outputs],
|
||||
**report,
|
||||
}
|
||||
|
||||
|
||||
def child_only(*, verify: bool) -> dict[str, Any]:
|
||||
outputs, report = child_only_outputs()
|
||||
drift: list[dict[str, Any]] = []
|
||||
for path, expected in outputs.items():
|
||||
observed = path.read_bytes() if path.is_file() and not path.is_symlink() else None
|
||||
if observed != expected:
|
||||
drift.append(
|
||||
{
|
||||
"path": _logical_path(path),
|
||||
"expected_sha256": sha256_bytes(expected),
|
||||
"observed_sha256": sha256_bytes(observed) if observed is not None else None,
|
||||
}
|
||||
)
|
||||
if not verify:
|
||||
_atomic_write(path, expected)
|
||||
if verify and drift:
|
||||
raise BuildError("S2_10_CHILD_CHAIN_DRIFT", json.dumps(drift, ensure_ascii=False))
|
||||
return {
|
||||
"status": "S2_10_CHILD_CHAIN_CHECK_PASS" if verify else "S2_10_CHILD_CHAIN_RESEALED",
|
||||
"paths": [_logical_path(path) for path in outputs],
|
||||
**report,
|
||||
}
|
||||
|
||||
|
||||
def parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--check", action="store_true", help="verify without writing")
|
||||
parser.add_argument(
|
||||
"--child-only",
|
||||
action="store_true",
|
||||
help="reseal/check only child release and dependent receipts against the frozen parent",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--prerequisites-only",
|
||||
action="store_true",
|
||||
help="rebuild/check only Agent projection, inline prompt receipt, and LLM binding",
|
||||
)
|
||||
return parser.parse_args(argv)
|
||||
|
||||
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
args = parse_args(argv)
|
||||
try:
|
||||
if args.child_only and args.prerequisites_only:
|
||||
raise BuildError("BUILD_MODE_CONFLICT", "choose only one bounded reseal mode")
|
||||
if args.child_only:
|
||||
result = child_only(verify=args.check)
|
||||
elif args.prerequisites_only:
|
||||
result = prerequisites_only(verify=args.check)
|
||||
else:
|
||||
result = check() if args.check else build()
|
||||
except BuildError as exc:
|
||||
print(json.dumps({"status": "FAIL", "code": exc.code, "detail": exc.detail}, ensure_ascii=False))
|
||||
|
||||
+128
@@ -1265,15 +1265,143 @@ def check() -> dict[str, Any]:
|
||||
}
|
||||
|
||||
|
||||
def child_only_outputs() -> tuple[dict[Path, bytes], dict[str, Any]]:
|
||||
"""Reseal only the downstream S2_10 child chain against a frozen parent.
|
||||
|
||||
The canonical parent/module builder owns the raw parent closure. This mode
|
||||
therefore never rewrites ``module_manifest.json``, ``stage2_release.json``,
|
||||
the Agent projection, prompt receipt, or LLM binding.
|
||||
"""
|
||||
|
||||
authoring_raw = _require_file(AUTHORING_PATH)
|
||||
if _require_file(PROJECTION_PATH) != authoring_raw:
|
||||
raise BuildError("S2_10_AGENT_PROJECTION_DRIFT", _logical_path(PROJECTION_PATH))
|
||||
contract = extract_agent_contract(_load_yaml(authoring_raw, _logical_path(AUTHORING_PATH)))
|
||||
binding_raw = _require_file(BINDING_PATH)
|
||||
binding = _load_yaml(binding_raw, _logical_path(BINDING_PATH))
|
||||
inline_receipt_raw = _require_file(INLINE_RECEIPT_PATH)
|
||||
parent_raw = _require_file(PARENT_RELEASE_PATH)
|
||||
child = build_child_release(parent_raw, binding)
|
||||
child_raw = canonical_json_bytes(child)
|
||||
outputs = {
|
||||
CHILD_RELEASE_PATH: child_raw,
|
||||
AGENT_RECEIPT_PATH: canonical_json_bytes(
|
||||
build_agent_receipt(authoring_raw, binding_raw, child_raw, contract)
|
||||
),
|
||||
PLATFORM_RECEIPT_PATH: canonical_json_bytes(
|
||||
build_platform_receipt(
|
||||
parent_raw=parent_raw, binding_raw=binding_raw, child_raw=child_raw
|
||||
)
|
||||
),
|
||||
MODEL_RECEIPT_PATH: canonical_json_bytes(
|
||||
build_model_receipt(binding_raw=binding_raw, child_raw=child_raw)
|
||||
),
|
||||
LEGAL_RECEIPT_PATH: canonical_json_bytes(
|
||||
build_legal_receipt(
|
||||
binding_raw=binding_raw,
|
||||
child_raw=child_raw,
|
||||
inline_receipt_raw=inline_receipt_raw,
|
||||
)
|
||||
),
|
||||
}
|
||||
return outputs, {
|
||||
"child_release_sha256": sha256_bytes(child_raw),
|
||||
"parent_release_sha256": sha256_bytes(parent_raw),
|
||||
}
|
||||
|
||||
|
||||
def prerequisite_outputs() -> tuple[dict[Path, bytes], dict[str, Any]]:
|
||||
"""Build only the parent-owned S2_10 projection prerequisites."""
|
||||
|
||||
authoring_raw = _require_file(AUTHORING_PATH)
|
||||
contract = extract_agent_contract(_load_yaml(authoring_raw, _logical_path(AUTHORING_PATH)))
|
||||
inline_receipt = build_inline_receipt(authoring_raw, contract)
|
||||
inline_receipt_raw = canonical_json_bytes(inline_receipt)
|
||||
binding, producer_material = build_binding(authoring_raw, contract, inline_receipt_raw)
|
||||
return {
|
||||
PROJECTION_PATH: authoring_raw,
|
||||
INLINE_RECEIPT_PATH: inline_receipt_raw,
|
||||
BINDING_PATH: _yaml_bytes(binding),
|
||||
}, {
|
||||
"producer_contract_material": producer_material,
|
||||
"producer_contract_digest": binding["producer_contract"]["s2_10_producer_contract_digest"],
|
||||
}
|
||||
|
||||
|
||||
def prerequisites_only(*, verify: bool) -> dict[str, Any]:
|
||||
outputs, report = prerequisite_outputs()
|
||||
drift: list[dict[str, Any]] = []
|
||||
for path, expected in outputs.items():
|
||||
observed = path.read_bytes() if path.is_file() and not path.is_symlink() else None
|
||||
if observed != expected:
|
||||
drift.append(
|
||||
{
|
||||
"path": _logical_path(path),
|
||||
"expected_sha256": sha256_bytes(expected),
|
||||
"observed_sha256": sha256_bytes(observed) if observed is not None else None,
|
||||
}
|
||||
)
|
||||
if not verify:
|
||||
_atomic_write(path, expected)
|
||||
if verify and drift:
|
||||
raise BuildError("S2_10_PREREQUISITE_DRIFT", json.dumps(drift, ensure_ascii=False))
|
||||
return {
|
||||
"status": "S2_10_PREREQUISITE_CHECK_PASS" if verify else "S2_10_PREREQUISITES_REBUILT",
|
||||
"paths": [_logical_path(path) for path in outputs],
|
||||
**report,
|
||||
}
|
||||
|
||||
|
||||
def child_only(*, verify: bool) -> dict[str, Any]:
|
||||
outputs, report = child_only_outputs()
|
||||
drift: list[dict[str, Any]] = []
|
||||
for path, expected in outputs.items():
|
||||
observed = path.read_bytes() if path.is_file() and not path.is_symlink() else None
|
||||
if observed != expected:
|
||||
drift.append(
|
||||
{
|
||||
"path": _logical_path(path),
|
||||
"expected_sha256": sha256_bytes(expected),
|
||||
"observed_sha256": sha256_bytes(observed) if observed is not None else None,
|
||||
}
|
||||
)
|
||||
if not verify:
|
||||
_atomic_write(path, expected)
|
||||
if verify and drift:
|
||||
raise BuildError("S2_10_CHILD_CHAIN_DRIFT", json.dumps(drift, ensure_ascii=False))
|
||||
return {
|
||||
"status": "S2_10_CHILD_CHAIN_CHECK_PASS" if verify else "S2_10_CHILD_CHAIN_RESEALED",
|
||||
"paths": [_logical_path(path) for path in outputs],
|
||||
**report,
|
||||
}
|
||||
|
||||
|
||||
def parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--check", action="store_true", help="verify without writing")
|
||||
parser.add_argument(
|
||||
"--child-only",
|
||||
action="store_true",
|
||||
help="reseal/check only child release and dependent receipts against the frozen parent",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--prerequisites-only",
|
||||
action="store_true",
|
||||
help="rebuild/check only Agent projection, inline prompt receipt, and LLM binding",
|
||||
)
|
||||
return parser.parse_args(argv)
|
||||
|
||||
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
args = parse_args(argv)
|
||||
try:
|
||||
if args.child_only and args.prerequisites_only:
|
||||
raise BuildError("BUILD_MODE_CONFLICT", "choose only one bounded reseal mode")
|
||||
if args.child_only:
|
||||
result = child_only(verify=args.check)
|
||||
elif args.prerequisites_only:
|
||||
result = prerequisites_only(verify=args.check)
|
||||
else:
|
||||
result = check() if args.check else build()
|
||||
except BuildError as exc:
|
||||
print(json.dumps({"status": "FAIL", "code": exc.code, "detail": exc.detail}, ensure_ascii=False))
|
||||
|
||||
+665
@@ -0,0 +1,665 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build/check the S2_20 single-task inline Python projection.
|
||||
|
||||
The authoring YAML is the sole editable source. This offline tool rejects
|
||||
duplicate YAML keys, validates the exact AgentBackend task graph and MCP
|
||||
``run_code`` contract, compiles and audits parser-returned inline Python, then
|
||||
projects authoring/code bytes without transformation. It never executes a
|
||||
matter or imports Stage 2 runtime modules.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import ast
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import sys
|
||||
import tempfile
|
||||
from typing import Any, Iterable, Mapping
|
||||
|
||||
try:
|
||||
import yaml
|
||||
except ImportError: # pragma: no cover - incomplete offline build host
|
||||
yaml = None # type: ignore[assignment]
|
||||
|
||||
|
||||
DEPLOYMENT_ROOT = Path(__file__).resolve().parents[1]
|
||||
MAIN_WORKING_DIRECTORY = DEPLOYMENT_ROOT.parent.parent
|
||||
AUTHORING_PATH = MAIN_WORKING_DIRECTORY / "Stage_2_S2_20.yml"
|
||||
PROJECTION_PATH = DEPLOYMENT_ROOT / "agent_scripts/Stage_2_S2_20.yml"
|
||||
MIRROR_PY_PATH = DEPLOYMENT_ROOT / "runtime/s2_20_reduce.py"
|
||||
MIRROR_TXT_PATH = DEPLOYMENT_ROOT / "runtime/s2_20_reduce.txt"
|
||||
RECEIPT_PATH = DEPLOYMENT_ROOT / "manifest/s2_20_inline_code_receipt.json"
|
||||
|
||||
EXPECTED_AGENT_NAME = "Stage_2_S2_20"
|
||||
EXPECTED_AGENT_VERSION = "1.0.0"
|
||||
EXPECTED_STAGE_NAME = "S2_20"
|
||||
EXPECTED_TASK_NAME = "Task_S2_20_deterministic_relief_plan"
|
||||
EXPECTED_PARAMETERS = {
|
||||
"language": "python",
|
||||
"requirements": "httpx==0.28.1",
|
||||
"network": "agent-network",
|
||||
"timeout": 300,
|
||||
}
|
||||
EXPECTED_LOCALDOCS_URL = "http://mcp-localdocs:8012/mcp"
|
||||
EXPECTED_CODE_EXECUTOR_URL = "https://code-executor.mcp.eroomai.com/mcp"
|
||||
EXPECTED_WEAVIATE_URL = "https://weaviate.eroomai.com/mcp"
|
||||
ALLOWED_CODE_URLS = frozenset({EXPECTED_LOCALDOCS_URL, EXPECTED_WEAVIATE_URL})
|
||||
ALLOWED_NON_STDLIB_IMPORTS = frozenset({"httpx"})
|
||||
FORBIDDEN_IMPORT_ROOTS = frozenset(
|
||||
{"ftplib", "http", "importlib", "smtplib", "socket", "subprocess", "telnetlib", "urllib", "xmlrpc"}
|
||||
)
|
||||
FORBIDDEN_CALL_NAMES = frozenset({"__import__", "compile", "eval", "exec", "open"})
|
||||
FORBIDDEN_ATTRIBUTE_CALLS = frozenset(
|
||||
{
|
||||
("os", "popen"),
|
||||
("os", "system"),
|
||||
("os", "spawnl"),
|
||||
("os", "spawnle"),
|
||||
("os", "spawnlp"),
|
||||
("os", "spawnlpe"),
|
||||
("os", "spawnv"),
|
||||
("os", "spawnve"),
|
||||
("os", "spawnvp"),
|
||||
("os", "spawnvpe"),
|
||||
}
|
||||
)
|
||||
MODEL_FIELDS = frozenset({"llm_provider", "llm_model", "llm_reasoning", "llm_verbosity", "prompt"})
|
||||
REQUIRED_CODE_TOKENS = (
|
||||
EXPECTED_LOCALDOCS_URL,
|
||||
EXPECTED_WEAVIATE_URL,
|
||||
"EXPECTED_STAGE2_RELEASE_SHA256",
|
||||
"{{__user_hash__}}",
|
||||
"{{__workspace_hash__}}",
|
||||
"read_binary_doc",
|
||||
"write_binary_file",
|
||||
"plan_publish_status.json",
|
||||
"build_portfolio",
|
||||
"build_party_projection",
|
||||
"calculate_all",
|
||||
"build_retrieval_branch",
|
||||
"execute_core",
|
||||
"publish",
|
||||
"publish_diagnostic",
|
||||
)
|
||||
URL_RE = re.compile(r"https?://[^\s'\"]+")
|
||||
PYTHON_SOURCE_REF_RE = re.compile(r"(?i)(?:^|[/\\])[^\r\n'\"]+\.py(?:$|[?#])")
|
||||
PLACEHOLDER_COMMENT_RE = re.compile(
|
||||
r"(?im)^\s*#\s*(?:TODO|FIXME|TBD|PLACEHOLDER|OMITTED\s+BODY|IMPLEMENT\s+ME)\b"
|
||||
)
|
||||
PLAINTEXT_SECRET_RES = (
|
||||
re.compile(r"(?i)\bAuthorization\s*:\s*Bearer\s+\S+"),
|
||||
re.compile(r"(?i)\bBearer\s+[A-Za-z0-9+/=_-]{12,}"),
|
||||
re.compile(
|
||||
r"(?i)\b(?:MCP_API_KEY|API_KEY|ACCESS_TOKEN|AUTH_TOKEN|CLIENT_SECRET)\s*=\s*['\"][^'\"]+['\"]"
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class ProjectionError(RuntimeError):
|
||||
"""Controlled build failure with a stable reason code."""
|
||||
|
||||
def __init__(self, code: str, detail: str) -> None:
|
||||
super().__init__(f"{code}: {detail}")
|
||||
self.code = code
|
||||
self.detail = detail
|
||||
|
||||
|
||||
if yaml is not None:
|
||||
|
||||
class UniqueKeySafeLoader(yaml.SafeLoader):
|
||||
"""SafeLoader variant that rejects duplicate mapping keys recursively."""
|
||||
|
||||
def construct_mapping(self, node: Any, deep: bool = False) -> dict[Any, Any]:
|
||||
if not isinstance(node, yaml.MappingNode):
|
||||
raise ProjectionError("YAML_MAPPING_REQUIRED", repr(node)[:200])
|
||||
self.flatten_mapping(node)
|
||||
result: dict[Any, Any] = {}
|
||||
for key_node, value_node in node.value:
|
||||
key = self.construct_object(key_node, deep=deep)
|
||||
try:
|
||||
duplicate = key in result
|
||||
except TypeError as exc:
|
||||
raise ProjectionError("YAML_UNHASHABLE_KEY", repr(key)[:200]) from exc
|
||||
if duplicate:
|
||||
mark = getattr(key_node, "start_mark", None)
|
||||
line = mark.line + 1 if mark is not None else "?"
|
||||
raise ProjectionError("YAML_DUPLICATE_KEY", f"{key!r}@line={line}")
|
||||
result[key] = self.construct_object(value_node, deep=deep)
|
||||
return result
|
||||
|
||||
else: # pragma: no cover
|
||||
|
||||
class UniqueKeySafeLoader: # type: ignore[no-redef]
|
||||
pass
|
||||
|
||||
|
||||
def sha256_bytes(value: bytes) -> str:
|
||||
return hashlib.sha256(value).hexdigest()
|
||||
|
||||
|
||||
def canonical_json_bytes(value: Any) -> bytes:
|
||||
return (
|
||||
json.dumps(value, ensure_ascii=False, allow_nan=False, sort_keys=True, separators=(",", ":"))
|
||||
+ "\n"
|
||||
).encode("utf-8")
|
||||
|
||||
|
||||
def _logical_path(path: Path) -> str:
|
||||
try:
|
||||
return path.resolve(strict=False).relative_to(MAIN_WORKING_DIRECTORY.resolve(strict=False)).as_posix()
|
||||
except ValueError:
|
||||
return path.as_posix()
|
||||
|
||||
|
||||
def _require_mapping(value: Any, code: str) -> dict[str, Any]:
|
||||
if not isinstance(value, dict):
|
||||
raise ProjectionError(code, repr(value)[:300])
|
||||
return value
|
||||
|
||||
|
||||
def _require_list(value: Any, code: str) -> list[Any]:
|
||||
if not isinstance(value, list):
|
||||
raise ProjectionError(code, repr(value)[:300])
|
||||
return value
|
||||
|
||||
|
||||
def parse_authoring_bytes(raw: bytes, *, source: str = "<authoring>") -> dict[str, Any]:
|
||||
if yaml is None:
|
||||
raise ProjectionError("PYYAML_REQUIRED", "install PyYAML on offline build host")
|
||||
try:
|
||||
text = raw.decode("utf-8")
|
||||
except UnicodeDecodeError as exc:
|
||||
raise ProjectionError("AUTHORING_UTF8_REQUIRED", f"{source}: {exc}") from exc
|
||||
if text.startswith("\ufeff"):
|
||||
raise ProjectionError("AUTHORING_UTF8_BOM_FORBIDDEN", source)
|
||||
for pattern in PLAINTEXT_SECRET_RES:
|
||||
if pattern.search(text):
|
||||
raise ProjectionError("AUTHORING_PLAINTEXT_SECRET", pattern.pattern)
|
||||
try:
|
||||
document = yaml.load(text, Loader=UniqueKeySafeLoader)
|
||||
except ProjectionError:
|
||||
raise
|
||||
except yaml.YAMLError as exc:
|
||||
raise ProjectionError("AUTHORING_YAML_INVALID", f"{source}: {exc}") from exc
|
||||
if not isinstance(document, dict):
|
||||
raise ProjectionError("AUTHORING_ROOT_OBJECT_REQUIRED", source)
|
||||
return document
|
||||
|
||||
|
||||
def load_authoring(path: Path | None = None) -> tuple[bytes, dict[str, Any]]:
|
||||
selected = AUTHORING_PATH if path is None else path
|
||||
if not selected.is_file() or selected.is_symlink():
|
||||
raise ProjectionError("AUTHORING_YAML_MISSING_OR_SYMLINK", selected.as_posix())
|
||||
raw = selected.read_bytes()
|
||||
return raw, parse_authoring_bytes(raw, source=selected.as_posix())
|
||||
|
||||
|
||||
def _find_forbidden_model_fields(value: Any, *, pointer: str = "") -> list[str]:
|
||||
findings: list[str] = []
|
||||
if isinstance(value, dict):
|
||||
for key, child in value.items():
|
||||
child_pointer = f"{pointer}/{key}"
|
||||
if key in MODEL_FIELDS:
|
||||
findings.append(child_pointer)
|
||||
findings.extend(_find_forbidden_model_fields(child, pointer=child_pointer))
|
||||
elif isinstance(value, list):
|
||||
for index, child in enumerate(value):
|
||||
findings.extend(_find_forbidden_model_fields(child, pointer=f"{pointer}/{index}"))
|
||||
return findings
|
||||
|
||||
|
||||
def extract_run_code_task(document: Mapping[str, Any]) -> tuple[dict[str, Any], dict[str, Any]]:
|
||||
agent = _require_mapping(document.get("Agent"), "AGENT_OBJECT_REQUIRED")
|
||||
if agent.get("name") != EXPECTED_AGENT_NAME or agent.get("version") != EXPECTED_AGENT_VERSION:
|
||||
raise ProjectionError(
|
||||
"AGENT_IDENTITY_MISMATCH",
|
||||
f"expected={EXPECTED_AGENT_NAME}/{EXPECTED_AGENT_VERSION} observed={agent.get('name')}/{agent.get('version')}",
|
||||
)
|
||||
forbidden_model_fields = _find_forbidden_model_fields(agent)
|
||||
if forbidden_model_fields:
|
||||
raise ProjectionError("LLM_OR_PROMPT_FIELD_FORBIDDEN", repr(forbidden_model_fields))
|
||||
stages = _require_list(agent.get("Stages"), "STAGES_ARRAY_REQUIRED")
|
||||
if len(stages) != 1:
|
||||
raise ProjectionError("EXACTLY_ONE_STAGE_REQUIRED", str(len(stages)))
|
||||
stage = _require_mapping(stages[0], "STAGE_OBJECT_REQUIRED")
|
||||
if stage.get("name") != EXPECTED_STAGE_NAME:
|
||||
raise ProjectionError("S2_20_STAGE_NAME_REQUIRED", repr(stage.get("name")))
|
||||
if stage.get("skip_confirm") is not True:
|
||||
raise ProjectionError("S2_20_SKIP_CONFIRM_TRUE_REQUIRED", repr(stage.get("skip_confirm")))
|
||||
if stage.get("prevs") != [] or stage.get("nexts") != []:
|
||||
raise ProjectionError("STANDALONE_STAGE_EDGES_MUST_BE_EMPTY", repr(stage))
|
||||
|
||||
servers = _require_mapping(
|
||||
_require_mapping(stage.get("tools"), "TOOLS_OBJECT_REQUIRED").get("mcpServers"),
|
||||
"MCP_SERVERS_OBJECT_REQUIRED",
|
||||
)
|
||||
if set(servers) != {"localdocs", "code-executor"}:
|
||||
raise ProjectionError("MCP_SERVER_SET_MISMATCH", repr(sorted(servers)))
|
||||
expected_servers = {
|
||||
"localdocs": {"type": "streamable-http", "url": EXPECTED_LOCALDOCS_URL},
|
||||
"code-executor": {"type": "streamable-http", "url": EXPECTED_CODE_EXECUTOR_URL},
|
||||
}
|
||||
for name, expected in expected_servers.items():
|
||||
observed = _require_mapping(servers.get(name), f"{name.upper()}_SERVER_REQUIRED")
|
||||
if observed != expected:
|
||||
raise ProjectionError("MCP_SERVER_BINDING_INVALID", f"{name}:{observed!r}")
|
||||
|
||||
tasks = _require_list(stage.get("tasks"), "TASKS_ARRAY_REQUIRED")
|
||||
if len(tasks) != 1:
|
||||
raise ProjectionError("EXACTLY_ONE_TASK_REQUIRED", str(len(tasks)))
|
||||
task = _require_mapping(tasks[0], "TASK_OBJECT_REQUIRED")
|
||||
run_code_tasks = [
|
||||
value
|
||||
for candidate_stage in stages
|
||||
for value in _require_list(
|
||||
_require_mapping(candidate_stage, "STAGE_OBJECT_REQUIRED").get("tasks"),
|
||||
"TASKS_ARRAY_REQUIRED",
|
||||
)
|
||||
if isinstance(value, dict)
|
||||
and value.get("mcp") == "code-executor"
|
||||
and value.get("tool_name") == "run_code"
|
||||
]
|
||||
if len(run_code_tasks) != 1:
|
||||
raise ProjectionError("EXACTLY_ONE_CODE_EXECUTOR_RUN_CODE_REQUIRED", str(len(run_code_tasks)))
|
||||
if task.get("task_name") != EXPECTED_TASK_NAME:
|
||||
raise ProjectionError("TASK_NAME_MISMATCH", repr(task.get("task_name")))
|
||||
if task.get("mcp") != "code-executor" or task.get("tool_name") != "run_code":
|
||||
raise ProjectionError("RUN_CODE_TASK_BINDING_MISMATCH", repr(task))
|
||||
parameters = _require_mapping(task.get("parameters"), "TASK_PARAMETERS_REQUIRED")
|
||||
if set(parameters) != set(EXPECTED_PARAMETERS) | {"code"}:
|
||||
raise ProjectionError("RUN_CODE_PARAMETER_SET_MISMATCH", repr(sorted(parameters)))
|
||||
for key, expected in EXPECTED_PARAMETERS.items():
|
||||
if parameters.get(key) != expected:
|
||||
raise ProjectionError(
|
||||
"RUN_CODE_PARAMETER_MISMATCH", f"{key}: expected={expected!r} observed={parameters.get(key)!r}"
|
||||
)
|
||||
code = parameters.get("code")
|
||||
if not isinstance(code, str) or not code:
|
||||
raise ProjectionError("INLINE_CODE_REQUIRED", repr(code)[:200])
|
||||
if code.endswith(("\n", "\r")):
|
||||
raise ProjectionError(
|
||||
"INLINE_CODE_TRAILING_NEWLINE_FORBIDDEN",
|
||||
"use code: |-; parser-returned code bytes must not be transformed",
|
||||
)
|
||||
|
||||
expected_procedure = {
|
||||
"IN": {"nexts": [EXPECTED_TASK_NAME], "wait_until": []},
|
||||
EXPECTED_TASK_NAME: {"nexts": ["OUT"], "wait_until": ["IN"]},
|
||||
"OUT": {"nexts": [], "wait_until": [EXPECTED_TASK_NAME]},
|
||||
}
|
||||
procedure = _require_mapping(stage.get("task_procedure"), "TASK_PROCEDURE_REQUIRED")
|
||||
if procedure != expected_procedure:
|
||||
raise ProjectionError("TASK_PROCEDURE_EXACT_IN_TASK_OUT_REQUIRED", repr(procedure)[:800])
|
||||
description = stage.get("description")
|
||||
if not isinstance(description, str) or not all(token in description for token in ("C20", "C21", "C25", "C26", "C22", "C27", "C28", "C29", "C30", "C35")):
|
||||
raise ProjectionError("INTERNAL_DAG_DESCRIPTION_REQUIRED", repr(description))
|
||||
return stage, task
|
||||
|
||||
|
||||
def extract_code(raw: bytes) -> bytes:
|
||||
"""Backward-compatible parser-based extraction used by existing tests."""
|
||||
|
||||
document = parse_authoring_bytes(raw)
|
||||
_, task = extract_run_code_task(document)
|
||||
parameters = _require_mapping(task.get("parameters"), "TASK_PARAMETERS_REQUIRED")
|
||||
return parameters["code"].encode("utf-8")
|
||||
|
||||
|
||||
def _import_root(name: str | None) -> str:
|
||||
return (name or "").split(".", 1)[0]
|
||||
|
||||
|
||||
def _assigned_names(node: ast.Assign | ast.AnnAssign) -> set[str]:
|
||||
targets = node.targets if isinstance(node, ast.Assign) else [node.target]
|
||||
return {target.id for target in targets if isinstance(target, ast.Name)}
|
||||
|
||||
|
||||
def validate_inline_code(code: str) -> dict[str, Any]:
|
||||
try:
|
||||
compile(code, "<Stage_2_S2_20.parameters.code>", "exec", dont_inherit=True)
|
||||
tree = ast.parse(code, filename="<Stage_2_S2_20.parameters.code>", mode="exec")
|
||||
except (SyntaxError, ValueError, UnicodeError) as exc:
|
||||
raise ProjectionError("INLINE_CODE_COMPILE_FAILED", str(exc)) from exc
|
||||
missing_tokens = [token for token in REQUIRED_CODE_TOKENS if token not in code]
|
||||
if missing_tokens:
|
||||
raise ProjectionError("INLINE_CODE_REQUIRED_TOKEN_MISSING", repr(missing_tokens))
|
||||
if PLACEHOLDER_COMMENT_RE.search(code):
|
||||
raise ProjectionError("INLINE_CODE_PLACEHOLDER_COMMENT", "TODO/FIXME/TBD placeholder")
|
||||
for pattern in PLAINTEXT_SECRET_RES:
|
||||
if pattern.search(code):
|
||||
raise ProjectionError("INLINE_CODE_PLAINTEXT_SECRET", pattern.pattern)
|
||||
|
||||
imports: set[str] = set()
|
||||
import_aliases: dict[str, str] = {}
|
||||
forbidden_callable_aliases: set[str] = set()
|
||||
forbidden_nodes: list[str] = []
|
||||
external_source_refs: set[str] = set()
|
||||
observed_urls: set[str] = set()
|
||||
release_constants: list[str] = []
|
||||
|
||||
for node in ast.walk(tree):
|
||||
if isinstance(node, ast.Import):
|
||||
for alias in node.names:
|
||||
root = _import_root(alias.name)
|
||||
imports.add(root)
|
||||
import_aliases[alias.asname or root] = root
|
||||
elif isinstance(node, ast.ImportFrom):
|
||||
root = _import_root(node.module)
|
||||
imports.add(root)
|
||||
if node.level:
|
||||
forbidden_nodes.append(f"relative-import:{node.module or ''}")
|
||||
for alias in node.names:
|
||||
local = alias.asname or alias.name
|
||||
if root == "builtins" and alias.name in FORBIDDEN_CALL_NAMES:
|
||||
forbidden_callable_aliases.add(local)
|
||||
if root == "os" and (
|
||||
("os", alias.name) in FORBIDDEN_ATTRIBUTE_CALLS or alias.name.startswith("exec")
|
||||
):
|
||||
forbidden_callable_aliases.add(local)
|
||||
|
||||
for node in ast.walk(tree):
|
||||
if isinstance(node, (ast.Assign, ast.AnnAssign)):
|
||||
value = node.value
|
||||
names = _assigned_names(node)
|
||||
if isinstance(value, ast.Name) and (
|
||||
value.id in FORBIDDEN_CALL_NAMES or value.id in forbidden_callable_aliases
|
||||
):
|
||||
forbidden_callable_aliases.update(names)
|
||||
if isinstance(value, ast.Attribute) and isinstance(value.value, ast.Name):
|
||||
module = import_aliases.get(value.value.id, value.value.id)
|
||||
if (module, value.attr) in FORBIDDEN_ATTRIBUTE_CALLS or (
|
||||
module == "os" and value.attr.startswith("exec")
|
||||
):
|
||||
forbidden_callable_aliases.update(names)
|
||||
if (
|
||||
"EXPECTED_STAGE2_RELEASE_SHA256" in names
|
||||
and isinstance(value, ast.Constant)
|
||||
and isinstance(value.value, str)
|
||||
):
|
||||
release_constants.append(value.value)
|
||||
elif isinstance(node, ast.Call):
|
||||
if isinstance(node.func, ast.Name) and (
|
||||
node.func.id in FORBIDDEN_CALL_NAMES or node.func.id in forbidden_callable_aliases
|
||||
):
|
||||
forbidden_nodes.append(f"call:{node.func.id}")
|
||||
elif isinstance(node.func, ast.Attribute) and isinstance(node.func.value, ast.Name):
|
||||
module = import_aliases.get(node.func.value.id, node.func.value.id)
|
||||
if (module, node.func.attr) in FORBIDDEN_ATTRIBUTE_CALLS or (
|
||||
module == "os" and node.func.attr.startswith("exec")
|
||||
):
|
||||
forbidden_nodes.append(f"call:{module}.{node.func.attr}")
|
||||
elif isinstance(node, ast.Pass):
|
||||
forbidden_nodes.append("Pass")
|
||||
elif isinstance(node, ast.Raise):
|
||||
target = node.exc.func if isinstance(node.exc, ast.Call) else node.exc
|
||||
if isinstance(target, ast.Name) and target.id == "NotImplementedError":
|
||||
forbidden_nodes.append("raise:NotImplementedError")
|
||||
elif isinstance(node, ast.Constant):
|
||||
if node.value is Ellipsis:
|
||||
forbidden_nodes.append("Ellipsis")
|
||||
if isinstance(node.value, str):
|
||||
observed_urls.update(match.rstrip(".,);]") for match in URL_RE.findall(node.value))
|
||||
if PYTHON_SOURCE_REF_RE.search(node.value.strip()):
|
||||
external_source_refs.add(node.value[:200])
|
||||
|
||||
imports.discard("")
|
||||
disallowed_imports = sorted(
|
||||
value
|
||||
for value in imports
|
||||
if value in FORBIDDEN_IMPORT_ROOTS
|
||||
or (value not in sys.stdlib_module_names and value not in ALLOWED_NON_STDLIB_IMPORTS)
|
||||
)
|
||||
if disallowed_imports:
|
||||
raise ProjectionError("INLINE_CODE_IMPORT_FORBIDDEN", repr(disallowed_imports))
|
||||
if forbidden_nodes:
|
||||
raise ProjectionError("INLINE_CODE_DYNAMIC_OR_PLACEHOLDER_FORBIDDEN", repr(forbidden_nodes))
|
||||
if external_source_refs:
|
||||
raise ProjectionError("INLINE_CODE_EXTERNAL_PY_SOURCE_REF_FORBIDDEN", repr(sorted(external_source_refs)))
|
||||
if observed_urls != set(ALLOWED_CODE_URLS):
|
||||
raise ProjectionError(
|
||||
"INLINE_CODE_ENDPOINT_LITERAL_SET_MISMATCH",
|
||||
json.dumps(
|
||||
{"expected": sorted(ALLOWED_CODE_URLS), "observed": sorted(observed_urls)},
|
||||
ensure_ascii=False,
|
||||
sort_keys=True,
|
||||
),
|
||||
)
|
||||
if len(release_constants) != 1 or not re.fullmatch(r"[a-f0-9]{64}", release_constants[0]):
|
||||
raise ProjectionError("EXPECTED_PARENT_RELEASE_CONSTANT_EXACT_ONE_REQUIRED", repr(release_constants))
|
||||
code_bytes = code.encode("utf-8")
|
||||
return {
|
||||
"yaml_pointer": "/Agent/Stages/0/tasks/0/parameters/code",
|
||||
"encoding": "UTF-8",
|
||||
"extraction_transform": "NONE",
|
||||
"code_sha256": sha256_bytes(code_bytes),
|
||||
"code_size_bytes": len(code_bytes),
|
||||
"compile_status": "PASS",
|
||||
"ast_status": "PASS",
|
||||
"imports": sorted(imports),
|
||||
"forbidden_import_count": 0,
|
||||
"forbidden_dynamic_call_count": 0,
|
||||
"external_python_source_ref_count": 0,
|
||||
"plaintext_secret_count": 0,
|
||||
"endpoint_literals": sorted(observed_urls),
|
||||
"expected_parent_stage2_release_sha256": release_constants[0],
|
||||
}
|
||||
|
||||
|
||||
def validate_agent(raw: bytes, code: bytes | None = None) -> dict[str, Any]:
|
||||
"""Backward-compatible complete validation of authoring and extracted code."""
|
||||
|
||||
document = parse_authoring_bytes(raw)
|
||||
_, task = extract_run_code_task(document)
|
||||
parsed_code = _require_mapping(task.get("parameters"), "TASK_PARAMETERS_REQUIRED")["code"]
|
||||
parsed_bytes = parsed_code.encode("utf-8")
|
||||
if code is not None and code != parsed_bytes:
|
||||
raise ProjectionError("EXTRACTED_CODE_BYTES_DRIFT", sha256_bytes(code))
|
||||
return validate_inline_code(parsed_code)
|
||||
|
||||
|
||||
def _canonical_task_semantics(
|
||||
document: Mapping[str, Any], stage: Mapping[str, Any], task: Mapping[str, Any]
|
||||
) -> dict[str, Any]:
|
||||
agent = _require_mapping(document.get("Agent"), "AGENT_OBJECT_REQUIRED")
|
||||
servers = _require_mapping(
|
||||
_require_mapping(stage.get("tools"), "TOOLS_OBJECT_REQUIRED").get("mcpServers"),
|
||||
"MCP_SERVERS_OBJECT_REQUIRED",
|
||||
)
|
||||
parameters = _require_mapping(task.get("parameters"), "TASK_PARAMETERS_REQUIRED")
|
||||
return {
|
||||
"agent": {"name": agent.get("name"), "version": agent.get("version")},
|
||||
"stage": {
|
||||
"name": stage.get("name"),
|
||||
"prevs": stage.get("prevs"),
|
||||
"nexts": stage.get("nexts"),
|
||||
"skip_confirm": stage.get("skip_confirm"),
|
||||
},
|
||||
"mcp_servers": {
|
||||
name: {"type": value.get("type"), "url": value.get("url")}
|
||||
for name, value in sorted(servers.items())
|
||||
if isinstance(value, dict)
|
||||
},
|
||||
"task": {
|
||||
"task_name": task.get("task_name"),
|
||||
"mcp": task.get("mcp"),
|
||||
"tool_name": task.get("tool_name"),
|
||||
"parameters": {key: parameters.get(key) for key in EXPECTED_PARAMETERS},
|
||||
"code_sha256": sha256_bytes(parameters["code"].encode("utf-8")),
|
||||
},
|
||||
"task_procedure": stage.get("task_procedure"),
|
||||
"internal_dag": "(C20||C21)->C25->C26->(C22||(C27->C28->C29))->C30->C35",
|
||||
}
|
||||
|
||||
|
||||
def expected_outputs(
|
||||
authoring_raw: bytes, document: Mapping[str, Any]
|
||||
) -> tuple[dict[Path, bytes], dict[str, Any]]:
|
||||
stage, task = extract_run_code_task(document)
|
||||
parameters = _require_mapping(task.get("parameters"), "TASK_PARAMETERS_REQUIRED")
|
||||
code = parameters["code"]
|
||||
validation = validate_inline_code(code)
|
||||
code_bytes = code.encode("utf-8")
|
||||
semantics = _canonical_task_semantics(document, stage, task)
|
||||
receipt = {
|
||||
"schema_version": "stage2_s2_20_inline_code_receipt.v1",
|
||||
"workflow_id": "S2_20",
|
||||
"authoring": {
|
||||
"path": _logical_path(AUTHORING_PATH),
|
||||
"sha256": sha256_bytes(authoring_raw),
|
||||
"size_bytes": len(authoring_raw),
|
||||
},
|
||||
"deployment_projection": {
|
||||
"path": _logical_path(PROJECTION_PATH),
|
||||
"sha256": sha256_bytes(authoring_raw),
|
||||
"size_bytes": len(authoring_raw),
|
||||
},
|
||||
"canonical_code": validation,
|
||||
"full_code_mirrors": [_logical_path(MIRROR_PY_PATH), _logical_path(MIRROR_TXT_PATH)],
|
||||
"task_contract": {
|
||||
**semantics,
|
||||
"canonical_task_semantics_sha256": sha256_bytes(canonical_json_bytes(semantics)),
|
||||
"exactly_one_stage": True,
|
||||
"exactly_one_task": True,
|
||||
"exactly_one_code_executor_run_code": True,
|
||||
"authoring_rewritten": False,
|
||||
"projection_byte_identical_to_authoring": True,
|
||||
"code_mirrors_byte_identical": True,
|
||||
"expected_parent_stage2_release_sha256": validation[
|
||||
"expected_parent_stage2_release_sha256"
|
||||
],
|
||||
},
|
||||
"parity_status": "PASS",
|
||||
}
|
||||
outputs = {
|
||||
PROJECTION_PATH: authoring_raw,
|
||||
MIRROR_PY_PATH: code_bytes,
|
||||
MIRROR_TXT_PATH: code_bytes,
|
||||
RECEIPT_PATH: canonical_json_bytes(receipt),
|
||||
}
|
||||
return outputs, receipt
|
||||
|
||||
|
||||
def _assert_output_target(path: Path) -> None:
|
||||
root = DEPLOYMENT_ROOT.resolve(strict=True)
|
||||
resolved = path.resolve(strict=False)
|
||||
try:
|
||||
resolved.relative_to(root)
|
||||
except ValueError as exc:
|
||||
raise ProjectionError("OUTPUT_OUTSIDE_DEPLOYMENT_ROOT", path.as_posix()) from exc
|
||||
if path.exists() and path.is_symlink():
|
||||
raise ProjectionError("OUTPUT_SYMLINK_FORBIDDEN", path.as_posix())
|
||||
|
||||
|
||||
def _atomic_write(path: Path, payload: bytes) -> None:
|
||||
_assert_output_target(path)
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
temporary_name: str | None = None
|
||||
try:
|
||||
with tempfile.NamedTemporaryFile(
|
||||
mode="wb",
|
||||
dir=path.parent,
|
||||
prefix=f".{path.name}.",
|
||||
suffix=".tmp",
|
||||
delete=False,
|
||||
) as handle:
|
||||
temporary_name = handle.name
|
||||
handle.write(payload)
|
||||
handle.flush()
|
||||
os.fsync(handle.fileno())
|
||||
os.replace(temporary_name, path)
|
||||
temporary_name = None
|
||||
finally:
|
||||
if temporary_name is not None:
|
||||
try:
|
||||
Path(temporary_name).unlink()
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
|
||||
def compare_outputs(outputs: Mapping[Path, bytes]) -> list[dict[str, Any]]:
|
||||
mismatches: list[dict[str, Any]] = []
|
||||
for path, expected in outputs.items():
|
||||
if not path.is_file():
|
||||
mismatches.append(
|
||||
{"path": _logical_path(path), "status": "MISSING", "expected_sha256": sha256_bytes(expected)}
|
||||
)
|
||||
continue
|
||||
observed = path.read_bytes()
|
||||
if observed != expected:
|
||||
mismatches.append(
|
||||
{
|
||||
"path": _logical_path(path),
|
||||
"status": "BYTE_MISMATCH",
|
||||
"expected_sha256": sha256_bytes(expected),
|
||||
"observed_sha256": sha256_bytes(observed),
|
||||
}
|
||||
)
|
||||
return mismatches
|
||||
|
||||
|
||||
def run(*, check: bool) -> tuple[int, dict[str, Any]]:
|
||||
before, document = load_authoring()
|
||||
outputs, receipt = expected_outputs(before, document)
|
||||
if check:
|
||||
mismatches = compare_outputs(outputs)
|
||||
return (
|
||||
0 if not mismatches else 1,
|
||||
{
|
||||
"status": "PARITY_PASS" if not mismatches else "PARITY_DRIFT",
|
||||
"mode": "CHECK_NO_WRITE",
|
||||
"authoring_sha256": sha256_bytes(before),
|
||||
"code_sha256": receipt["canonical_code"]["code_sha256"],
|
||||
"mismatches": mismatches,
|
||||
},
|
||||
)
|
||||
|
||||
receipt_payload = outputs[RECEIPT_PATH]
|
||||
for path, payload in outputs.items():
|
||||
if path != RECEIPT_PATH:
|
||||
_atomic_write(path, payload)
|
||||
if AUTHORING_PATH.read_bytes() != before:
|
||||
raise ProjectionError("AUTHORING_CHANGED_DURING_BUILD", AUTHORING_PATH.as_posix())
|
||||
_atomic_write(RECEIPT_PATH, receipt_payload)
|
||||
if AUTHORING_PATH.read_bytes() != before:
|
||||
raise ProjectionError("AUTHORING_CHANGED_DURING_RECEIPT_WRITE", AUTHORING_PATH.as_posix())
|
||||
mismatches = compare_outputs(outputs)
|
||||
if mismatches:
|
||||
raise ProjectionError("POST_BUILD_PARITY_FAILED", json.dumps(mismatches, sort_keys=True))
|
||||
return 0, {
|
||||
"status": "BUILT_AND_VERIFIED",
|
||||
"mode": "BUILD",
|
||||
"authoring_sha256": sha256_bytes(before),
|
||||
"code_sha256": receipt["canonical_code"]["code_sha256"],
|
||||
"outputs": [_logical_path(path) for path in outputs],
|
||||
}
|
||||
|
||||
|
||||
def _parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(description="Build or verify S2_20 inline Agent projection")
|
||||
parser.add_argument("--check", action="store_true", help="no-write byte parity check")
|
||||
return parser
|
||||
|
||||
|
||||
def main(argv: Iterable[str] | None = None) -> int:
|
||||
args = _parser().parse_args(list(argv) if argv is not None else None)
|
||||
try:
|
||||
status, payload = run(check=args.check)
|
||||
except ProjectionError as exc:
|
||||
status = 3 if exc.code == "AUTHORING_YAML_MISSING_OR_SYMLINK" else 2
|
||||
payload = {
|
||||
"status": "CONTROLLED_MISSING_AUTHORING" if status == 3 else "BUILD_FAILED",
|
||||
"reason_code": exc.code,
|
||||
"detail": exc.detail,
|
||||
"mode": "CHECK_NO_WRITE" if args.check else "BUILD",
|
||||
}
|
||||
print(json.dumps(payload, ensure_ascii=False, allow_nan=False, sort_keys=True))
|
||||
return status
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
+665
@@ -0,0 +1,665 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build/check the S2_20 single-task inline Python projection.
|
||||
|
||||
The authoring YAML is the sole editable source. This offline tool rejects
|
||||
duplicate YAML keys, validates the exact AgentBackend task graph and MCP
|
||||
``run_code`` contract, compiles and audits parser-returned inline Python, then
|
||||
projects authoring/code bytes without transformation. It never executes a
|
||||
matter or imports Stage 2 runtime modules.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import ast
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import sys
|
||||
import tempfile
|
||||
from typing import Any, Iterable, Mapping
|
||||
|
||||
try:
|
||||
import yaml
|
||||
except ImportError: # pragma: no cover - incomplete offline build host
|
||||
yaml = None # type: ignore[assignment]
|
||||
|
||||
|
||||
DEPLOYMENT_ROOT = Path(__file__).resolve().parents[1]
|
||||
MAIN_WORKING_DIRECTORY = DEPLOYMENT_ROOT.parent.parent
|
||||
AUTHORING_PATH = MAIN_WORKING_DIRECTORY / "Stage_2_S2_20.yml"
|
||||
PROJECTION_PATH = DEPLOYMENT_ROOT / "agent_scripts/Stage_2_S2_20.yml"
|
||||
MIRROR_PY_PATH = DEPLOYMENT_ROOT / "runtime/s2_20_reduce.py"
|
||||
MIRROR_TXT_PATH = DEPLOYMENT_ROOT / "runtime/s2_20_reduce.txt"
|
||||
RECEIPT_PATH = DEPLOYMENT_ROOT / "manifest/s2_20_inline_code_receipt.json"
|
||||
|
||||
EXPECTED_AGENT_NAME = "Stage_2_S2_20"
|
||||
EXPECTED_AGENT_VERSION = "1.0.0"
|
||||
EXPECTED_STAGE_NAME = "S2_20"
|
||||
EXPECTED_TASK_NAME = "Task_S2_20_deterministic_relief_plan"
|
||||
EXPECTED_PARAMETERS = {
|
||||
"language": "python",
|
||||
"requirements": "httpx==0.28.1",
|
||||
"network": "agent-network",
|
||||
"timeout": 300,
|
||||
}
|
||||
EXPECTED_LOCALDOCS_URL = "http://mcp-localdocs:8012/mcp"
|
||||
EXPECTED_CODE_EXECUTOR_URL = "https://code-executor.mcp.eroomai.com/mcp"
|
||||
EXPECTED_WEAVIATE_URL = "https://weaviate.eroomai.com/mcp"
|
||||
ALLOWED_CODE_URLS = frozenset({EXPECTED_LOCALDOCS_URL, EXPECTED_WEAVIATE_URL})
|
||||
ALLOWED_NON_STDLIB_IMPORTS = frozenset({"httpx"})
|
||||
FORBIDDEN_IMPORT_ROOTS = frozenset(
|
||||
{"ftplib", "http", "importlib", "smtplib", "socket", "subprocess", "telnetlib", "urllib", "xmlrpc"}
|
||||
)
|
||||
FORBIDDEN_CALL_NAMES = frozenset({"__import__", "compile", "eval", "exec", "open"})
|
||||
FORBIDDEN_ATTRIBUTE_CALLS = frozenset(
|
||||
{
|
||||
("os", "popen"),
|
||||
("os", "system"),
|
||||
("os", "spawnl"),
|
||||
("os", "spawnle"),
|
||||
("os", "spawnlp"),
|
||||
("os", "spawnlpe"),
|
||||
("os", "spawnv"),
|
||||
("os", "spawnve"),
|
||||
("os", "spawnvp"),
|
||||
("os", "spawnvpe"),
|
||||
}
|
||||
)
|
||||
MODEL_FIELDS = frozenset({"llm_provider", "llm_model", "llm_reasoning", "llm_verbosity", "prompt"})
|
||||
REQUIRED_CODE_TOKENS = (
|
||||
EXPECTED_LOCALDOCS_URL,
|
||||
EXPECTED_WEAVIATE_URL,
|
||||
"EXPECTED_STAGE2_RELEASE_SHA256",
|
||||
"{{__user_hash__}}",
|
||||
"{{__workspace_hash__}}",
|
||||
"read_binary_doc",
|
||||
"write_binary_file",
|
||||
"plan_publish_status.json",
|
||||
"build_portfolio",
|
||||
"build_party_projection",
|
||||
"calculate_all",
|
||||
"build_retrieval_branch",
|
||||
"execute_core",
|
||||
"publish",
|
||||
"publish_diagnostic",
|
||||
)
|
||||
URL_RE = re.compile(r"https?://[^\s'\"]+")
|
||||
PYTHON_SOURCE_REF_RE = re.compile(r"(?i)(?:^|[/\\])[^\r\n'\"]+\.py(?:$|[?#])")
|
||||
PLACEHOLDER_COMMENT_RE = re.compile(
|
||||
r"(?im)^\s*#\s*(?:TODO|FIXME|TBD|PLACEHOLDER|OMITTED\s+BODY|IMPLEMENT\s+ME)\b"
|
||||
)
|
||||
PLAINTEXT_SECRET_RES = (
|
||||
re.compile(r"(?i)\bAuthorization\s*:\s*Bearer\s+\S+"),
|
||||
re.compile(r"(?i)\bBearer\s+[A-Za-z0-9+/=_-]{12,}"),
|
||||
re.compile(
|
||||
r"(?i)\b(?:MCP_API_KEY|API_KEY|ACCESS_TOKEN|AUTH_TOKEN|CLIENT_SECRET)\s*=\s*['\"][^'\"]+['\"]"
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class ProjectionError(RuntimeError):
|
||||
"""Controlled build failure with a stable reason code."""
|
||||
|
||||
def __init__(self, code: str, detail: str) -> None:
|
||||
super().__init__(f"{code}: {detail}")
|
||||
self.code = code
|
||||
self.detail = detail
|
||||
|
||||
|
||||
if yaml is not None:
|
||||
|
||||
class UniqueKeySafeLoader(yaml.SafeLoader):
|
||||
"""SafeLoader variant that rejects duplicate mapping keys recursively."""
|
||||
|
||||
def construct_mapping(self, node: Any, deep: bool = False) -> dict[Any, Any]:
|
||||
if not isinstance(node, yaml.MappingNode):
|
||||
raise ProjectionError("YAML_MAPPING_REQUIRED", repr(node)[:200])
|
||||
self.flatten_mapping(node)
|
||||
result: dict[Any, Any] = {}
|
||||
for key_node, value_node in node.value:
|
||||
key = self.construct_object(key_node, deep=deep)
|
||||
try:
|
||||
duplicate = key in result
|
||||
except TypeError as exc:
|
||||
raise ProjectionError("YAML_UNHASHABLE_KEY", repr(key)[:200]) from exc
|
||||
if duplicate:
|
||||
mark = getattr(key_node, "start_mark", None)
|
||||
line = mark.line + 1 if mark is not None else "?"
|
||||
raise ProjectionError("YAML_DUPLICATE_KEY", f"{key!r}@line={line}")
|
||||
result[key] = self.construct_object(value_node, deep=deep)
|
||||
return result
|
||||
|
||||
else: # pragma: no cover
|
||||
|
||||
class UniqueKeySafeLoader: # type: ignore[no-redef]
|
||||
pass
|
||||
|
||||
|
||||
def sha256_bytes(value: bytes) -> str:
|
||||
return hashlib.sha256(value).hexdigest()
|
||||
|
||||
|
||||
def canonical_json_bytes(value: Any) -> bytes:
|
||||
return (
|
||||
json.dumps(value, ensure_ascii=False, allow_nan=False, sort_keys=True, separators=(",", ":"))
|
||||
+ "\n"
|
||||
).encode("utf-8")
|
||||
|
||||
|
||||
def _logical_path(path: Path) -> str:
|
||||
try:
|
||||
return path.resolve(strict=False).relative_to(MAIN_WORKING_DIRECTORY.resolve(strict=False)).as_posix()
|
||||
except ValueError:
|
||||
return path.as_posix()
|
||||
|
||||
|
||||
def _require_mapping(value: Any, code: str) -> dict[str, Any]:
|
||||
if not isinstance(value, dict):
|
||||
raise ProjectionError(code, repr(value)[:300])
|
||||
return value
|
||||
|
||||
|
||||
def _require_list(value: Any, code: str) -> list[Any]:
|
||||
if not isinstance(value, list):
|
||||
raise ProjectionError(code, repr(value)[:300])
|
||||
return value
|
||||
|
||||
|
||||
def parse_authoring_bytes(raw: bytes, *, source: str = "<authoring>") -> dict[str, Any]:
|
||||
if yaml is None:
|
||||
raise ProjectionError("PYYAML_REQUIRED", "install PyYAML on offline build host")
|
||||
try:
|
||||
text = raw.decode("utf-8")
|
||||
except UnicodeDecodeError as exc:
|
||||
raise ProjectionError("AUTHORING_UTF8_REQUIRED", f"{source}: {exc}") from exc
|
||||
if text.startswith("\ufeff"):
|
||||
raise ProjectionError("AUTHORING_UTF8_BOM_FORBIDDEN", source)
|
||||
for pattern in PLAINTEXT_SECRET_RES:
|
||||
if pattern.search(text):
|
||||
raise ProjectionError("AUTHORING_PLAINTEXT_SECRET", pattern.pattern)
|
||||
try:
|
||||
document = yaml.load(text, Loader=UniqueKeySafeLoader)
|
||||
except ProjectionError:
|
||||
raise
|
||||
except yaml.YAMLError as exc:
|
||||
raise ProjectionError("AUTHORING_YAML_INVALID", f"{source}: {exc}") from exc
|
||||
if not isinstance(document, dict):
|
||||
raise ProjectionError("AUTHORING_ROOT_OBJECT_REQUIRED", source)
|
||||
return document
|
||||
|
||||
|
||||
def load_authoring(path: Path | None = None) -> tuple[bytes, dict[str, Any]]:
|
||||
selected = AUTHORING_PATH if path is None else path
|
||||
if not selected.is_file() or selected.is_symlink():
|
||||
raise ProjectionError("AUTHORING_YAML_MISSING_OR_SYMLINK", selected.as_posix())
|
||||
raw = selected.read_bytes()
|
||||
return raw, parse_authoring_bytes(raw, source=selected.as_posix())
|
||||
|
||||
|
||||
def _find_forbidden_model_fields(value: Any, *, pointer: str = "") -> list[str]:
|
||||
findings: list[str] = []
|
||||
if isinstance(value, dict):
|
||||
for key, child in value.items():
|
||||
child_pointer = f"{pointer}/{key}"
|
||||
if key in MODEL_FIELDS:
|
||||
findings.append(child_pointer)
|
||||
findings.extend(_find_forbidden_model_fields(child, pointer=child_pointer))
|
||||
elif isinstance(value, list):
|
||||
for index, child in enumerate(value):
|
||||
findings.extend(_find_forbidden_model_fields(child, pointer=f"{pointer}/{index}"))
|
||||
return findings
|
||||
|
||||
|
||||
def extract_run_code_task(document: Mapping[str, Any]) -> tuple[dict[str, Any], dict[str, Any]]:
|
||||
agent = _require_mapping(document.get("Agent"), "AGENT_OBJECT_REQUIRED")
|
||||
if agent.get("name") != EXPECTED_AGENT_NAME or agent.get("version") != EXPECTED_AGENT_VERSION:
|
||||
raise ProjectionError(
|
||||
"AGENT_IDENTITY_MISMATCH",
|
||||
f"expected={EXPECTED_AGENT_NAME}/{EXPECTED_AGENT_VERSION} observed={agent.get('name')}/{agent.get('version')}",
|
||||
)
|
||||
forbidden_model_fields = _find_forbidden_model_fields(agent)
|
||||
if forbidden_model_fields:
|
||||
raise ProjectionError("LLM_OR_PROMPT_FIELD_FORBIDDEN", repr(forbidden_model_fields))
|
||||
stages = _require_list(agent.get("Stages"), "STAGES_ARRAY_REQUIRED")
|
||||
if len(stages) != 1:
|
||||
raise ProjectionError("EXACTLY_ONE_STAGE_REQUIRED", str(len(stages)))
|
||||
stage = _require_mapping(stages[0], "STAGE_OBJECT_REQUIRED")
|
||||
if stage.get("name") != EXPECTED_STAGE_NAME:
|
||||
raise ProjectionError("S2_20_STAGE_NAME_REQUIRED", repr(stage.get("name")))
|
||||
if stage.get("skip_confirm") is not True:
|
||||
raise ProjectionError("S2_20_SKIP_CONFIRM_TRUE_REQUIRED", repr(stage.get("skip_confirm")))
|
||||
if stage.get("prevs") != [] or stage.get("nexts") != []:
|
||||
raise ProjectionError("STANDALONE_STAGE_EDGES_MUST_BE_EMPTY", repr(stage))
|
||||
|
||||
servers = _require_mapping(
|
||||
_require_mapping(stage.get("tools"), "TOOLS_OBJECT_REQUIRED").get("mcpServers"),
|
||||
"MCP_SERVERS_OBJECT_REQUIRED",
|
||||
)
|
||||
if set(servers) != {"localdocs", "code-executor"}:
|
||||
raise ProjectionError("MCP_SERVER_SET_MISMATCH", repr(sorted(servers)))
|
||||
expected_servers = {
|
||||
"localdocs": {"type": "streamable-http", "url": EXPECTED_LOCALDOCS_URL},
|
||||
"code-executor": {"type": "streamable-http", "url": EXPECTED_CODE_EXECUTOR_URL},
|
||||
}
|
||||
for name, expected in expected_servers.items():
|
||||
observed = _require_mapping(servers.get(name), f"{name.upper()}_SERVER_REQUIRED")
|
||||
if observed != expected:
|
||||
raise ProjectionError("MCP_SERVER_BINDING_INVALID", f"{name}:{observed!r}")
|
||||
|
||||
tasks = _require_list(stage.get("tasks"), "TASKS_ARRAY_REQUIRED")
|
||||
if len(tasks) != 1:
|
||||
raise ProjectionError("EXACTLY_ONE_TASK_REQUIRED", str(len(tasks)))
|
||||
task = _require_mapping(tasks[0], "TASK_OBJECT_REQUIRED")
|
||||
run_code_tasks = [
|
||||
value
|
||||
for candidate_stage in stages
|
||||
for value in _require_list(
|
||||
_require_mapping(candidate_stage, "STAGE_OBJECT_REQUIRED").get("tasks"),
|
||||
"TASKS_ARRAY_REQUIRED",
|
||||
)
|
||||
if isinstance(value, dict)
|
||||
and value.get("mcp") == "code-executor"
|
||||
and value.get("tool_name") == "run_code"
|
||||
]
|
||||
if len(run_code_tasks) != 1:
|
||||
raise ProjectionError("EXACTLY_ONE_CODE_EXECUTOR_RUN_CODE_REQUIRED", str(len(run_code_tasks)))
|
||||
if task.get("task_name") != EXPECTED_TASK_NAME:
|
||||
raise ProjectionError("TASK_NAME_MISMATCH", repr(task.get("task_name")))
|
||||
if task.get("mcp") != "code-executor" or task.get("tool_name") != "run_code":
|
||||
raise ProjectionError("RUN_CODE_TASK_BINDING_MISMATCH", repr(task))
|
||||
parameters = _require_mapping(task.get("parameters"), "TASK_PARAMETERS_REQUIRED")
|
||||
if set(parameters) != set(EXPECTED_PARAMETERS) | {"code"}:
|
||||
raise ProjectionError("RUN_CODE_PARAMETER_SET_MISMATCH", repr(sorted(parameters)))
|
||||
for key, expected in EXPECTED_PARAMETERS.items():
|
||||
if parameters.get(key) != expected:
|
||||
raise ProjectionError(
|
||||
"RUN_CODE_PARAMETER_MISMATCH", f"{key}: expected={expected!r} observed={parameters.get(key)!r}"
|
||||
)
|
||||
code = parameters.get("code")
|
||||
if not isinstance(code, str) or not code:
|
||||
raise ProjectionError("INLINE_CODE_REQUIRED", repr(code)[:200])
|
||||
if code.endswith(("\n", "\r")):
|
||||
raise ProjectionError(
|
||||
"INLINE_CODE_TRAILING_NEWLINE_FORBIDDEN",
|
||||
"use code: |-; parser-returned code bytes must not be transformed",
|
||||
)
|
||||
|
||||
expected_procedure = {
|
||||
"IN": {"nexts": [EXPECTED_TASK_NAME], "wait_until": []},
|
||||
EXPECTED_TASK_NAME: {"nexts": ["OUT"], "wait_until": ["IN"]},
|
||||
"OUT": {"nexts": [], "wait_until": [EXPECTED_TASK_NAME]},
|
||||
}
|
||||
procedure = _require_mapping(stage.get("task_procedure"), "TASK_PROCEDURE_REQUIRED")
|
||||
if procedure != expected_procedure:
|
||||
raise ProjectionError("TASK_PROCEDURE_EXACT_IN_TASK_OUT_REQUIRED", repr(procedure)[:800])
|
||||
description = stage.get("description")
|
||||
if not isinstance(description, str) or not all(token in description for token in ("C20", "C21", "C25", "C26", "C22", "C27", "C28", "C29", "C30", "C35")):
|
||||
raise ProjectionError("INTERNAL_DAG_DESCRIPTION_REQUIRED", repr(description))
|
||||
return stage, task
|
||||
|
||||
|
||||
def extract_code(raw: bytes) -> bytes:
|
||||
"""Backward-compatible parser-based extraction used by existing tests."""
|
||||
|
||||
document = parse_authoring_bytes(raw)
|
||||
_, task = extract_run_code_task(document)
|
||||
parameters = _require_mapping(task.get("parameters"), "TASK_PARAMETERS_REQUIRED")
|
||||
return parameters["code"].encode("utf-8")
|
||||
|
||||
|
||||
def _import_root(name: str | None) -> str:
|
||||
return (name or "").split(".", 1)[0]
|
||||
|
||||
|
||||
def _assigned_names(node: ast.Assign | ast.AnnAssign) -> set[str]:
|
||||
targets = node.targets if isinstance(node, ast.Assign) else [node.target]
|
||||
return {target.id for target in targets if isinstance(target, ast.Name)}
|
||||
|
||||
|
||||
def validate_inline_code(code: str) -> dict[str, Any]:
|
||||
try:
|
||||
compile(code, "<Stage_2_S2_20.parameters.code>", "exec", dont_inherit=True)
|
||||
tree = ast.parse(code, filename="<Stage_2_S2_20.parameters.code>", mode="exec")
|
||||
except (SyntaxError, ValueError, UnicodeError) as exc:
|
||||
raise ProjectionError("INLINE_CODE_COMPILE_FAILED", str(exc)) from exc
|
||||
missing_tokens = [token for token in REQUIRED_CODE_TOKENS if token not in code]
|
||||
if missing_tokens:
|
||||
raise ProjectionError("INLINE_CODE_REQUIRED_TOKEN_MISSING", repr(missing_tokens))
|
||||
if PLACEHOLDER_COMMENT_RE.search(code):
|
||||
raise ProjectionError("INLINE_CODE_PLACEHOLDER_COMMENT", "TODO/FIXME/TBD placeholder")
|
||||
for pattern in PLAINTEXT_SECRET_RES:
|
||||
if pattern.search(code):
|
||||
raise ProjectionError("INLINE_CODE_PLAINTEXT_SECRET", pattern.pattern)
|
||||
|
||||
imports: set[str] = set()
|
||||
import_aliases: dict[str, str] = {}
|
||||
forbidden_callable_aliases: set[str] = set()
|
||||
forbidden_nodes: list[str] = []
|
||||
external_source_refs: set[str] = set()
|
||||
observed_urls: set[str] = set()
|
||||
release_constants: list[str] = []
|
||||
|
||||
for node in ast.walk(tree):
|
||||
if isinstance(node, ast.Import):
|
||||
for alias in node.names:
|
||||
root = _import_root(alias.name)
|
||||
imports.add(root)
|
||||
import_aliases[alias.asname or root] = root
|
||||
elif isinstance(node, ast.ImportFrom):
|
||||
root = _import_root(node.module)
|
||||
imports.add(root)
|
||||
if node.level:
|
||||
forbidden_nodes.append(f"relative-import:{node.module or ''}")
|
||||
for alias in node.names:
|
||||
local = alias.asname or alias.name
|
||||
if root == "builtins" and alias.name in FORBIDDEN_CALL_NAMES:
|
||||
forbidden_callable_aliases.add(local)
|
||||
if root == "os" and (
|
||||
("os", alias.name) in FORBIDDEN_ATTRIBUTE_CALLS or alias.name.startswith("exec")
|
||||
):
|
||||
forbidden_callable_aliases.add(local)
|
||||
|
||||
for node in ast.walk(tree):
|
||||
if isinstance(node, (ast.Assign, ast.AnnAssign)):
|
||||
value = node.value
|
||||
names = _assigned_names(node)
|
||||
if isinstance(value, ast.Name) and (
|
||||
value.id in FORBIDDEN_CALL_NAMES or value.id in forbidden_callable_aliases
|
||||
):
|
||||
forbidden_callable_aliases.update(names)
|
||||
if isinstance(value, ast.Attribute) and isinstance(value.value, ast.Name):
|
||||
module = import_aliases.get(value.value.id, value.value.id)
|
||||
if (module, value.attr) in FORBIDDEN_ATTRIBUTE_CALLS or (
|
||||
module == "os" and value.attr.startswith("exec")
|
||||
):
|
||||
forbidden_callable_aliases.update(names)
|
||||
if (
|
||||
"EXPECTED_STAGE2_RELEASE_SHA256" in names
|
||||
and isinstance(value, ast.Constant)
|
||||
and isinstance(value.value, str)
|
||||
):
|
||||
release_constants.append(value.value)
|
||||
elif isinstance(node, ast.Call):
|
||||
if isinstance(node.func, ast.Name) and (
|
||||
node.func.id in FORBIDDEN_CALL_NAMES or node.func.id in forbidden_callable_aliases
|
||||
):
|
||||
forbidden_nodes.append(f"call:{node.func.id}")
|
||||
elif isinstance(node.func, ast.Attribute) and isinstance(node.func.value, ast.Name):
|
||||
module = import_aliases.get(node.func.value.id, node.func.value.id)
|
||||
if (module, node.func.attr) in FORBIDDEN_ATTRIBUTE_CALLS or (
|
||||
module == "os" and node.func.attr.startswith("exec")
|
||||
):
|
||||
forbidden_nodes.append(f"call:{module}.{node.func.attr}")
|
||||
elif isinstance(node, ast.Pass):
|
||||
forbidden_nodes.append("Pass")
|
||||
elif isinstance(node, ast.Raise):
|
||||
target = node.exc.func if isinstance(node.exc, ast.Call) else node.exc
|
||||
if isinstance(target, ast.Name) and target.id == "NotImplementedError":
|
||||
forbidden_nodes.append("raise:NotImplementedError")
|
||||
elif isinstance(node, ast.Constant):
|
||||
if node.value is Ellipsis:
|
||||
forbidden_nodes.append("Ellipsis")
|
||||
if isinstance(node.value, str):
|
||||
observed_urls.update(match.rstrip(".,);]") for match in URL_RE.findall(node.value))
|
||||
if PYTHON_SOURCE_REF_RE.search(node.value.strip()):
|
||||
external_source_refs.add(node.value[:200])
|
||||
|
||||
imports.discard("")
|
||||
disallowed_imports = sorted(
|
||||
value
|
||||
for value in imports
|
||||
if value in FORBIDDEN_IMPORT_ROOTS
|
||||
or (value not in sys.stdlib_module_names and value not in ALLOWED_NON_STDLIB_IMPORTS)
|
||||
)
|
||||
if disallowed_imports:
|
||||
raise ProjectionError("INLINE_CODE_IMPORT_FORBIDDEN", repr(disallowed_imports))
|
||||
if forbidden_nodes:
|
||||
raise ProjectionError("INLINE_CODE_DYNAMIC_OR_PLACEHOLDER_FORBIDDEN", repr(forbidden_nodes))
|
||||
if external_source_refs:
|
||||
raise ProjectionError("INLINE_CODE_EXTERNAL_PY_SOURCE_REF_FORBIDDEN", repr(sorted(external_source_refs)))
|
||||
if observed_urls != set(ALLOWED_CODE_URLS):
|
||||
raise ProjectionError(
|
||||
"INLINE_CODE_ENDPOINT_LITERAL_SET_MISMATCH",
|
||||
json.dumps(
|
||||
{"expected": sorted(ALLOWED_CODE_URLS), "observed": sorted(observed_urls)},
|
||||
ensure_ascii=False,
|
||||
sort_keys=True,
|
||||
),
|
||||
)
|
||||
if len(release_constants) != 1 or not re.fullmatch(r"[a-f0-9]{64}", release_constants[0]):
|
||||
raise ProjectionError("EXPECTED_PARENT_RELEASE_CONSTANT_EXACT_ONE_REQUIRED", repr(release_constants))
|
||||
code_bytes = code.encode("utf-8")
|
||||
return {
|
||||
"yaml_pointer": "/Agent/Stages/0/tasks/0/parameters/code",
|
||||
"encoding": "UTF-8",
|
||||
"extraction_transform": "NONE",
|
||||
"code_sha256": sha256_bytes(code_bytes),
|
||||
"code_size_bytes": len(code_bytes),
|
||||
"compile_status": "PASS",
|
||||
"ast_status": "PASS",
|
||||
"imports": sorted(imports),
|
||||
"forbidden_import_count": 0,
|
||||
"forbidden_dynamic_call_count": 0,
|
||||
"external_python_source_ref_count": 0,
|
||||
"plaintext_secret_count": 0,
|
||||
"endpoint_literals": sorted(observed_urls),
|
||||
"expected_parent_stage2_release_sha256": release_constants[0],
|
||||
}
|
||||
|
||||
|
||||
def validate_agent(raw: bytes, code: bytes | None = None) -> dict[str, Any]:
|
||||
"""Backward-compatible complete validation of authoring and extracted code."""
|
||||
|
||||
document = parse_authoring_bytes(raw)
|
||||
_, task = extract_run_code_task(document)
|
||||
parsed_code = _require_mapping(task.get("parameters"), "TASK_PARAMETERS_REQUIRED")["code"]
|
||||
parsed_bytes = parsed_code.encode("utf-8")
|
||||
if code is not None and code != parsed_bytes:
|
||||
raise ProjectionError("EXTRACTED_CODE_BYTES_DRIFT", sha256_bytes(code))
|
||||
return validate_inline_code(parsed_code)
|
||||
|
||||
|
||||
def _canonical_task_semantics(
|
||||
document: Mapping[str, Any], stage: Mapping[str, Any], task: Mapping[str, Any]
|
||||
) -> dict[str, Any]:
|
||||
agent = _require_mapping(document.get("Agent"), "AGENT_OBJECT_REQUIRED")
|
||||
servers = _require_mapping(
|
||||
_require_mapping(stage.get("tools"), "TOOLS_OBJECT_REQUIRED").get("mcpServers"),
|
||||
"MCP_SERVERS_OBJECT_REQUIRED",
|
||||
)
|
||||
parameters = _require_mapping(task.get("parameters"), "TASK_PARAMETERS_REQUIRED")
|
||||
return {
|
||||
"agent": {"name": agent.get("name"), "version": agent.get("version")},
|
||||
"stage": {
|
||||
"name": stage.get("name"),
|
||||
"prevs": stage.get("prevs"),
|
||||
"nexts": stage.get("nexts"),
|
||||
"skip_confirm": stage.get("skip_confirm"),
|
||||
},
|
||||
"mcp_servers": {
|
||||
name: {"type": value.get("type"), "url": value.get("url")}
|
||||
for name, value in sorted(servers.items())
|
||||
if isinstance(value, dict)
|
||||
},
|
||||
"task": {
|
||||
"task_name": task.get("task_name"),
|
||||
"mcp": task.get("mcp"),
|
||||
"tool_name": task.get("tool_name"),
|
||||
"parameters": {key: parameters.get(key) for key in EXPECTED_PARAMETERS},
|
||||
"code_sha256": sha256_bytes(parameters["code"].encode("utf-8")),
|
||||
},
|
||||
"task_procedure": stage.get("task_procedure"),
|
||||
"internal_dag": "(C20||C21)->C25->C26->(C22||(C27->C28->C29))->C30->C35",
|
||||
}
|
||||
|
||||
|
||||
def expected_outputs(
|
||||
authoring_raw: bytes, document: Mapping[str, Any]
|
||||
) -> tuple[dict[Path, bytes], dict[str, Any]]:
|
||||
stage, task = extract_run_code_task(document)
|
||||
parameters = _require_mapping(task.get("parameters"), "TASK_PARAMETERS_REQUIRED")
|
||||
code = parameters["code"]
|
||||
validation = validate_inline_code(code)
|
||||
code_bytes = code.encode("utf-8")
|
||||
semantics = _canonical_task_semantics(document, stage, task)
|
||||
receipt = {
|
||||
"schema_version": "stage2_s2_20_inline_code_receipt.v1",
|
||||
"workflow_id": "S2_20",
|
||||
"authoring": {
|
||||
"path": _logical_path(AUTHORING_PATH),
|
||||
"sha256": sha256_bytes(authoring_raw),
|
||||
"size_bytes": len(authoring_raw),
|
||||
},
|
||||
"deployment_projection": {
|
||||
"path": _logical_path(PROJECTION_PATH),
|
||||
"sha256": sha256_bytes(authoring_raw),
|
||||
"size_bytes": len(authoring_raw),
|
||||
},
|
||||
"canonical_code": validation,
|
||||
"full_code_mirrors": [_logical_path(MIRROR_PY_PATH), _logical_path(MIRROR_TXT_PATH)],
|
||||
"task_contract": {
|
||||
**semantics,
|
||||
"canonical_task_semantics_sha256": sha256_bytes(canonical_json_bytes(semantics)),
|
||||
"exactly_one_stage": True,
|
||||
"exactly_one_task": True,
|
||||
"exactly_one_code_executor_run_code": True,
|
||||
"authoring_rewritten": False,
|
||||
"projection_byte_identical_to_authoring": True,
|
||||
"code_mirrors_byte_identical": True,
|
||||
"expected_parent_stage2_release_sha256": validation[
|
||||
"expected_parent_stage2_release_sha256"
|
||||
],
|
||||
},
|
||||
"parity_status": "PASS",
|
||||
}
|
||||
outputs = {
|
||||
PROJECTION_PATH: authoring_raw,
|
||||
MIRROR_PY_PATH: code_bytes,
|
||||
MIRROR_TXT_PATH: code_bytes,
|
||||
RECEIPT_PATH: canonical_json_bytes(receipt),
|
||||
}
|
||||
return outputs, receipt
|
||||
|
||||
|
||||
def _assert_output_target(path: Path) -> None:
|
||||
root = DEPLOYMENT_ROOT.resolve(strict=True)
|
||||
resolved = path.resolve(strict=False)
|
||||
try:
|
||||
resolved.relative_to(root)
|
||||
except ValueError as exc:
|
||||
raise ProjectionError("OUTPUT_OUTSIDE_DEPLOYMENT_ROOT", path.as_posix()) from exc
|
||||
if path.exists() and path.is_symlink():
|
||||
raise ProjectionError("OUTPUT_SYMLINK_FORBIDDEN", path.as_posix())
|
||||
|
||||
|
||||
def _atomic_write(path: Path, payload: bytes) -> None:
|
||||
_assert_output_target(path)
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
temporary_name: str | None = None
|
||||
try:
|
||||
with tempfile.NamedTemporaryFile(
|
||||
mode="wb",
|
||||
dir=path.parent,
|
||||
prefix=f".{path.name}.",
|
||||
suffix=".tmp",
|
||||
delete=False,
|
||||
) as handle:
|
||||
temporary_name = handle.name
|
||||
handle.write(payload)
|
||||
handle.flush()
|
||||
os.fsync(handle.fileno())
|
||||
os.replace(temporary_name, path)
|
||||
temporary_name = None
|
||||
finally:
|
||||
if temporary_name is not None:
|
||||
try:
|
||||
Path(temporary_name).unlink()
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
|
||||
def compare_outputs(outputs: Mapping[Path, bytes]) -> list[dict[str, Any]]:
|
||||
mismatches: list[dict[str, Any]] = []
|
||||
for path, expected in outputs.items():
|
||||
if not path.is_file():
|
||||
mismatches.append(
|
||||
{"path": _logical_path(path), "status": "MISSING", "expected_sha256": sha256_bytes(expected)}
|
||||
)
|
||||
continue
|
||||
observed = path.read_bytes()
|
||||
if observed != expected:
|
||||
mismatches.append(
|
||||
{
|
||||
"path": _logical_path(path),
|
||||
"status": "BYTE_MISMATCH",
|
||||
"expected_sha256": sha256_bytes(expected),
|
||||
"observed_sha256": sha256_bytes(observed),
|
||||
}
|
||||
)
|
||||
return mismatches
|
||||
|
||||
|
||||
def run(*, check: bool) -> tuple[int, dict[str, Any]]:
|
||||
before, document = load_authoring()
|
||||
outputs, receipt = expected_outputs(before, document)
|
||||
if check:
|
||||
mismatches = compare_outputs(outputs)
|
||||
return (
|
||||
0 if not mismatches else 1,
|
||||
{
|
||||
"status": "PARITY_PASS" if not mismatches else "PARITY_DRIFT",
|
||||
"mode": "CHECK_NO_WRITE",
|
||||
"authoring_sha256": sha256_bytes(before),
|
||||
"code_sha256": receipt["canonical_code"]["code_sha256"],
|
||||
"mismatches": mismatches,
|
||||
},
|
||||
)
|
||||
|
||||
receipt_payload = outputs[RECEIPT_PATH]
|
||||
for path, payload in outputs.items():
|
||||
if path != RECEIPT_PATH:
|
||||
_atomic_write(path, payload)
|
||||
if AUTHORING_PATH.read_bytes() != before:
|
||||
raise ProjectionError("AUTHORING_CHANGED_DURING_BUILD", AUTHORING_PATH.as_posix())
|
||||
_atomic_write(RECEIPT_PATH, receipt_payload)
|
||||
if AUTHORING_PATH.read_bytes() != before:
|
||||
raise ProjectionError("AUTHORING_CHANGED_DURING_RECEIPT_WRITE", AUTHORING_PATH.as_posix())
|
||||
mismatches = compare_outputs(outputs)
|
||||
if mismatches:
|
||||
raise ProjectionError("POST_BUILD_PARITY_FAILED", json.dumps(mismatches, sort_keys=True))
|
||||
return 0, {
|
||||
"status": "BUILT_AND_VERIFIED",
|
||||
"mode": "BUILD",
|
||||
"authoring_sha256": sha256_bytes(before),
|
||||
"code_sha256": receipt["canonical_code"]["code_sha256"],
|
||||
"outputs": [_logical_path(path) for path in outputs],
|
||||
}
|
||||
|
||||
|
||||
def _parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(description="Build or verify S2_20 inline Agent projection")
|
||||
parser.add_argument("--check", action="store_true", help="no-write byte parity check")
|
||||
return parser
|
||||
|
||||
|
||||
def main(argv: Iterable[str] | None = None) -> int:
|
||||
args = _parser().parse_args(list(argv) if argv is not None else None)
|
||||
try:
|
||||
status, payload = run(check=args.check)
|
||||
except ProjectionError as exc:
|
||||
status = 3 if exc.code == "AUTHORING_YAML_MISSING_OR_SYMLINK" else 2
|
||||
payload = {
|
||||
"status": "CONTROLLED_MISSING_AUTHORING" if status == 3 else "BUILD_FAILED",
|
||||
"reason_code": exc.code,
|
||||
"detail": exc.detail,
|
||||
"mode": "CHECK_NO_WRITE" if args.check else "BUILD",
|
||||
}
|
||||
print(json.dumps(payload, ensure_ascii=False, allow_nan=False, sort_keys=True))
|
||||
return status
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
+267
@@ -0,0 +1,267 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compile ``case_kinds.md`` into the canonical 137-row Stage 2 registry.
|
||||
|
||||
This is an offline, deterministic compiler. It reads the three-column source
|
||||
table, performs only Unicode/whitespace normalization and comma expansion, and
|
||||
emits legally *unclassified* rows. It must not infer claim capability,
|
||||
predicates, renderers, or any other legal content.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
from typing import Any, Iterable
|
||||
import unicodedata
|
||||
|
||||
|
||||
EXPECTED_HEADERS = ("소송 대분류", "분쟁 유형", "사건 종류")
|
||||
EXCLUDED_MAJOR_CATEGORIES = ("가사소송", "가족관계등록", "행정소송")
|
||||
EXPECTED_ROW_COUNT = 137
|
||||
DEFAULT_LOGICAL_SOURCE_PATH = "Case_02_Comparison_Research/case_kinds.md"
|
||||
|
||||
|
||||
class CatalogCompileError(ValueError):
|
||||
"""Controlled source/compile failure with a stable reason code."""
|
||||
|
||||
def __init__(self, code: str, detail: str) -> None:
|
||||
super().__init__(f"{code}: {detail}")
|
||||
self.code = code
|
||||
self.detail = detail
|
||||
|
||||
|
||||
def nfc(value: str) -> str:
|
||||
return unicodedata.normalize("NFC", value.strip())
|
||||
|
||||
|
||||
def sha256_bytes(value: bytes) -> str:
|
||||
return hashlib.sha256(value).hexdigest()
|
||||
|
||||
|
||||
def canonical_json_bytes(value: Any) -> bytes:
|
||||
return (
|
||||
json.dumps(
|
||||
value,
|
||||
ensure_ascii=False,
|
||||
allow_nan=False,
|
||||
indent=2,
|
||||
separators=(",", ": "),
|
||||
)
|
||||
+ "\n"
|
||||
).encode("utf-8")
|
||||
|
||||
|
||||
def _markdown_cells(line: str) -> tuple[str, str, str] | None:
|
||||
stripped = line.strip()
|
||||
if not (stripped.startswith("|") and stripped.endswith("|")):
|
||||
return None
|
||||
cells = tuple(nfc(cell) for cell in stripped[1:-1].split("|"))
|
||||
if len(cells) != 3:
|
||||
raise CatalogCompileError("CATALOG_TABLE_COLUMN_COUNT", repr(cells))
|
||||
return cells # type: ignore[return-value]
|
||||
|
||||
|
||||
def _is_separator_row(cells: tuple[str, str, str]) -> bool:
|
||||
return all(cell and set(cell) <= {"-", ":"} for cell in cells)
|
||||
|
||||
|
||||
def parse_catalog(path: Path) -> list[dict[str, Any]]:
|
||||
"""Return the 137 expanded source rows in physical table order."""
|
||||
|
||||
try:
|
||||
text = path.read_text(encoding="utf-8")
|
||||
except UnicodeDecodeError as exc:
|
||||
raise CatalogCompileError("CATALOG_UTF8_REQUIRED", str(exc)) from exc
|
||||
if text.startswith("\ufeff"):
|
||||
raise CatalogCompileError("CATALOG_UTF8_BOM_FORBIDDEN", path.as_posix())
|
||||
|
||||
rows = [cells for line in text.splitlines() if (cells := _markdown_cells(line))]
|
||||
if len(rows) < 2 or rows[0] != EXPECTED_HEADERS or not _is_separator_row(rows[1]):
|
||||
raise CatalogCompileError("CATALOG_TABLE_HEADER_MISMATCH", repr(rows[:2]))
|
||||
|
||||
expanded: list[dict[str, Any]] = []
|
||||
source_table_rows = rows[2:]
|
||||
for table_data_row, (major_category, dispute_type, case_kinds) in enumerate(
|
||||
source_table_rows, 1
|
||||
):
|
||||
if major_category in EXCLUDED_MAJOR_CATEGORIES:
|
||||
continue
|
||||
if not major_category:
|
||||
raise CatalogCompileError(
|
||||
"CATALOG_MAJOR_CATEGORY_REQUIRED", f"table_data_row={table_data_row}"
|
||||
)
|
||||
if case_kinds:
|
||||
names = [nfc(value) for value in case_kinds.split(",")]
|
||||
if not all(names):
|
||||
raise CatalogCompileError(
|
||||
"CATALOG_EMPTY_EXPANDED_NAME", f"table_data_row={table_data_row}"
|
||||
)
|
||||
source_entry_kind = "CATALOG_NAMED_ENTRY"
|
||||
else:
|
||||
if not dispute_type:
|
||||
raise CatalogCompileError(
|
||||
"CATALOG_GENERIC_SOURCE_LABEL_REQUIRED",
|
||||
f"table_data_row={table_data_row}",
|
||||
)
|
||||
names = [dispute_type]
|
||||
source_entry_kind = "GENERIC_SOURCE_ROW"
|
||||
|
||||
for expanded_item_ordinal, canonical_name in enumerate(names, 1):
|
||||
if nfc(canonical_name) != canonical_name:
|
||||
raise CatalogCompileError("CATALOG_NFC_NORMALIZATION_FAILED", canonical_name)
|
||||
expanded.append(
|
||||
{
|
||||
"canonical_name": canonical_name,
|
||||
"source_ref": {
|
||||
"table_data_row": table_data_row,
|
||||
"expanded_item_ordinal": expanded_item_ordinal,
|
||||
"major_category": major_category,
|
||||
"dispute_type": dispute_type,
|
||||
"source_entry_kind": source_entry_kind,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
names = [row["canonical_name"] for row in expanded]
|
||||
if len(expanded) != EXPECTED_ROW_COUNT:
|
||||
raise CatalogCompileError(
|
||||
"CASE_TYPE_CATALOG_EXACT_137_REQUIRED", f"observed={len(expanded)}"
|
||||
)
|
||||
if len(names) != len(set(names)):
|
||||
duplicates = sorted({name for name in names if names.count(name) > 1})
|
||||
raise CatalogCompileError("CASE_TYPE_CANONICAL_NAME_DUPLICATE", repr(duplicates))
|
||||
return expanded
|
||||
|
||||
|
||||
def compile_rows(
|
||||
source: Path,
|
||||
*,
|
||||
logical_source_path: str = DEFAULT_LOGICAL_SOURCE_PATH,
|
||||
) -> dict[str, Any]:
|
||||
raw = source.read_bytes()
|
||||
parsed = parse_catalog(source)
|
||||
case_types: list[dict[str, Any]] = []
|
||||
for ordinal, source_row in enumerate(parsed, 1):
|
||||
case_type_id = f"CT-{ordinal:03d}"
|
||||
case_types.append(
|
||||
{
|
||||
"catalog_row_id": f"CAT-{ordinal:03d}",
|
||||
"source_ordinal": ordinal,
|
||||
"source_label": source_row["canonical_name"],
|
||||
"case_type_id": case_type_id,
|
||||
"row_kind": "PENDING_LEGAL_CLASSIFICATION",
|
||||
"claim_capable_disposition": "PENDING_LEGAL_CONTENT",
|
||||
"case_type_predicates": [],
|
||||
"companion_case_type_ids": [],
|
||||
"legal_classification_status": "PENDING_LEGAL_CLASSIFICATION",
|
||||
"legal_content_status": "PENDING_LEGAL_CONTENT",
|
||||
"legal_review_receipt_ref": None,
|
||||
"legal_review_receipt_sha256": None,
|
||||
}
|
||||
)
|
||||
|
||||
catalog_row_ids = [row["catalog_row_id"] for row in case_types]
|
||||
if [row["case_type_id"] for row in case_types] != [
|
||||
f"CT-{ordinal:03d}" for ordinal in range(1, 138)
|
||||
]:
|
||||
raise CatalogCompileError("CASE_TYPE_ID_SEQUENCE_DRIFT", repr(case_types))
|
||||
|
||||
return {
|
||||
"schema_version": "stage2_case_type_registry.v1",
|
||||
"registry_id": "STAGE2_CASE_TYPE_137_V1",
|
||||
"catalog_source_ref": logical_source_path,
|
||||
"catalog_source_sha256": sha256_bytes(raw),
|
||||
"catalog_row_ids": catalog_row_ids,
|
||||
"rows": case_types,
|
||||
"claim_capable_case_type_ids": [],
|
||||
"pending_legal_classification_case_type_ids": [
|
||||
row["case_type_id"] for row in case_types
|
||||
],
|
||||
"fuzzy_match_allowed": False,
|
||||
"catch_all_fallback_allowed": False,
|
||||
"registry_status": "PENDING_LEGAL_CLASSIFICATION",
|
||||
}
|
||||
|
||||
|
||||
def _atomic_write(path: Path, payload: bytes) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
temporary_name: str | None = None
|
||||
try:
|
||||
with tempfile.NamedTemporaryFile(
|
||||
mode="wb",
|
||||
dir=path.parent,
|
||||
prefix=f".{path.name}.",
|
||||
suffix=".tmp",
|
||||
delete=False,
|
||||
) as handle:
|
||||
temporary_name = handle.name
|
||||
handle.write(payload)
|
||||
handle.flush()
|
||||
os.fsync(handle.fileno())
|
||||
os.replace(temporary_name, path)
|
||||
temporary_name = None
|
||||
finally:
|
||||
if temporary_name is not None:
|
||||
try:
|
||||
Path(temporary_name).unlink()
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
|
||||
def _parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Compile the canonical 137-kind civil-case catalog"
|
||||
)
|
||||
parser.add_argument("source", type=Path)
|
||||
parser.add_argument("output", type=Path)
|
||||
parser.add_argument(
|
||||
"--source-logical-path",
|
||||
default=DEFAULT_LOGICAL_SOURCE_PATH,
|
||||
help="version-free path recorded in the emitted registry",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--check",
|
||||
action="store_true",
|
||||
help="do not write; require output bytes to equal the deterministic projection",
|
||||
)
|
||||
return parser
|
||||
|
||||
|
||||
def main(argv: Iterable[str] | None = None) -> int:
|
||||
args = _parser().parse_args(list(argv) if argv is not None else None)
|
||||
try:
|
||||
payload = canonical_json_bytes(
|
||||
compile_rows(args.source, logical_source_path=args.source_logical_path)
|
||||
)
|
||||
if args.check:
|
||||
if not args.output.is_file() or args.output.read_bytes() != payload:
|
||||
raise CatalogCompileError(
|
||||
"CASE_TYPE_REGISTRY_PARITY_DRIFT", args.output.as_posix()
|
||||
)
|
||||
result = {"status": "PARITY_PASS", "sha256": sha256_bytes(payload)}
|
||||
else:
|
||||
_atomic_write(args.output, payload)
|
||||
result = {
|
||||
"status": "COMPILED",
|
||||
"row_count": EXPECTED_ROW_COUNT,
|
||||
"source_sha256": sha256_bytes(args.source.read_bytes()),
|
||||
"output_sha256": sha256_bytes(payload),
|
||||
}
|
||||
except (CatalogCompileError, OSError) as exc:
|
||||
result = {
|
||||
"status": "COMPILE_FAILED",
|
||||
"reason_code": getattr(exc, "code", type(exc).__name__),
|
||||
"detail": getattr(exc, "detail", str(exc)),
|
||||
}
|
||||
print(json.dumps(result, ensure_ascii=False, sort_keys=True))
|
||||
return 2
|
||||
print(json.dumps(result, ensure_ascii=False, sort_keys=True))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
+267
@@ -0,0 +1,267 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compile ``case_kinds.md`` into the canonical 137-row Stage 2 registry.
|
||||
|
||||
This is an offline, deterministic compiler. It reads the three-column source
|
||||
table, performs only Unicode/whitespace normalization and comma expansion, and
|
||||
emits legally *unclassified* rows. It must not infer claim capability,
|
||||
predicates, renderers, or any other legal content.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
from typing import Any, Iterable
|
||||
import unicodedata
|
||||
|
||||
|
||||
EXPECTED_HEADERS = ("소송 대분류", "분쟁 유형", "사건 종류")
|
||||
EXCLUDED_MAJOR_CATEGORIES = ("가사소송", "가족관계등록", "행정소송")
|
||||
EXPECTED_ROW_COUNT = 137
|
||||
DEFAULT_LOGICAL_SOURCE_PATH = "Case_02_Comparison_Research/case_kinds.md"
|
||||
|
||||
|
||||
class CatalogCompileError(ValueError):
|
||||
"""Controlled source/compile failure with a stable reason code."""
|
||||
|
||||
def __init__(self, code: str, detail: str) -> None:
|
||||
super().__init__(f"{code}: {detail}")
|
||||
self.code = code
|
||||
self.detail = detail
|
||||
|
||||
|
||||
def nfc(value: str) -> str:
|
||||
return unicodedata.normalize("NFC", value.strip())
|
||||
|
||||
|
||||
def sha256_bytes(value: bytes) -> str:
|
||||
return hashlib.sha256(value).hexdigest()
|
||||
|
||||
|
||||
def canonical_json_bytes(value: Any) -> bytes:
|
||||
return (
|
||||
json.dumps(
|
||||
value,
|
||||
ensure_ascii=False,
|
||||
allow_nan=False,
|
||||
indent=2,
|
||||
separators=(",", ": "),
|
||||
)
|
||||
+ "\n"
|
||||
).encode("utf-8")
|
||||
|
||||
|
||||
def _markdown_cells(line: str) -> tuple[str, str, str] | None:
|
||||
stripped = line.strip()
|
||||
if not (stripped.startswith("|") and stripped.endswith("|")):
|
||||
return None
|
||||
cells = tuple(nfc(cell) for cell in stripped[1:-1].split("|"))
|
||||
if len(cells) != 3:
|
||||
raise CatalogCompileError("CATALOG_TABLE_COLUMN_COUNT", repr(cells))
|
||||
return cells # type: ignore[return-value]
|
||||
|
||||
|
||||
def _is_separator_row(cells: tuple[str, str, str]) -> bool:
|
||||
return all(cell and set(cell) <= {"-", ":"} for cell in cells)
|
||||
|
||||
|
||||
def parse_catalog(path: Path) -> list[dict[str, Any]]:
|
||||
"""Return the 137 expanded source rows in physical table order."""
|
||||
|
||||
try:
|
||||
text = path.read_text(encoding="utf-8")
|
||||
except UnicodeDecodeError as exc:
|
||||
raise CatalogCompileError("CATALOG_UTF8_REQUIRED", str(exc)) from exc
|
||||
if text.startswith("\ufeff"):
|
||||
raise CatalogCompileError("CATALOG_UTF8_BOM_FORBIDDEN", path.as_posix())
|
||||
|
||||
rows = [cells for line in text.splitlines() if (cells := _markdown_cells(line))]
|
||||
if len(rows) < 2 or rows[0] != EXPECTED_HEADERS or not _is_separator_row(rows[1]):
|
||||
raise CatalogCompileError("CATALOG_TABLE_HEADER_MISMATCH", repr(rows[:2]))
|
||||
|
||||
expanded: list[dict[str, Any]] = []
|
||||
source_table_rows = rows[2:]
|
||||
for table_data_row, (major_category, dispute_type, case_kinds) in enumerate(
|
||||
source_table_rows, 1
|
||||
):
|
||||
if major_category in EXCLUDED_MAJOR_CATEGORIES:
|
||||
continue
|
||||
if not major_category:
|
||||
raise CatalogCompileError(
|
||||
"CATALOG_MAJOR_CATEGORY_REQUIRED", f"table_data_row={table_data_row}"
|
||||
)
|
||||
if case_kinds:
|
||||
names = [nfc(value) for value in case_kinds.split(",")]
|
||||
if not all(names):
|
||||
raise CatalogCompileError(
|
||||
"CATALOG_EMPTY_EXPANDED_NAME", f"table_data_row={table_data_row}"
|
||||
)
|
||||
source_entry_kind = "CATALOG_NAMED_ENTRY"
|
||||
else:
|
||||
if not dispute_type:
|
||||
raise CatalogCompileError(
|
||||
"CATALOG_GENERIC_SOURCE_LABEL_REQUIRED",
|
||||
f"table_data_row={table_data_row}",
|
||||
)
|
||||
names = [dispute_type]
|
||||
source_entry_kind = "GENERIC_SOURCE_ROW"
|
||||
|
||||
for expanded_item_ordinal, canonical_name in enumerate(names, 1):
|
||||
if nfc(canonical_name) != canonical_name:
|
||||
raise CatalogCompileError("CATALOG_NFC_NORMALIZATION_FAILED", canonical_name)
|
||||
expanded.append(
|
||||
{
|
||||
"canonical_name": canonical_name,
|
||||
"source_ref": {
|
||||
"table_data_row": table_data_row,
|
||||
"expanded_item_ordinal": expanded_item_ordinal,
|
||||
"major_category": major_category,
|
||||
"dispute_type": dispute_type,
|
||||
"source_entry_kind": source_entry_kind,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
names = [row["canonical_name"] for row in expanded]
|
||||
if len(expanded) != EXPECTED_ROW_COUNT:
|
||||
raise CatalogCompileError(
|
||||
"CASE_TYPE_CATALOG_EXACT_137_REQUIRED", f"observed={len(expanded)}"
|
||||
)
|
||||
if len(names) != len(set(names)):
|
||||
duplicates = sorted({name for name in names if names.count(name) > 1})
|
||||
raise CatalogCompileError("CASE_TYPE_CANONICAL_NAME_DUPLICATE", repr(duplicates))
|
||||
return expanded
|
||||
|
||||
|
||||
def compile_rows(
|
||||
source: Path,
|
||||
*,
|
||||
logical_source_path: str = DEFAULT_LOGICAL_SOURCE_PATH,
|
||||
) -> dict[str, Any]:
|
||||
raw = source.read_bytes()
|
||||
parsed = parse_catalog(source)
|
||||
case_types: list[dict[str, Any]] = []
|
||||
for ordinal, source_row in enumerate(parsed, 1):
|
||||
case_type_id = f"CT-{ordinal:03d}"
|
||||
case_types.append(
|
||||
{
|
||||
"catalog_row_id": f"CAT-{ordinal:03d}",
|
||||
"source_ordinal": ordinal,
|
||||
"source_label": source_row["canonical_name"],
|
||||
"case_type_id": case_type_id,
|
||||
"row_kind": "PENDING_LEGAL_CLASSIFICATION",
|
||||
"claim_capable_disposition": "PENDING_LEGAL_CONTENT",
|
||||
"case_type_predicates": [],
|
||||
"companion_case_type_ids": [],
|
||||
"legal_classification_status": "PENDING_LEGAL_CLASSIFICATION",
|
||||
"legal_content_status": "PENDING_LEGAL_CONTENT",
|
||||
"legal_review_receipt_ref": None,
|
||||
"legal_review_receipt_sha256": None,
|
||||
}
|
||||
)
|
||||
|
||||
catalog_row_ids = [row["catalog_row_id"] for row in case_types]
|
||||
if [row["case_type_id"] for row in case_types] != [
|
||||
f"CT-{ordinal:03d}" for ordinal in range(1, 138)
|
||||
]:
|
||||
raise CatalogCompileError("CASE_TYPE_ID_SEQUENCE_DRIFT", repr(case_types))
|
||||
|
||||
return {
|
||||
"schema_version": "stage2_case_type_registry.v1",
|
||||
"registry_id": "STAGE2_CASE_TYPE_137_V1",
|
||||
"catalog_source_ref": logical_source_path,
|
||||
"catalog_source_sha256": sha256_bytes(raw),
|
||||
"catalog_row_ids": catalog_row_ids,
|
||||
"rows": case_types,
|
||||
"claim_capable_case_type_ids": [],
|
||||
"pending_legal_classification_case_type_ids": [
|
||||
row["case_type_id"] for row in case_types
|
||||
],
|
||||
"fuzzy_match_allowed": False,
|
||||
"catch_all_fallback_allowed": False,
|
||||
"registry_status": "PENDING_LEGAL_CLASSIFICATION",
|
||||
}
|
||||
|
||||
|
||||
def _atomic_write(path: Path, payload: bytes) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
temporary_name: str | None = None
|
||||
try:
|
||||
with tempfile.NamedTemporaryFile(
|
||||
mode="wb",
|
||||
dir=path.parent,
|
||||
prefix=f".{path.name}.",
|
||||
suffix=".tmp",
|
||||
delete=False,
|
||||
) as handle:
|
||||
temporary_name = handle.name
|
||||
handle.write(payload)
|
||||
handle.flush()
|
||||
os.fsync(handle.fileno())
|
||||
os.replace(temporary_name, path)
|
||||
temporary_name = None
|
||||
finally:
|
||||
if temporary_name is not None:
|
||||
try:
|
||||
Path(temporary_name).unlink()
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
|
||||
def _parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Compile the canonical 137-kind civil-case catalog"
|
||||
)
|
||||
parser.add_argument("source", type=Path)
|
||||
parser.add_argument("output", type=Path)
|
||||
parser.add_argument(
|
||||
"--source-logical-path",
|
||||
default=DEFAULT_LOGICAL_SOURCE_PATH,
|
||||
help="version-free path recorded in the emitted registry",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--check",
|
||||
action="store_true",
|
||||
help="do not write; require output bytes to equal the deterministic projection",
|
||||
)
|
||||
return parser
|
||||
|
||||
|
||||
def main(argv: Iterable[str] | None = None) -> int:
|
||||
args = _parser().parse_args(list(argv) if argv is not None else None)
|
||||
try:
|
||||
payload = canonical_json_bytes(
|
||||
compile_rows(args.source, logical_source_path=args.source_logical_path)
|
||||
)
|
||||
if args.check:
|
||||
if not args.output.is_file() or args.output.read_bytes() != payload:
|
||||
raise CatalogCompileError(
|
||||
"CASE_TYPE_REGISTRY_PARITY_DRIFT", args.output.as_posix()
|
||||
)
|
||||
result = {"status": "PARITY_PASS", "sha256": sha256_bytes(payload)}
|
||||
else:
|
||||
_atomic_write(args.output, payload)
|
||||
result = {
|
||||
"status": "COMPILED",
|
||||
"row_count": EXPECTED_ROW_COUNT,
|
||||
"source_sha256": sha256_bytes(args.source.read_bytes()),
|
||||
"output_sha256": sha256_bytes(payload),
|
||||
}
|
||||
except (CatalogCompileError, OSError) as exc:
|
||||
result = {
|
||||
"status": "COMPILE_FAILED",
|
||||
"reason_code": getattr(exc, "code", type(exc).__name__),
|
||||
"detail": getattr(exc, "detail", str(exc)),
|
||||
}
|
||||
print(json.dumps(result, ensure_ascii=False, sort_keys=True))
|
||||
return 2
|
||||
print(json.dumps(result, ensure_ascii=False, sort_keys=True))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
+1006
File diff suppressed because it is too large
Load Diff
+1006
File diff suppressed because it is too large
Load Diff
+30
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"schema_version": "stage2_binding_signature_projection_registry.v1",
|
||||
"registry_id": "S2_20_BINDING_SIGNATURE_PROJECTION_V1",
|
||||
"source_schema_ref": "schemas/domain_verdict.schema.json#/$defs/normalized_claim_signature",
|
||||
"source_field_allowlist": [
|
||||
"legal_basis_family",
|
||||
"right_holder_refs",
|
||||
"obligor_refs",
|
||||
"performance_kind",
|
||||
"object_refs",
|
||||
"legal_effect",
|
||||
"source_occurrence_refs"
|
||||
],
|
||||
"target_field_order": [
|
||||
"claim_family",
|
||||
"legal_effect",
|
||||
"performance_kind",
|
||||
"object_kind",
|
||||
"party_role_pattern",
|
||||
"source_obligation_kind",
|
||||
"remedy_mode",
|
||||
"registry_action",
|
||||
"procedural_posture",
|
||||
"special_statute_tags"
|
||||
],
|
||||
"projection_rules": [],
|
||||
"free_text_fallback_allowed": false,
|
||||
"fuzzy_matching_allowed": false,
|
||||
"registry_status": "PENDING_LEGAL_REVIEW"
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"schema_version": "stage2.s2_20.calculation_descriptor.v1",
|
||||
"calculator_id": "CE-01",
|
||||
"descriptor_id": "CE-01-INTEREST-DELAY",
|
||||
"description": "obligation-and-defendant event rate timeline",
|
||||
"status": "PENDING_LEGAL_CONTENT",
|
||||
"execution_eligible": false,
|
||||
"formula_rows": [],
|
||||
"operand_contract": {
|
||||
"required_operands": [],
|
||||
"optional_operands": [],
|
||||
"unit_contracts": [],
|
||||
"missing_operand_policy": "ISSUE_AND_NO_RESULT"
|
||||
},
|
||||
"law_value_contract": {
|
||||
"law_value_refs": [],
|
||||
"literal_output_affecting_legal_numbers_forbidden": true,
|
||||
"effective_interval_required": true,
|
||||
"authority_release_binding_required": true
|
||||
},
|
||||
"numeric_contract": {
|
||||
"decimal_only": true,
|
||||
"binary_float_forbidden": true,
|
||||
"rounding_rule_required_per_formula": true,
|
||||
"explicit_units_required": true
|
||||
},
|
||||
"receipt_contract": {
|
||||
"required_fields": [
|
||||
"calculation_receipt_id",
|
||||
"calculator_id",
|
||||
"formula_id",
|
||||
"operand_refs",
|
||||
"units",
|
||||
"law_value_id",
|
||||
"effective_interval",
|
||||
"rounding_rule",
|
||||
"period_rows",
|
||||
"missing_operands",
|
||||
"result_sha256"
|
||||
]
|
||||
},
|
||||
"failure_codes": [
|
||||
"CALCULATION_DESCRIPTOR_NOT_APPROVED",
|
||||
"CALCULATION_FORMULA_NOT_AVAILABLE",
|
||||
"CALCULATION_OPERAND_MISSING",
|
||||
"CALCULATION_LAW_VALUE_UNRESOLVED",
|
||||
"CALCULATION_TEMPORAL_GAP",
|
||||
"CALCULATION_UNIT_MISMATCH"
|
||||
],
|
||||
"review": {
|
||||
"required_role": "KOREAN_LAWYER",
|
||||
"status": "PENDING_KOREAN_LAWYER_REVIEW"
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"schema_version": "stage2.s2_20.calculation_descriptor.v1",
|
||||
"calculator_id": "CE-02",
|
||||
"descriptor_id": "CE-02-ALLOCATION-SETOFF-BALANCE",
|
||||
"description": "allocation, setoff, and remaining balance",
|
||||
"status": "PENDING_LEGAL_CONTENT",
|
||||
"execution_eligible": false,
|
||||
"formula_rows": [],
|
||||
"operand_contract": {
|
||||
"required_operands": [],
|
||||
"optional_operands": [],
|
||||
"unit_contracts": [],
|
||||
"missing_operand_policy": "ISSUE_AND_NO_RESULT"
|
||||
},
|
||||
"law_value_contract": {
|
||||
"law_value_refs": [],
|
||||
"literal_output_affecting_legal_numbers_forbidden": true,
|
||||
"effective_interval_required": true,
|
||||
"authority_release_binding_required": true
|
||||
},
|
||||
"numeric_contract": {
|
||||
"decimal_only": true,
|
||||
"binary_float_forbidden": true,
|
||||
"rounding_rule_required_per_formula": true,
|
||||
"explicit_units_required": true
|
||||
},
|
||||
"receipt_contract": {
|
||||
"required_fields": [
|
||||
"calculation_receipt_id",
|
||||
"calculator_id",
|
||||
"formula_id",
|
||||
"operand_refs",
|
||||
"units",
|
||||
"law_value_id",
|
||||
"effective_interval",
|
||||
"rounding_rule",
|
||||
"period_rows",
|
||||
"missing_operands",
|
||||
"result_sha256"
|
||||
]
|
||||
},
|
||||
"failure_codes": [
|
||||
"CALCULATION_DESCRIPTOR_NOT_APPROVED",
|
||||
"CALCULATION_FORMULA_NOT_AVAILABLE",
|
||||
"CALCULATION_OPERAND_MISSING",
|
||||
"CALCULATION_LAW_VALUE_UNRESOLVED",
|
||||
"CALCULATION_TEMPORAL_GAP",
|
||||
"CALCULATION_UNIT_MISMATCH"
|
||||
],
|
||||
"review": {
|
||||
"required_role": "KOREAN_LAWYER",
|
||||
"status": "PENDING_KOREAN_LAWYER_REVIEW"
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"schema_version": "stage2.s2_20.calculation_descriptor.v1",
|
||||
"calculator_id": "CE-03",
|
||||
"descriptor_id": "CE-03-LIMITATION-DEADLINE",
|
||||
"description": "limitation, exclusion, filing deadline, and urgency",
|
||||
"status": "PENDING_LEGAL_CONTENT",
|
||||
"execution_eligible": false,
|
||||
"formula_rows": [],
|
||||
"operand_contract": {
|
||||
"required_operands": [],
|
||||
"optional_operands": [],
|
||||
"unit_contracts": [],
|
||||
"missing_operand_policy": "ISSUE_AND_NO_RESULT"
|
||||
},
|
||||
"law_value_contract": {
|
||||
"law_value_refs": [],
|
||||
"literal_output_affecting_legal_numbers_forbidden": true,
|
||||
"effective_interval_required": true,
|
||||
"authority_release_binding_required": true
|
||||
},
|
||||
"numeric_contract": {
|
||||
"decimal_only": true,
|
||||
"binary_float_forbidden": true,
|
||||
"rounding_rule_required_per_formula": true,
|
||||
"explicit_units_required": true
|
||||
},
|
||||
"receipt_contract": {
|
||||
"required_fields": [
|
||||
"calculation_receipt_id",
|
||||
"calculator_id",
|
||||
"formula_id",
|
||||
"operand_refs",
|
||||
"units",
|
||||
"law_value_id",
|
||||
"effective_interval",
|
||||
"rounding_rule",
|
||||
"period_rows",
|
||||
"missing_operands",
|
||||
"result_sha256"
|
||||
]
|
||||
},
|
||||
"failure_codes": [
|
||||
"CALCULATION_DESCRIPTOR_NOT_APPROVED",
|
||||
"CALCULATION_FORMULA_NOT_AVAILABLE",
|
||||
"CALCULATION_OPERAND_MISSING",
|
||||
"CALCULATION_LAW_VALUE_UNRESOLVED",
|
||||
"CALCULATION_TEMPORAL_GAP",
|
||||
"CALCULATION_UNIT_MISMATCH"
|
||||
],
|
||||
"review": {
|
||||
"required_role": "KOREAN_LAWYER",
|
||||
"status": "PENDING_KOREAN_LAWYER_REVIEW"
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"schema_version": "stage2.s2_20.calculation_descriptor.v1",
|
||||
"calculator_id": "CE-04",
|
||||
"descriptor_id": "CE-04-VALUATION",
|
||||
"description": "use gain and object valuation",
|
||||
"status": "PENDING_LEGAL_CONTENT",
|
||||
"execution_eligible": false,
|
||||
"formula_rows": [],
|
||||
"operand_contract": {
|
||||
"required_operands": [],
|
||||
"optional_operands": [],
|
||||
"unit_contracts": [],
|
||||
"missing_operand_policy": "ISSUE_AND_NO_RESULT"
|
||||
},
|
||||
"law_value_contract": {
|
||||
"law_value_refs": [],
|
||||
"literal_output_affecting_legal_numbers_forbidden": true,
|
||||
"effective_interval_required": true,
|
||||
"authority_release_binding_required": true
|
||||
},
|
||||
"numeric_contract": {
|
||||
"decimal_only": true,
|
||||
"binary_float_forbidden": true,
|
||||
"rounding_rule_required_per_formula": true,
|
||||
"explicit_units_required": true
|
||||
},
|
||||
"receipt_contract": {
|
||||
"required_fields": [
|
||||
"calculation_receipt_id",
|
||||
"calculator_id",
|
||||
"formula_id",
|
||||
"operand_refs",
|
||||
"units",
|
||||
"law_value_id",
|
||||
"effective_interval",
|
||||
"rounding_rule",
|
||||
"period_rows",
|
||||
"missing_operands",
|
||||
"result_sha256"
|
||||
]
|
||||
},
|
||||
"failure_codes": [
|
||||
"CALCULATION_DESCRIPTOR_NOT_APPROVED",
|
||||
"CALCULATION_FORMULA_NOT_AVAILABLE",
|
||||
"CALCULATION_OPERAND_MISSING",
|
||||
"CALCULATION_LAW_VALUE_UNRESOLVED",
|
||||
"CALCULATION_TEMPORAL_GAP",
|
||||
"CALCULATION_UNIT_MISMATCH"
|
||||
],
|
||||
"review": {
|
||||
"required_role": "KOREAN_LAWYER",
|
||||
"status": "PENDING_KOREAN_LAWYER_REVIEW"
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"schema_version": "stage2.s2_20.calculation_descriptor.v1",
|
||||
"calculator_id": "CE-05",
|
||||
"descriptor_id": "CE-05-PERSONAL-INJURY",
|
||||
"description": "lost earnings, treatment cost, and comparative fault",
|
||||
"status": "PENDING_LEGAL_CONTENT",
|
||||
"execution_eligible": false,
|
||||
"formula_rows": [],
|
||||
"operand_contract": {
|
||||
"required_operands": [],
|
||||
"optional_operands": [],
|
||||
"unit_contracts": [],
|
||||
"missing_operand_policy": "ISSUE_AND_NO_RESULT"
|
||||
},
|
||||
"law_value_contract": {
|
||||
"law_value_refs": [],
|
||||
"literal_output_affecting_legal_numbers_forbidden": true,
|
||||
"effective_interval_required": true,
|
||||
"authority_release_binding_required": true
|
||||
},
|
||||
"numeric_contract": {
|
||||
"decimal_only": true,
|
||||
"binary_float_forbidden": true,
|
||||
"rounding_rule_required_per_formula": true,
|
||||
"explicit_units_required": true
|
||||
},
|
||||
"receipt_contract": {
|
||||
"required_fields": [
|
||||
"calculation_receipt_id",
|
||||
"calculator_id",
|
||||
"formula_id",
|
||||
"operand_refs",
|
||||
"units",
|
||||
"law_value_id",
|
||||
"effective_interval",
|
||||
"rounding_rule",
|
||||
"period_rows",
|
||||
"missing_operands",
|
||||
"result_sha256"
|
||||
]
|
||||
},
|
||||
"failure_codes": [
|
||||
"CALCULATION_DESCRIPTOR_NOT_APPROVED",
|
||||
"CALCULATION_FORMULA_NOT_AVAILABLE",
|
||||
"CALCULATION_OPERAND_MISSING",
|
||||
"CALCULATION_LAW_VALUE_UNRESOLVED",
|
||||
"CALCULATION_TEMPORAL_GAP",
|
||||
"CALCULATION_UNIT_MISMATCH"
|
||||
],
|
||||
"review": {
|
||||
"required_role": "KOREAN_LAWYER",
|
||||
"status": "PENDING_KOREAN_LAWYER_REVIEW"
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"schema_version": "stage2.s2_20.calculation_descriptor.v1",
|
||||
"calculator_id": "CE-06",
|
||||
"descriptor_id": "CE-06-CONSTRUCTION-DEFECT",
|
||||
"description": "progress payment, extra work, defect repair, and reduction",
|
||||
"status": "PENDING_LEGAL_CONTENT",
|
||||
"execution_eligible": false,
|
||||
"formula_rows": [],
|
||||
"operand_contract": {
|
||||
"required_operands": [],
|
||||
"optional_operands": [],
|
||||
"unit_contracts": [],
|
||||
"missing_operand_policy": "ISSUE_AND_NO_RESULT"
|
||||
},
|
||||
"law_value_contract": {
|
||||
"law_value_refs": [],
|
||||
"literal_output_affecting_legal_numbers_forbidden": true,
|
||||
"effective_interval_required": true,
|
||||
"authority_release_binding_required": true
|
||||
},
|
||||
"numeric_contract": {
|
||||
"decimal_only": true,
|
||||
"binary_float_forbidden": true,
|
||||
"rounding_rule_required_per_formula": true,
|
||||
"explicit_units_required": true
|
||||
},
|
||||
"receipt_contract": {
|
||||
"required_fields": [
|
||||
"calculation_receipt_id",
|
||||
"calculator_id",
|
||||
"formula_id",
|
||||
"operand_refs",
|
||||
"units",
|
||||
"law_value_id",
|
||||
"effective_interval",
|
||||
"rounding_rule",
|
||||
"period_rows",
|
||||
"missing_operands",
|
||||
"result_sha256"
|
||||
]
|
||||
},
|
||||
"failure_codes": [
|
||||
"CALCULATION_DESCRIPTOR_NOT_APPROVED",
|
||||
"CALCULATION_FORMULA_NOT_AVAILABLE",
|
||||
"CALCULATION_OPERAND_MISSING",
|
||||
"CALCULATION_LAW_VALUE_UNRESOLVED",
|
||||
"CALCULATION_TEMPORAL_GAP",
|
||||
"CALCULATION_UNIT_MISMATCH"
|
||||
],
|
||||
"review": {
|
||||
"required_role": "KOREAN_LAWYER",
|
||||
"status": "PENDING_KOREAN_LAWYER_REVIEW"
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"schema_version": "stage2.s2_20.calculation_descriptor.v1",
|
||||
"calculator_id": "CE-07",
|
||||
"descriptor_id": "CE-07-LEASE-USE-GAIN",
|
||||
"description": "lease deductions and use gain",
|
||||
"status": "PENDING_LEGAL_CONTENT",
|
||||
"execution_eligible": false,
|
||||
"formula_rows": [],
|
||||
"operand_contract": {
|
||||
"required_operands": [],
|
||||
"optional_operands": [],
|
||||
"unit_contracts": [],
|
||||
"missing_operand_policy": "ISSUE_AND_NO_RESULT"
|
||||
},
|
||||
"law_value_contract": {
|
||||
"law_value_refs": [],
|
||||
"literal_output_affecting_legal_numbers_forbidden": true,
|
||||
"effective_interval_required": true,
|
||||
"authority_release_binding_required": true
|
||||
},
|
||||
"numeric_contract": {
|
||||
"decimal_only": true,
|
||||
"binary_float_forbidden": true,
|
||||
"rounding_rule_required_per_formula": true,
|
||||
"explicit_units_required": true
|
||||
},
|
||||
"receipt_contract": {
|
||||
"required_fields": [
|
||||
"calculation_receipt_id",
|
||||
"calculator_id",
|
||||
"formula_id",
|
||||
"operand_refs",
|
||||
"units",
|
||||
"law_value_id",
|
||||
"effective_interval",
|
||||
"rounding_rule",
|
||||
"period_rows",
|
||||
"missing_operands",
|
||||
"result_sha256"
|
||||
]
|
||||
},
|
||||
"failure_codes": [
|
||||
"CALCULATION_DESCRIPTOR_NOT_APPROVED",
|
||||
"CALCULATION_FORMULA_NOT_AVAILABLE",
|
||||
"CALCULATION_OPERAND_MISSING",
|
||||
"CALCULATION_LAW_VALUE_UNRESOLVED",
|
||||
"CALCULATION_TEMPORAL_GAP",
|
||||
"CALCULATION_UNIT_MISMATCH"
|
||||
],
|
||||
"review": {
|
||||
"required_role": "KOREAN_LAWYER",
|
||||
"status": "PENDING_KOREAN_LAWYER_REVIEW"
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"schema_version": "stage2.s2_20.calculation_descriptor.v1",
|
||||
"calculator_id": "CE-08",
|
||||
"descriptor_id": "CE-08-INHERITANCE-RESERVED-SHARE",
|
||||
"description": "reserved-share value and temporal remedy branch",
|
||||
"status": "PENDING_LEGAL_CONTENT",
|
||||
"execution_eligible": false,
|
||||
"formula_rows": [],
|
||||
"operand_contract": {
|
||||
"required_operands": [],
|
||||
"optional_operands": [],
|
||||
"unit_contracts": [],
|
||||
"missing_operand_policy": "ISSUE_AND_NO_RESULT"
|
||||
},
|
||||
"law_value_contract": {
|
||||
"law_value_refs": [],
|
||||
"literal_output_affecting_legal_numbers_forbidden": true,
|
||||
"effective_interval_required": true,
|
||||
"authority_release_binding_required": true
|
||||
},
|
||||
"numeric_contract": {
|
||||
"decimal_only": true,
|
||||
"binary_float_forbidden": true,
|
||||
"rounding_rule_required_per_formula": true,
|
||||
"explicit_units_required": true
|
||||
},
|
||||
"receipt_contract": {
|
||||
"required_fields": [
|
||||
"calculation_receipt_id",
|
||||
"calculator_id",
|
||||
"formula_id",
|
||||
"operand_refs",
|
||||
"units",
|
||||
"law_value_id",
|
||||
"effective_interval",
|
||||
"rounding_rule",
|
||||
"period_rows",
|
||||
"missing_operands",
|
||||
"result_sha256"
|
||||
]
|
||||
},
|
||||
"failure_codes": [
|
||||
"CALCULATION_DESCRIPTOR_NOT_APPROVED",
|
||||
"CALCULATION_FORMULA_NOT_AVAILABLE",
|
||||
"CALCULATION_OPERAND_MISSING",
|
||||
"CALCULATION_LAW_VALUE_UNRESOLVED",
|
||||
"CALCULATION_TEMPORAL_GAP",
|
||||
"CALCULATION_UNIT_MISMATCH"
|
||||
],
|
||||
"review": {
|
||||
"required_role": "KOREAN_LAWYER",
|
||||
"status": "PENDING_KOREAN_LAWYER_REVIEW"
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"schema_version": "stage2.s2_20.calculation_descriptor.v1",
|
||||
"calculator_id": "CE-09",
|
||||
"descriptor_id": "CE-09-WAGE-SEVERANCE",
|
||||
"description": "wage, allowance, and severance",
|
||||
"status": "PENDING_LEGAL_CONTENT",
|
||||
"execution_eligible": false,
|
||||
"formula_rows": [],
|
||||
"operand_contract": {
|
||||
"required_operands": [],
|
||||
"optional_operands": [],
|
||||
"unit_contracts": [],
|
||||
"missing_operand_policy": "ISSUE_AND_NO_RESULT"
|
||||
},
|
||||
"law_value_contract": {
|
||||
"law_value_refs": [],
|
||||
"literal_output_affecting_legal_numbers_forbidden": true,
|
||||
"effective_interval_required": true,
|
||||
"authority_release_binding_required": true
|
||||
},
|
||||
"numeric_contract": {
|
||||
"decimal_only": true,
|
||||
"binary_float_forbidden": true,
|
||||
"rounding_rule_required_per_formula": true,
|
||||
"explicit_units_required": true
|
||||
},
|
||||
"receipt_contract": {
|
||||
"required_fields": [
|
||||
"calculation_receipt_id",
|
||||
"calculator_id",
|
||||
"formula_id",
|
||||
"operand_refs",
|
||||
"units",
|
||||
"law_value_id",
|
||||
"effective_interval",
|
||||
"rounding_rule",
|
||||
"period_rows",
|
||||
"missing_operands",
|
||||
"result_sha256"
|
||||
]
|
||||
},
|
||||
"failure_codes": [
|
||||
"CALCULATION_DESCRIPTOR_NOT_APPROVED",
|
||||
"CALCULATION_FORMULA_NOT_AVAILABLE",
|
||||
"CALCULATION_OPERAND_MISSING",
|
||||
"CALCULATION_LAW_VALUE_UNRESOLVED",
|
||||
"CALCULATION_TEMPORAL_GAP",
|
||||
"CALCULATION_UNIT_MISMATCH"
|
||||
],
|
||||
"review": {
|
||||
"required_role": "KOREAN_LAWYER",
|
||||
"status": "PENDING_KOREAN_LAWYER_REVIEW"
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"schema_version": "stage2.s2_20.calculation_descriptor.v1",
|
||||
"calculator_id": "CE-10",
|
||||
"descriptor_id": "CE-10-ACTIO-INSOLVENCY-VALUE",
|
||||
"description": "preserved claim, common security, and beneficiary/transferee caps",
|
||||
"status": "PENDING_LEGAL_CONTENT",
|
||||
"execution_eligible": false,
|
||||
"formula_rows": [],
|
||||
"operand_contract": {
|
||||
"required_operands": [],
|
||||
"optional_operands": [],
|
||||
"unit_contracts": [],
|
||||
"missing_operand_policy": "ISSUE_AND_NO_RESULT"
|
||||
},
|
||||
"law_value_contract": {
|
||||
"law_value_refs": [],
|
||||
"literal_output_affecting_legal_numbers_forbidden": true,
|
||||
"effective_interval_required": true,
|
||||
"authority_release_binding_required": true
|
||||
},
|
||||
"numeric_contract": {
|
||||
"decimal_only": true,
|
||||
"binary_float_forbidden": true,
|
||||
"rounding_rule_required_per_formula": true,
|
||||
"explicit_units_required": true
|
||||
},
|
||||
"receipt_contract": {
|
||||
"required_fields": [
|
||||
"calculation_receipt_id",
|
||||
"calculator_id",
|
||||
"formula_id",
|
||||
"operand_refs",
|
||||
"units",
|
||||
"law_value_id",
|
||||
"effective_interval",
|
||||
"rounding_rule",
|
||||
"period_rows",
|
||||
"missing_operands",
|
||||
"result_sha256"
|
||||
]
|
||||
},
|
||||
"failure_codes": [
|
||||
"CALCULATION_DESCRIPTOR_NOT_APPROVED",
|
||||
"CALCULATION_FORMULA_NOT_AVAILABLE",
|
||||
"CALCULATION_OPERAND_MISSING",
|
||||
"CALCULATION_LAW_VALUE_UNRESOLVED",
|
||||
"CALCULATION_TEMPORAL_GAP",
|
||||
"CALCULATION_UNIT_MISMATCH"
|
||||
],
|
||||
"review": {
|
||||
"required_role": "KOREAN_LAWYER",
|
||||
"status": "PENDING_KOREAN_LAWYER_REVIEW"
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"schema_version": "stage2.s2_20.calculation_descriptor.v1",
|
||||
"calculator_id": "CE-11",
|
||||
"descriptor_id": "CE-11-DISTRIBUTION-SHARE-DIVISION",
|
||||
"description": "distribution, share, and division amount",
|
||||
"status": "PENDING_LEGAL_CONTENT",
|
||||
"execution_eligible": false,
|
||||
"formula_rows": [],
|
||||
"operand_contract": {
|
||||
"required_operands": [],
|
||||
"optional_operands": [],
|
||||
"unit_contracts": [],
|
||||
"missing_operand_policy": "ISSUE_AND_NO_RESULT"
|
||||
},
|
||||
"law_value_contract": {
|
||||
"law_value_refs": [],
|
||||
"literal_output_affecting_legal_numbers_forbidden": true,
|
||||
"effective_interval_required": true,
|
||||
"authority_release_binding_required": true
|
||||
},
|
||||
"numeric_contract": {
|
||||
"decimal_only": true,
|
||||
"binary_float_forbidden": true,
|
||||
"rounding_rule_required_per_formula": true,
|
||||
"explicit_units_required": true
|
||||
},
|
||||
"receipt_contract": {
|
||||
"required_fields": [
|
||||
"calculation_receipt_id",
|
||||
"calculator_id",
|
||||
"formula_id",
|
||||
"operand_refs",
|
||||
"units",
|
||||
"law_value_id",
|
||||
"effective_interval",
|
||||
"rounding_rule",
|
||||
"period_rows",
|
||||
"missing_operands",
|
||||
"result_sha256"
|
||||
]
|
||||
},
|
||||
"failure_codes": [
|
||||
"CALCULATION_DESCRIPTOR_NOT_APPROVED",
|
||||
"CALCULATION_FORMULA_NOT_AVAILABLE",
|
||||
"CALCULATION_OPERAND_MISSING",
|
||||
"CALCULATION_LAW_VALUE_UNRESOLVED",
|
||||
"CALCULATION_TEMPORAL_GAP",
|
||||
"CALCULATION_UNIT_MISMATCH"
|
||||
],
|
||||
"review": {
|
||||
"required_role": "KOREAN_LAWYER",
|
||||
"status": "PENDING_KOREAN_LAWYER_REVIEW"
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"schema_version": "stage2.s2_20.calculation_descriptor.v1",
|
||||
"calculator_id": "CE-12",
|
||||
"descriptor_id": "CE-12-INSURANCE",
|
||||
"description": "insured value, duplicate insurance, and deduction",
|
||||
"status": "PENDING_LEGAL_CONTENT",
|
||||
"execution_eligible": false,
|
||||
"formula_rows": [],
|
||||
"operand_contract": {
|
||||
"required_operands": [],
|
||||
"optional_operands": [],
|
||||
"unit_contracts": [],
|
||||
"missing_operand_policy": "ISSUE_AND_NO_RESULT"
|
||||
},
|
||||
"law_value_contract": {
|
||||
"law_value_refs": [],
|
||||
"literal_output_affecting_legal_numbers_forbidden": true,
|
||||
"effective_interval_required": true,
|
||||
"authority_release_binding_required": true
|
||||
},
|
||||
"numeric_contract": {
|
||||
"decimal_only": true,
|
||||
"binary_float_forbidden": true,
|
||||
"rounding_rule_required_per_formula": true,
|
||||
"explicit_units_required": true
|
||||
},
|
||||
"receipt_contract": {
|
||||
"required_fields": [
|
||||
"calculation_receipt_id",
|
||||
"calculator_id",
|
||||
"formula_id",
|
||||
"operand_refs",
|
||||
"units",
|
||||
"law_value_id",
|
||||
"effective_interval",
|
||||
"rounding_rule",
|
||||
"period_rows",
|
||||
"missing_operands",
|
||||
"result_sha256"
|
||||
]
|
||||
},
|
||||
"failure_codes": [
|
||||
"CALCULATION_DESCRIPTOR_NOT_APPROVED",
|
||||
"CALCULATION_FORMULA_NOT_AVAILABLE",
|
||||
"CALCULATION_OPERAND_MISSING",
|
||||
"CALCULATION_LAW_VALUE_UNRESOLVED",
|
||||
"CALCULATION_TEMPORAL_GAP",
|
||||
"CALCULATION_UNIT_MISMATCH"
|
||||
],
|
||||
"review": {
|
||||
"required_role": "KOREAN_LAWYER",
|
||||
"status": "PENDING_KOREAN_LAWYER_REVIEW"
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"schema_version": "stage2.s2_20.calculation_descriptor.v1",
|
||||
"calculator_id": "CE-13",
|
||||
"descriptor_id": "CE-13-COURT-VALUE-COST",
|
||||
"description": "amount in controversy, filing fee, service cost, joinder, and incidental claim",
|
||||
"status": "PENDING_LEGAL_CONTENT",
|
||||
"execution_eligible": false,
|
||||
"formula_rows": [],
|
||||
"operand_contract": {
|
||||
"required_operands": [],
|
||||
"optional_operands": [],
|
||||
"unit_contracts": [],
|
||||
"missing_operand_policy": "ISSUE_AND_NO_RESULT"
|
||||
},
|
||||
"law_value_contract": {
|
||||
"law_value_refs": [],
|
||||
"literal_output_affecting_legal_numbers_forbidden": true,
|
||||
"effective_interval_required": true,
|
||||
"authority_release_binding_required": true
|
||||
},
|
||||
"numeric_contract": {
|
||||
"decimal_only": true,
|
||||
"binary_float_forbidden": true,
|
||||
"rounding_rule_required_per_formula": true,
|
||||
"explicit_units_required": true
|
||||
},
|
||||
"receipt_contract": {
|
||||
"required_fields": [
|
||||
"calculation_receipt_id",
|
||||
"calculator_id",
|
||||
"formula_id",
|
||||
"operand_refs",
|
||||
"units",
|
||||
"law_value_id",
|
||||
"effective_interval",
|
||||
"rounding_rule",
|
||||
"period_rows",
|
||||
"missing_operands",
|
||||
"result_sha256"
|
||||
]
|
||||
},
|
||||
"failure_codes": [
|
||||
"CALCULATION_DESCRIPTOR_NOT_APPROVED",
|
||||
"CALCULATION_FORMULA_NOT_AVAILABLE",
|
||||
"CALCULATION_OPERAND_MISSING",
|
||||
"CALCULATION_LAW_VALUE_UNRESOLVED",
|
||||
"CALCULATION_TEMPORAL_GAP",
|
||||
"CALCULATION_UNIT_MISMATCH"
|
||||
],
|
||||
"review": {
|
||||
"required_role": "KOREAN_LAWYER",
|
||||
"status": "PENDING_KOREAN_LAWYER_REVIEW"
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"schema_version": "stage2.s2_20.calculation_descriptor.v1",
|
||||
"calculator_id": "CE-R1",
|
||||
"descriptor_id": "CE-R1-IP-DAMAGE",
|
||||
"description": "intellectual-property damage",
|
||||
"status": "PENDING_LEGAL_CONTENT",
|
||||
"execution_eligible": false,
|
||||
"formula_rows": [],
|
||||
"operand_contract": {
|
||||
"required_operands": [],
|
||||
"optional_operands": [],
|
||||
"unit_contracts": [],
|
||||
"missing_operand_policy": "ISSUE_AND_NO_RESULT"
|
||||
},
|
||||
"law_value_contract": {
|
||||
"law_value_refs": [],
|
||||
"literal_output_affecting_legal_numbers_forbidden": true,
|
||||
"effective_interval_required": true,
|
||||
"authority_release_binding_required": true
|
||||
},
|
||||
"numeric_contract": {
|
||||
"decimal_only": true,
|
||||
"binary_float_forbidden": true,
|
||||
"rounding_rule_required_per_formula": true,
|
||||
"explicit_units_required": true
|
||||
},
|
||||
"receipt_contract": {
|
||||
"required_fields": [
|
||||
"calculation_receipt_id",
|
||||
"calculator_id",
|
||||
"formula_id",
|
||||
"operand_refs",
|
||||
"units",
|
||||
"law_value_id",
|
||||
"effective_interval",
|
||||
"rounding_rule",
|
||||
"period_rows",
|
||||
"missing_operands",
|
||||
"result_sha256"
|
||||
]
|
||||
},
|
||||
"failure_codes": [
|
||||
"CALCULATION_DESCRIPTOR_NOT_APPROVED",
|
||||
"CALCULATION_FORMULA_NOT_AVAILABLE",
|
||||
"CALCULATION_OPERAND_MISSING",
|
||||
"CALCULATION_LAW_VALUE_UNRESOLVED",
|
||||
"CALCULATION_TEMPORAL_GAP",
|
||||
"CALCULATION_UNIT_MISMATCH"
|
||||
],
|
||||
"review": {
|
||||
"required_role": "KOREAN_LAWYER",
|
||||
"status": "PENDING_KOREAN_LAWYER_REVIEW"
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"schema_version": "stage2.s2_20.calculation_descriptor.v1",
|
||||
"calculator_id": "CE-R2",
|
||||
"descriptor_id": "CE-R2-ORG-LIQUIDATION",
|
||||
"description": "organization and partnership liquidation/distribution",
|
||||
"status": "PENDING_LEGAL_CONTENT",
|
||||
"execution_eligible": false,
|
||||
"formula_rows": [],
|
||||
"operand_contract": {
|
||||
"required_operands": [],
|
||||
"optional_operands": [],
|
||||
"unit_contracts": [],
|
||||
"missing_operand_policy": "ISSUE_AND_NO_RESULT"
|
||||
},
|
||||
"law_value_contract": {
|
||||
"law_value_refs": [],
|
||||
"literal_output_affecting_legal_numbers_forbidden": true,
|
||||
"effective_interval_required": true,
|
||||
"authority_release_binding_required": true
|
||||
},
|
||||
"numeric_contract": {
|
||||
"decimal_only": true,
|
||||
"binary_float_forbidden": true,
|
||||
"rounding_rule_required_per_formula": true,
|
||||
"explicit_units_required": true
|
||||
},
|
||||
"receipt_contract": {
|
||||
"required_fields": [
|
||||
"calculation_receipt_id",
|
||||
"calculator_id",
|
||||
"formula_id",
|
||||
"operand_refs",
|
||||
"units",
|
||||
"law_value_id",
|
||||
"effective_interval",
|
||||
"rounding_rule",
|
||||
"period_rows",
|
||||
"missing_operands",
|
||||
"result_sha256"
|
||||
]
|
||||
},
|
||||
"failure_codes": [
|
||||
"CALCULATION_DESCRIPTOR_NOT_APPROVED",
|
||||
"CALCULATION_FORMULA_NOT_AVAILABLE",
|
||||
"CALCULATION_OPERAND_MISSING",
|
||||
"CALCULATION_LAW_VALUE_UNRESOLVED",
|
||||
"CALCULATION_TEMPORAL_GAP",
|
||||
"CALCULATION_UNIT_MISMATCH"
|
||||
],
|
||||
"review": {
|
||||
"required_role": "KOREAN_LAWYER",
|
||||
"status": "PENDING_KOREAN_LAWYER_REVIEW"
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"schema_version": "stage2.s2_20.calculation_descriptor.v1",
|
||||
"calculator_id": "CE-R3",
|
||||
"descriptor_id": "CE-R3-TRANSPORT-MARITIME",
|
||||
"description": "transport and maritime damage/liability limit",
|
||||
"status": "PENDING_LEGAL_CONTENT",
|
||||
"execution_eligible": false,
|
||||
"formula_rows": [],
|
||||
"operand_contract": {
|
||||
"required_operands": [],
|
||||
"optional_operands": [],
|
||||
"unit_contracts": [],
|
||||
"missing_operand_policy": "ISSUE_AND_NO_RESULT"
|
||||
},
|
||||
"law_value_contract": {
|
||||
"law_value_refs": [],
|
||||
"literal_output_affecting_legal_numbers_forbidden": true,
|
||||
"effective_interval_required": true,
|
||||
"authority_release_binding_required": true
|
||||
},
|
||||
"numeric_contract": {
|
||||
"decimal_only": true,
|
||||
"binary_float_forbidden": true,
|
||||
"rounding_rule_required_per_formula": true,
|
||||
"explicit_units_required": true
|
||||
},
|
||||
"receipt_contract": {
|
||||
"required_fields": [
|
||||
"calculation_receipt_id",
|
||||
"calculator_id",
|
||||
"formula_id",
|
||||
"operand_refs",
|
||||
"units",
|
||||
"law_value_id",
|
||||
"effective_interval",
|
||||
"rounding_rule",
|
||||
"period_rows",
|
||||
"missing_operands",
|
||||
"result_sha256"
|
||||
]
|
||||
},
|
||||
"failure_codes": [
|
||||
"CALCULATION_DESCRIPTOR_NOT_APPROVED",
|
||||
"CALCULATION_FORMULA_NOT_AVAILABLE",
|
||||
"CALCULATION_OPERAND_MISSING",
|
||||
"CALCULATION_LAW_VALUE_UNRESOLVED",
|
||||
"CALCULATION_TEMPORAL_GAP",
|
||||
"CALCULATION_UNIT_MISMATCH"
|
||||
],
|
||||
"review": {
|
||||
"required_role": "KOREAN_LAWYER",
|
||||
"status": "PENDING_KOREAN_LAWYER_REVIEW"
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"schema_version": "stage2.s2_20.calculation_descriptor.v1",
|
||||
"calculator_id": "CE-R4",
|
||||
"descriptor_id": "CE-R4-FINANCIAL-INSTRUMENT",
|
||||
"description": "financial instrument, account, and settlement",
|
||||
"status": "PENDING_LEGAL_CONTENT",
|
||||
"execution_eligible": false,
|
||||
"formula_rows": [],
|
||||
"operand_contract": {
|
||||
"required_operands": [],
|
||||
"optional_operands": [],
|
||||
"unit_contracts": [],
|
||||
"missing_operand_policy": "ISSUE_AND_NO_RESULT"
|
||||
},
|
||||
"law_value_contract": {
|
||||
"law_value_refs": [],
|
||||
"literal_output_affecting_legal_numbers_forbidden": true,
|
||||
"effective_interval_required": true,
|
||||
"authority_release_binding_required": true
|
||||
},
|
||||
"numeric_contract": {
|
||||
"decimal_only": true,
|
||||
"binary_float_forbidden": true,
|
||||
"rounding_rule_required_per_formula": true,
|
||||
"explicit_units_required": true
|
||||
},
|
||||
"receipt_contract": {
|
||||
"required_fields": [
|
||||
"calculation_receipt_id",
|
||||
"calculator_id",
|
||||
"formula_id",
|
||||
"operand_refs",
|
||||
"units",
|
||||
"law_value_id",
|
||||
"effective_interval",
|
||||
"rounding_rule",
|
||||
"period_rows",
|
||||
"missing_operands",
|
||||
"result_sha256"
|
||||
]
|
||||
},
|
||||
"failure_codes": [
|
||||
"CALCULATION_DESCRIPTOR_NOT_APPROVED",
|
||||
"CALCULATION_FORMULA_NOT_AVAILABLE",
|
||||
"CALCULATION_OPERAND_MISSING",
|
||||
"CALCULATION_LAW_VALUE_UNRESOLVED",
|
||||
"CALCULATION_TEMPORAL_GAP",
|
||||
"CALCULATION_UNIT_MISMATCH"
|
||||
],
|
||||
"review": {
|
||||
"required_role": "KOREAN_LAWYER",
|
||||
"status": "PENDING_KOREAN_LAWYER_REVIEW"
|
||||
}
|
||||
}
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
{
|
||||
"schema_version": "stage2.s2_20.calculation_activation_registry.v1",
|
||||
"registry_id": "S2-20-CALCULATION-ACTIVATION",
|
||||
"status": "PENDING_LEGAL_CONTENT",
|
||||
"execution_eligible": false,
|
||||
"state_enum": [
|
||||
"ACTIVATED",
|
||||
"NOT_APPLICABLE",
|
||||
"ACTIVATION_GAP",
|
||||
"ACTIVATION_CONFLICT"
|
||||
],
|
||||
"allowed_predicate_inputs": [
|
||||
"normalized_claim_signature",
|
||||
"case_type_id",
|
||||
"rule_branch_key",
|
||||
"requested_metric"
|
||||
],
|
||||
"allowed_predicate_operators": [
|
||||
"EQ",
|
||||
"IN",
|
||||
"ALL_OF",
|
||||
"NONE_OF",
|
||||
"EXISTS"
|
||||
],
|
||||
"activation_rows": [],
|
||||
"activation_row_contract": {
|
||||
"required_fields": [
|
||||
"calculator_id",
|
||||
"requested_metrics",
|
||||
"equals"
|
||||
]
|
||||
},
|
||||
"calculator_descriptors": [
|
||||
{
|
||||
"calculator_id": "CE-01",
|
||||
"descriptor_path": "registry/calculations/CE-01_interest_delay.yml",
|
||||
"activation_status": "PENDING_LEGAL_CONTENT",
|
||||
"status": "PENDING_LEGAL_CONTENT",
|
||||
"execution_eligible": false
|
||||
},
|
||||
{
|
||||
"calculator_id": "CE-02",
|
||||
"descriptor_path": "registry/calculations/CE-02_allocation_setoff_balance.yml",
|
||||
"activation_status": "PENDING_LEGAL_CONTENT",
|
||||
"status": "PENDING_LEGAL_CONTENT",
|
||||
"execution_eligible": false
|
||||
},
|
||||
{
|
||||
"calculator_id": "CE-03",
|
||||
"descriptor_path": "registry/calculations/CE-03_limitation_deadline.yml",
|
||||
"activation_status": "PENDING_LEGAL_CONTENT",
|
||||
"status": "PENDING_LEGAL_CONTENT",
|
||||
"execution_eligible": false
|
||||
},
|
||||
{
|
||||
"calculator_id": "CE-04",
|
||||
"descriptor_path": "registry/calculations/CE-04_valuation.yml",
|
||||
"activation_status": "PENDING_LEGAL_CONTENT",
|
||||
"status": "PENDING_LEGAL_CONTENT",
|
||||
"execution_eligible": false
|
||||
},
|
||||
{
|
||||
"calculator_id": "CE-05",
|
||||
"descriptor_path": "registry/calculations/CE-05_personal_injury.yml",
|
||||
"activation_status": "PENDING_LEGAL_CONTENT",
|
||||
"status": "PENDING_LEGAL_CONTENT",
|
||||
"execution_eligible": false
|
||||
},
|
||||
{
|
||||
"calculator_id": "CE-06",
|
||||
"descriptor_path": "registry/calculations/CE-06_construction_defect.yml",
|
||||
"activation_status": "PENDING_LEGAL_CONTENT",
|
||||
"status": "PENDING_LEGAL_CONTENT",
|
||||
"execution_eligible": false
|
||||
},
|
||||
{
|
||||
"calculator_id": "CE-07",
|
||||
"descriptor_path": "registry/calculations/CE-07_lease_use_gain.yml",
|
||||
"activation_status": "PENDING_LEGAL_CONTENT",
|
||||
"status": "PENDING_LEGAL_CONTENT",
|
||||
"execution_eligible": false
|
||||
},
|
||||
{
|
||||
"calculator_id": "CE-08",
|
||||
"descriptor_path": "registry/calculations/CE-08_inheritance_reserved_share.yml",
|
||||
"activation_status": "PENDING_LEGAL_CONTENT",
|
||||
"status": "PENDING_LEGAL_CONTENT",
|
||||
"execution_eligible": false
|
||||
},
|
||||
{
|
||||
"calculator_id": "CE-09",
|
||||
"descriptor_path": "registry/calculations/CE-09_wage_severance.yml",
|
||||
"activation_status": "PENDING_LEGAL_CONTENT",
|
||||
"status": "PENDING_LEGAL_CONTENT",
|
||||
"execution_eligible": false
|
||||
},
|
||||
{
|
||||
"calculator_id": "CE-10",
|
||||
"descriptor_path": "registry/calculations/CE-10_actio_insolvency_value.yml",
|
||||
"activation_status": "PENDING_LEGAL_CONTENT",
|
||||
"status": "PENDING_LEGAL_CONTENT",
|
||||
"execution_eligible": false
|
||||
},
|
||||
{
|
||||
"calculator_id": "CE-11",
|
||||
"descriptor_path": "registry/calculations/CE-11_distribution_share_division.yml",
|
||||
"activation_status": "PENDING_LEGAL_CONTENT",
|
||||
"status": "PENDING_LEGAL_CONTENT",
|
||||
"execution_eligible": false
|
||||
},
|
||||
{
|
||||
"calculator_id": "CE-12",
|
||||
"descriptor_path": "registry/calculations/CE-12_insurance.yml",
|
||||
"activation_status": "PENDING_LEGAL_CONTENT",
|
||||
"status": "PENDING_LEGAL_CONTENT",
|
||||
"execution_eligible": false
|
||||
},
|
||||
{
|
||||
"calculator_id": "CE-13",
|
||||
"descriptor_path": "registry/calculations/CE-13_court_value_cost.yml",
|
||||
"activation_status": "PENDING_LEGAL_CONTENT",
|
||||
"status": "PENDING_LEGAL_CONTENT",
|
||||
"execution_eligible": false
|
||||
},
|
||||
{
|
||||
"calculator_id": "CE-R1",
|
||||
"descriptor_path": "registry/calculations/CE-R1_ip_damage.yml",
|
||||
"activation_status": "PENDING_LEGAL_CONTENT",
|
||||
"status": "PENDING_LEGAL_CONTENT",
|
||||
"execution_eligible": false
|
||||
},
|
||||
{
|
||||
"calculator_id": "CE-R2",
|
||||
"descriptor_path": "registry/calculations/CE-R2_org_liquidation.yml",
|
||||
"activation_status": "PENDING_LEGAL_CONTENT",
|
||||
"status": "PENDING_LEGAL_CONTENT",
|
||||
"execution_eligible": false
|
||||
},
|
||||
{
|
||||
"calculator_id": "CE-R3",
|
||||
"descriptor_path": "registry/calculations/CE-R3_transport_maritime.yml",
|
||||
"activation_status": "PENDING_LEGAL_CONTENT",
|
||||
"status": "PENDING_LEGAL_CONTENT",
|
||||
"execution_eligible": false
|
||||
},
|
||||
{
|
||||
"calculator_id": "CE-R4",
|
||||
"descriptor_path": "registry/calculations/CE-R4_financial_instrument.yml",
|
||||
"activation_status": "PENDING_LEGAL_CONTENT",
|
||||
"status": "PENDING_LEGAL_CONTENT",
|
||||
"execution_eligible": false
|
||||
}
|
||||
],
|
||||
"closure": {
|
||||
"exactly_one_state_per_calculator_required": true,
|
||||
"activated_requires_nonempty_approved_predicate": true,
|
||||
"activated_requires_release_selected_descriptor_hash": true,
|
||||
"activated_requires_receipt": true,
|
||||
"zero_match_issue_code": "CALCULATION_ACTIVATION_GAP",
|
||||
"multi_match_issue_code": "CALCULATION_ACTIVATION_CONFLICT",
|
||||
"model_arithmetic_forbidden": true,
|
||||
"missing_operand_default_zero_forbidden": true
|
||||
},
|
||||
"review": {
|
||||
"required_role": "KOREAN_LAWYER",
|
||||
"status": "PENDING_KOREAN_LAWYER_REVIEW"
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"schema_version": "stage2_requirement_fact_scope_registry.v1",
|
||||
"registry_id": "STAGE2_REQUIREMENT_FACT_SCOPE_V1",
|
||||
"rows": [],
|
||||
"registry_status": "PENDING_LEGAL_CONTENT",
|
||||
"fuzzy_fallback_allowed": false
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"schema_version": "stage2.s2_20.case_type_relief_rules.v1",
|
||||
"registry_id": "S2-20-CASE-TYPE-RELIEF-RULES",
|
||||
"status": "PENDING_LEGAL_CONTENT",
|
||||
"execution_eligible": false,
|
||||
"source_catalog_registry_path": "manifest/case_type_registry.yml",
|
||||
"source_rule_registry_path": "manifest/case_type_rule_registry.yml",
|
||||
"source_catalog_expected_count": 137,
|
||||
"branch_key_format": "case_type_id::sub_rule_id",
|
||||
"branch_contract": {
|
||||
"required_fields": [
|
||||
"case_type_id",
|
||||
"sub_rule_id",
|
||||
"predicate_id",
|
||||
"structured_relief_slots",
|
||||
"allowed_renderer_ids",
|
||||
"markdown_source_path",
|
||||
"markdown_source_sha256",
|
||||
"corpus_scope_id",
|
||||
"element_set_id",
|
||||
"ce_13_branch_id",
|
||||
"party_set_rule_id",
|
||||
"review_policy_id",
|
||||
"authority_refs",
|
||||
"review_status"
|
||||
]
|
||||
},
|
||||
"rule_branches": [],
|
||||
"closure": {
|
||||
"directory_scan_forbidden": true,
|
||||
"filename_match_forbidden": true,
|
||||
"nearest_rule_fallback_forbidden": true,
|
||||
"generic_wording_fallback_forbidden": true,
|
||||
"exactly_one_approved_branch_required_for_clean_draft": true,
|
||||
"zero_match_issue_code": "RELIEF_RULE_BRANCH_ZERO_MATCH",
|
||||
"multi_match_issue_code": "RELIEF_RULE_BRANCH_MULTI_MATCH",
|
||||
"source_hash_mismatch_issue_code": "RELIEF_RULE_SOURCE_HASH_MISMATCH"
|
||||
},
|
||||
"review": {
|
||||
"required_role": "KOREAN_LAWYER",
|
||||
"status": "PENDING_KOREAN_LAWYER_REVIEW"
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"schema_version": "stage2.s2_20.drafting_rule_registry.v1",
|
||||
"registry_id": "S2-20-COUNTER-PERFORMANCE-RULES",
|
||||
"purpose": "simultaneous or reciprocal performance branches",
|
||||
"status": "PENDING_LEGAL_CONTENT",
|
||||
"execution_eligible": false,
|
||||
"rule_rows": [],
|
||||
"row_contract": {
|
||||
"required_fields": [
|
||||
"rule_id",
|
||||
"predicate_id",
|
||||
"effect",
|
||||
"required_slots",
|
||||
"renderer_constraints",
|
||||
"authority_refs",
|
||||
"review_status"
|
||||
]
|
||||
},
|
||||
"closure": {
|
||||
"exactly_one_approved_rule_required_when_activated": true,
|
||||
"zero_match_issue_code": "S2-20-COUNTER-PERFORMANCE-RULES-ZERO-MATCH",
|
||||
"multi_match_issue_code": "S2-20-COUNTER-PERFORMANCE-RULES-MULTI-MATCH",
|
||||
"free_text_fallback_forbidden": true
|
||||
},
|
||||
"review": {
|
||||
"required_role": "KOREAN_LAWYER",
|
||||
"status": "PENDING_KOREAN_LAWYER_REVIEW"
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"schema_version": "stage2.s2_20.drafting_rule_registry.v1",
|
||||
"registry_id": "S2-20-FORBIDDEN-RELIEF-RULES",
|
||||
"purpose": "prohibited and permitted corrective relief branches",
|
||||
"status": "PENDING_LEGAL_CONTENT",
|
||||
"execution_eligible": false,
|
||||
"rule_rows": [],
|
||||
"row_contract": {
|
||||
"required_fields": [
|
||||
"rule_id",
|
||||
"predicate_id",
|
||||
"effect",
|
||||
"required_slots",
|
||||
"renderer_constraints",
|
||||
"authority_refs",
|
||||
"review_status"
|
||||
]
|
||||
},
|
||||
"closure": {
|
||||
"exactly_one_approved_rule_required_when_activated": true,
|
||||
"zero_match_issue_code": "S2-20-FORBIDDEN-RELIEF-RULES-ZERO-MATCH",
|
||||
"multi_match_issue_code": "S2-20-FORBIDDEN-RELIEF-RULES-MULTI-MATCH",
|
||||
"free_text_fallback_forbidden": true
|
||||
},
|
||||
"review": {
|
||||
"required_role": "KOREAN_LAWYER",
|
||||
"status": "PENDING_KOREAN_LAWYER_REVIEW"
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"schema_version": "stage2.s2_20.drafting_rule_registry.v1",
|
||||
"registry_id": "S2-20-PROCEDURAL-DECLARATION-RULES",
|
||||
"purpose": "service-based procedural declaration branches",
|
||||
"status": "PENDING_LEGAL_CONTENT",
|
||||
"execution_eligible": false,
|
||||
"rule_rows": [],
|
||||
"row_contract": {
|
||||
"required_fields": [
|
||||
"rule_id",
|
||||
"predicate_id",
|
||||
"effect",
|
||||
"required_slots",
|
||||
"renderer_constraints",
|
||||
"authority_refs",
|
||||
"review_status"
|
||||
]
|
||||
},
|
||||
"closure": {
|
||||
"exactly_one_approved_rule_required_when_activated": true,
|
||||
"zero_match_issue_code": "S2-20-PROCEDURAL-DECLARATION-RULES-ZERO-MATCH",
|
||||
"multi_match_issue_code": "S2-20-PROCEDURAL-DECLARATION-RULES-MULTI-MATCH",
|
||||
"free_text_fallback_forbidden": true
|
||||
},
|
||||
"review": {
|
||||
"required_role": "KOREAN_LAWYER",
|
||||
"status": "PENDING_KOREAN_LAWYER_REVIEW"
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"schema_version": "stage2_party_set_rules.v1",
|
||||
"registry_id": "S2_20_PARTY_SET_RULES_V1",
|
||||
"rules": [],
|
||||
"exactly_one_match_required": true,
|
||||
"fallback_allowed": false,
|
||||
"registry_status": "PENDING_LEGAL_CONTENT"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"schema_version": "stage2_option_disposition_policy.v1",
|
||||
"policy_id": "S2_20_OPTION_DISPOSITION_POLICY_V1",
|
||||
"precedence_order": [
|
||||
"LEGAL_EXCLUSION",
|
||||
"CLIENT_NO_SUE",
|
||||
"PRECONDITION",
|
||||
"COMPATIBILITY",
|
||||
"LEGAL_DECISION"
|
||||
],
|
||||
"rules": [],
|
||||
"exhaustive_cross_product_required": true,
|
||||
"exactly_one_match_required": true,
|
||||
"hidden_default_allowed": false,
|
||||
"policy_status": "PENDING_LEGAL_REVIEW"
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"schema_version": "stage2_finding_fixture_map.v1",
|
||||
"registry_id": "S2-FINDING-FIXTURE-MAP-PENDING",
|
||||
"status": "PENDING_SOURCE_FINDING_RECONCILIATION",
|
||||
"execution_eligible": false,
|
||||
"runtime_legal_source": false,
|
||||
"required_fixture_case_ids": [
|
||||
"S20-F01-SINGLE-OPTION-GROUP",
|
||||
"S20-F02-MIXED-PORTFOLIO",
|
||||
"S20-F03-CLIENT-NO-SUE",
|
||||
"S20-F04-CASE-RULE-EXACTNESS",
|
||||
"S20-F05-ROW-KIND-BRANCH",
|
||||
"S20-F06-PARTY-TITLE",
|
||||
"S20-F07-CE01-INTEREST",
|
||||
"S20-F08-CE03-LIMITATION",
|
||||
"S20-F09-CE08-RESERVED-SHARE",
|
||||
"S20-F10-ACTIO-ROUTE-CAP",
|
||||
"S20-F11-CE13-COURT-VALUE",
|
||||
"S20-F12-RETRIEVAL-BOUNDARY",
|
||||
"S20-F13-CORPUS-INTEGRITY",
|
||||
"S20-F14-EXHIBIT-LABEL",
|
||||
"S20-F15-OPTION-SILENT-LOSS",
|
||||
"S20-F16-PUBLISH-BARRIER",
|
||||
"S20-F17-LEGACY-PATH"
|
||||
],
|
||||
"mapping_rows": [],
|
||||
"mapped_fixture_case_count": 0,
|
||||
"required_fixture_case_count": 17,
|
||||
"source_finding_families": ["discrepancy_report_1..4", "improvement_report_1..4", "improvement_merged", "eval_stage_2_optimal_update_strategy_v.3"],
|
||||
"unresolved_reason_codes": ["SOURCE_FINDINGS_NOT_EXTRACTED","INVARIANT_MAPPING_NOT_REVIEWED","FIXTURE_CROSSWALK_NOT_SIGNED"],
|
||||
"guards": {"runtime_routing_use_forbidden":true,"legal_proposition_source_use_forbidden":true,"unmapped_finding_silent_drop_forbidden":true,"pending_map_is_not_coverage_proof":true}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"schema_version": "stage2.s2_20.review_policy_registry.v1",
|
||||
"registry_id": "S2-20-REVIEW-POLICY",
|
||||
"status": "PENDING_LEGAL_CONTENT",
|
||||
"execution_eligible": false,
|
||||
"base_status_enum": [
|
||||
"CLEAN_DRAFT",
|
||||
"REVIEW_DRAFT",
|
||||
"WORKNOTE_ONLY"
|
||||
],
|
||||
"trigger_contract": {
|
||||
"sources": [
|
||||
"case_type_rule",
|
||||
"calculation",
|
||||
"authority",
|
||||
"corpus",
|
||||
"party",
|
||||
"runtime_issue"
|
||||
],
|
||||
"exactly_one_policy_result_required": true
|
||||
},
|
||||
"policy_rows": [],
|
||||
"default_unapproved_result": {
|
||||
"drafting_permission": "WORKNOTE_ONLY",
|
||||
"issue_code": "REVIEW_POLICY_NOT_APPROVED",
|
||||
"ready_eligible": false
|
||||
},
|
||||
"closure": {
|
||||
"ready_requires_all_required_receipts": true,
|
||||
"ready_requires_no_unresolved_blocking_issue": true,
|
||||
"missing_policy_cannot_upgrade_permission": true
|
||||
},
|
||||
"review": {
|
||||
"required_role": "KOREAN_LAWYER",
|
||||
"status": "PENDING_KOREAN_LAWYER_REVIEW"
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"schema_version": "stage2.s2_20.inheritance_reserved_share_temporal.v1",
|
||||
"registry_id": "S2-20-INHERITANCE-RESERVED-SHARE-TEMPORAL",
|
||||
"status": "PENDING_LEGAL_CONTENT",
|
||||
"execution_eligible": false,
|
||||
"temporal_basis_field": "inheritance_opening_date",
|
||||
"branch_rows": [],
|
||||
"row_contract": {
|
||||
"required_fields": [
|
||||
"temporal_rule_id",
|
||||
"effective_from",
|
||||
"effective_to",
|
||||
"remedy_mode",
|
||||
"renderer_id",
|
||||
"interest_branch_id",
|
||||
"authority_refs",
|
||||
"review_status"
|
||||
],
|
||||
"interval_overlap_forbidden": true,
|
||||
"uncovered_interval_issue_code": "INHERITANCE_RESERVED_SHARE_TEMPORAL_GAP"
|
||||
},
|
||||
"closure": {
|
||||
"missing_date_issue_code": "INHERITANCE_OPENING_DATE_MISSING",
|
||||
"guessed_effective_date_forbidden": true,
|
||||
"fallback_to_current_law_forbidden": true
|
||||
},
|
||||
"review": {
|
||||
"required_role": "KOREAN_LAWYER",
|
||||
"status": "PENDING_KOREAN_LAWYER_REVIEW"
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Produce a non-destructive rollback decision receipt for an admitted canary."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("admission", type=Path)
|
||||
parser.add_argument("baseline", type=Path)
|
||||
parser.add_argument("output", type=Path)
|
||||
args = parser.parse_args()
|
||||
admission_raw = args.admission.read_bytes()
|
||||
baseline_raw = args.baseline.read_bytes()
|
||||
admission = json.loads(admission_raw)
|
||||
baseline = json.loads(baseline_raw)
|
||||
rollback = admission.get("canary_status") != "PASS"
|
||||
payload = {
|
||||
"schema_version": "stage2.canary_rollback_receipt.v1",
|
||||
"decision": "ROLLBACK_REQUIRED" if rollback else "KEEP_CANARY",
|
||||
"admission_sha256": hashlib.sha256(admission_raw).hexdigest(),
|
||||
"baseline_sha256": hashlib.sha256(baseline_raw).hexdigest(),
|
||||
"rollback_target_release_sha256": baseline.get("release_sha256") if rollback else None,
|
||||
"mutation_performed": False,
|
||||
}
|
||||
args.output.parent.mkdir(parents=True, exist_ok=True)
|
||||
args.output.write_text(json.dumps(payload, sort_keys=True, separators=(",", ":")) + "\n", encoding="utf-8")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Produce a non-destructive rollback decision receipt for an admitted canary."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("admission", type=Path)
|
||||
parser.add_argument("baseline", type=Path)
|
||||
parser.add_argument("output", type=Path)
|
||||
args = parser.parse_args()
|
||||
admission_raw = args.admission.read_bytes()
|
||||
baseline_raw = args.baseline.read_bytes()
|
||||
admission = json.loads(admission_raw)
|
||||
baseline = json.loads(baseline_raw)
|
||||
rollback = admission.get("canary_status") != "PASS"
|
||||
payload = {
|
||||
"schema_version": "stage2.canary_rollback_receipt.v1",
|
||||
"decision": "ROLLBACK_REQUIRED" if rollback else "KEEP_CANARY",
|
||||
"admission_sha256": hashlib.sha256(admission_raw).hexdigest(),
|
||||
"baseline_sha256": hashlib.sha256(baseline_raw).hexdigest(),
|
||||
"rollback_target_release_sha256": baseline.get("release_sha256") if rollback else None,
|
||||
"mutation_performed": False,
|
||||
}
|
||||
args.output.parent.mkdir(parents=True, exist_ok=True)
|
||||
args.output.write_text(json.dumps(payload, sort_keys=True, separators=(",", ":")) + "\n", encoding="utf-8")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
||||
+581
@@ -0,0 +1,581 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Read-only validator for the canonical Stage-2 raw release closure.
|
||||
|
||||
The report separates contract errors from honest pending legal/live evidence.
|
||||
An offline PASS therefore never means that 137-case legal content, Weaviate,
|
||||
Code Executor, host CAS or production admission has been approved.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import copy
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path, PurePosixPath
|
||||
from typing import Any
|
||||
|
||||
|
||||
MODULE_MANIFEST_SCHEMA = "stage2_module_manifest.v1"
|
||||
PARENT_RELEASE_SCHEMA = "stage2_release.v2"
|
||||
SHA256_LENGTH = 64
|
||||
EXPECTED_CASE_TYPE_IDS = tuple(f"CT-{index:03d}" for index in range(1, 138))
|
||||
EXPECTED_CATALOG_ROW_IDS = tuple(f"CAT-{index:03d}" for index in range(1, 138))
|
||||
EXPECTED_MANIFEST_DIGEST_CONTRACT = {
|
||||
"algorithm_id": "STAGE2-MODULE-MANIFEST-DIGEST-V1",
|
||||
"array_order": "PRESERVE_DECLARED_ORDER",
|
||||
"canonicalization_algorithm_id": "STAGE2-CANONICAL-JSON-V1",
|
||||
"digest_algorithm": "sha256",
|
||||
"digest_scope": "ENTIRE_DOCUMENT_AFTER_REMOVING_TOP_LEVEL_MANIFEST_DIGEST",
|
||||
"encoding": "UTF-8",
|
||||
"line_termination": "LF_ONE_TRAILING_NEWLINE",
|
||||
"non_finite_numbers_allowed": False,
|
||||
"object_key_order": "SORT_CODEPOINT",
|
||||
}
|
||||
EXPECTED_RELEASE_DIGEST_CONTRACT = {
|
||||
"algorithm_id": "STAGE2-RELEASE-DIGEST-V1",
|
||||
"array_order": "PRESERVE_DECLARED_ORDER",
|
||||
"canonicalization_algorithm_id": "STAGE2-CANONICAL-JSON-V1",
|
||||
"digest_algorithm": "sha256",
|
||||
"digest_scope": "ENTIRE_DOCUMENT_AFTER_REMOVING_TOP_LEVEL_RELEASE_DIGEST",
|
||||
"encoding": "UTF-8",
|
||||
"line_termination": "LF_ONE_TRAILING_NEWLINE",
|
||||
"non_finite_numbers_allowed": False,
|
||||
"object_key_order": "SORT_CODEPOINT",
|
||||
}
|
||||
FORBIDDEN_PARENT_MEMBERS = frozenset(
|
||||
{
|
||||
"manifest/module_manifest.json",
|
||||
"manifest/stage2_release.json",
|
||||
"deployment/stage2_code_executor_binding.yml",
|
||||
"manifest/stage2_deterministic_admission_receipt.json",
|
||||
"agent_scripts/Stage_2_S2_00.yml",
|
||||
"runtime/s2_00_ingress.py",
|
||||
"runtime/s2_00_ingress.txt",
|
||||
"manifest/s2_00_inline_code_receipt.json",
|
||||
"agent_scripts/Stage_2_S2_20.yml",
|
||||
"runtime/s2_20_reduce.py",
|
||||
"runtime/s2_20_reduce.txt",
|
||||
"manifest/s2_20_inline_code_receipt.json",
|
||||
"manifest/s2_10_release.json",
|
||||
"manifest/s2_10_agent_receipt.json",
|
||||
"manifest/s2_10_platform_adapter_receipt.json",
|
||||
"manifest/s2_10_model_benchmark_receipt.json",
|
||||
"manifest/s2_10_legal_review_receipt.json",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class ValidationInputError(ValueError):
|
||||
"""Raised when a validation input cannot be parsed deterministically."""
|
||||
|
||||
|
||||
def _reject_duplicate_pairs(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
|
||||
result: dict[str, Any] = {}
|
||||
for key, value in pairs:
|
||||
if key in result:
|
||||
raise ValidationInputError(f"DUPLICATE_JSON_KEY:{key}")
|
||||
result[key] = value
|
||||
return result
|
||||
|
||||
|
||||
def _load_json(path: Path) -> Any:
|
||||
try:
|
||||
return json.loads(
|
||||
path.read_text(encoding="utf-8"),
|
||||
object_pairs_hook=_reject_duplicate_pairs,
|
||||
parse_constant=lambda token: (_ for _ in ()).throw(
|
||||
ValidationInputError(f"NON_FINITE_JSON_NUMBER:{token}")
|
||||
),
|
||||
)
|
||||
except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc:
|
||||
raise ValidationInputError(f"JSON_READ_FAILED:{path.as_posix()}:{exc}") from exc
|
||||
|
||||
|
||||
def _canonical_bytes(document: Any) -> bytes:
|
||||
return (
|
||||
json.dumps(
|
||||
document,
|
||||
ensure_ascii=False,
|
||||
sort_keys=True,
|
||||
separators=(",", ":"),
|
||||
allow_nan=False,
|
||||
)
|
||||
+ "\n"
|
||||
).encode("utf-8")
|
||||
|
||||
|
||||
def _sha256(raw: bytes) -> str:
|
||||
return hashlib.sha256(raw).hexdigest()
|
||||
|
||||
|
||||
def _document_digest(document: dict[str, Any], digest_field: str) -> str:
|
||||
payload = copy.deepcopy(document)
|
||||
payload.pop(digest_field, None)
|
||||
return _sha256(_canonical_bytes(payload))
|
||||
|
||||
|
||||
def _finding(code: str, path: str, detail: str) -> dict[str, str]:
|
||||
return {"code": code, "detail": detail, "path": path}
|
||||
|
||||
|
||||
def _is_sha256(value: Any) -> bool:
|
||||
return (
|
||||
isinstance(value, str)
|
||||
and len(value) == SHA256_LENGTH
|
||||
and all(character in "0123456789abcdef" for character in value)
|
||||
)
|
||||
|
||||
|
||||
def _contains_contract(value: Any, expected: dict[str, Any]) -> bool:
|
||||
return isinstance(value, dict) and all(value.get(key) == item for key, item in expected.items())
|
||||
|
||||
|
||||
def _relative_path(value: Any) -> str | None:
|
||||
if not isinstance(value, str) or not value or "\\" in value:
|
||||
return None
|
||||
pure = PurePosixPath(value)
|
||||
if pure.is_absolute() or any(part in {"", ".", ".."} for part in pure.parts):
|
||||
return None
|
||||
return pure.as_posix()
|
||||
|
||||
|
||||
def _legacy_path(relative: str) -> bool:
|
||||
return any(part in {"v.0", "v.1", "v.2", "v.3"} for part in PurePosixPath(relative).parts)
|
||||
|
||||
|
||||
def _physical(root: Path, relative: str) -> Path | None:
|
||||
candidate = root / relative
|
||||
if candidate.is_symlink():
|
||||
return None
|
||||
try:
|
||||
resolved = candidate.resolve(strict=True)
|
||||
root_real = root.resolve(strict=True)
|
||||
except FileNotFoundError:
|
||||
return None
|
||||
if root_real not in resolved.parents or not resolved.is_file():
|
||||
return None
|
||||
return resolved
|
||||
|
||||
|
||||
def _validate_module_manifest(
|
||||
root: Path,
|
||||
manifest: dict[str, Any],
|
||||
) -> tuple[list[dict[str, str]], list[dict[str, str]], dict[str, int]]:
|
||||
errors: list[dict[str, str]] = []
|
||||
pending: list[dict[str, str]] = []
|
||||
if manifest.get("schema_version") != MODULE_MANIFEST_SCHEMA:
|
||||
errors.append(_finding("MODULE_SCHEMA_VERSION", "$/schema_version", repr(manifest.get("schema_version"))))
|
||||
declared_digest = manifest.get("manifest_digest")
|
||||
observed_digest = _document_digest(manifest, "manifest_digest")
|
||||
if declared_digest != observed_digest:
|
||||
errors.append(_finding("MANIFEST_DIGEST_MISMATCH", "$/manifest_digest", f"declared={declared_digest};observed={observed_digest}"))
|
||||
if not _contains_contract(manifest.get("manifest_digest_contract"), EXPECTED_MANIFEST_DIGEST_CONTRACT):
|
||||
errors.append(_finding("MANIFEST_DIGEST_CONTRACT_INVALID", "$/manifest_digest_contract", repr(manifest.get("manifest_digest_contract"))))
|
||||
|
||||
rows = manifest.get("modules")
|
||||
if not isinstance(rows, list):
|
||||
return errors + [_finding("MODULES_REQUIRED", "$/modules", "canonical modules array missing")], pending, {"module_count": 0, "mirror_count": 0}
|
||||
seen_ids: set[str] = set()
|
||||
seen_paths: set[str] = set()
|
||||
row_by_path: dict[str, dict[str, Any]] = {}
|
||||
for index, row in enumerate(rows):
|
||||
location = f"$/modules/{index}"
|
||||
if not isinstance(row, dict):
|
||||
errors.append(_finding("MODULE_ROW_INVALID", location, "object required"))
|
||||
continue
|
||||
module_id = row.get("module_id")
|
||||
if not isinstance(module_id, str) or not module_id or module_id in seen_ids:
|
||||
errors.append(_finding("MODULE_ID_INVALID_OR_DUPLICATE", f"{location}/module_id", repr(module_id)))
|
||||
else:
|
||||
seen_ids.add(module_id)
|
||||
relative = _relative_path(row.get("path"))
|
||||
if relative is None or relative in seen_paths:
|
||||
errors.append(_finding("MODULE_PATH_INVALID_OR_DUPLICATE", f"{location}/path", repr(row.get("path"))))
|
||||
continue
|
||||
seen_paths.add(relative)
|
||||
row_by_path[relative] = row
|
||||
if relative in FORBIDDEN_PARENT_MEMBERS:
|
||||
errors.append(_finding("NON_CYCLIC_PARENT_VIOLATION", f"{location}/path", relative))
|
||||
if _legacy_path(relative):
|
||||
errors.append(_finding("LEGACY_STAGE2_DEPENDENCY", f"{location}/path", relative))
|
||||
physical = _physical(root, relative)
|
||||
if physical is None:
|
||||
errors.append(_finding("MODULE_MISSING_OR_SYMLINK", f"{location}/path", relative))
|
||||
continue
|
||||
raw = physical.read_bytes()
|
||||
observed = _sha256(raw)
|
||||
if row.get("sha256") != observed:
|
||||
errors.append(_finding("MODULE_HASH_MISMATCH", f"{location}/sha256", f"path={relative};observed={observed}"))
|
||||
if row.get("size_bytes") != len(raw):
|
||||
errors.append(_finding("MODULE_SIZE_MISMATCH", f"{location}/size_bytes", f"path={relative};observed={len(raw)}"))
|
||||
|
||||
if relative.endswith(".py"):
|
||||
mirror = relative[:-3] + ".txt"
|
||||
mirror_physical = _physical(root, mirror)
|
||||
if mirror_physical is None:
|
||||
errors.append(_finding("PYTHON_DOCUMENTATION_MIRROR_MISSING", location, mirror))
|
||||
continue
|
||||
mirror_raw = mirror_physical.read_bytes()
|
||||
if raw != mirror_raw:
|
||||
errors.append(_finding("PYTHON_DOCUMENTATION_MIRROR_MISMATCH", location, relative))
|
||||
expected_fields = {
|
||||
"mirror_path": mirror,
|
||||
"mirror_sha256": _sha256(mirror_raw),
|
||||
"mirror_size_bytes": len(mirror_raw),
|
||||
"mirror_byte_parity": True,
|
||||
}
|
||||
for key, expected in expected_fields.items():
|
||||
if row.get(key) != expected:
|
||||
errors.append(_finding("MODULE_MIRROR_BINDING_MISMATCH", f"{location}/{key}", f"expected={expected!r};observed={row.get(key)!r}"))
|
||||
|
||||
mirrors = manifest.get("documentation_mirrors")
|
||||
if not isinstance(mirrors, list):
|
||||
errors.append(_finding("DOCUMENTATION_MIRRORS_REQUIRED", "$/documentation_mirrors", "array required"))
|
||||
mirrors = []
|
||||
seen_sources: set[str] = set()
|
||||
for index, mirror in enumerate(mirrors):
|
||||
location = f"$/documentation_mirrors/{index}"
|
||||
if not isinstance(mirror, dict):
|
||||
errors.append(_finding("DOCUMENTATION_MIRROR_ROW_INVALID", location, "object required"))
|
||||
continue
|
||||
source = _relative_path(mirror.get("source_path"))
|
||||
target = _relative_path(mirror.get("mirror_path"))
|
||||
if source is None or target is None or source in seen_sources:
|
||||
errors.append(_finding("DOCUMENTATION_MIRROR_PATH_INVALID", location, repr(mirror)))
|
||||
continue
|
||||
seen_sources.add(source)
|
||||
if source in FORBIDDEN_PARENT_MEMBERS or target in FORBIDDEN_PARENT_MEMBERS:
|
||||
errors.append(_finding("NON_CYCLIC_MIRROR_VIOLATION", location, f"{source}->{target}"))
|
||||
source_physical = _physical(root, source)
|
||||
target_physical = _physical(root, target)
|
||||
if source_physical is None or target_physical is None:
|
||||
errors.append(_finding("DOCUMENTATION_MIRROR_MISSING", location, f"{source}->{target}"))
|
||||
continue
|
||||
source_raw = source_physical.read_bytes()
|
||||
target_raw = target_physical.read_bytes()
|
||||
expected = {
|
||||
"source_sha256": _sha256(source_raw),
|
||||
"source_size_bytes": len(source_raw),
|
||||
"mirror_sha256": _sha256(target_raw),
|
||||
"mirror_size_bytes": len(target_raw),
|
||||
"byte_identical": True,
|
||||
"runtime_import_allowed": False,
|
||||
}
|
||||
if source_raw != target_raw:
|
||||
errors.append(_finding("DOCUMENTATION_MIRROR_BYTES_DIFFER", location, source))
|
||||
for key, expected_value in expected.items():
|
||||
if mirror.get(key) != expected_value:
|
||||
errors.append(_finding("DOCUMENTATION_MIRROR_METADATA_MISMATCH", f"{location}/{key}", f"expected={expected_value!r};observed={mirror.get(key)!r}"))
|
||||
row = row_by_path.get(source)
|
||||
if row is None or row.get("mirror_path") != target:
|
||||
errors.append(_finding("DOCUMENTATION_MIRROR_MODULE_ORPHAN", location, source))
|
||||
|
||||
py_sources = {path for path in row_by_path if path.endswith(".py")}
|
||||
if seen_sources != py_sources:
|
||||
errors.append(_finding("DOCUMENTATION_MIRROR_COVERAGE_MISMATCH", "$/documentation_mirrors", f"expected={sorted(py_sources)};observed={sorted(seen_sources)}"))
|
||||
|
||||
dependency_graph: dict[str, tuple[str, ...]] = {}
|
||||
for index, row in enumerate(rows):
|
||||
if not isinstance(row, dict) or not isinstance(row.get("module_id"), str):
|
||||
continue
|
||||
dependencies = row.get("dependency_module_ids", [])
|
||||
location = f"$/modules/{index}/dependency_module_ids"
|
||||
if (
|
||||
not isinstance(dependencies, list)
|
||||
or not all(isinstance(item, str) and item for item in dependencies)
|
||||
or len(dependencies) != len(set(dependencies))
|
||||
):
|
||||
errors.append(_finding("MODULE_DEPENDENCY_LIST_INVALID", location, repr(dependencies)))
|
||||
continue
|
||||
missing = sorted(set(dependencies) - seen_ids)
|
||||
if missing:
|
||||
errors.append(_finding("MODULE_DEPENDENCY_MISSING", location, repr(missing)))
|
||||
if row["module_id"] in dependencies:
|
||||
errors.append(_finding("MODULE_SELF_DEPENDENCY", location, row["module_id"]))
|
||||
dependency_graph[row["module_id"]] = tuple(dependencies)
|
||||
|
||||
visiting: set[str] = set()
|
||||
visited: set[str] = set()
|
||||
|
||||
def visit(module_id: str, chain: tuple[str, ...]) -> None:
|
||||
if module_id in visiting:
|
||||
errors.append(_finding("MODULE_DEPENDENCY_CYCLE", "$/modules", "->".join(chain + (module_id,))))
|
||||
return
|
||||
if module_id in visited:
|
||||
return
|
||||
visiting.add(module_id)
|
||||
for dependency in dependency_graph.get(module_id, ()):
|
||||
if dependency in dependency_graph:
|
||||
visit(dependency, chain + (module_id,))
|
||||
visiting.remove(module_id)
|
||||
visited.add(module_id)
|
||||
|
||||
for module_id in sorted(dependency_graph):
|
||||
visit(module_id, ())
|
||||
summary = manifest.get("closure_summary")
|
||||
if not isinstance(summary, dict):
|
||||
errors.append(_finding("CLOSURE_SUMMARY_REQUIRED", "$/closure_summary", "object required"))
|
||||
else:
|
||||
for key, expected in (
|
||||
("module_count", len(rows)),
|
||||
("documentation_mirror_count", len(mirrors)),
|
||||
):
|
||||
if summary.get(key) != expected:
|
||||
errors.append(_finding("CLOSURE_SUMMARY_COUNT_MISMATCH", f"$/closure_summary/{key}", f"expected={expected};observed={summary.get(key)}"))
|
||||
for key in (
|
||||
"executable_agent_hash_included",
|
||||
"executor_binding_hash_included",
|
||||
"inline_runtime_code_hash_included",
|
||||
"self_hash_included",
|
||||
"stage2_release_hash_included",
|
||||
):
|
||||
if key in summary and summary.get(key) is not False:
|
||||
errors.append(_finding("NON_CYCLIC_SUMMARY_FLAG_INVALID", f"$/closure_summary/{key}", repr(summary.get(key))))
|
||||
return errors, pending, {"module_count": len(rows), "mirror_count": len(mirrors)}
|
||||
|
||||
|
||||
def _walk_parent_refs(
|
||||
root: Path,
|
||||
value: Any,
|
||||
errors: list[dict[str, str]],
|
||||
location: str = "$",
|
||||
) -> None:
|
||||
if isinstance(value, list):
|
||||
for index, item in enumerate(value):
|
||||
_walk_parent_refs(root, item, errors, f"{location}/{index}")
|
||||
return
|
||||
if not isinstance(value, dict):
|
||||
return
|
||||
relative = _relative_path(value.get("path"))
|
||||
if relative is not None and "<" not in relative:
|
||||
if relative == "manifest/module_manifest.json":
|
||||
pass
|
||||
elif relative in FORBIDDEN_PARENT_MEMBERS:
|
||||
if "sha256" in value or value.get("binding_status") == "BOUND":
|
||||
errors.append(_finding("NON_CYCLIC_PARENT_HASH_REFERENCE", location, relative))
|
||||
elif "sha256" in value and _is_sha256(value.get("sha256")):
|
||||
physical = _physical(root, relative)
|
||||
if physical is not None:
|
||||
observed = _sha256(physical.read_bytes())
|
||||
if value["sha256"] != observed:
|
||||
errors.append(_finding("PARENT_ASSET_HASH_MISMATCH", f"{location}/sha256", f"path={relative};observed={observed}"))
|
||||
for key, item in value.items():
|
||||
_walk_parent_refs(root, item, errors, f"{location}/{key}")
|
||||
|
||||
|
||||
def _validate_parent_release(
|
||||
root: Path,
|
||||
release: dict[str, Any],
|
||||
manifest_raw: bytes,
|
||||
) -> tuple[list[dict[str, str]], list[dict[str, str]]]:
|
||||
errors: list[dict[str, str]] = []
|
||||
pending: list[dict[str, str]] = []
|
||||
if release.get("schema_version") != PARENT_RELEASE_SCHEMA:
|
||||
errors.append(_finding("PARENT_RELEASE_SCHEMA_VERSION", "$/schema_version", repr(release.get("schema_version"))))
|
||||
if not _contains_contract(release.get("release_digest_contract"), EXPECTED_RELEASE_DIGEST_CONTRACT):
|
||||
errors.append(_finding("PARENT_RELEASE_DIGEST_CONTRACT_INVALID", "$/release_digest_contract", repr(release.get("release_digest_contract"))))
|
||||
declared = release.get("release_digest")
|
||||
observed = _document_digest(release, "release_digest")
|
||||
if declared != observed:
|
||||
errors.append(_finding("PARENT_RELEASE_DIGEST_MISMATCH", "$/release_digest", f"declared={declared};observed={observed}"))
|
||||
module_ref = release.get("module_manifest_ref")
|
||||
if not isinstance(module_ref, dict) or module_ref.get("path") != "manifest/module_manifest.json":
|
||||
errors.append(_finding("PARENT_MODULE_REF_INVALID", "$/module_manifest_ref", repr(module_ref)))
|
||||
else:
|
||||
expected_hash = _sha256(manifest_raw)
|
||||
if module_ref.get("sha256") != expected_hash:
|
||||
errors.append(_finding("PARENT_MODULE_REF_HASH_MISMATCH", "$/module_manifest_ref/sha256", f"expected={expected_hash};observed={module_ref.get('sha256')}"))
|
||||
executor_ref = release.get("executor_binding_ref")
|
||||
if (
|
||||
not isinstance(executor_ref, dict)
|
||||
or executor_ref.get("path") != "deployment/stage2_code_executor_binding.yml"
|
||||
or executor_ref.get("schema_id") != "stage2_code_executor_binding.v3"
|
||||
or "sha256" in executor_ref
|
||||
or executor_ref.get("binding_status") == "BOUND"
|
||||
):
|
||||
errors.append(_finding("PARENT_EXECUTOR_TRUST_ROOT_REF_INVALID", "$/executor_binding_ref", repr(executor_ref)))
|
||||
_walk_parent_refs(root, release, errors)
|
||||
|
||||
status = release.get("release_status")
|
||||
authorization = release.get("authorization_status")
|
||||
signature = release.get("signature")
|
||||
if status == "ADMITTED":
|
||||
if authorization not in {"CANARY_ADMITTED", "PRODUCTION_ADMITTED"}:
|
||||
errors.append(_finding("ADMITTED_RELEASE_AUTHORIZATION_INVALID", "$/authorization_status", repr(authorization)))
|
||||
if signature == "PENDING_SEQUENTIAL_BIND" or not isinstance(signature, str) or len(signature) < 32:
|
||||
errors.append(_finding("ADMITTED_RELEASE_SIGNATURE_INVALID", "$/signature", repr(signature)))
|
||||
else:
|
||||
pending.append(_finding("PARENT_RELEASE_NOT_ADMITTED", "$/release_status", str(status)))
|
||||
return errors, pending
|
||||
|
||||
|
||||
def _nested_pending(value: Any) -> bool:
|
||||
if isinstance(value, str):
|
||||
return "PENDING" in value or value in {"UNVERIFIED", "NOT_IMPLEMENTED", "DEV_UNAVAILABLE"}
|
||||
if isinstance(value, list):
|
||||
return any(_nested_pending(item) for item in value)
|
||||
if isinstance(value, dict):
|
||||
return any(_nested_pending(item) for item in value.values())
|
||||
return False
|
||||
|
||||
|
||||
def _contains_ready_claim(value: Any) -> bool:
|
||||
if isinstance(value, str):
|
||||
return value in {"FULL_137_PRODUCTION_READY", "PRODUCTION_READY", "ADMITTED"}
|
||||
if isinstance(value, list):
|
||||
return any(_contains_ready_claim(item) for item in value)
|
||||
if isinstance(value, dict):
|
||||
return any(_contains_ready_claim(item) for item in value.values())
|
||||
return False
|
||||
|
||||
|
||||
def _validate_137_coverage(
|
||||
root: Path,
|
||||
case_registry: dict[str, Any],
|
||||
rule_registry: dict[str, Any],
|
||||
coverage: dict[str, Any],
|
||||
) -> tuple[list[dict[str, str]], list[dict[str, str]], dict[str, int]]:
|
||||
errors: list[dict[str, str]] = []
|
||||
pending: list[dict[str, str]] = []
|
||||
case_rows = case_registry.get("rows")
|
||||
rule_rows = rule_registry.get("rows")
|
||||
coverage_rows = coverage.get("coverage_rows")
|
||||
if not isinstance(case_rows, list) or len(case_rows) != 137:
|
||||
errors.append(_finding("CASE_TYPE_REGISTRY_EXACT_137_REQUIRED", "case_type_registry:/rows", f"observed={len(case_rows) if isinstance(case_rows, list) else 'invalid'}"))
|
||||
case_rows = []
|
||||
if not isinstance(rule_rows, list) or len(rule_rows) != 137:
|
||||
errors.append(_finding("CASE_TYPE_RULE_REGISTRY_EXACT_137_REQUIRED", "case_type_rule_registry:/rows", f"observed={len(rule_rows) if isinstance(rule_rows, list) else 'invalid'}"))
|
||||
rule_rows = []
|
||||
if not isinstance(coverage_rows, list) or len(coverage_rows) != 137:
|
||||
errors.append(_finding("CASE_TYPE_COVERAGE_EXACT_137_REQUIRED", "case_type_coverage:/coverage_rows", f"observed={len(coverage_rows) if isinstance(coverage_rows, list) else 'invalid'}"))
|
||||
coverage_rows = []
|
||||
|
||||
observed_case_ids = tuple(row.get("case_type_id") for row in case_rows if isinstance(row, dict))
|
||||
observed_catalog_ids = tuple(row.get("catalog_row_id") for row in case_rows if isinstance(row, dict))
|
||||
declared_catalog_ids = tuple(case_registry.get("catalog_row_ids", ()))
|
||||
observed_rule_ids = tuple(row.get("case_type_id") for row in rule_rows if isinstance(row, dict))
|
||||
observed_coverage_ids = tuple(row.get("case_type_id") for row in coverage_rows if isinstance(row, dict))
|
||||
for code, path, observed, expected in (
|
||||
("CASE_TYPE_ID_ORDER_MISMATCH", "case_type_registry:/rows", observed_case_ids, EXPECTED_CASE_TYPE_IDS),
|
||||
("CATALOG_ROW_ID_ORDER_MISMATCH", "case_type_registry:/rows", observed_catalog_ids, EXPECTED_CATALOG_ROW_IDS),
|
||||
("DECLARED_CATALOG_ROW_ID_ORDER_MISMATCH", "case_type_registry:/catalog_row_ids", declared_catalog_ids, EXPECTED_CATALOG_ROW_IDS),
|
||||
("RULE_CASE_TYPE_ID_ORDER_MISMATCH", "case_type_rule_registry:/rows", observed_rule_ids, EXPECTED_CASE_TYPE_IDS),
|
||||
("COVERAGE_CASE_TYPE_ID_ORDER_MISMATCH", "case_type_coverage:/coverage_rows", observed_coverage_ids, EXPECTED_CASE_TYPE_IDS),
|
||||
):
|
||||
if observed != expected:
|
||||
errors.append(_finding(code, path, "exact CT/CAT ordinal sequence required"))
|
||||
|
||||
case_path = _physical(root, "manifest/case_type_registry.yml")
|
||||
if case_path is None:
|
||||
errors.append(_finding("CASE_TYPE_REGISTRY_PHYSICAL_FILE_REQUIRED", "case_type_registry:$", "manifest/case_type_registry.yml"))
|
||||
case_raw = _canonical_bytes(case_registry)
|
||||
else:
|
||||
case_raw = case_path.read_bytes()
|
||||
case_ref = rule_registry.get("case_type_registry_ref")
|
||||
case_hash = rule_registry.get("case_type_registry_sha256")
|
||||
if case_ref != "manifest/case_type_registry.yml" or case_hash != _sha256(case_raw):
|
||||
errors.append(_finding("RULE_REGISTRY_CASE_TYPE_REF_MISMATCH", "case_type_rule_registry:/case_type_registry_sha256", f"expected={_sha256(case_raw)};observed={case_hash}"))
|
||||
|
||||
coverage_dependency = coverage.get("registry_dependency")
|
||||
if isinstance(coverage_dependency, dict):
|
||||
declared = coverage_dependency.get("sha256")
|
||||
if declared is not None and declared != _sha256(case_raw):
|
||||
errors.append(_finding("COVERAGE_REGISTRY_HASH_MISMATCH", "case_type_coverage:/registry_dependency/sha256", f"expected={_sha256(case_raw)};observed={declared}"))
|
||||
else:
|
||||
errors.append(_finding("COVERAGE_REGISTRY_DEPENDENCY_REQUIRED", "case_type_coverage:/registry_dependency", "object required"))
|
||||
|
||||
rule_doc_count = 0
|
||||
for case_type_id in EXPECTED_CASE_TYPE_IDS:
|
||||
if _physical(root, f"rules/relief/{case_type_id}.md") is not None:
|
||||
rule_doc_count += 1
|
||||
pending_legal = (
|
||||
rule_doc_count != 137
|
||||
or _nested_pending(case_registry)
|
||||
or _nested_pending(rule_registry)
|
||||
or _nested_pending(coverage)
|
||||
)
|
||||
if pending_legal:
|
||||
if coverage.get("release_eligible") is True or _contains_ready_claim(coverage):
|
||||
errors.append(_finding("DISHONEST_FULL_137_READY_CLAIM", "case_type_coverage:$", f"rule_document_count={rule_doc_count};pending_contract=true"))
|
||||
pending.append(_finding("FULL_137_LEGAL_COVERAGE_PENDING", "case_type_coverage:$", f"rule_document_count={rule_doc_count};legal registry/sign-off not fully admitted"))
|
||||
return errors, pending, {
|
||||
"case_type_count": len(case_rows),
|
||||
"case_type_rule_row_count": len(rule_rows),
|
||||
"coverage_row_count": len(coverage_rows),
|
||||
"relief_rule_document_count": rule_doc_count,
|
||||
}
|
||||
|
||||
|
||||
def validate_package(
|
||||
root: Path,
|
||||
manifest_path: Path,
|
||||
release_path: Path,
|
||||
case_registry_path: Path,
|
||||
rule_registry_path: Path,
|
||||
coverage_path: Path,
|
||||
) -> dict[str, Any]:
|
||||
manifest = _load_json(manifest_path)
|
||||
release = _load_json(release_path)
|
||||
case_registry = _load_json(case_registry_path)
|
||||
rule_registry = _load_json(rule_registry_path)
|
||||
coverage = _load_json(coverage_path)
|
||||
if not all(isinstance(row, dict) for row in (manifest, release, case_registry, rule_registry, coverage)):
|
||||
raise ValidationInputError("ALL_RELEASE_INPUTS_MUST_BE_OBJECTS")
|
||||
errors, pending, counts = _validate_module_manifest(root, manifest)
|
||||
parent_errors, parent_pending = _validate_parent_release(root, release, manifest_path.read_bytes())
|
||||
coverage_errors, coverage_pending, coverage_counts = _validate_137_coverage(root, case_registry, rule_registry, coverage)
|
||||
errors.extend(parent_errors)
|
||||
errors.extend(coverage_errors)
|
||||
pending.extend(parent_pending)
|
||||
pending.extend(coverage_pending)
|
||||
if (
|
||||
any(row["code"] == "FULL_137_LEGAL_COVERAGE_PENDING" for row in coverage_pending)
|
||||
and (
|
||||
release.get("release_class") == "PRODUCTION_RELEASE"
|
||||
or (isinstance(release.get("bundle"), dict) and release["bundle"].get("mode") == "PRODUCTION")
|
||||
)
|
||||
):
|
||||
errors.append(_finding("PRODUCTION_RELEASE_WITH_PENDING_137_COVERAGE", "stage2_release:$", "full-137 legal/corpus coverage is not admitted"))
|
||||
counts.update(coverage_counts)
|
||||
errors = sorted(errors, key=lambda row: (row["code"], row["path"], row["detail"]))
|
||||
pending = sorted(pending, key=lambda row: (row["code"], row["path"], row["detail"]))
|
||||
return {
|
||||
"counts": dict(sorted(counts.items())),
|
||||
"errors": errors,
|
||||
"pending": pending,
|
||||
"schema_version": "stage2_release_validation.v1",
|
||||
"status": "PASS" if not errors else "FAIL",
|
||||
}
|
||||
|
||||
|
||||
def validate(root: Path, manifest: dict[str, object]) -> list[str]:
|
||||
"""Compatibility wrapper for callers that only validate module rows."""
|
||||
|
||||
errors, _, _ = _validate_module_manifest(root, manifest)
|
||||
return [f"{row['code']}:{row['path']}:{row['detail']}" for row in errors]
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Validate canonical Stage-2 raw release closure")
|
||||
parser.add_argument("root", type=Path)
|
||||
parser.add_argument("manifest", type=Path, nargs="?")
|
||||
parser.add_argument("--release", type=Path)
|
||||
parser.add_argument("--case-registry", type=Path)
|
||||
parser.add_argument("--rule-registry", type=Path)
|
||||
parser.add_argument("--coverage", type=Path)
|
||||
args = parser.parse_args()
|
||||
report = validate_package(
|
||||
args.root,
|
||||
args.manifest or args.root / "manifest/module_manifest.json",
|
||||
args.release or args.root / "manifest/stage2_release.json",
|
||||
args.case_registry or args.root / "manifest/case_type_registry.yml",
|
||||
args.rule_registry or args.root / "manifest/case_type_rule_registry.yml",
|
||||
args.coverage or args.root / "manifest/case_type_coverage.json",
|
||||
)
|
||||
print(json.dumps(report, ensure_ascii=False, sort_keys=True, separators=(",", ":")))
|
||||
return 0 if report["status"] == "PASS" else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
+581
@@ -0,0 +1,581 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Read-only validator for the canonical Stage-2 raw release closure.
|
||||
|
||||
The report separates contract errors from honest pending legal/live evidence.
|
||||
An offline PASS therefore never means that 137-case legal content, Weaviate,
|
||||
Code Executor, host CAS or production admission has been approved.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import copy
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path, PurePosixPath
|
||||
from typing import Any
|
||||
|
||||
|
||||
MODULE_MANIFEST_SCHEMA = "stage2_module_manifest.v1"
|
||||
PARENT_RELEASE_SCHEMA = "stage2_release.v2"
|
||||
SHA256_LENGTH = 64
|
||||
EXPECTED_CASE_TYPE_IDS = tuple(f"CT-{index:03d}" for index in range(1, 138))
|
||||
EXPECTED_CATALOG_ROW_IDS = tuple(f"CAT-{index:03d}" for index in range(1, 138))
|
||||
EXPECTED_MANIFEST_DIGEST_CONTRACT = {
|
||||
"algorithm_id": "STAGE2-MODULE-MANIFEST-DIGEST-V1",
|
||||
"array_order": "PRESERVE_DECLARED_ORDER",
|
||||
"canonicalization_algorithm_id": "STAGE2-CANONICAL-JSON-V1",
|
||||
"digest_algorithm": "sha256",
|
||||
"digest_scope": "ENTIRE_DOCUMENT_AFTER_REMOVING_TOP_LEVEL_MANIFEST_DIGEST",
|
||||
"encoding": "UTF-8",
|
||||
"line_termination": "LF_ONE_TRAILING_NEWLINE",
|
||||
"non_finite_numbers_allowed": False,
|
||||
"object_key_order": "SORT_CODEPOINT",
|
||||
}
|
||||
EXPECTED_RELEASE_DIGEST_CONTRACT = {
|
||||
"algorithm_id": "STAGE2-RELEASE-DIGEST-V1",
|
||||
"array_order": "PRESERVE_DECLARED_ORDER",
|
||||
"canonicalization_algorithm_id": "STAGE2-CANONICAL-JSON-V1",
|
||||
"digest_algorithm": "sha256",
|
||||
"digest_scope": "ENTIRE_DOCUMENT_AFTER_REMOVING_TOP_LEVEL_RELEASE_DIGEST",
|
||||
"encoding": "UTF-8",
|
||||
"line_termination": "LF_ONE_TRAILING_NEWLINE",
|
||||
"non_finite_numbers_allowed": False,
|
||||
"object_key_order": "SORT_CODEPOINT",
|
||||
}
|
||||
FORBIDDEN_PARENT_MEMBERS = frozenset(
|
||||
{
|
||||
"manifest/module_manifest.json",
|
||||
"manifest/stage2_release.json",
|
||||
"deployment/stage2_code_executor_binding.yml",
|
||||
"manifest/stage2_deterministic_admission_receipt.json",
|
||||
"agent_scripts/Stage_2_S2_00.yml",
|
||||
"runtime/s2_00_ingress.py",
|
||||
"runtime/s2_00_ingress.txt",
|
||||
"manifest/s2_00_inline_code_receipt.json",
|
||||
"agent_scripts/Stage_2_S2_20.yml",
|
||||
"runtime/s2_20_reduce.py",
|
||||
"runtime/s2_20_reduce.txt",
|
||||
"manifest/s2_20_inline_code_receipt.json",
|
||||
"manifest/s2_10_release.json",
|
||||
"manifest/s2_10_agent_receipt.json",
|
||||
"manifest/s2_10_platform_adapter_receipt.json",
|
||||
"manifest/s2_10_model_benchmark_receipt.json",
|
||||
"manifest/s2_10_legal_review_receipt.json",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class ValidationInputError(ValueError):
|
||||
"""Raised when a validation input cannot be parsed deterministically."""
|
||||
|
||||
|
||||
def _reject_duplicate_pairs(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
|
||||
result: dict[str, Any] = {}
|
||||
for key, value in pairs:
|
||||
if key in result:
|
||||
raise ValidationInputError(f"DUPLICATE_JSON_KEY:{key}")
|
||||
result[key] = value
|
||||
return result
|
||||
|
||||
|
||||
def _load_json(path: Path) -> Any:
|
||||
try:
|
||||
return json.loads(
|
||||
path.read_text(encoding="utf-8"),
|
||||
object_pairs_hook=_reject_duplicate_pairs,
|
||||
parse_constant=lambda token: (_ for _ in ()).throw(
|
||||
ValidationInputError(f"NON_FINITE_JSON_NUMBER:{token}")
|
||||
),
|
||||
)
|
||||
except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc:
|
||||
raise ValidationInputError(f"JSON_READ_FAILED:{path.as_posix()}:{exc}") from exc
|
||||
|
||||
|
||||
def _canonical_bytes(document: Any) -> bytes:
|
||||
return (
|
||||
json.dumps(
|
||||
document,
|
||||
ensure_ascii=False,
|
||||
sort_keys=True,
|
||||
separators=(",", ":"),
|
||||
allow_nan=False,
|
||||
)
|
||||
+ "\n"
|
||||
).encode("utf-8")
|
||||
|
||||
|
||||
def _sha256(raw: bytes) -> str:
|
||||
return hashlib.sha256(raw).hexdigest()
|
||||
|
||||
|
||||
def _document_digest(document: dict[str, Any], digest_field: str) -> str:
|
||||
payload = copy.deepcopy(document)
|
||||
payload.pop(digest_field, None)
|
||||
return _sha256(_canonical_bytes(payload))
|
||||
|
||||
|
||||
def _finding(code: str, path: str, detail: str) -> dict[str, str]:
|
||||
return {"code": code, "detail": detail, "path": path}
|
||||
|
||||
|
||||
def _is_sha256(value: Any) -> bool:
|
||||
return (
|
||||
isinstance(value, str)
|
||||
and len(value) == SHA256_LENGTH
|
||||
and all(character in "0123456789abcdef" for character in value)
|
||||
)
|
||||
|
||||
|
||||
def _contains_contract(value: Any, expected: dict[str, Any]) -> bool:
|
||||
return isinstance(value, dict) and all(value.get(key) == item for key, item in expected.items())
|
||||
|
||||
|
||||
def _relative_path(value: Any) -> str | None:
|
||||
if not isinstance(value, str) or not value or "\\" in value:
|
||||
return None
|
||||
pure = PurePosixPath(value)
|
||||
if pure.is_absolute() or any(part in {"", ".", ".."} for part in pure.parts):
|
||||
return None
|
||||
return pure.as_posix()
|
||||
|
||||
|
||||
def _legacy_path(relative: str) -> bool:
|
||||
return any(part in {"v.0", "v.1", "v.2", "v.3"} for part in PurePosixPath(relative).parts)
|
||||
|
||||
|
||||
def _physical(root: Path, relative: str) -> Path | None:
|
||||
candidate = root / relative
|
||||
if candidate.is_symlink():
|
||||
return None
|
||||
try:
|
||||
resolved = candidate.resolve(strict=True)
|
||||
root_real = root.resolve(strict=True)
|
||||
except FileNotFoundError:
|
||||
return None
|
||||
if root_real not in resolved.parents or not resolved.is_file():
|
||||
return None
|
||||
return resolved
|
||||
|
||||
|
||||
def _validate_module_manifest(
|
||||
root: Path,
|
||||
manifest: dict[str, Any],
|
||||
) -> tuple[list[dict[str, str]], list[dict[str, str]], dict[str, int]]:
|
||||
errors: list[dict[str, str]] = []
|
||||
pending: list[dict[str, str]] = []
|
||||
if manifest.get("schema_version") != MODULE_MANIFEST_SCHEMA:
|
||||
errors.append(_finding("MODULE_SCHEMA_VERSION", "$/schema_version", repr(manifest.get("schema_version"))))
|
||||
declared_digest = manifest.get("manifest_digest")
|
||||
observed_digest = _document_digest(manifest, "manifest_digest")
|
||||
if declared_digest != observed_digest:
|
||||
errors.append(_finding("MANIFEST_DIGEST_MISMATCH", "$/manifest_digest", f"declared={declared_digest};observed={observed_digest}"))
|
||||
if not _contains_contract(manifest.get("manifest_digest_contract"), EXPECTED_MANIFEST_DIGEST_CONTRACT):
|
||||
errors.append(_finding("MANIFEST_DIGEST_CONTRACT_INVALID", "$/manifest_digest_contract", repr(manifest.get("manifest_digest_contract"))))
|
||||
|
||||
rows = manifest.get("modules")
|
||||
if not isinstance(rows, list):
|
||||
return errors + [_finding("MODULES_REQUIRED", "$/modules", "canonical modules array missing")], pending, {"module_count": 0, "mirror_count": 0}
|
||||
seen_ids: set[str] = set()
|
||||
seen_paths: set[str] = set()
|
||||
row_by_path: dict[str, dict[str, Any]] = {}
|
||||
for index, row in enumerate(rows):
|
||||
location = f"$/modules/{index}"
|
||||
if not isinstance(row, dict):
|
||||
errors.append(_finding("MODULE_ROW_INVALID", location, "object required"))
|
||||
continue
|
||||
module_id = row.get("module_id")
|
||||
if not isinstance(module_id, str) or not module_id or module_id in seen_ids:
|
||||
errors.append(_finding("MODULE_ID_INVALID_OR_DUPLICATE", f"{location}/module_id", repr(module_id)))
|
||||
else:
|
||||
seen_ids.add(module_id)
|
||||
relative = _relative_path(row.get("path"))
|
||||
if relative is None or relative in seen_paths:
|
||||
errors.append(_finding("MODULE_PATH_INVALID_OR_DUPLICATE", f"{location}/path", repr(row.get("path"))))
|
||||
continue
|
||||
seen_paths.add(relative)
|
||||
row_by_path[relative] = row
|
||||
if relative in FORBIDDEN_PARENT_MEMBERS:
|
||||
errors.append(_finding("NON_CYCLIC_PARENT_VIOLATION", f"{location}/path", relative))
|
||||
if _legacy_path(relative):
|
||||
errors.append(_finding("LEGACY_STAGE2_DEPENDENCY", f"{location}/path", relative))
|
||||
physical = _physical(root, relative)
|
||||
if physical is None:
|
||||
errors.append(_finding("MODULE_MISSING_OR_SYMLINK", f"{location}/path", relative))
|
||||
continue
|
||||
raw = physical.read_bytes()
|
||||
observed = _sha256(raw)
|
||||
if row.get("sha256") != observed:
|
||||
errors.append(_finding("MODULE_HASH_MISMATCH", f"{location}/sha256", f"path={relative};observed={observed}"))
|
||||
if row.get("size_bytes") != len(raw):
|
||||
errors.append(_finding("MODULE_SIZE_MISMATCH", f"{location}/size_bytes", f"path={relative};observed={len(raw)}"))
|
||||
|
||||
if relative.endswith(".py"):
|
||||
mirror = relative[:-3] + ".txt"
|
||||
mirror_physical = _physical(root, mirror)
|
||||
if mirror_physical is None:
|
||||
errors.append(_finding("PYTHON_DOCUMENTATION_MIRROR_MISSING", location, mirror))
|
||||
continue
|
||||
mirror_raw = mirror_physical.read_bytes()
|
||||
if raw != mirror_raw:
|
||||
errors.append(_finding("PYTHON_DOCUMENTATION_MIRROR_MISMATCH", location, relative))
|
||||
expected_fields = {
|
||||
"mirror_path": mirror,
|
||||
"mirror_sha256": _sha256(mirror_raw),
|
||||
"mirror_size_bytes": len(mirror_raw),
|
||||
"mirror_byte_parity": True,
|
||||
}
|
||||
for key, expected in expected_fields.items():
|
||||
if row.get(key) != expected:
|
||||
errors.append(_finding("MODULE_MIRROR_BINDING_MISMATCH", f"{location}/{key}", f"expected={expected!r};observed={row.get(key)!r}"))
|
||||
|
||||
mirrors = manifest.get("documentation_mirrors")
|
||||
if not isinstance(mirrors, list):
|
||||
errors.append(_finding("DOCUMENTATION_MIRRORS_REQUIRED", "$/documentation_mirrors", "array required"))
|
||||
mirrors = []
|
||||
seen_sources: set[str] = set()
|
||||
for index, mirror in enumerate(mirrors):
|
||||
location = f"$/documentation_mirrors/{index}"
|
||||
if not isinstance(mirror, dict):
|
||||
errors.append(_finding("DOCUMENTATION_MIRROR_ROW_INVALID", location, "object required"))
|
||||
continue
|
||||
source = _relative_path(mirror.get("source_path"))
|
||||
target = _relative_path(mirror.get("mirror_path"))
|
||||
if source is None or target is None or source in seen_sources:
|
||||
errors.append(_finding("DOCUMENTATION_MIRROR_PATH_INVALID", location, repr(mirror)))
|
||||
continue
|
||||
seen_sources.add(source)
|
||||
if source in FORBIDDEN_PARENT_MEMBERS or target in FORBIDDEN_PARENT_MEMBERS:
|
||||
errors.append(_finding("NON_CYCLIC_MIRROR_VIOLATION", location, f"{source}->{target}"))
|
||||
source_physical = _physical(root, source)
|
||||
target_physical = _physical(root, target)
|
||||
if source_physical is None or target_physical is None:
|
||||
errors.append(_finding("DOCUMENTATION_MIRROR_MISSING", location, f"{source}->{target}"))
|
||||
continue
|
||||
source_raw = source_physical.read_bytes()
|
||||
target_raw = target_physical.read_bytes()
|
||||
expected = {
|
||||
"source_sha256": _sha256(source_raw),
|
||||
"source_size_bytes": len(source_raw),
|
||||
"mirror_sha256": _sha256(target_raw),
|
||||
"mirror_size_bytes": len(target_raw),
|
||||
"byte_identical": True,
|
||||
"runtime_import_allowed": False,
|
||||
}
|
||||
if source_raw != target_raw:
|
||||
errors.append(_finding("DOCUMENTATION_MIRROR_BYTES_DIFFER", location, source))
|
||||
for key, expected_value in expected.items():
|
||||
if mirror.get(key) != expected_value:
|
||||
errors.append(_finding("DOCUMENTATION_MIRROR_METADATA_MISMATCH", f"{location}/{key}", f"expected={expected_value!r};observed={mirror.get(key)!r}"))
|
||||
row = row_by_path.get(source)
|
||||
if row is None or row.get("mirror_path") != target:
|
||||
errors.append(_finding("DOCUMENTATION_MIRROR_MODULE_ORPHAN", location, source))
|
||||
|
||||
py_sources = {path for path in row_by_path if path.endswith(".py")}
|
||||
if seen_sources != py_sources:
|
||||
errors.append(_finding("DOCUMENTATION_MIRROR_COVERAGE_MISMATCH", "$/documentation_mirrors", f"expected={sorted(py_sources)};observed={sorted(seen_sources)}"))
|
||||
|
||||
dependency_graph: dict[str, tuple[str, ...]] = {}
|
||||
for index, row in enumerate(rows):
|
||||
if not isinstance(row, dict) or not isinstance(row.get("module_id"), str):
|
||||
continue
|
||||
dependencies = row.get("dependency_module_ids", [])
|
||||
location = f"$/modules/{index}/dependency_module_ids"
|
||||
if (
|
||||
not isinstance(dependencies, list)
|
||||
or not all(isinstance(item, str) and item for item in dependencies)
|
||||
or len(dependencies) != len(set(dependencies))
|
||||
):
|
||||
errors.append(_finding("MODULE_DEPENDENCY_LIST_INVALID", location, repr(dependencies)))
|
||||
continue
|
||||
missing = sorted(set(dependencies) - seen_ids)
|
||||
if missing:
|
||||
errors.append(_finding("MODULE_DEPENDENCY_MISSING", location, repr(missing)))
|
||||
if row["module_id"] in dependencies:
|
||||
errors.append(_finding("MODULE_SELF_DEPENDENCY", location, row["module_id"]))
|
||||
dependency_graph[row["module_id"]] = tuple(dependencies)
|
||||
|
||||
visiting: set[str] = set()
|
||||
visited: set[str] = set()
|
||||
|
||||
def visit(module_id: str, chain: tuple[str, ...]) -> None:
|
||||
if module_id in visiting:
|
||||
errors.append(_finding("MODULE_DEPENDENCY_CYCLE", "$/modules", "->".join(chain + (module_id,))))
|
||||
return
|
||||
if module_id in visited:
|
||||
return
|
||||
visiting.add(module_id)
|
||||
for dependency in dependency_graph.get(module_id, ()):
|
||||
if dependency in dependency_graph:
|
||||
visit(dependency, chain + (module_id,))
|
||||
visiting.remove(module_id)
|
||||
visited.add(module_id)
|
||||
|
||||
for module_id in sorted(dependency_graph):
|
||||
visit(module_id, ())
|
||||
summary = manifest.get("closure_summary")
|
||||
if not isinstance(summary, dict):
|
||||
errors.append(_finding("CLOSURE_SUMMARY_REQUIRED", "$/closure_summary", "object required"))
|
||||
else:
|
||||
for key, expected in (
|
||||
("module_count", len(rows)),
|
||||
("documentation_mirror_count", len(mirrors)),
|
||||
):
|
||||
if summary.get(key) != expected:
|
||||
errors.append(_finding("CLOSURE_SUMMARY_COUNT_MISMATCH", f"$/closure_summary/{key}", f"expected={expected};observed={summary.get(key)}"))
|
||||
for key in (
|
||||
"executable_agent_hash_included",
|
||||
"executor_binding_hash_included",
|
||||
"inline_runtime_code_hash_included",
|
||||
"self_hash_included",
|
||||
"stage2_release_hash_included",
|
||||
):
|
||||
if key in summary and summary.get(key) is not False:
|
||||
errors.append(_finding("NON_CYCLIC_SUMMARY_FLAG_INVALID", f"$/closure_summary/{key}", repr(summary.get(key))))
|
||||
return errors, pending, {"module_count": len(rows), "mirror_count": len(mirrors)}
|
||||
|
||||
|
||||
def _walk_parent_refs(
|
||||
root: Path,
|
||||
value: Any,
|
||||
errors: list[dict[str, str]],
|
||||
location: str = "$",
|
||||
) -> None:
|
||||
if isinstance(value, list):
|
||||
for index, item in enumerate(value):
|
||||
_walk_parent_refs(root, item, errors, f"{location}/{index}")
|
||||
return
|
||||
if not isinstance(value, dict):
|
||||
return
|
||||
relative = _relative_path(value.get("path"))
|
||||
if relative is not None and "<" not in relative:
|
||||
if relative == "manifest/module_manifest.json":
|
||||
pass
|
||||
elif relative in FORBIDDEN_PARENT_MEMBERS:
|
||||
if "sha256" in value or value.get("binding_status") == "BOUND":
|
||||
errors.append(_finding("NON_CYCLIC_PARENT_HASH_REFERENCE", location, relative))
|
||||
elif "sha256" in value and _is_sha256(value.get("sha256")):
|
||||
physical = _physical(root, relative)
|
||||
if physical is not None:
|
||||
observed = _sha256(physical.read_bytes())
|
||||
if value["sha256"] != observed:
|
||||
errors.append(_finding("PARENT_ASSET_HASH_MISMATCH", f"{location}/sha256", f"path={relative};observed={observed}"))
|
||||
for key, item in value.items():
|
||||
_walk_parent_refs(root, item, errors, f"{location}/{key}")
|
||||
|
||||
|
||||
def _validate_parent_release(
|
||||
root: Path,
|
||||
release: dict[str, Any],
|
||||
manifest_raw: bytes,
|
||||
) -> tuple[list[dict[str, str]], list[dict[str, str]]]:
|
||||
errors: list[dict[str, str]] = []
|
||||
pending: list[dict[str, str]] = []
|
||||
if release.get("schema_version") != PARENT_RELEASE_SCHEMA:
|
||||
errors.append(_finding("PARENT_RELEASE_SCHEMA_VERSION", "$/schema_version", repr(release.get("schema_version"))))
|
||||
if not _contains_contract(release.get("release_digest_contract"), EXPECTED_RELEASE_DIGEST_CONTRACT):
|
||||
errors.append(_finding("PARENT_RELEASE_DIGEST_CONTRACT_INVALID", "$/release_digest_contract", repr(release.get("release_digest_contract"))))
|
||||
declared = release.get("release_digest")
|
||||
observed = _document_digest(release, "release_digest")
|
||||
if declared != observed:
|
||||
errors.append(_finding("PARENT_RELEASE_DIGEST_MISMATCH", "$/release_digest", f"declared={declared};observed={observed}"))
|
||||
module_ref = release.get("module_manifest_ref")
|
||||
if not isinstance(module_ref, dict) or module_ref.get("path") != "manifest/module_manifest.json":
|
||||
errors.append(_finding("PARENT_MODULE_REF_INVALID", "$/module_manifest_ref", repr(module_ref)))
|
||||
else:
|
||||
expected_hash = _sha256(manifest_raw)
|
||||
if module_ref.get("sha256") != expected_hash:
|
||||
errors.append(_finding("PARENT_MODULE_REF_HASH_MISMATCH", "$/module_manifest_ref/sha256", f"expected={expected_hash};observed={module_ref.get('sha256')}"))
|
||||
executor_ref = release.get("executor_binding_ref")
|
||||
if (
|
||||
not isinstance(executor_ref, dict)
|
||||
or executor_ref.get("path") != "deployment/stage2_code_executor_binding.yml"
|
||||
or executor_ref.get("schema_id") != "stage2_code_executor_binding.v3"
|
||||
or "sha256" in executor_ref
|
||||
or executor_ref.get("binding_status") == "BOUND"
|
||||
):
|
||||
errors.append(_finding("PARENT_EXECUTOR_TRUST_ROOT_REF_INVALID", "$/executor_binding_ref", repr(executor_ref)))
|
||||
_walk_parent_refs(root, release, errors)
|
||||
|
||||
status = release.get("release_status")
|
||||
authorization = release.get("authorization_status")
|
||||
signature = release.get("signature")
|
||||
if status == "ADMITTED":
|
||||
if authorization not in {"CANARY_ADMITTED", "PRODUCTION_ADMITTED"}:
|
||||
errors.append(_finding("ADMITTED_RELEASE_AUTHORIZATION_INVALID", "$/authorization_status", repr(authorization)))
|
||||
if signature == "PENDING_SEQUENTIAL_BIND" or not isinstance(signature, str) or len(signature) < 32:
|
||||
errors.append(_finding("ADMITTED_RELEASE_SIGNATURE_INVALID", "$/signature", repr(signature)))
|
||||
else:
|
||||
pending.append(_finding("PARENT_RELEASE_NOT_ADMITTED", "$/release_status", str(status)))
|
||||
return errors, pending
|
||||
|
||||
|
||||
def _nested_pending(value: Any) -> bool:
|
||||
if isinstance(value, str):
|
||||
return "PENDING" in value or value in {"UNVERIFIED", "NOT_IMPLEMENTED", "DEV_UNAVAILABLE"}
|
||||
if isinstance(value, list):
|
||||
return any(_nested_pending(item) for item in value)
|
||||
if isinstance(value, dict):
|
||||
return any(_nested_pending(item) for item in value.values())
|
||||
return False
|
||||
|
||||
|
||||
def _contains_ready_claim(value: Any) -> bool:
|
||||
if isinstance(value, str):
|
||||
return value in {"FULL_137_PRODUCTION_READY", "PRODUCTION_READY", "ADMITTED"}
|
||||
if isinstance(value, list):
|
||||
return any(_contains_ready_claim(item) for item in value)
|
||||
if isinstance(value, dict):
|
||||
return any(_contains_ready_claim(item) for item in value.values())
|
||||
return False
|
||||
|
||||
|
||||
def _validate_137_coverage(
|
||||
root: Path,
|
||||
case_registry: dict[str, Any],
|
||||
rule_registry: dict[str, Any],
|
||||
coverage: dict[str, Any],
|
||||
) -> tuple[list[dict[str, str]], list[dict[str, str]], dict[str, int]]:
|
||||
errors: list[dict[str, str]] = []
|
||||
pending: list[dict[str, str]] = []
|
||||
case_rows = case_registry.get("rows")
|
||||
rule_rows = rule_registry.get("rows")
|
||||
coverage_rows = coverage.get("coverage_rows")
|
||||
if not isinstance(case_rows, list) or len(case_rows) != 137:
|
||||
errors.append(_finding("CASE_TYPE_REGISTRY_EXACT_137_REQUIRED", "case_type_registry:/rows", f"observed={len(case_rows) if isinstance(case_rows, list) else 'invalid'}"))
|
||||
case_rows = []
|
||||
if not isinstance(rule_rows, list) or len(rule_rows) != 137:
|
||||
errors.append(_finding("CASE_TYPE_RULE_REGISTRY_EXACT_137_REQUIRED", "case_type_rule_registry:/rows", f"observed={len(rule_rows) if isinstance(rule_rows, list) else 'invalid'}"))
|
||||
rule_rows = []
|
||||
if not isinstance(coverage_rows, list) or len(coverage_rows) != 137:
|
||||
errors.append(_finding("CASE_TYPE_COVERAGE_EXACT_137_REQUIRED", "case_type_coverage:/coverage_rows", f"observed={len(coverage_rows) if isinstance(coverage_rows, list) else 'invalid'}"))
|
||||
coverage_rows = []
|
||||
|
||||
observed_case_ids = tuple(row.get("case_type_id") for row in case_rows if isinstance(row, dict))
|
||||
observed_catalog_ids = tuple(row.get("catalog_row_id") for row in case_rows if isinstance(row, dict))
|
||||
declared_catalog_ids = tuple(case_registry.get("catalog_row_ids", ()))
|
||||
observed_rule_ids = tuple(row.get("case_type_id") for row in rule_rows if isinstance(row, dict))
|
||||
observed_coverage_ids = tuple(row.get("case_type_id") for row in coverage_rows if isinstance(row, dict))
|
||||
for code, path, observed, expected in (
|
||||
("CASE_TYPE_ID_ORDER_MISMATCH", "case_type_registry:/rows", observed_case_ids, EXPECTED_CASE_TYPE_IDS),
|
||||
("CATALOG_ROW_ID_ORDER_MISMATCH", "case_type_registry:/rows", observed_catalog_ids, EXPECTED_CATALOG_ROW_IDS),
|
||||
("DECLARED_CATALOG_ROW_ID_ORDER_MISMATCH", "case_type_registry:/catalog_row_ids", declared_catalog_ids, EXPECTED_CATALOG_ROW_IDS),
|
||||
("RULE_CASE_TYPE_ID_ORDER_MISMATCH", "case_type_rule_registry:/rows", observed_rule_ids, EXPECTED_CASE_TYPE_IDS),
|
||||
("COVERAGE_CASE_TYPE_ID_ORDER_MISMATCH", "case_type_coverage:/coverage_rows", observed_coverage_ids, EXPECTED_CASE_TYPE_IDS),
|
||||
):
|
||||
if observed != expected:
|
||||
errors.append(_finding(code, path, "exact CT/CAT ordinal sequence required"))
|
||||
|
||||
case_path = _physical(root, "manifest/case_type_registry.yml")
|
||||
if case_path is None:
|
||||
errors.append(_finding("CASE_TYPE_REGISTRY_PHYSICAL_FILE_REQUIRED", "case_type_registry:$", "manifest/case_type_registry.yml"))
|
||||
case_raw = _canonical_bytes(case_registry)
|
||||
else:
|
||||
case_raw = case_path.read_bytes()
|
||||
case_ref = rule_registry.get("case_type_registry_ref")
|
||||
case_hash = rule_registry.get("case_type_registry_sha256")
|
||||
if case_ref != "manifest/case_type_registry.yml" or case_hash != _sha256(case_raw):
|
||||
errors.append(_finding("RULE_REGISTRY_CASE_TYPE_REF_MISMATCH", "case_type_rule_registry:/case_type_registry_sha256", f"expected={_sha256(case_raw)};observed={case_hash}"))
|
||||
|
||||
coverage_dependency = coverage.get("registry_dependency")
|
||||
if isinstance(coverage_dependency, dict):
|
||||
declared = coverage_dependency.get("sha256")
|
||||
if declared is not None and declared != _sha256(case_raw):
|
||||
errors.append(_finding("COVERAGE_REGISTRY_HASH_MISMATCH", "case_type_coverage:/registry_dependency/sha256", f"expected={_sha256(case_raw)};observed={declared}"))
|
||||
else:
|
||||
errors.append(_finding("COVERAGE_REGISTRY_DEPENDENCY_REQUIRED", "case_type_coverage:/registry_dependency", "object required"))
|
||||
|
||||
rule_doc_count = 0
|
||||
for case_type_id in EXPECTED_CASE_TYPE_IDS:
|
||||
if _physical(root, f"rules/relief/{case_type_id}.md") is not None:
|
||||
rule_doc_count += 1
|
||||
pending_legal = (
|
||||
rule_doc_count != 137
|
||||
or _nested_pending(case_registry)
|
||||
or _nested_pending(rule_registry)
|
||||
or _nested_pending(coverage)
|
||||
)
|
||||
if pending_legal:
|
||||
if coverage.get("release_eligible") is True or _contains_ready_claim(coverage):
|
||||
errors.append(_finding("DISHONEST_FULL_137_READY_CLAIM", "case_type_coverage:$", f"rule_document_count={rule_doc_count};pending_contract=true"))
|
||||
pending.append(_finding("FULL_137_LEGAL_COVERAGE_PENDING", "case_type_coverage:$", f"rule_document_count={rule_doc_count};legal registry/sign-off not fully admitted"))
|
||||
return errors, pending, {
|
||||
"case_type_count": len(case_rows),
|
||||
"case_type_rule_row_count": len(rule_rows),
|
||||
"coverage_row_count": len(coverage_rows),
|
||||
"relief_rule_document_count": rule_doc_count,
|
||||
}
|
||||
|
||||
|
||||
def validate_package(
|
||||
root: Path,
|
||||
manifest_path: Path,
|
||||
release_path: Path,
|
||||
case_registry_path: Path,
|
||||
rule_registry_path: Path,
|
||||
coverage_path: Path,
|
||||
) -> dict[str, Any]:
|
||||
manifest = _load_json(manifest_path)
|
||||
release = _load_json(release_path)
|
||||
case_registry = _load_json(case_registry_path)
|
||||
rule_registry = _load_json(rule_registry_path)
|
||||
coverage = _load_json(coverage_path)
|
||||
if not all(isinstance(row, dict) for row in (manifest, release, case_registry, rule_registry, coverage)):
|
||||
raise ValidationInputError("ALL_RELEASE_INPUTS_MUST_BE_OBJECTS")
|
||||
errors, pending, counts = _validate_module_manifest(root, manifest)
|
||||
parent_errors, parent_pending = _validate_parent_release(root, release, manifest_path.read_bytes())
|
||||
coverage_errors, coverage_pending, coverage_counts = _validate_137_coverage(root, case_registry, rule_registry, coverage)
|
||||
errors.extend(parent_errors)
|
||||
errors.extend(coverage_errors)
|
||||
pending.extend(parent_pending)
|
||||
pending.extend(coverage_pending)
|
||||
if (
|
||||
any(row["code"] == "FULL_137_LEGAL_COVERAGE_PENDING" for row in coverage_pending)
|
||||
and (
|
||||
release.get("release_class") == "PRODUCTION_RELEASE"
|
||||
or (isinstance(release.get("bundle"), dict) and release["bundle"].get("mode") == "PRODUCTION")
|
||||
)
|
||||
):
|
||||
errors.append(_finding("PRODUCTION_RELEASE_WITH_PENDING_137_COVERAGE", "stage2_release:$", "full-137 legal/corpus coverage is not admitted"))
|
||||
counts.update(coverage_counts)
|
||||
errors = sorted(errors, key=lambda row: (row["code"], row["path"], row["detail"]))
|
||||
pending = sorted(pending, key=lambda row: (row["code"], row["path"], row["detail"]))
|
||||
return {
|
||||
"counts": dict(sorted(counts.items())),
|
||||
"errors": errors,
|
||||
"pending": pending,
|
||||
"schema_version": "stage2_release_validation.v1",
|
||||
"status": "PASS" if not errors else "FAIL",
|
||||
}
|
||||
|
||||
|
||||
def validate(root: Path, manifest: dict[str, object]) -> list[str]:
|
||||
"""Compatibility wrapper for callers that only validate module rows."""
|
||||
|
||||
errors, _, _ = _validate_module_manifest(root, manifest)
|
||||
return [f"{row['code']}:{row['path']}:{row['detail']}" for row in errors]
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Validate canonical Stage-2 raw release closure")
|
||||
parser.add_argument("root", type=Path)
|
||||
parser.add_argument("manifest", type=Path, nargs="?")
|
||||
parser.add_argument("--release", type=Path)
|
||||
parser.add_argument("--case-registry", type=Path)
|
||||
parser.add_argument("--rule-registry", type=Path)
|
||||
parser.add_argument("--coverage", type=Path)
|
||||
args = parser.parse_args()
|
||||
report = validate_package(
|
||||
args.root,
|
||||
args.manifest or args.root / "manifest/module_manifest.json",
|
||||
args.release or args.root / "manifest/stage2_release.json",
|
||||
args.case_registry or args.root / "manifest/case_type_registry.yml",
|
||||
args.rule_registry or args.root / "manifest/case_type_rule_registry.yml",
|
||||
args.coverage or args.root / "manifest/case_type_coverage.json",
|
||||
)
|
||||
print(json.dumps(report, ensure_ascii=False, sort_keys=True, separators=(",", ":")))
|
||||
return 0 if report["status"] == "PASS" else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"schema_version": "stage2.s2_20.renderer_descriptor.v1",
|
||||
"renderer_id": "R01",
|
||||
"renderer_kind": "MONEY_PAYMENT",
|
||||
"status": "PENDING_LEGAL_CONTENT",
|
||||
"execution_eligible": false,
|
||||
"stage2_20_role": "SELECT_AND_SEAL_ONLY",
|
||||
"stage2_40_role": "CLOSED_RENDER",
|
||||
"typed_slot_contract": {
|
||||
"structural_slot_names": [
|
||||
"amount",
|
||||
"currency",
|
||||
"obligor_ref",
|
||||
"payment_event_ref"
|
||||
],
|
||||
"legal_branch_may_add_required_slots_only_after_review": true,
|
||||
"missing_required_slot_issue_code": "RENDERER_REQUIRED_SLOT_MISSING"
|
||||
},
|
||||
"branch_rows": [],
|
||||
"closure": {
|
||||
"exactly_one_approved_branch_required": true,
|
||||
"free_text_template_fallback_forbidden": true,
|
||||
"renderer_may_not_change_claim_or_facts": true,
|
||||
"zero_match_issue_code": "RENDERER_BRANCH_ZERO_MATCH",
|
||||
"multi_match_issue_code": "RENDERER_BRANCH_MULTI_MATCH"
|
||||
},
|
||||
"review": {
|
||||
"required_role": "KOREAN_LAWYER",
|
||||
"status": "PENDING_KOREAN_LAWYER_REVIEW"
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"schema_version": "stage2.s2_20.renderer_descriptor.v1",
|
||||
"renderer_id": "R02",
|
||||
"renderer_kind": "DELIVERY_POSSESSION",
|
||||
"status": "PENDING_LEGAL_CONTENT",
|
||||
"execution_eligible": false,
|
||||
"stage2_20_role": "SELECT_AND_SEAL_ONLY",
|
||||
"stage2_40_role": "CLOSED_RENDER",
|
||||
"typed_slot_contract": {
|
||||
"structural_slot_names": [
|
||||
"object_ref",
|
||||
"obligor_ref",
|
||||
"performance_mode"
|
||||
],
|
||||
"legal_branch_may_add_required_slots_only_after_review": true,
|
||||
"missing_required_slot_issue_code": "RENDERER_REQUIRED_SLOT_MISSING"
|
||||
},
|
||||
"branch_rows": [],
|
||||
"closure": {
|
||||
"exactly_one_approved_branch_required": true,
|
||||
"free_text_template_fallback_forbidden": true,
|
||||
"renderer_may_not_change_claim_or_facts": true,
|
||||
"zero_match_issue_code": "RENDERER_BRANCH_ZERO_MATCH",
|
||||
"multi_match_issue_code": "RENDERER_BRANCH_MULTI_MATCH"
|
||||
},
|
||||
"review": {
|
||||
"required_role": "KOREAN_LAWYER",
|
||||
"status": "PENDING_KOREAN_LAWYER_REVIEW"
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"schema_version": "stage2.s2_20.renderer_descriptor.v1",
|
||||
"renderer_id": "R03",
|
||||
"renderer_kind": "DECLARATION_REGISTRY",
|
||||
"status": "PENDING_LEGAL_CONTENT",
|
||||
"execution_eligible": false,
|
||||
"stage2_20_role": "SELECT_AND_SEAL_ONLY",
|
||||
"stage2_40_role": "CLOSED_RENDER",
|
||||
"typed_slot_contract": {
|
||||
"structural_slot_names": [
|
||||
"registry_object_ref",
|
||||
"registry_action",
|
||||
"obligor_ref"
|
||||
],
|
||||
"legal_branch_may_add_required_slots_only_after_review": true,
|
||||
"missing_required_slot_issue_code": "RENDERER_REQUIRED_SLOT_MISSING"
|
||||
},
|
||||
"branch_rows": [],
|
||||
"closure": {
|
||||
"exactly_one_approved_branch_required": true,
|
||||
"free_text_template_fallback_forbidden": true,
|
||||
"renderer_may_not_change_claim_or_facts": true,
|
||||
"zero_match_issue_code": "RENDERER_BRANCH_ZERO_MATCH",
|
||||
"multi_match_issue_code": "RENDERER_BRANCH_MULTI_MATCH"
|
||||
},
|
||||
"review": {
|
||||
"required_role": "KOREAN_LAWYER",
|
||||
"status": "PENDING_KOREAN_LAWYER_REVIEW"
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"schema_version": "stage2.s2_20.renderer_descriptor.v1",
|
||||
"renderer_id": "R04",
|
||||
"renderer_kind": "SPECIAL_NONMONEY_PERFORMANCE",
|
||||
"status": "PENDING_LEGAL_CONTENT",
|
||||
"execution_eligible": false,
|
||||
"stage2_20_role": "SELECT_AND_SEAL_ONLY",
|
||||
"stage2_40_role": "CLOSED_RENDER",
|
||||
"typed_slot_contract": {
|
||||
"structural_slot_names": [
|
||||
"performance_object_ref",
|
||||
"performance_mode",
|
||||
"obligor_ref"
|
||||
],
|
||||
"legal_branch_may_add_required_slots_only_after_review": true,
|
||||
"missing_required_slot_issue_code": "RENDERER_REQUIRED_SLOT_MISSING"
|
||||
},
|
||||
"branch_rows": [],
|
||||
"closure": {
|
||||
"exactly_one_approved_branch_required": true,
|
||||
"free_text_template_fallback_forbidden": true,
|
||||
"renderer_may_not_change_claim_or_facts": true,
|
||||
"zero_match_issue_code": "RENDERER_BRANCH_ZERO_MATCH",
|
||||
"multi_match_issue_code": "RENDERER_BRANCH_MULTI_MATCH"
|
||||
},
|
||||
"review": {
|
||||
"required_role": "KOREAN_LAWYER",
|
||||
"status": "PENDING_KOREAN_LAWYER_REVIEW"
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"schema_version": "stage2.s2_20.renderer_descriptor.v1",
|
||||
"renderer_id": "R05",
|
||||
"renderer_kind": "DECLARATORY",
|
||||
"status": "PENDING_LEGAL_CONTENT",
|
||||
"execution_eligible": false,
|
||||
"stage2_20_role": "SELECT_AND_SEAL_ONLY",
|
||||
"stage2_40_role": "CLOSED_RENDER",
|
||||
"typed_slot_contract": {
|
||||
"structural_slot_names": [
|
||||
"legal_relation_ref",
|
||||
"declaration_scope",
|
||||
"opposing_party_ref"
|
||||
],
|
||||
"legal_branch_may_add_required_slots_only_after_review": true,
|
||||
"missing_required_slot_issue_code": "RENDERER_REQUIRED_SLOT_MISSING"
|
||||
},
|
||||
"branch_rows": [],
|
||||
"closure": {
|
||||
"exactly_one_approved_branch_required": true,
|
||||
"free_text_template_fallback_forbidden": true,
|
||||
"renderer_may_not_change_claim_or_facts": true,
|
||||
"zero_match_issue_code": "RENDERER_BRANCH_ZERO_MATCH",
|
||||
"multi_match_issue_code": "RENDERER_BRANCH_MULTI_MATCH"
|
||||
},
|
||||
"review": {
|
||||
"required_role": "KOREAN_LAWYER",
|
||||
"status": "PENDING_KOREAN_LAWYER_REVIEW"
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"schema_version": "stage2.s2_20.renderer_descriptor.v1",
|
||||
"renderer_id": "R06",
|
||||
"renderer_kind": "CONSTITUTIVE_JUDGMENT_CHALLENGE",
|
||||
"status": "PENDING_LEGAL_CONTENT",
|
||||
"execution_eligible": false,
|
||||
"stage2_20_role": "SELECT_AND_SEAL_ONLY",
|
||||
"stage2_40_role": "CLOSED_RENDER",
|
||||
"typed_slot_contract": {
|
||||
"structural_slot_names": [
|
||||
"legal_effect_ref",
|
||||
"constitutive_method",
|
||||
"opposing_party_ref"
|
||||
],
|
||||
"legal_branch_may_add_required_slots_only_after_review": true,
|
||||
"missing_required_slot_issue_code": "RENDERER_REQUIRED_SLOT_MISSING"
|
||||
},
|
||||
"branch_rows": [],
|
||||
"closure": {
|
||||
"exactly_one_approved_branch_required": true,
|
||||
"free_text_template_fallback_forbidden": true,
|
||||
"renderer_may_not_change_claim_or_facts": true,
|
||||
"zero_match_issue_code": "RENDERER_BRANCH_ZERO_MATCH",
|
||||
"multi_match_issue_code": "RENDERER_BRANCH_MULTI_MATCH"
|
||||
},
|
||||
"review": {
|
||||
"required_role": "KOREAN_LAWYER",
|
||||
"status": "PENDING_KOREAN_LAWYER_REVIEW"
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"schema_version": "stage2.s2_20.actio_pauliana_mortgage_route.v1",
|
||||
"registry_id": "S2-20-ACTIO-MORTGAGE-ROUTES",
|
||||
"status": "PENDING_LEGAL_CONTENT",
|
||||
"execution_eligible": false,
|
||||
"route_rows": [],
|
||||
"required_distinctions": [
|
||||
"ENCUMBERED_TRANSFER",
|
||||
"FRAUDULENT_MORTGAGE_CREATION",
|
||||
"MORTGAGE_CANCELLATION",
|
||||
"DISTRIBUTION_OR_DIVIDEND_EFFECT"
|
||||
],
|
||||
"closure": {
|
||||
"distinction_must_be_authority_backed": true,
|
||||
"zero_match_issue_code": "ACTIO_MORTGAGE_ROUTE_ZERO_MATCH",
|
||||
"multi_match_issue_code": "ACTIO_MORTGAGE_ROUTE_MULTI_MATCH",
|
||||
"value_compensation_guess_forbidden": true
|
||||
},
|
||||
"review": {
|
||||
"required_role": "KOREAN_LAWYER",
|
||||
"status": "PENDING_KOREAN_LAWYER_REVIEW"
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"schema_version": "stage2.s2_20.actio_pauliana_route_registry.v1",
|
||||
"registry_id": "S2-20-ACTIO-PAULIANA-ROUTES",
|
||||
"status": "PENDING_LEGAL_CONTENT",
|
||||
"execution_eligible": false,
|
||||
"required_approved_route_count": 6,
|
||||
"route_rows": [],
|
||||
"route_contract": {
|
||||
"required_fields": [
|
||||
"route_id",
|
||||
"predicate_id",
|
||||
"required_operands",
|
||||
"defendant_role",
|
||||
"restoration_recipient_role",
|
||||
"renderer_id",
|
||||
"calculation_branch_id",
|
||||
"authority_refs",
|
||||
"review_status"
|
||||
]
|
||||
},
|
||||
"closure": {
|
||||
"exactly_one_approved_route_required": true,
|
||||
"zero_match_issue_code": "ACTIO_ROUTE_ZERO_MATCH",
|
||||
"multi_match_issue_code": "ACTIO_ROUTE_MULTI_MATCH",
|
||||
"generic_actio_fallback_forbidden": true
|
||||
},
|
||||
"migration_sources_are_runtime_inputs": false,
|
||||
"review": {
|
||||
"required_role": "KOREAN_LAWYER",
|
||||
"status": "PENDING_KOREAN_LAWYER_REVIEW"
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Offline authority/law-value release integrity oracle."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import date
|
||||
import hashlib
|
||||
from pathlib import Path
|
||||
from typing import Any, Mapping, Sequence
|
||||
|
||||
|
||||
class AuthorityPreflightError(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
def verify_authority_rows(
|
||||
root: Path,
|
||||
authority_rows: Sequence[Mapping[str, Any]],
|
||||
as_of: date,
|
||||
) -> dict[str, Any]:
|
||||
verified: list[str] = []
|
||||
pending: list[dict[str, str]] = []
|
||||
for row in sorted(authority_rows, key=lambda item: str(item.get("authority_id"))):
|
||||
authority_id = row.get("authority_id")
|
||||
path = row.get("capture_path")
|
||||
expected_hash = row.get("capture_sha256")
|
||||
if not all(isinstance(value, str) and value for value in (authority_id, path, expected_hash)):
|
||||
raise AuthorityPreflightError("AUTHORITY_ROW_INCOMPLETE")
|
||||
target = (root / str(path)).resolve()
|
||||
if root.resolve() not in target.parents or not target.is_file():
|
||||
pending.append({"authority_id": str(authority_id), "reason": "CAPTURE_UNAVAILABLE"})
|
||||
continue
|
||||
if hashlib.sha256(target.read_bytes()).hexdigest() != expected_hash:
|
||||
pending.append({"authority_id": str(authority_id), "reason": "CAPTURE_HASH_MISMATCH"})
|
||||
continue
|
||||
effective_from = date.fromisoformat(str(row["effective_from"]))
|
||||
effective_to_raw = row.get("effective_to")
|
||||
effective_to = date.fromisoformat(str(effective_to_raw)) if effective_to_raw else None
|
||||
if as_of < effective_from or (effective_to is not None and as_of > effective_to):
|
||||
pending.append({"authority_id": str(authority_id), "reason": "OUTSIDE_EFFECTIVE_INTERVAL"})
|
||||
continue
|
||||
if row.get("approval_status") != "APPROVED":
|
||||
pending.append({"authority_id": str(authority_id), "reason": "LEGAL_APPROVAL_PENDING"})
|
||||
continue
|
||||
verified.append(str(authority_id))
|
||||
return {
|
||||
"status": "PASS" if not pending else "PENDING",
|
||||
"verified_authority_ids": verified,
|
||||
"pending_rows": pending,
|
||||
}
|
||||
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Offline authority/law-value release integrity oracle."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import date
|
||||
import hashlib
|
||||
from pathlib import Path
|
||||
from typing import Any, Mapping, Sequence
|
||||
|
||||
|
||||
class AuthorityPreflightError(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
def verify_authority_rows(
|
||||
root: Path,
|
||||
authority_rows: Sequence[Mapping[str, Any]],
|
||||
as_of: date,
|
||||
) -> dict[str, Any]:
|
||||
verified: list[str] = []
|
||||
pending: list[dict[str, str]] = []
|
||||
for row in sorted(authority_rows, key=lambda item: str(item.get("authority_id"))):
|
||||
authority_id = row.get("authority_id")
|
||||
path = row.get("capture_path")
|
||||
expected_hash = row.get("capture_sha256")
|
||||
if not all(isinstance(value, str) and value for value in (authority_id, path, expected_hash)):
|
||||
raise AuthorityPreflightError("AUTHORITY_ROW_INCOMPLETE")
|
||||
target = (root / str(path)).resolve()
|
||||
if root.resolve() not in target.parents or not target.is_file():
|
||||
pending.append({"authority_id": str(authority_id), "reason": "CAPTURE_UNAVAILABLE"})
|
||||
continue
|
||||
if hashlib.sha256(target.read_bytes()).hexdigest() != expected_hash:
|
||||
pending.append({"authority_id": str(authority_id), "reason": "CAPTURE_HASH_MISMATCH"})
|
||||
continue
|
||||
effective_from = date.fromisoformat(str(row["effective_from"]))
|
||||
effective_to_raw = row.get("effective_to")
|
||||
effective_to = date.fromisoformat(str(effective_to_raw)) if effective_to_raw else None
|
||||
if as_of < effective_from or (effective_to is not None and as_of > effective_to):
|
||||
pending.append({"authority_id": str(authority_id), "reason": "OUTSIDE_EFFECTIVE_INTERVAL"})
|
||||
continue
|
||||
if row.get("approval_status") != "APPROVED":
|
||||
pending.append({"authority_id": str(authority_id), "reason": "LEGAL_APPROVAL_PENDING"})
|
||||
continue
|
||||
verified.append(str(authority_id))
|
||||
return {
|
||||
"status": "PASS" if not pending else "PENDING",
|
||||
"verified_authority_ids": verified,
|
||||
"pending_rows": pending,
|
||||
}
|
||||
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Deterministic S2_10 -> C25 signature projection pure core.
|
||||
|
||||
The module is an offline oracle. Runtime delivery copies these functions into
|
||||
the single S2_20 YAML ``run_code`` body; a case run must not import this file.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from typing import Any, Mapping, Sequence
|
||||
|
||||
|
||||
SOURCE_FIELDS = frozenset(
|
||||
{
|
||||
"vocabulary_version",
|
||||
"legal_basis_family",
|
||||
"right_holder_refs",
|
||||
"obligor_refs",
|
||||
"performance_kind",
|
||||
"object_refs",
|
||||
"legal_effect",
|
||||
"source_occurrence_refs",
|
||||
}
|
||||
)
|
||||
TARGET_FIELDS = (
|
||||
"claim_family",
|
||||
"legal_effect",
|
||||
"performance_kind",
|
||||
"object_kind",
|
||||
"party_role_pattern",
|
||||
"source_obligation_kind",
|
||||
"remedy_mode",
|
||||
"registry_action",
|
||||
"procedural_posture",
|
||||
"special_statute_tags",
|
||||
)
|
||||
ALLOWED_SOURCE_ROOTS = frozenset({"signature", "c20", "c21", "remedy"})
|
||||
|
||||
|
||||
class ProjectionError(ValueError):
|
||||
"""Closed projection contract violation."""
|
||||
|
||||
|
||||
def canonical_json_bytes(value: Any) -> bytes:
|
||||
return (
|
||||
json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"), allow_nan=False)
|
||||
+ "\n"
|
||||
).encode("utf-8")
|
||||
|
||||
|
||||
def _dig(root: Mapping[str, Any], path: str) -> Any:
|
||||
parts = path.split(".")
|
||||
if not parts or parts[0] not in ALLOWED_SOURCE_ROOTS:
|
||||
raise ProjectionError(f"SOURCE_PATH_NOT_ALLOWED:{path}")
|
||||
current: Any = root
|
||||
for part in parts:
|
||||
if not isinstance(current, Mapping) or part not in current:
|
||||
return None
|
||||
current = current[part]
|
||||
return current
|
||||
|
||||
|
||||
def _stable_values(value: Any) -> list[str]:
|
||||
if value is None:
|
||||
return []
|
||||
raw = value if isinstance(value, list) else [value]
|
||||
if not all(isinstance(item, str) and item for item in raw):
|
||||
raise ProjectionError("NON_TOKEN_SOURCE_VALUE")
|
||||
return sorted(set(raw))
|
||||
|
||||
|
||||
def project_signature(
|
||||
source_signature: Mapping[str, Any],
|
||||
c20_projection: Mapping[str, Any],
|
||||
c21_projection: Mapping[str, Any],
|
||||
remedy_context: Mapping[str, Any],
|
||||
projection_registry: Mapping[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
"""Apply release-bound mappings; never infer from labels or free text."""
|
||||
|
||||
unknown_source = sorted(set(source_signature) - SOURCE_FIELDS)
|
||||
if unknown_source:
|
||||
raise ProjectionError(f"UNEXPECTED_S2_10_SIGNATURE_FIELDS:{unknown_source}")
|
||||
missing_source = sorted(SOURCE_FIELDS - set(source_signature))
|
||||
if missing_source:
|
||||
raise ProjectionError(f"MISSING_S2_10_SIGNATURE_FIELDS:{missing_source}")
|
||||
if source_signature.get("vocabulary_version") != "stage2.claim_signature_vocab.v1":
|
||||
raise ProjectionError("UNSUPPORTED_SOURCE_VOCABULARY")
|
||||
|
||||
rules = projection_registry.get("projection_rules")
|
||||
if not isinstance(rules, list):
|
||||
raise ProjectionError("PROJECTION_RULES_REQUIRED")
|
||||
if projection_registry.get("registry_status") != "APPROVED":
|
||||
rules = []
|
||||
by_target: dict[str, Mapping[str, Any]] = {}
|
||||
for rule in rules:
|
||||
if not isinstance(rule, Mapping):
|
||||
raise ProjectionError("PROJECTION_ROW_NOT_OBJECT")
|
||||
target = rule.get("target_field")
|
||||
if target not in TARGET_FIELDS or target in by_target:
|
||||
raise ProjectionError(f"TARGET_FIELD_INVALID_OR_DUPLICATE:{target}")
|
||||
by_target[str(target)] = rule
|
||||
if rules and set(by_target) != set(TARGET_FIELDS):
|
||||
raise ProjectionError("TARGET_FIELD_COVERAGE_MISMATCH")
|
||||
|
||||
source_values: Mapping[str, Any] = {
|
||||
**source_signature,
|
||||
"c20_remedy_projection": remedy_context.get("remedy_mode"),
|
||||
"c20_dependency_projection": c20_projection.get("dependency_kind"),
|
||||
"c21_party_projection": c21_projection.get("party_role_pattern"),
|
||||
"c21_object_projection": c21_projection.get("object_kind"),
|
||||
}
|
||||
target: dict[str, Any] = {}
|
||||
provenance: list[dict[str, Any]] = []
|
||||
for field in TARGET_FIELDS:
|
||||
rule = by_target.get(field)
|
||||
if rule is None:
|
||||
value = [] if field == "special_statute_tags" else "UNKNOWN"
|
||||
paths: list[str] = []
|
||||
distinct: list[str] = []
|
||||
else:
|
||||
paths = rule.get("source_fields")
|
||||
allowed = rule.get("allowed_output_values")
|
||||
operator = rule.get("derivation_operator")
|
||||
if not isinstance(paths, list) or not paths or not isinstance(allowed, list):
|
||||
raise ProjectionError(f"INVALID_PROJECTION_RULE:{field}")
|
||||
candidates = [token for path in paths for token in _stable_values(source_values.get(path))]
|
||||
distinct = sorted({token for token in candidates if token in allowed})
|
||||
if operator in {"DIRECT_COPY", "CLOSED_LOOKUP"}:
|
||||
value = distinct[0] if len(distinct) == 1 else "UNKNOWN"
|
||||
elif operator in {"SET_INTERSECTION", "SET_UNION"} and field == "special_statute_tags":
|
||||
value = distinct
|
||||
else:
|
||||
value = [] if field == "special_statute_tags" else "UNKNOWN"
|
||||
target[field] = value
|
||||
provenance.append(
|
||||
{
|
||||
"target_field": field,
|
||||
"value": value,
|
||||
"source_paths": sorted(paths),
|
||||
"status": "DERIVED" if value not in ("UNKNOWN", []) else "UNKNOWN",
|
||||
}
|
||||
)
|
||||
|
||||
payload = {
|
||||
"schema_version": "stage2.binding_target_signature.v1",
|
||||
"source_vocabulary_version": source_signature["vocabulary_version"],
|
||||
"target_vocabulary_version": projection_registry.get(
|
||||
"target_vocabulary_version", "stage2.case_type_signature_vocab.v1"
|
||||
),
|
||||
"signature": target,
|
||||
"provenance": provenance,
|
||||
}
|
||||
payload["signature_sha256"] = hashlib.sha256(canonical_json_bytes(target)).hexdigest()
|
||||
return payload
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Deterministic S2_10 -> C25 signature projection pure core.
|
||||
|
||||
The module is an offline oracle. Runtime delivery copies these functions into
|
||||
the single S2_20 YAML ``run_code`` body; a case run must not import this file.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from typing import Any, Mapping, Sequence
|
||||
|
||||
|
||||
SOURCE_FIELDS = frozenset(
|
||||
{
|
||||
"vocabulary_version",
|
||||
"legal_basis_family",
|
||||
"right_holder_refs",
|
||||
"obligor_refs",
|
||||
"performance_kind",
|
||||
"object_refs",
|
||||
"legal_effect",
|
||||
"source_occurrence_refs",
|
||||
}
|
||||
)
|
||||
TARGET_FIELDS = (
|
||||
"claim_family",
|
||||
"legal_effect",
|
||||
"performance_kind",
|
||||
"object_kind",
|
||||
"party_role_pattern",
|
||||
"source_obligation_kind",
|
||||
"remedy_mode",
|
||||
"registry_action",
|
||||
"procedural_posture",
|
||||
"special_statute_tags",
|
||||
)
|
||||
ALLOWED_SOURCE_ROOTS = frozenset({"signature", "c20", "c21", "remedy"})
|
||||
|
||||
|
||||
class ProjectionError(ValueError):
|
||||
"""Closed projection contract violation."""
|
||||
|
||||
|
||||
def canonical_json_bytes(value: Any) -> bytes:
|
||||
return (
|
||||
json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"), allow_nan=False)
|
||||
+ "\n"
|
||||
).encode("utf-8")
|
||||
|
||||
|
||||
def _dig(root: Mapping[str, Any], path: str) -> Any:
|
||||
parts = path.split(".")
|
||||
if not parts or parts[0] not in ALLOWED_SOURCE_ROOTS:
|
||||
raise ProjectionError(f"SOURCE_PATH_NOT_ALLOWED:{path}")
|
||||
current: Any = root
|
||||
for part in parts:
|
||||
if not isinstance(current, Mapping) or part not in current:
|
||||
return None
|
||||
current = current[part]
|
||||
return current
|
||||
|
||||
|
||||
def _stable_values(value: Any) -> list[str]:
|
||||
if value is None:
|
||||
return []
|
||||
raw = value if isinstance(value, list) else [value]
|
||||
if not all(isinstance(item, str) and item for item in raw):
|
||||
raise ProjectionError("NON_TOKEN_SOURCE_VALUE")
|
||||
return sorted(set(raw))
|
||||
|
||||
|
||||
def project_signature(
|
||||
source_signature: Mapping[str, Any],
|
||||
c20_projection: Mapping[str, Any],
|
||||
c21_projection: Mapping[str, Any],
|
||||
remedy_context: Mapping[str, Any],
|
||||
projection_registry: Mapping[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
"""Apply release-bound mappings; never infer from labels or free text."""
|
||||
|
||||
unknown_source = sorted(set(source_signature) - SOURCE_FIELDS)
|
||||
if unknown_source:
|
||||
raise ProjectionError(f"UNEXPECTED_S2_10_SIGNATURE_FIELDS:{unknown_source}")
|
||||
missing_source = sorted(SOURCE_FIELDS - set(source_signature))
|
||||
if missing_source:
|
||||
raise ProjectionError(f"MISSING_S2_10_SIGNATURE_FIELDS:{missing_source}")
|
||||
if source_signature.get("vocabulary_version") != "stage2.claim_signature_vocab.v1":
|
||||
raise ProjectionError("UNSUPPORTED_SOURCE_VOCABULARY")
|
||||
|
||||
rules = projection_registry.get("projection_rules")
|
||||
if not isinstance(rules, list):
|
||||
raise ProjectionError("PROJECTION_RULES_REQUIRED")
|
||||
if projection_registry.get("registry_status") != "APPROVED":
|
||||
rules = []
|
||||
by_target: dict[str, Mapping[str, Any]] = {}
|
||||
for rule in rules:
|
||||
if not isinstance(rule, Mapping):
|
||||
raise ProjectionError("PROJECTION_ROW_NOT_OBJECT")
|
||||
target = rule.get("target_field")
|
||||
if target not in TARGET_FIELDS or target in by_target:
|
||||
raise ProjectionError(f"TARGET_FIELD_INVALID_OR_DUPLICATE:{target}")
|
||||
by_target[str(target)] = rule
|
||||
if rules and set(by_target) != set(TARGET_FIELDS):
|
||||
raise ProjectionError("TARGET_FIELD_COVERAGE_MISMATCH")
|
||||
|
||||
source_values: Mapping[str, Any] = {
|
||||
**source_signature,
|
||||
"c20_remedy_projection": remedy_context.get("remedy_mode"),
|
||||
"c20_dependency_projection": c20_projection.get("dependency_kind"),
|
||||
"c21_party_projection": c21_projection.get("party_role_pattern"),
|
||||
"c21_object_projection": c21_projection.get("object_kind"),
|
||||
}
|
||||
target: dict[str, Any] = {}
|
||||
provenance: list[dict[str, Any]] = []
|
||||
for field in TARGET_FIELDS:
|
||||
rule = by_target.get(field)
|
||||
if rule is None:
|
||||
value = [] if field == "special_statute_tags" else "UNKNOWN"
|
||||
paths: list[str] = []
|
||||
distinct: list[str] = []
|
||||
else:
|
||||
paths = rule.get("source_fields")
|
||||
allowed = rule.get("allowed_output_values")
|
||||
operator = rule.get("derivation_operator")
|
||||
if not isinstance(paths, list) or not paths or not isinstance(allowed, list):
|
||||
raise ProjectionError(f"INVALID_PROJECTION_RULE:{field}")
|
||||
candidates = [token for path in paths for token in _stable_values(source_values.get(path))]
|
||||
distinct = sorted({token for token in candidates if token in allowed})
|
||||
if operator in {"DIRECT_COPY", "CLOSED_LOOKUP"}:
|
||||
value = distinct[0] if len(distinct) == 1 else "UNKNOWN"
|
||||
elif operator in {"SET_INTERSECTION", "SET_UNION"} and field == "special_statute_tags":
|
||||
value = distinct
|
||||
else:
|
||||
value = [] if field == "special_statute_tags" else "UNKNOWN"
|
||||
target[field] = value
|
||||
provenance.append(
|
||||
{
|
||||
"target_field": field,
|
||||
"value": value,
|
||||
"source_paths": sorted(paths),
|
||||
"status": "DERIVED" if value not in ("UNKNOWN", []) else "UNKNOWN",
|
||||
}
|
||||
)
|
||||
|
||||
payload = {
|
||||
"schema_version": "stage2.binding_target_signature.v1",
|
||||
"source_vocabulary_version": source_signature["vocabulary_version"],
|
||||
"target_vocabulary_version": projection_registry.get(
|
||||
"target_vocabulary_version", "stage2.case_type_signature_vocab.v1"
|
||||
),
|
||||
"signature": target,
|
||||
"provenance": provenance,
|
||||
}
|
||||
payload["signature_sha256"] = hashlib.sha256(canonical_json_bytes(target)).hexdigest()
|
||||
return payload
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Closed-predicate C25 case-type binding pure core."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Mapping, Sequence
|
||||
|
||||
|
||||
ALLOWED_OPERATORS = frozenset({"EQ", "IN", "ALL_OF", "NONE_OF", "EXISTS"})
|
||||
ELIGIBLE_ROW_KINDS = frozenset({"CLAIM", "GENERIC"})
|
||||
|
||||
|
||||
class BindingError(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
def evaluate_predicate(signature: Mapping[str, Any], clauses: Sequence[Mapping[str, Any]]) -> bool:
|
||||
for clause in clauses:
|
||||
field = clause.get("field")
|
||||
operator = clause.get("operator")
|
||||
expected = clause.get("value")
|
||||
if not isinstance(field, str) or operator not in ALLOWED_OPERATORS:
|
||||
raise BindingError("INVALID_PREDICATE_CLAUSE")
|
||||
actual = signature.get(field, "UNKNOWN")
|
||||
if operator == "EQ":
|
||||
passed = actual == expected
|
||||
elif operator == "IN":
|
||||
passed = isinstance(expected, list) and actual in expected
|
||||
elif operator == "ALL_OF":
|
||||
passed = isinstance(actual, list) and isinstance(expected, list) and set(expected) <= set(actual)
|
||||
elif operator == "NONE_OF":
|
||||
passed = isinstance(actual, list) and isinstance(expected, list) and set(expected).isdisjoint(actual)
|
||||
else:
|
||||
passed = (actual not in (None, "UNKNOWN", [])) is bool(expected)
|
||||
if not passed:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def bind_case_type(signature: Mapping[str, Any], registry_rows: Sequence[Mapping[str, Any]]) -> dict[str, Any]:
|
||||
matches: list[dict[str, str]] = []
|
||||
seen_ids: set[str] = set()
|
||||
for row in registry_rows:
|
||||
case_type_id = row.get("case_type_id")
|
||||
if not isinstance(case_type_id, str):
|
||||
raise BindingError("REGISTRY_ID_REQUIRED")
|
||||
if case_type_id in seen_ids:
|
||||
raise BindingError("DUPLICATE_CASE_TYPE_ID")
|
||||
seen_ids.add(case_type_id)
|
||||
row_kind = row.get("row_kind")
|
||||
if row_kind not in ELIGIBLE_ROW_KINDS:
|
||||
continue
|
||||
if (
|
||||
row.get("claim_capable_disposition") != "CLAIM_CAPABLE"
|
||||
or row.get("legal_classification_status") != "APPROVED"
|
||||
or row.get("legal_content_status") != "APPROVED"
|
||||
):
|
||||
continue
|
||||
predicates = row.get("case_type_predicates")
|
||||
if not isinstance(predicates, list) or len(predicates) != 1:
|
||||
continue
|
||||
predicate = predicates[0]
|
||||
if (
|
||||
not isinstance(predicate, Mapping)
|
||||
or predicate.get("legal_review_status") != "APPROVED"
|
||||
or not isinstance(predicate.get("predicate_id"), str)
|
||||
or not isinstance(predicate.get("clauses"), list)
|
||||
):
|
||||
continue
|
||||
predicate_id = str(predicate["predicate_id"])
|
||||
if evaluate_predicate(signature, predicate["clauses"]):
|
||||
matches.append({"case_type_id": case_type_id, "matched_case_type_predicate_id": predicate_id})
|
||||
matches.sort(key=lambda item: (item["case_type_id"], item["matched_case_type_predicate_id"]))
|
||||
if len(matches) == 1:
|
||||
return {"binding_status": "BOUND", **matches[0], "binding_issue_codes": []}
|
||||
return {
|
||||
"binding_status": "UNRESOLVED" if not matches else "CONFLICT",
|
||||
"matched_case_type_id": None,
|
||||
"matched_case_type_predicate_id": None,
|
||||
"candidate_matches": matches,
|
||||
"binding_issue_codes": [
|
||||
"CASE_TYPE_BINDING_ZERO_MATCH" if not matches else "CASE_TYPE_BINDING_MULTI_MATCH"
|
||||
],
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Closed-predicate C25 case-type binding pure core."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Mapping, Sequence
|
||||
|
||||
|
||||
ALLOWED_OPERATORS = frozenset({"EQ", "IN", "ALL_OF", "NONE_OF", "EXISTS"})
|
||||
ELIGIBLE_ROW_KINDS = frozenset({"CLAIM", "GENERIC"})
|
||||
|
||||
|
||||
class BindingError(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
def evaluate_predicate(signature: Mapping[str, Any], clauses: Sequence[Mapping[str, Any]]) -> bool:
|
||||
for clause in clauses:
|
||||
field = clause.get("field")
|
||||
operator = clause.get("operator")
|
||||
expected = clause.get("value")
|
||||
if not isinstance(field, str) or operator not in ALLOWED_OPERATORS:
|
||||
raise BindingError("INVALID_PREDICATE_CLAUSE")
|
||||
actual = signature.get(field, "UNKNOWN")
|
||||
if operator == "EQ":
|
||||
passed = actual == expected
|
||||
elif operator == "IN":
|
||||
passed = isinstance(expected, list) and actual in expected
|
||||
elif operator == "ALL_OF":
|
||||
passed = isinstance(actual, list) and isinstance(expected, list) and set(expected) <= set(actual)
|
||||
elif operator == "NONE_OF":
|
||||
passed = isinstance(actual, list) and isinstance(expected, list) and set(expected).isdisjoint(actual)
|
||||
else:
|
||||
passed = (actual not in (None, "UNKNOWN", [])) is bool(expected)
|
||||
if not passed:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def bind_case_type(signature: Mapping[str, Any], registry_rows: Sequence[Mapping[str, Any]]) -> dict[str, Any]:
|
||||
matches: list[dict[str, str]] = []
|
||||
seen_ids: set[str] = set()
|
||||
for row in registry_rows:
|
||||
case_type_id = row.get("case_type_id")
|
||||
if not isinstance(case_type_id, str):
|
||||
raise BindingError("REGISTRY_ID_REQUIRED")
|
||||
if case_type_id in seen_ids:
|
||||
raise BindingError("DUPLICATE_CASE_TYPE_ID")
|
||||
seen_ids.add(case_type_id)
|
||||
row_kind = row.get("row_kind")
|
||||
if row_kind not in ELIGIBLE_ROW_KINDS:
|
||||
continue
|
||||
if (
|
||||
row.get("claim_capable_disposition") != "CLAIM_CAPABLE"
|
||||
or row.get("legal_classification_status") != "APPROVED"
|
||||
or row.get("legal_content_status") != "APPROVED"
|
||||
):
|
||||
continue
|
||||
predicates = row.get("case_type_predicates")
|
||||
if not isinstance(predicates, list) or len(predicates) != 1:
|
||||
continue
|
||||
predicate = predicates[0]
|
||||
if (
|
||||
not isinstance(predicate, Mapping)
|
||||
or predicate.get("legal_review_status") != "APPROVED"
|
||||
or not isinstance(predicate.get("predicate_id"), str)
|
||||
or not isinstance(predicate.get("clauses"), list)
|
||||
):
|
||||
continue
|
||||
predicate_id = str(predicate["predicate_id"])
|
||||
if evaluate_predicate(signature, predicate["clauses"]):
|
||||
matches.append({"case_type_id": case_type_id, "matched_case_type_predicate_id": predicate_id})
|
||||
matches.sort(key=lambda item: (item["case_type_id"], item["matched_case_type_predicate_id"]))
|
||||
if len(matches) == 1:
|
||||
return {"binding_status": "BOUND", **matches[0], "binding_issue_codes": []}
|
||||
return {
|
||||
"binding_status": "UNRESOLVED" if not matches else "CONFLICT",
|
||||
"matched_case_type_id": None,
|
||||
"matched_case_type_predicate_id": None,
|
||||
"candidate_matches": matches,
|
||||
"binding_issue_codes": [
|
||||
"CASE_TYPE_BINDING_ZERO_MATCH" if not matches else "CASE_TYPE_BINDING_MULTI_MATCH"
|
||||
],
|
||||
}
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
#!/usr/bin/env python3
|
||||
"""C26 exact sub-rule/renderer/party/corpus binding pure core."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Mapping, Sequence
|
||||
|
||||
|
||||
class RuleBindingError(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
def _evaluate(signature: Mapping[str, Any], clauses: Sequence[Mapping[str, Any]]) -> bool:
|
||||
allowed = {"EQ", "IN", "ALL_OF", "NONE_OF", "EXISTS"}
|
||||
for clause in clauses:
|
||||
field = clause.get("field")
|
||||
op = clause.get("operator")
|
||||
expected = clause.get("value")
|
||||
if not isinstance(field, str) or op not in allowed:
|
||||
raise RuleBindingError("INVALID_SUB_RULE_PREDICATE")
|
||||
actual = signature.get(field, "UNKNOWN")
|
||||
if op == "EQ":
|
||||
ok = actual == expected
|
||||
elif op == "IN":
|
||||
ok = isinstance(expected, list) and actual in expected
|
||||
elif op == "ALL_OF":
|
||||
ok = isinstance(actual, list) and isinstance(expected, list) and set(expected) <= set(actual)
|
||||
elif op == "NONE_OF":
|
||||
ok = isinstance(actual, list) and isinstance(expected, list) and set(expected).isdisjoint(actual)
|
||||
else:
|
||||
ok = (actual not in (None, "UNKNOWN", [])) is bool(expected)
|
||||
if not ok:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def bind_rule_branch(
|
||||
signature: Mapping[str, Any],
|
||||
case_binding: Mapping[str, Any],
|
||||
rule_rows: Sequence[Mapping[str, Any]],
|
||||
asset_hashes: Mapping[str, str],
|
||||
) -> dict[str, Any]:
|
||||
if case_binding.get("binding_status") != "BOUND":
|
||||
return {
|
||||
"binding_status": "UNRESOLVED",
|
||||
"binding_issue_codes": ["QUERY_NOT_RUN_UNRESOLVED_BINDING"],
|
||||
}
|
||||
case_type_id = case_binding.get("case_type_id") or case_binding.get("matched_case_type_id")
|
||||
candidates: list[Mapping[str, Any]] = []
|
||||
for row in rule_rows:
|
||||
if row.get("case_type_id") != case_type_id:
|
||||
continue
|
||||
if (
|
||||
row.get("claim_capable_disposition") != "CLAIM_CAPABLE"
|
||||
or row.get("legal_classification_status") != "APPROVED"
|
||||
or row.get("legal_content_status") != "APPROVED"
|
||||
):
|
||||
continue
|
||||
branches = row.get("rule_branches")
|
||||
if not isinstance(branches, list):
|
||||
raise RuleBindingError("RULE_BRANCHES_ARRAY_REQUIRED")
|
||||
for branch in branches:
|
||||
if not isinstance(branch, Mapping) or branch.get("legal_content_status") != "APPROVED":
|
||||
continue
|
||||
predicates = branch.get("sub_rule_predicates")
|
||||
if not isinstance(predicates, list) or len(predicates) != 1:
|
||||
continue
|
||||
predicate = predicates[0]
|
||||
if (
|
||||
isinstance(predicate, Mapping)
|
||||
and predicate.get("legal_review_status") == "APPROVED"
|
||||
and isinstance(predicate.get("clauses"), list)
|
||||
and _evaluate(signature, predicate["clauses"])
|
||||
):
|
||||
candidates.append(branch)
|
||||
candidates.sort(key=lambda row: str(row.get("sub_rule_id")))
|
||||
if len(candidates) != 1:
|
||||
return {
|
||||
"binding_status": "UNRESOLVED" if not candidates else "CONFLICT",
|
||||
"binding_issue_codes": [
|
||||
"SUB_RULE_BINDING_ZERO_MATCH" if not candidates else "SUB_RULE_BINDING_MULTI_MATCH"
|
||||
],
|
||||
"candidate_sub_rule_ids": [row.get("sub_rule_id") for row in candidates],
|
||||
}
|
||||
row = candidates[0]
|
||||
required_fields = (
|
||||
"sub_rule_id", "rule_branch_key", "markdown_source_path",
|
||||
"markdown_source_sha256", "renderer_ids", "corpus_scope_id",
|
||||
"element_set_id", "party_set_rule_id", "ce13_branch_id",
|
||||
)
|
||||
missing = [name for name in required_fields if row.get(name) in (None, "", [])]
|
||||
markdown_path = row.get("markdown_source_path")
|
||||
markdown_hash = row.get("markdown_source_sha256")
|
||||
hash_mismatch = (
|
||||
isinstance(markdown_path, str)
|
||||
and markdown_path in asset_hashes
|
||||
and asset_hashes[markdown_path] != markdown_hash
|
||||
)
|
||||
if missing or hash_mismatch:
|
||||
return {
|
||||
"binding_status": "CONFLICT",
|
||||
"binding_issue_codes": sorted(
|
||||
({"RULE_BRANCH_MISSING"} if missing else set())
|
||||
| ({"RULE_HASH_MISMATCH"} if hash_mismatch else set())
|
||||
),
|
||||
"missing_refs": sorted(missing),
|
||||
"hash_mismatches": ["markdown_source_sha256"] if hash_mismatch else [],
|
||||
}
|
||||
sub_rule_id = row.get("sub_rule_id")
|
||||
predicates = row.get("sub_rule_predicates", [])
|
||||
return {
|
||||
"binding_status": "BOUND",
|
||||
"case_type_id": case_type_id,
|
||||
"sub_rule_id": sub_rule_id,
|
||||
"rule_branch_key": row.get("rule_branch_key"),
|
||||
"matched_sub_rule_predicate_id": predicates[0].get("predicate_id"),
|
||||
"renderer_ids": list(row.get("renderer_ids", [])),
|
||||
"party_set_rule_id": row.get("party_set_rule_id"),
|
||||
"corpus_scope_id": row.get("corpus_scope_id"),
|
||||
"element_set_id": row.get("element_set_id"),
|
||||
"ce13_branch_id": row.get("ce13_branch_id"),
|
||||
"binding_issue_codes": [],
|
||||
}
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
#!/usr/bin/env python3
|
||||
"""C26 exact sub-rule/renderer/party/corpus binding pure core."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Mapping, Sequence
|
||||
|
||||
|
||||
class RuleBindingError(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
def _evaluate(signature: Mapping[str, Any], clauses: Sequence[Mapping[str, Any]]) -> bool:
|
||||
allowed = {"EQ", "IN", "ALL_OF", "NONE_OF", "EXISTS"}
|
||||
for clause in clauses:
|
||||
field = clause.get("field")
|
||||
op = clause.get("operator")
|
||||
expected = clause.get("value")
|
||||
if not isinstance(field, str) or op not in allowed:
|
||||
raise RuleBindingError("INVALID_SUB_RULE_PREDICATE")
|
||||
actual = signature.get(field, "UNKNOWN")
|
||||
if op == "EQ":
|
||||
ok = actual == expected
|
||||
elif op == "IN":
|
||||
ok = isinstance(expected, list) and actual in expected
|
||||
elif op == "ALL_OF":
|
||||
ok = isinstance(actual, list) and isinstance(expected, list) and set(expected) <= set(actual)
|
||||
elif op == "NONE_OF":
|
||||
ok = isinstance(actual, list) and isinstance(expected, list) and set(expected).isdisjoint(actual)
|
||||
else:
|
||||
ok = (actual not in (None, "UNKNOWN", [])) is bool(expected)
|
||||
if not ok:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def bind_rule_branch(
|
||||
signature: Mapping[str, Any],
|
||||
case_binding: Mapping[str, Any],
|
||||
rule_rows: Sequence[Mapping[str, Any]],
|
||||
asset_hashes: Mapping[str, str],
|
||||
) -> dict[str, Any]:
|
||||
if case_binding.get("binding_status") != "BOUND":
|
||||
return {
|
||||
"binding_status": "UNRESOLVED",
|
||||
"binding_issue_codes": ["QUERY_NOT_RUN_UNRESOLVED_BINDING"],
|
||||
}
|
||||
case_type_id = case_binding.get("case_type_id") or case_binding.get("matched_case_type_id")
|
||||
candidates: list[Mapping[str, Any]] = []
|
||||
for row in rule_rows:
|
||||
if row.get("case_type_id") != case_type_id:
|
||||
continue
|
||||
if (
|
||||
row.get("claim_capable_disposition") != "CLAIM_CAPABLE"
|
||||
or row.get("legal_classification_status") != "APPROVED"
|
||||
or row.get("legal_content_status") != "APPROVED"
|
||||
):
|
||||
continue
|
||||
branches = row.get("rule_branches")
|
||||
if not isinstance(branches, list):
|
||||
raise RuleBindingError("RULE_BRANCHES_ARRAY_REQUIRED")
|
||||
for branch in branches:
|
||||
if not isinstance(branch, Mapping) or branch.get("legal_content_status") != "APPROVED":
|
||||
continue
|
||||
predicates = branch.get("sub_rule_predicates")
|
||||
if not isinstance(predicates, list) or len(predicates) != 1:
|
||||
continue
|
||||
predicate = predicates[0]
|
||||
if (
|
||||
isinstance(predicate, Mapping)
|
||||
and predicate.get("legal_review_status") == "APPROVED"
|
||||
and isinstance(predicate.get("clauses"), list)
|
||||
and _evaluate(signature, predicate["clauses"])
|
||||
):
|
||||
candidates.append(branch)
|
||||
candidates.sort(key=lambda row: str(row.get("sub_rule_id")))
|
||||
if len(candidates) != 1:
|
||||
return {
|
||||
"binding_status": "UNRESOLVED" if not candidates else "CONFLICT",
|
||||
"binding_issue_codes": [
|
||||
"SUB_RULE_BINDING_ZERO_MATCH" if not candidates else "SUB_RULE_BINDING_MULTI_MATCH"
|
||||
],
|
||||
"candidate_sub_rule_ids": [row.get("sub_rule_id") for row in candidates],
|
||||
}
|
||||
row = candidates[0]
|
||||
required_fields = (
|
||||
"sub_rule_id", "rule_branch_key", "markdown_source_path",
|
||||
"markdown_source_sha256", "renderer_ids", "corpus_scope_id",
|
||||
"element_set_id", "party_set_rule_id", "ce13_branch_id",
|
||||
)
|
||||
missing = [name for name in required_fields if row.get(name) in (None, "", [])]
|
||||
markdown_path = row.get("markdown_source_path")
|
||||
markdown_hash = row.get("markdown_source_sha256")
|
||||
hash_mismatch = (
|
||||
isinstance(markdown_path, str)
|
||||
and markdown_path in asset_hashes
|
||||
and asset_hashes[markdown_path] != markdown_hash
|
||||
)
|
||||
if missing or hash_mismatch:
|
||||
return {
|
||||
"binding_status": "CONFLICT",
|
||||
"binding_issue_codes": sorted(
|
||||
({"RULE_BRANCH_MISSING"} if missing else set())
|
||||
| ({"RULE_HASH_MISMATCH"} if hash_mismatch else set())
|
||||
),
|
||||
"missing_refs": sorted(missing),
|
||||
"hash_mismatches": ["markdown_source_sha256"] if hash_mismatch else [],
|
||||
}
|
||||
sub_rule_id = row.get("sub_rule_id")
|
||||
predicates = row.get("sub_rule_predicates", [])
|
||||
return {
|
||||
"binding_status": "BOUND",
|
||||
"case_type_id": case_type_id,
|
||||
"sub_rule_id": sub_rule_id,
|
||||
"rule_branch_key": row.get("rule_branch_key"),
|
||||
"matched_sub_rule_predicate_id": predicates[0].get("predicate_id"),
|
||||
"renderer_ids": list(row.get("renderer_ids", [])),
|
||||
"party_set_rule_id": row.get("party_set_rule_id"),
|
||||
"corpus_scope_id": row.get("corpus_scope_id"),
|
||||
"element_set_id": row.get("element_set_id"),
|
||||
"ce13_branch_id": row.get("ce13_branch_id"),
|
||||
"binding_issue_codes": [],
|
||||
}
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build schema-conformant, release-bound C27 requirement-fact query plans."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from typing import Any, Mapping, Sequence
|
||||
|
||||
|
||||
ROLES = ("element", "defense", "rebuttal", "burden", "relief_consistency")
|
||||
SHA256_KEYS = (
|
||||
"embedding_configuration_digest",
|
||||
"reranker_configuration_digest",
|
||||
"index_configuration_digest",
|
||||
)
|
||||
|
||||
|
||||
class QueryPlanError(ValueError):
|
||||
"""Controlled C27 contract failure."""
|
||||
|
||||
|
||||
def _digest(value: Any) -> str:
|
||||
raw = (
|
||||
json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":")) + "\n"
|
||||
).encode("utf-8")
|
||||
return hashlib.sha256(raw).hexdigest()
|
||||
|
||||
|
||||
def _nonempty(value: Any, code: str) -> str:
|
||||
if not isinstance(value, str) or not value:
|
||||
raise QueryPlanError(code)
|
||||
return value
|
||||
|
||||
|
||||
def _sha256(value: Any, code: str) -> str:
|
||||
text = _nonempty(value, code)
|
||||
if len(text) != 64 or any(character not in "0123456789abcdef" for character in text):
|
||||
raise QueryPlanError(code)
|
||||
return text
|
||||
|
||||
|
||||
def _bound(binding: Mapping[str, Any]) -> bool:
|
||||
return (
|
||||
binding.get("case_type_binding_status") == "BOUND"
|
||||
and binding.get("sub_rule_binding_status") == "BOUND"
|
||||
and binding.get("legal_content_status") == "APPROVED"
|
||||
)
|
||||
|
||||
|
||||
def build_query_plan(
|
||||
atomic_claim_id: str,
|
||||
binding: Mapping[str, Any],
|
||||
scope: Mapping[str, Any],
|
||||
role_element_ids: Mapping[str, Sequence[str]],
|
||||
*,
|
||||
run_binding_digest: str,
|
||||
effective_on: str,
|
||||
) -> dict[str, Any]:
|
||||
"""Return the exact ``query_plan`` contract from binding_retrieval.schema.
|
||||
|
||||
An unresolved binding produces a valid empty plan. It never manufactures a
|
||||
case type or rule ID merely to populate a query row.
|
||||
"""
|
||||
|
||||
atomic_claim_id = _nonempty(atomic_claim_id, "ATOMIC_CLAIM_ID_REQUIRED")
|
||||
run_binding_digest = _sha256(run_binding_digest, "RUN_BINDING_DIGEST_INVALID")
|
||||
if not _bound(binding):
|
||||
core = {
|
||||
"schema_version": "stage2_requirement_fact_query_plan.v1",
|
||||
"run_binding_digest": run_binding_digest,
|
||||
"atomic_claim_id": atomic_claim_id,
|
||||
"rows": [],
|
||||
}
|
||||
return {**core, "query_plan_sha256": _digest(core)}
|
||||
|
||||
case_type_id = _nonempty(binding.get("case_type_id"), "CASE_TYPE_ID_REQUIRED")
|
||||
sub_rule_id = _nonempty(binding.get("sub_rule_id"), "SUB_RULE_ID_REQUIRED")
|
||||
rule_branch_key = _nonempty(binding.get("rule_branch_key"), "RULE_BRANCH_KEY_REQUIRED")
|
||||
corpus_scope_id = _nonempty(binding.get("corpus_scope_id"), "CORPUS_SCOPE_ID_REQUIRED")
|
||||
element_set_id = _nonempty(binding.get("element_set_id"), "ELEMENT_SET_ID_REQUIRED")
|
||||
if rule_branch_key != f"{case_type_id}::{sub_rule_id}":
|
||||
raise QueryPlanError("RULE_BRANCH_KEY_MISMATCH")
|
||||
if scope.get("corpus_scope_id") not in {None, corpus_scope_id}:
|
||||
raise QueryPlanError("CORPUS_SCOPE_BINDING_MISMATCH")
|
||||
if scope.get("element_set_id") not in {None, element_set_id}:
|
||||
raise QueryPlanError("ELEMENT_SET_BINDING_MISMATCH")
|
||||
|
||||
snapshot_id = _nonempty(scope.get("snapshot_id"), "SNAPSHOT_ID_REQUIRED")
|
||||
effective_on = _nonempty(effective_on, "EFFECTIVE_ON_REQUIRED")
|
||||
query_contract = scope.get("query_contract")
|
||||
if not isinstance(query_contract, Mapping):
|
||||
raise QueryPlanError("QUERY_CONTRACT_REQUIRED")
|
||||
alpha = query_contract.get("alpha")
|
||||
top_k = query_contract.get("top_k")
|
||||
if not isinstance(alpha, (int, float)) or isinstance(alpha, bool) or not 0 <= alpha <= 1:
|
||||
raise QueryPlanError("QUERY_ALPHA_INVALID")
|
||||
if not isinstance(top_k, int) or isinstance(top_k, bool) or not 1 <= top_k <= 100:
|
||||
raise QueryPlanError("QUERY_TOP_K_INVALID")
|
||||
configuration_digests = {
|
||||
key: _sha256(query_contract.get(key), f"{key.upper()}_INVALID") for key in SHA256_KEYS
|
||||
}
|
||||
allowed_elements = scope.get("corpus_element_ids", [])
|
||||
if not isinstance(allowed_elements, list) or not all(
|
||||
isinstance(item, str) and item for item in allowed_elements
|
||||
):
|
||||
raise QueryPlanError("CORPUS_ELEMENT_IDS_INVALID")
|
||||
allowed_set = set(allowed_elements)
|
||||
|
||||
rows: list[dict[str, Any]] = []
|
||||
for role in ROLES:
|
||||
raw_ids = role_element_ids.get(role, ())
|
||||
if not isinstance(raw_ids, Sequence) or isinstance(raw_ids, (str, bytes)):
|
||||
raise QueryPlanError("ROLE_ELEMENT_IDS_INVALID")
|
||||
element_ids = sorted(set(raw_ids))
|
||||
if not all(isinstance(item, str) and item for item in element_ids):
|
||||
raise QueryPlanError("ROLE_ELEMENT_IDS_INVALID")
|
||||
if allowed_set and not set(element_ids).issubset(allowed_set):
|
||||
raise QueryPlanError("ROLE_ELEMENT_OUTSIDE_APPROVED_SCOPE")
|
||||
query_text = " ".join([case_type_id, sub_rule_id, role, *element_ids])
|
||||
query_text_sha256 = _digest(query_text)
|
||||
query_identity = {
|
||||
"atomic_claim_id": atomic_claim_id,
|
||||
"rule_branch_key": rule_branch_key,
|
||||
"doctrine_role": role,
|
||||
"query_sha256": query_text_sha256,
|
||||
}
|
||||
row = {
|
||||
"query_id": f"Q-{_digest(query_identity)[:24]}",
|
||||
"atomic_claim_id": atomic_claim_id,
|
||||
"case_type_id": case_type_id,
|
||||
"sub_rule_id": sub_rule_id,
|
||||
"rule_branch_key": rule_branch_key,
|
||||
"corpus_scope_id": corpus_scope_id,
|
||||
"element_set_id": element_set_id,
|
||||
"doctrine_role": role,
|
||||
"query_status": "READY",
|
||||
"query_text": query_text,
|
||||
"query_sha256": query_text_sha256,
|
||||
"filter": {
|
||||
"case_type_id": case_type_id,
|
||||
"sub_rule_id": sub_rule_id,
|
||||
"rule_branch_key": rule_branch_key,
|
||||
"corpus_scope_id": corpus_scope_id,
|
||||
"element_set_id": element_set_id,
|
||||
"doctrine_role": role,
|
||||
"jurisdiction": "KR",
|
||||
"snapshot_id": snapshot_id,
|
||||
"approval_status": "APPROVED",
|
||||
"effective_on": effective_on,
|
||||
},
|
||||
"alpha": float(alpha),
|
||||
"top_k": top_k,
|
||||
**configuration_digests,
|
||||
"expected_corpus_element_ids": element_ids,
|
||||
"issue_codes": [],
|
||||
}
|
||||
rows.append(row)
|
||||
|
||||
core = {
|
||||
"schema_version": "stage2_requirement_fact_query_plan.v1",
|
||||
"run_binding_digest": run_binding_digest,
|
||||
"atomic_claim_id": atomic_claim_id,
|
||||
"rows": rows,
|
||||
}
|
||||
return {**core, "query_plan_sha256": _digest(core)}
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build schema-conformant, release-bound C27 requirement-fact query plans."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from typing import Any, Mapping, Sequence
|
||||
|
||||
|
||||
ROLES = ("element", "defense", "rebuttal", "burden", "relief_consistency")
|
||||
SHA256_KEYS = (
|
||||
"embedding_configuration_digest",
|
||||
"reranker_configuration_digest",
|
||||
"index_configuration_digest",
|
||||
)
|
||||
|
||||
|
||||
class QueryPlanError(ValueError):
|
||||
"""Controlled C27 contract failure."""
|
||||
|
||||
|
||||
def _digest(value: Any) -> str:
|
||||
raw = (
|
||||
json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":")) + "\n"
|
||||
).encode("utf-8")
|
||||
return hashlib.sha256(raw).hexdigest()
|
||||
|
||||
|
||||
def _nonempty(value: Any, code: str) -> str:
|
||||
if not isinstance(value, str) or not value:
|
||||
raise QueryPlanError(code)
|
||||
return value
|
||||
|
||||
|
||||
def _sha256(value: Any, code: str) -> str:
|
||||
text = _nonempty(value, code)
|
||||
if len(text) != 64 or any(character not in "0123456789abcdef" for character in text):
|
||||
raise QueryPlanError(code)
|
||||
return text
|
||||
|
||||
|
||||
def _bound(binding: Mapping[str, Any]) -> bool:
|
||||
return (
|
||||
binding.get("case_type_binding_status") == "BOUND"
|
||||
and binding.get("sub_rule_binding_status") == "BOUND"
|
||||
and binding.get("legal_content_status") == "APPROVED"
|
||||
)
|
||||
|
||||
|
||||
def build_query_plan(
|
||||
atomic_claim_id: str,
|
||||
binding: Mapping[str, Any],
|
||||
scope: Mapping[str, Any],
|
||||
role_element_ids: Mapping[str, Sequence[str]],
|
||||
*,
|
||||
run_binding_digest: str,
|
||||
effective_on: str,
|
||||
) -> dict[str, Any]:
|
||||
"""Return the exact ``query_plan`` contract from binding_retrieval.schema.
|
||||
|
||||
An unresolved binding produces a valid empty plan. It never manufactures a
|
||||
case type or rule ID merely to populate a query row.
|
||||
"""
|
||||
|
||||
atomic_claim_id = _nonempty(atomic_claim_id, "ATOMIC_CLAIM_ID_REQUIRED")
|
||||
run_binding_digest = _sha256(run_binding_digest, "RUN_BINDING_DIGEST_INVALID")
|
||||
if not _bound(binding):
|
||||
core = {
|
||||
"schema_version": "stage2_requirement_fact_query_plan.v1",
|
||||
"run_binding_digest": run_binding_digest,
|
||||
"atomic_claim_id": atomic_claim_id,
|
||||
"rows": [],
|
||||
}
|
||||
return {**core, "query_plan_sha256": _digest(core)}
|
||||
|
||||
case_type_id = _nonempty(binding.get("case_type_id"), "CASE_TYPE_ID_REQUIRED")
|
||||
sub_rule_id = _nonempty(binding.get("sub_rule_id"), "SUB_RULE_ID_REQUIRED")
|
||||
rule_branch_key = _nonempty(binding.get("rule_branch_key"), "RULE_BRANCH_KEY_REQUIRED")
|
||||
corpus_scope_id = _nonempty(binding.get("corpus_scope_id"), "CORPUS_SCOPE_ID_REQUIRED")
|
||||
element_set_id = _nonempty(binding.get("element_set_id"), "ELEMENT_SET_ID_REQUIRED")
|
||||
if rule_branch_key != f"{case_type_id}::{sub_rule_id}":
|
||||
raise QueryPlanError("RULE_BRANCH_KEY_MISMATCH")
|
||||
if scope.get("corpus_scope_id") not in {None, corpus_scope_id}:
|
||||
raise QueryPlanError("CORPUS_SCOPE_BINDING_MISMATCH")
|
||||
if scope.get("element_set_id") not in {None, element_set_id}:
|
||||
raise QueryPlanError("ELEMENT_SET_BINDING_MISMATCH")
|
||||
|
||||
snapshot_id = _nonempty(scope.get("snapshot_id"), "SNAPSHOT_ID_REQUIRED")
|
||||
effective_on = _nonempty(effective_on, "EFFECTIVE_ON_REQUIRED")
|
||||
query_contract = scope.get("query_contract")
|
||||
if not isinstance(query_contract, Mapping):
|
||||
raise QueryPlanError("QUERY_CONTRACT_REQUIRED")
|
||||
alpha = query_contract.get("alpha")
|
||||
top_k = query_contract.get("top_k")
|
||||
if not isinstance(alpha, (int, float)) or isinstance(alpha, bool) or not 0 <= alpha <= 1:
|
||||
raise QueryPlanError("QUERY_ALPHA_INVALID")
|
||||
if not isinstance(top_k, int) or isinstance(top_k, bool) or not 1 <= top_k <= 100:
|
||||
raise QueryPlanError("QUERY_TOP_K_INVALID")
|
||||
configuration_digests = {
|
||||
key: _sha256(query_contract.get(key), f"{key.upper()}_INVALID") for key in SHA256_KEYS
|
||||
}
|
||||
allowed_elements = scope.get("corpus_element_ids", [])
|
||||
if not isinstance(allowed_elements, list) or not all(
|
||||
isinstance(item, str) and item for item in allowed_elements
|
||||
):
|
||||
raise QueryPlanError("CORPUS_ELEMENT_IDS_INVALID")
|
||||
allowed_set = set(allowed_elements)
|
||||
|
||||
rows: list[dict[str, Any]] = []
|
||||
for role in ROLES:
|
||||
raw_ids = role_element_ids.get(role, ())
|
||||
if not isinstance(raw_ids, Sequence) or isinstance(raw_ids, (str, bytes)):
|
||||
raise QueryPlanError("ROLE_ELEMENT_IDS_INVALID")
|
||||
element_ids = sorted(set(raw_ids))
|
||||
if not all(isinstance(item, str) and item for item in element_ids):
|
||||
raise QueryPlanError("ROLE_ELEMENT_IDS_INVALID")
|
||||
if allowed_set and not set(element_ids).issubset(allowed_set):
|
||||
raise QueryPlanError("ROLE_ELEMENT_OUTSIDE_APPROVED_SCOPE")
|
||||
query_text = " ".join([case_type_id, sub_rule_id, role, *element_ids])
|
||||
query_text_sha256 = _digest(query_text)
|
||||
query_identity = {
|
||||
"atomic_claim_id": atomic_claim_id,
|
||||
"rule_branch_key": rule_branch_key,
|
||||
"doctrine_role": role,
|
||||
"query_sha256": query_text_sha256,
|
||||
}
|
||||
row = {
|
||||
"query_id": f"Q-{_digest(query_identity)[:24]}",
|
||||
"atomic_claim_id": atomic_claim_id,
|
||||
"case_type_id": case_type_id,
|
||||
"sub_rule_id": sub_rule_id,
|
||||
"rule_branch_key": rule_branch_key,
|
||||
"corpus_scope_id": corpus_scope_id,
|
||||
"element_set_id": element_set_id,
|
||||
"doctrine_role": role,
|
||||
"query_status": "READY",
|
||||
"query_text": query_text,
|
||||
"query_sha256": query_text_sha256,
|
||||
"filter": {
|
||||
"case_type_id": case_type_id,
|
||||
"sub_rule_id": sub_rule_id,
|
||||
"rule_branch_key": rule_branch_key,
|
||||
"corpus_scope_id": corpus_scope_id,
|
||||
"element_set_id": element_set_id,
|
||||
"doctrine_role": role,
|
||||
"jurisdiction": "KR",
|
||||
"snapshot_id": snapshot_id,
|
||||
"approval_status": "APPROVED",
|
||||
"effective_on": effective_on,
|
||||
},
|
||||
"alpha": float(alpha),
|
||||
"top_k": top_k,
|
||||
**configuration_digests,
|
||||
"expected_corpus_element_ids": element_ids,
|
||||
"issue_codes": [],
|
||||
}
|
||||
rows.append(row)
|
||||
|
||||
core = {
|
||||
"schema_version": "stage2_requirement_fact_query_plan.v1",
|
||||
"run_binding_digest": run_binding_digest,
|
||||
"atomic_claim_id": atomic_claim_id,
|
||||
"rows": rows,
|
||||
}
|
||||
return {**core, "query_plan_sha256": _digest(core)}
|
||||
+298
@@ -0,0 +1,298 @@
|
||||
#!/usr/bin/env python3
|
||||
"""C28 exact-filter retrieval and canonical result/receipt normalization."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from typing import Any, Callable, Mapping, Sequence
|
||||
|
||||
|
||||
class RetrievalError(RuntimeError):
|
||||
"""Controlled C28 transport or contract failure."""
|
||||
|
||||
|
||||
RESPONSE_KEYS = frozenset(
|
||||
{
|
||||
"schema_version", "request_id", "request_sha256", "response_status",
|
||||
"raw_hit_count", "selected_hits", "dropped_hits", "deterministic_order_digest",
|
||||
"retry_count",
|
||||
}
|
||||
)
|
||||
HIT_KEYS = frozenset(
|
||||
{
|
||||
"chunk_id", "chunk_sha256", "source_document_id", "source_document_sha256",
|
||||
"source_locator", "pre_rerank_rank", "hybrid_score", "post_rerank_rank",
|
||||
"reranker_score", "metadata",
|
||||
}
|
||||
)
|
||||
METADATA_KEYS = frozenset(
|
||||
{
|
||||
"schema_version", "document_id", "document_sha256", "chunk_id", "chunk_sha256",
|
||||
"source_path", "source_locator", "case_type_ids", "sub_rule_ids",
|
||||
"rule_branch_keys", "corpus_scope_ids", "element_set_ids", "corpus_element_ids",
|
||||
"doctrine_roles", "jurisdiction", "effective_from", "effective_to", "authority_ids",
|
||||
"proposition_ids", "approval_status", "legal_reviewer_receipt_ref",
|
||||
"legal_reviewer_receipt_sha256",
|
||||
}
|
||||
)
|
||||
DROP_KEYS = frozenset({"chunk_id", "chunk_sha256", "drop_reason_code"})
|
||||
DROP_REASONS = frozenset(
|
||||
{"FILTER_MISMATCH", "UNAPPROVED_CHUNK", "HASH_MISMATCH", "SNAPSHOT_MISMATCH", "CROSSWALK_GAP", "DUPLICATE_CHUNK"}
|
||||
)
|
||||
|
||||
|
||||
def _canonical(value: Any) -> bytes:
|
||||
return (
|
||||
json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":")) + "\n"
|
||||
).encode("utf-8")
|
||||
|
||||
|
||||
def _digest(value: Any) -> str:
|
||||
return hashlib.sha256(_canonical(value)).hexdigest()
|
||||
|
||||
|
||||
def _nonempty(value: Any, code: str) -> str:
|
||||
if not isinstance(value, str) or not value:
|
||||
raise RetrievalError(code)
|
||||
return value
|
||||
|
||||
|
||||
def build_request(query_row: Mapping[str, Any], collection: Mapping[str, Any]) -> dict[str, Any]:
|
||||
"""Build the exact Weaviate ``hybrid_search_request`` contract."""
|
||||
|
||||
if query_row.get("query_status") != "READY":
|
||||
raise RetrievalError("QUERY_ROW_NOT_READY")
|
||||
filter_value = query_row.get("filter")
|
||||
if not isinstance(filter_value, Mapping):
|
||||
raise RetrievalError("QUERY_FILTER_REQUIRED")
|
||||
request_seed = {
|
||||
"query_id": query_row.get("query_id"),
|
||||
"query_sha256": query_row.get("query_sha256"),
|
||||
"snapshot_sha256": collection.get("snapshot_sha256"),
|
||||
}
|
||||
return {
|
||||
"schema_version": "stage2_weaviate_hybrid_search_request.v1",
|
||||
"request_id": f"REQ-{_digest(request_seed)[:24]}",
|
||||
"query_plan_id": _nonempty(query_row.get("query_id"), "QUERY_ID_REQUIRED"),
|
||||
"atomic_claim_id": _nonempty(
|
||||
query_row.get("atomic_claim_id"), "ATOMIC_CLAIM_ID_REQUIRED"
|
||||
),
|
||||
"collection_name": _nonempty(
|
||||
collection.get("collection_name"), "COLLECTION_NAME_REQUIRED"
|
||||
),
|
||||
"tenant": _nonempty(collection.get("tenant"), "TENANT_REQUIRED"),
|
||||
"query_text": _nonempty(query_row.get("query_text"), "QUERY_TEXT_REQUIRED"),
|
||||
"query_sha256": _nonempty(query_row.get("query_sha256"), "QUERY_SHA256_REQUIRED"),
|
||||
"filter": dict(filter_value),
|
||||
"alpha": query_row.get("alpha"),
|
||||
"top_k": query_row.get("top_k"),
|
||||
"embedding_configuration_digest": query_row.get("embedding_configuration_digest"),
|
||||
"reranker_configuration_digest": query_row.get("reranker_configuration_digest"),
|
||||
"index_configuration_digest": query_row.get("index_configuration_digest"),
|
||||
"snapshot_sha256": _nonempty(
|
||||
collection.get("snapshot_sha256"), "SNAPSHOT_SHA256_REQUIRED"
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def _drop(hit: Mapping[str, Any], reason: str) -> dict[str, Any]:
|
||||
return {
|
||||
"chunk_id": _nonempty(hit.get("chunk_id"), "HIT_CHUNK_ID_REQUIRED"),
|
||||
"chunk_sha256": _nonempty(hit.get("chunk_sha256"), "HIT_CHUNK_SHA256_REQUIRED"),
|
||||
"drop_reason_code": reason,
|
||||
}
|
||||
|
||||
|
||||
def _hit_filter_mismatch(hit: Mapping[str, Any], filter_value: Mapping[str, Any]) -> str | None:
|
||||
if set(hit) != HIT_KEYS:
|
||||
raise RetrievalError("RETRIEVAL_HIT_KEYS_INVALID")
|
||||
metadata = hit.get("metadata")
|
||||
if not isinstance(metadata, Mapping):
|
||||
raise RetrievalError("RETRIEVAL_HIT_METADATA_INVALID")
|
||||
if set(metadata) != METADATA_KEYS:
|
||||
raise RetrievalError("RETRIEVAL_HIT_METADATA_KEYS_INVALID")
|
||||
if metadata.get("approval_status") != "APPROVED":
|
||||
return "UNAPPROVED_CHUNK"
|
||||
scalar_to_set = {
|
||||
"case_type_id": "case_type_ids",
|
||||
"sub_rule_id": "sub_rule_ids",
|
||||
"rule_branch_key": "rule_branch_keys",
|
||||
"corpus_scope_id": "corpus_scope_ids",
|
||||
"element_set_id": "element_set_ids",
|
||||
"doctrine_role": "doctrine_roles",
|
||||
}
|
||||
for filter_key, metadata_key in scalar_to_set.items():
|
||||
values = metadata.get(metadata_key)
|
||||
if not isinstance(values, list) or filter_value.get(filter_key) not in values:
|
||||
return "FILTER_MISMATCH"
|
||||
if metadata.get("jurisdiction") != "KR":
|
||||
return "FILTER_MISMATCH"
|
||||
if hit.get("chunk_sha256") != metadata.get("chunk_sha256"):
|
||||
return "HASH_MISMATCH"
|
||||
return None
|
||||
|
||||
|
||||
def _receipt(
|
||||
*,
|
||||
query_plan: Mapping[str, Any],
|
||||
query_row: Mapping[str, Any],
|
||||
request: Mapping[str, Any],
|
||||
response: Mapping[str, Any],
|
||||
collection: Mapping[str, Any],
|
||||
credential_ref: str,
|
||||
) -> dict[str, Any]:
|
||||
configuration = {
|
||||
key: query_row.get(key)
|
||||
for key in (
|
||||
"alpha",
|
||||
"top_k",
|
||||
"embedding_configuration_digest",
|
||||
"reranker_configuration_digest",
|
||||
"index_configuration_digest",
|
||||
)
|
||||
}
|
||||
core = {
|
||||
"schema_version": "stage2_requirement_fact_retrieval_receipt.v1",
|
||||
"run_binding_digest": query_plan["run_binding_digest"],
|
||||
"atomic_claim_id": query_plan["atomic_claim_id"],
|
||||
"query_plan_sha256": query_plan["query_plan_sha256"],
|
||||
"request_sha256": _digest(request),
|
||||
"response_sha256": _digest(response),
|
||||
"transport": "MCP_OVER_HTTP_JSON_RPC",
|
||||
"endpoint": "https://weaviate.eroomai.com/mcp",
|
||||
"tool_name": "search_hybrid",
|
||||
"collection_name": collection["collection_name"],
|
||||
"tenant": collection["tenant"],
|
||||
"snapshot_sha256": collection["snapshot_sha256"],
|
||||
"filter_sha256": _digest(query_row["filter"]),
|
||||
"configuration_digest": _digest(configuration),
|
||||
"attempt_count": int(response.get("retry_count", 0)) + 1,
|
||||
"replay_status": "NOT_REQUESTED",
|
||||
"read_only_verified": True,
|
||||
"credential_ref": _nonempty(credential_ref, "CREDENTIAL_REF_REQUIRED"),
|
||||
"credential_material_persisted": False,
|
||||
}
|
||||
return {**core, "receipt_sha256": _digest(core)}
|
||||
|
||||
|
||||
def retrieve_exact(
|
||||
query_plan: Mapping[str, Any],
|
||||
collection: Mapping[str, Any],
|
||||
search_hybrid: Callable[[Mapping[str, Any]], Mapping[str, Any]],
|
||||
*,
|
||||
credential_ref: str,
|
||||
) -> tuple[dict[str, Any], list[dict[str, Any]]]:
|
||||
"""Execute READY rows without fallback and return canonical result + receipts."""
|
||||
|
||||
rows = query_plan.get("rows")
|
||||
if not isinstance(rows, Sequence) or isinstance(rows, (str, bytes)):
|
||||
raise RetrievalError("QUERY_PLAN_ROWS_INVALID")
|
||||
selected: list[dict[str, Any]] = []
|
||||
dropped: list[dict[str, Any]] = []
|
||||
receipts: list[dict[str, Any]] = []
|
||||
issue_codes: set[str] = set()
|
||||
technical_incomplete = False
|
||||
for query_row in rows:
|
||||
if not isinstance(query_row, Mapping) or query_row.get("query_status") != "READY":
|
||||
raise RetrievalError("QUERY_PLAN_READY_ROW_REQUIRED")
|
||||
request = build_request(query_row, collection)
|
||||
request_sha256 = _digest(request)
|
||||
response = search_hybrid(request)
|
||||
if not isinstance(response, Mapping) or response.get("request_sha256") != request_sha256:
|
||||
raise RetrievalError("RETRIEVAL_REQUEST_ECHO_MISMATCH")
|
||||
if set(response) != RESPONSE_KEYS:
|
||||
raise RetrievalError("RETRIEVAL_RESPONSE_KEYS_INVALID")
|
||||
if response.get("schema_version") != "stage2_weaviate_hybrid_search_response.v1":
|
||||
raise RetrievalError("RETRIEVAL_RESPONSE_SCHEMA_MISMATCH")
|
||||
if response.get("request_id") != request["request_id"]:
|
||||
raise RetrievalError("RETRIEVAL_REQUEST_ID_ECHO_MISMATCH")
|
||||
retry_count = response.get("retry_count")
|
||||
if not isinstance(retry_count, int) or isinstance(retry_count, bool) or not 0 <= retry_count <= 2:
|
||||
raise RetrievalError("RETRIEVAL_RETRY_COUNT_INVALID")
|
||||
response_status = response.get("response_status")
|
||||
if response_status not in {"COMPLETE", "NO_HIT", "TECHNICAL_INCOMPLETE"}:
|
||||
raise RetrievalError("RETRIEVAL_RESPONSE_STATUS_INVALID")
|
||||
if response_status == "TECHNICAL_INCOMPLETE":
|
||||
technical_incomplete = True
|
||||
issue_codes.add("RETRIEVAL_TECHNICAL_INCOMPLETE")
|
||||
raw_hits = response.get("selected_hits")
|
||||
raw_drops = response.get("dropped_hits")
|
||||
if not isinstance(raw_hits, list) or not isinstance(raw_drops, list):
|
||||
raise RetrievalError("RETRIEVAL_RESPONSE_ROWS_INVALID")
|
||||
for hit in raw_hits:
|
||||
if not isinstance(hit, Mapping):
|
||||
raise RetrievalError("RETRIEVAL_HIT_INVALID")
|
||||
reason = _hit_filter_mismatch(hit, query_row["filter"])
|
||||
if reason is None:
|
||||
selected.append(dict(hit))
|
||||
else:
|
||||
dropped.append(_drop(hit, reason))
|
||||
issue_codes.add(reason)
|
||||
for row in raw_drops:
|
||||
if (
|
||||
not isinstance(row, Mapping)
|
||||
or set(row) != DROP_KEYS
|
||||
or row.get("drop_reason_code") not in DROP_REASONS
|
||||
):
|
||||
raise RetrievalError("DROPPED_HIT_INVALID")
|
||||
dropped.append(dict(row))
|
||||
receipts.append(
|
||||
_receipt(
|
||||
query_plan=query_plan,
|
||||
query_row=query_row,
|
||||
request=request,
|
||||
response=response,
|
||||
collection=collection,
|
||||
credential_ref=credential_ref,
|
||||
)
|
||||
)
|
||||
|
||||
unique: dict[str, dict[str, Any]] = {}
|
||||
for hit in sorted(
|
||||
selected,
|
||||
key=lambda value: (
|
||||
int(value.get("post_rerank_rank", 2**31 - 1)),
|
||||
-float(value.get("reranker_score") or value.get("hybrid_score") or 0),
|
||||
str(value.get("chunk_id", "")),
|
||||
),
|
||||
):
|
||||
chunk_id = _nonempty(hit.get("chunk_id"), "HIT_CHUNK_ID_REQUIRED")
|
||||
if chunk_id in unique:
|
||||
dropped.append(_drop(hit, "DUPLICATE_CHUNK"))
|
||||
issue_codes.add("DUPLICATE_CHUNK")
|
||||
else:
|
||||
unique[chunk_id] = hit
|
||||
selected_hits = list(unique.values())
|
||||
result_status = (
|
||||
"NOT_RUN_UNRESOLVED_BINDING"
|
||||
if not rows
|
||||
else "TECHNICAL_INCOMPLETE"
|
||||
if technical_incomplete
|
||||
else "COMPLETE"
|
||||
if selected_hits
|
||||
else "NO_HIT"
|
||||
)
|
||||
if not rows:
|
||||
issue_codes.add("QUERY_NOT_RUN_UNRESOLVED_BINDING")
|
||||
core = {
|
||||
"schema_version": "stage2_requirement_fact_retrieval_result.v1",
|
||||
"run_binding_digest": query_plan["run_binding_digest"],
|
||||
"atomic_claim_id": query_plan["atomic_claim_id"],
|
||||
"query_plan_sha256": query_plan["query_plan_sha256"],
|
||||
"snapshot_id": _nonempty(collection.get("snapshot_id"), "SNAPSHOT_ID_REQUIRED"),
|
||||
"snapshot_sha256": _nonempty(
|
||||
collection.get("snapshot_sha256"), "SNAPSHOT_SHA256_REQUIRED"
|
||||
),
|
||||
"result_status": result_status,
|
||||
"selected_hits": selected_hits,
|
||||
"dropped_hits": sorted(
|
||||
dropped,
|
||||
key=lambda value: (
|
||||
str(value.get("chunk_id", "")),
|
||||
str(value.get("drop_reason_code", "")),
|
||||
),
|
||||
),
|
||||
"issue_codes": sorted(issue_codes),
|
||||
}
|
||||
return {**core, "result_sha256": _digest(core)}, receipts
|
||||
+298
@@ -0,0 +1,298 @@
|
||||
#!/usr/bin/env python3
|
||||
"""C28 exact-filter retrieval and canonical result/receipt normalization."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from typing import Any, Callable, Mapping, Sequence
|
||||
|
||||
|
||||
class RetrievalError(RuntimeError):
|
||||
"""Controlled C28 transport or contract failure."""
|
||||
|
||||
|
||||
RESPONSE_KEYS = frozenset(
|
||||
{
|
||||
"schema_version", "request_id", "request_sha256", "response_status",
|
||||
"raw_hit_count", "selected_hits", "dropped_hits", "deterministic_order_digest",
|
||||
"retry_count",
|
||||
}
|
||||
)
|
||||
HIT_KEYS = frozenset(
|
||||
{
|
||||
"chunk_id", "chunk_sha256", "source_document_id", "source_document_sha256",
|
||||
"source_locator", "pre_rerank_rank", "hybrid_score", "post_rerank_rank",
|
||||
"reranker_score", "metadata",
|
||||
}
|
||||
)
|
||||
METADATA_KEYS = frozenset(
|
||||
{
|
||||
"schema_version", "document_id", "document_sha256", "chunk_id", "chunk_sha256",
|
||||
"source_path", "source_locator", "case_type_ids", "sub_rule_ids",
|
||||
"rule_branch_keys", "corpus_scope_ids", "element_set_ids", "corpus_element_ids",
|
||||
"doctrine_roles", "jurisdiction", "effective_from", "effective_to", "authority_ids",
|
||||
"proposition_ids", "approval_status", "legal_reviewer_receipt_ref",
|
||||
"legal_reviewer_receipt_sha256",
|
||||
}
|
||||
)
|
||||
DROP_KEYS = frozenset({"chunk_id", "chunk_sha256", "drop_reason_code"})
|
||||
DROP_REASONS = frozenset(
|
||||
{"FILTER_MISMATCH", "UNAPPROVED_CHUNK", "HASH_MISMATCH", "SNAPSHOT_MISMATCH", "CROSSWALK_GAP", "DUPLICATE_CHUNK"}
|
||||
)
|
||||
|
||||
|
||||
def _canonical(value: Any) -> bytes:
|
||||
return (
|
||||
json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":")) + "\n"
|
||||
).encode("utf-8")
|
||||
|
||||
|
||||
def _digest(value: Any) -> str:
|
||||
return hashlib.sha256(_canonical(value)).hexdigest()
|
||||
|
||||
|
||||
def _nonempty(value: Any, code: str) -> str:
|
||||
if not isinstance(value, str) or not value:
|
||||
raise RetrievalError(code)
|
||||
return value
|
||||
|
||||
|
||||
def build_request(query_row: Mapping[str, Any], collection: Mapping[str, Any]) -> dict[str, Any]:
|
||||
"""Build the exact Weaviate ``hybrid_search_request`` contract."""
|
||||
|
||||
if query_row.get("query_status") != "READY":
|
||||
raise RetrievalError("QUERY_ROW_NOT_READY")
|
||||
filter_value = query_row.get("filter")
|
||||
if not isinstance(filter_value, Mapping):
|
||||
raise RetrievalError("QUERY_FILTER_REQUIRED")
|
||||
request_seed = {
|
||||
"query_id": query_row.get("query_id"),
|
||||
"query_sha256": query_row.get("query_sha256"),
|
||||
"snapshot_sha256": collection.get("snapshot_sha256"),
|
||||
}
|
||||
return {
|
||||
"schema_version": "stage2_weaviate_hybrid_search_request.v1",
|
||||
"request_id": f"REQ-{_digest(request_seed)[:24]}",
|
||||
"query_plan_id": _nonempty(query_row.get("query_id"), "QUERY_ID_REQUIRED"),
|
||||
"atomic_claim_id": _nonempty(
|
||||
query_row.get("atomic_claim_id"), "ATOMIC_CLAIM_ID_REQUIRED"
|
||||
),
|
||||
"collection_name": _nonempty(
|
||||
collection.get("collection_name"), "COLLECTION_NAME_REQUIRED"
|
||||
),
|
||||
"tenant": _nonempty(collection.get("tenant"), "TENANT_REQUIRED"),
|
||||
"query_text": _nonempty(query_row.get("query_text"), "QUERY_TEXT_REQUIRED"),
|
||||
"query_sha256": _nonempty(query_row.get("query_sha256"), "QUERY_SHA256_REQUIRED"),
|
||||
"filter": dict(filter_value),
|
||||
"alpha": query_row.get("alpha"),
|
||||
"top_k": query_row.get("top_k"),
|
||||
"embedding_configuration_digest": query_row.get("embedding_configuration_digest"),
|
||||
"reranker_configuration_digest": query_row.get("reranker_configuration_digest"),
|
||||
"index_configuration_digest": query_row.get("index_configuration_digest"),
|
||||
"snapshot_sha256": _nonempty(
|
||||
collection.get("snapshot_sha256"), "SNAPSHOT_SHA256_REQUIRED"
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def _drop(hit: Mapping[str, Any], reason: str) -> dict[str, Any]:
|
||||
return {
|
||||
"chunk_id": _nonempty(hit.get("chunk_id"), "HIT_CHUNK_ID_REQUIRED"),
|
||||
"chunk_sha256": _nonempty(hit.get("chunk_sha256"), "HIT_CHUNK_SHA256_REQUIRED"),
|
||||
"drop_reason_code": reason,
|
||||
}
|
||||
|
||||
|
||||
def _hit_filter_mismatch(hit: Mapping[str, Any], filter_value: Mapping[str, Any]) -> str | None:
|
||||
if set(hit) != HIT_KEYS:
|
||||
raise RetrievalError("RETRIEVAL_HIT_KEYS_INVALID")
|
||||
metadata = hit.get("metadata")
|
||||
if not isinstance(metadata, Mapping):
|
||||
raise RetrievalError("RETRIEVAL_HIT_METADATA_INVALID")
|
||||
if set(metadata) != METADATA_KEYS:
|
||||
raise RetrievalError("RETRIEVAL_HIT_METADATA_KEYS_INVALID")
|
||||
if metadata.get("approval_status") != "APPROVED":
|
||||
return "UNAPPROVED_CHUNK"
|
||||
scalar_to_set = {
|
||||
"case_type_id": "case_type_ids",
|
||||
"sub_rule_id": "sub_rule_ids",
|
||||
"rule_branch_key": "rule_branch_keys",
|
||||
"corpus_scope_id": "corpus_scope_ids",
|
||||
"element_set_id": "element_set_ids",
|
||||
"doctrine_role": "doctrine_roles",
|
||||
}
|
||||
for filter_key, metadata_key in scalar_to_set.items():
|
||||
values = metadata.get(metadata_key)
|
||||
if not isinstance(values, list) or filter_value.get(filter_key) not in values:
|
||||
return "FILTER_MISMATCH"
|
||||
if metadata.get("jurisdiction") != "KR":
|
||||
return "FILTER_MISMATCH"
|
||||
if hit.get("chunk_sha256") != metadata.get("chunk_sha256"):
|
||||
return "HASH_MISMATCH"
|
||||
return None
|
||||
|
||||
|
||||
def _receipt(
|
||||
*,
|
||||
query_plan: Mapping[str, Any],
|
||||
query_row: Mapping[str, Any],
|
||||
request: Mapping[str, Any],
|
||||
response: Mapping[str, Any],
|
||||
collection: Mapping[str, Any],
|
||||
credential_ref: str,
|
||||
) -> dict[str, Any]:
|
||||
configuration = {
|
||||
key: query_row.get(key)
|
||||
for key in (
|
||||
"alpha",
|
||||
"top_k",
|
||||
"embedding_configuration_digest",
|
||||
"reranker_configuration_digest",
|
||||
"index_configuration_digest",
|
||||
)
|
||||
}
|
||||
core = {
|
||||
"schema_version": "stage2_requirement_fact_retrieval_receipt.v1",
|
||||
"run_binding_digest": query_plan["run_binding_digest"],
|
||||
"atomic_claim_id": query_plan["atomic_claim_id"],
|
||||
"query_plan_sha256": query_plan["query_plan_sha256"],
|
||||
"request_sha256": _digest(request),
|
||||
"response_sha256": _digest(response),
|
||||
"transport": "MCP_OVER_HTTP_JSON_RPC",
|
||||
"endpoint": "https://weaviate.eroomai.com/mcp",
|
||||
"tool_name": "search_hybrid",
|
||||
"collection_name": collection["collection_name"],
|
||||
"tenant": collection["tenant"],
|
||||
"snapshot_sha256": collection["snapshot_sha256"],
|
||||
"filter_sha256": _digest(query_row["filter"]),
|
||||
"configuration_digest": _digest(configuration),
|
||||
"attempt_count": int(response.get("retry_count", 0)) + 1,
|
||||
"replay_status": "NOT_REQUESTED",
|
||||
"read_only_verified": True,
|
||||
"credential_ref": _nonempty(credential_ref, "CREDENTIAL_REF_REQUIRED"),
|
||||
"credential_material_persisted": False,
|
||||
}
|
||||
return {**core, "receipt_sha256": _digest(core)}
|
||||
|
||||
|
||||
def retrieve_exact(
|
||||
query_plan: Mapping[str, Any],
|
||||
collection: Mapping[str, Any],
|
||||
search_hybrid: Callable[[Mapping[str, Any]], Mapping[str, Any]],
|
||||
*,
|
||||
credential_ref: str,
|
||||
) -> tuple[dict[str, Any], list[dict[str, Any]]]:
|
||||
"""Execute READY rows without fallback and return canonical result + receipts."""
|
||||
|
||||
rows = query_plan.get("rows")
|
||||
if not isinstance(rows, Sequence) or isinstance(rows, (str, bytes)):
|
||||
raise RetrievalError("QUERY_PLAN_ROWS_INVALID")
|
||||
selected: list[dict[str, Any]] = []
|
||||
dropped: list[dict[str, Any]] = []
|
||||
receipts: list[dict[str, Any]] = []
|
||||
issue_codes: set[str] = set()
|
||||
technical_incomplete = False
|
||||
for query_row in rows:
|
||||
if not isinstance(query_row, Mapping) or query_row.get("query_status") != "READY":
|
||||
raise RetrievalError("QUERY_PLAN_READY_ROW_REQUIRED")
|
||||
request = build_request(query_row, collection)
|
||||
request_sha256 = _digest(request)
|
||||
response = search_hybrid(request)
|
||||
if not isinstance(response, Mapping) or response.get("request_sha256") != request_sha256:
|
||||
raise RetrievalError("RETRIEVAL_REQUEST_ECHO_MISMATCH")
|
||||
if set(response) != RESPONSE_KEYS:
|
||||
raise RetrievalError("RETRIEVAL_RESPONSE_KEYS_INVALID")
|
||||
if response.get("schema_version") != "stage2_weaviate_hybrid_search_response.v1":
|
||||
raise RetrievalError("RETRIEVAL_RESPONSE_SCHEMA_MISMATCH")
|
||||
if response.get("request_id") != request["request_id"]:
|
||||
raise RetrievalError("RETRIEVAL_REQUEST_ID_ECHO_MISMATCH")
|
||||
retry_count = response.get("retry_count")
|
||||
if not isinstance(retry_count, int) or isinstance(retry_count, bool) or not 0 <= retry_count <= 2:
|
||||
raise RetrievalError("RETRIEVAL_RETRY_COUNT_INVALID")
|
||||
response_status = response.get("response_status")
|
||||
if response_status not in {"COMPLETE", "NO_HIT", "TECHNICAL_INCOMPLETE"}:
|
||||
raise RetrievalError("RETRIEVAL_RESPONSE_STATUS_INVALID")
|
||||
if response_status == "TECHNICAL_INCOMPLETE":
|
||||
technical_incomplete = True
|
||||
issue_codes.add("RETRIEVAL_TECHNICAL_INCOMPLETE")
|
||||
raw_hits = response.get("selected_hits")
|
||||
raw_drops = response.get("dropped_hits")
|
||||
if not isinstance(raw_hits, list) or not isinstance(raw_drops, list):
|
||||
raise RetrievalError("RETRIEVAL_RESPONSE_ROWS_INVALID")
|
||||
for hit in raw_hits:
|
||||
if not isinstance(hit, Mapping):
|
||||
raise RetrievalError("RETRIEVAL_HIT_INVALID")
|
||||
reason = _hit_filter_mismatch(hit, query_row["filter"])
|
||||
if reason is None:
|
||||
selected.append(dict(hit))
|
||||
else:
|
||||
dropped.append(_drop(hit, reason))
|
||||
issue_codes.add(reason)
|
||||
for row in raw_drops:
|
||||
if (
|
||||
not isinstance(row, Mapping)
|
||||
or set(row) != DROP_KEYS
|
||||
or row.get("drop_reason_code") not in DROP_REASONS
|
||||
):
|
||||
raise RetrievalError("DROPPED_HIT_INVALID")
|
||||
dropped.append(dict(row))
|
||||
receipts.append(
|
||||
_receipt(
|
||||
query_plan=query_plan,
|
||||
query_row=query_row,
|
||||
request=request,
|
||||
response=response,
|
||||
collection=collection,
|
||||
credential_ref=credential_ref,
|
||||
)
|
||||
)
|
||||
|
||||
unique: dict[str, dict[str, Any]] = {}
|
||||
for hit in sorted(
|
||||
selected,
|
||||
key=lambda value: (
|
||||
int(value.get("post_rerank_rank", 2**31 - 1)),
|
||||
-float(value.get("reranker_score") or value.get("hybrid_score") or 0),
|
||||
str(value.get("chunk_id", "")),
|
||||
),
|
||||
):
|
||||
chunk_id = _nonempty(hit.get("chunk_id"), "HIT_CHUNK_ID_REQUIRED")
|
||||
if chunk_id in unique:
|
||||
dropped.append(_drop(hit, "DUPLICATE_CHUNK"))
|
||||
issue_codes.add("DUPLICATE_CHUNK")
|
||||
else:
|
||||
unique[chunk_id] = hit
|
||||
selected_hits = list(unique.values())
|
||||
result_status = (
|
||||
"NOT_RUN_UNRESOLVED_BINDING"
|
||||
if not rows
|
||||
else "TECHNICAL_INCOMPLETE"
|
||||
if technical_incomplete
|
||||
else "COMPLETE"
|
||||
if selected_hits
|
||||
else "NO_HIT"
|
||||
)
|
||||
if not rows:
|
||||
issue_codes.add("QUERY_NOT_RUN_UNRESOLVED_BINDING")
|
||||
core = {
|
||||
"schema_version": "stage2_requirement_fact_retrieval_result.v1",
|
||||
"run_binding_digest": query_plan["run_binding_digest"],
|
||||
"atomic_claim_id": query_plan["atomic_claim_id"],
|
||||
"query_plan_sha256": query_plan["query_plan_sha256"],
|
||||
"snapshot_id": _nonempty(collection.get("snapshot_id"), "SNAPSHOT_ID_REQUIRED"),
|
||||
"snapshot_sha256": _nonempty(
|
||||
collection.get("snapshot_sha256"), "SNAPSHOT_SHA256_REQUIRED"
|
||||
),
|
||||
"result_status": result_status,
|
||||
"selected_hits": selected_hits,
|
||||
"dropped_hits": sorted(
|
||||
dropped,
|
||||
key=lambda value: (
|
||||
str(value.get("chunk_id", "")),
|
||||
str(value.get("drop_reason_code", "")),
|
||||
),
|
||||
),
|
||||
"issue_codes": sorted(issue_codes),
|
||||
}
|
||||
return {**core, "result_sha256": _digest(core)}, receipts
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
#!/usr/bin/env python3
|
||||
"""C29 retrieval provenance and Stage-1-slot crosswalk verifier."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from typing import Any, Mapping, Sequence
|
||||
|
||||
|
||||
class PackError(ValueError):
|
||||
"""Controlled C29 contract failure."""
|
||||
|
||||
|
||||
def _digest(value: Any) -> str:
|
||||
raw = (
|
||||
json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":")) + "\n"
|
||||
).encode("utf-8")
|
||||
return hashlib.sha256(raw).hexdigest()
|
||||
|
||||
|
||||
def _sha256(value: Any, code: str) -> str:
|
||||
if (
|
||||
not isinstance(value, str)
|
||||
or len(value) != 64
|
||||
or any(character not in "0123456789abcdef" for character in value)
|
||||
):
|
||||
raise PackError(code)
|
||||
return value
|
||||
|
||||
|
||||
def verify_pack(
|
||||
binding: Mapping[str, Any],
|
||||
retrieval_result: Mapping[str, Any],
|
||||
corpus_release: Mapping[str, Any],
|
||||
crosswalk_rows: Sequence[Mapping[str, Any]],
|
||||
*,
|
||||
claim_group_id: str,
|
||||
retrieval_receipt_refs: Sequence[Mapping[str, Any]],
|
||||
) -> dict[str, Any]:
|
||||
"""Return the exact ``requirement_fact_pack`` schema contract."""
|
||||
|
||||
run_binding_digest = retrieval_result.get("run_binding_digest")
|
||||
atomic_claim_id = retrieval_result.get("atomic_claim_id")
|
||||
run_binding_digest = _sha256(run_binding_digest, "RUN_BINDING_DIGEST_INVALID")
|
||||
if not isinstance(atomic_claim_id, str) or not atomic_claim_id:
|
||||
raise PackError("ATOMIC_CLAIM_ID_REQUIRED")
|
||||
expected_snapshot = corpus_release.get("snapshot_id")
|
||||
expected_snapshot_hash = corpus_release.get("snapshot_sha256")
|
||||
if (
|
||||
retrieval_result.get("snapshot_id") != expected_snapshot
|
||||
or retrieval_result.get("snapshot_sha256") != expected_snapshot_hash
|
||||
):
|
||||
raise PackError("RETRIEVAL_RESULT_SNAPSHOT_MISMATCH")
|
||||
corpus_release_sha256 = _sha256(
|
||||
corpus_release.get("corpus_release_sha256"), "CORPUS_RELEASE_SHA256_INVALID"
|
||||
)
|
||||
expected_snapshot_hash = _sha256(expected_snapshot_hash, "SNAPSHOT_SHA256_INVALID")
|
||||
|
||||
crosswalk = {
|
||||
(row.get("corpus_element_id"), row.get("doctrine_role")): row
|
||||
for row in crosswalk_rows
|
||||
if isinstance(row, Mapping)
|
||||
}
|
||||
items: list[dict[str, Any]] = []
|
||||
unmapped: set[str] = set()
|
||||
conflicting: set[str] = set()
|
||||
issue_codes: set[str] = set(retrieval_result.get("issue_codes", []))
|
||||
for hit in retrieval_result.get("selected_hits", []):
|
||||
if not isinstance(hit, Mapping):
|
||||
raise PackError("RETRIEVAL_HIT_INVALID")
|
||||
metadata = hit.get("metadata")
|
||||
if not isinstance(metadata, Mapping):
|
||||
raise PackError("RETRIEVAL_HIT_METADATA_REQUIRED")
|
||||
chunk_id = hit.get("chunk_id")
|
||||
if not isinstance(chunk_id, str) or not chunk_id:
|
||||
raise PackError("CHUNK_ID_REQUIRED")
|
||||
mismatch = (
|
||||
binding.get("case_type_id") not in metadata.get("case_type_ids", [])
|
||||
or binding.get("sub_rule_id") not in metadata.get("sub_rule_ids", [])
|
||||
or binding.get("rule_branch_key") not in metadata.get("rule_branch_keys", [])
|
||||
)
|
||||
if mismatch:
|
||||
conflicting.add(chunk_id)
|
||||
issue_codes.add("RETRIEVAL_BINDING_MISMATCH")
|
||||
continue
|
||||
for corpus_element_id in sorted(set(metadata.get("corpus_element_ids", []))):
|
||||
for doctrine_role in sorted(set(metadata.get("doctrine_roles", []))):
|
||||
row = crosswalk.get((corpus_element_id, doctrine_role))
|
||||
if row is None or row.get("mapping_status") in {None, "UNMAPPED"}:
|
||||
unmapped.add(chunk_id)
|
||||
issue_codes.add("SLOT_CROSSWALK_MISSING")
|
||||
continue
|
||||
if row.get("mapping_status") == "CONFLICT":
|
||||
conflicting.add(chunk_id)
|
||||
issue_codes.add("SLOT_CROSSWALK_CONFLICT")
|
||||
continue
|
||||
items.append(
|
||||
{
|
||||
"corpus_element_id": corpus_element_id,
|
||||
"doctrine_role": doctrine_role,
|
||||
"chunk_id": chunk_id,
|
||||
"chunk_sha256": hit["chunk_sha256"],
|
||||
"source_document_id": hit["source_document_id"],
|
||||
"source_document_sha256": hit["source_document_sha256"],
|
||||
"source_locator": hit["source_locator"],
|
||||
"authority_ids": sorted(set(metadata.get("authority_ids", []))),
|
||||
"proposition_ids": sorted(set(metadata.get("proposition_ids", []))),
|
||||
"stage1_slot_refs": sorted(set(row.get("stage1_slot_refs", []))),
|
||||
"mapping_status": row["mapping_status"],
|
||||
"fact_creation_allowed": False,
|
||||
"legal_decision_override_allowed": False,
|
||||
}
|
||||
)
|
||||
items.sort(
|
||||
key=lambda item: (
|
||||
item["corpus_element_id"],
|
||||
item["doctrine_role"],
|
||||
item["chunk_id"],
|
||||
)
|
||||
)
|
||||
core = {
|
||||
"schema_version": "stage2_requirement_fact_pack.v1",
|
||||
"run_binding_digest": run_binding_digest,
|
||||
"claim_group_id": claim_group_id,
|
||||
"atomic_claim_ids": [atomic_claim_id],
|
||||
"corpus_release_sha256": corpus_release_sha256,
|
||||
"snapshot_id": expected_snapshot,
|
||||
"snapshot_sha256": expected_snapshot_hash,
|
||||
"retrieval_receipt_refs": [dict(row) for row in retrieval_receipt_refs],
|
||||
"items": items,
|
||||
"unmapped_chunk_ids": sorted(unmapped),
|
||||
"conflicting_chunk_ids": sorted(conflicting),
|
||||
"authority_class": "CAUSE_AND_ELEMENT_CHECK",
|
||||
"issue_codes": sorted(issue_codes),
|
||||
}
|
||||
return {**core, "pack_sha256": _digest(core)}
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
#!/usr/bin/env python3
|
||||
"""C29 retrieval provenance and Stage-1-slot crosswalk verifier."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from typing import Any, Mapping, Sequence
|
||||
|
||||
|
||||
class PackError(ValueError):
|
||||
"""Controlled C29 contract failure."""
|
||||
|
||||
|
||||
def _digest(value: Any) -> str:
|
||||
raw = (
|
||||
json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":")) + "\n"
|
||||
).encode("utf-8")
|
||||
return hashlib.sha256(raw).hexdigest()
|
||||
|
||||
|
||||
def _sha256(value: Any, code: str) -> str:
|
||||
if (
|
||||
not isinstance(value, str)
|
||||
or len(value) != 64
|
||||
or any(character not in "0123456789abcdef" for character in value)
|
||||
):
|
||||
raise PackError(code)
|
||||
return value
|
||||
|
||||
|
||||
def verify_pack(
|
||||
binding: Mapping[str, Any],
|
||||
retrieval_result: Mapping[str, Any],
|
||||
corpus_release: Mapping[str, Any],
|
||||
crosswalk_rows: Sequence[Mapping[str, Any]],
|
||||
*,
|
||||
claim_group_id: str,
|
||||
retrieval_receipt_refs: Sequence[Mapping[str, Any]],
|
||||
) -> dict[str, Any]:
|
||||
"""Return the exact ``requirement_fact_pack`` schema contract."""
|
||||
|
||||
run_binding_digest = retrieval_result.get("run_binding_digest")
|
||||
atomic_claim_id = retrieval_result.get("atomic_claim_id")
|
||||
run_binding_digest = _sha256(run_binding_digest, "RUN_BINDING_DIGEST_INVALID")
|
||||
if not isinstance(atomic_claim_id, str) or not atomic_claim_id:
|
||||
raise PackError("ATOMIC_CLAIM_ID_REQUIRED")
|
||||
expected_snapshot = corpus_release.get("snapshot_id")
|
||||
expected_snapshot_hash = corpus_release.get("snapshot_sha256")
|
||||
if (
|
||||
retrieval_result.get("snapshot_id") != expected_snapshot
|
||||
or retrieval_result.get("snapshot_sha256") != expected_snapshot_hash
|
||||
):
|
||||
raise PackError("RETRIEVAL_RESULT_SNAPSHOT_MISMATCH")
|
||||
corpus_release_sha256 = _sha256(
|
||||
corpus_release.get("corpus_release_sha256"), "CORPUS_RELEASE_SHA256_INVALID"
|
||||
)
|
||||
expected_snapshot_hash = _sha256(expected_snapshot_hash, "SNAPSHOT_SHA256_INVALID")
|
||||
|
||||
crosswalk = {
|
||||
(row.get("corpus_element_id"), row.get("doctrine_role")): row
|
||||
for row in crosswalk_rows
|
||||
if isinstance(row, Mapping)
|
||||
}
|
||||
items: list[dict[str, Any]] = []
|
||||
unmapped: set[str] = set()
|
||||
conflicting: set[str] = set()
|
||||
issue_codes: set[str] = set(retrieval_result.get("issue_codes", []))
|
||||
for hit in retrieval_result.get("selected_hits", []):
|
||||
if not isinstance(hit, Mapping):
|
||||
raise PackError("RETRIEVAL_HIT_INVALID")
|
||||
metadata = hit.get("metadata")
|
||||
if not isinstance(metadata, Mapping):
|
||||
raise PackError("RETRIEVAL_HIT_METADATA_REQUIRED")
|
||||
chunk_id = hit.get("chunk_id")
|
||||
if not isinstance(chunk_id, str) or not chunk_id:
|
||||
raise PackError("CHUNK_ID_REQUIRED")
|
||||
mismatch = (
|
||||
binding.get("case_type_id") not in metadata.get("case_type_ids", [])
|
||||
or binding.get("sub_rule_id") not in metadata.get("sub_rule_ids", [])
|
||||
or binding.get("rule_branch_key") not in metadata.get("rule_branch_keys", [])
|
||||
)
|
||||
if mismatch:
|
||||
conflicting.add(chunk_id)
|
||||
issue_codes.add("RETRIEVAL_BINDING_MISMATCH")
|
||||
continue
|
||||
for corpus_element_id in sorted(set(metadata.get("corpus_element_ids", []))):
|
||||
for doctrine_role in sorted(set(metadata.get("doctrine_roles", []))):
|
||||
row = crosswalk.get((corpus_element_id, doctrine_role))
|
||||
if row is None or row.get("mapping_status") in {None, "UNMAPPED"}:
|
||||
unmapped.add(chunk_id)
|
||||
issue_codes.add("SLOT_CROSSWALK_MISSING")
|
||||
continue
|
||||
if row.get("mapping_status") == "CONFLICT":
|
||||
conflicting.add(chunk_id)
|
||||
issue_codes.add("SLOT_CROSSWALK_CONFLICT")
|
||||
continue
|
||||
items.append(
|
||||
{
|
||||
"corpus_element_id": corpus_element_id,
|
||||
"doctrine_role": doctrine_role,
|
||||
"chunk_id": chunk_id,
|
||||
"chunk_sha256": hit["chunk_sha256"],
|
||||
"source_document_id": hit["source_document_id"],
|
||||
"source_document_sha256": hit["source_document_sha256"],
|
||||
"source_locator": hit["source_locator"],
|
||||
"authority_ids": sorted(set(metadata.get("authority_ids", []))),
|
||||
"proposition_ids": sorted(set(metadata.get("proposition_ids", []))),
|
||||
"stage1_slot_refs": sorted(set(row.get("stage1_slot_refs", []))),
|
||||
"mapping_status": row["mapping_status"],
|
||||
"fact_creation_allowed": False,
|
||||
"legal_decision_override_allowed": False,
|
||||
}
|
||||
)
|
||||
items.sort(
|
||||
key=lambda item: (
|
||||
item["corpus_element_id"],
|
||||
item["doctrine_role"],
|
||||
item["chunk_id"],
|
||||
)
|
||||
)
|
||||
core = {
|
||||
"schema_version": "stage2_requirement_fact_pack.v1",
|
||||
"run_binding_digest": run_binding_digest,
|
||||
"claim_group_id": claim_group_id,
|
||||
"atomic_claim_ids": [atomic_claim_id],
|
||||
"corpus_release_sha256": corpus_release_sha256,
|
||||
"snapshot_id": expected_snapshot,
|
||||
"snapshot_sha256": expected_snapshot_hash,
|
||||
"retrieval_receipt_refs": [dict(row) for row in retrieval_receipt_refs],
|
||||
"items": items,
|
||||
"unmapped_chunk_ids": sorted(unmapped),
|
||||
"conflicting_chunk_ids": sorted(conflicting),
|
||||
"authority_class": "CAUSE_AND_ELEMENT_CHECK",
|
||||
"issue_codes": sorted(issue_codes),
|
||||
}
|
||||
return {**core, "pack_sha256": _digest(core)}
|
||||
+286
@@ -0,0 +1,286 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Safe Decimal interpreter that emits canonical S2_20 calculation receipts."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from decimal import Decimal, InvalidOperation, ROUND_DOWN, ROUND_HALF_UP, ROUND_UP
|
||||
import hashlib
|
||||
import json
|
||||
import re
|
||||
from typing import Any, Mapping, Sequence
|
||||
|
||||
|
||||
OPERATORS = frozenset({"ADD", "SUBTRACT", "MULTIPLY", "DIVIDE", "MIN", "MAX"})
|
||||
ROUNDING_MODES = {
|
||||
"ROUND_HALF_UP": ROUND_HALF_UP,
|
||||
"ROUND_DOWN": ROUND_DOWN,
|
||||
"ROUND_UP": ROUND_UP,
|
||||
"TRUNCATE": ROUND_DOWN,
|
||||
}
|
||||
CALCULATOR_ID = re.compile(r"^CE-(?:0[1-9]|1[0-3]|R[1-4])$")
|
||||
SHA256 = re.compile(r"^[a-f0-9]{64}$")
|
||||
|
||||
|
||||
class CalculationError(ValueError):
|
||||
"""Controlled calculator contract failure."""
|
||||
|
||||
|
||||
def _decimal(value: Any) -> Decimal:
|
||||
try:
|
||||
return Decimal(str(value))
|
||||
except (InvalidOperation, ValueError) as exc:
|
||||
raise CalculationError("INVALID_DECIMAL_OPERAND") from exc
|
||||
|
||||
|
||||
def _canonical(value: Any) -> bytes:
|
||||
return (
|
||||
json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":")) + "\n"
|
||||
).encode("utf-8")
|
||||
|
||||
|
||||
def _digest(value: Any) -> str:
|
||||
return hashlib.sha256(_canonical(value)).hexdigest()
|
||||
|
||||
|
||||
def _sha256(value: Any, code: str) -> str:
|
||||
if not isinstance(value, str) or SHA256.fullmatch(value) is None:
|
||||
raise CalculationError(code)
|
||||
return value
|
||||
|
||||
|
||||
def activate_calculators(
|
||||
signature: Mapping[str, Any],
|
||||
requested_metrics: Sequence[str],
|
||||
activation_rows: Sequence[Mapping[str, Any]],
|
||||
) -> list[dict[str, Any]]:
|
||||
"""Evaluate the closed activation registry without a default branch."""
|
||||
|
||||
rows: list[dict[str, Any]] = []
|
||||
for rule in sorted(
|
||||
activation_rows,
|
||||
key=lambda row: (str(row.get("calculator_id")), str(row.get("activation_row_id"))),
|
||||
):
|
||||
calculator_id = rule.get("calculator_id")
|
||||
allowed_metrics = rule.get("requested_metrics", [])
|
||||
predicates = rule.get("equals", {})
|
||||
if (
|
||||
not isinstance(calculator_id, str)
|
||||
or CALCULATOR_ID.fullmatch(calculator_id) is None
|
||||
or not isinstance(allowed_metrics, list)
|
||||
or not isinstance(predicates, Mapping)
|
||||
or not predicates
|
||||
):
|
||||
raise CalculationError("INVALID_ACTIVATION_ROW")
|
||||
metric_match = bool(set(requested_metrics) & set(allowed_metrics))
|
||||
predicate_match = all(signature.get(key, "UNKNOWN") == value for key, value in predicates.items())
|
||||
rows.append(
|
||||
{
|
||||
"calculator_id": calculator_id,
|
||||
"activation_row_id": rule.get("activation_row_id"),
|
||||
"activation_status": "ACTIVATED" if metric_match and predicate_match else "NOT_APPLICABLE",
|
||||
}
|
||||
)
|
||||
return rows
|
||||
|
||||
|
||||
def _operand_row(operand_id: str, supplied: Any, unit_id: str, currency: str | None) -> dict[str, Any]:
|
||||
if supplied is None:
|
||||
return {
|
||||
"operand_id": operand_id,
|
||||
"source_ref": f"OPERAND:{operand_id}",
|
||||
"status": "MISSING",
|
||||
"value_type": "DECIMAL",
|
||||
"value": None,
|
||||
"unit_id": unit_id,
|
||||
"currency": currency,
|
||||
"value_sha256": None,
|
||||
}
|
||||
if isinstance(supplied, Mapping):
|
||||
status = supplied.get("status", "PRESENT")
|
||||
value = supplied.get("value")
|
||||
source_ref = supplied.get("source_ref", f"OPERAND:{operand_id}")
|
||||
value_type = supplied.get("value_type", "DECIMAL")
|
||||
row_unit = supplied.get("unit_id", unit_id)
|
||||
row_currency = supplied.get("currency", currency)
|
||||
else:
|
||||
status = "PRESENT"
|
||||
value = supplied
|
||||
source_ref = f"OPERAND:{operand_id}"
|
||||
value_type = "DECIMAL"
|
||||
row_unit = unit_id
|
||||
row_currency = currency
|
||||
if status not in {"PRESENT", "MISSING", "CONFLICT"}:
|
||||
raise CalculationError("OPERAND_STATUS_INVALID")
|
||||
if status != "PRESENT":
|
||||
value = None
|
||||
normalized = format(_decimal(value), "f") if status == "PRESENT" and value_type == "DECIMAL" else value
|
||||
return {
|
||||
"operand_id": operand_id,
|
||||
"source_ref": str(source_ref),
|
||||
"status": status,
|
||||
"value_type": value_type,
|
||||
"value": normalized,
|
||||
"unit_id": row_unit,
|
||||
"currency": row_currency,
|
||||
"value_sha256": _digest(normalized) if status == "PRESENT" else None,
|
||||
}
|
||||
|
||||
|
||||
def _law_value_uses(
|
||||
law_value_ids: Sequence[str], law_values: Mapping[str, Mapping[str, Any]]
|
||||
) -> tuple[list[dict[str, Any]], list[str]]:
|
||||
rows: list[dict[str, Any]] = []
|
||||
missing: list[str] = []
|
||||
for law_value_id in law_value_ids:
|
||||
source = law_values.get(law_value_id)
|
||||
if not isinstance(source, Mapping) or source.get("approval_status") != "APPROVED":
|
||||
missing.append(law_value_id)
|
||||
continue
|
||||
rows.append(
|
||||
{
|
||||
"law_value_id": law_value_id,
|
||||
"authority_id": source["authority_id"],
|
||||
"authority_release_sha256": _sha256(
|
||||
source.get("authority_release_sha256"), "AUTHORITY_RELEASE_SHA256_INVALID"
|
||||
),
|
||||
"law_value_release_sha256": _sha256(
|
||||
source.get("law_value_release_sha256"), "LAW_VALUE_RELEASE_SHA256_INVALID"
|
||||
),
|
||||
"effective_interval": dict(source["effective_interval"]),
|
||||
"value": format(_decimal(source["value"]), "f"),
|
||||
"unit_id": source["unit_id"],
|
||||
"approval_status": "APPROVED",
|
||||
}
|
||||
)
|
||||
return rows, missing
|
||||
|
||||
|
||||
def execute_formula(
|
||||
calculator: Mapping[str, Any],
|
||||
operands: Mapping[str, Any],
|
||||
law_values: Mapping[str, Mapping[str, Any]],
|
||||
*,
|
||||
run_binding_digest: str,
|
||||
atomic_claim_id: str,
|
||||
claim_group_id: str,
|
||||
activation_row_id: str,
|
||||
descriptor_sha256: str,
|
||||
) -> dict[str, Any]:
|
||||
"""Execute one admitted formula and emit every required receipt field."""
|
||||
|
||||
run_binding_digest = _sha256(run_binding_digest, "RUN_BINDING_DIGEST_INVALID")
|
||||
descriptor_sha256 = _sha256(descriptor_sha256, "DESCRIPTOR_SHA256_INVALID")
|
||||
calculator_id = calculator.get("calculator_id")
|
||||
if not isinstance(calculator_id, str) or CALCULATOR_ID.fullmatch(calculator_id) is None:
|
||||
raise CalculationError("CALCULATOR_ID_INVALID")
|
||||
formula_id = calculator.get("formula_id")
|
||||
if not isinstance(formula_id, str) or not formula_id:
|
||||
raise CalculationError("FORMULA_ID_REQUIRED")
|
||||
required = calculator.get("input_operand_ids", calculator.get("required_operands", []))
|
||||
if not isinstance(required, list) or not required or not all(
|
||||
isinstance(item, str) and item for item in required
|
||||
):
|
||||
raise CalculationError("FORMULA_OPERANDS_INVALID")
|
||||
output_metric_id = calculator.get("output_metric_id")
|
||||
output_unit_id = calculator.get("output_unit_id", calculator.get("unit"))
|
||||
if not isinstance(output_metric_id, str) or not output_metric_id:
|
||||
raise CalculationError("OUTPUT_METRIC_ID_REQUIRED")
|
||||
if not isinstance(output_unit_id, str) or not output_unit_id:
|
||||
raise CalculationError("OUTPUT_UNIT_ID_REQUIRED")
|
||||
currency = calculator.get("currency")
|
||||
rounding_rule = calculator.get("rounding_rule")
|
||||
effective_interval = calculator.get("effective_interval")
|
||||
if not isinstance(rounding_rule, Mapping) or rounding_rule.get("mode") not in ROUNDING_MODES:
|
||||
raise CalculationError("ROUNDING_RULE_INVALID")
|
||||
if not isinstance(effective_interval, Mapping):
|
||||
raise CalculationError("EFFECTIVE_INTERVAL_REQUIRED")
|
||||
formula_scope = {key: value for key, value in calculator.items() if key != "formula_sha256"}
|
||||
formula_sha256 = calculator.get("formula_sha256") or _digest(formula_scope)
|
||||
formula_sha256 = _sha256(formula_sha256, "FORMULA_SHA256_INVALID")
|
||||
|
||||
operand_rows = [
|
||||
_operand_row(operand_id, operands.get(operand_id), output_unit_id, currency)
|
||||
for operand_id in required
|
||||
]
|
||||
missing = sorted(
|
||||
row["operand_id"] for row in operand_rows if row["status"] != "PRESENT"
|
||||
)
|
||||
law_value_ids = calculator.get("law_value_ids", [])
|
||||
if not isinstance(law_value_ids, list):
|
||||
raise CalculationError("LAW_VALUE_IDS_INVALID")
|
||||
law_value_uses, missing_law_values = _law_value_uses(law_value_ids, law_values)
|
||||
issue_codes: list[str] = []
|
||||
calculation_status = "CALCULATED"
|
||||
if missing:
|
||||
calculation_status = "MISSING_OPERAND"
|
||||
issue_codes.append("CALCULATION_OPERAND_MISSING")
|
||||
elif missing_law_values:
|
||||
calculation_status = "LAW_VALUE_GAP"
|
||||
issue_codes.extend(f"CALCULATION_LAW_VALUE_UNRESOLVED:{item}" for item in missing_law_values)
|
||||
|
||||
result: dict[str, Any] | None = None
|
||||
result_sha256: str | None = None
|
||||
if calculation_status == "CALCULATED":
|
||||
operator = calculator.get("operator")
|
||||
if operator not in OPERATORS:
|
||||
raise CalculationError("UNSUPPORTED_FORMULA_OPERATOR")
|
||||
values = [_decimal(operands[name]["value"] if isinstance(operands[name], Mapping) else operands[name]) for name in required]
|
||||
if operator == "ADD":
|
||||
computed = sum(values, Decimal("0"))
|
||||
elif operator == "SUBTRACT":
|
||||
computed = values[0] - sum(values[1:], Decimal("0"))
|
||||
elif operator == "MULTIPLY":
|
||||
computed = Decimal("1")
|
||||
for value in values:
|
||||
computed *= value
|
||||
elif operator == "DIVIDE":
|
||||
if len(values) != 2 or values[1] == 0:
|
||||
raise CalculationError("INVALID_DIVISION")
|
||||
computed = values[0] / values[1]
|
||||
elif operator == "MIN":
|
||||
computed = min(values)
|
||||
else:
|
||||
computed = max(values)
|
||||
quantum = _decimal(rounding_rule["quantum"])
|
||||
rounded = format(computed.quantize(quantum, rounding=ROUNDING_MODES[rounding_rule["mode"]]), "f")
|
||||
result = {
|
||||
"metric_id": output_metric_id,
|
||||
"value": rounded,
|
||||
"unit_id": output_unit_id,
|
||||
"currency": currency,
|
||||
}
|
||||
result_sha256 = _digest(result)
|
||||
|
||||
identity = {
|
||||
"run_binding_digest": run_binding_digest,
|
||||
"atomic_claim_id": atomic_claim_id,
|
||||
"claim_group_id": claim_group_id,
|
||||
"calculator_id": calculator_id,
|
||||
"formula_id": formula_id,
|
||||
}
|
||||
core = {
|
||||
"schema_version": "stage2.calculation_receipt.v1",
|
||||
"calculation_receipt_id": f"CR-{_digest(identity)[:24]}",
|
||||
"run_binding_digest": run_binding_digest,
|
||||
"atomic_claim_id": atomic_claim_id,
|
||||
"claim_group_id": claim_group_id,
|
||||
"calculator_id": calculator_id,
|
||||
"activation_status": "ACTIVATED",
|
||||
"calculation_status": calculation_status,
|
||||
"activation_row_id": activation_row_id,
|
||||
"descriptor_sha256": descriptor_sha256,
|
||||
"formula_id": formula_id,
|
||||
"formula_sha256": formula_sha256,
|
||||
"operand_rows": operand_rows,
|
||||
"operand_refs": sorted(required),
|
||||
"units": sorted({row["unit_id"] for row in operand_rows if row["unit_id"]}),
|
||||
"law_value_uses": law_value_uses,
|
||||
"effective_interval": dict(effective_interval),
|
||||
"rounding_rule": dict(rounding_rule),
|
||||
"period_rows": [],
|
||||
"missing_operands": missing,
|
||||
"result": result,
|
||||
"result_sha256": result_sha256,
|
||||
"issue_codes": sorted(issue_codes),
|
||||
}
|
||||
return {**core, "receipt_sha256": _digest(core)}
|
||||
+286
@@ -0,0 +1,286 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Safe Decimal interpreter that emits canonical S2_20 calculation receipts."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from decimal import Decimal, InvalidOperation, ROUND_DOWN, ROUND_HALF_UP, ROUND_UP
|
||||
import hashlib
|
||||
import json
|
||||
import re
|
||||
from typing import Any, Mapping, Sequence
|
||||
|
||||
|
||||
OPERATORS = frozenset({"ADD", "SUBTRACT", "MULTIPLY", "DIVIDE", "MIN", "MAX"})
|
||||
ROUNDING_MODES = {
|
||||
"ROUND_HALF_UP": ROUND_HALF_UP,
|
||||
"ROUND_DOWN": ROUND_DOWN,
|
||||
"ROUND_UP": ROUND_UP,
|
||||
"TRUNCATE": ROUND_DOWN,
|
||||
}
|
||||
CALCULATOR_ID = re.compile(r"^CE-(?:0[1-9]|1[0-3]|R[1-4])$")
|
||||
SHA256 = re.compile(r"^[a-f0-9]{64}$")
|
||||
|
||||
|
||||
class CalculationError(ValueError):
|
||||
"""Controlled calculator contract failure."""
|
||||
|
||||
|
||||
def _decimal(value: Any) -> Decimal:
|
||||
try:
|
||||
return Decimal(str(value))
|
||||
except (InvalidOperation, ValueError) as exc:
|
||||
raise CalculationError("INVALID_DECIMAL_OPERAND") from exc
|
||||
|
||||
|
||||
def _canonical(value: Any) -> bytes:
|
||||
return (
|
||||
json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":")) + "\n"
|
||||
).encode("utf-8")
|
||||
|
||||
|
||||
def _digest(value: Any) -> str:
|
||||
return hashlib.sha256(_canonical(value)).hexdigest()
|
||||
|
||||
|
||||
def _sha256(value: Any, code: str) -> str:
|
||||
if not isinstance(value, str) or SHA256.fullmatch(value) is None:
|
||||
raise CalculationError(code)
|
||||
return value
|
||||
|
||||
|
||||
def activate_calculators(
|
||||
signature: Mapping[str, Any],
|
||||
requested_metrics: Sequence[str],
|
||||
activation_rows: Sequence[Mapping[str, Any]],
|
||||
) -> list[dict[str, Any]]:
|
||||
"""Evaluate the closed activation registry without a default branch."""
|
||||
|
||||
rows: list[dict[str, Any]] = []
|
||||
for rule in sorted(
|
||||
activation_rows,
|
||||
key=lambda row: (str(row.get("calculator_id")), str(row.get("activation_row_id"))),
|
||||
):
|
||||
calculator_id = rule.get("calculator_id")
|
||||
allowed_metrics = rule.get("requested_metrics", [])
|
||||
predicates = rule.get("equals", {})
|
||||
if (
|
||||
not isinstance(calculator_id, str)
|
||||
or CALCULATOR_ID.fullmatch(calculator_id) is None
|
||||
or not isinstance(allowed_metrics, list)
|
||||
or not isinstance(predicates, Mapping)
|
||||
or not predicates
|
||||
):
|
||||
raise CalculationError("INVALID_ACTIVATION_ROW")
|
||||
metric_match = bool(set(requested_metrics) & set(allowed_metrics))
|
||||
predicate_match = all(signature.get(key, "UNKNOWN") == value for key, value in predicates.items())
|
||||
rows.append(
|
||||
{
|
||||
"calculator_id": calculator_id,
|
||||
"activation_row_id": rule.get("activation_row_id"),
|
||||
"activation_status": "ACTIVATED" if metric_match and predicate_match else "NOT_APPLICABLE",
|
||||
}
|
||||
)
|
||||
return rows
|
||||
|
||||
|
||||
def _operand_row(operand_id: str, supplied: Any, unit_id: str, currency: str | None) -> dict[str, Any]:
|
||||
if supplied is None:
|
||||
return {
|
||||
"operand_id": operand_id,
|
||||
"source_ref": f"OPERAND:{operand_id}",
|
||||
"status": "MISSING",
|
||||
"value_type": "DECIMAL",
|
||||
"value": None,
|
||||
"unit_id": unit_id,
|
||||
"currency": currency,
|
||||
"value_sha256": None,
|
||||
}
|
||||
if isinstance(supplied, Mapping):
|
||||
status = supplied.get("status", "PRESENT")
|
||||
value = supplied.get("value")
|
||||
source_ref = supplied.get("source_ref", f"OPERAND:{operand_id}")
|
||||
value_type = supplied.get("value_type", "DECIMAL")
|
||||
row_unit = supplied.get("unit_id", unit_id)
|
||||
row_currency = supplied.get("currency", currency)
|
||||
else:
|
||||
status = "PRESENT"
|
||||
value = supplied
|
||||
source_ref = f"OPERAND:{operand_id}"
|
||||
value_type = "DECIMAL"
|
||||
row_unit = unit_id
|
||||
row_currency = currency
|
||||
if status not in {"PRESENT", "MISSING", "CONFLICT"}:
|
||||
raise CalculationError("OPERAND_STATUS_INVALID")
|
||||
if status != "PRESENT":
|
||||
value = None
|
||||
normalized = format(_decimal(value), "f") if status == "PRESENT" and value_type == "DECIMAL" else value
|
||||
return {
|
||||
"operand_id": operand_id,
|
||||
"source_ref": str(source_ref),
|
||||
"status": status,
|
||||
"value_type": value_type,
|
||||
"value": normalized,
|
||||
"unit_id": row_unit,
|
||||
"currency": row_currency,
|
||||
"value_sha256": _digest(normalized) if status == "PRESENT" else None,
|
||||
}
|
||||
|
||||
|
||||
def _law_value_uses(
|
||||
law_value_ids: Sequence[str], law_values: Mapping[str, Mapping[str, Any]]
|
||||
) -> tuple[list[dict[str, Any]], list[str]]:
|
||||
rows: list[dict[str, Any]] = []
|
||||
missing: list[str] = []
|
||||
for law_value_id in law_value_ids:
|
||||
source = law_values.get(law_value_id)
|
||||
if not isinstance(source, Mapping) or source.get("approval_status") != "APPROVED":
|
||||
missing.append(law_value_id)
|
||||
continue
|
||||
rows.append(
|
||||
{
|
||||
"law_value_id": law_value_id,
|
||||
"authority_id": source["authority_id"],
|
||||
"authority_release_sha256": _sha256(
|
||||
source.get("authority_release_sha256"), "AUTHORITY_RELEASE_SHA256_INVALID"
|
||||
),
|
||||
"law_value_release_sha256": _sha256(
|
||||
source.get("law_value_release_sha256"), "LAW_VALUE_RELEASE_SHA256_INVALID"
|
||||
),
|
||||
"effective_interval": dict(source["effective_interval"]),
|
||||
"value": format(_decimal(source["value"]), "f"),
|
||||
"unit_id": source["unit_id"],
|
||||
"approval_status": "APPROVED",
|
||||
}
|
||||
)
|
||||
return rows, missing
|
||||
|
||||
|
||||
def execute_formula(
|
||||
calculator: Mapping[str, Any],
|
||||
operands: Mapping[str, Any],
|
||||
law_values: Mapping[str, Mapping[str, Any]],
|
||||
*,
|
||||
run_binding_digest: str,
|
||||
atomic_claim_id: str,
|
||||
claim_group_id: str,
|
||||
activation_row_id: str,
|
||||
descriptor_sha256: str,
|
||||
) -> dict[str, Any]:
|
||||
"""Execute one admitted formula and emit every required receipt field."""
|
||||
|
||||
run_binding_digest = _sha256(run_binding_digest, "RUN_BINDING_DIGEST_INVALID")
|
||||
descriptor_sha256 = _sha256(descriptor_sha256, "DESCRIPTOR_SHA256_INVALID")
|
||||
calculator_id = calculator.get("calculator_id")
|
||||
if not isinstance(calculator_id, str) or CALCULATOR_ID.fullmatch(calculator_id) is None:
|
||||
raise CalculationError("CALCULATOR_ID_INVALID")
|
||||
formula_id = calculator.get("formula_id")
|
||||
if not isinstance(formula_id, str) or not formula_id:
|
||||
raise CalculationError("FORMULA_ID_REQUIRED")
|
||||
required = calculator.get("input_operand_ids", calculator.get("required_operands", []))
|
||||
if not isinstance(required, list) or not required or not all(
|
||||
isinstance(item, str) and item for item in required
|
||||
):
|
||||
raise CalculationError("FORMULA_OPERANDS_INVALID")
|
||||
output_metric_id = calculator.get("output_metric_id")
|
||||
output_unit_id = calculator.get("output_unit_id", calculator.get("unit"))
|
||||
if not isinstance(output_metric_id, str) or not output_metric_id:
|
||||
raise CalculationError("OUTPUT_METRIC_ID_REQUIRED")
|
||||
if not isinstance(output_unit_id, str) or not output_unit_id:
|
||||
raise CalculationError("OUTPUT_UNIT_ID_REQUIRED")
|
||||
currency = calculator.get("currency")
|
||||
rounding_rule = calculator.get("rounding_rule")
|
||||
effective_interval = calculator.get("effective_interval")
|
||||
if not isinstance(rounding_rule, Mapping) or rounding_rule.get("mode") not in ROUNDING_MODES:
|
||||
raise CalculationError("ROUNDING_RULE_INVALID")
|
||||
if not isinstance(effective_interval, Mapping):
|
||||
raise CalculationError("EFFECTIVE_INTERVAL_REQUIRED")
|
||||
formula_scope = {key: value for key, value in calculator.items() if key != "formula_sha256"}
|
||||
formula_sha256 = calculator.get("formula_sha256") or _digest(formula_scope)
|
||||
formula_sha256 = _sha256(formula_sha256, "FORMULA_SHA256_INVALID")
|
||||
|
||||
operand_rows = [
|
||||
_operand_row(operand_id, operands.get(operand_id), output_unit_id, currency)
|
||||
for operand_id in required
|
||||
]
|
||||
missing = sorted(
|
||||
row["operand_id"] for row in operand_rows if row["status"] != "PRESENT"
|
||||
)
|
||||
law_value_ids = calculator.get("law_value_ids", [])
|
||||
if not isinstance(law_value_ids, list):
|
||||
raise CalculationError("LAW_VALUE_IDS_INVALID")
|
||||
law_value_uses, missing_law_values = _law_value_uses(law_value_ids, law_values)
|
||||
issue_codes: list[str] = []
|
||||
calculation_status = "CALCULATED"
|
||||
if missing:
|
||||
calculation_status = "MISSING_OPERAND"
|
||||
issue_codes.append("CALCULATION_OPERAND_MISSING")
|
||||
elif missing_law_values:
|
||||
calculation_status = "LAW_VALUE_GAP"
|
||||
issue_codes.extend(f"CALCULATION_LAW_VALUE_UNRESOLVED:{item}" for item in missing_law_values)
|
||||
|
||||
result: dict[str, Any] | None = None
|
||||
result_sha256: str | None = None
|
||||
if calculation_status == "CALCULATED":
|
||||
operator = calculator.get("operator")
|
||||
if operator not in OPERATORS:
|
||||
raise CalculationError("UNSUPPORTED_FORMULA_OPERATOR")
|
||||
values = [_decimal(operands[name]["value"] if isinstance(operands[name], Mapping) else operands[name]) for name in required]
|
||||
if operator == "ADD":
|
||||
computed = sum(values, Decimal("0"))
|
||||
elif operator == "SUBTRACT":
|
||||
computed = values[0] - sum(values[1:], Decimal("0"))
|
||||
elif operator == "MULTIPLY":
|
||||
computed = Decimal("1")
|
||||
for value in values:
|
||||
computed *= value
|
||||
elif operator == "DIVIDE":
|
||||
if len(values) != 2 or values[1] == 0:
|
||||
raise CalculationError("INVALID_DIVISION")
|
||||
computed = values[0] / values[1]
|
||||
elif operator == "MIN":
|
||||
computed = min(values)
|
||||
else:
|
||||
computed = max(values)
|
||||
quantum = _decimal(rounding_rule["quantum"])
|
||||
rounded = format(computed.quantize(quantum, rounding=ROUNDING_MODES[rounding_rule["mode"]]), "f")
|
||||
result = {
|
||||
"metric_id": output_metric_id,
|
||||
"value": rounded,
|
||||
"unit_id": output_unit_id,
|
||||
"currency": currency,
|
||||
}
|
||||
result_sha256 = _digest(result)
|
||||
|
||||
identity = {
|
||||
"run_binding_digest": run_binding_digest,
|
||||
"atomic_claim_id": atomic_claim_id,
|
||||
"claim_group_id": claim_group_id,
|
||||
"calculator_id": calculator_id,
|
||||
"formula_id": formula_id,
|
||||
}
|
||||
core = {
|
||||
"schema_version": "stage2.calculation_receipt.v1",
|
||||
"calculation_receipt_id": f"CR-{_digest(identity)[:24]}",
|
||||
"run_binding_digest": run_binding_digest,
|
||||
"atomic_claim_id": atomic_claim_id,
|
||||
"claim_group_id": claim_group_id,
|
||||
"calculator_id": calculator_id,
|
||||
"activation_status": "ACTIVATED",
|
||||
"calculation_status": calculation_status,
|
||||
"activation_row_id": activation_row_id,
|
||||
"descriptor_sha256": descriptor_sha256,
|
||||
"formula_id": formula_id,
|
||||
"formula_sha256": formula_sha256,
|
||||
"operand_rows": operand_rows,
|
||||
"operand_refs": sorted(required),
|
||||
"units": sorted({row["unit_id"] for row in operand_rows if row["unit_id"]}),
|
||||
"law_value_uses": law_value_uses,
|
||||
"effective_interval": dict(effective_interval),
|
||||
"rounding_rule": dict(rounding_rule),
|
||||
"period_rows": [],
|
||||
"missing_operands": missing,
|
||||
"result": result,
|
||||
"result_sha256": result_sha256,
|
||||
"issue_codes": sorted(issue_codes),
|
||||
}
|
||||
return {**core, "receipt_sha256": _digest(core)}
|
||||
+1
-1
@@ -149,7 +149,7 @@ _RAW_VALUE_UNSET = object()
|
||||
# literal placeholders in the offline parity mirror and its unit tests.
|
||||
INLINE_USER_HASH = "{{__user_hash__}}"
|
||||
INLINE_WORKSPACE_HASH = "{{__workspace_hash__}}"
|
||||
EXPECTED_STAGE2_RELEASE_SHA256 = "0f21af3ddca538d9b6a5853dac23222c0b632d0e573de371a8de2709e691699d"
|
||||
EXPECTED_STAGE2_RELEASE_SHA256 = "70ef317cc0c557e44619b6cd6b4ad567e02e73271a36a0bbc93b748ae3f34f92"
|
||||
INLINE_REQUEST_PATH = "stage2_control/s2_00_request.json"
|
||||
INLINE_STAGE2_ASSET_ROOT = "Default_Agent/Stage_2_Clean"
|
||||
INLINE_STAGE2_RELEASE_PATH = (
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user