feat(stage2): run S2_00 through inline MCP
Replace the unresolved host-loader invocation with one hash-bound Code Executor task so deterministic ingress has a concrete Agent execution path.\n\nBREAKING CHANGE: S2_00 no longer invokes stage2_loader.py. The legacy loader binding is reference-only and cannot authorize or provide fallback.
This commit is contained in:
+6097
File diff suppressed because it is too large
Load Diff
+89
@@ -0,0 +1,89 @@
|
|||||||
|
schema_version: stage2_code_executor_binding.v1
|
||||||
|
binding_id: S2-BINDING-S2_00-CODE-EXECUTOR-V1
|
||||||
|
workflow_id: S2_00
|
||||||
|
execution_class: NON-LLM-DETERMINISTIC
|
||||||
|
active_runtime_authority: true
|
||||||
|
|
||||||
|
agent_script_ref:
|
||||||
|
asset_id: AGENT-S2_00-INLINE
|
||||||
|
path: agent_scripts/Stage_2_S2_00.yml
|
||||||
|
sha256: 2e0bf36786a64d74070f8cc97dfde05e9d17c7d7bef3a5b1e60aecdb0accc71d
|
||||||
|
schema_id: liti_agent_yaml.v1
|
||||||
|
binding_status: BOUND
|
||||||
|
|
||||||
|
workflow_contract_ref:
|
||||||
|
asset_id: WF-S2_00
|
||||||
|
path: workflows/S2_00_stage1_ingress_normalize_and_bundle_compile.yml
|
||||||
|
sha256: 74f8844963952ca853286662e46ab4c7b24c5785faf521c0557548778c446cea
|
||||||
|
schema_id: stage2_workflow_contract.v1.1
|
||||||
|
binding_status: BOUND
|
||||||
|
|
||||||
|
inline_code_receipt_ref:
|
||||||
|
asset_id: RECEIPT-S2_00-INLINE-CODE
|
||||||
|
path: manifest/s2_00_inline_code_receipt.json
|
||||||
|
sha256: ce82890b543315f1dc8a0793c68e35c19e6975e56f90c97715136462a1b72ecb
|
||||||
|
schema_id: stage2_s2_00_inline_code_receipt.v1
|
||||||
|
binding_status: BOUND
|
||||||
|
|
||||||
|
stage2_release_ref:
|
||||||
|
asset_id: RELEASE-STAGE2-CLEAN
|
||||||
|
path: manifest/stage2_release.json
|
||||||
|
sha256: 5c892cce3e686dea27c249b1585f86e44b60eb2cda074740d0d8c391095655e2
|
||||||
|
schema_id: stage2_release.v1
|
||||||
|
binding_status: BOUND
|
||||||
|
|
||||||
|
expected_release_sha256: 5c892cce3e686dea27c249b1585f86e44b60eb2cda074740d0d8c391095655e2
|
||||||
|
agent_script_sha256: 2e0bf36786a64d74070f8cc97dfde05e9d17c7d7bef3a5b1e60aecdb0accc71d
|
||||||
|
canonical_code_sha256: 269d775690beb806a1e922b71909448b1f11a10f344e6a2d064295bf80ae1c10
|
||||||
|
|
||||||
|
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_00_request.json
|
||||||
|
read_path_allowlist:
|
||||||
|
- stage2_control/s2_00_request.json
|
||||||
|
- Default_Agent/Stage_2_Clean/manifest/stage2_release.json
|
||||||
|
- Default_Agent/Stage_2_Clean/manifest/module_manifest.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/review_status.schema.json
|
||||||
|
- Default_Agent/Stage_2_Clean/<release-bound-stage2-direct-path>
|
||||||
|
- <stage1_run_root_ref>/<release-bound-stage1-source-path>
|
||||||
|
- <stage1_deployment_root_ref>/<release-bound-stage1-dependency-path>
|
||||||
|
- stage2_runs/by-binding/<run_binding_digest>/<published-artifact-path>
|
||||||
|
write_root_rule: stage2_runs/by-binding/<run_binding_digest>/
|
||||||
|
|
||||||
|
egress_profile_id: S2_00_LOCALDOCS_ONLY_V1
|
||||||
|
egress_profile_status: PENDING_LIVE_VERIFICATION
|
||||||
|
|
||||||
|
downstream_llm_candidate_bindings:
|
||||||
|
- workflow_id: S2_10
|
||||||
|
provider: openai
|
||||||
|
model: gpt-5.6-sol
|
||||||
|
reasoning_effort: ultra
|
||||||
|
binding_status: CANDIDATE_PENDING_BENCHMARK_AND_LEGAL_REVIEW
|
||||||
|
runtime_activation_allowed: false
|
||||||
|
required_admission_evidence:
|
||||||
|
- PHASE4_REPRESENTATIVE_BENCHMARK_PASS
|
||||||
|
- KOREAN_LAWYER_BLIND_REVIEW_PASS
|
||||||
|
- SIGNED_RELEASE_BINDING
|
||||||
|
|
||||||
|
live_admission_status: PENDING_SECRET_BINDING
|
||||||
|
legacy_fallbacks: []
|
||||||
+18
-119
@@ -1,119 +1,18 @@
|
|||||||
{
|
schema_version: stage2_loader_binding.reference.v1
|
||||||
"schema_version": "stage2_loader_binding.v1",
|
binding_id: S2-BINDING-S2_00-V1
|
||||||
"binding_id": "S2-BINDING-S2_00-V1",
|
workflow_id: S2_00
|
||||||
"workflow_id": "S2_00",
|
disposition: REFERENCE_ONLY_SUPERSEDED_FOR_S2_00
|
||||||
"execution_class": "NON-LLM-DETERMINISTIC",
|
active_runtime_authority: false
|
||||||
"workflow_ref": {
|
invocation_allowed: false
|
||||||
"asset_id": "WF-S2_00",
|
fallback_allowed: false
|
||||||
"path": "workflows/S2_00_stage1_ingress_normalize_and_bundle_compile.yml",
|
superseded_by:
|
||||||
"sha256": "f554db71899dbe21009628275cdcea763bfa654ca5537c1f03605a5381ef417b",
|
asset_id: BINDING-S2_00-CODE-EXECUTOR
|
||||||
"schema_id": "stage2_workflow_contract.v1",
|
path: deployment/stage2_code_executor_binding.yml
|
||||||
"binding_status": "BOUND"
|
schema_id: stage2_code_executor_binding.v1
|
||||||
},
|
binding_status: EXTERNAL_TRUST_ROOT_PENDING_LIVE_ADMISSION
|
||||||
"loader_ref": {
|
retained_legacy_refs:
|
||||||
"asset_id": "LOADER-STAGE2",
|
- release_ops/stage2_loader.py
|
||||||
"path": "release_ops/stage2_loader.py",
|
- release_ops/stage2_loader.txt
|
||||||
"sha256": "bd961b79117240bfb2985fa71e84d24cabfd66fb4eabd55b03f80d6ce11698d2",
|
- runtime/s2_00_ingress.py
|
||||||
"schema_id": null,
|
- runtime/s2_00_ingress.txt
|
||||||
"binding_status": "BOUND"
|
reason_code: O-06_SUPERSEDED_NOT_APPLICABLE_BY_DIRECT_MCP_CODE_EXECUTOR
|
||||||
},
|
|
||||||
"runtime_ref": {
|
|
||||||
"asset_id": "RUNTIME-S2_00",
|
|
||||||
"path": "runtime/s2_00_ingress.py",
|
|
||||||
"sha256": "3abbc90b23b9ab7893addffc30302c36483d256e105712508f7f5e901a5f9f2e",
|
|
||||||
"schema_id": null,
|
|
||||||
"binding_status": "BOUND"
|
|
||||||
},
|
|
||||||
"schema_refs": [
|
|
||||||
{
|
|
||||||
"asset_id": "SCHEMA-INGRESS",
|
|
||||||
"path": "schemas/ingress.schema.json",
|
|
||||||
"sha256": "b0d77e9833ec498e1c633cea7c7dca0ab8e171e30e18dc27bf6774f3975559a6",
|
|
||||||
"schema_id": "https://schemas.liti-agent.local/stage2/s2_00/ingress.schema.v1.json",
|
|
||||||
"binding_status": "BOUND"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"asset_id": "SCHEMA-CONTEXT",
|
|
||||||
"path": "schemas/context.schema.json",
|
|
||||||
"sha256": "378a7b4e0b8bc3315deb83bd58d2f20db830b82e22c685224b4f02c80f6923b9",
|
|
||||||
"schema_id": "https://schemas.liti-agent.local/stage2/s2_00/context.schema.v1.json",
|
|
||||||
"binding_status": "BOUND"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"asset_id": "SCHEMA-DEPLOYMENT",
|
|
||||||
"path": "schemas/deployment.schema.json",
|
|
||||||
"sha256": "9ca010e4c1e42ea6150fc1caa8ec33b4b98224d1b947e1e51925b0766325c73f",
|
|
||||||
"schema_id": "https://schemas.liti-agent.local/stage2/shared/deployment.schema.v1.json",
|
|
||||||
"binding_status": "BOUND"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"asset_id": "SCHEMA-REVIEW-STATUS",
|
|
||||||
"path": "schemas/review_status.schema.json",
|
|
||||||
"sha256": "edcdd127e648c6b045abb852146a63f19936d300a350023b8987700afa05fde7",
|
|
||||||
"schema_id": "https://schemas.liti-agent.local/stage2/shared/review_status.schema.v1.json",
|
|
||||||
"binding_status": "BOUND"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"module_manifest_ref": {
|
|
||||||
"asset_id": "MANIFEST-MODULE",
|
|
||||||
"path": "manifest/module_manifest.json",
|
|
||||||
"sha256": "PENDING_SEQUENTIAL_BIND",
|
|
||||||
"schema_id": "stage2_module_manifest.v1",
|
|
||||||
"binding_status": "PENDING_SEQUENTIAL_BIND"
|
|
||||||
},
|
|
||||||
"stage2_release_ref": {
|
|
||||||
"asset_id": "MANIFEST-STAGE2-RELEASE",
|
|
||||||
"path": "manifest/stage2_release.json",
|
|
||||||
"sha256": "PENDING_SEQUENTIAL_BIND",
|
|
||||||
"schema_id": "stage2_release.v1",
|
|
||||||
"binding_status": "PENDING_SEQUENTIAL_BIND"
|
|
||||||
},
|
|
||||||
"fixed_argv_contract": {
|
|
||||||
"executable_source": "sys.executable",
|
|
||||||
"runtime_entrypoint": "runtime/s2_00_ingress.py",
|
|
||||||
"allowed_flags": [
|
|
||||||
"--workflow-id",
|
|
||||||
"--release-ref",
|
|
||||||
"--run-id",
|
|
||||||
"--attempt-id",
|
|
||||||
"--user-context-sha256",
|
|
||||||
"--workspace-context-sha256",
|
|
||||||
"--stage1-run-root-ref",
|
|
||||||
"--stage1-deployment-root-ref"
|
|
||||||
],
|
|
||||||
"positional_argument_count": 0,
|
|
||||||
"shell": false,
|
|
||||||
"arbitrary_command_allowed": false,
|
|
||||||
"arbitrary_path_allowed": false
|
|
||||||
},
|
|
||||||
"workspace_root_policy": {
|
|
||||||
"authoring_root_source": "loader_file_parent_parent",
|
|
||||||
"resolved_root_must_equal_loader_parent": true,
|
|
||||||
"symlink_escape_allowed": false,
|
|
||||||
"runtime_output_root_argument_allowed": false,
|
|
||||||
"user_workspace_hashes_required": true
|
|
||||||
},
|
|
||||||
"authorization_policy": {
|
|
||||||
"skip_confirm_owned_by_host": true,
|
|
||||||
"loader_bypass_allowed": false,
|
|
||||||
"canary_production_signed_admission_required": true
|
|
||||||
},
|
|
||||||
"downstream_llm_candidate_bindings": [
|
|
||||||
{
|
|
||||||
"workflow_id": "S2_10",
|
|
||||||
"provider": "openai",
|
|
||||||
"model": "gpt-5.6-sol",
|
|
||||||
"reasoning_effort": "ultra",
|
|
||||||
"binding_status": "CANDIDATE_PENDING_BENCHMARK_AND_LEGAL_REVIEW",
|
|
||||||
"runtime_activation_allowed": false,
|
|
||||||
"required_admission_evidence": [
|
|
||||||
"PHASE4_REPRESENTATIVE_BENCHMARK_PASS",
|
|
||||||
"KOREAN_LAWYER_BLIND_REVIEW_PASS",
|
|
||||||
"SIGNED_RELEASE_BINDING"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"invocation_status": "OPEN_EXTERNAL_BACKEND",
|
|
||||||
"legacy_fallbacks": [],
|
|
||||||
"external_network_access_allowed": false
|
|
||||||
}
|
|
||||||
|
|||||||
+119
-170
@@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"schema_version": "stage2_module_manifest.v1",
|
"schema_version": "stage2_module_manifest.v1",
|
||||||
"manifest_id": "STAGE2-CLEAN-S2_00-SHARED-DRAFT-V1",
|
"manifest_id": "STAGE2-CLEAN-S2_00-SHARED-DRAFT-V1-INLINE",
|
||||||
"manifest_scope": "S2_00_AND_SHARED_HANDOFF_DRAFT",
|
"manifest_scope": "S2_00_AND_SHARED_HANDOFF_DRAFT",
|
||||||
"manifest_status": "DEV_HASH_BOUND_PENDING_CROSS_AUDIT",
|
"manifest_status": "DEV_HASH_BOUND_INLINE_PENDING_LIVE_ADMISSION",
|
||||||
"manifest_digest": "7aa491d3e053e8dba9325b0ed06ea1c9e5e9a9f3c82f4b4340a04bd8b8c944ba",
|
"manifest_digest": "de7a70dae9773a8c3b348c6cd8861f858ce10d9438c66b8ce209a3c34ce8deda",
|
||||||
"manifest_digest_contract": {
|
"manifest_digest_contract": {
|
||||||
"algorithm_id": "STAGE2-MODULE-MANIFEST-DIGEST-V1",
|
"algorithm_id": "STAGE2-MODULE-MANIFEST-DIGEST-V1",
|
||||||
"digest_algorithm": "sha256",
|
"digest_algorithm": "sha256",
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "LEGACY-STAGE2-LOADER",
|
"code": "LEGACY-STAGE2-LOADER",
|
||||||
"meaning": "Only the canonical Stage_2_Clean loader and binding may cross the host trust boundary.",
|
"meaning": "The historical host loader and loader binding are reference-only and cannot authorize, execute, or provide fallback for S2_00.",
|
||||||
"legacy_runtime_dependency_allowed": false
|
"legacy_runtime_dependency_allowed": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -62,7 +62,6 @@
|
|||||||
"produced_schema_ids": [],
|
"produced_schema_ids": [],
|
||||||
"authority_ids": [],
|
"authority_ids": [],
|
||||||
"dependency_module_ids": [
|
"dependency_module_ids": [
|
||||||
"RUNTIME-S2_00",
|
|
||||||
"SCHEMA-INGRESS",
|
"SCHEMA-INGRESS",
|
||||||
"SCHEMA-CONTEXT",
|
"SCHEMA-CONTEXT",
|
||||||
"SCHEMA-REVIEW-STATUS"
|
"SCHEMA-REVIEW-STATUS"
|
||||||
@@ -72,10 +71,10 @@
|
|||||||
"LEGACY-STAGE2-V0-V3"
|
"LEGACY-STAGE2-V0-V3"
|
||||||
],
|
],
|
||||||
"path": "workflows/S2_00_stage1_ingress_normalize_and_bundle_compile.yml",
|
"path": "workflows/S2_00_stage1_ingress_normalize_and_bundle_compile.yml",
|
||||||
"sha256": "f554db71899dbe21009628275cdcea763bfa654ca5537c1f03605a5381ef417b",
|
"sha256": "74f8844963952ca853286662e46ab4c7b24c5785faf521c0557548778c446cea",
|
||||||
"schema_version": "stage2_workflow_contract.v1",
|
"schema_version": "stage2_workflow_contract.v1",
|
||||||
"asset_version": "s2_00.1",
|
"asset_version": "s2_00.1.1",
|
||||||
"implementation_status": "DEV_HASH_BOUND",
|
"implementation_status": "IMPLEMENTED_DECLARATIVE_CONTRACT",
|
||||||
"semantic_review_status": "PENDING_CROSS_AUDIT",
|
"semantic_review_status": "PENDING_CROSS_AUDIT",
|
||||||
"owner": "Stage_2_workflow_maintainer",
|
"owner": "Stage_2_workflow_maintainer",
|
||||||
"inputs": [
|
"inputs": [
|
||||||
@@ -87,48 +86,6 @@
|
|||||||
"S2_00-DIAGNOSTIC-BRANCH"
|
"S2_00-DIAGNOSTIC-BRANCH"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"module_id": "RUNTIME-S2_00",
|
|
||||||
"module_kind": "FIXED_RUNTIME",
|
|
||||||
"scope_predicate": "workflow_id == S2_00",
|
|
||||||
"consumed_schema_ids": [
|
|
||||||
"https://schemas.liti-agent.local/stage2/s2_00/ingress.schema.v1.json",
|
|
||||||
"https://schemas.liti-agent.local/stage2/s2_00/context.schema.v1.json",
|
|
||||||
"https://schemas.liti-agent.local/stage2/shared/review_status.schema.v1.json"
|
|
||||||
],
|
|
||||||
"produced_schema_ids": [
|
|
||||||
"stage2_s2_00_stage1_input_manifest.v1",
|
|
||||||
"stage2_s2_00_intake_report.v1",
|
|
||||||
"stage2_s2_00_ingress_status.v1",
|
|
||||||
"stage2_s2_00_context.v1",
|
|
||||||
"stage2_issue_ledger_base.v1"
|
|
||||||
],
|
|
||||||
"authority_ids": [],
|
|
||||||
"dependency_module_ids": [
|
|
||||||
"SCHEMA-INGRESS",
|
|
||||||
"SCHEMA-CONTEXT",
|
|
||||||
"SCHEMA-REVIEW-STATUS"
|
|
||||||
],
|
|
||||||
"incompatible_module_ids": [],
|
|
||||||
"forbidden_contract_codes": [
|
|
||||||
"LEGACY-STAGE2-V0-V3"
|
|
||||||
],
|
|
||||||
"path": "runtime/s2_00_ingress.py",
|
|
||||||
"sha256": "3abbc90b23b9ab7893addffc30302c36483d256e105712508f7f5e901a5f9f2e",
|
|
||||||
"schema_version": "fixed_python_runtime.v1",
|
|
||||||
"asset_version": "s2_00.runtime.1",
|
|
||||||
"implementation_status": "DEV_HASH_BOUND",
|
|
||||||
"semantic_review_status": "PENDING_CROSS_AUDIT",
|
|
||||||
"owner": "Stage_2_runtime_maintainer",
|
|
||||||
"inputs": [
|
|
||||||
"STAGE1-ALLOWLIST-V1",
|
|
||||||
"STAGE2-RELEASE-V1"
|
|
||||||
],
|
|
||||||
"outputs": [
|
|
||||||
"S2_00-NORMAL-BRANCH",
|
|
||||||
"S2_00-DIAGNOSTIC-BRANCH"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"module_id": "SCHEMA-INGRESS",
|
"module_id": "SCHEMA-INGRESS",
|
||||||
"module_kind": "JSON_SCHEMA",
|
"module_kind": "JSON_SCHEMA",
|
||||||
@@ -141,9 +98,9 @@
|
|||||||
"dependency_module_ids": [],
|
"dependency_module_ids": [],
|
||||||
"incompatible_module_ids": [],
|
"incompatible_module_ids": [],
|
||||||
"path": "schemas/ingress.schema.json",
|
"path": "schemas/ingress.schema.json",
|
||||||
"sha256": "b0d77e9833ec498e1c633cea7c7dca0ab8e171e30e18dc27bf6774f3975559a6",
|
"sha256": "8fd7b76015b7d7dfbbe056e08999dda7ea8e1012bb16cec50634b08f7ea97302",
|
||||||
"schema_version": "stage2_s2_00_ingress.v1",
|
"schema_version": "stage2_s2_00_ingress.v1",
|
||||||
"asset_version": "s2_00.schema.1",
|
"asset_version": "s2_00.schema.1.1",
|
||||||
"implementation_status": "DEV_HASH_BOUND",
|
"implementation_status": "DEV_HASH_BOUND",
|
||||||
"semantic_review_status": "PENDING_CROSS_AUDIT",
|
"semantic_review_status": "PENDING_CROSS_AUDIT",
|
||||||
"owner": "Stage_2_schema_owner",
|
"owner": "Stage_2_schema_owner",
|
||||||
@@ -191,9 +148,9 @@
|
|||||||
"dependency_module_ids": [],
|
"dependency_module_ids": [],
|
||||||
"incompatible_module_ids": [],
|
"incompatible_module_ids": [],
|
||||||
"path": "schemas/deployment.schema.json",
|
"path": "schemas/deployment.schema.json",
|
||||||
"sha256": "9ca010e4c1e42ea6150fc1caa8ec33b4b98224d1b947e1e51925b0766325c73f",
|
"sha256": "361f894e9ef91957b9c0613e88d0a31c2fb65ec40c8a0c1da46df006b3eca460",
|
||||||
"schema_version": "stage2_deployment.v1",
|
"schema_version": "stage2_deployment.v1",
|
||||||
"asset_version": "stage2.shared.1",
|
"asset_version": "stage2.shared.1.1",
|
||||||
"implementation_status": "DEV_HASH_BOUND",
|
"implementation_status": "DEV_HASH_BOUND",
|
||||||
"semantic_review_status": "PENDING_CROSS_AUDIT",
|
"semantic_review_status": "PENDING_CROSS_AUDIT",
|
||||||
"owner": "Stage_2_deployment_schema_owner",
|
"owner": "Stage_2_deployment_schema_owner",
|
||||||
@@ -225,73 +182,6 @@
|
|||||||
"REVIEW-STATUS-SCHEMA-DEFS"
|
"REVIEW-STATUS-SCHEMA-DEFS"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"module_id": "LOADER-STAGE2",
|
|
||||||
"module_kind": "HOST_TRUST_BOUNDARY",
|
|
||||||
"scope_predicate": "workflow_id == S2_00",
|
|
||||||
"consumed_schema_ids": [
|
|
||||||
"https://schemas.liti-agent.local/stage2/shared/deployment.schema.v1.json"
|
|
||||||
],
|
|
||||||
"produced_schema_ids": [
|
|
||||||
"stage2_loader_receipt.v1"
|
|
||||||
],
|
|
||||||
"authority_ids": [],
|
|
||||||
"dependency_module_ids": [
|
|
||||||
"BINDING-S2_00",
|
|
||||||
"RUNTIME-S2_00",
|
|
||||||
"SCHEMA-DEPLOYMENT"
|
|
||||||
],
|
|
||||||
"incompatible_module_ids": [],
|
|
||||||
"forbidden_contract_codes": [
|
|
||||||
"LEGACY-STAGE2-LOADER"
|
|
||||||
],
|
|
||||||
"path": "release_ops/stage2_loader.py",
|
|
||||||
"sha256": "bd961b79117240bfb2985fa71e84d24cabfd66fb4eabd55b03f80d6ce11698d2",
|
|
||||||
"schema_version": "stage2_loader.v1",
|
|
||||||
"asset_version": "stage2.loader.1",
|
|
||||||
"implementation_status": "DEV_HASH_BOUND",
|
|
||||||
"semantic_review_status": "PENDING_CROSS_AUDIT",
|
|
||||||
"owner": "Stage_2_loader_owner",
|
|
||||||
"inputs": [
|
|
||||||
"STAGE2-RELEASE-V1",
|
|
||||||
"BINDING-S2_00"
|
|
||||||
],
|
|
||||||
"outputs": [
|
|
||||||
"STAGE2-LOADER-RECEIPT"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_id": "BINDING-S2_00",
|
|
||||||
"module_kind": "DEPLOYMENT_BINDING",
|
|
||||||
"scope_predicate": "workflow_id == S2_00",
|
|
||||||
"consumed_schema_ids": [
|
|
||||||
"https://schemas.liti-agent.local/stage2/shared/deployment.schema.v1.json"
|
|
||||||
],
|
|
||||||
"produced_schema_ids": [],
|
|
||||||
"authority_ids": [],
|
|
||||||
"dependency_module_ids": [
|
|
||||||
"WF-S2_00",
|
|
||||||
"RUNTIME-S2_00",
|
|
||||||
"SCHEMA-DEPLOYMENT"
|
|
||||||
],
|
|
||||||
"incompatible_module_ids": [],
|
|
||||||
"forbidden_contract_codes": [
|
|
||||||
"LEGACY-STAGE2-LOADER"
|
|
||||||
],
|
|
||||||
"path": "deployment/stage2_loader_binding.yml",
|
|
||||||
"sha256": "d740f73274f39c5196c48e2bd3760ccd2347eadf8c59938fbe84a9f56ccb16f1",
|
|
||||||
"schema_version": "stage2_loader_binding.v1",
|
|
||||||
"asset_version": "stage2.binding.1",
|
|
||||||
"implementation_status": "DEV_HASH_BOUND_WITH_RELEASE_ORACLE_REFS_DEFERRED",
|
|
||||||
"semantic_review_status": "PENDING_CROSS_AUDIT",
|
|
||||||
"owner": "Stage_2_loader_owner",
|
|
||||||
"inputs": [
|
|
||||||
"STAGE2-RELEASE-V1"
|
|
||||||
],
|
|
||||||
"outputs": [
|
|
||||||
"FIXED-ARGV-BINDING"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"module_id": "CACHE-POLICY-STATIC-PREFIX",
|
"module_id": "CACHE-POLICY-STATIC-PREFIX",
|
||||||
"module_kind": "CACHE_POLICY",
|
"module_kind": "CACHE_POLICY",
|
||||||
@@ -302,8 +192,7 @@
|
|||||||
"dependency_module_ids": [
|
"dependency_module_ids": [
|
||||||
"P00",
|
"P00",
|
||||||
"P10",
|
"P10",
|
||||||
"SCHEMA-CONTEXT",
|
"SCHEMA-CONTEXT"
|
||||||
"BINDING-S2_00"
|
|
||||||
],
|
],
|
||||||
"incompatible_module_ids": [],
|
"incompatible_module_ids": [],
|
||||||
"path": "registry/cache/prompt_cache_policy.yml",
|
"path": "registry/cache/prompt_cache_policy.yml",
|
||||||
@@ -334,9 +223,9 @@
|
|||||||
"dependency_module_ids": [],
|
"dependency_module_ids": [],
|
||||||
"incompatible_module_ids": [],
|
"incompatible_module_ids": [],
|
||||||
"path": "tests/fixtures/regression_manifest.json",
|
"path": "tests/fixtures/regression_manifest.json",
|
||||||
"sha256": "7dc2b23309537f3f71dac0774f86cf918fed82fcbbc89b2f38399488e31f8043",
|
"sha256": "d367ad04364c3881bfdc48ac1b4ccf36c2a5a68b213d7e95d8874d17887aad40",
|
||||||
"schema_version": "stage2_s2_00_regression_manifest.v1",
|
"schema_version": "stage2_s2_00_regression_manifest.v1",
|
||||||
"asset_version": "s2_00.test.1",
|
"asset_version": "s2_00.test.1.1",
|
||||||
"implementation_status": "DEV_HASH_BOUND",
|
"implementation_status": "DEV_HASH_BOUND",
|
||||||
"semantic_review_status": "PENDING_CROSS_AUDIT",
|
"semantic_review_status": "PENDING_CROSS_AUDIT",
|
||||||
"owner": "Stage_2_regression_owner",
|
"owner": "Stage_2_regression_owner",
|
||||||
@@ -359,7 +248,6 @@
|
|||||||
],
|
],
|
||||||
"authority_ids": [],
|
"authority_ids": [],
|
||||||
"dependency_module_ids": [
|
"dependency_module_ids": [
|
||||||
"BINDING-S2_00",
|
|
||||||
"CACHE-POLICY-STATIC-PREFIX",
|
"CACHE-POLICY-STATIC-PREFIX",
|
||||||
"P00",
|
"P00",
|
||||||
"P10",
|
"P10",
|
||||||
@@ -371,9 +259,9 @@
|
|||||||
"RAW-STAGE1-REREAD"
|
"RAW-STAGE1-REREAD"
|
||||||
],
|
],
|
||||||
"path": "workflows/S2_10_domain_relief_resolution_map.yml",
|
"path": "workflows/S2_10_domain_relief_resolution_map.yml",
|
||||||
"sha256": "e6d59ce93edb0c5854052e2d521a4c122cf9feda24d35e7344d101c2c29f4d26",
|
"sha256": "fb181fedd5ceb1e5503203ef05dfc361c20ff101e050930c2648d95a4e8760da",
|
||||||
"schema_version": "stage2_workflow_contract.v1",
|
"schema_version": "stage2_workflow_contract.v1",
|
||||||
"asset_version": "s2_10.1",
|
"asset_version": "s2_10.1.1",
|
||||||
"implementation_status": "DRAFT_HANDOFF_STUB_HASH_BOUND",
|
"implementation_status": "DRAFT_HANDOFF_STUB_HASH_BOUND",
|
||||||
"semantic_review_status": "PENDING_CROSS_AUDIT",
|
"semantic_review_status": "PENDING_CROSS_AUDIT",
|
||||||
"owner": "Stage_2_S2_10_owner",
|
"owner": "Stage_2_S2_10_owner",
|
||||||
@@ -432,12 +320,10 @@
|
|||||||
"consumed_schema_ids": [],
|
"consumed_schema_ids": [],
|
||||||
"produced_schema_ids": [],
|
"produced_schema_ids": [],
|
||||||
"authority_ids": [],
|
"authority_ids": [],
|
||||||
"dependency_module_ids": [
|
"dependency_module_ids": [],
|
||||||
"RUNTIME-S2_00"
|
|
||||||
],
|
|
||||||
"incompatible_module_ids": [],
|
"incompatible_module_ids": [],
|
||||||
"path": "tests/s2_00/test_resolver_source_lock.py",
|
"path": "tests/s2_00/test_resolver_source_lock.py",
|
||||||
"sha256": "235e05985fb4b574c59cc44ff1cc803f4ec064d7f0fdb7fcf8c694e93d445fe0",
|
"sha256": "51bc1b310d40f5068df0a66446e5b96b00ea9b5b0137ddaf563caadf9f8fc51e",
|
||||||
"schema_version": "pytest.v1",
|
"schema_version": "pytest.v1",
|
||||||
"asset_version": "s2_00.test.1",
|
"asset_version": "s2_00.test.1",
|
||||||
"implementation_status": "DEV_HASH_BOUND",
|
"implementation_status": "DEV_HASH_BOUND",
|
||||||
@@ -457,9 +343,7 @@
|
|||||||
"consumed_schema_ids": [],
|
"consumed_schema_ids": [],
|
||||||
"produced_schema_ids": [],
|
"produced_schema_ids": [],
|
||||||
"authority_ids": [],
|
"authority_ids": [],
|
||||||
"dependency_module_ids": [
|
"dependency_module_ids": [],
|
||||||
"RUNTIME-S2_00"
|
|
||||||
],
|
|
||||||
"incompatible_module_ids": [],
|
"incompatible_module_ids": [],
|
||||||
"path": "tests/s2_00/test_schema_hash_and_signals.py",
|
"path": "tests/s2_00/test_schema_hash_and_signals.py",
|
||||||
"sha256": "71a9a15e5910d99c8e8e7b8b2f607b41244b3fd83a8c6e07d03bc0a0b986193c",
|
"sha256": "71a9a15e5910d99c8e8e7b8b2f607b41244b3fd83a8c6e07d03bc0a0b986193c",
|
||||||
@@ -482,9 +366,7 @@
|
|||||||
"consumed_schema_ids": [],
|
"consumed_schema_ids": [],
|
||||||
"produced_schema_ids": [],
|
"produced_schema_ids": [],
|
||||||
"authority_ids": [],
|
"authority_ids": [],
|
||||||
"dependency_module_ids": [
|
"dependency_module_ids": [],
|
||||||
"RUNTIME-S2_00"
|
|
||||||
],
|
|
||||||
"incompatible_module_ids": [],
|
"incompatible_module_ids": [],
|
||||||
"path": "tests/s2_00/test_conservation.py",
|
"path": "tests/s2_00/test_conservation.py",
|
||||||
"sha256": "c906dff7e11678a60556818d1baf24e66844d9c982c9d333ec339d2ed9ca6061",
|
"sha256": "c906dff7e11678a60556818d1baf24e66844d9c982c9d333ec339d2ed9ca6061",
|
||||||
@@ -507,9 +389,7 @@
|
|||||||
"consumed_schema_ids": [],
|
"consumed_schema_ids": [],
|
||||||
"produced_schema_ids": [],
|
"produced_schema_ids": [],
|
||||||
"authority_ids": [],
|
"authority_ids": [],
|
||||||
"dependency_module_ids": [
|
"dependency_module_ids": [],
|
||||||
"RUNTIME-S2_00"
|
|
||||||
],
|
|
||||||
"incompatible_module_ids": [],
|
"incompatible_module_ids": [],
|
||||||
"path": "tests/s2_00/test_canonical_ids.py",
|
"path": "tests/s2_00/test_canonical_ids.py",
|
||||||
"sha256": "c463506a90859daeef7eafddc4a6c08c563e2ae73200d921c1f00efb7bc24361",
|
"sha256": "c463506a90859daeef7eafddc4a6c08c563e2ae73200d921c1f00efb7bc24361",
|
||||||
@@ -532,12 +412,10 @@
|
|||||||
"consumed_schema_ids": [],
|
"consumed_schema_ids": [],
|
||||||
"produced_schema_ids": [],
|
"produced_schema_ids": [],
|
||||||
"authority_ids": [],
|
"authority_ids": [],
|
||||||
"dependency_module_ids": [
|
"dependency_module_ids": [],
|
||||||
"RUNTIME-S2_00"
|
|
||||||
],
|
|
||||||
"incompatible_module_ids": [],
|
"incompatible_module_ids": [],
|
||||||
"path": "tests/s2_00/test_cluster_bundle_compile.py",
|
"path": "tests/s2_00/test_cluster_bundle_compile.py",
|
||||||
"sha256": "8e37dda051562b4c1a3fc595a1678fc7758742f89b19cfe8f9d5a0b342dfa5d1",
|
"sha256": "c2b01c8d39be31d43b11f13a9901db036ef967337a388a4b85d951fbb4f51efe",
|
||||||
"schema_version": "pytest.v1",
|
"schema_version": "pytest.v1",
|
||||||
"asset_version": "s2_00.test.1",
|
"asset_version": "s2_00.test.1",
|
||||||
"implementation_status": "DEV_HASH_BOUND",
|
"implementation_status": "DEV_HASH_BOUND",
|
||||||
@@ -557,12 +435,10 @@
|
|||||||
"consumed_schema_ids": [],
|
"consumed_schema_ids": [],
|
||||||
"produced_schema_ids": [],
|
"produced_schema_ids": [],
|
||||||
"authority_ids": [],
|
"authority_ids": [],
|
||||||
"dependency_module_ids": [
|
"dependency_module_ids": [],
|
||||||
"RUNTIME-S2_00"
|
|
||||||
],
|
|
||||||
"incompatible_module_ids": [],
|
"incompatible_module_ids": [],
|
||||||
"path": "tests/s2_00/test_failure_and_atomic_publish.py",
|
"path": "tests/s2_00/test_failure_and_atomic_publish.py",
|
||||||
"sha256": "bb7879a23c207bba304cae1eefe89a8f0b7b9263787c80c3fbcea523300f0fb8",
|
"sha256": "4bc5060ce95ec884c1193f16e8348d095c1b1a89181b0455ad809b2bb05cfb0c",
|
||||||
"schema_version": "pytest.v1",
|
"schema_version": "pytest.v1",
|
||||||
"asset_version": "s2_00.test.1",
|
"asset_version": "s2_00.test.1",
|
||||||
"implementation_status": "DEV_HASH_BOUND",
|
"implementation_status": "DEV_HASH_BOUND",
|
||||||
@@ -1790,22 +1666,82 @@
|
|||||||
"outputs": [
|
"outputs": [
|
||||||
"ACTIVE_PROFILE_CONTEXT"
|
"ACTIVE_PROFILE_CONTEXT"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"module_id": "BUILD-S2_00-INLINE-PROJECTION",
|
||||||
|
"module_kind": "BUILD_TOOL",
|
||||||
|
"scope_predicate": "build_target == S2_00_INLINE_AGENT",
|
||||||
|
"consumed_schema_ids": [
|
||||||
|
"https://schemas.liti-agent.local/stage2/shared/deployment.schema.v1.json"
|
||||||
|
],
|
||||||
|
"produced_schema_ids": [
|
||||||
|
"stage2_s2_00_inline_code_receipt.v1"
|
||||||
|
],
|
||||||
|
"authority_ids": [],
|
||||||
|
"dependency_module_ids": [
|
||||||
|
"SCHEMA-DEPLOYMENT"
|
||||||
|
],
|
||||||
|
"incompatible_module_ids": [],
|
||||||
|
"forbidden_contract_codes": [
|
||||||
|
"LEGACY-STAGE2-V0-V3"
|
||||||
|
],
|
||||||
|
"path": "offline_build/build_s2_00_inline_projection.py",
|
||||||
|
"sha256": "930f058f99e76b63b2c4fc8921892e40bfdcca7df51c2cb0a6aff34d721c0e10",
|
||||||
|
"schema_version": "offline_projection_builder.v1",
|
||||||
|
"asset_version": "s2_00.build.1",
|
||||||
|
"implementation_status": "DEV_HASH_BOUND_OFFLINE_ONLY",
|
||||||
|
"semantic_review_status": "PENDING_CROSS_AUDIT",
|
||||||
|
"owner": "Stage_2_release_build_owner",
|
||||||
|
"inputs": [
|
||||||
|
"AUTHORING-AGENT-YAML"
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
"DEPLOYMENT-AGENT-PROJECTION",
|
||||||
|
"FULL-CODE-MIRRORS",
|
||||||
|
"INLINE-CODE-RECEIPT"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"module_id": "TEST-S2_00-INLINE-CODE-PARITY",
|
||||||
|
"module_kind": "TEST",
|
||||||
|
"scope_predicate": "test_axis == inline_code_parity",
|
||||||
|
"consumed_schema_ids": [
|
||||||
|
"https://schemas.liti-agent.local/stage2/shared/deployment.schema.v1.json"
|
||||||
|
],
|
||||||
|
"produced_schema_ids": [],
|
||||||
|
"authority_ids": [],
|
||||||
|
"dependency_module_ids": [
|
||||||
|
"BUILD-S2_00-INLINE-PROJECTION",
|
||||||
|
"SCHEMA-DEPLOYMENT",
|
||||||
|
"WF-S2_00"
|
||||||
|
],
|
||||||
|
"incompatible_module_ids": [],
|
||||||
|
"forbidden_contract_codes": [
|
||||||
|
"LEGACY-STAGE2-V0-V3"
|
||||||
|
],
|
||||||
|
"path": "tests/s2_00/test_inline_code_parity.py",
|
||||||
|
"sha256": "6a03a8e67661c5599925c0fa01b4a79602d4b745936f783d5b68617ec2c4633e",
|
||||||
|
"schema_version": "pytest.v1",
|
||||||
|
"asset_version": "s2_00.test.1",
|
||||||
|
"implementation_status": "DEV_HASH_BOUND",
|
||||||
|
"semantic_review_status": "PENDING_CROSS_AUDIT",
|
||||||
|
"owner": "Stage_2_test_owner",
|
||||||
|
"inputs": [
|
||||||
|
"AUTHORING-AGENT-YAML",
|
||||||
|
"DEPLOYMENT-AGENT-PROJECTION",
|
||||||
|
"FULL-CODE-MIRRORS"
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
"TEST-RECEIPT"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"documentation_mirrors": [
|
"documentation_mirrors": [
|
||||||
{
|
{
|
||||||
"source_path": "release_ops/stage2_loader.py",
|
"source_path": "offline_build/build_s2_00_inline_projection.py",
|
||||||
"mirror_path": "release_ops/stage2_loader.txt",
|
"mirror_path": "offline_build/build_s2_00_inline_projection.txt",
|
||||||
"source_sha256": "bd961b79117240bfb2985fa71e84d24cabfd66fb4eabd55b03f80d6ce11698d2",
|
"source_sha256": "930f058f99e76b63b2c4fc8921892e40bfdcca7df51c2cb0a6aff34d721c0e10",
|
||||||
"mirror_sha256": "bd961b79117240bfb2985fa71e84d24cabfd66fb4eabd55b03f80d6ce11698d2",
|
"mirror_sha256": "930f058f99e76b63b2c4fc8921892e40bfdcca7df51c2cb0a6aff34d721c0e10",
|
||||||
"byte_identical": true,
|
|
||||||
"runtime_import_allowed": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source_path": "runtime/s2_00_ingress.py",
|
|
||||||
"mirror_path": "runtime/s2_00_ingress.txt",
|
|
||||||
"source_sha256": "3abbc90b23b9ab7893addffc30302c36483d256e105712508f7f5e901a5f9f2e",
|
|
||||||
"mirror_sha256": "3abbc90b23b9ab7893addffc30302c36483d256e105712508f7f5e901a5f9f2e",
|
|
||||||
"byte_identical": true,
|
"byte_identical": true,
|
||||||
"runtime_import_allowed": false
|
"runtime_import_allowed": false
|
||||||
},
|
},
|
||||||
@@ -1820,8 +1756,8 @@
|
|||||||
{
|
{
|
||||||
"source_path": "tests/s2_00/test_cluster_bundle_compile.py",
|
"source_path": "tests/s2_00/test_cluster_bundle_compile.py",
|
||||||
"mirror_path": "tests/s2_00/test_cluster_bundle_compile.txt",
|
"mirror_path": "tests/s2_00/test_cluster_bundle_compile.txt",
|
||||||
"source_sha256": "8e37dda051562b4c1a3fc595a1678fc7758742f89b19cfe8f9d5a0b342dfa5d1",
|
"source_sha256": "c2b01c8d39be31d43b11f13a9901db036ef967337a388a4b85d951fbb4f51efe",
|
||||||
"mirror_sha256": "8e37dda051562b4c1a3fc595a1678fc7758742f89b19cfe8f9d5a0b342dfa5d1",
|
"mirror_sha256": "c2b01c8d39be31d43b11f13a9901db036ef967337a388a4b85d951fbb4f51efe",
|
||||||
"byte_identical": true,
|
"byte_identical": true,
|
||||||
"runtime_import_allowed": false
|
"runtime_import_allowed": false
|
||||||
},
|
},
|
||||||
@@ -1836,16 +1772,24 @@
|
|||||||
{
|
{
|
||||||
"source_path": "tests/s2_00/test_failure_and_atomic_publish.py",
|
"source_path": "tests/s2_00/test_failure_and_atomic_publish.py",
|
||||||
"mirror_path": "tests/s2_00/test_failure_and_atomic_publish.txt",
|
"mirror_path": "tests/s2_00/test_failure_and_atomic_publish.txt",
|
||||||
"source_sha256": "bb7879a23c207bba304cae1eefe89a8f0b7b9263787c80c3fbcea523300f0fb8",
|
"source_sha256": "4bc5060ce95ec884c1193f16e8348d095c1b1a89181b0455ad809b2bb05cfb0c",
|
||||||
"mirror_sha256": "bb7879a23c207bba304cae1eefe89a8f0b7b9263787c80c3fbcea523300f0fb8",
|
"mirror_sha256": "4bc5060ce95ec884c1193f16e8348d095c1b1a89181b0455ad809b2bb05cfb0c",
|
||||||
|
"byte_identical": true,
|
||||||
|
"runtime_import_allowed": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source_path": "tests/s2_00/test_inline_code_parity.py",
|
||||||
|
"mirror_path": "tests/s2_00/test_inline_code_parity.txt",
|
||||||
|
"source_sha256": "6a03a8e67661c5599925c0fa01b4a79602d4b745936f783d5b68617ec2c4633e",
|
||||||
|
"mirror_sha256": "6a03a8e67661c5599925c0fa01b4a79602d4b745936f783d5b68617ec2c4633e",
|
||||||
"byte_identical": true,
|
"byte_identical": true,
|
||||||
"runtime_import_allowed": false
|
"runtime_import_allowed": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"source_path": "tests/s2_00/test_resolver_source_lock.py",
|
"source_path": "tests/s2_00/test_resolver_source_lock.py",
|
||||||
"mirror_path": "tests/s2_00/test_resolver_source_lock.txt",
|
"mirror_path": "tests/s2_00/test_resolver_source_lock.txt",
|
||||||
"source_sha256": "235e05985fb4b574c59cc44ff1cc803f4ec064d7f0fdb7fcf8c694e93d445fe0",
|
"source_sha256": "51bc1b310d40f5068df0a66446e5b96b00ea9b5b0137ddaf563caadf9f8fc51e",
|
||||||
"mirror_sha256": "235e05985fb4b574c59cc44ff1cc803f4ec064d7f0fdb7fcf8c694e93d445fe0",
|
"mirror_sha256": "51bc1b310d40f5068df0a66446e5b96b00ea9b5b0137ddaf563caadf9f8fc51e",
|
||||||
"byte_identical": true,
|
"byte_identical": true,
|
||||||
"runtime_import_allowed": false
|
"runtime_import_allowed": false
|
||||||
},
|
},
|
||||||
@@ -1859,14 +1803,19 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"closure_summary": {
|
"closure_summary": {
|
||||||
"module_count": 65,
|
"module_count": 64,
|
||||||
"prompt_module_count": 2,
|
"prompt_module_count": 2,
|
||||||
"profile_module_count": 45,
|
"profile_module_count": 45,
|
||||||
"test_module_count": 6,
|
"test_module_count": 7,
|
||||||
"documentation_mirror_count": 8,
|
"documentation_mirror_count": 8,
|
||||||
"fixture_closure_ref": "tests/fixtures/regression_manifest.json",
|
"fixture_closure_ref": "tests/fixtures/regression_manifest.json",
|
||||||
"fixture_descriptor_count": 60,
|
"fixture_descriptor_count": 60,
|
||||||
"self_hash_included": false,
|
"self_hash_included": false,
|
||||||
"stage2_release_hash_included": false
|
"stage2_release_hash_included": false,
|
||||||
|
"build_tool_module_count": 1,
|
||||||
|
"runtime_code_hash_included": false,
|
||||||
|
"executable_agent_hash_included": false,
|
||||||
|
"executor_binding_hash_included": false,
|
||||||
|
"cycle_breaking_contract": "AGENT_CODE_RECEIPT_AND_EXECUTOR_BINDING_EXTERNALLY_BIND_RELEASE_RAW_HASH"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
{"authoring":{"path":"Stage_2_S2_00_v.1.yml","sha256":"2e0bf36786a64d74070f8cc97dfde05e9d17c7d7bef3a5b1e60aecdb0accc71d","size_bytes":362391,"unique_key_parse":"PASS"},"authoring_rewritten":false,"build_kind":"OFFLINE_AUTHORING_PROJECTION","canonical_code":{"ast_status":"PASS","code_sha256":"269d775690beb806a1e922b71909448b1f11a10f344e6a2d064295bf80ae1c10","code_size_bytes":280045,"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":"93f79ac6acae086471beb3f3cf60c8bb1538f29748fee96cad0c9d33d178a85a","path":"Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00.yml","sha256":"2e0bf36786a64d74070f8cc97dfde05e9d17c7d7bef3a5b1e60aecdb0accc71d","size_bytes":362391},"full_code_mirrors":[{"byte_identical_to_canonical_code":true,"path":"Default_Agent/Stage_2_Clean/runtime/s2_00_ingress.py","sha256":"269d775690beb806a1e922b71909448b1f11a10f344e6a2d064295bf80ae1c10","size_bytes":280045},{"byte_identical_to_canonical_code":true,"path":"Default_Agent/Stage_2_Clean/runtime/s2_00_ingress.txt","sha256":"269d775690beb806a1e922b71909448b1f11a10f344e6a2d064295bf80ae1c10","size_bytes":280045}],"parity_status":"PASS","schema_version":"stage2_s2_00_inline_code_receipt.v1","source_of_truth":"Stage_2_S2_00_v.1.yml","task_contract":{"agent":{"name":"Stage_2_S2_00_v1","version":"1.1.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":"269d775690beb806a1e922b71909448b1f11a10f344e6a2d064295bf80ae1c10","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"}
|
||||||
+59
-40
@@ -1,24 +1,24 @@
|
|||||||
{
|
{
|
||||||
"schema_version": "stage2_release.v1",
|
"schema_version": "stage2_release.v1",
|
||||||
"release_id": "STAGE2-CLEAN-DEV-DRAFT-2026-08-30",
|
"release_id": "STAGE2-CLEAN-DEV-DRAFT-INLINE-S2_00-2026-08-30",
|
||||||
"release_class": "DEV_FIXTURE_RELEASE",
|
"release_class": "DEV_FIXTURE_RELEASE",
|
||||||
"release_status": "DRAFT_NOT_EXECUTABLE",
|
"release_status": "DRAFT_NOT_EXECUTABLE",
|
||||||
"authorization_status": "DEV_VALIDATION_ONLY",
|
"authorization_status": "DEV_VALIDATION_ONLY",
|
||||||
"release_digest": "49ff3967f8eaa9c2291af6d0c17af1da2504296f03a020a84d1ea21c5954a2f0",
|
"release_digest": "fb88cc08ba7e32f9d12fe74411bf666df71b9e6e43e85ace897db1fba1dfe8af",
|
||||||
"signature": "PENDING_SEQUENTIAL_BIND",
|
"signature": "PENDING_SEQUENTIAL_BIND",
|
||||||
"authoring_root": "Case_02_Comparison_Research/YAML_Prompts/2. Stage_2/Default_Agent/Stage_2_Clean",
|
"authoring_root": "Case_02_Comparison_Research/YAML_Prompts/2. Stage_2/Default_Agent/Stage_2_Clean",
|
||||||
"module_manifest_ref": {
|
"module_manifest_ref": {
|
||||||
"asset_id": "MANIFEST-MODULE",
|
"asset_id": "MANIFEST-MODULE",
|
||||||
"path": "manifest/module_manifest.json",
|
"path": "manifest/module_manifest.json",
|
||||||
"sha256": "45841ecb5d05399974cd11751bb42ac95ad0285c914f5b7545e4bc97eb86a4ad",
|
"sha256": "5c18affa5bdcbca7df28f5f00d9b93c977f604e213bfc5fdf403f46f607c2321",
|
||||||
"schema_id": "stage2_module_manifest.v1",
|
"schema_id": "stage2_module_manifest.v1",
|
||||||
"binding_status": "BOUND"
|
"binding_status": "BOUND"
|
||||||
},
|
},
|
||||||
"loader_binding_ref": {
|
"loader_binding_ref": {
|
||||||
"asset_id": "BINDING-S2_00",
|
"asset_id": "BINDING-S2_00-LEGACY-REFERENCE",
|
||||||
"path": "deployment/stage2_loader_binding.yml",
|
"path": "deployment/stage2_loader_binding.yml",
|
||||||
"sha256": "d740f73274f39c5196c48e2bd3760ccd2347eadf8c59938fbe84a9f56ccb16f1",
|
"sha256": "61b2f85a319103e8c915f13dd642c75c7b7cf2d89575c8fe493d8ee074a8508b",
|
||||||
"schema_id": "stage2_loader_binding.v1",
|
"schema_id": "stage2_loader_binding.reference.v1",
|
||||||
"binding_status": "BOUND"
|
"binding_status": "BOUND"
|
||||||
},
|
},
|
||||||
"dependency_locks": {
|
"dependency_locks": {
|
||||||
@@ -473,12 +473,6 @@
|
|||||||
"path": null,
|
"path": null,
|
||||||
"sha256": null
|
"sha256": null
|
||||||
},
|
},
|
||||||
"completion_seal_ref": {
|
|
||||||
"status": "PENDING_SEQUENTIAL_BIND",
|
|
||||||
"path": "PENDING_SEQUENTIAL_BIND",
|
|
||||||
"sha256": "PENDING_SEQUENTIAL_BIND",
|
|
||||||
"producer_id": "PENDING_SEQUENTIAL_BIND"
|
|
||||||
},
|
|
||||||
"closure_scope": "REFERENCED_55_ONLY_NOT_FULL_STAGE1_RUNTIME_RELEASE",
|
"closure_scope": "REFERENCED_55_ONLY_NOT_FULL_STAGE1_RUNTIME_RELEASE",
|
||||||
"full_stage1_runtime_release_status": "STAGE1_NOT_RELEASE_READY"
|
"full_stage1_runtime_release_status": "STAGE1_NOT_RELEASE_READY"
|
||||||
},
|
},
|
||||||
@@ -1359,18 +1353,6 @@
|
|||||||
"bidirectional_match_allowed": true,
|
"bidirectional_match_allowed": true,
|
||||||
"global_alias_allowed": false
|
"global_alias_allowed": false
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
"adapter_id": "S2A-SNAPSHOT-SEAL-V1",
|
|
||||||
"adapter_kind": "SNAPSHOT_SEAL",
|
|
||||||
"contract_status": "PENDING_SEQUENTIAL_BIND",
|
|
||||||
"schema_ref": "schemas/ingress.schema.json#/$defs/source_contract_row",
|
|
||||||
"decision": {
|
|
||||||
"completion_seal_path": "PENDING_SEQUENTIAL_BIND",
|
|
||||||
"producer_id": "PENDING_SEQUENTIAL_BIND",
|
|
||||||
"one_read_same_descriptor_required": true,
|
|
||||||
"pre_post_seal_compare_required": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"retry_policies": [
|
"retry_policies": [
|
||||||
@@ -1399,20 +1381,14 @@
|
|||||||
{
|
{
|
||||||
"evidence_id": "DEV-DRAFT-UNSIGNED-001",
|
"evidence_id": "DEV-DRAFT-UNSIGNED-001",
|
||||||
"release_class": "DEV_FIXTURE_RELEASE",
|
"release_class": "DEV_FIXTURE_RELEASE",
|
||||||
"scope": "HASH_BOUND_STRUCTURAL_VALIDATION_ONLY",
|
"scope": "INLINE_CODE_EXECUTOR_OFFLINE_HASH_BOUND_VALIDATION_ONLY",
|
||||||
"signer_id": "UNSIGNED_DRAFT",
|
"signer_id": "UNSIGNED_DRAFT",
|
||||||
"input_digest": "660fde446a46832300e8580fa05d3e27312f57a5943a16748fbdad98e6e86cb0",
|
"input_digest": "5c18affa5bdcbca7df28f5f00d9b93c977f604e213bfc5fdf403f46f607c2321",
|
||||||
"signature": "PENDING_SEQUENTIAL_BIND",
|
"signature": "PENDING_SEQUENTIAL_BIND",
|
||||||
"status": "PENDING_SEQUENTIAL_BIND"
|
"status": "PENDING_SEQUENTIAL_BIND"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"open_items": [
|
"open_items": [
|
||||||
{
|
|
||||||
"open_id": "O-06",
|
|
||||||
"status": "OPEN_EXTERNAL_BACKEND",
|
|
||||||
"owner": "AgentBackend_owner",
|
|
||||||
"required_evidence": "native external .py loader invocation receipt proving loader-only fixed argv"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"open_id": "O-07",
|
"open_id": "O-07",
|
||||||
"status": "OPEN_RELEASE_AUTHORIZATION",
|
"status": "OPEN_RELEASE_AUTHORIZATION",
|
||||||
@@ -1420,16 +1396,52 @@
|
|||||||
"required_evidence": "signed canary or production admission with current Stage 1 integrity closure"
|
"required_evidence": "signed canary or production admission with current Stage 1 integrity closure"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"open_id": "SNAPSHOT-SEAL-BIND",
|
"open_id": "CEG-02-DIRECT-MCP-LIVE",
|
||||||
"status": "PENDING_SEQUENTIAL_BIND",
|
"status": "OPEN_EXTERNAL_BACKEND",
|
||||||
"owner": "Stage_1_contract_owner",
|
"owner": "AgentBackend_owner",
|
||||||
"required_evidence": "exact completion seal path, producer and raw SHA-256"
|
"required_evidence": "live code-executor.run_code outer result and inner single-JSON receipt with exact Agent/code/release binding"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"open_id": "CEG-03-SECRET-INJECTION",
|
||||||
|
"status": "OPEN_EXTERNAL_BACKEND",
|
||||||
|
"owner": "AgentBackend_owner",
|
||||||
|
"required_evidence": "backend-injected or pre-registered Code Executor authentication with no plaintext credential in YAML or code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"open_id": "CEG-04-RUNTIME-IMAGE-PIN",
|
||||||
|
"status": "OPEN_EXTERNAL_BACKEND",
|
||||||
|
"owner": "Code_Executor_owner",
|
||||||
|
"required_evidence": "verified Python runtime image digest compatible with the bound httpx dependency"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"open_id": "CEG-05-REQUEST-SIZE-TIMEOUT",
|
||||||
|
"status": "OPEN_EXTERNAL_BACKEND",
|
||||||
|
"owner": "Code_Executor_owner",
|
||||||
|
"required_evidence": "representative S2_00 Agent request accepted and completed within backend request-size and 300-second limits"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"open_id": "CEG-06-BINARY-LOCALDOCS",
|
||||||
|
"status": "OPEN_EXTERNAL_BACKEND",
|
||||||
|
"owner": "localdocs_owner",
|
||||||
|
"required_evidence": "live read_binary_doc and write_binary_file byte-preservation receipt over the approved path set"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"open_id": "CEG-07-ISOLATION-EGRESS",
|
||||||
|
"status": "OPEN_EXTERNAL_BACKEND",
|
||||||
|
"owner": "AgentBackend_security_owner",
|
||||||
|
"required_evidence": "workspace identity substitution and backend-enforced S2_00_LOCALDOCS_ONLY_V1 egress verification"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"open_id": "CEG-08-BARRIER-ROUTE",
|
||||||
|
"status": "OPEN_EXTERNAL_BACKEND",
|
||||||
|
"owner": "Stage_2_integration_owner",
|
||||||
|
"required_evidence": "live status-last logical publish and exactly-one downstream route receipt"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"open_id": "DEV-DETACHED-RELEASE-ENVELOPE",
|
"open_id": "DEV-DETACHED-RELEASE-ENVELOPE",
|
||||||
"status": "OPEN_RELEASE_AUTHORIZATION",
|
"status": "OPEN_RELEASE_AUTHORIZATION",
|
||||||
"owner": "release_operator",
|
"owner": "release_operator",
|
||||||
"required_evidence": "detached signed envelope closes binding-to-release and release admission without a cross-file hash cycle"
|
"required_evidence": "detached signed envelope admits the release and executor binding without a cross-file hash cycle"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"open_id": "DOWNSTREAM-CASE-TYPE-REGISTRY",
|
"open_id": "DOWNSTREAM-CASE-TYPE-REGISTRY",
|
||||||
@@ -1461,7 +1473,7 @@
|
|||||||
"expected_prefix_sha256": "3634cb6929cce8395683048eabb068ed63aed0427869b19ce4b56c586ab48071",
|
"expected_prefix_sha256": "3634cb6929cce8395683048eabb068ed63aed0427869b19ce4b56c586ab48071",
|
||||||
"cache_key_contract": {
|
"cache_key_contract": {
|
||||||
"policy_id": "S2-CACHE-STATIC-PREFIX-V1",
|
"policy_id": "S2-CACHE-STATIC-PREFIX-V1",
|
||||||
"model_binding_ref": "deployment/stage2_loader_binding.yml#/downstream_llm_candidate_bindings/0",
|
"model_binding_ref": "deployment/stage2_code_executor_binding.yml#/downstream_llm_candidate_bindings/0",
|
||||||
"model_id": "gpt-5.6-sol",
|
"model_id": "gpt-5.6-sol",
|
||||||
"reasoning_effort": "ultra",
|
"reasoning_effort": "ultra",
|
||||||
"prompt_contract_version": "stage2.prompt_contract.v1",
|
"prompt_contract_version": "stage2.prompt_contract.v1",
|
||||||
@@ -1476,7 +1488,8 @@
|
|||||||
"reason_codes": [
|
"reason_codes": [
|
||||||
"STRUCTURAL_FIXTURE_NOT_EXECUTABLE",
|
"STRUCTURAL_FIXTURE_NOT_EXECUTABLE",
|
||||||
"AUTHORITY_RELEASE_UNAVAILABLE",
|
"AUTHORITY_RELEASE_UNAVAILABLE",
|
||||||
"PROMPT_AND_PROFILE_LEGAL_REVIEW_PENDING"
|
"PROMPT_AND_PROFILE_LEGAL_REVIEW_PENDING",
|
||||||
|
"DIRECT_MCP_LIVE_ADMISSION_PENDING"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"stage1_sources": [
|
"stage1_sources": [
|
||||||
@@ -1783,5 +1796,11 @@
|
|||||||
"transaction_identity_pointer": null,
|
"transaction_identity_pointer": null,
|
||||||
"raw_hash_source": "MANIFEST_ROW"
|
"raw_hash_source": "MANIFEST_ROW"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"executor_binding_ref": {
|
||||||
|
"asset_id": "BINDING-S2_00-CODE-EXECUTOR",
|
||||||
|
"path": "deployment/stage2_code_executor_binding.yml",
|
||||||
|
"schema_id": "stage2_code_executor_binding.v1",
|
||||||
|
"binding_status": "EXTERNAL_TRUST_ROOT_PENDING_LIVE_ADMISSION"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+751
@@ -0,0 +1,751 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Build the version-free S2_00 Agent projection from its sole authoring YAML.
|
||||||
|
|
||||||
|
This is an offline build tool. It never executes a matter, imports project
|
||||||
|
runtime code, rewrites the authoring YAML, or invokes a subprocess. The YAML
|
||||||
|
parser-returned ``parameters.code`` string is encoded directly as UTF-8; no
|
||||||
|
dedent, newline normalization, or source transformation is permitted.
|
||||||
|
"""
|
||||||
|
|
||||||
|
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 - exercised only on an incomplete 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_00_v.1.yml"
|
||||||
|
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"
|
||||||
|
|
||||||
|
EXPECTED_TASK_NAME = "Task_S2_00_deterministic_ingress"
|
||||||
|
EXPECTED_AGENT_NAME = "Stage_2_S2_00_v1"
|
||||||
|
EXPECTED_AGENT_VERSION = "1.1.0"
|
||||||
|
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"
|
||||||
|
REQUIRED_CODE_TOKENS = (
|
||||||
|
EXPECTED_LOCALDOCS_URL,
|
||||||
|
"{{__user_hash__}}",
|
||||||
|
"{{__workspace_hash__}}",
|
||||||
|
"read_binary_doc",
|
||||||
|
"write_binary_file",
|
||||||
|
)
|
||||||
|
REQUIRED_CODE_TOKEN_ALTERNATIVES = (
|
||||||
|
("expected_stage2_release_sha256", "EXPECTED_STAGE2_RELEASE_SHA256"),
|
||||||
|
)
|
||||||
|
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"})
|
||||||
|
FORBIDDEN_ATTRIBUTE_CALLS = frozenset(
|
||||||
|
{
|
||||||
|
("os", "popen"),
|
||||||
|
("os", "spawnl"),
|
||||||
|
("os", "spawnle"),
|
||||||
|
("os", "spawnlp"),
|
||||||
|
("os", "spawnlpe"),
|
||||||
|
("os", "spawnv"),
|
||||||
|
("os", "spawnve"),
|
||||||
|
("os", "spawnvp"),
|
||||||
|
("os", "spawnvpe"),
|
||||||
|
("os", "system"),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
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*['\"][^'\"]+['\"]"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
URL_RE = re.compile(r"https?://[^\s'\"]+")
|
||||||
|
PYTHON_SOURCE_REF_RE = re.compile(r"(?i)(?:^|[/\\])[^\r\n'\"]+\.py(?:$|[?#])")
|
||||||
|
ALLOWED_IDENTIFIER_URL_PREFIXES = ("https://schemas.liti-agent.local/",)
|
||||||
|
HTTP_NETWORK_METHODS = frozenset({"delete", "get", "head", "options", "patch", "post", "put", "request", "stream"})
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectionError(RuntimeError):
|
||||||
|
"""A 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
|
||||||
|
|
||||||
|
|
||||||
|
class AuthoringMissingError(ProjectionError):
|
||||||
|
def __init__(self, path: Path) -> None:
|
||||||
|
super().__init__("AUTHORING_YAML_MISSING", path.as_posix())
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
location = f" line {mark.line + 1}" if mark is not None else ""
|
||||||
|
raise ProjectionError("YAML_DUPLICATE_KEY", f"{key!r}{location}")
|
||||||
|
result[key] = self.construct_object(value_node, deep=deep)
|
||||||
|
return result
|
||||||
|
|
||||||
|
else: # pragma: no cover - type placeholder for hosts without PyYAML
|
||||||
|
|
||||||
|
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 parse_authoring_bytes(raw: bytes, *, source: str = "<authoring>") -> dict[str, Any]:
|
||||||
|
if yaml is None:
|
||||||
|
raise ProjectionError("PYYAML_REQUIRED", "install PyYAML on the 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:
|
||||||
|
loaded = 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(loaded, dict):
|
||||||
|
raise ProjectionError("AUTHORING_ROOT_OBJECT_REQUIRED", source)
|
||||||
|
return loaded
|
||||||
|
|
||||||
|
|
||||||
|
def load_authoring(path: Path | None = None) -> tuple[bytes, dict[str, Any]]:
|
||||||
|
path = AUTHORING_PATH if path is None else path
|
||||||
|
if not path.is_file():
|
||||||
|
raise AuthoringMissingError(path)
|
||||||
|
if path.is_symlink():
|
||||||
|
raise ProjectionError("AUTHORING_SYMLINK_FORBIDDEN", path.as_posix())
|
||||||
|
raw = path.read_bytes()
|
||||||
|
return raw, parse_authoring_bytes(raw, source=path.as_posix())
|
||||||
|
|
||||||
|
|
||||||
|
def _require_mapping(value: Any, code: str) -> dict[str, Any]:
|
||||||
|
if not isinstance(value, dict):
|
||||||
|
raise ProjectionError(code, repr(value)[:200])
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def _require_list(value: Any, code: str) -> list[Any]:
|
||||||
|
if not isinstance(value, list):
|
||||||
|
raise ProjectionError(code, repr(value)[:200])
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
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}",
|
||||||
|
)
|
||||||
|
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") != "S2_00":
|
||||||
|
raise ProjectionError("S2_00_STAGE_NAME_REQUIRED", repr(stage.get("name")))
|
||||||
|
if "skip_confirm" in stage:
|
||||||
|
raise ProjectionError(
|
||||||
|
"SKIP_CONFIRM_HOST_POLICY_ONLY",
|
||||||
|
"skip_confirm belongs to the executor binding, not the authoring YAML",
|
||||||
|
)
|
||||||
|
for forbidden in ("llm_provider", "llm_model", "llm_reasoning", "llm_verbosity"):
|
||||||
|
if forbidden in stage:
|
||||||
|
raise ProjectionError("MODEL_FIELD_FORBIDDEN", forbidden)
|
||||||
|
|
||||||
|
servers = _require_mapping(
|
||||||
|
_require_mapping(stage.get("tools"), "TOOLS_OBJECT_REQUIRED").get("mcpServers"),
|
||||||
|
"MCP_SERVERS_OBJECT_REQUIRED",
|
||||||
|
)
|
||||||
|
localdocs = _require_mapping(servers.get("localdocs"), "LOCALDOCS_SERVER_REQUIRED")
|
||||||
|
code_executor = _require_mapping(
|
||||||
|
servers.get("code-executor"), "CODE_EXECUTOR_SERVER_REQUIRED"
|
||||||
|
)
|
||||||
|
if localdocs.get("type") != "streamable-http" or localdocs.get("url") != EXPECTED_LOCALDOCS_URL:
|
||||||
|
raise ProjectionError("LOCALDOCS_SERVER_BINDING_INVALID", repr(localdocs))
|
||||||
|
if (
|
||||||
|
code_executor.get("type") != "streamable-http"
|
||||||
|
or code_executor.get("url") != EXPECTED_CODE_EXECUTOR_URL
|
||||||
|
):
|
||||||
|
raise ProjectionError("CODE_EXECUTOR_SERVER_BINDING_INVALID", repr(code_executor))
|
||||||
|
if "headers" in code_executor:
|
||||||
|
raise ProjectionError(
|
||||||
|
"PLAINTEXT_OR_INLINE_AUTH_HEADER_FORBIDDEN",
|
||||||
|
"authentication must be injected or pre-registered by the backend",
|
||||||
|
)
|
||||||
|
|
||||||
|
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 = [
|
||||||
|
row
|
||||||
|
for row in tasks
|
||||||
|
if isinstance(row, dict)
|
||||||
|
and row.get("mcp") == "code-executor"
|
||||||
|
and row.get("tool_name") == "run_code"
|
||||||
|
]
|
||||||
|
if len(run_code_tasks) != 1:
|
||||||
|
raise ProjectionError("EXACTLY_ONE_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")))
|
||||||
|
parameters = _require_mapping(task.get("parameters"), "TASK_PARAMETERS_REQUIRED")
|
||||||
|
expected_parameter_keys = set(EXPECTED_PARAMETERS) | {"code"}
|
||||||
|
if set(parameters) != expected_parameter_keys:
|
||||||
|
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)[:100])
|
||||||
|
if code.endswith(("\n", "\r")):
|
||||||
|
raise ProjectionError(
|
||||||
|
"INLINE_CODE_TRAILING_NEWLINE_FORBIDDEN",
|
||||||
|
"authoring YAML must use code: |- so parser-returned code has no trailing newline",
|
||||||
|
)
|
||||||
|
|
||||||
|
procedure = _require_mapping(stage.get("task_procedure"), "TASK_PROCEDURE_REQUIRED")
|
||||||
|
expected_procedure = {
|
||||||
|
"IN": {"nexts": [EXPECTED_TASK_NAME], "wait_until": []},
|
||||||
|
EXPECTED_TASK_NAME: {"nexts": ["OUT"], "wait_until": ["IN"]},
|
||||||
|
"OUT": {"nexts": [], "wait_until": [EXPECTED_TASK_NAME]},
|
||||||
|
}
|
||||||
|
if procedure != expected_procedure:
|
||||||
|
raise ProjectionError("TASK_PROCEDURE_MISMATCH", repr(procedure)[:500])
|
||||||
|
if stage.get("prevs") != [] or stage.get("nexts") != []:
|
||||||
|
raise ProjectionError("STANDALONE_STAGE_EDGES_MUST_BE_EMPTY", repr(stage))
|
||||||
|
return stage, task
|
||||||
|
|
||||||
|
|
||||||
|
def _import_root(name: str | None) -> str:
|
||||||
|
return (name or "").split(".", 1)[0]
|
||||||
|
|
||||||
|
|
||||||
|
def _attribute_pair(node: ast.Attribute) -> tuple[str, str] | None:
|
||||||
|
if isinstance(node.value, ast.Name):
|
||||||
|
return node.value.id, node.attr
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _is_http_client_receiver(
|
||||||
|
node: ast.expr,
|
||||||
|
derived_client_names: set[str] | None = None,
|
||||||
|
) -> bool:
|
||||||
|
if isinstance(node, ast.Name):
|
||||||
|
return node.id in {"client", "http_client", "httpx"} | (derived_client_names or set())
|
||||||
|
if isinstance(node, ast.Attribute):
|
||||||
|
return (
|
||||||
|
isinstance(node.value, ast.Name)
|
||||||
|
and node.value.id == "self"
|
||||||
|
and node.attr in {"client", "http_client"}
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _is_direct_localdocs_post(call: ast.Call) -> bool:
|
||||||
|
if not isinstance(call.func, ast.Attribute) or call.func.attr != "post":
|
||||||
|
return False
|
||||||
|
receiver = call.func.value
|
||||||
|
if not (
|
||||||
|
isinstance(receiver, ast.Attribute)
|
||||||
|
and isinstance(receiver.value, ast.Name)
|
||||||
|
and receiver.value.id == "self"
|
||||||
|
and receiver.attr == "client"
|
||||||
|
):
|
||||||
|
return False
|
||||||
|
return _is_localdocs_endpoint(_network_endpoint(call) or ast.Constant(value=None))
|
||||||
|
|
||||||
|
|
||||||
|
def _network_endpoint(call: ast.Call) -> ast.expr | None:
|
||||||
|
"""Return a statically identifiable httpx/client endpoint expression."""
|
||||||
|
|
||||||
|
if not isinstance(call.func, ast.Attribute) or call.func.attr not in HTTP_NETWORK_METHODS:
|
||||||
|
return None
|
||||||
|
if not _is_http_client_receiver(call.func.value):
|
||||||
|
return None
|
||||||
|
for keyword in call.keywords:
|
||||||
|
if keyword.arg == "url":
|
||||||
|
return keyword.value
|
||||||
|
index = 1 if call.func.attr == "request" else 0
|
||||||
|
return call.args[index] if len(call.args) > index else ast.Constant(value=None)
|
||||||
|
|
||||||
|
|
||||||
|
def _is_localdocs_endpoint(node: ast.expr) -> bool:
|
||||||
|
return (
|
||||||
|
isinstance(node, ast.Name)
|
||||||
|
and node.id == "LOCALDOCS_URL"
|
||||||
|
or isinstance(node, ast.Constant)
|
||||||
|
and node.value == EXPECTED_LOCALDOCS_URL
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_inline_code(code: str) -> dict[str, Any]:
|
||||||
|
code_bytes = code.encode("utf-8")
|
||||||
|
try:
|
||||||
|
compile(code, "<Stage_2_S2_00.parameters.code>", "exec", dont_inherit=True)
|
||||||
|
tree = ast.parse(code, filename="<Stage_2_S2_00.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]
|
||||||
|
missing_tokens.extend(
|
||||||
|
"|".join(alternatives)
|
||||||
|
for alternatives in REQUIRED_CODE_TOKEN_ALTERNATIVES
|
||||||
|
if not any(token in code for token in alternatives)
|
||||||
|
)
|
||||||
|
if missing_tokens:
|
||||||
|
raise ProjectionError("INLINE_CODE_REQUIRED_TOKEN_MISSING", ",".join(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()
|
||||||
|
forbidden_nodes: list[str] = []
|
||||||
|
external_urls: set[str] = set()
|
||||||
|
forbidden_network_endpoints: set[str] = set()
|
||||||
|
project_source_refs: set[str] = set()
|
||||||
|
derived_client_names: set[str] = set()
|
||||||
|
for candidate in ast.walk(tree):
|
||||||
|
if not isinstance(candidate, (ast.Assign, ast.AnnAssign)):
|
||||||
|
continue
|
||||||
|
value = candidate.value
|
||||||
|
if not (
|
||||||
|
isinstance(value, ast.Call)
|
||||||
|
and isinstance(value.func, ast.Attribute)
|
||||||
|
and isinstance(value.func.value, ast.Name)
|
||||||
|
and value.func.value.id == "httpx"
|
||||||
|
and value.func.attr in {"Client", "AsyncClient"}
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
targets = candidate.targets if isinstance(candidate, ast.Assign) else [candidate.target]
|
||||||
|
derived_client_names.update(target.id for target in targets if isinstance(target, ast.Name))
|
||||||
|
for node in ast.walk(tree):
|
||||||
|
if isinstance(node, ast.Import):
|
||||||
|
imports.update(_import_root(alias.name) for alias in node.names)
|
||||||
|
elif isinstance(node, ast.ImportFrom):
|
||||||
|
if node.level:
|
||||||
|
forbidden_nodes.append(f"relative-import:{node.module or ''}")
|
||||||
|
imports.add(_import_root(node.module))
|
||||||
|
elif isinstance(node, ast.Pass):
|
||||||
|
forbidden_nodes.append("Pass")
|
||||||
|
elif isinstance(node, ast.Expr) and isinstance(node.value, ast.Constant):
|
||||||
|
if node.value.value is Ellipsis:
|
||||||
|
forbidden_nodes.append("Ellipsis-expression")
|
||||||
|
elif isinstance(node, (ast.Assign, ast.AnnAssign)):
|
||||||
|
if isinstance(node.value, ast.Constant) and node.value.value is Ellipsis:
|
||||||
|
forbidden_nodes.append("Ellipsis-assignment")
|
||||||
|
if (
|
||||||
|
isinstance(node.value, ast.Attribute)
|
||||||
|
and node.value.attr in HTTP_NETWORK_METHODS
|
||||||
|
and _is_http_client_receiver(node.value.value, derived_client_names)
|
||||||
|
):
|
||||||
|
forbidden_nodes.append(f"network-method-alias:{node.value.attr}")
|
||||||
|
elif isinstance(node, ast.Constant):
|
||||||
|
if isinstance(node.value, str):
|
||||||
|
for match in URL_RE.findall(node.value):
|
||||||
|
normalized = match.rstrip(".,);]")
|
||||||
|
if (
|
||||||
|
normalized != EXPECTED_LOCALDOCS_URL
|
||||||
|
and not normalized.startswith(ALLOWED_IDENTIFIER_URL_PREFIXES)
|
||||||
|
):
|
||||||
|
external_urls.add(normalized)
|
||||||
|
if PYTHON_SOURCE_REF_RE.search(node.value.strip()):
|
||||||
|
project_source_refs.add(node.value[:200])
|
||||||
|
if node.value.strip().lower() in {
|
||||||
|
"todo",
|
||||||
|
"tbd",
|
||||||
|
"placeholder",
|
||||||
|
"omitted",
|
||||||
|
"implement me",
|
||||||
|
"...",
|
||||||
|
}:
|
||||||
|
forbidden_nodes.append(f"placeholder-string:{node.value!r}")
|
||||||
|
elif isinstance(node, ast.Call):
|
||||||
|
if (
|
||||||
|
isinstance(node.func, ast.Attribute)
|
||||||
|
and node.func.attr in HTTP_NETWORK_METHODS
|
||||||
|
and _is_http_client_receiver(node.func.value, derived_client_names)
|
||||||
|
):
|
||||||
|
if not _is_direct_localdocs_post(node):
|
||||||
|
forbidden_network_endpoints.add(ast.unparse(node.func)[:200])
|
||||||
|
if (
|
||||||
|
isinstance(node.func, ast.Name)
|
||||||
|
and node.func.id == "getattr"
|
||||||
|
and len(node.args) >= 2
|
||||||
|
and isinstance(node.args[1], ast.Constant)
|
||||||
|
and node.args[1].value in HTTP_NETWORK_METHODS
|
||||||
|
):
|
||||||
|
forbidden_nodes.append(f"dynamic-network-method:{node.args[1].value}")
|
||||||
|
if isinstance(node.func, ast.Name) and node.func.id in FORBIDDEN_CALL_NAMES:
|
||||||
|
forbidden_nodes.append(f"call:{node.func.id}")
|
||||||
|
elif isinstance(node.func, ast.Attribute):
|
||||||
|
pair = _attribute_pair(node.func)
|
||||||
|
if pair in FORBIDDEN_ATTRIBUTE_CALLS:
|
||||||
|
forbidden_nodes.append(f"call:{pair[0]}.{pair[1]}")
|
||||||
|
elif isinstance(node, ast.Raise):
|
||||||
|
target = node.exc
|
||||||
|
if isinstance(target, ast.Call):
|
||||||
|
target = target.func
|
||||||
|
if isinstance(target, ast.Name) and target.id == "NotImplementedError":
|
||||||
|
forbidden_nodes.append("raise:NotImplementedError")
|
||||||
|
|
||||||
|
imports.discard("")
|
||||||
|
disallowed_imports = sorted(
|
||||||
|
root
|
||||||
|
for root in imports
|
||||||
|
if root in FORBIDDEN_IMPORT_ROOTS
|
||||||
|
or (root not in sys.stdlib_module_names and root not in ALLOWED_NON_STDLIB_IMPORTS)
|
||||||
|
)
|
||||||
|
if disallowed_imports:
|
||||||
|
raise ProjectionError("INLINE_CODE_IMPORT_FORBIDDEN", ",".join(disallowed_imports))
|
||||||
|
if forbidden_nodes:
|
||||||
|
raise ProjectionError("INLINE_CODE_DYNAMIC_OR_PLACEHOLDER_FORBIDDEN", ",".join(forbidden_nodes))
|
||||||
|
if external_urls:
|
||||||
|
raise ProjectionError("INLINE_CODE_EXTERNAL_URL_FORBIDDEN", ",".join(sorted(external_urls)))
|
||||||
|
if forbidden_network_endpoints:
|
||||||
|
raise ProjectionError(
|
||||||
|
"INLINE_CODE_NETWORK_ENDPOINT_FORBIDDEN",
|
||||||
|
",".join(sorted(forbidden_network_endpoints)),
|
||||||
|
)
|
||||||
|
if project_source_refs:
|
||||||
|
raise ProjectionError(
|
||||||
|
"INLINE_CODE_EXTERNAL_PY_SOURCE_REF_FORBIDDEN", ",".join(sorted(project_source_refs))
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"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_url_count": 0,
|
||||||
|
"placeholder_count": 0,
|
||||||
|
"plaintext_secret_count": 0,
|
||||||
|
"external_python_source_ref_count": 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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"),
|
||||||
|
},
|
||||||
|
"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 ("language", "requirements", "network", "timeout")
|
||||||
|
},
|
||||||
|
"code_sha256": sha256_bytes(parameters["code"].encode("utf-8")),
|
||||||
|
},
|
||||||
|
"task_procedure": stage.get("task_procedure"),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
semantics_sha256 = sha256_bytes(canonical_json_bytes(semantics))
|
||||||
|
|
||||||
|
receipt = {
|
||||||
|
"schema_version": "stage2_s2_00_inline_code_receipt.v1",
|
||||||
|
"workflow_id": "S2_00",
|
||||||
|
"build_kind": "OFFLINE_AUTHORING_PROJECTION",
|
||||||
|
"source_of_truth": _logical_path(AUTHORING_PATH),
|
||||||
|
"authoring_rewritten": False,
|
||||||
|
"authoring": {
|
||||||
|
"path": _logical_path(AUTHORING_PATH),
|
||||||
|
"sha256": sha256_bytes(authoring_raw),
|
||||||
|
"size_bytes": len(authoring_raw),
|
||||||
|
"unique_key_parse": "PASS",
|
||||||
|
},
|
||||||
|
"deployment_projection": {
|
||||||
|
"path": _logical_path(PROJECTION_PATH),
|
||||||
|
"sha256": sha256_bytes(authoring_raw),
|
||||||
|
"size_bytes": len(authoring_raw),
|
||||||
|
"byte_identical_to_authoring": True,
|
||||||
|
"canonical_task_semantics_sha256": semantics_sha256,
|
||||||
|
},
|
||||||
|
"canonical_code": {
|
||||||
|
"yaml_pointer": "/Agent/Stages/0/tasks/0/parameters/code",
|
||||||
|
"encoding": "UTF-8",
|
||||||
|
"extraction_transform": "NONE",
|
||||||
|
**validation,
|
||||||
|
},
|
||||||
|
"full_code_mirrors": [
|
||||||
|
{
|
||||||
|
"path": _logical_path(MIRROR_PY_PATH),
|
||||||
|
"sha256": validation["code_sha256"],
|
||||||
|
"size_bytes": len(code_bytes),
|
||||||
|
"byte_identical_to_canonical_code": True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": _logical_path(MIRROR_TXT_PATH),
|
||||||
|
"sha256": validation["code_sha256"],
|
||||||
|
"size_bytes": len(code_bytes),
|
||||||
|
"byte_identical_to_canonical_code": True,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"task_contract": semantics,
|
||||||
|
"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:
|
||||||
|
continue
|
||||||
|
_atomic_write(path, payload)
|
||||||
|
after_artifacts = AUTHORING_PATH.read_bytes()
|
||||||
|
if after_artifacts != before:
|
||||||
|
raise ProjectionError(
|
||||||
|
"AUTHORING_CHANGED_DURING_BUILD",
|
||||||
|
f"before={sha256_bytes(before)} after={sha256_bytes(after_artifacts)}",
|
||||||
|
)
|
||||||
|
_atomic_write(RECEIPT_PATH, receipt_payload)
|
||||||
|
after_receipt = AUTHORING_PATH.read_bytes()
|
||||||
|
if after_receipt != before:
|
||||||
|
raise ProjectionError(
|
||||||
|
"AUTHORING_CHANGED_DURING_RECEIPT_WRITE",
|
||||||
|
f"before={sha256_bytes(before)} after={sha256_bytes(after_receipt)}",
|
||||||
|
)
|
||||||
|
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 the S2_00 inline Agent projection"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--check",
|
||||||
|
action="store_true",
|
||||||
|
help="perform a no-write byte-parity check against generated outputs",
|
||||||
|
)
|
||||||
|
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" 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())
|
||||||
+751
@@ -0,0 +1,751 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Build the version-free S2_00 Agent projection from its sole authoring YAML.
|
||||||
|
|
||||||
|
This is an offline build tool. It never executes a matter, imports project
|
||||||
|
runtime code, rewrites the authoring YAML, or invokes a subprocess. The YAML
|
||||||
|
parser-returned ``parameters.code`` string is encoded directly as UTF-8; no
|
||||||
|
dedent, newline normalization, or source transformation is permitted.
|
||||||
|
"""
|
||||||
|
|
||||||
|
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 - exercised only on an incomplete 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_00_v.1.yml"
|
||||||
|
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"
|
||||||
|
|
||||||
|
EXPECTED_TASK_NAME = "Task_S2_00_deterministic_ingress"
|
||||||
|
EXPECTED_AGENT_NAME = "Stage_2_S2_00_v1"
|
||||||
|
EXPECTED_AGENT_VERSION = "1.1.0"
|
||||||
|
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"
|
||||||
|
REQUIRED_CODE_TOKENS = (
|
||||||
|
EXPECTED_LOCALDOCS_URL,
|
||||||
|
"{{__user_hash__}}",
|
||||||
|
"{{__workspace_hash__}}",
|
||||||
|
"read_binary_doc",
|
||||||
|
"write_binary_file",
|
||||||
|
)
|
||||||
|
REQUIRED_CODE_TOKEN_ALTERNATIVES = (
|
||||||
|
("expected_stage2_release_sha256", "EXPECTED_STAGE2_RELEASE_SHA256"),
|
||||||
|
)
|
||||||
|
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"})
|
||||||
|
FORBIDDEN_ATTRIBUTE_CALLS = frozenset(
|
||||||
|
{
|
||||||
|
("os", "popen"),
|
||||||
|
("os", "spawnl"),
|
||||||
|
("os", "spawnle"),
|
||||||
|
("os", "spawnlp"),
|
||||||
|
("os", "spawnlpe"),
|
||||||
|
("os", "spawnv"),
|
||||||
|
("os", "spawnve"),
|
||||||
|
("os", "spawnvp"),
|
||||||
|
("os", "spawnvpe"),
|
||||||
|
("os", "system"),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
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*['\"][^'\"]+['\"]"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
URL_RE = re.compile(r"https?://[^\s'\"]+")
|
||||||
|
PYTHON_SOURCE_REF_RE = re.compile(r"(?i)(?:^|[/\\])[^\r\n'\"]+\.py(?:$|[?#])")
|
||||||
|
ALLOWED_IDENTIFIER_URL_PREFIXES = ("https://schemas.liti-agent.local/",)
|
||||||
|
HTTP_NETWORK_METHODS = frozenset({"delete", "get", "head", "options", "patch", "post", "put", "request", "stream"})
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectionError(RuntimeError):
|
||||||
|
"""A 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
|
||||||
|
|
||||||
|
|
||||||
|
class AuthoringMissingError(ProjectionError):
|
||||||
|
def __init__(self, path: Path) -> None:
|
||||||
|
super().__init__("AUTHORING_YAML_MISSING", path.as_posix())
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
location = f" line {mark.line + 1}" if mark is not None else ""
|
||||||
|
raise ProjectionError("YAML_DUPLICATE_KEY", f"{key!r}{location}")
|
||||||
|
result[key] = self.construct_object(value_node, deep=deep)
|
||||||
|
return result
|
||||||
|
|
||||||
|
else: # pragma: no cover - type placeholder for hosts without PyYAML
|
||||||
|
|
||||||
|
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 parse_authoring_bytes(raw: bytes, *, source: str = "<authoring>") -> dict[str, Any]:
|
||||||
|
if yaml is None:
|
||||||
|
raise ProjectionError("PYYAML_REQUIRED", "install PyYAML on the 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:
|
||||||
|
loaded = 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(loaded, dict):
|
||||||
|
raise ProjectionError("AUTHORING_ROOT_OBJECT_REQUIRED", source)
|
||||||
|
return loaded
|
||||||
|
|
||||||
|
|
||||||
|
def load_authoring(path: Path | None = None) -> tuple[bytes, dict[str, Any]]:
|
||||||
|
path = AUTHORING_PATH if path is None else path
|
||||||
|
if not path.is_file():
|
||||||
|
raise AuthoringMissingError(path)
|
||||||
|
if path.is_symlink():
|
||||||
|
raise ProjectionError("AUTHORING_SYMLINK_FORBIDDEN", path.as_posix())
|
||||||
|
raw = path.read_bytes()
|
||||||
|
return raw, parse_authoring_bytes(raw, source=path.as_posix())
|
||||||
|
|
||||||
|
|
||||||
|
def _require_mapping(value: Any, code: str) -> dict[str, Any]:
|
||||||
|
if not isinstance(value, dict):
|
||||||
|
raise ProjectionError(code, repr(value)[:200])
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def _require_list(value: Any, code: str) -> list[Any]:
|
||||||
|
if not isinstance(value, list):
|
||||||
|
raise ProjectionError(code, repr(value)[:200])
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
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}",
|
||||||
|
)
|
||||||
|
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") != "S2_00":
|
||||||
|
raise ProjectionError("S2_00_STAGE_NAME_REQUIRED", repr(stage.get("name")))
|
||||||
|
if "skip_confirm" in stage:
|
||||||
|
raise ProjectionError(
|
||||||
|
"SKIP_CONFIRM_HOST_POLICY_ONLY",
|
||||||
|
"skip_confirm belongs to the executor binding, not the authoring YAML",
|
||||||
|
)
|
||||||
|
for forbidden in ("llm_provider", "llm_model", "llm_reasoning", "llm_verbosity"):
|
||||||
|
if forbidden in stage:
|
||||||
|
raise ProjectionError("MODEL_FIELD_FORBIDDEN", forbidden)
|
||||||
|
|
||||||
|
servers = _require_mapping(
|
||||||
|
_require_mapping(stage.get("tools"), "TOOLS_OBJECT_REQUIRED").get("mcpServers"),
|
||||||
|
"MCP_SERVERS_OBJECT_REQUIRED",
|
||||||
|
)
|
||||||
|
localdocs = _require_mapping(servers.get("localdocs"), "LOCALDOCS_SERVER_REQUIRED")
|
||||||
|
code_executor = _require_mapping(
|
||||||
|
servers.get("code-executor"), "CODE_EXECUTOR_SERVER_REQUIRED"
|
||||||
|
)
|
||||||
|
if localdocs.get("type") != "streamable-http" or localdocs.get("url") != EXPECTED_LOCALDOCS_URL:
|
||||||
|
raise ProjectionError("LOCALDOCS_SERVER_BINDING_INVALID", repr(localdocs))
|
||||||
|
if (
|
||||||
|
code_executor.get("type") != "streamable-http"
|
||||||
|
or code_executor.get("url") != EXPECTED_CODE_EXECUTOR_URL
|
||||||
|
):
|
||||||
|
raise ProjectionError("CODE_EXECUTOR_SERVER_BINDING_INVALID", repr(code_executor))
|
||||||
|
if "headers" in code_executor:
|
||||||
|
raise ProjectionError(
|
||||||
|
"PLAINTEXT_OR_INLINE_AUTH_HEADER_FORBIDDEN",
|
||||||
|
"authentication must be injected or pre-registered by the backend",
|
||||||
|
)
|
||||||
|
|
||||||
|
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 = [
|
||||||
|
row
|
||||||
|
for row in tasks
|
||||||
|
if isinstance(row, dict)
|
||||||
|
and row.get("mcp") == "code-executor"
|
||||||
|
and row.get("tool_name") == "run_code"
|
||||||
|
]
|
||||||
|
if len(run_code_tasks) != 1:
|
||||||
|
raise ProjectionError("EXACTLY_ONE_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")))
|
||||||
|
parameters = _require_mapping(task.get("parameters"), "TASK_PARAMETERS_REQUIRED")
|
||||||
|
expected_parameter_keys = set(EXPECTED_PARAMETERS) | {"code"}
|
||||||
|
if set(parameters) != expected_parameter_keys:
|
||||||
|
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)[:100])
|
||||||
|
if code.endswith(("\n", "\r")):
|
||||||
|
raise ProjectionError(
|
||||||
|
"INLINE_CODE_TRAILING_NEWLINE_FORBIDDEN",
|
||||||
|
"authoring YAML must use code: |- so parser-returned code has no trailing newline",
|
||||||
|
)
|
||||||
|
|
||||||
|
procedure = _require_mapping(stage.get("task_procedure"), "TASK_PROCEDURE_REQUIRED")
|
||||||
|
expected_procedure = {
|
||||||
|
"IN": {"nexts": [EXPECTED_TASK_NAME], "wait_until": []},
|
||||||
|
EXPECTED_TASK_NAME: {"nexts": ["OUT"], "wait_until": ["IN"]},
|
||||||
|
"OUT": {"nexts": [], "wait_until": [EXPECTED_TASK_NAME]},
|
||||||
|
}
|
||||||
|
if procedure != expected_procedure:
|
||||||
|
raise ProjectionError("TASK_PROCEDURE_MISMATCH", repr(procedure)[:500])
|
||||||
|
if stage.get("prevs") != [] or stage.get("nexts") != []:
|
||||||
|
raise ProjectionError("STANDALONE_STAGE_EDGES_MUST_BE_EMPTY", repr(stage))
|
||||||
|
return stage, task
|
||||||
|
|
||||||
|
|
||||||
|
def _import_root(name: str | None) -> str:
|
||||||
|
return (name or "").split(".", 1)[0]
|
||||||
|
|
||||||
|
|
||||||
|
def _attribute_pair(node: ast.Attribute) -> tuple[str, str] | None:
|
||||||
|
if isinstance(node.value, ast.Name):
|
||||||
|
return node.value.id, node.attr
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _is_http_client_receiver(
|
||||||
|
node: ast.expr,
|
||||||
|
derived_client_names: set[str] | None = None,
|
||||||
|
) -> bool:
|
||||||
|
if isinstance(node, ast.Name):
|
||||||
|
return node.id in {"client", "http_client", "httpx"} | (derived_client_names or set())
|
||||||
|
if isinstance(node, ast.Attribute):
|
||||||
|
return (
|
||||||
|
isinstance(node.value, ast.Name)
|
||||||
|
and node.value.id == "self"
|
||||||
|
and node.attr in {"client", "http_client"}
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _is_direct_localdocs_post(call: ast.Call) -> bool:
|
||||||
|
if not isinstance(call.func, ast.Attribute) or call.func.attr != "post":
|
||||||
|
return False
|
||||||
|
receiver = call.func.value
|
||||||
|
if not (
|
||||||
|
isinstance(receiver, ast.Attribute)
|
||||||
|
and isinstance(receiver.value, ast.Name)
|
||||||
|
and receiver.value.id == "self"
|
||||||
|
and receiver.attr == "client"
|
||||||
|
):
|
||||||
|
return False
|
||||||
|
return _is_localdocs_endpoint(_network_endpoint(call) or ast.Constant(value=None))
|
||||||
|
|
||||||
|
|
||||||
|
def _network_endpoint(call: ast.Call) -> ast.expr | None:
|
||||||
|
"""Return a statically identifiable httpx/client endpoint expression."""
|
||||||
|
|
||||||
|
if not isinstance(call.func, ast.Attribute) or call.func.attr not in HTTP_NETWORK_METHODS:
|
||||||
|
return None
|
||||||
|
if not _is_http_client_receiver(call.func.value):
|
||||||
|
return None
|
||||||
|
for keyword in call.keywords:
|
||||||
|
if keyword.arg == "url":
|
||||||
|
return keyword.value
|
||||||
|
index = 1 if call.func.attr == "request" else 0
|
||||||
|
return call.args[index] if len(call.args) > index else ast.Constant(value=None)
|
||||||
|
|
||||||
|
|
||||||
|
def _is_localdocs_endpoint(node: ast.expr) -> bool:
|
||||||
|
return (
|
||||||
|
isinstance(node, ast.Name)
|
||||||
|
and node.id == "LOCALDOCS_URL"
|
||||||
|
or isinstance(node, ast.Constant)
|
||||||
|
and node.value == EXPECTED_LOCALDOCS_URL
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_inline_code(code: str) -> dict[str, Any]:
|
||||||
|
code_bytes = code.encode("utf-8")
|
||||||
|
try:
|
||||||
|
compile(code, "<Stage_2_S2_00.parameters.code>", "exec", dont_inherit=True)
|
||||||
|
tree = ast.parse(code, filename="<Stage_2_S2_00.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]
|
||||||
|
missing_tokens.extend(
|
||||||
|
"|".join(alternatives)
|
||||||
|
for alternatives in REQUIRED_CODE_TOKEN_ALTERNATIVES
|
||||||
|
if not any(token in code for token in alternatives)
|
||||||
|
)
|
||||||
|
if missing_tokens:
|
||||||
|
raise ProjectionError("INLINE_CODE_REQUIRED_TOKEN_MISSING", ",".join(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()
|
||||||
|
forbidden_nodes: list[str] = []
|
||||||
|
external_urls: set[str] = set()
|
||||||
|
forbidden_network_endpoints: set[str] = set()
|
||||||
|
project_source_refs: set[str] = set()
|
||||||
|
derived_client_names: set[str] = set()
|
||||||
|
for candidate in ast.walk(tree):
|
||||||
|
if not isinstance(candidate, (ast.Assign, ast.AnnAssign)):
|
||||||
|
continue
|
||||||
|
value = candidate.value
|
||||||
|
if not (
|
||||||
|
isinstance(value, ast.Call)
|
||||||
|
and isinstance(value.func, ast.Attribute)
|
||||||
|
and isinstance(value.func.value, ast.Name)
|
||||||
|
and value.func.value.id == "httpx"
|
||||||
|
and value.func.attr in {"Client", "AsyncClient"}
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
targets = candidate.targets if isinstance(candidate, ast.Assign) else [candidate.target]
|
||||||
|
derived_client_names.update(target.id for target in targets if isinstance(target, ast.Name))
|
||||||
|
for node in ast.walk(tree):
|
||||||
|
if isinstance(node, ast.Import):
|
||||||
|
imports.update(_import_root(alias.name) for alias in node.names)
|
||||||
|
elif isinstance(node, ast.ImportFrom):
|
||||||
|
if node.level:
|
||||||
|
forbidden_nodes.append(f"relative-import:{node.module or ''}")
|
||||||
|
imports.add(_import_root(node.module))
|
||||||
|
elif isinstance(node, ast.Pass):
|
||||||
|
forbidden_nodes.append("Pass")
|
||||||
|
elif isinstance(node, ast.Expr) and isinstance(node.value, ast.Constant):
|
||||||
|
if node.value.value is Ellipsis:
|
||||||
|
forbidden_nodes.append("Ellipsis-expression")
|
||||||
|
elif isinstance(node, (ast.Assign, ast.AnnAssign)):
|
||||||
|
if isinstance(node.value, ast.Constant) and node.value.value is Ellipsis:
|
||||||
|
forbidden_nodes.append("Ellipsis-assignment")
|
||||||
|
if (
|
||||||
|
isinstance(node.value, ast.Attribute)
|
||||||
|
and node.value.attr in HTTP_NETWORK_METHODS
|
||||||
|
and _is_http_client_receiver(node.value.value, derived_client_names)
|
||||||
|
):
|
||||||
|
forbidden_nodes.append(f"network-method-alias:{node.value.attr}")
|
||||||
|
elif isinstance(node, ast.Constant):
|
||||||
|
if isinstance(node.value, str):
|
||||||
|
for match in URL_RE.findall(node.value):
|
||||||
|
normalized = match.rstrip(".,);]")
|
||||||
|
if (
|
||||||
|
normalized != EXPECTED_LOCALDOCS_URL
|
||||||
|
and not normalized.startswith(ALLOWED_IDENTIFIER_URL_PREFIXES)
|
||||||
|
):
|
||||||
|
external_urls.add(normalized)
|
||||||
|
if PYTHON_SOURCE_REF_RE.search(node.value.strip()):
|
||||||
|
project_source_refs.add(node.value[:200])
|
||||||
|
if node.value.strip().lower() in {
|
||||||
|
"todo",
|
||||||
|
"tbd",
|
||||||
|
"placeholder",
|
||||||
|
"omitted",
|
||||||
|
"implement me",
|
||||||
|
"...",
|
||||||
|
}:
|
||||||
|
forbidden_nodes.append(f"placeholder-string:{node.value!r}")
|
||||||
|
elif isinstance(node, ast.Call):
|
||||||
|
if (
|
||||||
|
isinstance(node.func, ast.Attribute)
|
||||||
|
and node.func.attr in HTTP_NETWORK_METHODS
|
||||||
|
and _is_http_client_receiver(node.func.value, derived_client_names)
|
||||||
|
):
|
||||||
|
if not _is_direct_localdocs_post(node):
|
||||||
|
forbidden_network_endpoints.add(ast.unparse(node.func)[:200])
|
||||||
|
if (
|
||||||
|
isinstance(node.func, ast.Name)
|
||||||
|
and node.func.id == "getattr"
|
||||||
|
and len(node.args) >= 2
|
||||||
|
and isinstance(node.args[1], ast.Constant)
|
||||||
|
and node.args[1].value in HTTP_NETWORK_METHODS
|
||||||
|
):
|
||||||
|
forbidden_nodes.append(f"dynamic-network-method:{node.args[1].value}")
|
||||||
|
if isinstance(node.func, ast.Name) and node.func.id in FORBIDDEN_CALL_NAMES:
|
||||||
|
forbidden_nodes.append(f"call:{node.func.id}")
|
||||||
|
elif isinstance(node.func, ast.Attribute):
|
||||||
|
pair = _attribute_pair(node.func)
|
||||||
|
if pair in FORBIDDEN_ATTRIBUTE_CALLS:
|
||||||
|
forbidden_nodes.append(f"call:{pair[0]}.{pair[1]}")
|
||||||
|
elif isinstance(node, ast.Raise):
|
||||||
|
target = node.exc
|
||||||
|
if isinstance(target, ast.Call):
|
||||||
|
target = target.func
|
||||||
|
if isinstance(target, ast.Name) and target.id == "NotImplementedError":
|
||||||
|
forbidden_nodes.append("raise:NotImplementedError")
|
||||||
|
|
||||||
|
imports.discard("")
|
||||||
|
disallowed_imports = sorted(
|
||||||
|
root
|
||||||
|
for root in imports
|
||||||
|
if root in FORBIDDEN_IMPORT_ROOTS
|
||||||
|
or (root not in sys.stdlib_module_names and root not in ALLOWED_NON_STDLIB_IMPORTS)
|
||||||
|
)
|
||||||
|
if disallowed_imports:
|
||||||
|
raise ProjectionError("INLINE_CODE_IMPORT_FORBIDDEN", ",".join(disallowed_imports))
|
||||||
|
if forbidden_nodes:
|
||||||
|
raise ProjectionError("INLINE_CODE_DYNAMIC_OR_PLACEHOLDER_FORBIDDEN", ",".join(forbidden_nodes))
|
||||||
|
if external_urls:
|
||||||
|
raise ProjectionError("INLINE_CODE_EXTERNAL_URL_FORBIDDEN", ",".join(sorted(external_urls)))
|
||||||
|
if forbidden_network_endpoints:
|
||||||
|
raise ProjectionError(
|
||||||
|
"INLINE_CODE_NETWORK_ENDPOINT_FORBIDDEN",
|
||||||
|
",".join(sorted(forbidden_network_endpoints)),
|
||||||
|
)
|
||||||
|
if project_source_refs:
|
||||||
|
raise ProjectionError(
|
||||||
|
"INLINE_CODE_EXTERNAL_PY_SOURCE_REF_FORBIDDEN", ",".join(sorted(project_source_refs))
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"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_url_count": 0,
|
||||||
|
"placeholder_count": 0,
|
||||||
|
"plaintext_secret_count": 0,
|
||||||
|
"external_python_source_ref_count": 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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"),
|
||||||
|
},
|
||||||
|
"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 ("language", "requirements", "network", "timeout")
|
||||||
|
},
|
||||||
|
"code_sha256": sha256_bytes(parameters["code"].encode("utf-8")),
|
||||||
|
},
|
||||||
|
"task_procedure": stage.get("task_procedure"),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
semantics_sha256 = sha256_bytes(canonical_json_bytes(semantics))
|
||||||
|
|
||||||
|
receipt = {
|
||||||
|
"schema_version": "stage2_s2_00_inline_code_receipt.v1",
|
||||||
|
"workflow_id": "S2_00",
|
||||||
|
"build_kind": "OFFLINE_AUTHORING_PROJECTION",
|
||||||
|
"source_of_truth": _logical_path(AUTHORING_PATH),
|
||||||
|
"authoring_rewritten": False,
|
||||||
|
"authoring": {
|
||||||
|
"path": _logical_path(AUTHORING_PATH),
|
||||||
|
"sha256": sha256_bytes(authoring_raw),
|
||||||
|
"size_bytes": len(authoring_raw),
|
||||||
|
"unique_key_parse": "PASS",
|
||||||
|
},
|
||||||
|
"deployment_projection": {
|
||||||
|
"path": _logical_path(PROJECTION_PATH),
|
||||||
|
"sha256": sha256_bytes(authoring_raw),
|
||||||
|
"size_bytes": len(authoring_raw),
|
||||||
|
"byte_identical_to_authoring": True,
|
||||||
|
"canonical_task_semantics_sha256": semantics_sha256,
|
||||||
|
},
|
||||||
|
"canonical_code": {
|
||||||
|
"yaml_pointer": "/Agent/Stages/0/tasks/0/parameters/code",
|
||||||
|
"encoding": "UTF-8",
|
||||||
|
"extraction_transform": "NONE",
|
||||||
|
**validation,
|
||||||
|
},
|
||||||
|
"full_code_mirrors": [
|
||||||
|
{
|
||||||
|
"path": _logical_path(MIRROR_PY_PATH),
|
||||||
|
"sha256": validation["code_sha256"],
|
||||||
|
"size_bytes": len(code_bytes),
|
||||||
|
"byte_identical_to_canonical_code": True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": _logical_path(MIRROR_TXT_PATH),
|
||||||
|
"sha256": validation["code_sha256"],
|
||||||
|
"size_bytes": len(code_bytes),
|
||||||
|
"byte_identical_to_canonical_code": True,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"task_contract": semantics,
|
||||||
|
"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:
|
||||||
|
continue
|
||||||
|
_atomic_write(path, payload)
|
||||||
|
after_artifacts = AUTHORING_PATH.read_bytes()
|
||||||
|
if after_artifacts != before:
|
||||||
|
raise ProjectionError(
|
||||||
|
"AUTHORING_CHANGED_DURING_BUILD",
|
||||||
|
f"before={sha256_bytes(before)} after={sha256_bytes(after_artifacts)}",
|
||||||
|
)
|
||||||
|
_atomic_write(RECEIPT_PATH, receipt_payload)
|
||||||
|
after_receipt = AUTHORING_PATH.read_bytes()
|
||||||
|
if after_receipt != before:
|
||||||
|
raise ProjectionError(
|
||||||
|
"AUTHORING_CHANGED_DURING_RECEIPT_WRITE",
|
||||||
|
f"before={sha256_bytes(before)} after={sha256_bytes(after_receipt)}",
|
||||||
|
)
|
||||||
|
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 the S2_00 inline Agent projection"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--check",
|
||||||
|
action="store_true",
|
||||||
|
help="perform a no-write byte-parity check against generated outputs",
|
||||||
|
)
|
||||||
|
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" 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())
|
||||||
+930
-23
File diff suppressed because it is too large
Load Diff
+930
-23
File diff suppressed because it is too large
Load Diff
+632
-34
@@ -1,12 +1,16 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
"$id": "https://schemas.liti-agent.local/stage2/shared/deployment.schema.v1.json",
|
"$id": "https://schemas.liti-agent.local/stage2/shared/deployment.schema.v1.json",
|
||||||
"title": "Stage 2 release, loader binding and invocation receipt",
|
"title": "Stage 2 release, direct Code Executor binding and invocation receipts",
|
||||||
"schema_version": "stage2_deployment.v1",
|
"schema_version": "stage2_deployment.v1.1",
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
{"$ref": "#/$defs/workflow_binding"},
|
{"$ref": "#/$defs/code_executor_binding"},
|
||||||
{"$ref": "#/$defs/stage2_release"},
|
{"$ref": "#/$defs/inline_code_receipt"},
|
||||||
{"$ref": "#/$defs/loader_receipt"}
|
{"$ref": "#/$defs/code_executor_outer_result"},
|
||||||
|
{"$ref": "#/$defs/s2_00_inner_receipt"},
|
||||||
|
{"$ref": "#/$defs/logical_publish_receipt"},
|
||||||
|
{"$ref": "#/$defs/superseded_loader_binding"},
|
||||||
|
{"$ref": "#/$defs/stage2_release"}
|
||||||
],
|
],
|
||||||
"$defs": {
|
"$defs": {
|
||||||
"sha256": {
|
"sha256": {
|
||||||
@@ -46,6 +50,524 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"external_execution_binding_ref": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"$comment": "Cycle-breaking release pointer. The executor binding independently binds the release raw hash at the external trust boundary.",
|
||||||
|
"required": ["asset_id", "path", "schema_id", "binding_status"],
|
||||||
|
"properties": {
|
||||||
|
"asset_id": {"$ref": "#/$defs/nonempty_string"},
|
||||||
|
"path": {"const": "deployment/stage2_code_executor_binding.yml"},
|
||||||
|
"schema_id": {"const": "stage2_code_executor_binding.v1"},
|
||||||
|
"binding_status": {
|
||||||
|
"enum": [
|
||||||
|
"EXTERNAL_TRUST_ROOT_PENDING_LIVE_ADMISSION",
|
||||||
|
"EXTERNAL_TRUST_ROOT_ADMITTED"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dependency_lock": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["requirements", "requirements_sha256", "lock_status"],
|
||||||
|
"properties": {
|
||||||
|
"requirements": {"const": "httpx==0.28.1"},
|
||||||
|
"requirements_sha256": {"$ref": "#/$defs/sha256"},
|
||||||
|
"lock_status": {"enum": ["STATIC_PIN_BOUND", "LIVE_BACKEND_PENDING"]}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"code_executor_binding": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"schema_version",
|
||||||
|
"binding_id",
|
||||||
|
"workflow_id",
|
||||||
|
"execution_class",
|
||||||
|
"active_runtime_authority",
|
||||||
|
"agent_script_ref",
|
||||||
|
"workflow_contract_ref",
|
||||||
|
"inline_code_receipt_ref",
|
||||||
|
"stage2_release_ref",
|
||||||
|
"expected_release_sha256",
|
||||||
|
"agent_script_sha256",
|
||||||
|
"canonical_code_sha256",
|
||||||
|
"mcp_server_id",
|
||||||
|
"tool_name",
|
||||||
|
"language",
|
||||||
|
"network",
|
||||||
|
"timeout_seconds",
|
||||||
|
"runtime_image_digest",
|
||||||
|
"runtime_image_status",
|
||||||
|
"dependency_lock",
|
||||||
|
"localdocs_contract",
|
||||||
|
"egress_profile_id",
|
||||||
|
"egress_profile_status",
|
||||||
|
"downstream_llm_candidate_bindings",
|
||||||
|
"live_admission_status",
|
||||||
|
"legacy_fallbacks"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"schema_version": {"const": "stage2_code_executor_binding.v1"},
|
||||||
|
"binding_id": {"$ref": "#/$defs/nonempty_string"},
|
||||||
|
"workflow_id": {"const": "S2_00"},
|
||||||
|
"execution_class": {"const": "NON-LLM-DETERMINISTIC"},
|
||||||
|
"active_runtime_authority": {"const": true},
|
||||||
|
"agent_script_ref": {"$ref": "#/$defs/asset_ref"},
|
||||||
|
"workflow_contract_ref": {"$ref": "#/$defs/asset_ref"},
|
||||||
|
"inline_code_receipt_ref": {"$ref": "#/$defs/asset_ref"},
|
||||||
|
"stage2_release_ref": {"$ref": "#/$defs/asset_ref"},
|
||||||
|
"expected_release_sha256": {"$ref": "#/$defs/bindable_sha256"},
|
||||||
|
"agent_script_sha256": {"$ref": "#/$defs/bindable_sha256"},
|
||||||
|
"canonical_code_sha256": {"$ref": "#/$defs/bindable_sha256"},
|
||||||
|
"mcp_server_id": {"const": "code-executor"},
|
||||||
|
"tool_name": {"const": "run_code"},
|
||||||
|
"language": {"const": "python"},
|
||||||
|
"network": {"const": "agent-network"},
|
||||||
|
"timeout_seconds": {"const": 300},
|
||||||
|
"runtime_image_digest": {"$ref": "#/$defs/bindable_sha256"},
|
||||||
|
"runtime_image_status": {
|
||||||
|
"enum": ["PINNED_VERIFIED", "PENDING_BACKEND_EVIDENCE"]
|
||||||
|
},
|
||||||
|
"dependency_lock": {"$ref": "#/$defs/dependency_lock"},
|
||||||
|
"localdocs_contract": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"endpoint",
|
||||||
|
"user_id_template",
|
||||||
|
"workspace_id_template",
|
||||||
|
"tool_allowlist",
|
||||||
|
"fixed_request_path",
|
||||||
|
"read_path_allowlist",
|
||||||
|
"write_root_rule"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"endpoint": {"const": "http://mcp-localdocs:8012/mcp"},
|
||||||
|
"user_id_template": {"const": "{{__user_hash__}}"},
|
||||||
|
"workspace_id_template": {"const": "{{__workspace_hash__}}"},
|
||||||
|
"tool_allowlist": {
|
||||||
|
"type": "array",
|
||||||
|
"prefixItems": [
|
||||||
|
{"const": "read_binary_doc"},
|
||||||
|
{"const": "write_binary_file"}
|
||||||
|
],
|
||||||
|
"items": false,
|
||||||
|
"minItems": 2,
|
||||||
|
"maxItems": 2
|
||||||
|
},
|
||||||
|
"fixed_request_path": {"const": "stage2_control/s2_00_request.json"},
|
||||||
|
"read_path_allowlist": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {"$ref": "#/$defs/path_ref"},
|
||||||
|
"minItems": 1,
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
"write_root_rule": {
|
||||||
|
"const": "stage2_runs/by-binding/<run_binding_digest>/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"egress_profile_id": {"const": "S2_00_LOCALDOCS_ONLY_V1"},
|
||||||
|
"egress_profile_status": {
|
||||||
|
"enum": ["STATIC_ALLOWLIST_BOUND", "PENDING_LIVE_VERIFICATION"]
|
||||||
|
},
|
||||||
|
"downstream_llm_candidate_bindings": {
|
||||||
|
"type": "array",
|
||||||
|
"prefixItems": [
|
||||||
|
{"$ref": "#/$defs/downstream_llm_candidate_binding"}
|
||||||
|
],
|
||||||
|
"items": false,
|
||||||
|
"minItems": 1,
|
||||||
|
"maxItems": 1
|
||||||
|
},
|
||||||
|
"live_admission_status": {
|
||||||
|
"enum": [
|
||||||
|
"PENDING_SECRET_BINDING",
|
||||||
|
"PENDING_LIVE_EVIDENCE",
|
||||||
|
"LIVE_CANARY_ADMITTED"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"legacy_fallbacks": {"type": "array", "maxItems": 0}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"inline_code_receipt": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"schema_version",
|
||||||
|
"workflow_id",
|
||||||
|
"build_kind",
|
||||||
|
"source_of_truth",
|
||||||
|
"authoring_rewritten",
|
||||||
|
"authoring",
|
||||||
|
"deployment_projection",
|
||||||
|
"canonical_code",
|
||||||
|
"full_code_mirrors",
|
||||||
|
"task_contract",
|
||||||
|
"parity_status"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"schema_version": {"const": "stage2_s2_00_inline_code_receipt.v1"},
|
||||||
|
"workflow_id": {"const": "S2_00"},
|
||||||
|
"build_kind": {"const": "OFFLINE_AUTHORING_PROJECTION"},
|
||||||
|
"source_of_truth": {"const": "Stage_2_S2_00_v.1.yml"},
|
||||||
|
"authoring_rewritten": {"const": false},
|
||||||
|
"authoring": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["path", "sha256", "size_bytes", "unique_key_parse"],
|
||||||
|
"properties": {
|
||||||
|
"path": {"const": "Stage_2_S2_00_v.1.yml"},
|
||||||
|
"sha256": {"$ref": "#/$defs/sha256"},
|
||||||
|
"size_bytes": {"type": "integer", "minimum": 1},
|
||||||
|
"unique_key_parse": {"const": "PASS"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deployment_projection": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"path",
|
||||||
|
"sha256",
|
||||||
|
"size_bytes",
|
||||||
|
"byte_identical_to_authoring",
|
||||||
|
"canonical_task_semantics_sha256"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"path": {"const": "Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00.yml"},
|
||||||
|
"sha256": {"$ref": "#/$defs/sha256"},
|
||||||
|
"size_bytes": {"type": "integer", "minimum": 1},
|
||||||
|
"byte_identical_to_authoring": {"const": true},
|
||||||
|
"canonical_task_semantics_sha256": {"$ref": "#/$defs/sha256"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"canonical_code": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"ast_status",
|
||||||
|
"code_sha256",
|
||||||
|
"code_size_bytes",
|
||||||
|
"compile_status",
|
||||||
|
"encoding",
|
||||||
|
"external_python_source_ref_count",
|
||||||
|
"external_url_count",
|
||||||
|
"extraction_transform",
|
||||||
|
"forbidden_dynamic_call_count",
|
||||||
|
"forbidden_import_count",
|
||||||
|
"imports",
|
||||||
|
"placeholder_count",
|
||||||
|
"plaintext_secret_count",
|
||||||
|
"yaml_pointer"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"ast_status": {"const": "PASS"},
|
||||||
|
"code_sha256": {"$ref": "#/$defs/sha256"},
|
||||||
|
"code_size_bytes": {"type": "integer", "minimum": 1},
|
||||||
|
"compile_status": {"const": "PASS"},
|
||||||
|
"encoding": {"const": "UTF-8"},
|
||||||
|
"external_python_source_ref_count": {"const": 0},
|
||||||
|
"external_url_count": {"const": 0},
|
||||||
|
"extraction_transform": {"const": "NONE"},
|
||||||
|
"forbidden_dynamic_call_count": {"const": 0},
|
||||||
|
"forbidden_import_count": {"const": 0},
|
||||||
|
"imports": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {"$ref": "#/$defs/nonempty_string"},
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
"placeholder_count": {"const": 0},
|
||||||
|
"plaintext_secret_count": {"const": 0},
|
||||||
|
"yaml_pointer": {"const": "/Agent/Stages/0/tasks/0/parameters/code"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"full_code_mirrors": {
|
||||||
|
"type": "array",
|
||||||
|
"prefixItems": [
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["path", "sha256", "size_bytes", "byte_identical_to_canonical_code"],
|
||||||
|
"properties": {
|
||||||
|
"path": {"const": "Default_Agent/Stage_2_Clean/runtime/s2_00_ingress.py"},
|
||||||
|
"sha256": {"$ref": "#/$defs/sha256"},
|
||||||
|
"size_bytes": {"type": "integer", "minimum": 1},
|
||||||
|
"byte_identical_to_canonical_code": {"const": true}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["path", "sha256", "size_bytes", "byte_identical_to_canonical_code"],
|
||||||
|
"properties": {
|
||||||
|
"path": {"const": "Default_Agent/Stage_2_Clean/runtime/s2_00_ingress.txt"},
|
||||||
|
"sha256": {"$ref": "#/$defs/sha256"},
|
||||||
|
"size_bytes": {"type": "integer", "minimum": 1},
|
||||||
|
"byte_identical_to_canonical_code": {"const": true}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"items": false,
|
||||||
|
"minItems": 2,
|
||||||
|
"maxItems": 2
|
||||||
|
},
|
||||||
|
"task_contract": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["agent", "mcp_servers", "stage", "task", "task_procedure"],
|
||||||
|
"properties": {
|
||||||
|
"agent": {"const": {"name": "Stage_2_S2_00_v1", "version": "1.1.0"}},
|
||||||
|
"mcp_servers": {
|
||||||
|
"const": {
|
||||||
|
"code-executor": {"type": "streamable-http", "url": "https://code-executor.mcp.eroomai.com/mcp"},
|
||||||
|
"localdocs": {"type": "streamable-http", "url": "http://mcp-localdocs:8012/mcp"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"stage": {"const": {"name": "S2_00", "nexts": [], "prevs": []}},
|
||||||
|
"task": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["task_name", "mcp", "tool_name", "parameters", "code_sha256"],
|
||||||
|
"properties": {
|
||||||
|
"task_name": {"const": "Task_S2_00_deterministic_ingress"},
|
||||||
|
"mcp": {"const": "code-executor"},
|
||||||
|
"tool_name": {"const": "run_code"},
|
||||||
|
"parameters": {
|
||||||
|
"const": {
|
||||||
|
"language": "python",
|
||||||
|
"requirements": "httpx==0.28.1",
|
||||||
|
"network": "agent-network",
|
||||||
|
"timeout": 300
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"code_sha256": {"$ref": "#/$defs/sha256"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"task_procedure": {
|
||||||
|
"const": {
|
||||||
|
"IN": {"nexts": ["Task_S2_00_deterministic_ingress"], "wait_until": []},
|
||||||
|
"Task_S2_00_deterministic_ingress": {"nexts": ["OUT"], "wait_until": ["IN"]},
|
||||||
|
"OUT": {"nexts": [], "wait_until": ["Task_S2_00_deterministic_ingress"]}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parity_status": {"const": "PASS"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"published_artifact_ref": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["logical_artifact_id", "path", "schema_id", "raw_sha256", "byte_length"],
|
||||||
|
"properties": {
|
||||||
|
"logical_artifact_id": {"$ref": "#/$defs/nonempty_string"},
|
||||||
|
"path": {"$ref": "#/$defs/path_ref"},
|
||||||
|
"schema_id": {"$ref": "#/$defs/nonempty_string"},
|
||||||
|
"raw_sha256": {"$ref": "#/$defs/sha256"},
|
||||||
|
"byte_length": {"type": "integer", "minimum": 0}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"logical_publish_receipt": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"schema_version",
|
||||||
|
"barrier_id",
|
||||||
|
"barrier_path",
|
||||||
|
"publish_semantics",
|
||||||
|
"canonical_output_root",
|
||||||
|
"run_binding_digest",
|
||||||
|
"branch",
|
||||||
|
"artifacts",
|
||||||
|
"artifact_set_digest",
|
||||||
|
"barrier_raw_sha256",
|
||||||
|
"non_status_artifacts_read_back_verified",
|
||||||
|
"barrier_written_last",
|
||||||
|
"downstream_consumption_allowed",
|
||||||
|
"publication_status"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"schema_version": {"const": "stage2_logical_publish_receipt.v1"},
|
||||||
|
"barrier_id": {"const": "S2_00_INGRESS_STATUS_BARRIER"},
|
||||||
|
"barrier_path": {"const": "ingress/ingress_status.json"},
|
||||||
|
"publish_semantics": {"const": "STATUS_LAST_LOGICAL_COMMIT"},
|
||||||
|
"canonical_output_root": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^stage2_runs/by-binding/[a-f0-9]{64}/$"
|
||||||
|
},
|
||||||
|
"run_binding_digest": {"$ref": "#/$defs/sha256"},
|
||||||
|
"branch": {"enum": ["NORMAL", "DIAGNOSTIC"]},
|
||||||
|
"artifacts": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {"$ref": "#/$defs/published_artifact_ref"}
|
||||||
|
},
|
||||||
|
"artifact_set_digest": {"$ref": "#/$defs/sha256"},
|
||||||
|
"barrier_raw_sha256": {"$ref": "#/$defs/sha256"},
|
||||||
|
"non_status_artifacts_read_back_verified": {"const": true},
|
||||||
|
"barrier_written_last": {"const": true},
|
||||||
|
"downstream_consumption_allowed": {"type": "boolean"},
|
||||||
|
"publication_status": {
|
||||||
|
"enum": ["PUBLISHED_STATUS_LAST", "IDEMPOTENT_SUCCESS"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"$comment": "The output-root digest segment and all run identities are recomputed and compared to run_binding_digest by deterministic validation."
|
||||||
|
},
|
||||||
|
"s2_00_inner_receipt": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"schema_version",
|
||||||
|
"workflow_id",
|
||||||
|
"ok",
|
||||||
|
"status",
|
||||||
|
"request_id",
|
||||||
|
"route",
|
||||||
|
"run_id",
|
||||||
|
"run_binding_digest",
|
||||||
|
"expected_release_sha256",
|
||||||
|
"algorithm_digest",
|
||||||
|
"artifact_set_digest",
|
||||||
|
"ingress_status_sha256",
|
||||||
|
"logical_publish_receipt"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"schema_version": {"const": "stage2_s2_00_inner_receipt.v1"},
|
||||||
|
"workflow_id": {"const": "S2_00"},
|
||||||
|
"ok": {"const": true},
|
||||||
|
"status": {"enum": ["SUCCEEDED", "DIAGNOSTIC_PUBLISHED"]},
|
||||||
|
"request_id": {"$ref": "#/$defs/nonempty_string"},
|
||||||
|
"route": {"enum": ["TO_S2_10", "TO_S2_10_WITH_ISSUES", "TO_S2_40_STATUS_ONLY"]},
|
||||||
|
"run_id": {"type": "string", "pattern": "^S2RUN-[a-f0-9]{64}$"},
|
||||||
|
"run_binding_digest": {"$ref": "#/$defs/sha256"},
|
||||||
|
"expected_release_sha256": {"$ref": "#/$defs/sha256"},
|
||||||
|
"algorithm_digest": {"$ref": "#/$defs/sha256"},
|
||||||
|
"artifact_set_digest": {"$ref": "#/$defs/sha256"},
|
||||||
|
"ingress_status_sha256": {"$ref": "#/$defs/sha256"},
|
||||||
|
"logical_publish_receipt": {"$ref": "#/$defs/logical_publish_receipt"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["schema_version", "workflow_id", "ok", "status", "expected_release_sha256", "error"],
|
||||||
|
"properties": {
|
||||||
|
"schema_version": {"const": "stage2_s2_00_inner_receipt.v1"},
|
||||||
|
"workflow_id": {"const": "S2_00"},
|
||||||
|
"ok": {"const": false},
|
||||||
|
"status": {"const": "FAILED_NO_BARRIER"},
|
||||||
|
"expected_release_sha256": {"$ref": "#/$defs/sha256"},
|
||||||
|
"error": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["code", "message"],
|
||||||
|
"properties": {
|
||||||
|
"code": {"$ref": "#/$defs/nonempty_string"},
|
||||||
|
"message": {"type": "string"},
|
||||||
|
"logical_input_id": {"$ref": "#/$defs/nonempty_string"},
|
||||||
|
"details": {"type": "object"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"code_executor_outer_result": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"schema_version",
|
||||||
|
"workflow_id",
|
||||||
|
"task_name",
|
||||||
|
"mcp_server_id",
|
||||||
|
"tool_name",
|
||||||
|
"expected_release_sha256",
|
||||||
|
"agent_script_sha256",
|
||||||
|
"canonical_code_sha256",
|
||||||
|
"runtime_image_digest",
|
||||||
|
"runtime_image_status",
|
||||||
|
"dependency_lock",
|
||||||
|
"egress_profile_id",
|
||||||
|
"egress_profile_status",
|
||||||
|
"inner_receipt_sha256",
|
||||||
|
"tool_result_status",
|
||||||
|
"live_admission_status"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"schema_version": {"const": "stage2_code_executor_outer_result.v1"},
|
||||||
|
"workflow_id": {"const": "S2_00"},
|
||||||
|
"task_name": {"const": "Task_S2_00_deterministic_ingress"},
|
||||||
|
"mcp_server_id": {"const": "code-executor"},
|
||||||
|
"tool_name": {"const": "run_code"},
|
||||||
|
"expected_release_sha256": {"$ref": "#/$defs/sha256"},
|
||||||
|
"agent_script_sha256": {"$ref": "#/$defs/sha256"},
|
||||||
|
"canonical_code_sha256": {"$ref": "#/$defs/sha256"},
|
||||||
|
"runtime_image_digest": {"$ref": "#/$defs/bindable_sha256"},
|
||||||
|
"runtime_image_status": {"enum": ["PINNED_VERIFIED", "PENDING_BACKEND_EVIDENCE"]},
|
||||||
|
"dependency_lock": {"$ref": "#/$defs/dependency_lock"},
|
||||||
|
"egress_profile_id": {"const": "S2_00_LOCALDOCS_ONLY_V1"},
|
||||||
|
"egress_profile_status": {"enum": ["VERIFIED", "PENDING_LIVE_VERIFICATION"]},
|
||||||
|
"inner_receipt_sha256": {
|
||||||
|
"oneOf": [
|
||||||
|
{"$ref": "#/$defs/sha256"},
|
||||||
|
{"type": "null"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"tool_result_status": {"enum": ["SUCCEEDED", "FAILED", "TIMED_OUT"]},
|
||||||
|
"live_admission_status": {
|
||||||
|
"enum": ["PENDING_SECRET_BINDING", "PENDING_LIVE_EVIDENCE", "LIVE_CANARY_ADMITTED"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"if": {
|
||||||
|
"properties": {"tool_result_status": {"const": "SUCCEEDED"}},
|
||||||
|
"required": ["tool_result_status"]
|
||||||
|
},
|
||||||
|
"then": {
|
||||||
|
"properties": {"inner_receipt_sha256": {"$ref": "#/$defs/sha256"}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"superseded_loader_binding": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"schema_version",
|
||||||
|
"binding_id",
|
||||||
|
"workflow_id",
|
||||||
|
"disposition",
|
||||||
|
"active_runtime_authority",
|
||||||
|
"invocation_allowed",
|
||||||
|
"fallback_allowed",
|
||||||
|
"superseded_by",
|
||||||
|
"retained_legacy_refs",
|
||||||
|
"reason_code"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"schema_version": {"const": "stage2_loader_binding.reference.v1"},
|
||||||
|
"binding_id": {"const": "S2-BINDING-S2_00-V1"},
|
||||||
|
"workflow_id": {"const": "S2_00"},
|
||||||
|
"disposition": {"const": "REFERENCE_ONLY_SUPERSEDED_FOR_S2_00"},
|
||||||
|
"active_runtime_authority": {"const": false},
|
||||||
|
"invocation_allowed": {"const": false},
|
||||||
|
"fallback_allowed": {"const": false},
|
||||||
|
"superseded_by": {"$ref": "#/$defs/external_execution_binding_ref"},
|
||||||
|
"retained_legacy_refs": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {"$ref": "#/$defs/path_ref"},
|
||||||
|
"minItems": 1,
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
"reason_code": {
|
||||||
|
"const": "O-06_SUPERSEDED_NOT_APPLICABLE_BY_DIRECT_MCP_CODE_EXECUTOR"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"fixed_argv_contract": {
|
"fixed_argv_contract": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
@@ -149,6 +671,8 @@
|
|||||||
"workflow_binding": {
|
"workflow_binding": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
|
"deprecated": true,
|
||||||
|
"$comment": "Historical host-loader shape retained for audit parsing only; it is not an S2_00 runtime authority or fallback in v1.1.",
|
||||||
"required": [
|
"required": [
|
||||||
"schema_version",
|
"schema_version",
|
||||||
"binding_id",
|
"binding_id",
|
||||||
@@ -354,7 +878,6 @@
|
|||||||
"raw_hash_source": {
|
"raw_hash_source": {
|
||||||
"description": "Static release records only the runtime hash authority. It never embeds case-run bytes or their observed raw hash.",
|
"description": "Static release records only the runtime hash authority. It never embeds case-run bytes or their observed raw hash.",
|
||||||
"enum": [
|
"enum": [
|
||||||
"COMPLETION_SEAL",
|
|
||||||
"MANIFEST_ROW",
|
"MANIFEST_ROW",
|
||||||
"UNAVAILABLE_DEV"
|
"UNAVAILABLE_DEV"
|
||||||
]
|
]
|
||||||
@@ -420,7 +943,7 @@
|
|||||||
"properties": {
|
"properties": {
|
||||||
"policy_id": {"const": "S2-CACHE-STATIC-PREFIX-V1"},
|
"policy_id": {"const": "S2-CACHE-STATIC-PREFIX-V1"},
|
||||||
"model_binding_ref": {
|
"model_binding_ref": {
|
||||||
"const": "deployment/stage2_loader_binding.yml#/downstream_llm_candidate_bindings/0"
|
"const": "deployment/stage2_code_executor_binding.yml#/downstream_llm_candidate_bindings/0"
|
||||||
},
|
},
|
||||||
"model_id": {"const": "gpt-5.6-sol"},
|
"model_id": {"const": "gpt-5.6-sol"},
|
||||||
"reasoning_effort": {"const": "ultra"},
|
"reasoning_effort": {"const": "ultra"},
|
||||||
@@ -488,6 +1011,30 @@
|
|||||||
"executable": {"const": false}
|
"executable": {"const": false}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"if": {
|
||||||
|
"properties": {"mode": {"const": "SUBSET_CANARY"}},
|
||||||
|
"required": ["mode"]
|
||||||
|
},
|
||||||
|
"then": {
|
||||||
|
"properties": {
|
||||||
|
"authority_release_status": {"const": "SUBSET_CANARY_ADMITTED"},
|
||||||
|
"executable": {"const": true}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"if": {
|
||||||
|
"properties": {"mode": {"const": "PRODUCTION"}},
|
||||||
|
"required": ["mode"]
|
||||||
|
},
|
||||||
|
"then": {
|
||||||
|
"properties": {
|
||||||
|
"authority_release_status": {"const": "PRODUCTION_ADMITTED"},
|
||||||
|
"executable": {"const": true}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -619,7 +1166,7 @@
|
|||||||
"signature",
|
"signature",
|
||||||
"authoring_root",
|
"authoring_root",
|
||||||
"module_manifest_ref",
|
"module_manifest_ref",
|
||||||
"loader_binding_ref",
|
"executor_binding_ref",
|
||||||
"stage1_sources",
|
"stage1_sources",
|
||||||
"dependency_locks",
|
"dependency_locks",
|
||||||
"adapter_decisions",
|
"adapter_decisions",
|
||||||
@@ -663,7 +1210,12 @@
|
|||||||
"const": "Case_02_Comparison_Research/YAML_Prompts/2. Stage_2/Default_Agent/Stage_2_Clean"
|
"const": "Case_02_Comparison_Research/YAML_Prompts/2. Stage_2/Default_Agent/Stage_2_Clean"
|
||||||
},
|
},
|
||||||
"module_manifest_ref": {"$ref": "#/$defs/asset_ref"},
|
"module_manifest_ref": {"$ref": "#/$defs/asset_ref"},
|
||||||
"loader_binding_ref": {"$ref": "#/$defs/asset_ref"},
|
"executor_binding_ref": {"$ref": "#/$defs/external_execution_binding_ref"},
|
||||||
|
"loader_binding_ref": {
|
||||||
|
"$ref": "#/$defs/asset_ref",
|
||||||
|
"deprecated": true,
|
||||||
|
"$comment": "Historical audit pointer only; it cannot authorize or provide fallback execution for S2_00."
|
||||||
|
},
|
||||||
"stage1_sources": {
|
"stage1_sources": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {"$ref": "#/$defs/stage1_source_contract_row"},
|
"items": {"$ref": "#/$defs/stage1_source_contract_row"},
|
||||||
@@ -683,7 +1235,6 @@
|
|||||||
"expected_concrete_path_count",
|
"expected_concrete_path_count",
|
||||||
"concrete_paths",
|
"concrete_paths",
|
||||||
"contract_manifest_ref",
|
"contract_manifest_ref",
|
||||||
"completion_seal_ref",
|
|
||||||
"closure_scope",
|
"closure_scope",
|
||||||
"full_stage1_runtime_release_status"
|
"full_stage1_runtime_release_status"
|
||||||
],
|
],
|
||||||
@@ -717,27 +1268,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"completion_seal_ref": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": false,
|
|
||||||
"required": ["status", "path", "sha256", "producer_id"],
|
|
||||||
"properties": {
|
|
||||||
"status": {
|
|
||||||
"enum": [
|
|
||||||
"PENDING_SEQUENTIAL_BIND",
|
|
||||||
"BOUND"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"path": {
|
|
||||||
"anyOf": [
|
|
||||||
{"$ref": "#/$defs/path_ref"},
|
|
||||||
{"const": "PENDING_SEQUENTIAL_BIND"}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"sha256": {"$ref": "#/$defs/bindable_sha256"},
|
|
||||||
"producer_id": {"$ref": "#/$defs/nonempty_string"}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"closure_scope": {
|
"closure_scope": {
|
||||||
"const": "REFERENCED_55_ONLY_NOT_FULL_STAGE1_RUNTIME_RELEASE"
|
"const": "REFERENCED_55_ONLY_NOT_FULL_STAGE1_RUNTIME_RELEASE"
|
||||||
},
|
},
|
||||||
@@ -798,17 +1328,85 @@
|
|||||||
{
|
{
|
||||||
"if": {
|
"if": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"release_class": {
|
"release_class": {"const": "DEV_FIXTURE_RELEASE"}
|
||||||
"enum": ["SUBSET_CANARY_RELEASE", "PRODUCTION_RELEASE"]
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"required": ["release_class"]
|
"required": ["release_class"]
|
||||||
},
|
},
|
||||||
|
"then": {
|
||||||
|
"properties": {
|
||||||
|
"release_status": {"enum": ["DRAFT_NOT_EXECUTABLE", "DEV_VALIDATED"]},
|
||||||
|
"authorization_status": {"const": "DEV_VALIDATION_ONLY"},
|
||||||
|
"executor_binding_ref": {
|
||||||
|
"properties": {
|
||||||
|
"binding_status": {"const": "EXTERNAL_TRUST_ROOT_PENDING_LIVE_ADMISSION"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bundle": {
|
||||||
|
"properties": {
|
||||||
|
"mode": {"const": "STRUCTURAL_FIXTURE"},
|
||||||
|
"authority_release_status": {"const": "DEV_UNAVAILABLE"},
|
||||||
|
"executable": {"const": false}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"if": {
|
||||||
|
"properties": {"release_class": {"const": "SUBSET_CANARY_RELEASE"}},
|
||||||
|
"required": ["release_class"]
|
||||||
|
},
|
||||||
"then": {
|
"then": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"release_status": {"const": "ADMITTED"},
|
"release_status": {"const": "ADMITTED"},
|
||||||
|
"authorization_status": {"const": "CANARY_ADMITTED"},
|
||||||
"release_digest": {"$ref": "#/$defs/sha256"},
|
"release_digest": {"$ref": "#/$defs/sha256"},
|
||||||
"signature": {"not": {"const": "PENDING_SEQUENTIAL_BIND"}},
|
"signature": {"not": {"const": "PENDING_SEQUENTIAL_BIND"}},
|
||||||
|
"executor_binding_ref": {
|
||||||
|
"properties": {
|
||||||
|
"binding_status": {"const": "EXTERNAL_TRUST_ROOT_ADMITTED"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bundle": {
|
||||||
|
"properties": {
|
||||||
|
"mode": {"const": "SUBSET_CANARY"},
|
||||||
|
"authority_release_status": {"const": "SUBSET_CANARY_ADMITTED"},
|
||||||
|
"executable": {"const": true}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"release_evidence": {
|
||||||
|
"contains": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {"status": {"const": "SIGNED_ADMITTED"}},
|
||||||
|
"required": ["status"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"if": {
|
||||||
|
"properties": {"release_class": {"const": "PRODUCTION_RELEASE"}},
|
||||||
|
"required": ["release_class"]
|
||||||
|
},
|
||||||
|
"then": {
|
||||||
|
"properties": {
|
||||||
|
"release_status": {"const": "ADMITTED"},
|
||||||
|
"authorization_status": {"const": "PRODUCTION_ADMITTED"},
|
||||||
|
"release_digest": {"$ref": "#/$defs/sha256"},
|
||||||
|
"signature": {"not": {"const": "PENDING_SEQUENTIAL_BIND"}},
|
||||||
|
"executor_binding_ref": {
|
||||||
|
"properties": {
|
||||||
|
"binding_status": {"const": "EXTERNAL_TRUST_ROOT_ADMITTED"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bundle": {
|
||||||
|
"properties": {
|
||||||
|
"mode": {"const": "PRODUCTION"},
|
||||||
|
"authority_release_status": {"const": "PRODUCTION_ADMITTED"},
|
||||||
|
"executable": {"const": true}
|
||||||
|
}
|
||||||
|
},
|
||||||
"release_evidence": {
|
"release_evidence": {
|
||||||
"contains": {
|
"contains": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|||||||
+186
-37
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
"$id": "https://schemas.liti-agent.local/stage2/s2_00/ingress.schema.v1.json",
|
"$id": "https://schemas.liti-agent.local/stage2/s2_00/ingress.schema.v1.json",
|
||||||
"title": "Stage 2 S2_00 ingress and routing artifacts",
|
"title": "Stage 2 S2_00 direct-Code-Executor ingress and routing artifacts",
|
||||||
"schema_version": "stage2_s2_00_ingress.v1",
|
"schema_version": "stage2_s2_00_ingress.v1.1",
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
{"$ref": "#/$defs/run_request"},
|
{"$ref": "#/$defs/execution_request"},
|
||||||
{"$ref": "#/$defs/stage1_input_manifest"},
|
{"$ref": "#/$defs/stage1_input_manifest"},
|
||||||
{"$ref": "#/$defs/intake_report"},
|
{"$ref": "#/$defs/intake_report"},
|
||||||
{"$ref": "#/$defs/ingress_status"},
|
{"$ref": "#/$defs/ingress_status"},
|
||||||
@@ -20,6 +20,21 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"minLength": 1
|
"minLength": 1
|
||||||
},
|
},
|
||||||
|
"logical_path": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"pattern": "^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$))(?!.*\\x00).+$"
|
||||||
|
},
|
||||||
|
"logical_root_ref": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 256,
|
||||||
|
"pattern": "^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$))(?!.*\\x00)[A-Za-z0-9][A-Za-z0-9._/-]*$"
|
||||||
|
},
|
||||||
|
"canonical_run_id": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^S2RUN-[a-f0-9]{64}$"
|
||||||
|
},
|
||||||
"string_set": {
|
"string_set": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {"$ref": "#/$defs/nonempty_string"},
|
"items": {"$ref": "#/$defs/nonempty_string"},
|
||||||
@@ -206,23 +221,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"run_request": {
|
"execution_request": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
|
"$comment": "The only accepted runtime request is read from stage2_control/s2_00_request.json. User/workspace identities and release class are backend/release bindings, not caller fields.",
|
||||||
"required": [
|
"required": [
|
||||||
"schema_version",
|
"schema_version",
|
||||||
"workflow_id",
|
"workflow_id",
|
||||||
"run_id",
|
"request_id",
|
||||||
"release_class",
|
"attempt_id",
|
||||||
"user_context_sha256",
|
|
||||||
"workspace_context_sha256",
|
|
||||||
"stage1_run_root_ref",
|
"stage1_run_root_ref",
|
||||||
"stage1_deployment_root_ref"
|
"stage1_deployment_root_ref"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"schema_version": {"const": "stage2_s2_00_run_request.v1"},
|
"schema_version": {"const": "stage2_s2_00_execution_request.v1"},
|
||||||
"workflow_id": {"const": "S2_00"},
|
"workflow_id": {"const": "S2_00"},
|
||||||
"run_id": {
|
"request_id": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$"
|
"pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$"
|
||||||
},
|
},
|
||||||
@@ -230,25 +244,15 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$"
|
"pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$"
|
||||||
},
|
},
|
||||||
"release_class": {
|
"stage1_run_root_ref": {"$ref": "#/$defs/logical_root_ref"},
|
||||||
"enum": [
|
"stage1_deployment_root_ref": {"$ref": "#/$defs/logical_root_ref"}
|
||||||
"DEV_FIXTURE_RELEASE",
|
|
||||||
"SUBSET_CANARY_RELEASE",
|
|
||||||
"PRODUCTION_RELEASE"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"user_context_sha256": {"$ref": "#/$defs/sha256"},
|
|
||||||
"workspace_context_sha256": {"$ref": "#/$defs/sha256"},
|
|
||||||
"stage1_run_root_ref": {
|
|
||||||
"type": "string",
|
|
||||||
"pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$"
|
|
||||||
},
|
|
||||||
"stage1_deployment_root_ref": {
|
|
||||||
"type": "string",
|
|
||||||
"pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"run_request": {
|
||||||
|
"$ref": "#/$defs/execution_request",
|
||||||
|
"deprecated": true,
|
||||||
|
"$comment": "Compatibility definition name only; it does not restore caller-supplied run_id, release, or host-loader invocation."
|
||||||
|
},
|
||||||
"all_expansion_contract": {
|
"all_expansion_contract": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
@@ -632,6 +636,58 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"binary_hydration_source_receipt": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"logical_input_id",
|
||||||
|
"logical_path",
|
||||||
|
"pass_1_raw_sha256",
|
||||||
|
"pass_2_raw_sha256",
|
||||||
|
"pass_1_byte_length",
|
||||||
|
"pass_2_byte_length",
|
||||||
|
"stability_status"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"logical_input_id": {"$ref": "#/$defs/nonempty_string"},
|
||||||
|
"logical_path": {"$ref": "#/$defs/logical_path"},
|
||||||
|
"pass_1_raw_sha256": {"$ref": "#/$defs/sha256"},
|
||||||
|
"pass_2_raw_sha256": {"$ref": "#/$defs/sha256"},
|
||||||
|
"pass_1_byte_length": {"type": "integer", "minimum": 0},
|
||||||
|
"pass_2_byte_length": {"type": "integer", "minimum": 0},
|
||||||
|
"stability_status": {"enum": ["STABLE", "CHANGED", "UNEVALUABLE"]}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"two_pass_hydration_stability_receipt": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"$comment": "Every release-enumerated source is read as binary at most twice. Equality is over the complete ordered logical-path to raw-hash map, not normalized text.",
|
||||||
|
"required": [
|
||||||
|
"schema_version",
|
||||||
|
"transport",
|
||||||
|
"read_policy",
|
||||||
|
"read_pass_count",
|
||||||
|
"max_read_passes",
|
||||||
|
"pass_1_raw_hash_map_digest",
|
||||||
|
"pass_2_raw_hash_map_digest",
|
||||||
|
"source_receipts",
|
||||||
|
"stability_status"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"schema_version": {"const": "stage2_s2_00_two_pass_hydration_receipt.v1"},
|
||||||
|
"transport": {"const": "localdocs.read_binary_doc"},
|
||||||
|
"read_policy": {"const": "BOUNDED_TWO_PASS_BINARY_RAW_HASH_MAP_EQUALITY"},
|
||||||
|
"read_pass_count": {"const": 2},
|
||||||
|
"max_read_passes": {"const": 2},
|
||||||
|
"pass_1_raw_hash_map_digest": {"$ref": "#/$defs/sha256"},
|
||||||
|
"pass_2_raw_hash_map_digest": {"$ref": "#/$defs/sha256"},
|
||||||
|
"source_receipts": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {"$ref": "#/$defs/binary_hydration_source_receipt"}
|
||||||
|
},
|
||||||
|
"stability_status": {"enum": ["STABLE", "CHANGED", "UNEVALUABLE"]}
|
||||||
|
}
|
||||||
|
},
|
||||||
"stage1_input_manifest": {
|
"stage1_input_manifest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
@@ -643,14 +699,14 @@
|
|||||||
"source_row_order",
|
"source_row_order",
|
||||||
"signal_all_adapter_id",
|
"signal_all_adapter_id",
|
||||||
"dual_sg01_adapter_id",
|
"dual_sg01_adapter_id",
|
||||||
"snapshot_start_digest",
|
|
||||||
"snapshot_end_digest",
|
|
||||||
"snapshot_status",
|
|
||||||
"input_set_digest"
|
"input_set_digest"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"schema_version": {
|
"schema_version": {
|
||||||
"const": "stage2_s2_00_stage1_input_manifest.v1"
|
"enum": [
|
||||||
|
"stage2_s2_00_stage1_input_manifest.v1",
|
||||||
|
"stage2_s2_00_stage1_input_manifest.v1.1"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"run_id": {"$ref": "#/$defs/nonempty_string"},
|
"run_id": {"$ref": "#/$defs/nonempty_string"},
|
||||||
"release_class": {
|
"release_class": {
|
||||||
@@ -670,11 +726,35 @@
|
|||||||
},
|
},
|
||||||
"signal_all_adapter_id": {"const": "S2A-SIGNAL-ALL-V1"},
|
"signal_all_adapter_id": {"const": "S2A-SIGNAL-ALL-V1"},
|
||||||
"dual_sg01_adapter_id": {"const": "S2A-DUAL-SG01-V1"},
|
"dual_sg01_adapter_id": {"const": "S2A-DUAL-SG01-V1"},
|
||||||
|
"hydration_stability_receipt": {
|
||||||
|
"$ref": "#/$defs/two_pass_hydration_stability_receipt"
|
||||||
|
},
|
||||||
"snapshot_start_digest": {"$ref": "#/$defs/sha256"},
|
"snapshot_start_digest": {"$ref": "#/$defs/sha256"},
|
||||||
"snapshot_end_digest": {"$ref": "#/$defs/sha256"},
|
"snapshot_end_digest": {"$ref": "#/$defs/sha256"},
|
||||||
"snapshot_status": {"enum": ["STABLE", "CHANGED", "UNEVALUABLE"]},
|
"snapshot_status": {"enum": ["STABLE", "CHANGED", "UNEVALUABLE"]},
|
||||||
"input_set_digest": {"$ref": "#/$defs/sha256"}
|
"input_set_digest": {"$ref": "#/$defs/sha256"}
|
||||||
}
|
},
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"if": {
|
||||||
|
"properties": {"schema_version": {"const": "stage2_s2_00_stage1_input_manifest.v1"}},
|
||||||
|
"required": ["schema_version"]
|
||||||
|
},
|
||||||
|
"then": {
|
||||||
|
"required": ["snapshot_start_digest", "snapshot_end_digest", "snapshot_status"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"if": {
|
||||||
|
"properties": {"schema_version": {"const": "stage2_s2_00_stage1_input_manifest.v1.1"}},
|
||||||
|
"required": ["schema_version"]
|
||||||
|
},
|
||||||
|
"then": {
|
||||||
|
"required": ["hydration_stability_receipt"],
|
||||||
|
"properties": {"run_id": {"$ref": "#/$defs/canonical_run_id"}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"intake_report": {
|
"intake_report": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@@ -734,7 +814,16 @@
|
|||||||
"workspace_context_sha256"
|
"workspace_context_sha256"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"schema_version": {"const": "stage2_s2_00_run_binding_receipt.v1"},
|
"schema_version": {
|
||||||
|
"enum": [
|
||||||
|
"stage2_s2_00_run_binding_receipt.v1",
|
||||||
|
"stage2_s2_00_run_binding_receipt.v1.1"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"request_id": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$"
|
||||||
|
},
|
||||||
"run_id": {"$ref": "#/$defs/nonempty_string"},
|
"run_id": {"$ref": "#/$defs/nonempty_string"},
|
||||||
"input_set_digest": {"$ref": "#/$defs/sha256"},
|
"input_set_digest": {"$ref": "#/$defs/sha256"},
|
||||||
"stage2_release_digest": {"$ref": "#/$defs/sha256"},
|
"stage2_release_digest": {"$ref": "#/$defs/sha256"},
|
||||||
@@ -747,9 +836,37 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"run_binding_digest": {"$ref": "#/$defs/sha256"},
|
"run_binding_digest": {"$ref": "#/$defs/sha256"},
|
||||||
|
"canonical_output_root": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^stage2_runs/by-binding/[a-f0-9]{64}/$"
|
||||||
|
},
|
||||||
|
"run_identity_derivation": {
|
||||||
|
"const": "RUN_ID_PREFIXED_FROM_RUN_BINDING_DIGEST"
|
||||||
|
},
|
||||||
|
"output_root_derivation": {
|
||||||
|
"const": "stage2_runs/by-binding/<run_binding_digest>/"
|
||||||
|
},
|
||||||
"user_context_sha256": {"$ref": "#/$defs/sha256"},
|
"user_context_sha256": {"$ref": "#/$defs/sha256"},
|
||||||
"workspace_context_sha256": {"$ref": "#/$defs/sha256"}
|
"workspace_context_sha256": {"$ref": "#/$defs/sha256"}
|
||||||
}
|
},
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"if": {
|
||||||
|
"properties": {"schema_version": {"const": "stage2_s2_00_run_binding_receipt.v1.1"}},
|
||||||
|
"required": ["schema_version"]
|
||||||
|
},
|
||||||
|
"then": {
|
||||||
|
"required": [
|
||||||
|
"request_id",
|
||||||
|
"canonical_output_root",
|
||||||
|
"run_identity_derivation",
|
||||||
|
"output_root_derivation"
|
||||||
|
],
|
||||||
|
"properties": {"run_id": {"$ref": "#/$defs/canonical_run_id"}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"$comment": "For v1.1, run_id is S2RUN- plus run_binding_digest while the output-root digest segment is the raw run_binding_digest; deterministic validation recomputes both equalities."
|
||||||
},
|
},
|
||||||
"output_artifact_ref": {
|
"output_artifact_ref": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@@ -757,9 +874,10 @@
|
|||||||
"required": ["logical_artifact_id", "path", "schema_id", "raw_sha256"],
|
"required": ["logical_artifact_id", "path", "schema_id", "raw_sha256"],
|
||||||
"properties": {
|
"properties": {
|
||||||
"logical_artifact_id": {"$ref": "#/$defs/nonempty_string"},
|
"logical_artifact_id": {"$ref": "#/$defs/nonempty_string"},
|
||||||
"path": {"$ref": "#/$defs/nonempty_string"},
|
"path": {"$ref": "#/$defs/logical_path"},
|
||||||
"schema_id": {"$ref": "#/$defs/nonempty_string"},
|
"schema_id": {"$ref": "#/$defs/nonempty_string"},
|
||||||
"raw_sha256": {"$ref": "#/$defs/sha256"}
|
"raw_sha256": {"$ref": "#/$defs/sha256"},
|
||||||
|
"byte_length": {"type": "integer", "minimum": 0}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"output_barrier": {
|
"output_barrier": {
|
||||||
@@ -774,12 +892,19 @@
|
|||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"barrier_id": {"const": "S2_00_INGRESS_STATUS_BARRIER"},
|
"barrier_id": {"const": "S2_00_INGRESS_STATUS_BARRIER"},
|
||||||
|
"barrier_path": {"const": "ingress/ingress_status.json"},
|
||||||
|
"publish_semantics": {"const": "STATUS_LAST_LOGICAL_COMMIT"},
|
||||||
|
"canonical_output_root": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^stage2_runs/by-binding/[a-f0-9]{64}/$"
|
||||||
|
},
|
||||||
"branch": {"enum": ["NORMAL", "DIAGNOSTIC"]},
|
"branch": {"enum": ["NORMAL", "DIAGNOSTIC"]},
|
||||||
"artifacts": {
|
"artifacts": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {"$ref": "#/$defs/output_artifact_ref"}
|
"items": {"$ref": "#/$defs/output_artifact_ref"}
|
||||||
},
|
},
|
||||||
"artifact_set_digest": {"$ref": "#/$defs/sha256"},
|
"artifact_set_digest": {"$ref": "#/$defs/sha256"},
|
||||||
|
"non_status_artifacts_read_back_verified": {"const": true},
|
||||||
"written_last": {"const": true}
|
"written_last": {"const": true}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -797,7 +922,12 @@
|
|||||||
"output_barrier"
|
"output_barrier"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"schema_version": {"const": "stage2_s2_00_ingress_status.v1"},
|
"schema_version": {
|
||||||
|
"enum": [
|
||||||
|
"stage2_s2_00_ingress_status.v1",
|
||||||
|
"stage2_s2_00_ingress_status.v1.1"
|
||||||
|
]
|
||||||
|
},
|
||||||
"run_id": {"$ref": "#/$defs/nonempty_string"},
|
"run_id": {"$ref": "#/$defs/nonempty_string"},
|
||||||
"run_binding_receipt": {"$ref": "#/$defs/run_binding_receipt"},
|
"run_binding_receipt": {"$ref": "#/$defs/run_binding_receipt"},
|
||||||
"route": {
|
"route": {
|
||||||
@@ -813,6 +943,25 @@
|
|||||||
"output_barrier": {"$ref": "#/$defs/output_barrier"}
|
"output_barrier": {"$ref": "#/$defs/output_barrier"}
|
||||||
},
|
},
|
||||||
"allOf": [
|
"allOf": [
|
||||||
|
{
|
||||||
|
"if": {
|
||||||
|
"properties": {"schema_version": {"const": "stage2_s2_00_ingress_status.v1.1"}},
|
||||||
|
"required": ["schema_version"]
|
||||||
|
},
|
||||||
|
"then": {
|
||||||
|
"properties": {
|
||||||
|
"run_id": {"$ref": "#/$defs/canonical_run_id"},
|
||||||
|
"output_barrier": {
|
||||||
|
"required": [
|
||||||
|
"barrier_path",
|
||||||
|
"publish_semantics",
|
||||||
|
"canonical_output_root",
|
||||||
|
"non_status_artifacts_read_back_verified"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"if": {
|
"if": {
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
+8
-3
@@ -1199,7 +1199,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "tests/s2_00/test_cluster_bundle_compile.py",
|
"path": "tests/s2_00/test_cluster_bundle_compile.py",
|
||||||
"sha256": "8e37dda051562b4c1a3fc595a1678fc7758742f89b19cfe8f9d5a0b342dfa5d1",
|
"sha256": "c2b01c8d39be31d43b11f13a9901db036ef967337a388a4b85d951fbb4f51efe",
|
||||||
"status": "DEV_HASH_BOUND"
|
"status": "DEV_HASH_BOUND"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1209,12 +1209,17 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "tests/s2_00/test_failure_and_atomic_publish.py",
|
"path": "tests/s2_00/test_failure_and_atomic_publish.py",
|
||||||
"sha256": "bb7879a23c207bba304cae1eefe89a8f0b7b9263787c80c3fbcea523300f0fb8",
|
"sha256": "4bc5060ce95ec884c1193f16e8348d095c1b1a89181b0455ad809b2bb05cfb0c",
|
||||||
|
"status": "DEV_HASH_BOUND"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "tests/s2_00/test_inline_code_parity.py",
|
||||||
|
"sha256": "6a03a8e67661c5599925c0fa01b4a79602d4b745936f783d5b68617ec2c4633e",
|
||||||
"status": "DEV_HASH_BOUND"
|
"status": "DEV_HASH_BOUND"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "tests/s2_00/test_resolver_source_lock.py",
|
"path": "tests/s2_00/test_resolver_source_lock.py",
|
||||||
"sha256": "235e05985fb4b574c59cc44ff1cc803f4ec064d7f0fdb7fcf8c694e93d445fe0",
|
"sha256": "51bc1b310d40f5068df0a66446e5b96b00ea9b5b0137ddaf563caadf9f8fc51e",
|
||||||
"status": "DEV_HASH_BOUND"
|
"status": "DEV_HASH_BOUND"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
+76
-49
@@ -13,7 +13,6 @@ import yaml
|
|||||||
ROOT = Path(__file__).resolve().parents[2]
|
ROOT = Path(__file__).resolve().parents[2]
|
||||||
sys.path.insert(0, str(ROOT))
|
sys.path.insert(0, str(ROOT))
|
||||||
from runtime import s2_00_ingress as s2 # noqa: E402
|
from runtime import s2_00_ingress as s2 # noqa: E402
|
||||||
from release_ops import stage2_loader as loader # noqa: E402
|
|
||||||
|
|
||||||
|
|
||||||
def members() -> list[dict[str, object]]:
|
def members() -> list[dict[str, object]]:
|
||||||
@@ -296,7 +295,21 @@ class ClusterAndBundleTests(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
stage = workflow["Agent"]["Stages"][0]
|
stage = workflow["Agent"]["Stages"][0]
|
||||||
handoff = stage["handoff_contract"]
|
handoff = stage["handoff_contract"]
|
||||||
required = set(handoff["item_contract"]["required_fields"])
|
required_fields = handoff["item_contract"]["required_fields"]
|
||||||
|
self.assertEqual(
|
||||||
|
required_fields,
|
||||||
|
[
|
||||||
|
"run_id",
|
||||||
|
"cluster_id",
|
||||||
|
"cluster_slice_json",
|
||||||
|
"cluster_slice_sha256",
|
||||||
|
"executable_bundle_json",
|
||||||
|
"executable_bundle_sha256",
|
||||||
|
"input_set_digest",
|
||||||
|
"stage2_release_digest",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
required = set(required_fields)
|
||||||
self.assertNotIn("materialized_static_prefix_text", required)
|
self.assertNotIn("materialized_static_prefix_text", required)
|
||||||
self.assertNotIn("materialization_receipt_json", required)
|
self.assertNotIn("materialization_receipt_json", required)
|
||||||
self.assertIn("executable_bundle_json", required)
|
self.assertIn("executable_bundle_json", required)
|
||||||
@@ -331,13 +344,17 @@ class ClusterAndBundleTests(unittest.TestCase):
|
|||||||
self.assertTrue(materialization["verify_raw_sha256_before_decode"])
|
self.assertTrue(materialization["verify_raw_sha256_before_decode"])
|
||||||
self.assertTrue(materialization["verify_canonical_sha256_after_decode"])
|
self.assertTrue(materialization["verify_canonical_sha256_after_decode"])
|
||||||
self.assertFalse(materialization["llm_filesystem_resolution_allowed"])
|
self.assertFalse(materialization["llm_filesystem_resolution_allowed"])
|
||||||
|
self.assertFalse(handoff["raw_stage1_reread_allowed"])
|
||||||
|
self.assertFalse(handoff["file_write_allowed"])
|
||||||
|
self.assertFalse(handoff["tool_calls_allowed"])
|
||||||
|
self.assertTrue(handoff["item_contract"]["exact_cluster_membership_required"])
|
||||||
|
|
||||||
task = stage["tasks"][0]
|
task = stage["tasks"][0]
|
||||||
self.assertNotIn("llm_model", task)
|
self.assertNotIn("llm_model", task)
|
||||||
self.assertNotIn("llm_reasoning", task)
|
self.assertNotIn("llm_reasoning", task)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
task["deployment_binding_ref"],
|
task["deployment_binding_ref"],
|
||||||
"deployment/stage2_loader_binding.yml#/downstream_llm_candidate_bindings/0",
|
"deployment/stage2_code_executor_binding.yml#/downstream_llm_candidate_bindings/0",
|
||||||
)
|
)
|
||||||
assembly = task["prompt_assembly"]
|
assembly = task["prompt_assembly"]
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
@@ -357,8 +374,10 @@ class ClusterAndBundleTests(unittest.TestCase):
|
|||||||
"prompt_assembly.static_prefix",
|
"prompt_assembly.static_prefix",
|
||||||
)
|
)
|
||||||
|
|
||||||
binding = json.loads(
|
binding = yaml.safe_load(
|
||||||
(ROOT / "deployment/stage2_loader_binding.yml").read_text(encoding="utf-8")
|
(ROOT / "deployment/stage2_code_executor_binding.yml").read_text(
|
||||||
|
encoding="utf-8"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
candidate = binding["downstream_llm_candidate_bindings"]
|
candidate = binding["downstream_llm_candidate_bindings"]
|
||||||
self.assertEqual(len(candidate), 1)
|
self.assertEqual(len(candidate), 1)
|
||||||
@@ -385,53 +404,61 @@ class ClusterAndBundleTests(unittest.TestCase):
|
|||||||
stage["handoff_contract"]["final_status_fields"]["legal_readiness"],
|
stage["handoff_contract"]["final_status_fields"]["legal_readiness"],
|
||||||
["NOT_ASSESSED"],
|
["NOT_ASSESSED"],
|
||||||
)
|
)
|
||||||
|
expected_inputs = [
|
||||||
|
"ingress/ingress_status.json",
|
||||||
|
"ingress/technical_diagnostic.json",
|
||||||
|
"ingress/stage1_input_manifest.json",
|
||||||
|
"ingress/intake_report.json",
|
||||||
|
"review/issue_ledger.base.json",
|
||||||
|
]
|
||||||
|
self.assertEqual(stage["handoff_contract"]["exact_input_count"], 5)
|
||||||
|
self.assertEqual(stage["handoff_contract"]["exact_inputs"], expected_inputs)
|
||||||
|
self.assertEqual(task["exact_input_files"], expected_inputs)
|
||||||
|
self.assertFalse(stage["handoff_contract"]["additional_input_allowed"])
|
||||||
|
self.assertEqual(
|
||||||
|
workflow["Agent"]["metadata"]["final_status_single_writer"],
|
||||||
|
"S2_40",
|
||||||
|
)
|
||||||
|
self.assertFalse(
|
||||||
|
stage["prohibitions"]["final_status_writer_other_than_s2_40_allowed"]
|
||||||
|
)
|
||||||
|
self.assertEqual(stage["prohibitions"]["output_paths"], ["control/run_status.json"])
|
||||||
|
|
||||||
def test_release_oracle_verifies_module_manifest_and_binding_raw_bytes(self) -> None:
|
def test_release_oracle_verifies_active_executor_cross_hashes(self) -> None:
|
||||||
with tempfile.TemporaryDirectory() as directory:
|
release_path = ROOT / "manifest/stage2_release.json"
|
||||||
root = Path(directory).resolve()
|
release = s2.load_json_strict(release_path.read_bytes())
|
||||||
module_path = root / loader.MODULE_MANIFEST_REF
|
binding_path = ROOT / "deployment/stage2_code_executor_binding.yml"
|
||||||
binding_path = root / loader.BINDING_REF
|
binding = yaml.safe_load(binding_path.read_text(encoding="utf-8"))
|
||||||
module_path.parent.mkdir(parents=True)
|
|
||||||
binding_path.parent.mkdir(parents=True)
|
|
||||||
module_path.write_bytes(b'{"modules":[]}\n')
|
|
||||||
binding_path.write_bytes(b'{"binding_id":"fixture"}\n')
|
|
||||||
|
|
||||||
def raw_sha256(path: Path) -> str:
|
def assert_ref(ref: dict[str, str]) -> Path:
|
||||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
path = ROOT / ref["path"]
|
||||||
|
self.assertTrue(path.is_file(), path)
|
||||||
|
self.assertEqual(hashlib.sha256(path.read_bytes()).hexdigest(), ref["sha256"])
|
||||||
|
return path
|
||||||
|
|
||||||
release = {
|
assert_ref(release["module_manifest_ref"])
|
||||||
"module_manifest_ref": {
|
self.assertEqual(
|
||||||
"asset_id": "MANIFEST-MODULE",
|
release["executor_binding_ref"]["path"],
|
||||||
"path": loader.MODULE_MANIFEST_REF,
|
"deployment/stage2_code_executor_binding.yml",
|
||||||
"sha256": raw_sha256(module_path),
|
)
|
||||||
"schema_id": "stage2_module_manifest.v1",
|
self.assertIn(
|
||||||
"binding_status": "BOUND",
|
release["executor_binding_ref"]["binding_status"],
|
||||||
},
|
{
|
||||||
"loader_binding_ref": {
|
"EXTERNAL_TRUST_ROOT_PENDING_LIVE_ADMISSION",
|
||||||
"asset_id": "BINDING-S2_00",
|
"EXTERNAL_TRUST_ROOT_ADMITTED",
|
||||||
"path": loader.BINDING_REF,
|
},
|
||||||
"sha256": raw_sha256(binding_path),
|
)
|
||||||
"schema_id": "stage2_loader_binding.v1",
|
agent_path = assert_ref(binding["agent_script_ref"])
|
||||||
"binding_status": "BOUND",
|
assert_ref(binding["workflow_contract_ref"])
|
||||||
},
|
assert_ref(binding["inline_code_receipt_ref"])
|
||||||
}
|
bound_release_path = assert_ref(binding["stage2_release_ref"])
|
||||||
binding = {
|
self.assertEqual(bound_release_path, release_path)
|
||||||
"module_manifest_ref": {"path": loader.MODULE_MANIFEST_REF},
|
self.assertEqual(binding["expected_release_sha256"], binding["stage2_release_ref"]["sha256"])
|
||||||
"stage2_release_ref": {"path": loader.RELEASE_REF},
|
self.assertEqual(binding["expected_release_sha256"], hashlib.sha256(release_path.read_bytes()).hexdigest())
|
||||||
}
|
agent = yaml.safe_load(agent_path.read_text(encoding="utf-8"))
|
||||||
receipt = loader._validate_cross_binding(root, release, binding)
|
code = agent["Agent"]["Stages"][0]["tasks"][0]["parameters"]["code"]
|
||||||
self.assertEqual([row["status"] for row in receipt], ["PASS", "PASS"])
|
self.assertEqual(hashlib.sha256(code.encode("utf-8")).hexdigest(), binding["canonical_code_sha256"])
|
||||||
|
self.assertEqual(binding["agent_script_sha256"], binding["agent_script_ref"]["sha256"])
|
||||||
module_path.write_bytes(b'{"modules":["tampered"]}\n')
|
|
||||||
with self.assertRaises(loader.LoaderError) as caught:
|
|
||||||
loader._validate_cross_binding(root, release, binding)
|
|
||||||
self.assertEqual(caught.exception.reason_code, "RELEASE_ORACLE_HASH_MISMATCH")
|
|
||||||
|
|
||||||
module_path.write_bytes(b'{"modules":[]}\n')
|
|
||||||
binding_path.write_bytes(b'{"binding_id":"tampered"}\n')
|
|
||||||
with self.assertRaises(loader.LoaderError) as caught:
|
|
||||||
loader._validate_cross_binding(root, release, binding)
|
|
||||||
self.assertEqual(caught.exception.reason_code, "RELEASE_ORACLE_HASH_MISMATCH")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
+76
-49
@@ -13,7 +13,6 @@ import yaml
|
|||||||
ROOT = Path(__file__).resolve().parents[2]
|
ROOT = Path(__file__).resolve().parents[2]
|
||||||
sys.path.insert(0, str(ROOT))
|
sys.path.insert(0, str(ROOT))
|
||||||
from runtime import s2_00_ingress as s2 # noqa: E402
|
from runtime import s2_00_ingress as s2 # noqa: E402
|
||||||
from release_ops import stage2_loader as loader # noqa: E402
|
|
||||||
|
|
||||||
|
|
||||||
def members() -> list[dict[str, object]]:
|
def members() -> list[dict[str, object]]:
|
||||||
@@ -296,7 +295,21 @@ class ClusterAndBundleTests(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
stage = workflow["Agent"]["Stages"][0]
|
stage = workflow["Agent"]["Stages"][0]
|
||||||
handoff = stage["handoff_contract"]
|
handoff = stage["handoff_contract"]
|
||||||
required = set(handoff["item_contract"]["required_fields"])
|
required_fields = handoff["item_contract"]["required_fields"]
|
||||||
|
self.assertEqual(
|
||||||
|
required_fields,
|
||||||
|
[
|
||||||
|
"run_id",
|
||||||
|
"cluster_id",
|
||||||
|
"cluster_slice_json",
|
||||||
|
"cluster_slice_sha256",
|
||||||
|
"executable_bundle_json",
|
||||||
|
"executable_bundle_sha256",
|
||||||
|
"input_set_digest",
|
||||||
|
"stage2_release_digest",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
required = set(required_fields)
|
||||||
self.assertNotIn("materialized_static_prefix_text", required)
|
self.assertNotIn("materialized_static_prefix_text", required)
|
||||||
self.assertNotIn("materialization_receipt_json", required)
|
self.assertNotIn("materialization_receipt_json", required)
|
||||||
self.assertIn("executable_bundle_json", required)
|
self.assertIn("executable_bundle_json", required)
|
||||||
@@ -331,13 +344,17 @@ class ClusterAndBundleTests(unittest.TestCase):
|
|||||||
self.assertTrue(materialization["verify_raw_sha256_before_decode"])
|
self.assertTrue(materialization["verify_raw_sha256_before_decode"])
|
||||||
self.assertTrue(materialization["verify_canonical_sha256_after_decode"])
|
self.assertTrue(materialization["verify_canonical_sha256_after_decode"])
|
||||||
self.assertFalse(materialization["llm_filesystem_resolution_allowed"])
|
self.assertFalse(materialization["llm_filesystem_resolution_allowed"])
|
||||||
|
self.assertFalse(handoff["raw_stage1_reread_allowed"])
|
||||||
|
self.assertFalse(handoff["file_write_allowed"])
|
||||||
|
self.assertFalse(handoff["tool_calls_allowed"])
|
||||||
|
self.assertTrue(handoff["item_contract"]["exact_cluster_membership_required"])
|
||||||
|
|
||||||
task = stage["tasks"][0]
|
task = stage["tasks"][0]
|
||||||
self.assertNotIn("llm_model", task)
|
self.assertNotIn("llm_model", task)
|
||||||
self.assertNotIn("llm_reasoning", task)
|
self.assertNotIn("llm_reasoning", task)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
task["deployment_binding_ref"],
|
task["deployment_binding_ref"],
|
||||||
"deployment/stage2_loader_binding.yml#/downstream_llm_candidate_bindings/0",
|
"deployment/stage2_code_executor_binding.yml#/downstream_llm_candidate_bindings/0",
|
||||||
)
|
)
|
||||||
assembly = task["prompt_assembly"]
|
assembly = task["prompt_assembly"]
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
@@ -357,8 +374,10 @@ class ClusterAndBundleTests(unittest.TestCase):
|
|||||||
"prompt_assembly.static_prefix",
|
"prompt_assembly.static_prefix",
|
||||||
)
|
)
|
||||||
|
|
||||||
binding = json.loads(
|
binding = yaml.safe_load(
|
||||||
(ROOT / "deployment/stage2_loader_binding.yml").read_text(encoding="utf-8")
|
(ROOT / "deployment/stage2_code_executor_binding.yml").read_text(
|
||||||
|
encoding="utf-8"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
candidate = binding["downstream_llm_candidate_bindings"]
|
candidate = binding["downstream_llm_candidate_bindings"]
|
||||||
self.assertEqual(len(candidate), 1)
|
self.assertEqual(len(candidate), 1)
|
||||||
@@ -385,53 +404,61 @@ class ClusterAndBundleTests(unittest.TestCase):
|
|||||||
stage["handoff_contract"]["final_status_fields"]["legal_readiness"],
|
stage["handoff_contract"]["final_status_fields"]["legal_readiness"],
|
||||||
["NOT_ASSESSED"],
|
["NOT_ASSESSED"],
|
||||||
)
|
)
|
||||||
|
expected_inputs = [
|
||||||
|
"ingress/ingress_status.json",
|
||||||
|
"ingress/technical_diagnostic.json",
|
||||||
|
"ingress/stage1_input_manifest.json",
|
||||||
|
"ingress/intake_report.json",
|
||||||
|
"review/issue_ledger.base.json",
|
||||||
|
]
|
||||||
|
self.assertEqual(stage["handoff_contract"]["exact_input_count"], 5)
|
||||||
|
self.assertEqual(stage["handoff_contract"]["exact_inputs"], expected_inputs)
|
||||||
|
self.assertEqual(task["exact_input_files"], expected_inputs)
|
||||||
|
self.assertFalse(stage["handoff_contract"]["additional_input_allowed"])
|
||||||
|
self.assertEqual(
|
||||||
|
workflow["Agent"]["metadata"]["final_status_single_writer"],
|
||||||
|
"S2_40",
|
||||||
|
)
|
||||||
|
self.assertFalse(
|
||||||
|
stage["prohibitions"]["final_status_writer_other_than_s2_40_allowed"]
|
||||||
|
)
|
||||||
|
self.assertEqual(stage["prohibitions"]["output_paths"], ["control/run_status.json"])
|
||||||
|
|
||||||
def test_release_oracle_verifies_module_manifest_and_binding_raw_bytes(self) -> None:
|
def test_release_oracle_verifies_active_executor_cross_hashes(self) -> None:
|
||||||
with tempfile.TemporaryDirectory() as directory:
|
release_path = ROOT / "manifest/stage2_release.json"
|
||||||
root = Path(directory).resolve()
|
release = s2.load_json_strict(release_path.read_bytes())
|
||||||
module_path = root / loader.MODULE_MANIFEST_REF
|
binding_path = ROOT / "deployment/stage2_code_executor_binding.yml"
|
||||||
binding_path = root / loader.BINDING_REF
|
binding = yaml.safe_load(binding_path.read_text(encoding="utf-8"))
|
||||||
module_path.parent.mkdir(parents=True)
|
|
||||||
binding_path.parent.mkdir(parents=True)
|
|
||||||
module_path.write_bytes(b'{"modules":[]}\n')
|
|
||||||
binding_path.write_bytes(b'{"binding_id":"fixture"}\n')
|
|
||||||
|
|
||||||
def raw_sha256(path: Path) -> str:
|
def assert_ref(ref: dict[str, str]) -> Path:
|
||||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
path = ROOT / ref["path"]
|
||||||
|
self.assertTrue(path.is_file(), path)
|
||||||
|
self.assertEqual(hashlib.sha256(path.read_bytes()).hexdigest(), ref["sha256"])
|
||||||
|
return path
|
||||||
|
|
||||||
release = {
|
assert_ref(release["module_manifest_ref"])
|
||||||
"module_manifest_ref": {
|
self.assertEqual(
|
||||||
"asset_id": "MANIFEST-MODULE",
|
release["executor_binding_ref"]["path"],
|
||||||
"path": loader.MODULE_MANIFEST_REF,
|
"deployment/stage2_code_executor_binding.yml",
|
||||||
"sha256": raw_sha256(module_path),
|
)
|
||||||
"schema_id": "stage2_module_manifest.v1",
|
self.assertIn(
|
||||||
"binding_status": "BOUND",
|
release["executor_binding_ref"]["binding_status"],
|
||||||
},
|
{
|
||||||
"loader_binding_ref": {
|
"EXTERNAL_TRUST_ROOT_PENDING_LIVE_ADMISSION",
|
||||||
"asset_id": "BINDING-S2_00",
|
"EXTERNAL_TRUST_ROOT_ADMITTED",
|
||||||
"path": loader.BINDING_REF,
|
},
|
||||||
"sha256": raw_sha256(binding_path),
|
)
|
||||||
"schema_id": "stage2_loader_binding.v1",
|
agent_path = assert_ref(binding["agent_script_ref"])
|
||||||
"binding_status": "BOUND",
|
assert_ref(binding["workflow_contract_ref"])
|
||||||
},
|
assert_ref(binding["inline_code_receipt_ref"])
|
||||||
}
|
bound_release_path = assert_ref(binding["stage2_release_ref"])
|
||||||
binding = {
|
self.assertEqual(bound_release_path, release_path)
|
||||||
"module_manifest_ref": {"path": loader.MODULE_MANIFEST_REF},
|
self.assertEqual(binding["expected_release_sha256"], binding["stage2_release_ref"]["sha256"])
|
||||||
"stage2_release_ref": {"path": loader.RELEASE_REF},
|
self.assertEqual(binding["expected_release_sha256"], hashlib.sha256(release_path.read_bytes()).hexdigest())
|
||||||
}
|
agent = yaml.safe_load(agent_path.read_text(encoding="utf-8"))
|
||||||
receipt = loader._validate_cross_binding(root, release, binding)
|
code = agent["Agent"]["Stages"][0]["tasks"][0]["parameters"]["code"]
|
||||||
self.assertEqual([row["status"] for row in receipt], ["PASS", "PASS"])
|
self.assertEqual(hashlib.sha256(code.encode("utf-8")).hexdigest(), binding["canonical_code_sha256"])
|
||||||
|
self.assertEqual(binding["agent_script_sha256"], binding["agent_script_ref"]["sha256"])
|
||||||
module_path.write_bytes(b'{"modules":["tampered"]}\n')
|
|
||||||
with self.assertRaises(loader.LoaderError) as caught:
|
|
||||||
loader._validate_cross_binding(root, release, binding)
|
|
||||||
self.assertEqual(caught.exception.reason_code, "RELEASE_ORACLE_HASH_MISMATCH")
|
|
||||||
|
|
||||||
module_path.write_bytes(b'{"modules":[]}\n')
|
|
||||||
binding_path.write_bytes(b'{"binding_id":"tampered"}\n')
|
|
||||||
with self.assertRaises(loader.LoaderError) as caught:
|
|
||||||
loader._validate_cross_binding(root, release, binding)
|
|
||||||
self.assertEqual(caught.exception.reason_code, "RELEASE_ORACLE_HASH_MISMATCH")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
+303
-62
@@ -1,12 +1,13 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import io
|
||||||
import json
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import subprocess
|
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
|
||||||
ROOT = Path(__file__).resolve().parents[2]
|
ROOT = Path(__file__).resolve().parents[2]
|
||||||
@@ -14,27 +15,79 @@ sys.path.insert(0, str(ROOT))
|
|||||||
from runtime import s2_00_ingress as s2 # noqa: E402
|
from runtime import s2_00_ingress as s2 # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
|
class MemoryLocaldocs:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.files: dict[str, bytes] = {}
|
||||||
|
self.operations: list[tuple[str, str]] = []
|
||||||
|
|
||||||
|
def read_binary_optional(self, path: str) -> bytes | None:
|
||||||
|
self.operations.append(("read_optional", path))
|
||||||
|
return self.files.get(path)
|
||||||
|
|
||||||
|
def read_binary(self, path: str) -> bytes:
|
||||||
|
self.operations.append(("read", path))
|
||||||
|
if path not in self.files:
|
||||||
|
raise s2.IngressError("LOCALDOCS_NOT_FOUND", f"missing: {path}")
|
||||||
|
return self.files[path]
|
||||||
|
|
||||||
|
def write_binary_verified(self, path: str, payload: bytes) -> str:
|
||||||
|
self.operations.append(("write", path))
|
||||||
|
self.files[path] = payload
|
||||||
|
self.operations.append(("read", path))
|
||||||
|
if self.files[path] != payload:
|
||||||
|
raise AssertionError("memory read-back failed")
|
||||||
|
return hashlib.sha256(payload).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
class CaptureStdout:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.buffer = io.BytesIO()
|
||||||
|
|
||||||
|
def write(self, _text: str) -> int:
|
||||||
|
raise AssertionError("receipt must be written through stdout.buffer")
|
||||||
|
|
||||||
|
def flush(self) -> None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class FailureAndAtomicPublishTests(unittest.TestCase):
|
class FailureAndAtomicPublishTests(unittest.TestCase):
|
||||||
def _artifacts(self, route: str = "TO_S2_10") -> dict[str, object]:
|
def _artifacts(self, route: str = "TO_S2_10") -> dict[str, object]:
|
||||||
|
binding_digest = "a" * 64
|
||||||
|
run_id = f"S2RUN-{binding_digest}"
|
||||||
run_binding = {
|
run_binding = {
|
||||||
"schema_version": "stage2_s2_00_run_binding_receipt.v1",
|
"schema_version": "stage2_s2_00_run_binding_receipt.v1.1",
|
||||||
"run_id": "RUN-1",
|
"request_id": "REQUEST-1",
|
||||||
|
"run_id": run_id,
|
||||||
"input_set_digest": "1" * 64,
|
"input_set_digest": "1" * 64,
|
||||||
"stage2_release_digest": "2" * 64,
|
"stage2_release_digest": "2" * 64,
|
||||||
"algorithm_digest": "3" * 64,
|
"algorithm_digest": "3" * 64,
|
||||||
"release_class": "SUBSET_CANARY_RELEASE",
|
"release_class": "SUBSET_CANARY_RELEASE",
|
||||||
"run_binding_digest": "a" * 64,
|
"run_binding_digest": binding_digest,
|
||||||
|
"canonical_output_root": f"stage2_runs/by-binding/{binding_digest}/",
|
||||||
|
"run_identity_derivation": "RUN_ID_PREFIXED_FROM_RUN_BINDING_DIGEST",
|
||||||
|
"output_root_derivation": "stage2_runs/by-binding/<run_binding_digest>/",
|
||||||
"user_context_sha256": "4" * 64,
|
"user_context_sha256": "4" * 64,
|
||||||
"workspace_context_sha256": "5" * 64,
|
"workspace_context_sha256": "5" * 64,
|
||||||
}
|
}
|
||||||
manifest = {
|
manifest = {
|
||||||
"schema_version": "stage2_s2_00_stage1_input_manifest.v1",
|
"schema_version": "stage2_s2_00_stage1_input_manifest.v1.1",
|
||||||
"run_id": "RUN-1",
|
"run_id": run_id,
|
||||||
"release_class": "SUBSET_CANARY_RELEASE",
|
"release_class": "SUBSET_CANARY_RELEASE",
|
||||||
"source_rows": [],
|
"source_rows": [],
|
||||||
"source_row_order": [],
|
"source_row_order": [],
|
||||||
"signal_all_adapter_id": "S2A-SIGNAL-ALL-V1",
|
"signal_all_adapter_id": "S2A-SIGNAL-ALL-V1",
|
||||||
"dual_sg01_adapter_id": "S2A-DUAL-SG01-V1",
|
"dual_sg01_adapter_id": "S2A-DUAL-SG01-V1",
|
||||||
|
"hydration_stability_receipt": {
|
||||||
|
"schema_version": "stage2_s2_00_two_pass_hydration_receipt.v1",
|
||||||
|
"transport": "localdocs.read_binary_doc",
|
||||||
|
"read_policy": "BOUNDED_TWO_PASS_BINARY_RAW_HASH_MAP_EQUALITY",
|
||||||
|
"read_pass_count": 2,
|
||||||
|
"max_read_passes": 2,
|
||||||
|
"pass_1_raw_hash_map_digest": "6" * 64,
|
||||||
|
"pass_2_raw_hash_map_digest": "6" * 64,
|
||||||
|
"source_receipts": [],
|
||||||
|
"stability_status": "STABLE",
|
||||||
|
},
|
||||||
"snapshot_start_digest": "6" * 64,
|
"snapshot_start_digest": "6" * 64,
|
||||||
"snapshot_end_digest": "6" * 64,
|
"snapshot_end_digest": "6" * 64,
|
||||||
"snapshot_status": "STABLE",
|
"snapshot_status": "STABLE",
|
||||||
@@ -42,7 +95,7 @@ class FailureAndAtomicPublishTests(unittest.TestCase):
|
|||||||
}
|
}
|
||||||
intake = {
|
intake = {
|
||||||
"schema_version": "stage2_s2_00_intake_report.v1",
|
"schema_version": "stage2_s2_00_intake_report.v1",
|
||||||
"run_id": "RUN-1",
|
"run_id": run_id,
|
||||||
"source_counts": {
|
"source_counts": {
|
||||||
"declared": 0,
|
"declared": 0,
|
||||||
"observed": 0,
|
"observed": 0,
|
||||||
@@ -74,7 +127,7 @@ class FailureAndAtomicPublishTests(unittest.TestCase):
|
|||||||
ledger = {
|
ledger = {
|
||||||
"schema_version": "stage2_issue_ledger_base.v1",
|
"schema_version": "stage2_issue_ledger_base.v1",
|
||||||
"producer_id": "S2_00",
|
"producer_id": "S2_00",
|
||||||
"run_id": "RUN-1",
|
"run_id": run_id,
|
||||||
"input_set_digest": "1" * 64,
|
"input_set_digest": "1" * 64,
|
||||||
"mapping_table_version": "S2-REVIEW-MAP-V1",
|
"mapping_table_version": "S2-REVIEW-MAP-V1",
|
||||||
"issues": [],
|
"issues": [],
|
||||||
@@ -87,8 +140,8 @@ class FailureAndAtomicPublishTests(unittest.TestCase):
|
|||||||
"ingress/intake_report.json": intake,
|
"ingress/intake_report.json": intake,
|
||||||
"review/issue_ledger.base.json": ledger,
|
"review/issue_ledger.base.json": ledger,
|
||||||
"ingress/ingress_status.json": {
|
"ingress/ingress_status.json": {
|
||||||
"schema_version": "stage2_s2_00_ingress_status.v1",
|
"schema_version": "stage2_s2_00_ingress_status.v1.1",
|
||||||
"run_id": "RUN-1",
|
"run_id": run_id,
|
||||||
"run_binding_receipt": run_binding,
|
"run_binding_receipt": run_binding,
|
||||||
"route": route,
|
"route": route,
|
||||||
"executable_cluster_ids": ["CL-" + "a" * 24],
|
"executable_cluster_ids": ["CL-" + "a" * 24],
|
||||||
@@ -96,9 +149,13 @@ class FailureAndAtomicPublishTests(unittest.TestCase):
|
|||||||
"issue_codes": [],
|
"issue_codes": [],
|
||||||
"output_barrier": {
|
"output_barrier": {
|
||||||
"barrier_id": "S2_00_INGRESS_STATUS_BARRIER",
|
"barrier_id": "S2_00_INGRESS_STATUS_BARRIER",
|
||||||
|
"barrier_path": "ingress/ingress_status.json",
|
||||||
|
"publish_semantics": "STATUS_LAST_LOGICAL_COMMIT",
|
||||||
|
"canonical_output_root": f"stage2_runs/by-binding/{binding_digest}/",
|
||||||
"branch": "NORMAL",
|
"branch": "NORMAL",
|
||||||
"artifacts": [],
|
"artifacts": [],
|
||||||
"artifact_set_digest": s2.canonical_digest([]),
|
"artifact_set_digest": s2.canonical_digest([]),
|
||||||
|
"non_status_artifacts_read_back_verified": True,
|
||||||
"written_last": True,
|
"written_last": True,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -134,22 +191,245 @@ class FailureAndAtomicPublishTests(unittest.TestCase):
|
|||||||
self.assertEqual(raised.exception.code, "RUN_TUPLE_CONFLICT")
|
self.assertEqual(raised.exception.code, "RUN_TUPLE_CONFLICT")
|
||||||
self.assertEqual((output / "ingress" / "ingress_status.json").read_bytes(), original)
|
self.assertEqual((output / "ingress" / "ingress_status.json").read_bytes(), original)
|
||||||
|
|
||||||
|
def test_remote_publish_is_status_last_and_idempotent(self) -> None:
|
||||||
|
binding = "a" * 64
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
local_output = Path(directory) / "core"
|
||||||
|
s2.publish_atomically(
|
||||||
|
local_output,
|
||||||
|
self._artifacts(),
|
||||||
|
run_binding_digest=binding,
|
||||||
|
attempt_id="A-REMOTE",
|
||||||
|
)
|
||||||
|
files = s2._inline_output_files(local_output, binding)
|
||||||
|
localdocs = MemoryLocaldocs()
|
||||||
|
receipt = s2._inline_publish_remote(localdocs, files, binding)
|
||||||
|
self.assertEqual(receipt["publication_status"], "PUBLISHED_STATUS_LAST")
|
||||||
|
self.assertEqual(receipt["schema_version"], "stage2_logical_publish_receipt.v1")
|
||||||
|
self.assertEqual(receipt["run_binding_digest"], binding)
|
||||||
|
self.assertEqual(receipt["canonical_output_root"], f"stage2_runs/by-binding/{binding}/")
|
||||||
|
self.assertTrue(receipt["barrier_written_last"])
|
||||||
|
self.assertTrue(receipt["non_status_artifacts_read_back_verified"])
|
||||||
|
self.assertEqual(
|
||||||
|
receipt["artifact_set_digest"],
|
||||||
|
s2.canonical_digest(receipt["artifacts"]),
|
||||||
|
)
|
||||||
|
self.assertTrue(all("byte_length" in row for row in receipt["artifacts"]))
|
||||||
|
writes = [row for row in localdocs.operations if row[0] == "write"]
|
||||||
|
self.assertTrue(writes)
|
||||||
|
self.assertTrue(writes[-1][1].endswith("/ingress/ingress_status.json"))
|
||||||
|
before = list(localdocs.operations)
|
||||||
|
repeated = s2._inline_publish_remote(localdocs, files, binding)
|
||||||
|
self.assertEqual(repeated["publication_status"], "IDEMPOTENT_SUCCESS")
|
||||||
|
self.assertFalse(any(op == "write" for op, _path in localdocs.operations[len(before) :]))
|
||||||
|
|
||||||
|
def test_localdocs_write_requires_byte_identical_readback(self) -> None:
|
||||||
|
class Response:
|
||||||
|
def __init__(self, payload: object) -> None:
|
||||||
|
self.content = json.dumps(payload).encode("utf-8")
|
||||||
|
self.headers = {"mcp-session-id": "session-a"}
|
||||||
|
|
||||||
|
def raise_for_status(self) -> None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
class Client:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
wrong = b"wrong"
|
||||||
|
self.responses = [
|
||||||
|
Response(
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 10,
|
||||||
|
"result": {"content": [{"type": "text", "text": "{}"}]},
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Response(
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 11,
|
||||||
|
"result": {
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": json.dumps(
|
||||||
|
{
|
||||||
|
"content_base64": __import__("base64").b64encode(wrong).decode("ascii"),
|
||||||
|
"byte_length": len(wrong),
|
||||||
|
"sha256": hashlib.sha256(wrong).hexdigest(),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
def post(self, *_args: object, **_kwargs: object) -> Response:
|
||||||
|
return self.responses.pop(0)
|
||||||
|
|
||||||
|
def close(self) -> None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
localdocs = s2._InlineLocaldocs("a" * 64, "b" * 64, client=Client())
|
||||||
|
localdocs._initialized = True
|
||||||
|
localdocs._session_id = "session-a"
|
||||||
|
localdocs.headers["mcp-session-id"] = "session-a"
|
||||||
|
with self.assertRaises(s2.IngressError) as raised:
|
||||||
|
localdocs.write_binary_verified("stage2_runs/by-binding/" + "a" * 64 + "/x.json", b"expected")
|
||||||
|
self.assertEqual(raised.exception.code, "LOCALDOCS_WRITE_READBACK_MISMATCH")
|
||||||
|
|
||||||
|
def test_inline_runner_suppresses_internal_stdout_and_emits_schema_shaped_receipts(self) -> None:
|
||||||
|
class NoisyLocaldocs:
|
||||||
|
def __init__(self, *_args: object, **_kwargs: object) -> None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def initialize(self) -> None:
|
||||||
|
print("internal initialize noise")
|
||||||
|
|
||||||
|
def close(self) -> None:
|
||||||
|
print("internal close noise")
|
||||||
|
|
||||||
|
binding = "a" * 64
|
||||||
|
logical_receipt = {
|
||||||
|
"schema_version": "stage2_logical_publish_receipt.v1",
|
||||||
|
"barrier_id": "S2_00_INGRESS_STATUS_BARRIER",
|
||||||
|
"barrier_path": "ingress/ingress_status.json",
|
||||||
|
"publish_semantics": "STATUS_LAST_LOGICAL_COMMIT",
|
||||||
|
"canonical_output_root": f"stage2_runs/by-binding/{binding}/",
|
||||||
|
"run_binding_digest": binding,
|
||||||
|
"branch": "NORMAL",
|
||||||
|
"artifacts": [],
|
||||||
|
"artifact_set_digest": s2.canonical_digest([]),
|
||||||
|
"barrier_raw_sha256": "b" * 64,
|
||||||
|
"non_status_artifacts_read_back_verified": True,
|
||||||
|
"barrier_written_last": True,
|
||||||
|
"downstream_consumption_allowed": True,
|
||||||
|
"publication_status": "PUBLISHED_STATUS_LAST",
|
||||||
|
}
|
||||||
|
capture = CaptureStdout()
|
||||||
|
with (
|
||||||
|
mock.patch.object(s2.sys, "stdout", capture),
|
||||||
|
mock.patch.object(s2, "_InlineLocaldocs", NoisyLocaldocs),
|
||||||
|
mock.patch.object(
|
||||||
|
s2,
|
||||||
|
"_inline_hydrate",
|
||||||
|
return_value=(
|
||||||
|
{"request_id": "REQUEST-1", "attempt_id": "ATTEMPT-1"},
|
||||||
|
{},
|
||||||
|
Path("stage1"),
|
||||||
|
Path("stage1_deployment"),
|
||||||
|
Path("stage2_assets"),
|
||||||
|
{},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
mock.patch.object(s2, "load_release_lock", return_value={}),
|
||||||
|
mock.patch.object(s2, "_load_bound_contract_manifest", return_value=None),
|
||||||
|
mock.patch.object(
|
||||||
|
s2,
|
||||||
|
"execute_ingress",
|
||||||
|
side_effect=lambda *_args, **_kwargs: (
|
||||||
|
print("internal core noise")
|
||||||
|
or {"route": "TO_S2_10", "run_binding_digest": binding}
|
||||||
|
),
|
||||||
|
),
|
||||||
|
mock.patch.object(s2, "_inline_output_files", return_value={}),
|
||||||
|
mock.patch.object(s2, "_inline_publish_remote", return_value=logical_receipt),
|
||||||
|
):
|
||||||
|
self.assertEqual(s2.run_inline_mcp(), 0)
|
||||||
|
success = json.loads(capture.buffer.getvalue())
|
||||||
|
self.assertTrue(success["ok"])
|
||||||
|
self.assertEqual(success["workflow_id"], "S2_00")
|
||||||
|
self.assertEqual(success["status"], "SUCCEEDED")
|
||||||
|
self.assertEqual(success["run_id"], f"S2RUN-{binding}")
|
||||||
|
self.assertEqual(success["logical_publish_receipt"], logical_receipt)
|
||||||
|
self.assertNotIn(b"noise", capture.buffer.getvalue())
|
||||||
|
|
||||||
|
class FailingLocaldocs(NoisyLocaldocs):
|
||||||
|
def initialize(self) -> None:
|
||||||
|
print("failure noise")
|
||||||
|
raise s2.IngressError("FORCED_FAILURE", "forced")
|
||||||
|
|
||||||
|
capture = CaptureStdout()
|
||||||
|
with mock.patch.object(s2.sys, "stdout", capture), mock.patch.object(
|
||||||
|
s2,
|
||||||
|
"_InlineLocaldocs",
|
||||||
|
FailingLocaldocs,
|
||||||
|
):
|
||||||
|
self.assertEqual(s2.run_inline_mcp(), 2)
|
||||||
|
failure = json.loads(capture.buffer.getvalue())
|
||||||
|
self.assertFalse(failure["ok"])
|
||||||
|
self.assertEqual(failure["workflow_id"], "S2_00")
|
||||||
|
self.assertEqual(failure["status"], "FAILED_NO_BARRIER")
|
||||||
|
self.assertEqual(failure["expected_release_sha256"], s2.EXPECTED_STAGE2_RELEASE_SHA256)
|
||||||
|
self.assertEqual(failure["error"]["code"], "FORCED_FAILURE")
|
||||||
|
|
||||||
|
def test_existing_remote_status_with_other_binding_blocks_all_writes(self) -> None:
|
||||||
|
binding = "a" * 64
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
local_output = Path(directory) / "core"
|
||||||
|
s2.publish_atomically(
|
||||||
|
local_output,
|
||||||
|
self._artifacts(),
|
||||||
|
run_binding_digest=binding,
|
||||||
|
attempt_id="A-CONFLICT",
|
||||||
|
)
|
||||||
|
files = s2._inline_output_files(local_output, binding)
|
||||||
|
localdocs = MemoryLocaldocs()
|
||||||
|
output_root = f"stage2_runs/by-binding/{binding}"
|
||||||
|
status_path = f"{output_root}/ingress/ingress_status.json"
|
||||||
|
conflict = json.loads(files["ingress/ingress_status.json"])
|
||||||
|
conflict["run_binding_receipt"]["run_binding_digest"] = "b" * 64
|
||||||
|
localdocs.files[status_path] = s2.canonical_json_bytes(conflict)
|
||||||
|
with self.assertRaises(s2.IngressError) as raised:
|
||||||
|
s2._inline_publish_remote(localdocs, files, binding)
|
||||||
|
self.assertEqual(raised.exception.code, "RUN_ID_BINDING_CONFLICT")
|
||||||
|
self.assertFalse(any(op == "write" for op, _path in localdocs.operations))
|
||||||
|
|
||||||
def test_structural_fixture_cli_stdout_is_one_json_and_does_not_publish(self) -> None:
|
def test_structural_fixture_cli_stdout_is_one_json_and_does_not_publish(self) -> None:
|
||||||
release_path = ROOT / "manifest" / "stage2_release.json"
|
release_path = ROOT / "manifest" / "stage2_release.json"
|
||||||
fixture_path = ROOT / "tests" / "fixtures" / "cases" / "case_001_minimal_single_domain_single_cluster.json"
|
fixture_path = ROOT / "tests" / "fixtures" / "cases" / "case_001_minimal_single_domain_single_cluster.json"
|
||||||
direct = s2._execute_structural_fixture(fixture_path, s2.load_release_lock(release_path))
|
direct = s2._execute_structural_fixture(fixture_path, s2.load_release_lock(release_path))
|
||||||
self.assertFalse(direct["published"])
|
self.assertFalse(direct["published"])
|
||||||
project_root = ROOT.parents[3]
|
project_root = ROOT.parents[3]
|
||||||
completed = subprocess.run(
|
argv = [
|
||||||
[
|
"--workflow-id",
|
||||||
sys.executable,
|
"S2_00",
|
||||||
str(ROOT / "runtime" / "s2_00_ingress.py"),
|
"--release-ref",
|
||||||
|
"manifest/stage2_release.json",
|
||||||
|
"--run-id",
|
||||||
|
"UNIT-CLI-DEV-NOPUBLISH",
|
||||||
|
"--attempt-id",
|
||||||
|
"ATTEMPT-1",
|
||||||
|
"--user-context-sha256",
|
||||||
|
"a" * 64,
|
||||||
|
"--workspace-context-sha256",
|
||||||
|
"b" * 64,
|
||||||
|
"--stage1-run-root-ref",
|
||||||
|
str(project_root),
|
||||||
|
"--stage1-deployment-root-ref",
|
||||||
|
str(project_root),
|
||||||
|
]
|
||||||
|
capture = CaptureStdout()
|
||||||
|
with mock.patch.object(s2.sys, "stdout", capture):
|
||||||
|
returncode = s2.main(argv)
|
||||||
|
self.assertEqual(returncode, 2, capture.buffer.getvalue())
|
||||||
|
parsed = json.loads(capture.buffer.getvalue())
|
||||||
|
self.assertFalse(parsed["ok"])
|
||||||
|
self.assertEqual(parsed["error"]["code"], "DEV_FIXTURE_REAL_RUN_FORBIDDEN")
|
||||||
|
self.assertFalse((project_root / "stage2_runs" / "UNIT-CLI-DEV-NOPUBLISH").exists())
|
||||||
|
|
||||||
|
def test_structural_fixture_cannot_accept_output_argument(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
root = Path(directory)
|
||||||
|
project_root = ROOT.parents[3]
|
||||||
|
argv = [
|
||||||
"--workflow-id",
|
"--workflow-id",
|
||||||
"S2_00",
|
"S2_00",
|
||||||
"--release-ref",
|
"--release-ref",
|
||||||
"manifest/stage2_release.json",
|
"manifest/stage2_release.json",
|
||||||
"--run-id",
|
"--run-id",
|
||||||
"UNIT-CLI-DEV-NOPUBLISH",
|
"UNIT-CLI-BAD-ARGV",
|
||||||
"--attempt-id",
|
"--attempt-id",
|
||||||
"ATTEMPT-1",
|
"ATTEMPT-1",
|
||||||
"--user-context-sha256",
|
"--user-context-sha256",
|
||||||
@@ -160,53 +440,14 @@ class FailureAndAtomicPublishTests(unittest.TestCase):
|
|||||||
str(project_root),
|
str(project_root),
|
||||||
"--stage1-deployment-root-ref",
|
"--stage1-deployment-root-ref",
|
||||||
str(project_root),
|
str(project_root),
|
||||||
],
|
"--output-dir",
|
||||||
check=False,
|
str(root / "forbidden"),
|
||||||
capture_output=True,
|
]
|
||||||
text=True,
|
capture = CaptureStdout()
|
||||||
timeout=10,
|
with mock.patch.object(s2.sys, "stdout", capture):
|
||||||
)
|
returncode = s2.main(argv)
|
||||||
self.assertEqual(completed.returncode, 2, completed.stdout)
|
self.assertEqual(returncode, 2)
|
||||||
parsed = json.loads(completed.stdout)
|
self.assertEqual(json.loads(capture.buffer.getvalue())["error"]["code"], "CLI_ARGUMENT_ERROR")
|
||||||
self.assertFalse(parsed["ok"])
|
|
||||||
self.assertEqual(parsed["error"]["code"], "DEV_FIXTURE_REAL_RUN_FORBIDDEN")
|
|
||||||
self.assertFalse((project_root / "stage2_runs" / "UNIT-CLI-DEV-NOPUBLISH").exists())
|
|
||||||
self.assertEqual(completed.stderr, "")
|
|
||||||
|
|
||||||
def test_structural_fixture_cannot_accept_output_argument(self) -> None:
|
|
||||||
with tempfile.TemporaryDirectory() as directory:
|
|
||||||
root = Path(directory)
|
|
||||||
project_root = ROOT.parents[3]
|
|
||||||
completed = subprocess.run(
|
|
||||||
[
|
|
||||||
sys.executable,
|
|
||||||
str(ROOT / "runtime" / "s2_00_ingress.py"),
|
|
||||||
"--workflow-id",
|
|
||||||
"S2_00",
|
|
||||||
"--release-ref",
|
|
||||||
"manifest/stage2_release.json",
|
|
||||||
"--run-id",
|
|
||||||
"UNIT-CLI-BAD-ARGV",
|
|
||||||
"--attempt-id",
|
|
||||||
"ATTEMPT-1",
|
|
||||||
"--user-context-sha256",
|
|
||||||
"a" * 64,
|
|
||||||
"--workspace-context-sha256",
|
|
||||||
"b" * 64,
|
|
||||||
"--stage1-run-root-ref",
|
|
||||||
str(project_root),
|
|
||||||
"--stage1-deployment-root-ref",
|
|
||||||
str(project_root),
|
|
||||||
"--output-dir",
|
|
||||||
str(root / "forbidden"),
|
|
||||||
],
|
|
||||||
check=False,
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
timeout=10,
|
|
||||||
)
|
|
||||||
self.assertEqual(completed.returncode, 2)
|
|
||||||
self.assertEqual(json.loads(completed.stdout)["error"]["code"], "CLI_ARGUMENT_ERROR")
|
|
||||||
self.assertFalse((root / "forbidden").exists())
|
self.assertFalse((root / "forbidden").exists())
|
||||||
|
|
||||||
def test_fixture_inventory_has_all_ten_cases_and_single_defect_mutations(self) -> None:
|
def test_fixture_inventory_has_all_ten_cases_and_single_defect_mutations(self) -> None:
|
||||||
|
|||||||
+303
-62
@@ -1,12 +1,13 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import io
|
||||||
import json
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import subprocess
|
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
|
||||||
ROOT = Path(__file__).resolve().parents[2]
|
ROOT = Path(__file__).resolve().parents[2]
|
||||||
@@ -14,27 +15,79 @@ sys.path.insert(0, str(ROOT))
|
|||||||
from runtime import s2_00_ingress as s2 # noqa: E402
|
from runtime import s2_00_ingress as s2 # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
|
class MemoryLocaldocs:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.files: dict[str, bytes] = {}
|
||||||
|
self.operations: list[tuple[str, str]] = []
|
||||||
|
|
||||||
|
def read_binary_optional(self, path: str) -> bytes | None:
|
||||||
|
self.operations.append(("read_optional", path))
|
||||||
|
return self.files.get(path)
|
||||||
|
|
||||||
|
def read_binary(self, path: str) -> bytes:
|
||||||
|
self.operations.append(("read", path))
|
||||||
|
if path not in self.files:
|
||||||
|
raise s2.IngressError("LOCALDOCS_NOT_FOUND", f"missing: {path}")
|
||||||
|
return self.files[path]
|
||||||
|
|
||||||
|
def write_binary_verified(self, path: str, payload: bytes) -> str:
|
||||||
|
self.operations.append(("write", path))
|
||||||
|
self.files[path] = payload
|
||||||
|
self.operations.append(("read", path))
|
||||||
|
if self.files[path] != payload:
|
||||||
|
raise AssertionError("memory read-back failed")
|
||||||
|
return hashlib.sha256(payload).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
class CaptureStdout:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.buffer = io.BytesIO()
|
||||||
|
|
||||||
|
def write(self, _text: str) -> int:
|
||||||
|
raise AssertionError("receipt must be written through stdout.buffer")
|
||||||
|
|
||||||
|
def flush(self) -> None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class FailureAndAtomicPublishTests(unittest.TestCase):
|
class FailureAndAtomicPublishTests(unittest.TestCase):
|
||||||
def _artifacts(self, route: str = "TO_S2_10") -> dict[str, object]:
|
def _artifacts(self, route: str = "TO_S2_10") -> dict[str, object]:
|
||||||
|
binding_digest = "a" * 64
|
||||||
|
run_id = f"S2RUN-{binding_digest}"
|
||||||
run_binding = {
|
run_binding = {
|
||||||
"schema_version": "stage2_s2_00_run_binding_receipt.v1",
|
"schema_version": "stage2_s2_00_run_binding_receipt.v1.1",
|
||||||
"run_id": "RUN-1",
|
"request_id": "REQUEST-1",
|
||||||
|
"run_id": run_id,
|
||||||
"input_set_digest": "1" * 64,
|
"input_set_digest": "1" * 64,
|
||||||
"stage2_release_digest": "2" * 64,
|
"stage2_release_digest": "2" * 64,
|
||||||
"algorithm_digest": "3" * 64,
|
"algorithm_digest": "3" * 64,
|
||||||
"release_class": "SUBSET_CANARY_RELEASE",
|
"release_class": "SUBSET_CANARY_RELEASE",
|
||||||
"run_binding_digest": "a" * 64,
|
"run_binding_digest": binding_digest,
|
||||||
|
"canonical_output_root": f"stage2_runs/by-binding/{binding_digest}/",
|
||||||
|
"run_identity_derivation": "RUN_ID_PREFIXED_FROM_RUN_BINDING_DIGEST",
|
||||||
|
"output_root_derivation": "stage2_runs/by-binding/<run_binding_digest>/",
|
||||||
"user_context_sha256": "4" * 64,
|
"user_context_sha256": "4" * 64,
|
||||||
"workspace_context_sha256": "5" * 64,
|
"workspace_context_sha256": "5" * 64,
|
||||||
}
|
}
|
||||||
manifest = {
|
manifest = {
|
||||||
"schema_version": "stage2_s2_00_stage1_input_manifest.v1",
|
"schema_version": "stage2_s2_00_stage1_input_manifest.v1.1",
|
||||||
"run_id": "RUN-1",
|
"run_id": run_id,
|
||||||
"release_class": "SUBSET_CANARY_RELEASE",
|
"release_class": "SUBSET_CANARY_RELEASE",
|
||||||
"source_rows": [],
|
"source_rows": [],
|
||||||
"source_row_order": [],
|
"source_row_order": [],
|
||||||
"signal_all_adapter_id": "S2A-SIGNAL-ALL-V1",
|
"signal_all_adapter_id": "S2A-SIGNAL-ALL-V1",
|
||||||
"dual_sg01_adapter_id": "S2A-DUAL-SG01-V1",
|
"dual_sg01_adapter_id": "S2A-DUAL-SG01-V1",
|
||||||
|
"hydration_stability_receipt": {
|
||||||
|
"schema_version": "stage2_s2_00_two_pass_hydration_receipt.v1",
|
||||||
|
"transport": "localdocs.read_binary_doc",
|
||||||
|
"read_policy": "BOUNDED_TWO_PASS_BINARY_RAW_HASH_MAP_EQUALITY",
|
||||||
|
"read_pass_count": 2,
|
||||||
|
"max_read_passes": 2,
|
||||||
|
"pass_1_raw_hash_map_digest": "6" * 64,
|
||||||
|
"pass_2_raw_hash_map_digest": "6" * 64,
|
||||||
|
"source_receipts": [],
|
||||||
|
"stability_status": "STABLE",
|
||||||
|
},
|
||||||
"snapshot_start_digest": "6" * 64,
|
"snapshot_start_digest": "6" * 64,
|
||||||
"snapshot_end_digest": "6" * 64,
|
"snapshot_end_digest": "6" * 64,
|
||||||
"snapshot_status": "STABLE",
|
"snapshot_status": "STABLE",
|
||||||
@@ -42,7 +95,7 @@ class FailureAndAtomicPublishTests(unittest.TestCase):
|
|||||||
}
|
}
|
||||||
intake = {
|
intake = {
|
||||||
"schema_version": "stage2_s2_00_intake_report.v1",
|
"schema_version": "stage2_s2_00_intake_report.v1",
|
||||||
"run_id": "RUN-1",
|
"run_id": run_id,
|
||||||
"source_counts": {
|
"source_counts": {
|
||||||
"declared": 0,
|
"declared": 0,
|
||||||
"observed": 0,
|
"observed": 0,
|
||||||
@@ -74,7 +127,7 @@ class FailureAndAtomicPublishTests(unittest.TestCase):
|
|||||||
ledger = {
|
ledger = {
|
||||||
"schema_version": "stage2_issue_ledger_base.v1",
|
"schema_version": "stage2_issue_ledger_base.v1",
|
||||||
"producer_id": "S2_00",
|
"producer_id": "S2_00",
|
||||||
"run_id": "RUN-1",
|
"run_id": run_id,
|
||||||
"input_set_digest": "1" * 64,
|
"input_set_digest": "1" * 64,
|
||||||
"mapping_table_version": "S2-REVIEW-MAP-V1",
|
"mapping_table_version": "S2-REVIEW-MAP-V1",
|
||||||
"issues": [],
|
"issues": [],
|
||||||
@@ -87,8 +140,8 @@ class FailureAndAtomicPublishTests(unittest.TestCase):
|
|||||||
"ingress/intake_report.json": intake,
|
"ingress/intake_report.json": intake,
|
||||||
"review/issue_ledger.base.json": ledger,
|
"review/issue_ledger.base.json": ledger,
|
||||||
"ingress/ingress_status.json": {
|
"ingress/ingress_status.json": {
|
||||||
"schema_version": "stage2_s2_00_ingress_status.v1",
|
"schema_version": "stage2_s2_00_ingress_status.v1.1",
|
||||||
"run_id": "RUN-1",
|
"run_id": run_id,
|
||||||
"run_binding_receipt": run_binding,
|
"run_binding_receipt": run_binding,
|
||||||
"route": route,
|
"route": route,
|
||||||
"executable_cluster_ids": ["CL-" + "a" * 24],
|
"executable_cluster_ids": ["CL-" + "a" * 24],
|
||||||
@@ -96,9 +149,13 @@ class FailureAndAtomicPublishTests(unittest.TestCase):
|
|||||||
"issue_codes": [],
|
"issue_codes": [],
|
||||||
"output_barrier": {
|
"output_barrier": {
|
||||||
"barrier_id": "S2_00_INGRESS_STATUS_BARRIER",
|
"barrier_id": "S2_00_INGRESS_STATUS_BARRIER",
|
||||||
|
"barrier_path": "ingress/ingress_status.json",
|
||||||
|
"publish_semantics": "STATUS_LAST_LOGICAL_COMMIT",
|
||||||
|
"canonical_output_root": f"stage2_runs/by-binding/{binding_digest}/",
|
||||||
"branch": "NORMAL",
|
"branch": "NORMAL",
|
||||||
"artifacts": [],
|
"artifacts": [],
|
||||||
"artifact_set_digest": s2.canonical_digest([]),
|
"artifact_set_digest": s2.canonical_digest([]),
|
||||||
|
"non_status_artifacts_read_back_verified": True,
|
||||||
"written_last": True,
|
"written_last": True,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -134,22 +191,245 @@ class FailureAndAtomicPublishTests(unittest.TestCase):
|
|||||||
self.assertEqual(raised.exception.code, "RUN_TUPLE_CONFLICT")
|
self.assertEqual(raised.exception.code, "RUN_TUPLE_CONFLICT")
|
||||||
self.assertEqual((output / "ingress" / "ingress_status.json").read_bytes(), original)
|
self.assertEqual((output / "ingress" / "ingress_status.json").read_bytes(), original)
|
||||||
|
|
||||||
|
def test_remote_publish_is_status_last_and_idempotent(self) -> None:
|
||||||
|
binding = "a" * 64
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
local_output = Path(directory) / "core"
|
||||||
|
s2.publish_atomically(
|
||||||
|
local_output,
|
||||||
|
self._artifacts(),
|
||||||
|
run_binding_digest=binding,
|
||||||
|
attempt_id="A-REMOTE",
|
||||||
|
)
|
||||||
|
files = s2._inline_output_files(local_output, binding)
|
||||||
|
localdocs = MemoryLocaldocs()
|
||||||
|
receipt = s2._inline_publish_remote(localdocs, files, binding)
|
||||||
|
self.assertEqual(receipt["publication_status"], "PUBLISHED_STATUS_LAST")
|
||||||
|
self.assertEqual(receipt["schema_version"], "stage2_logical_publish_receipt.v1")
|
||||||
|
self.assertEqual(receipt["run_binding_digest"], binding)
|
||||||
|
self.assertEqual(receipt["canonical_output_root"], f"stage2_runs/by-binding/{binding}/")
|
||||||
|
self.assertTrue(receipt["barrier_written_last"])
|
||||||
|
self.assertTrue(receipt["non_status_artifacts_read_back_verified"])
|
||||||
|
self.assertEqual(
|
||||||
|
receipt["artifact_set_digest"],
|
||||||
|
s2.canonical_digest(receipt["artifacts"]),
|
||||||
|
)
|
||||||
|
self.assertTrue(all("byte_length" in row for row in receipt["artifacts"]))
|
||||||
|
writes = [row for row in localdocs.operations if row[0] == "write"]
|
||||||
|
self.assertTrue(writes)
|
||||||
|
self.assertTrue(writes[-1][1].endswith("/ingress/ingress_status.json"))
|
||||||
|
before = list(localdocs.operations)
|
||||||
|
repeated = s2._inline_publish_remote(localdocs, files, binding)
|
||||||
|
self.assertEqual(repeated["publication_status"], "IDEMPOTENT_SUCCESS")
|
||||||
|
self.assertFalse(any(op == "write" for op, _path in localdocs.operations[len(before) :]))
|
||||||
|
|
||||||
|
def test_localdocs_write_requires_byte_identical_readback(self) -> None:
|
||||||
|
class Response:
|
||||||
|
def __init__(self, payload: object) -> None:
|
||||||
|
self.content = json.dumps(payload).encode("utf-8")
|
||||||
|
self.headers = {"mcp-session-id": "session-a"}
|
||||||
|
|
||||||
|
def raise_for_status(self) -> None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
class Client:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
wrong = b"wrong"
|
||||||
|
self.responses = [
|
||||||
|
Response(
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 10,
|
||||||
|
"result": {"content": [{"type": "text", "text": "{}"}]},
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Response(
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 11,
|
||||||
|
"result": {
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": json.dumps(
|
||||||
|
{
|
||||||
|
"content_base64": __import__("base64").b64encode(wrong).decode("ascii"),
|
||||||
|
"byte_length": len(wrong),
|
||||||
|
"sha256": hashlib.sha256(wrong).hexdigest(),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
def post(self, *_args: object, **_kwargs: object) -> Response:
|
||||||
|
return self.responses.pop(0)
|
||||||
|
|
||||||
|
def close(self) -> None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
localdocs = s2._InlineLocaldocs("a" * 64, "b" * 64, client=Client())
|
||||||
|
localdocs._initialized = True
|
||||||
|
localdocs._session_id = "session-a"
|
||||||
|
localdocs.headers["mcp-session-id"] = "session-a"
|
||||||
|
with self.assertRaises(s2.IngressError) as raised:
|
||||||
|
localdocs.write_binary_verified("stage2_runs/by-binding/" + "a" * 64 + "/x.json", b"expected")
|
||||||
|
self.assertEqual(raised.exception.code, "LOCALDOCS_WRITE_READBACK_MISMATCH")
|
||||||
|
|
||||||
|
def test_inline_runner_suppresses_internal_stdout_and_emits_schema_shaped_receipts(self) -> None:
|
||||||
|
class NoisyLocaldocs:
|
||||||
|
def __init__(self, *_args: object, **_kwargs: object) -> None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def initialize(self) -> None:
|
||||||
|
print("internal initialize noise")
|
||||||
|
|
||||||
|
def close(self) -> None:
|
||||||
|
print("internal close noise")
|
||||||
|
|
||||||
|
binding = "a" * 64
|
||||||
|
logical_receipt = {
|
||||||
|
"schema_version": "stage2_logical_publish_receipt.v1",
|
||||||
|
"barrier_id": "S2_00_INGRESS_STATUS_BARRIER",
|
||||||
|
"barrier_path": "ingress/ingress_status.json",
|
||||||
|
"publish_semantics": "STATUS_LAST_LOGICAL_COMMIT",
|
||||||
|
"canonical_output_root": f"stage2_runs/by-binding/{binding}/",
|
||||||
|
"run_binding_digest": binding,
|
||||||
|
"branch": "NORMAL",
|
||||||
|
"artifacts": [],
|
||||||
|
"artifact_set_digest": s2.canonical_digest([]),
|
||||||
|
"barrier_raw_sha256": "b" * 64,
|
||||||
|
"non_status_artifacts_read_back_verified": True,
|
||||||
|
"barrier_written_last": True,
|
||||||
|
"downstream_consumption_allowed": True,
|
||||||
|
"publication_status": "PUBLISHED_STATUS_LAST",
|
||||||
|
}
|
||||||
|
capture = CaptureStdout()
|
||||||
|
with (
|
||||||
|
mock.patch.object(s2.sys, "stdout", capture),
|
||||||
|
mock.patch.object(s2, "_InlineLocaldocs", NoisyLocaldocs),
|
||||||
|
mock.patch.object(
|
||||||
|
s2,
|
||||||
|
"_inline_hydrate",
|
||||||
|
return_value=(
|
||||||
|
{"request_id": "REQUEST-1", "attempt_id": "ATTEMPT-1"},
|
||||||
|
{},
|
||||||
|
Path("stage1"),
|
||||||
|
Path("stage1_deployment"),
|
||||||
|
Path("stage2_assets"),
|
||||||
|
{},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
mock.patch.object(s2, "load_release_lock", return_value={}),
|
||||||
|
mock.patch.object(s2, "_load_bound_contract_manifest", return_value=None),
|
||||||
|
mock.patch.object(
|
||||||
|
s2,
|
||||||
|
"execute_ingress",
|
||||||
|
side_effect=lambda *_args, **_kwargs: (
|
||||||
|
print("internal core noise")
|
||||||
|
or {"route": "TO_S2_10", "run_binding_digest": binding}
|
||||||
|
),
|
||||||
|
),
|
||||||
|
mock.patch.object(s2, "_inline_output_files", return_value={}),
|
||||||
|
mock.patch.object(s2, "_inline_publish_remote", return_value=logical_receipt),
|
||||||
|
):
|
||||||
|
self.assertEqual(s2.run_inline_mcp(), 0)
|
||||||
|
success = json.loads(capture.buffer.getvalue())
|
||||||
|
self.assertTrue(success["ok"])
|
||||||
|
self.assertEqual(success["workflow_id"], "S2_00")
|
||||||
|
self.assertEqual(success["status"], "SUCCEEDED")
|
||||||
|
self.assertEqual(success["run_id"], f"S2RUN-{binding}")
|
||||||
|
self.assertEqual(success["logical_publish_receipt"], logical_receipt)
|
||||||
|
self.assertNotIn(b"noise", capture.buffer.getvalue())
|
||||||
|
|
||||||
|
class FailingLocaldocs(NoisyLocaldocs):
|
||||||
|
def initialize(self) -> None:
|
||||||
|
print("failure noise")
|
||||||
|
raise s2.IngressError("FORCED_FAILURE", "forced")
|
||||||
|
|
||||||
|
capture = CaptureStdout()
|
||||||
|
with mock.patch.object(s2.sys, "stdout", capture), mock.patch.object(
|
||||||
|
s2,
|
||||||
|
"_InlineLocaldocs",
|
||||||
|
FailingLocaldocs,
|
||||||
|
):
|
||||||
|
self.assertEqual(s2.run_inline_mcp(), 2)
|
||||||
|
failure = json.loads(capture.buffer.getvalue())
|
||||||
|
self.assertFalse(failure["ok"])
|
||||||
|
self.assertEqual(failure["workflow_id"], "S2_00")
|
||||||
|
self.assertEqual(failure["status"], "FAILED_NO_BARRIER")
|
||||||
|
self.assertEqual(failure["expected_release_sha256"], s2.EXPECTED_STAGE2_RELEASE_SHA256)
|
||||||
|
self.assertEqual(failure["error"]["code"], "FORCED_FAILURE")
|
||||||
|
|
||||||
|
def test_existing_remote_status_with_other_binding_blocks_all_writes(self) -> None:
|
||||||
|
binding = "a" * 64
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
local_output = Path(directory) / "core"
|
||||||
|
s2.publish_atomically(
|
||||||
|
local_output,
|
||||||
|
self._artifacts(),
|
||||||
|
run_binding_digest=binding,
|
||||||
|
attempt_id="A-CONFLICT",
|
||||||
|
)
|
||||||
|
files = s2._inline_output_files(local_output, binding)
|
||||||
|
localdocs = MemoryLocaldocs()
|
||||||
|
output_root = f"stage2_runs/by-binding/{binding}"
|
||||||
|
status_path = f"{output_root}/ingress/ingress_status.json"
|
||||||
|
conflict = json.loads(files["ingress/ingress_status.json"])
|
||||||
|
conflict["run_binding_receipt"]["run_binding_digest"] = "b" * 64
|
||||||
|
localdocs.files[status_path] = s2.canonical_json_bytes(conflict)
|
||||||
|
with self.assertRaises(s2.IngressError) as raised:
|
||||||
|
s2._inline_publish_remote(localdocs, files, binding)
|
||||||
|
self.assertEqual(raised.exception.code, "RUN_ID_BINDING_CONFLICT")
|
||||||
|
self.assertFalse(any(op == "write" for op, _path in localdocs.operations))
|
||||||
|
|
||||||
def test_structural_fixture_cli_stdout_is_one_json_and_does_not_publish(self) -> None:
|
def test_structural_fixture_cli_stdout_is_one_json_and_does_not_publish(self) -> None:
|
||||||
release_path = ROOT / "manifest" / "stage2_release.json"
|
release_path = ROOT / "manifest" / "stage2_release.json"
|
||||||
fixture_path = ROOT / "tests" / "fixtures" / "cases" / "case_001_minimal_single_domain_single_cluster.json"
|
fixture_path = ROOT / "tests" / "fixtures" / "cases" / "case_001_minimal_single_domain_single_cluster.json"
|
||||||
direct = s2._execute_structural_fixture(fixture_path, s2.load_release_lock(release_path))
|
direct = s2._execute_structural_fixture(fixture_path, s2.load_release_lock(release_path))
|
||||||
self.assertFalse(direct["published"])
|
self.assertFalse(direct["published"])
|
||||||
project_root = ROOT.parents[3]
|
project_root = ROOT.parents[3]
|
||||||
completed = subprocess.run(
|
argv = [
|
||||||
[
|
"--workflow-id",
|
||||||
sys.executable,
|
"S2_00",
|
||||||
str(ROOT / "runtime" / "s2_00_ingress.py"),
|
"--release-ref",
|
||||||
|
"manifest/stage2_release.json",
|
||||||
|
"--run-id",
|
||||||
|
"UNIT-CLI-DEV-NOPUBLISH",
|
||||||
|
"--attempt-id",
|
||||||
|
"ATTEMPT-1",
|
||||||
|
"--user-context-sha256",
|
||||||
|
"a" * 64,
|
||||||
|
"--workspace-context-sha256",
|
||||||
|
"b" * 64,
|
||||||
|
"--stage1-run-root-ref",
|
||||||
|
str(project_root),
|
||||||
|
"--stage1-deployment-root-ref",
|
||||||
|
str(project_root),
|
||||||
|
]
|
||||||
|
capture = CaptureStdout()
|
||||||
|
with mock.patch.object(s2.sys, "stdout", capture):
|
||||||
|
returncode = s2.main(argv)
|
||||||
|
self.assertEqual(returncode, 2, capture.buffer.getvalue())
|
||||||
|
parsed = json.loads(capture.buffer.getvalue())
|
||||||
|
self.assertFalse(parsed["ok"])
|
||||||
|
self.assertEqual(parsed["error"]["code"], "DEV_FIXTURE_REAL_RUN_FORBIDDEN")
|
||||||
|
self.assertFalse((project_root / "stage2_runs" / "UNIT-CLI-DEV-NOPUBLISH").exists())
|
||||||
|
|
||||||
|
def test_structural_fixture_cannot_accept_output_argument(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
root = Path(directory)
|
||||||
|
project_root = ROOT.parents[3]
|
||||||
|
argv = [
|
||||||
"--workflow-id",
|
"--workflow-id",
|
||||||
"S2_00",
|
"S2_00",
|
||||||
"--release-ref",
|
"--release-ref",
|
||||||
"manifest/stage2_release.json",
|
"manifest/stage2_release.json",
|
||||||
"--run-id",
|
"--run-id",
|
||||||
"UNIT-CLI-DEV-NOPUBLISH",
|
"UNIT-CLI-BAD-ARGV",
|
||||||
"--attempt-id",
|
"--attempt-id",
|
||||||
"ATTEMPT-1",
|
"ATTEMPT-1",
|
||||||
"--user-context-sha256",
|
"--user-context-sha256",
|
||||||
@@ -160,53 +440,14 @@ class FailureAndAtomicPublishTests(unittest.TestCase):
|
|||||||
str(project_root),
|
str(project_root),
|
||||||
"--stage1-deployment-root-ref",
|
"--stage1-deployment-root-ref",
|
||||||
str(project_root),
|
str(project_root),
|
||||||
],
|
"--output-dir",
|
||||||
check=False,
|
str(root / "forbidden"),
|
||||||
capture_output=True,
|
]
|
||||||
text=True,
|
capture = CaptureStdout()
|
||||||
timeout=10,
|
with mock.patch.object(s2.sys, "stdout", capture):
|
||||||
)
|
returncode = s2.main(argv)
|
||||||
self.assertEqual(completed.returncode, 2, completed.stdout)
|
self.assertEqual(returncode, 2)
|
||||||
parsed = json.loads(completed.stdout)
|
self.assertEqual(json.loads(capture.buffer.getvalue())["error"]["code"], "CLI_ARGUMENT_ERROR")
|
||||||
self.assertFalse(parsed["ok"])
|
|
||||||
self.assertEqual(parsed["error"]["code"], "DEV_FIXTURE_REAL_RUN_FORBIDDEN")
|
|
||||||
self.assertFalse((project_root / "stage2_runs" / "UNIT-CLI-DEV-NOPUBLISH").exists())
|
|
||||||
self.assertEqual(completed.stderr, "")
|
|
||||||
|
|
||||||
def test_structural_fixture_cannot_accept_output_argument(self) -> None:
|
|
||||||
with tempfile.TemporaryDirectory() as directory:
|
|
||||||
root = Path(directory)
|
|
||||||
project_root = ROOT.parents[3]
|
|
||||||
completed = subprocess.run(
|
|
||||||
[
|
|
||||||
sys.executable,
|
|
||||||
str(ROOT / "runtime" / "s2_00_ingress.py"),
|
|
||||||
"--workflow-id",
|
|
||||||
"S2_00",
|
|
||||||
"--release-ref",
|
|
||||||
"manifest/stage2_release.json",
|
|
||||||
"--run-id",
|
|
||||||
"UNIT-CLI-BAD-ARGV",
|
|
||||||
"--attempt-id",
|
|
||||||
"ATTEMPT-1",
|
|
||||||
"--user-context-sha256",
|
|
||||||
"a" * 64,
|
|
||||||
"--workspace-context-sha256",
|
|
||||||
"b" * 64,
|
|
||||||
"--stage1-run-root-ref",
|
|
||||||
str(project_root),
|
|
||||||
"--stage1-deployment-root-ref",
|
|
||||||
str(project_root),
|
|
||||||
"--output-dir",
|
|
||||||
str(root / "forbidden"),
|
|
||||||
],
|
|
||||||
check=False,
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
timeout=10,
|
|
||||||
)
|
|
||||||
self.assertEqual(completed.returncode, 2)
|
|
||||||
self.assertEqual(json.loads(completed.stdout)["error"]["code"], "CLI_ARGUMENT_ERROR")
|
|
||||||
self.assertFalse((root / "forbidden").exists())
|
self.assertFalse((root / "forbidden").exists())
|
||||||
|
|
||||||
def test_fixture_inventory_has_all_ten_cases_and_single_defect_mutations(self) -> None:
|
def test_fixture_inventory_has_all_ten_cases_and_single_defect_mutations(self) -> None:
|
||||||
|
|||||||
+281
@@ -0,0 +1,281 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
sys.path.insert(0, str(ROOT))
|
||||||
|
from offline_build import build_s2_00_inline_projection as builder # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
|
def valid_inline_code() -> str:
|
||||||
|
return "\n".join(
|
||||||
|
[
|
||||||
|
"#!/usr/bin/env python3",
|
||||||
|
"from __future__ import annotations",
|
||||||
|
"import json",
|
||||||
|
"import httpx",
|
||||||
|
"LOCALDOCS_URL = 'http://mcp-localdocs:8012/mcp'",
|
||||||
|
"USER_HASH = '{{__user_hash__}}'",
|
||||||
|
"WORKSPACE_HASH = '{{__workspace_hash__}}'",
|
||||||
|
"READ_TOOL = 'read_binary_doc'",
|
||||||
|
"WRITE_TOOL = 'write_binary_file'",
|
||||||
|
"expected_stage2_release_sha256 = '0' * 64",
|
||||||
|
"def main():",
|
||||||
|
" return {'status': 'OK'}",
|
||||||
|
"print(json.dumps(main(), ensure_ascii=False, sort_keys=True))",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def authoring_yaml(code: str | None = None) -> bytes:
|
||||||
|
source = valid_inline_code() if code is None else code
|
||||||
|
indented = "\n".join(f" {line}" for line in source.split("\n"))
|
||||||
|
return (
|
||||||
|
"Agent:\n"
|
||||||
|
" name: Stage_2_S2_00_v1\n"
|
||||||
|
" version: '1.1.0'\n"
|
||||||
|
" Stages:\n"
|
||||||
|
" - name: S2_00\n"
|
||||||
|
" tools:\n"
|
||||||
|
" mcpServers:\n"
|
||||||
|
" localdocs:\n"
|
||||||
|
" type: streamable-http\n"
|
||||||
|
" url: http://mcp-localdocs:8012/mcp\n"
|
||||||
|
" code-executor:\n"
|
||||||
|
" type: streamable-http\n"
|
||||||
|
" url: https://code-executor.mcp.eroomai.com/mcp\n"
|
||||||
|
" tasks:\n"
|
||||||
|
" - task_name: Task_S2_00_deterministic_ingress\n"
|
||||||
|
" mcp: code-executor\n"
|
||||||
|
" tool_name: run_code\n"
|
||||||
|
" parameters:\n"
|
||||||
|
" language: python\n"
|
||||||
|
" requirements: 'httpx==0.28.1'\n"
|
||||||
|
" network: agent-network\n"
|
||||||
|
" timeout: 300\n"
|
||||||
|
" code: |-\n"
|
||||||
|
f"{indented}\n"
|
||||||
|
" task_procedure:\n"
|
||||||
|
" IN:\n"
|
||||||
|
" nexts: [Task_S2_00_deterministic_ingress]\n"
|
||||||
|
" wait_until: []\n"
|
||||||
|
" Task_S2_00_deterministic_ingress:\n"
|
||||||
|
" nexts: [OUT]\n"
|
||||||
|
" wait_until: [IN]\n"
|
||||||
|
" OUT:\n"
|
||||||
|
" nexts: []\n"
|
||||||
|
" wait_until: [Task_S2_00_deterministic_ingress]\n"
|
||||||
|
" prevs: []\n"
|
||||||
|
" nexts: []\n"
|
||||||
|
).encode("utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
class InlineCodeProjectionUnitTests(unittest.TestCase):
|
||||||
|
def test_unique_key_loader_rejects_duplicate_mapping_key(self) -> None:
|
||||||
|
with self.assertRaises(builder.ProjectionError) as raised:
|
||||||
|
builder.parse_authoring_bytes(b"Agent:\n name: one\n name: two\n")
|
||||||
|
self.assertEqual(raised.exception.code, "YAML_DUPLICATE_KEY")
|
||||||
|
|
||||||
|
def test_authoring_plaintext_secret_outside_code_is_rejected(self) -> None:
|
||||||
|
raw = authoring_yaml().replace(
|
||||||
|
b" version: '1.1.0'",
|
||||||
|
b" version: '1.1.0'\n leaked_token: 'Bearer abcdefghijklmnopqrstuvwxyz'",
|
||||||
|
)
|
||||||
|
with self.assertRaises(builder.ProjectionError) as raised:
|
||||||
|
builder.parse_authoring_bytes(raw)
|
||||||
|
self.assertEqual(raised.exception.code, "AUTHORING_PLAINTEXT_SECRET")
|
||||||
|
|
||||||
|
def test_parser_returned_code_bytes_are_not_transformed(self) -> None:
|
||||||
|
expected_code = valid_inline_code()
|
||||||
|
raw = authoring_yaml(expected_code)
|
||||||
|
document = builder.parse_authoring_bytes(raw)
|
||||||
|
_stage, task = builder.extract_run_code_task(document)
|
||||||
|
observed = task["parameters"]["code"]
|
||||||
|
self.assertEqual(observed, expected_code)
|
||||||
|
self.assertEqual(observed.encode("utf-8"), expected_code.encode("utf-8"))
|
||||||
|
self.assertFalse(observed.endswith("\n"))
|
||||||
|
|
||||||
|
def test_valid_code_compiles_and_has_closed_imports(self) -> None:
|
||||||
|
report = builder.validate_inline_code(valid_inline_code())
|
||||||
|
self.assertEqual(report["compile_status"], "PASS")
|
||||||
|
self.assertEqual(report["ast_status"], "PASS")
|
||||||
|
self.assertEqual(report["imports"], ["__future__", "httpx", "json"])
|
||||||
|
self.assertEqual(report["code_sha256"], builder.sha256_bytes(valid_inline_code().encode("utf-8")))
|
||||||
|
|
||||||
|
def test_schema_identifier_url_is_allowed_but_network_endpoint_is_localdocs_only(self) -> None:
|
||||||
|
schema_identifier = (
|
||||||
|
valid_inline_code()
|
||||||
|
+ "\nSCHEMA_ID = 'https://schemas.liti-agent.local/stage2/s2_00/context.schema.v1.json'"
|
||||||
|
)
|
||||||
|
self.assertEqual(builder.validate_inline_code(schema_identifier)["external_url_count"], 0)
|
||||||
|
with self.assertRaises(builder.ProjectionError) as raised:
|
||||||
|
builder.validate_inline_code(
|
||||||
|
schema_identifier + "\nhttpx.post('https://schemas.liti-agent.local/not-an-endpoint')"
|
||||||
|
)
|
||||||
|
self.assertEqual(raised.exception.code, "INLINE_CODE_NETWORK_ENDPOINT_FORBIDDEN")
|
||||||
|
|
||||||
|
def test_expected_outputs_are_exact_source_and_code_bytes(self) -> None:
|
||||||
|
raw = authoring_yaml()
|
||||||
|
document = builder.parse_authoring_bytes(raw)
|
||||||
|
outputs, receipt = builder.expected_outputs(raw, document)
|
||||||
|
self.assertEqual(outputs[builder.PROJECTION_PATH], raw)
|
||||||
|
self.assertEqual(outputs[builder.MIRROR_PY_PATH], valid_inline_code().encode("utf-8"))
|
||||||
|
self.assertEqual(outputs[builder.MIRROR_TXT_PATH], outputs[builder.MIRROR_PY_PATH])
|
||||||
|
parsed_receipt = json.loads(outputs[builder.RECEIPT_PATH])
|
||||||
|
self.assertEqual(parsed_receipt, receipt)
|
||||||
|
self.assertEqual(parsed_receipt["parity_status"], "PASS")
|
||||||
|
self.assertFalse(parsed_receipt["authoring_rewritten"])
|
||||||
|
|
||||||
|
def test_forbidden_runtime_constructs_are_rejected(self) -> None:
|
||||||
|
cases = {
|
||||||
|
"subprocess": "import subprocess",
|
||||||
|
"dynamic-import": "import importlib",
|
||||||
|
"exec": "exec('x = 1')",
|
||||||
|
"eval": "eval('1 + 1')",
|
||||||
|
"compile": "compile('1', '<x>', 'eval')",
|
||||||
|
"external-project-import": "import project_runtime",
|
||||||
|
"external-python-source": "SOURCE = 'runtime/other.py'",
|
||||||
|
"external-url": "REMOTE = 'https://example.invalid/api'",
|
||||||
|
"placeholder-comment": "# TODO implement body",
|
||||||
|
"pass-node": "def unfinished():\n pass",
|
||||||
|
"ellipsis-placeholder": "UNFINISHED = ...",
|
||||||
|
"plaintext-secret": "TOKEN = 'Bearer abcdefghijklmnopqrstuvwxyz'",
|
||||||
|
}
|
||||||
|
for label, addition in cases.items():
|
||||||
|
with self.subTest(label=label):
|
||||||
|
with self.assertRaises(builder.ProjectionError):
|
||||||
|
builder.validate_inline_code(valid_inline_code() + "\n" + addition)
|
||||||
|
|
||||||
|
typed_ellipsis = valid_inline_code() + "\nPAIR: tuple[str, ...] = ('ok',)"
|
||||||
|
self.assertEqual(builder.validate_inline_code(typed_ellipsis)["ast_status"], "PASS")
|
||||||
|
|
||||||
|
def test_run_code_parameter_drift_is_rejected(self) -> None:
|
||||||
|
raw = authoring_yaml().replace(b"timeout: 300", b"timeout: 301")
|
||||||
|
document = builder.parse_authoring_bytes(raw)
|
||||||
|
with self.assertRaises(builder.ProjectionError) as raised:
|
||||||
|
builder.extract_run_code_task(document)
|
||||||
|
self.assertEqual(raised.exception.code, "RUN_CODE_PARAMETER_MISMATCH")
|
||||||
|
|
||||||
|
def test_code_scalar_must_strip_trailing_newline(self) -> None:
|
||||||
|
raw = authoring_yaml().replace(b" code: |-", b" code: |")
|
||||||
|
document = builder.parse_authoring_bytes(raw)
|
||||||
|
with self.assertRaises(builder.ProjectionError) as raised:
|
||||||
|
builder.extract_run_code_task(document)
|
||||||
|
self.assertEqual(raised.exception.code, "INLINE_CODE_TRAILING_NEWLINE_FORBIDDEN")
|
||||||
|
|
||||||
|
def test_agent_identity_and_exact_parameter_set_are_closed(self) -> None:
|
||||||
|
wrong_agent = authoring_yaml().replace(
|
||||||
|
b"name: Stage_2_S2_00_v1",
|
||||||
|
b"name: Stage_2_S2_00_drift",
|
||||||
|
)
|
||||||
|
with self.assertRaises(builder.ProjectionError) as raised:
|
||||||
|
builder.extract_run_code_task(builder.parse_authoring_bytes(wrong_agent))
|
||||||
|
self.assertEqual(raised.exception.code, "AGENT_IDENTITY_MISMATCH")
|
||||||
|
|
||||||
|
extra_parameter = authoring_yaml().replace(
|
||||||
|
b" code: |-",
|
||||||
|
b" unapproved: true\n code: |-",
|
||||||
|
)
|
||||||
|
with self.assertRaises(builder.ProjectionError) as raised:
|
||||||
|
builder.extract_run_code_task(builder.parse_authoring_bytes(extra_parameter))
|
||||||
|
self.assertEqual(raised.exception.code, "RUN_CODE_PARAMETER_SET_MISMATCH")
|
||||||
|
|
||||||
|
def test_only_direct_localdocs_post_is_allowed(self) -> None:
|
||||||
|
direct = (
|
||||||
|
valid_inline_code()
|
||||||
|
+ "\nclass DirectClient:\n"
|
||||||
|
+ " def send(self):\n"
|
||||||
|
+ " return self.client.post(LOCALDOCS_URL)"
|
||||||
|
)
|
||||||
|
self.assertEqual(builder.validate_inline_code(direct)["ast_status"], "PASS")
|
||||||
|
|
||||||
|
cases = {
|
||||||
|
"method-alias": "post = httpx.post\npost(LOCALDOCS_URL)",
|
||||||
|
"dynamic-endpoint": (
|
||||||
|
"class DynamicClient:\n"
|
||||||
|
" def send(self, target):\n"
|
||||||
|
" return self.client.post(target)"
|
||||||
|
),
|
||||||
|
"dynamic-getattr": "getattr(httpx, 'post')(LOCALDOCS_URL)",
|
||||||
|
"network-stdlib": "import urllib.request",
|
||||||
|
}
|
||||||
|
for label, addition in cases.items():
|
||||||
|
with self.subTest(label=label):
|
||||||
|
with self.assertRaises(builder.ProjectionError):
|
||||||
|
builder.validate_inline_code(valid_inline_code() + "\n" + addition)
|
||||||
|
|
||||||
|
def test_missing_authoring_is_a_controlled_condition(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
missing = Path(directory) / "Stage_2_S2_00_v.1.yml"
|
||||||
|
with self.assertRaises(builder.AuthoringMissingError) as raised:
|
||||||
|
builder.load_authoring(missing)
|
||||||
|
self.assertEqual(raised.exception.code, "AUTHORING_YAML_MISSING")
|
||||||
|
|
||||||
|
def test_build_and_check_modes_preserve_authoring_and_close_parity(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
main = Path(directory) / "2. Stage_2"
|
||||||
|
root = main / "Default_Agent" / "Stage_2_Clean"
|
||||||
|
authoring = main / "Stage_2_S2_00_v.1.yml"
|
||||||
|
projection = root / "agent_scripts" / "Stage_2_S2_00.yml"
|
||||||
|
mirror_py = root / "runtime" / "s2_00_ingress.py"
|
||||||
|
mirror_txt = root / "runtime" / "s2_00_ingress.txt"
|
||||||
|
receipt = root / "manifest" / "s2_00_inline_code_receipt.json"
|
||||||
|
root.mkdir(parents=True)
|
||||||
|
original = authoring_yaml()
|
||||||
|
authoring.write_bytes(original)
|
||||||
|
with (
|
||||||
|
mock.patch.object(builder, "MAIN_WORKING_DIRECTORY", main),
|
||||||
|
mock.patch.object(builder, "DEPLOYMENT_ROOT", root),
|
||||||
|
mock.patch.object(builder, "AUTHORING_PATH", authoring),
|
||||||
|
mock.patch.object(builder, "PROJECTION_PATH", projection),
|
||||||
|
mock.patch.object(builder, "MIRROR_PY_PATH", mirror_py),
|
||||||
|
mock.patch.object(builder, "MIRROR_TXT_PATH", mirror_txt),
|
||||||
|
mock.patch.object(builder, "RECEIPT_PATH", receipt),
|
||||||
|
):
|
||||||
|
status, payload = builder.run(check=False)
|
||||||
|
self.assertEqual(status, 0)
|
||||||
|
self.assertEqual(payload["status"], "BUILT_AND_VERIFIED")
|
||||||
|
self.assertEqual(authoring.read_bytes(), original)
|
||||||
|
self.assertEqual(projection.read_bytes(), original)
|
||||||
|
self.assertEqual(mirror_py.read_bytes(), valid_inline_code().encode("utf-8"))
|
||||||
|
self.assertEqual(mirror_py.read_bytes(), mirror_txt.read_bytes())
|
||||||
|
receipt_before = receipt.read_bytes()
|
||||||
|
status, payload = builder.run(check=True)
|
||||||
|
self.assertEqual(status, 0)
|
||||||
|
self.assertEqual(payload["status"], "PARITY_PASS")
|
||||||
|
self.assertEqual(receipt.read_bytes(), receipt_before)
|
||||||
|
|
||||||
|
|
||||||
|
class DeployedArtifactParityTests(unittest.TestCase):
|
||||||
|
def test_builder_and_test_sources_have_byte_identical_txt_mirrors(self) -> None:
|
||||||
|
pairs = [
|
||||||
|
(
|
||||||
|
ROOT / "offline_build" / "build_s2_00_inline_projection.py",
|
||||||
|
ROOT / "offline_build" / "build_s2_00_inline_projection.txt",
|
||||||
|
),
|
||||||
|
(Path(__file__), Path(__file__).with_suffix(".txt")),
|
||||||
|
]
|
||||||
|
for source, mirror in pairs:
|
||||||
|
with self.subTest(source=source.name):
|
||||||
|
self.assertTrue(mirror.is_file(), mirror)
|
||||||
|
self.assertEqual(source.read_bytes(), mirror.read_bytes())
|
||||||
|
|
||||||
|
def test_real_authoring_projection_and_mirrors_are_in_parity(self) -> None:
|
||||||
|
if not builder.AUTHORING_PATH.is_file():
|
||||||
|
self.skipTest(
|
||||||
|
"CONTROLLED_MISSING_AUTHORING_YAML: main agent has not created Stage_2_S2_00_v.1.yml"
|
||||||
|
)
|
||||||
|
raw, document = builder.load_authoring()
|
||||||
|
outputs, _receipt = builder.expected_outputs(raw, document)
|
||||||
|
mismatches = builder.compare_outputs(outputs)
|
||||||
|
self.assertEqual(mismatches, [], json.dumps(mismatches, ensure_ascii=False, indent=2))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
+281
@@ -0,0 +1,281 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
sys.path.insert(0, str(ROOT))
|
||||||
|
from offline_build import build_s2_00_inline_projection as builder # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
|
def valid_inline_code() -> str:
|
||||||
|
return "\n".join(
|
||||||
|
[
|
||||||
|
"#!/usr/bin/env python3",
|
||||||
|
"from __future__ import annotations",
|
||||||
|
"import json",
|
||||||
|
"import httpx",
|
||||||
|
"LOCALDOCS_URL = 'http://mcp-localdocs:8012/mcp'",
|
||||||
|
"USER_HASH = '{{__user_hash__}}'",
|
||||||
|
"WORKSPACE_HASH = '{{__workspace_hash__}}'",
|
||||||
|
"READ_TOOL = 'read_binary_doc'",
|
||||||
|
"WRITE_TOOL = 'write_binary_file'",
|
||||||
|
"expected_stage2_release_sha256 = '0' * 64",
|
||||||
|
"def main():",
|
||||||
|
" return {'status': 'OK'}",
|
||||||
|
"print(json.dumps(main(), ensure_ascii=False, sort_keys=True))",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def authoring_yaml(code: str | None = None) -> bytes:
|
||||||
|
source = valid_inline_code() if code is None else code
|
||||||
|
indented = "\n".join(f" {line}" for line in source.split("\n"))
|
||||||
|
return (
|
||||||
|
"Agent:\n"
|
||||||
|
" name: Stage_2_S2_00_v1\n"
|
||||||
|
" version: '1.1.0'\n"
|
||||||
|
" Stages:\n"
|
||||||
|
" - name: S2_00\n"
|
||||||
|
" tools:\n"
|
||||||
|
" mcpServers:\n"
|
||||||
|
" localdocs:\n"
|
||||||
|
" type: streamable-http\n"
|
||||||
|
" url: http://mcp-localdocs:8012/mcp\n"
|
||||||
|
" code-executor:\n"
|
||||||
|
" type: streamable-http\n"
|
||||||
|
" url: https://code-executor.mcp.eroomai.com/mcp\n"
|
||||||
|
" tasks:\n"
|
||||||
|
" - task_name: Task_S2_00_deterministic_ingress\n"
|
||||||
|
" mcp: code-executor\n"
|
||||||
|
" tool_name: run_code\n"
|
||||||
|
" parameters:\n"
|
||||||
|
" language: python\n"
|
||||||
|
" requirements: 'httpx==0.28.1'\n"
|
||||||
|
" network: agent-network\n"
|
||||||
|
" timeout: 300\n"
|
||||||
|
" code: |-\n"
|
||||||
|
f"{indented}\n"
|
||||||
|
" task_procedure:\n"
|
||||||
|
" IN:\n"
|
||||||
|
" nexts: [Task_S2_00_deterministic_ingress]\n"
|
||||||
|
" wait_until: []\n"
|
||||||
|
" Task_S2_00_deterministic_ingress:\n"
|
||||||
|
" nexts: [OUT]\n"
|
||||||
|
" wait_until: [IN]\n"
|
||||||
|
" OUT:\n"
|
||||||
|
" nexts: []\n"
|
||||||
|
" wait_until: [Task_S2_00_deterministic_ingress]\n"
|
||||||
|
" prevs: []\n"
|
||||||
|
" nexts: []\n"
|
||||||
|
).encode("utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
class InlineCodeProjectionUnitTests(unittest.TestCase):
|
||||||
|
def test_unique_key_loader_rejects_duplicate_mapping_key(self) -> None:
|
||||||
|
with self.assertRaises(builder.ProjectionError) as raised:
|
||||||
|
builder.parse_authoring_bytes(b"Agent:\n name: one\n name: two\n")
|
||||||
|
self.assertEqual(raised.exception.code, "YAML_DUPLICATE_KEY")
|
||||||
|
|
||||||
|
def test_authoring_plaintext_secret_outside_code_is_rejected(self) -> None:
|
||||||
|
raw = authoring_yaml().replace(
|
||||||
|
b" version: '1.1.0'",
|
||||||
|
b" version: '1.1.0'\n leaked_token: 'Bearer abcdefghijklmnopqrstuvwxyz'",
|
||||||
|
)
|
||||||
|
with self.assertRaises(builder.ProjectionError) as raised:
|
||||||
|
builder.parse_authoring_bytes(raw)
|
||||||
|
self.assertEqual(raised.exception.code, "AUTHORING_PLAINTEXT_SECRET")
|
||||||
|
|
||||||
|
def test_parser_returned_code_bytes_are_not_transformed(self) -> None:
|
||||||
|
expected_code = valid_inline_code()
|
||||||
|
raw = authoring_yaml(expected_code)
|
||||||
|
document = builder.parse_authoring_bytes(raw)
|
||||||
|
_stage, task = builder.extract_run_code_task(document)
|
||||||
|
observed = task["parameters"]["code"]
|
||||||
|
self.assertEqual(observed, expected_code)
|
||||||
|
self.assertEqual(observed.encode("utf-8"), expected_code.encode("utf-8"))
|
||||||
|
self.assertFalse(observed.endswith("\n"))
|
||||||
|
|
||||||
|
def test_valid_code_compiles_and_has_closed_imports(self) -> None:
|
||||||
|
report = builder.validate_inline_code(valid_inline_code())
|
||||||
|
self.assertEqual(report["compile_status"], "PASS")
|
||||||
|
self.assertEqual(report["ast_status"], "PASS")
|
||||||
|
self.assertEqual(report["imports"], ["__future__", "httpx", "json"])
|
||||||
|
self.assertEqual(report["code_sha256"], builder.sha256_bytes(valid_inline_code().encode("utf-8")))
|
||||||
|
|
||||||
|
def test_schema_identifier_url_is_allowed_but_network_endpoint_is_localdocs_only(self) -> None:
|
||||||
|
schema_identifier = (
|
||||||
|
valid_inline_code()
|
||||||
|
+ "\nSCHEMA_ID = 'https://schemas.liti-agent.local/stage2/s2_00/context.schema.v1.json'"
|
||||||
|
)
|
||||||
|
self.assertEqual(builder.validate_inline_code(schema_identifier)["external_url_count"], 0)
|
||||||
|
with self.assertRaises(builder.ProjectionError) as raised:
|
||||||
|
builder.validate_inline_code(
|
||||||
|
schema_identifier + "\nhttpx.post('https://schemas.liti-agent.local/not-an-endpoint')"
|
||||||
|
)
|
||||||
|
self.assertEqual(raised.exception.code, "INLINE_CODE_NETWORK_ENDPOINT_FORBIDDEN")
|
||||||
|
|
||||||
|
def test_expected_outputs_are_exact_source_and_code_bytes(self) -> None:
|
||||||
|
raw = authoring_yaml()
|
||||||
|
document = builder.parse_authoring_bytes(raw)
|
||||||
|
outputs, receipt = builder.expected_outputs(raw, document)
|
||||||
|
self.assertEqual(outputs[builder.PROJECTION_PATH], raw)
|
||||||
|
self.assertEqual(outputs[builder.MIRROR_PY_PATH], valid_inline_code().encode("utf-8"))
|
||||||
|
self.assertEqual(outputs[builder.MIRROR_TXT_PATH], outputs[builder.MIRROR_PY_PATH])
|
||||||
|
parsed_receipt = json.loads(outputs[builder.RECEIPT_PATH])
|
||||||
|
self.assertEqual(parsed_receipt, receipt)
|
||||||
|
self.assertEqual(parsed_receipt["parity_status"], "PASS")
|
||||||
|
self.assertFalse(parsed_receipt["authoring_rewritten"])
|
||||||
|
|
||||||
|
def test_forbidden_runtime_constructs_are_rejected(self) -> None:
|
||||||
|
cases = {
|
||||||
|
"subprocess": "import subprocess",
|
||||||
|
"dynamic-import": "import importlib",
|
||||||
|
"exec": "exec('x = 1')",
|
||||||
|
"eval": "eval('1 + 1')",
|
||||||
|
"compile": "compile('1', '<x>', 'eval')",
|
||||||
|
"external-project-import": "import project_runtime",
|
||||||
|
"external-python-source": "SOURCE = 'runtime/other.py'",
|
||||||
|
"external-url": "REMOTE = 'https://example.invalid/api'",
|
||||||
|
"placeholder-comment": "# TODO implement body",
|
||||||
|
"pass-node": "def unfinished():\n pass",
|
||||||
|
"ellipsis-placeholder": "UNFINISHED = ...",
|
||||||
|
"plaintext-secret": "TOKEN = 'Bearer abcdefghijklmnopqrstuvwxyz'",
|
||||||
|
}
|
||||||
|
for label, addition in cases.items():
|
||||||
|
with self.subTest(label=label):
|
||||||
|
with self.assertRaises(builder.ProjectionError):
|
||||||
|
builder.validate_inline_code(valid_inline_code() + "\n" + addition)
|
||||||
|
|
||||||
|
typed_ellipsis = valid_inline_code() + "\nPAIR: tuple[str, ...] = ('ok',)"
|
||||||
|
self.assertEqual(builder.validate_inline_code(typed_ellipsis)["ast_status"], "PASS")
|
||||||
|
|
||||||
|
def test_run_code_parameter_drift_is_rejected(self) -> None:
|
||||||
|
raw = authoring_yaml().replace(b"timeout: 300", b"timeout: 301")
|
||||||
|
document = builder.parse_authoring_bytes(raw)
|
||||||
|
with self.assertRaises(builder.ProjectionError) as raised:
|
||||||
|
builder.extract_run_code_task(document)
|
||||||
|
self.assertEqual(raised.exception.code, "RUN_CODE_PARAMETER_MISMATCH")
|
||||||
|
|
||||||
|
def test_code_scalar_must_strip_trailing_newline(self) -> None:
|
||||||
|
raw = authoring_yaml().replace(b" code: |-", b" code: |")
|
||||||
|
document = builder.parse_authoring_bytes(raw)
|
||||||
|
with self.assertRaises(builder.ProjectionError) as raised:
|
||||||
|
builder.extract_run_code_task(document)
|
||||||
|
self.assertEqual(raised.exception.code, "INLINE_CODE_TRAILING_NEWLINE_FORBIDDEN")
|
||||||
|
|
||||||
|
def test_agent_identity_and_exact_parameter_set_are_closed(self) -> None:
|
||||||
|
wrong_agent = authoring_yaml().replace(
|
||||||
|
b"name: Stage_2_S2_00_v1",
|
||||||
|
b"name: Stage_2_S2_00_drift",
|
||||||
|
)
|
||||||
|
with self.assertRaises(builder.ProjectionError) as raised:
|
||||||
|
builder.extract_run_code_task(builder.parse_authoring_bytes(wrong_agent))
|
||||||
|
self.assertEqual(raised.exception.code, "AGENT_IDENTITY_MISMATCH")
|
||||||
|
|
||||||
|
extra_parameter = authoring_yaml().replace(
|
||||||
|
b" code: |-",
|
||||||
|
b" unapproved: true\n code: |-",
|
||||||
|
)
|
||||||
|
with self.assertRaises(builder.ProjectionError) as raised:
|
||||||
|
builder.extract_run_code_task(builder.parse_authoring_bytes(extra_parameter))
|
||||||
|
self.assertEqual(raised.exception.code, "RUN_CODE_PARAMETER_SET_MISMATCH")
|
||||||
|
|
||||||
|
def test_only_direct_localdocs_post_is_allowed(self) -> None:
|
||||||
|
direct = (
|
||||||
|
valid_inline_code()
|
||||||
|
+ "\nclass DirectClient:\n"
|
||||||
|
+ " def send(self):\n"
|
||||||
|
+ " return self.client.post(LOCALDOCS_URL)"
|
||||||
|
)
|
||||||
|
self.assertEqual(builder.validate_inline_code(direct)["ast_status"], "PASS")
|
||||||
|
|
||||||
|
cases = {
|
||||||
|
"method-alias": "post = httpx.post\npost(LOCALDOCS_URL)",
|
||||||
|
"dynamic-endpoint": (
|
||||||
|
"class DynamicClient:\n"
|
||||||
|
" def send(self, target):\n"
|
||||||
|
" return self.client.post(target)"
|
||||||
|
),
|
||||||
|
"dynamic-getattr": "getattr(httpx, 'post')(LOCALDOCS_URL)",
|
||||||
|
"network-stdlib": "import urllib.request",
|
||||||
|
}
|
||||||
|
for label, addition in cases.items():
|
||||||
|
with self.subTest(label=label):
|
||||||
|
with self.assertRaises(builder.ProjectionError):
|
||||||
|
builder.validate_inline_code(valid_inline_code() + "\n" + addition)
|
||||||
|
|
||||||
|
def test_missing_authoring_is_a_controlled_condition(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
missing = Path(directory) / "Stage_2_S2_00_v.1.yml"
|
||||||
|
with self.assertRaises(builder.AuthoringMissingError) as raised:
|
||||||
|
builder.load_authoring(missing)
|
||||||
|
self.assertEqual(raised.exception.code, "AUTHORING_YAML_MISSING")
|
||||||
|
|
||||||
|
def test_build_and_check_modes_preserve_authoring_and_close_parity(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
main = Path(directory) / "2. Stage_2"
|
||||||
|
root = main / "Default_Agent" / "Stage_2_Clean"
|
||||||
|
authoring = main / "Stage_2_S2_00_v.1.yml"
|
||||||
|
projection = root / "agent_scripts" / "Stage_2_S2_00.yml"
|
||||||
|
mirror_py = root / "runtime" / "s2_00_ingress.py"
|
||||||
|
mirror_txt = root / "runtime" / "s2_00_ingress.txt"
|
||||||
|
receipt = root / "manifest" / "s2_00_inline_code_receipt.json"
|
||||||
|
root.mkdir(parents=True)
|
||||||
|
original = authoring_yaml()
|
||||||
|
authoring.write_bytes(original)
|
||||||
|
with (
|
||||||
|
mock.patch.object(builder, "MAIN_WORKING_DIRECTORY", main),
|
||||||
|
mock.patch.object(builder, "DEPLOYMENT_ROOT", root),
|
||||||
|
mock.patch.object(builder, "AUTHORING_PATH", authoring),
|
||||||
|
mock.patch.object(builder, "PROJECTION_PATH", projection),
|
||||||
|
mock.patch.object(builder, "MIRROR_PY_PATH", mirror_py),
|
||||||
|
mock.patch.object(builder, "MIRROR_TXT_PATH", mirror_txt),
|
||||||
|
mock.patch.object(builder, "RECEIPT_PATH", receipt),
|
||||||
|
):
|
||||||
|
status, payload = builder.run(check=False)
|
||||||
|
self.assertEqual(status, 0)
|
||||||
|
self.assertEqual(payload["status"], "BUILT_AND_VERIFIED")
|
||||||
|
self.assertEqual(authoring.read_bytes(), original)
|
||||||
|
self.assertEqual(projection.read_bytes(), original)
|
||||||
|
self.assertEqual(mirror_py.read_bytes(), valid_inline_code().encode("utf-8"))
|
||||||
|
self.assertEqual(mirror_py.read_bytes(), mirror_txt.read_bytes())
|
||||||
|
receipt_before = receipt.read_bytes()
|
||||||
|
status, payload = builder.run(check=True)
|
||||||
|
self.assertEqual(status, 0)
|
||||||
|
self.assertEqual(payload["status"], "PARITY_PASS")
|
||||||
|
self.assertEqual(receipt.read_bytes(), receipt_before)
|
||||||
|
|
||||||
|
|
||||||
|
class DeployedArtifactParityTests(unittest.TestCase):
|
||||||
|
def test_builder_and_test_sources_have_byte_identical_txt_mirrors(self) -> None:
|
||||||
|
pairs = [
|
||||||
|
(
|
||||||
|
ROOT / "offline_build" / "build_s2_00_inline_projection.py",
|
||||||
|
ROOT / "offline_build" / "build_s2_00_inline_projection.txt",
|
||||||
|
),
|
||||||
|
(Path(__file__), Path(__file__).with_suffix(".txt")),
|
||||||
|
]
|
||||||
|
for source, mirror in pairs:
|
||||||
|
with self.subTest(source=source.name):
|
||||||
|
self.assertTrue(mirror.is_file(), mirror)
|
||||||
|
self.assertEqual(source.read_bytes(), mirror.read_bytes())
|
||||||
|
|
||||||
|
def test_real_authoring_projection_and_mirrors_are_in_parity(self) -> None:
|
||||||
|
if not builder.AUTHORING_PATH.is_file():
|
||||||
|
self.skipTest(
|
||||||
|
"CONTROLLED_MISSING_AUTHORING_YAML: main agent has not created Stage_2_S2_00_v.1.yml"
|
||||||
|
)
|
||||||
|
raw, document = builder.load_authoring()
|
||||||
|
outputs, _receipt = builder.expected_outputs(raw, document)
|
||||||
|
mismatches = builder.compare_outputs(outputs)
|
||||||
|
self.assertEqual(mismatches, [], json.dumps(mismatches, ensure_ascii=False, indent=2))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
+236
-4
@@ -1,10 +1,14 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import base64
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
|
||||||
ROOT = Path(__file__).resolve().parents[2]
|
ROOT = Path(__file__).resolve().parents[2]
|
||||||
@@ -52,14 +56,242 @@ class ResolverAndSourceLockTests(unittest.TestCase):
|
|||||||
def test_default_allowlist_is_exact_and_override_cannot_invent_kind(self) -> None:
|
def test_default_allowlist_is_exact_and_override_cannot_invent_kind(self) -> None:
|
||||||
with tempfile.TemporaryDirectory() as directory:
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
rows = s2.resolve_stage1_sources(directory)
|
rows = s2.resolve_stage1_sources(directory)
|
||||||
self.assertEqual(len(rows), 16)
|
expected = [
|
||||||
observed = {row["observed_path"] for row in rows}
|
("evidence_indexed", "evidence_indexed.json", "evidence_scope"),
|
||||||
self.assertIn("routing/domain_activation_manifest.json", observed)
|
("evidence_event_candidates", "evidence_event_candidates.json", "event_scope"),
|
||||||
self.assertNotIn("domain_activation_manifest.json", observed)
|
("client_goal", "client_goal.json", "optimization_context"),
|
||||||
|
("domain_screening", "routing/domain_screening.json", "routing_profile_backbone"),
|
||||||
|
("domain_activation_manifest", "routing/domain_activation_manifest.json", "routing_profile_backbone"),
|
||||||
|
("b1_evidence_indexed_gate", "quality_gates/B1_evidence_indexed_gate.json", "integrity_corroborator"),
|
||||||
|
("b2_event_candidates_gate", "quality_gates/B2_event_candidates_gate.json", "integrity_corroborator"),
|
||||||
|
("stage1_part1_soft_gate_handoff", "quality_gates/stage1_part1_soft_gate_handoff.json", "integrity_corroborator"),
|
||||||
|
("bo", "BO.json", "identity_backbone"),
|
||||||
|
("signal_manifest", "signals/signal_manifest.json", "routing_profile_backbone"),
|
||||||
|
("stage1_part2_review_handoff", "quality_gates/stage1_part2_review_handoff.json", "integrity_corroborator"),
|
||||||
|
("legal_effect_structures", "legal_effect_structures.json", "routing_profile_backbone"),
|
||||||
|
("stage1_part3_review_handoff", "quality_gates/stage1_part3_review_handoff.json", "integrity_corroborator"),
|
||||||
|
("fact_ledger_base", "Fact_Ledger_base.json", "identity_backbone"),
|
||||||
|
("fact_ledger_writer_report", "stage1_tmp/fact_ledger/fact_ledger_writer_report.json", "integrity_corroborator"),
|
||||||
|
("stage1_part4_review_handoff", "quality_gates/stage1_part4_review_handoff.json", "integrity_corroborator"),
|
||||||
|
]
|
||||||
|
observed = [
|
||||||
|
(row["logical_input_id"], row["observed_path"], row["criticality"])
|
||||||
|
for row in rows
|
||||||
|
]
|
||||||
|
self.assertEqual(observed, expected)
|
||||||
with self.assertRaises(s2.IngressError) as invented:
|
with self.assertRaises(s2.IngressError) as invented:
|
||||||
s2.resolve_stage1_sources(directory, {"path_overrides": {"invented": "x.json"}})
|
s2.resolve_stage1_sources(directory, {"path_overrides": {"invented": "x.json"}})
|
||||||
self.assertEqual(invented.exception.code, "UNAPPROVED_LOGICAL_KIND")
|
self.assertEqual(invented.exception.code, "UNAPPROVED_LOGICAL_KIND")
|
||||||
|
|
||||||
|
def test_mcp_terminal_parser_requires_exact_jsonrpc_id_and_strict_json(self) -> None:
|
||||||
|
direct = s2._inline_parse_mcp_payload(
|
||||||
|
b'{"jsonrpc":"2.0","id":10,"result":{}}',
|
||||||
|
10,
|
||||||
|
)
|
||||||
|
self.assertEqual(direct["id"], 10)
|
||||||
|
sse = s2._inline_parse_mcp_payload(
|
||||||
|
b'event: message\ndata: {"jsonrpc":"2.0","id":11,"result":{}}\n\n',
|
||||||
|
11,
|
||||||
|
)
|
||||||
|
self.assertEqual(sse["id"], 11)
|
||||||
|
for raw, code in (
|
||||||
|
(b'{"jsonrpc":"2.0","id":12,"result":{}}', "MCP_RESPONSE_ID_MISMATCH"),
|
||||||
|
(b'{"jsonrpc":"2.0","id":10,"id":10,"result":{}}', "MCP_SSE_SHAPE"),
|
||||||
|
(b'{"jsonrpc":"2.0","id":10,"result":{}} {}', "MCP_SSE_SHAPE"),
|
||||||
|
(b'{"jsonrpc":"2.0","id":10,"error":{"code":-1}}', "MCP_JSONRPC_ERROR"),
|
||||||
|
):
|
||||||
|
with self.subTest(raw=raw):
|
||||||
|
with self.assertRaises(s2.IngressError) as raised:
|
||||||
|
s2._inline_parse_mcp_payload(raw, 10)
|
||||||
|
self.assertEqual(raised.exception.code, code)
|
||||||
|
|
||||||
|
def test_binary_envelope_uses_strict_base64_and_declared_integrity(self) -> None:
|
||||||
|
payload = b"\x00raw-stage1-bytes\xff"
|
||||||
|
envelope = json.dumps(
|
||||||
|
{
|
||||||
|
"content_base64": base64.b64encode(payload).decode("ascii"),
|
||||||
|
"byte_length": len(payload),
|
||||||
|
"sha256": __import__("hashlib").sha256(payload).hexdigest(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.assertEqual(s2._inline_binary_envelope(envelope, "x.bin"), payload)
|
||||||
|
for malformed, code in (
|
||||||
|
('{"content_base64":"%%%%"}', "LOCALDOCS_BASE64_INVALID"),
|
||||||
|
('{"content_base64":"YQ==","byte_length":2}', "LOCALDOCS_BYTE_LENGTH_MISMATCH"),
|
||||||
|
('{"content_base64":"YQ==","sha256":"' + "0" * 64 + '"}', "LOCALDOCS_HASH_MISMATCH"),
|
||||||
|
):
|
||||||
|
with self.subTest(malformed=malformed):
|
||||||
|
with self.assertRaises(s2.IngressError) as raised:
|
||||||
|
s2._inline_binary_envelope(malformed, "x.bin")
|
||||||
|
self.assertEqual(raised.exception.code, code)
|
||||||
|
|
||||||
|
def test_tool_result_parser_enforces_one_text_block_and_is_error(self) -> None:
|
||||||
|
self.assertEqual(
|
||||||
|
s2._inline_tool_text(
|
||||||
|
{"content": [{"type": "text", "text": "payload"}]},
|
||||||
|
"read_binary_doc",
|
||||||
|
),
|
||||||
|
"payload",
|
||||||
|
)
|
||||||
|
for result, code in (
|
||||||
|
({"content": []}, "MCP_CONTENT_CARDINALITY"),
|
||||||
|
({"content": [{"type": "image", "data": "x"}]}, "MCP_CONTENT_SHAPE"),
|
||||||
|
({"isError": True, "content": [{"type": "text", "text": "not found"}]}, "LOCALDOCS_NOT_FOUND"),
|
||||||
|
):
|
||||||
|
with self.subTest(result=result):
|
||||||
|
with self.assertRaises(s2.IngressError) as raised:
|
||||||
|
s2._inline_tool_text(result, "read_binary_doc")
|
||||||
|
self.assertEqual(raised.exception.code, code)
|
||||||
|
|
||||||
|
def test_mcp_initialize_locks_protocol_and_session_identity(self) -> None:
|
||||||
|
class Response:
|
||||||
|
def __init__(self, payload: object, session_id: str | None) -> None:
|
||||||
|
self.content = json.dumps(payload).encode("utf-8")
|
||||||
|
self.headers = {} if session_id is None else {"mcp-session-id": session_id}
|
||||||
|
|
||||||
|
def raise_for_status(self) -> None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
class Client:
|
||||||
|
def __init__(self, responses: list[Response]) -> None:
|
||||||
|
self.responses = list(responses)
|
||||||
|
|
||||||
|
def post(self, *_args: object, **_kwargs: object) -> Response:
|
||||||
|
return self.responses.pop(0)
|
||||||
|
|
||||||
|
def close(self) -> None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
initialize = {
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 1,
|
||||||
|
"result": {"protocolVersion": s2.MCP_PROTOCOL_VERSION},
|
||||||
|
}
|
||||||
|
notification = {"jsonrpc": "2.0", "result": {}}
|
||||||
|
client = Client([Response(initialize, "session-a"), Response(notification, "session-a")])
|
||||||
|
localdocs = s2._InlineLocaldocs("a" * 64, "b" * 64, client=client)
|
||||||
|
localdocs.initialize()
|
||||||
|
self.assertEqual(localdocs.headers["mcp-session-id"], "session-a")
|
||||||
|
|
||||||
|
changed = Client(
|
||||||
|
[
|
||||||
|
Response(initialize, "session-a"),
|
||||||
|
Response(notification, "session-a"),
|
||||||
|
Response({"jsonrpc": "2.0", "id": 10, "result": {}}, "session-b"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
localdocs = s2._InlineLocaldocs("a" * 64, "b" * 64, client=changed)
|
||||||
|
localdocs.initialize()
|
||||||
|
with self.assertRaises(s2.IngressError) as raised:
|
||||||
|
localdocs.call("read_binary_doc", {"doc_name": "x"})
|
||||||
|
self.assertEqual(raised.exception.code, "MCP_SESSION_ID_CHANGED")
|
||||||
|
|
||||||
|
bad_protocol = dict(initialize)
|
||||||
|
bad_protocol["result"] = {"protocolVersion": "1900-01-01"}
|
||||||
|
localdocs = s2._InlineLocaldocs(
|
||||||
|
"a" * 64,
|
||||||
|
"b" * 64,
|
||||||
|
client=Client([Response(bad_protocol, "session-a")]),
|
||||||
|
)
|
||||||
|
with self.assertRaises(s2.IngressError) as raised:
|
||||||
|
localdocs.initialize()
|
||||||
|
self.assertEqual(raised.exception.code, "MCP_PROTOCOL_VERSION_MISMATCH")
|
||||||
|
|
||||||
|
def test_inline_hydration_rejects_second_pass_mutation_and_initial_budget(self) -> None:
|
||||||
|
request = {
|
||||||
|
"schema_version": "stage2_s2_00_execution_request.v1",
|
||||||
|
"workflow_id": "S2_00",
|
||||||
|
"request_id": "trace-1",
|
||||||
|
"attempt_id": "attempt-1",
|
||||||
|
"stage1_run_root_ref": "stage1_runs/current",
|
||||||
|
"stage1_deployment_root_ref": "Default_Agent",
|
||||||
|
}
|
||||||
|
request_raw = s2.canonical_json_bytes(request)
|
||||||
|
release_raw = s2.canonical_json_bytes(
|
||||||
|
{"limits": {"max_file_bytes": s2.MAX_FILE_BYTES, "max_run_bytes": s2.MAX_RUN_BYTES, "max_hydration_paths": 1024}}
|
||||||
|
)
|
||||||
|
|
||||||
|
class Reader:
|
||||||
|
def __init__(self, mutate_request: bool = False) -> None:
|
||||||
|
self.counts: dict[str, int] = {}
|
||||||
|
self.mutate_request = mutate_request
|
||||||
|
|
||||||
|
def read_binary(self, path: str) -> bytes:
|
||||||
|
self.counts[path] = self.counts.get(path, 0) + 1
|
||||||
|
if path == s2.INLINE_REQUEST_PATH:
|
||||||
|
if self.mutate_request and self.counts[path] > 1:
|
||||||
|
return request_raw.replace(b"trace-1", b"trace-2")
|
||||||
|
return request_raw
|
||||||
|
if path == s2.INLINE_STAGE2_RELEASE_PATH:
|
||||||
|
return release_raw
|
||||||
|
raise AssertionError(path)
|
||||||
|
|
||||||
|
destinations = [
|
||||||
|
(s2.INLINE_REQUEST_PATH, "stage1_run", "request.json"),
|
||||||
|
(s2.INLINE_STAGE2_RELEASE_PATH, "stage2_asset", "manifest/stage2_release.json"),
|
||||||
|
]
|
||||||
|
release_hash = hashlib.sha256(release_raw).hexdigest()
|
||||||
|
with tempfile.TemporaryDirectory() as directory, mock.patch.object(
|
||||||
|
s2,
|
||||||
|
"EXPECTED_STAGE2_RELEASE_SHA256",
|
||||||
|
release_hash,
|
||||||
|
), mock.patch.object(
|
||||||
|
s2,
|
||||||
|
"_inline_release_materialization_plan",
|
||||||
|
return_value=(destinations, {s2.INLINE_STAGE2_RELEASE_PATH: release_hash}),
|
||||||
|
):
|
||||||
|
with self.assertRaises(s2.IngressError) as raised:
|
||||||
|
s2._inline_hydrate(Reader(mutate_request=True), Path(directory))
|
||||||
|
self.assertEqual(raised.exception.code, "SOURCE_SNAPSHOT_CHANGED")
|
||||||
|
|
||||||
|
tiny_release_raw = s2.canonical_json_bytes(
|
||||||
|
{"limits": {"max_file_bytes": s2.MAX_FILE_BYTES, "max_run_bytes": 1, "max_hydration_paths": 1024}}
|
||||||
|
)
|
||||||
|
tiny_hash = hashlib.sha256(tiny_release_raw).hexdigest()
|
||||||
|
|
||||||
|
class TinyReader(Reader):
|
||||||
|
def read_binary(self, path: str) -> bytes:
|
||||||
|
if path == s2.INLINE_STAGE2_RELEASE_PATH:
|
||||||
|
return tiny_release_raw
|
||||||
|
return super().read_binary(path)
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory() as directory, mock.patch.object(
|
||||||
|
s2,
|
||||||
|
"EXPECTED_STAGE2_RELEASE_SHA256",
|
||||||
|
tiny_hash,
|
||||||
|
):
|
||||||
|
with self.assertRaises(s2.IngressError) as raised:
|
||||||
|
s2._inline_hydrate(TinyReader(), Path(directory))
|
||||||
|
self.assertEqual(raised.exception.code, "AGGREGATE_RUN_SIZE_LIMIT")
|
||||||
|
|
||||||
|
def test_execution_request_is_closed_and_paths_are_workspace_relative(self) -> None:
|
||||||
|
valid = {
|
||||||
|
"schema_version": "stage2_s2_00_execution_request.v1",
|
||||||
|
"workflow_id": "S2_00",
|
||||||
|
"request_id": "trace-1",
|
||||||
|
"attempt_id": "attempt-1",
|
||||||
|
"stage1_run_root_ref": "stage1_runs/current",
|
||||||
|
"stage1_deployment_root_ref": "Default_Agent",
|
||||||
|
}
|
||||||
|
parsed = s2._inline_validate_request(json.dumps(valid).encode())
|
||||||
|
self.assertEqual(parsed["request_id"], "trace-1")
|
||||||
|
for mutation, code in (
|
||||||
|
({**valid, "unknown": True}, "RUN_REQUEST_CLOSED_SHAPE"),
|
||||||
|
({**valid, "stage1_run_root_ref": "../escape"}, "STAGE1_RUN_ROOT_REF_INVALID"),
|
||||||
|
({**valid, "stage1_deployment_root_ref": "/absolute"}, "STAGE1_DEPLOYMENT_ROOT_REF_INVALID"),
|
||||||
|
):
|
||||||
|
with self.subTest(mutation=mutation):
|
||||||
|
with self.assertRaises(s2.IngressError) as raised:
|
||||||
|
s2._inline_validate_request(json.dumps(mutation).encode())
|
||||||
|
self.assertEqual(raised.exception.code, code)
|
||||||
|
|
||||||
|
def test_algorithm_digest_and_run_id_do_not_depend_on_mirror_path(self) -> None:
|
||||||
|
self.assertEqual(len(s2.ALGORITHM_SEMANTIC_DIGEST), 64)
|
||||||
|
binding = "a" * 64
|
||||||
|
self.assertEqual(s2.canonical_run_id(binding), f"S2RUN-{binding}")
|
||||||
|
with self.assertRaises(s2.IngressError):
|
||||||
|
s2.canonical_run_id("not-a-digest")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
+236
-4
@@ -1,10 +1,14 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import base64
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
|
||||||
ROOT = Path(__file__).resolve().parents[2]
|
ROOT = Path(__file__).resolve().parents[2]
|
||||||
@@ -52,14 +56,242 @@ class ResolverAndSourceLockTests(unittest.TestCase):
|
|||||||
def test_default_allowlist_is_exact_and_override_cannot_invent_kind(self) -> None:
|
def test_default_allowlist_is_exact_and_override_cannot_invent_kind(self) -> None:
|
||||||
with tempfile.TemporaryDirectory() as directory:
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
rows = s2.resolve_stage1_sources(directory)
|
rows = s2.resolve_stage1_sources(directory)
|
||||||
self.assertEqual(len(rows), 16)
|
expected = [
|
||||||
observed = {row["observed_path"] for row in rows}
|
("evidence_indexed", "evidence_indexed.json", "evidence_scope"),
|
||||||
self.assertIn("routing/domain_activation_manifest.json", observed)
|
("evidence_event_candidates", "evidence_event_candidates.json", "event_scope"),
|
||||||
self.assertNotIn("domain_activation_manifest.json", observed)
|
("client_goal", "client_goal.json", "optimization_context"),
|
||||||
|
("domain_screening", "routing/domain_screening.json", "routing_profile_backbone"),
|
||||||
|
("domain_activation_manifest", "routing/domain_activation_manifest.json", "routing_profile_backbone"),
|
||||||
|
("b1_evidence_indexed_gate", "quality_gates/B1_evidence_indexed_gate.json", "integrity_corroborator"),
|
||||||
|
("b2_event_candidates_gate", "quality_gates/B2_event_candidates_gate.json", "integrity_corroborator"),
|
||||||
|
("stage1_part1_soft_gate_handoff", "quality_gates/stage1_part1_soft_gate_handoff.json", "integrity_corroborator"),
|
||||||
|
("bo", "BO.json", "identity_backbone"),
|
||||||
|
("signal_manifest", "signals/signal_manifest.json", "routing_profile_backbone"),
|
||||||
|
("stage1_part2_review_handoff", "quality_gates/stage1_part2_review_handoff.json", "integrity_corroborator"),
|
||||||
|
("legal_effect_structures", "legal_effect_structures.json", "routing_profile_backbone"),
|
||||||
|
("stage1_part3_review_handoff", "quality_gates/stage1_part3_review_handoff.json", "integrity_corroborator"),
|
||||||
|
("fact_ledger_base", "Fact_Ledger_base.json", "identity_backbone"),
|
||||||
|
("fact_ledger_writer_report", "stage1_tmp/fact_ledger/fact_ledger_writer_report.json", "integrity_corroborator"),
|
||||||
|
("stage1_part4_review_handoff", "quality_gates/stage1_part4_review_handoff.json", "integrity_corroborator"),
|
||||||
|
]
|
||||||
|
observed = [
|
||||||
|
(row["logical_input_id"], row["observed_path"], row["criticality"])
|
||||||
|
for row in rows
|
||||||
|
]
|
||||||
|
self.assertEqual(observed, expected)
|
||||||
with self.assertRaises(s2.IngressError) as invented:
|
with self.assertRaises(s2.IngressError) as invented:
|
||||||
s2.resolve_stage1_sources(directory, {"path_overrides": {"invented": "x.json"}})
|
s2.resolve_stage1_sources(directory, {"path_overrides": {"invented": "x.json"}})
|
||||||
self.assertEqual(invented.exception.code, "UNAPPROVED_LOGICAL_KIND")
|
self.assertEqual(invented.exception.code, "UNAPPROVED_LOGICAL_KIND")
|
||||||
|
|
||||||
|
def test_mcp_terminal_parser_requires_exact_jsonrpc_id_and_strict_json(self) -> None:
|
||||||
|
direct = s2._inline_parse_mcp_payload(
|
||||||
|
b'{"jsonrpc":"2.0","id":10,"result":{}}',
|
||||||
|
10,
|
||||||
|
)
|
||||||
|
self.assertEqual(direct["id"], 10)
|
||||||
|
sse = s2._inline_parse_mcp_payload(
|
||||||
|
b'event: message\ndata: {"jsonrpc":"2.0","id":11,"result":{}}\n\n',
|
||||||
|
11,
|
||||||
|
)
|
||||||
|
self.assertEqual(sse["id"], 11)
|
||||||
|
for raw, code in (
|
||||||
|
(b'{"jsonrpc":"2.0","id":12,"result":{}}', "MCP_RESPONSE_ID_MISMATCH"),
|
||||||
|
(b'{"jsonrpc":"2.0","id":10,"id":10,"result":{}}', "MCP_SSE_SHAPE"),
|
||||||
|
(b'{"jsonrpc":"2.0","id":10,"result":{}} {}', "MCP_SSE_SHAPE"),
|
||||||
|
(b'{"jsonrpc":"2.0","id":10,"error":{"code":-1}}', "MCP_JSONRPC_ERROR"),
|
||||||
|
):
|
||||||
|
with self.subTest(raw=raw):
|
||||||
|
with self.assertRaises(s2.IngressError) as raised:
|
||||||
|
s2._inline_parse_mcp_payload(raw, 10)
|
||||||
|
self.assertEqual(raised.exception.code, code)
|
||||||
|
|
||||||
|
def test_binary_envelope_uses_strict_base64_and_declared_integrity(self) -> None:
|
||||||
|
payload = b"\x00raw-stage1-bytes\xff"
|
||||||
|
envelope = json.dumps(
|
||||||
|
{
|
||||||
|
"content_base64": base64.b64encode(payload).decode("ascii"),
|
||||||
|
"byte_length": len(payload),
|
||||||
|
"sha256": __import__("hashlib").sha256(payload).hexdigest(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.assertEqual(s2._inline_binary_envelope(envelope, "x.bin"), payload)
|
||||||
|
for malformed, code in (
|
||||||
|
('{"content_base64":"%%%%"}', "LOCALDOCS_BASE64_INVALID"),
|
||||||
|
('{"content_base64":"YQ==","byte_length":2}', "LOCALDOCS_BYTE_LENGTH_MISMATCH"),
|
||||||
|
('{"content_base64":"YQ==","sha256":"' + "0" * 64 + '"}', "LOCALDOCS_HASH_MISMATCH"),
|
||||||
|
):
|
||||||
|
with self.subTest(malformed=malformed):
|
||||||
|
with self.assertRaises(s2.IngressError) as raised:
|
||||||
|
s2._inline_binary_envelope(malformed, "x.bin")
|
||||||
|
self.assertEqual(raised.exception.code, code)
|
||||||
|
|
||||||
|
def test_tool_result_parser_enforces_one_text_block_and_is_error(self) -> None:
|
||||||
|
self.assertEqual(
|
||||||
|
s2._inline_tool_text(
|
||||||
|
{"content": [{"type": "text", "text": "payload"}]},
|
||||||
|
"read_binary_doc",
|
||||||
|
),
|
||||||
|
"payload",
|
||||||
|
)
|
||||||
|
for result, code in (
|
||||||
|
({"content": []}, "MCP_CONTENT_CARDINALITY"),
|
||||||
|
({"content": [{"type": "image", "data": "x"}]}, "MCP_CONTENT_SHAPE"),
|
||||||
|
({"isError": True, "content": [{"type": "text", "text": "not found"}]}, "LOCALDOCS_NOT_FOUND"),
|
||||||
|
):
|
||||||
|
with self.subTest(result=result):
|
||||||
|
with self.assertRaises(s2.IngressError) as raised:
|
||||||
|
s2._inline_tool_text(result, "read_binary_doc")
|
||||||
|
self.assertEqual(raised.exception.code, code)
|
||||||
|
|
||||||
|
def test_mcp_initialize_locks_protocol_and_session_identity(self) -> None:
|
||||||
|
class Response:
|
||||||
|
def __init__(self, payload: object, session_id: str | None) -> None:
|
||||||
|
self.content = json.dumps(payload).encode("utf-8")
|
||||||
|
self.headers = {} if session_id is None else {"mcp-session-id": session_id}
|
||||||
|
|
||||||
|
def raise_for_status(self) -> None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
class Client:
|
||||||
|
def __init__(self, responses: list[Response]) -> None:
|
||||||
|
self.responses = list(responses)
|
||||||
|
|
||||||
|
def post(self, *_args: object, **_kwargs: object) -> Response:
|
||||||
|
return self.responses.pop(0)
|
||||||
|
|
||||||
|
def close(self) -> None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
initialize = {
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 1,
|
||||||
|
"result": {"protocolVersion": s2.MCP_PROTOCOL_VERSION},
|
||||||
|
}
|
||||||
|
notification = {"jsonrpc": "2.0", "result": {}}
|
||||||
|
client = Client([Response(initialize, "session-a"), Response(notification, "session-a")])
|
||||||
|
localdocs = s2._InlineLocaldocs("a" * 64, "b" * 64, client=client)
|
||||||
|
localdocs.initialize()
|
||||||
|
self.assertEqual(localdocs.headers["mcp-session-id"], "session-a")
|
||||||
|
|
||||||
|
changed = Client(
|
||||||
|
[
|
||||||
|
Response(initialize, "session-a"),
|
||||||
|
Response(notification, "session-a"),
|
||||||
|
Response({"jsonrpc": "2.0", "id": 10, "result": {}}, "session-b"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
localdocs = s2._InlineLocaldocs("a" * 64, "b" * 64, client=changed)
|
||||||
|
localdocs.initialize()
|
||||||
|
with self.assertRaises(s2.IngressError) as raised:
|
||||||
|
localdocs.call("read_binary_doc", {"doc_name": "x"})
|
||||||
|
self.assertEqual(raised.exception.code, "MCP_SESSION_ID_CHANGED")
|
||||||
|
|
||||||
|
bad_protocol = dict(initialize)
|
||||||
|
bad_protocol["result"] = {"protocolVersion": "1900-01-01"}
|
||||||
|
localdocs = s2._InlineLocaldocs(
|
||||||
|
"a" * 64,
|
||||||
|
"b" * 64,
|
||||||
|
client=Client([Response(bad_protocol, "session-a")]),
|
||||||
|
)
|
||||||
|
with self.assertRaises(s2.IngressError) as raised:
|
||||||
|
localdocs.initialize()
|
||||||
|
self.assertEqual(raised.exception.code, "MCP_PROTOCOL_VERSION_MISMATCH")
|
||||||
|
|
||||||
|
def test_inline_hydration_rejects_second_pass_mutation_and_initial_budget(self) -> None:
|
||||||
|
request = {
|
||||||
|
"schema_version": "stage2_s2_00_execution_request.v1",
|
||||||
|
"workflow_id": "S2_00",
|
||||||
|
"request_id": "trace-1",
|
||||||
|
"attempt_id": "attempt-1",
|
||||||
|
"stage1_run_root_ref": "stage1_runs/current",
|
||||||
|
"stage1_deployment_root_ref": "Default_Agent",
|
||||||
|
}
|
||||||
|
request_raw = s2.canonical_json_bytes(request)
|
||||||
|
release_raw = s2.canonical_json_bytes(
|
||||||
|
{"limits": {"max_file_bytes": s2.MAX_FILE_BYTES, "max_run_bytes": s2.MAX_RUN_BYTES, "max_hydration_paths": 1024}}
|
||||||
|
)
|
||||||
|
|
||||||
|
class Reader:
|
||||||
|
def __init__(self, mutate_request: bool = False) -> None:
|
||||||
|
self.counts: dict[str, int] = {}
|
||||||
|
self.mutate_request = mutate_request
|
||||||
|
|
||||||
|
def read_binary(self, path: str) -> bytes:
|
||||||
|
self.counts[path] = self.counts.get(path, 0) + 1
|
||||||
|
if path == s2.INLINE_REQUEST_PATH:
|
||||||
|
if self.mutate_request and self.counts[path] > 1:
|
||||||
|
return request_raw.replace(b"trace-1", b"trace-2")
|
||||||
|
return request_raw
|
||||||
|
if path == s2.INLINE_STAGE2_RELEASE_PATH:
|
||||||
|
return release_raw
|
||||||
|
raise AssertionError(path)
|
||||||
|
|
||||||
|
destinations = [
|
||||||
|
(s2.INLINE_REQUEST_PATH, "stage1_run", "request.json"),
|
||||||
|
(s2.INLINE_STAGE2_RELEASE_PATH, "stage2_asset", "manifest/stage2_release.json"),
|
||||||
|
]
|
||||||
|
release_hash = hashlib.sha256(release_raw).hexdigest()
|
||||||
|
with tempfile.TemporaryDirectory() as directory, mock.patch.object(
|
||||||
|
s2,
|
||||||
|
"EXPECTED_STAGE2_RELEASE_SHA256",
|
||||||
|
release_hash,
|
||||||
|
), mock.patch.object(
|
||||||
|
s2,
|
||||||
|
"_inline_release_materialization_plan",
|
||||||
|
return_value=(destinations, {s2.INLINE_STAGE2_RELEASE_PATH: release_hash}),
|
||||||
|
):
|
||||||
|
with self.assertRaises(s2.IngressError) as raised:
|
||||||
|
s2._inline_hydrate(Reader(mutate_request=True), Path(directory))
|
||||||
|
self.assertEqual(raised.exception.code, "SOURCE_SNAPSHOT_CHANGED")
|
||||||
|
|
||||||
|
tiny_release_raw = s2.canonical_json_bytes(
|
||||||
|
{"limits": {"max_file_bytes": s2.MAX_FILE_BYTES, "max_run_bytes": 1, "max_hydration_paths": 1024}}
|
||||||
|
)
|
||||||
|
tiny_hash = hashlib.sha256(tiny_release_raw).hexdigest()
|
||||||
|
|
||||||
|
class TinyReader(Reader):
|
||||||
|
def read_binary(self, path: str) -> bytes:
|
||||||
|
if path == s2.INLINE_STAGE2_RELEASE_PATH:
|
||||||
|
return tiny_release_raw
|
||||||
|
return super().read_binary(path)
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory() as directory, mock.patch.object(
|
||||||
|
s2,
|
||||||
|
"EXPECTED_STAGE2_RELEASE_SHA256",
|
||||||
|
tiny_hash,
|
||||||
|
):
|
||||||
|
with self.assertRaises(s2.IngressError) as raised:
|
||||||
|
s2._inline_hydrate(TinyReader(), Path(directory))
|
||||||
|
self.assertEqual(raised.exception.code, "AGGREGATE_RUN_SIZE_LIMIT")
|
||||||
|
|
||||||
|
def test_execution_request_is_closed_and_paths_are_workspace_relative(self) -> None:
|
||||||
|
valid = {
|
||||||
|
"schema_version": "stage2_s2_00_execution_request.v1",
|
||||||
|
"workflow_id": "S2_00",
|
||||||
|
"request_id": "trace-1",
|
||||||
|
"attempt_id": "attempt-1",
|
||||||
|
"stage1_run_root_ref": "stage1_runs/current",
|
||||||
|
"stage1_deployment_root_ref": "Default_Agent",
|
||||||
|
}
|
||||||
|
parsed = s2._inline_validate_request(json.dumps(valid).encode())
|
||||||
|
self.assertEqual(parsed["request_id"], "trace-1")
|
||||||
|
for mutation, code in (
|
||||||
|
({**valid, "unknown": True}, "RUN_REQUEST_CLOSED_SHAPE"),
|
||||||
|
({**valid, "stage1_run_root_ref": "../escape"}, "STAGE1_RUN_ROOT_REF_INVALID"),
|
||||||
|
({**valid, "stage1_deployment_root_ref": "/absolute"}, "STAGE1_DEPLOYMENT_ROOT_REF_INVALID"),
|
||||||
|
):
|
||||||
|
with self.subTest(mutation=mutation):
|
||||||
|
with self.assertRaises(s2.IngressError) as raised:
|
||||||
|
s2._inline_validate_request(json.dumps(mutation).encode())
|
||||||
|
self.assertEqual(raised.exception.code, code)
|
||||||
|
|
||||||
|
def test_algorithm_digest_and_run_id_do_not_depend_on_mirror_path(self) -> None:
|
||||||
|
self.assertEqual(len(s2.ALGORITHM_SEMANTIC_DIGEST), 64)
|
||||||
|
binding = "a" * 64
|
||||||
|
self.assertEqual(s2.canonical_run_id(binding), f"S2RUN-{binding}")
|
||||||
|
with self.assertRaises(s2.IngressError):
|
||||||
|
s2.canonical_run_id("not-a-digest")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
+88
-31
@@ -3,23 +3,27 @@ Agent:
|
|||||||
description: >-
|
description: >-
|
||||||
Stage 1 Part 1-4의 봉인 입력을 결정적으로 검증·보존·정규화하고,
|
Stage 1 Part 1-4의 봉인 입력을 결정적으로 검증·보존·정규화하고,
|
||||||
claim-neutral cluster slice와 bundle plan을 작성하는 NON-LLM workflow 계약.
|
claim-neutral cluster slice와 bundle plan을 작성하는 NON-LLM workflow 계약.
|
||||||
version: "1.0.0-draft"
|
version: "1.1.0"
|
||||||
metadata:
|
metadata:
|
||||||
workflow_id: S2_00
|
workflow_id: S2_00
|
||||||
execution_class: NON-LLM-DETERMINISTIC
|
execution_class: NON-LLM-DETERMINISTIC
|
||||||
workflow_schema_version: stage2_workflow_contract.v1
|
workflow_schema_version: stage2_workflow_contract.v1.1
|
||||||
asset_version: s2_00.1
|
asset_version: s2_00.1.1
|
||||||
owner: Stage_2_workflow_maintainer
|
owner: Stage_2_workflow_maintainer
|
||||||
implementation_status: DRAFT_PENDING_SEQUENTIAL_BIND
|
implementation_status: IMPLEMENTED_DECLARATIVE_CONTRACT
|
||||||
invocation_status: OPEN_EXTERNAL_BACKEND
|
invocation_status: PENDING_SECRET_AND_LIVE_BINDING
|
||||||
release_binding_ref: manifest/stage2_release.json#/module_manifest_ref
|
release_binding_ref: manifest/stage2_release.json
|
||||||
loader_binding_ref: deployment/stage2_loader_binding.yml
|
executor_binding_ref: deployment/stage2_code_executor_binding.yml
|
||||||
|
inline_code_receipt_ref: manifest/s2_00_inline_code_receipt.json
|
||||||
|
agent_script_ref: agent_scripts/Stage_2_S2_00.yml
|
||||||
|
loader_binding_disposition: REFERENCE_ONLY_SUPERSEDED_FOR_S2_00
|
||||||
|
|
||||||
Stages:
|
Stages:
|
||||||
- name: S2_00
|
- name: S2_00
|
||||||
description: >-
|
description: >-
|
||||||
C00, C05, C10, C15를 순서대로 수행하는 fixed-runtime entry.
|
C00, C05, C10, C15의 IO·순서·분기·배리어를 선언하는 NON-LLM 계약.
|
||||||
외부 AgentBackend primitive가 검증되기 전에는 이 선언 자체가 실행 가능성을 뜻하지 않는다.
|
실행 Python은 이 문서가 아니라 Agent YAML의 exactly-one
|
||||||
|
code-executor.run_code task에만 존재한다.
|
||||||
prevs: []
|
prevs: []
|
||||||
nexts:
|
nexts:
|
||||||
- S2_10
|
- S2_10
|
||||||
@@ -29,10 +33,14 @@ Agent:
|
|||||||
orchestration_contract:
|
orchestration_contract:
|
||||||
workflow_id: S2_00
|
workflow_id: S2_00
|
||||||
execution_class: NON-LLM-DETERMINISTIC
|
execution_class: NON-LLM-DETERMINISTIC
|
||||||
entrypoint_ref: runtime/s2_00_ingress.py
|
executable_agent_ref: agent_scripts/Stage_2_S2_00.yml
|
||||||
loader_ref: release_ops/stage2_loader.py
|
executor_binding_ref: deployment/stage2_code_executor_binding.yml
|
||||||
binding_ref: deployment/stage2_loader_binding.yml
|
inline_code_receipt_ref: manifest/s2_00_inline_code_receipt.json
|
||||||
invocation_status: OPEN_EXTERNAL_BACKEND
|
workflow_contract_is_executable: false
|
||||||
|
task_count: 0
|
||||||
|
invocation_status: PENDING_SECRET_AND_LIVE_BINDING
|
||||||
|
external_runtime_mirror_ref: runtime/s2_00_ingress.py
|
||||||
|
external_runtime_mirror_disposition: TEST_PARITY_ORACLE_ONLY_RUNTIME_IMPORT_FORBIDDEN
|
||||||
component_order:
|
component_order:
|
||||||
- C00
|
- C00
|
||||||
- C05
|
- C05
|
||||||
@@ -80,7 +88,7 @@ Agent:
|
|||||||
- compile_cluster_slices
|
- compile_cluster_slices
|
||||||
- compile_bundle_plan
|
- compile_bundle_plan
|
||||||
- validate_bundle_release_cohorts
|
- validate_bundle_release_cohorts
|
||||||
- publish_atomically
|
- publish_with_status_last_logical_barrier
|
||||||
writes:
|
writes:
|
||||||
- context/cluster_plan.json
|
- context/cluster_plan.json
|
||||||
- context/cluster_slices/<cluster_id>.json
|
- context/cluster_slices/<cluster_id>.json
|
||||||
@@ -88,7 +96,14 @@ Agent:
|
|||||||
- ingress/ingress_status.json
|
- ingress/ingress_status.json
|
||||||
|
|
||||||
execution:
|
execution:
|
||||||
timeout_seconds: 600
|
mcp_server_id: code-executor
|
||||||
|
tool_name: run_code
|
||||||
|
language: python
|
||||||
|
requirements: "httpx==0.28.1"
|
||||||
|
network_profile: agent-network
|
||||||
|
timeout_seconds: 300
|
||||||
|
inline_task_count: 1
|
||||||
|
executable_task_owner_ref: agent_scripts/Stage_2_S2_00.yml#/Agent/Stages/0/tasks/0
|
||||||
retry_policy_id: S2-RETRY-TRANSIENT-READ-V1
|
retry_policy_id: S2-RETRY-TRANSIENT-READ-V1
|
||||||
retry_policy_ref: manifest/stage2_release.json#/retry_policies/0
|
retry_policy_ref: manifest/stage2_release.json#/retry_policies/0
|
||||||
idempotence_key_material:
|
idempotence_key_material:
|
||||||
@@ -98,24 +113,47 @@ Agent:
|
|||||||
- release_class
|
- release_class
|
||||||
attempt_id_is_canonical_id_input: false
|
attempt_id_is_canonical_id_input: false
|
||||||
same_input_outputs_must_be_byte_identical: true
|
same_input_outputs_must_be_byte_identical: true
|
||||||
whole_tree_atomic_publish_required: true
|
whole_tree_atomic_publish_required: false
|
||||||
|
physical_directory_rename_claimed: false
|
||||||
|
logical_publish_barrier_required: true
|
||||||
|
logical_publish_barrier_id: S2_00_INGRESS_STATUS_BARRIER
|
||||||
|
logical_publish_barrier_path: ingress/ingress_status.json
|
||||||
ingress_status_written_last: true
|
ingress_status_written_last: true
|
||||||
|
|
||||||
root_arguments:
|
request_contract:
|
||||||
- argument_id: stage1_run_root_ref
|
fixed_path: stage2_control/s2_00_request.json
|
||||||
binding: backend_workspace_transport
|
schema_ref: schemas/ingress.schema.json#/$defs/execution_request
|
||||||
|
directory_scan_allowed: false
|
||||||
|
alternate_request_path_allowed: false
|
||||||
|
required_fields:
|
||||||
|
- schema_version
|
||||||
|
- workflow_id
|
||||||
|
- request_id
|
||||||
|
- attempt_id
|
||||||
|
- stage1_run_root_ref
|
||||||
|
- stage1_deployment_root_ref
|
||||||
|
|
||||||
|
root_bindings:
|
||||||
|
- binding_id: workspace_logical_root
|
||||||
|
binding: localdocs_initialized_user_workspace_session
|
||||||
arbitrary_absolute_path_allowed: false
|
arbitrary_absolute_path_allowed: false
|
||||||
- argument_id: stage1_deployment_root_ref
|
- binding_id: stage1_run_root_ref
|
||||||
binding: stage2_release_dependency_lock
|
binding: fixed_request_payload
|
||||||
arbitrary_absolute_path_allowed: false
|
arbitrary_absolute_path_allowed: false
|
||||||
- argument_id: run_id
|
- binding_id: stage1_deployment_root_ref
|
||||||
binding: run_tuple
|
binding: fixed_request_payload_and_stage2_release_dependency_lock
|
||||||
- argument_id: attempt_id
|
arbitrary_absolute_path_allowed: false
|
||||||
binding: transient_retry_only
|
- binding_id: canonical_run_id
|
||||||
- argument_id: user_context_sha256
|
binding: run_binding_digest
|
||||||
binding: backend_session
|
caller_supplied_value_allowed: false
|
||||||
- argument_id: workspace_context_sha256
|
- binding_id: canonical_output_root
|
||||||
binding: backend_session
|
binding: stage2_runs/by-binding/<run_binding_digest>/
|
||||||
|
caller_supplied_value_allowed: false
|
||||||
|
- binding_id: attempt_id
|
||||||
|
binding: fixed_request_payload_transient_retry_only
|
||||||
|
- binding_id: user_workspace_context
|
||||||
|
binding: localdocs_initialize_client_info_hash_templates
|
||||||
|
raw_identifier_persistence_allowed: false
|
||||||
|
|
||||||
input_contract:
|
input_contract:
|
||||||
- logical_input_id: evidence_indexed
|
- logical_input_id: evidence_indexed
|
||||||
@@ -203,6 +241,8 @@ Agent:
|
|||||||
glob_allowed: false
|
glob_allowed: false
|
||||||
fuzzy_basename_allowed: false
|
fuzzy_basename_allowed: false
|
||||||
root_level_domain_activation_manifest_allowed: false
|
root_level_domain_activation_manifest_allowed: false
|
||||||
|
hydration_stability_policy: BOUNDED_TWO_PASS_BINARY_RAW_HASH_MAP_EQUALITY
|
||||||
|
hydration_max_passes: 2
|
||||||
|
|
||||||
signal_contract:
|
signal_contract:
|
||||||
all_expansion_adapter_id: S2A-SIGNAL-ALL-V1
|
all_expansion_adapter_id: S2A-SIGNAL-ALL-V1
|
||||||
@@ -295,6 +335,14 @@ Agent:
|
|||||||
- ingress/ingress_status.json
|
- ingress/ingress_status.json
|
||||||
normal_branch_technical_diagnostic_allowed: false
|
normal_branch_technical_diagnostic_allowed: false
|
||||||
diagnostic_branch_context_publish_allowed: false
|
diagnostic_branch_context_publish_allowed: false
|
||||||
|
canonical_output_root_rule: stage2_runs/by-binding/<run_binding_digest>/
|
||||||
|
output_root_from_request_allowed: false
|
||||||
|
publish_barrier:
|
||||||
|
barrier_id: S2_00_INGRESS_STATUS_BARRIER
|
||||||
|
relative_path: ingress/ingress_status.json
|
||||||
|
semantics: STATUS_LAST_LOGICAL_COMMIT
|
||||||
|
non_status_artifact_readback_required: true
|
||||||
|
barrier_readback_required: true
|
||||||
|
|
||||||
routing:
|
routing:
|
||||||
success_handoff:
|
success_handoff:
|
||||||
@@ -314,9 +362,18 @@ Agent:
|
|||||||
prohibitions:
|
prohibitions:
|
||||||
llm_calls_allowed: false
|
llm_calls_allowed: false
|
||||||
llm_configuration_present: false
|
llm_configuration_present: false
|
||||||
inline_python_allowed: false
|
inline_python_allowed_in_workflow_contract: false
|
||||||
|
executable_inline_python_owner: agent_scripts/Stage_2_S2_00.yml#/Agent/Stages/0/tasks/0/parameters/code
|
||||||
inline_schema_allowed: false
|
inline_schema_allowed: false
|
||||||
external_network_access_allowed: false
|
external_internet_access_allowed: false
|
||||||
|
mcp_endpoint_allowlist:
|
||||||
|
- http://mcp-localdocs:8012/mcp
|
||||||
|
localdocs_tool_allowlist:
|
||||||
|
- read_binary_doc
|
||||||
|
- write_binary_file
|
||||||
|
external_runtime_import_allowed: false
|
||||||
|
loader_invocation_allowed: false
|
||||||
|
loader_fallback_allowed: false
|
||||||
dynamic_code_allowed: false
|
dynamic_code_allowed: false
|
||||||
runtime_package_install_allowed: false
|
runtime_package_install_allowed: false
|
||||||
raw_stage1_id_normalization_allowed: false
|
raw_stage1_id_normalization_allowed: false
|
||||||
|
|||||||
+1
-1
@@ -112,7 +112,7 @@ Agent:
|
|||||||
- task_name: Task_S2_10_resolve_cluster_*
|
- task_name: Task_S2_10_resolve_cluster_*
|
||||||
max_concurrency: 8
|
max_concurrency: 8
|
||||||
execution_class: LLM-DIRECT
|
execution_class: LLM-DIRECT
|
||||||
deployment_binding_ref: deployment/stage2_loader_binding.yml#/downstream_llm_candidate_bindings/0
|
deployment_binding_ref: deployment/stage2_code_executor_binding.yml#/downstream_llm_candidate_bindings/0
|
||||||
deployment_binding_status_required: SIGNED_RELEASE_BOUND
|
deployment_binding_status_required: SIGNED_RELEASE_BOUND
|
||||||
llm_verbosity: medium
|
llm_verbosity: medium
|
||||||
llm_endpoint: responses
|
llm_endpoint: responses
|
||||||
|
|||||||
@@ -16,8 +16,20 @@
|
|||||||
- 하위 에이전트(subagents)를 사용하여 핵심 테마와 교훈을 식별하고 MEMORY.md에 섹션으로 저장하라.
|
- 하위 에이전트(subagents)를 사용하여 핵심 테마와 교훈을 식별하고 MEMORY.md에 섹션으로 저장하라.
|
||||||
- 향후 세션 시작 시 MEMORY.md의 이전 섹션을 참조하라.
|
- 향후 세션 시작 시 MEMORY.md의 이전 섹션을 참조하라.
|
||||||
|
|
||||||
|
## 2026-08-30 — Stage 2 v5 direct MCP inline S2_00 구현
|
||||||
|
|
||||||
|
한 줄 요약: v4의 외부 loader 호출 전제를 폐기하고, `Stage_2_S2_00_v.1.yml`의 단일 `run_code`가 MCP Code Executor 프로토콜로 `runtime/s2_00_ingress.py`와 byte-identical한 코드를 직접 실행하도록 v5 전략·SOW·자산 계약과 S2_00 구현을 일관되게 개정했다.
|
||||||
|
|
||||||
|
- 핵심 산출물: `stage_2_optimal_update_strategy_v.5.md`, `stage_2_optimal_update_strategy_v.5_workflow.svg`, `S_00_SOW_v.1.md`, `S2_00_assets_v.1.md`, `Stage_2_S2_00_v.1.yml`; 배포 projection은 `Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00.yml`이다. deterministic stage는 각 1개의 executable Agent script와 정확히 1개의 `run_code`를 소유하며 전체 계획 수량은 declarative YAML 92 + executable Agent script 5 = 97이다.
|
||||||
|
- 실행·배포 계약: authoring YAML의 `code: |-` scalar를 단일 원천으로 삼고 `offline_build/build_s2_00_inline_projection.py`가 runtime `.py`·byte-identical `.txt` mirror·배포 YAML·inline receipt를 투영한다. 고정 요청은 `stage2_control/s2_00_request.json`, 결과는 `stage2_runs/by-binding/<run_binding_digest>/` 아래에 저장하며, O-06 external native loader binding은 `SUPERSEDED_BY_DIRECT_MCP_INLINE_CODE`로 닫았다.
|
||||||
|
- 안전·정합성 교정: two-pass bounded raw-byte hydration, initialize protocolVersion·MCP session 고정, localdocs-only 직접 호출, stdout single-JSON 격리, 성공/실패 inner receipt와 outer receipt schema, release/module/executor 상호 hash 결속, output readback allowlist·idempotency, S2_10 exact handoff와 S2_40 exact five-input single-writer 경계를 구현했다. unbound user/workspace identity는 network 전 단계에서 schema-valid `FAILED_NO_BARRIER` receipt 하나만 출력한다.
|
||||||
|
- 검증 이력: 전략·SOW·자산 문서는 독립 evaluator 2개씩 정확히 2회, 변경 자산 17개는 자산별 독립 verifier로 정확히 1회 검증 후 일괄 개정했다. YAML 검증 Round 1의 두 결함(unbound identity의 legacy CLI 분기, `code: |` chomping)을 교정했고 Round 2는 추가 finding 없이 PASS했다. 최종 builder parity, JSON/YAML/schema/hash·mirror 무결성, 직접 failure-path 실행과 `tests/s2_00` 64/64 PASS를 확인했다.
|
||||||
|
- 상태 경계: 현 상태는 `IMPLEMENTED_OFFLINE_VERIFIED / LIVE_ADMISSION_PENDING`이다. Code Executor secret/auth, runtime image digest, live egress enforcement, 300초 live benchmark, 실제 binary I/O·outer receipt, canary/production admission과 법률 sign-off는 별도 운영 검증 대상이며 완료로 주장하지 않는다.
|
||||||
|
|
||||||
## 2026-08-30 — S2_00 AgentBackend YAML 작업명세 구현
|
## 2026-08-30 — S2_00 AgentBackend YAML 작업명세 구현
|
||||||
|
|
||||||
|
> 상태: 아래 O-06 대기형 설계는 바로 위의 v5 direct MCP inline 구현으로 대체되었으며, 역사 기록으로만 유지한다.
|
||||||
|
|
||||||
한 줄 요약: `S_00_SOW.md`의 S2_00 계약을 두 독립 후보의 앙상블과 독립 evaluator 2개씩 정확히 2회 검증·증분개정하여 `Stage_2_S2_00.yml`로 구현하되, 미확인 native primitive를 창작하지 않고 O-06이 닫힐 때까지 명시적인 비실행 authoring projection으로 고정했다.
|
한 줄 요약: `S_00_SOW.md`의 S2_00 계약을 두 독립 후보의 앙상블과 독립 evaluator 2개씩 정확히 2회 검증·증분개정하여 `Stage_2_S2_00.yml`로 구현하되, 미확인 native primitive를 창작하지 않고 O-06이 닫힐 때까지 명시적인 비실행 authoring projection으로 고정했다.
|
||||||
|
|
||||||
- 최종 산출물: `YAML_Prompts/2. Stage_2/Stage_2_S2_00.yml`; 954행/45,833 bytes/SHA-256 `3fbadd9bf48e8d3921b43f766690bf67ad1dd2cd0a8f624d78a9b3223edeeeb7`. 후보 A는 계약 완전성, 후보 B는 canonical 참조 최소화를 강화했고 main agent가 thin loader-adapter 구조로 병합했다.
|
- 최종 산출물: `YAML_Prompts/2. Stage_2/Stage_2_S2_00.yml`; 954행/45,833 bytes/SHA-256 `3fbadd9bf48e8d3921b43f766690bf67ad1dd2cd0a8f624d78a9b3223edeeeb7`. 후보 A는 계약 완전성, 후보 B는 canonical 참조 최소화를 강화했고 main agent가 thin loader-adapter 구조로 병합했다.
|
||||||
@@ -39,6 +51,38 @@
|
|||||||
- 최종 검증: `unittest discover tests/s2_00` 38/38 PASS, loader validate-only `VALIDATED_WITH_PENDING_BINDINGS`, JSON/YAML parse·Python in-memory compile·deployment schema·모든 raw hash·module/release digest·P00/P10 NFC/prefix 검증 PASS. release digest는 `49ff3967f8eaa9c2291af6d0c17af1da2504296f03a020a84d1ea21c5954a2f0`, materialized base prefix digest는 `3634cb6929cce8395683048eabb068ed63aed0427869b19ce4b56c586ab48071`이다.
|
- 최종 검증: `unittest discover tests/s2_00` 38/38 PASS, loader validate-only `VALIDATED_WITH_PENDING_BINDINGS`, JSON/YAML parse·Python in-memory compile·deployment schema·모든 raw hash·module/release digest·P00/P10 NFC/prefix 검증 PASS. release digest는 `49ff3967f8eaa9c2291af6d0c17af1da2504296f03a020a84d1ea21c5954a2f0`, materialized base prefix digest는 `3634cb6929cce8395683048eabb068ed63aed0427869b19ce4b56c586ab48071`이다.
|
||||||
- 상태·잔여한계: release는 의도적으로 `DRAFT_NOT_EXECUTABLE`·`DEV_VALIDATION_ONLY`다. O-06 external AgentBackend invocation, O-07 signed admission, Stage 1 completion seal, detached signature가 열려 있고 full Stage 1 runtime manifest도 `STAGE1_NOT_RELEASE_READY`다. authority/corpus는 미배포, P00/P10과 profile은 `DRAFT_UNVERIFIED`·법률가 검수 대기다. 10+50 fixture는 `NOT_RUN_DESCRIPTOR_BOUND_ONLY`이며 live Stage 1→S2_00→S2_10/S2_40 E2E·대한민국 변호사 법률 sign-off·production 배포를 수행하거나 주장하지 않는다.
|
- 상태·잔여한계: release는 의도적으로 `DRAFT_NOT_EXECUTABLE`·`DEV_VALIDATION_ONLY`다. O-06 external AgentBackend invocation, O-07 signed admission, Stage 1 completion seal, detached signature가 열려 있고 full Stage 1 runtime manifest도 `STAGE1_NOT_RELEASE_READY`다. authority/corpus는 미배포, P00/P10과 profile은 `DRAFT_UNVERIFIED`·법률가 검수 대기다. 10+50 fixture는 `NOT_RUN_DESCRIPTOR_BOUND_ONLY`이며 live Stage 1→S2_00→S2_10/S2_40 E2E·대한민국 변호사 법률 sign-off·production 배포를 수행하거나 주장하지 않는다.
|
||||||
|
|
||||||
|
## 2026-08-29 — S2_00 YAML·assets·sources 생성 SOW
|
||||||
|
|
||||||
|
한 줄 요약: Stage 2 v4의 `S2_00`을 현행 Stage 1 Part 1~4만 소비하는 단일 fixed-Python deterministic ingress로 구현하기 위한 source lock·보존식·context ID·cluster/bundle·loader·atomic publish·fixture/release 계약을 `S_00_SOW.md`에 확정했다.
|
||||||
|
|
||||||
|
- 최종 산출물: `S_00_SOW.md`; 978행/67,381 bytes/SHA-256 `a1a20207480d63ee90d61f83fa88848d1936f318d0ce8ab8f5b34b5cf1ca11c4`. 실제 생성대상은 전담 workflow YAML 1, fixed Python 1, schema 2, test 6축과 fixture family이며, 공유 변경은 §2.2의 10 logical family다.
|
||||||
|
- 실행 backbone: `C00 exact bounded snapshot/source lock → C05 독립 보존검사 → C10 context·crosswalk·base issue → C15 explicit-relation cluster·slice·bundle`. executable cluster가 있으면 exact S2_10 workflow로, coherent executable package가 없으면 정확한 5개 artifact만 exact S2_40 status-only workflow로 보낸다.
|
||||||
|
- Stage 1 실물 교정: `routing/domain_screening.json`, routing/signal dual SG-01, `downstream_read_sets.stage2=["ALL"] + files[]`, P1 flat seven-key/P2 flat/P3·P4 wrapper, current-v8 Fact Ledger의 필수 `domain_effects`·`calculation_requests`, `client_goal.json` 내부 target/asset JSON Pointer를 고정했다. Stage 1 제5 task나 별도 target/asset file을 요구하지 않는다.
|
||||||
|
- 보존·식별 계약: signal file row Counter와 semantic record occurrence Counter를 분리하고 compatibility view는 integrity-only로 둔다. Stage 1에 canonical party/object ID가 없으면 실체 동일성을 확정하지 않는 occurrence·lineage context ID만 mint하며 fuzzy merge를 금지한다. review ID는 logical input·canonical raw hash·duplicate occurrence index로 만든다.
|
||||||
|
- 실행 경계: v4의 `release_ops/stage2_loader.py`가 signed release/hash/workspace를 확인하는 유일 host trust boundary다. raw digest는 byte-preserving one-read snapshot만 사용하고 TOCTOU·resource limit을 검사한다. 하나의 run ID는 input/release/algorithm/class tuple에 영구 결속하며 sibling staging 전체를 fsync 후 whole-tree atomic rename한다.
|
||||||
|
- 평가: 독립 설계안 2개를 앙상블한 뒤 서로 다른 evaluator로 정확히 2회 평가·증분 개정했다. Round 1 `CRITICAL 3/MAJOR 8/MINOR 3`, Round 2 `CRITICAL 4/MAJOR 9/MINOR 3`의 30건을 모두 `APPLIED`로 닫았다. 사용자 지정 횟수에 따라 제3회 평가·사후 PASS 선언은 하지 않았다.
|
||||||
|
- 정적검사: Markdown fence 36(짝수), Round finding 14/16, shared inventory 10행, UTF-8 PASS, trailing whitespace 0, 구 Stage 2 v.0~v.3 import edge 0.
|
||||||
|
- 경계: 이번 작업은 구현 SOW와 검증 기록의 작성이다. `Default_Agent/Stage_2_Clean/`의 실제 YAML/Python/schema/test/fixture 생성·배포, live loader invocation, Stage 1→S2_00→S2_10/S2_40 E2E, canary/production admission과 대한민국 변호사 법률 검수는 아직 수행하지 않았다.
|
||||||
|
|
||||||
|
## 2026-08-29 — 137종 청구취지 규칙 문서와 원자적 `sub_rule_id`
|
||||||
|
|
||||||
|
한 줄 요약: 사건종류별 청구취지 Markdown은 137개 고정 문서로 사전 구축하되, 복합·주제형·포괄 문서 내부의 원자적 규칙 branch를 안정적인 `sub_rule_id`로 분리하고 runtime은 구조화 registry에서 정확한 branch만 선택한다.
|
||||||
|
|
||||||
|
- 수량 정정: `N_claim_capable`은 사건별 식별 청구권 수가 아니다. 사용자의 확정 설계에서는 물리 Markdown 문서 수를 `137`로 고정하고, 별도의 구조화된 원자적 규칙 수를 `N_rule_branch`로 관리한다. 하나의 문서가 여러 개별 청구 규칙을 포함할 수 있으므로 두 수량을 혼동하지 않는다.
|
||||||
|
- 실행정책: 사건 실행 중 문서를 생성하지 않는다. S2_10/S2_20에서 식별·정규화한 atomic claim signature를 C25가 exact predicate로 `case_type_id`와 `sub_rule_id`에 결속하고, S2_30에는 해당 사전 승인 branch만 공급한다.
|
||||||
|
- 문서 metadata: 각 문서는 document-level `case_type_id`를 가지고, 각 원자적 section에는 `sub_rule_id=RELIEF.<case_type_id>.<immutable_branch_key>`, `signature_predicate_id`, `renderer_id`, `required_slots`, 병합·예비적 관계, 금지조건, authority·적용기간·review status를 기계 판독 가능한 YAML block으로 기록한다. 순번·내용 hash 기반 ID와 폐기 ID 재사용은 금지한다.
|
||||||
|
- projection: 법률가가 branch 경계·내용·ID를 승인한 뒤 `offline_build/compile_relief_rule_projection.py`가 Markdown을 검증하여 `case_type_rule_registry.yml`과 `case_type_relief_rules.yml`의 `signature predicate → case_type_id → sub_rule_id` row로 projection한다. runtime은 Markdown 자유문을 직접 해석하지 않는다.
|
||||||
|
- YAML 성격: canonical 92개는 실제로 `LLM-DIRECT` 2, `LLM-CONTEXT` 45, `DUAL-CONTRACT` 6, `NON-LLM-DETERMINISTIC` 39의 네 종류다. DIRECT만 모델을 호출하고, CONTEXT는 선택 주입되는 법리정보, NON-LLM은 고정 Python용 선언 데이터, DUAL은 deterministic 정본과 제한된 LLM projection을 함께 가진다.
|
||||||
|
- 경계: 이번 세션은 설계 해석을 확정하여 MEMORY에 기록한 것이다. `assets_for_stage_2_v.4.md`의 `N_claim_capable` 표기, v4 전략서, 137개 Markdown, `sub_rule_id` registry와 compiler 구현은 아직 개정·생성하지 않았다.
|
||||||
|
|
||||||
|
## 2026-08-29 — ACTIO 지정 10종의 migration 경계
|
||||||
|
|
||||||
|
한 줄 요약: 기존 ACTIO 10종은 사건별 runtime 입력이 아니라 신규 canonical ACTIO 자산을 만드는 일회성 offline migration source이며, runtime은 검수·봉인된 canonical target만 소비한다.
|
||||||
|
|
||||||
|
- migration 처리: 원본에서 법리 명제·route·계산 operand·항변·검증조건을 추출하고 현행 공식 법령·판례와 대조한 뒤, 승인된 내용만 CE-10 계산규칙, ACTIO overlay·route registry·validator·regression fixture 등으로 이관한다. 원본 내용의 무검증 복사나 runtime fallback은 금지한다.
|
||||||
|
- 감사증적: `migration/actio_assets_receipt.json`은 원본 exact path, raw basename bytes/NFC display name, source hash, 추출 proposition, official authority, 교정·제외 이유, canonical target와 target hash를 기록한다.
|
||||||
|
- 경계: 이번 작업은 위 문구의 의미를 확정하여 MEMORY에 기록한 것이며 ACTIO canonical 자산·migration receipt·compiler를 실제 생성하거나 실행하지 않았다.
|
||||||
|
|
||||||
## 2026-08-28 — Stage 2 최적 개정 전략 v4 및 자산 명세
|
## 2026-08-28 — Stage 2 최적 개정 전략 v4 및 자산 명세
|
||||||
|
|
||||||
한 줄 요약: v3의 유효 계약을 보존하면서 `take_away_from_eval_v.3.md`의 채택사항을 5-task clean-slate DAG에 흡수하고, 2회 독립 병렬 전략 검증과 1회 독립 자산 검증을 거쳐 v4 전략서·상세 SVG·version-free 자산/source inventory를 완성했다.
|
한 줄 요약: v3의 유효 계약을 보존하면서 `take_away_from_eval_v.3.md`의 채택사항을 5-task clean-slate DAG에 흡수하고, 2회 독립 병렬 전략 검증과 1회 독립 자산 검증을 거쳐 v4 전략서·상세 SVG·version-free 자산/source inventory를 완성했다.
|
||||||
|
|||||||
@@ -0,0 +1,412 @@
|
|||||||
|
# S2_00 고정 자산·계약·source inventory v1
|
||||||
|
|
||||||
|
> 기준 문서: `S_00_SOW_v.1.md`; 최종 SHA-256은 문서·자산 검증 완료 시 receipt에 기록
|
||||||
|
>
|
||||||
|
> 기준일: 2026-08-30 (Asia/Seoul)
|
||||||
|
>
|
||||||
|
> 문서 목적: S2_00을 구현·시험·배포·실행할 때 필요한 고정 파일과 고정 locator를 누락 없이 구분한다.
|
||||||
|
|
||||||
|
## 0. 판독 기준
|
||||||
|
|
||||||
|
이 문서는 `stage_2_optimal_update_strategy_v.5.md`와 `S_00_SOW_v.1.md`의 direct MCP inline Code Executor 계약을 기준으로 작성한다.
|
||||||
|
|
||||||
|
```text
|
||||||
|
P = workspace logical root
|
||||||
|
M = Case_02_Comparison_Research/YAML_Prompts/2. Stage_2/
|
||||||
|
R = Default_Agent/Stage_2_Clean/
|
||||||
|
U = workspace Stage 1 current-run logical root
|
||||||
|
D = Default_Agent/
|
||||||
|
Q = stage2_control/s2_00_request.json (사건별 runtime input; 고정 자산 아님)
|
||||||
|
O = stage2_runs/by-binding/<run_binding_digest>/
|
||||||
|
```
|
||||||
|
|
||||||
|
2026-08-30 현재 repository authoring tree의 `M/Default_Agent/Stage_2_Clean/`에는 S2_00 고정 자산이 존재한다. 아래 표는 이를 runtime version-free `R/`로 투영한 경로를 사용하고 각 자산을 `REUSE`, `UPDATE`, `NEW`, `REFERENCE_ONLY`로 처분한다.
|
||||||
|
|
||||||
|
이 문서에서 구분하는 물리 성격은 다음과 같다.
|
||||||
|
|
||||||
|
| 성격 | 의미 |
|
||||||
|
|---|---|
|
||||||
|
| `EXACT-OWNED` | S2_00이 직접 소유하는 고정 파일 |
|
||||||
|
| `EXACT-SHARED` | 다른 task/release owner가 소유하지만 S2_00 때문에 생성·개정해야 하는 고정 파일 |
|
||||||
|
| `FIXED-RELEASE` | 사건과 무관하게 미리 작성되며, release가 활성 subset의 exact path/hash를 봉인하는 파일 |
|
||||||
|
| `FIXED-FAMILY` | 파일명 개수는 release에 따라 달라지지만 각 물리 파일은 실행 전에 생성·검수·봉인되는 family |
|
||||||
|
| `UPSTREAM-DEPLOYMENT` | Stage 1 배포본의 read-only 고정 계약 파일 |
|
||||||
|
| `RUNTIME-LOCATOR` | 경로·producer 계약은 고정이나 내용은 사건마다 Stage 1이 생성하는 입력 파일 |
|
||||||
|
| `CONDITIONAL` | 기본 배포에는 없고 명시된 조건에서만 release가 exact path/hash를 제공하는 파일 |
|
||||||
|
|
||||||
|
### 0.1 수량 요약
|
||||||
|
|
||||||
|
| 범주 | exact file | family | 비고 |
|
||||||
|
|---|---:|---:|---|
|
||||||
|
| S2_00 실행·binding 신규 | 4 physical | 0 | authoring YAML, deployment projection, executor binding, inline-code receipt |
|
||||||
|
| S2_00 직접 소유 core | 4 logical / 5 physical | 0 | workflow 1, full-code mirror pair 2, schema 2 |
|
||||||
|
| S2_00 projection builder | 1 logical / 2 physical | 0 | build-only `.py/.txt` pair |
|
||||||
|
| S2_00 명명 test source | 7 logical / 14 physical | 0 | 기존 6축 + inline-code parity; 각 `.py`는 byte-identical `.txt` mirror 필요 |
|
||||||
|
| S2_00 fixture | 0 | 2 | case fixture와 mutation fixture |
|
||||||
|
| 공유 계약·executor·handoff | 10 logical | 0 | loader 2종은 active closure에서 빠지고 executor binding/receipt가 들어감 |
|
||||||
|
| S2_10 direct bundle 전체 planned catalog | 49 | 1 | P00/P10 2, profile 45, authority registry/release 2, capture family 1; 사건별로는 active subset만 결속 |
|
||||||
|
| authority closure 조건부 law-value | 2 | 0 | authority release가 참조할 때만 exact closure에 포함; bundle 전체-row 복제 금지 |
|
||||||
|
| 후단 availability manifest | 2 | 0 | S2_00은 내용 ingest 없이 digest/availability만 확인 |
|
||||||
|
| Stage 1 배포 계약 현행 closure | 55 | 0 | anchor 3 + domain config 26 + platform schema 9 + signal-support schema 17 |
|
||||||
|
| Stage 1 사건별 입력 | 16 | 1 | direct allowlist 16 + signal payload family; optional JSON Pointer 2개는 파일 아님 |
|
||||||
|
| 조건부 relocation 계약 | 0 | 1 | 기본 배포에는 요구하지 않음 |
|
||||||
|
|
||||||
|
서로 성격이 다른 범주를 합산한 단일 “총 파일 수”는 사용하지 않는다. 특히 runtime input과 run output은 신규 S2_00 고정 자산 수에 포함하지 않는다.
|
||||||
|
|
||||||
|
## 1. S2_00 전담 생성 파일
|
||||||
|
|
||||||
|
### 1.0 v1 실행·binding 신규 4종
|
||||||
|
|
||||||
|
| 파일·배포 위치 | 성격 | brief 역할 | 조치 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `M/Stage_2_S2_00_v.1.yml` | `AUTHORING_EXECUTABLE_SHAPE / PENDING_SECRET_AND_LIVE_BINDING` | 유일 편집 정본; exactly-one inline `run_code` task | `NEW` |
|
||||||
|
| `R/agent_scripts/Stage_2_S2_00.yml` | `EXACT-OWNED` deployment projection candidate | builder가 authoring 정본에서 생성하는 version-free signed 실행본; canary/production 실행 대상 | `NEW` |
|
||||||
|
| `R/deployment/stage2_code_executor_binding.yml` | `EXACT-SHARED` | MCP server/tool, Python/runtime image와 `httpx==0.28.1`, agent-network/300초, Agent/code/mirror/release hash, egress profile, localdocs tool/path allowlist | `NEW` |
|
||||||
|
| `R/manifest/s2_00_inline_code_receipt.json` | `EXACT-SHARED` | authoring/deployment canonical task semantics, canonical code bytes, full mirror hash·compile·AST parity receipt | `NEW` |
|
||||||
|
|
||||||
|
### 1.1 retained·updated core 4 logical units
|
||||||
|
|
||||||
|
| 번호 | 파일·배포 위치 | 성격 | brief 역할 | 조치 |
|
||||||
|
|---:|---|---|---|---|
|
||||||
|
| 1 | `R/workflows/S2_00_stage1_ingress_normalize_and_bundle_compile.yml` | `EXACT-OWNED` | C00→C05→C10→C15 IO·route·binary localdocs·status-last barrier 계약 | `UPDATE` |
|
||||||
|
| 2 | `R/runtime/s2_00_ingress.py`와 `.txt` | `EXACT-OWNED` full-code parity oracle | unique-key parser가 반환한 전체 YAML `parameters.code` 문자열을 변경 없이 UTF-8 encode한 byte-identical mirror; runtime read/import 금지 | `UPDATE` |
|
||||||
|
| 3 | `R/schemas/ingress.schema.json` | `EXACT-OWNED` | source contract, two-pass binary snapshot receipt, input/intake/diagnostic/logical barrier/run binding | `UPDATE` |
|
||||||
|
| 4 | `R/schemas/context.schema.json` | `EXACT-OWNED` | case/evidence/object/party-title/slot/cluster/slice/bundle 의미계약 | `REUSE` |
|
||||||
|
|
||||||
|
### 1.1A build-only projection asset
|
||||||
|
|
||||||
|
| 파일·배포 위치 | 성격 | brief 역할 | 조치 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `R/offline_build/build_s2_00_inline_projection.py`와 `.txt` | `BUILD_ONLY` | authoring YAML을 읽어 version-free projection→전체 code mirror→receipt만 생성하고 hash/compile/parity를 검증; authoring YAML 재작성·사건 runtime 실행 금지 | `NEW` |
|
||||||
|
|
||||||
|
### 1.2 명명된 test source 7 logical axes
|
||||||
|
|
||||||
|
| 번호 | 파일·배포 위치 | 성격 | brief 역할 | 조치 |
|
||||||
|
|---:|---|---|---|---|
|
||||||
|
| 5 | `R/tests/s2_00/test_resolver_source_lock.py`와 `.txt` | `EXACT-OWNED` | localdocs binary hydration, workspace/path isolation, bounded two-pass hash stability, strict parser/source lock | `UPDATE` |
|
||||||
|
| 6 | `R/tests/s2_00/test_schema_hash_and_signals.py`와 `.txt` | `EXACT-OWNED` | schema/hash, signal file·record 이중 보존식, routing/signal dual SG-01 시험 | `REUSE` |
|
||||||
|
| 7 | `R/tests/s2_00/test_conservation.py`와 `.txt` | `EXACT-OWNED` | BO·Fact Ledger·LES·evidence·event·signal·review multiset과 join 보존 시험 | `REUSE` |
|
||||||
|
| 8 | `R/tests/s2_00/test_canonical_ids.py`와 `.txt` | `EXACT-OWNED` | Stage 1 ID 보존, party/object occurrence ID, review ID, Unicode와 collision 시험 | `REUSE` |
|
||||||
|
| 9 | `R/tests/s2_00/test_cluster_bundle_compile.py`와 `.txt` | `EXACT-OWNED` | explicit-relation cluster, SCC/DAG, slice, cache prefix와 executable cohort 시험 | `REUSE` |
|
||||||
|
| 10 | `R/tests/s2_00/test_failure_and_atomic_publish.py`와 `.txt` | `EXACT-OWNED` | affected-scope route, retry/idempotence, partial write와 status-last barrier | `UPDATE` |
|
||||||
|
| 11 | `R/tests/s2_00/test_inline_code_parity.py`와 `.txt` | `EXACT-OWNED` | YAML code extraction·compile·hash·mirror parity, placeholder/외부 `.py`/plaintext credential 0 | `NEW` |
|
||||||
|
|
||||||
|
각 `.py` test는 동일 내용의 `.txt` mirror를 유지한다. test file을 통합하려면 `module_manifest.json`, `regression_manifest.json`과 본 inventory를 동시에 개정한다.
|
||||||
|
|
||||||
|
### 1.3 fixture family
|
||||||
|
|
||||||
|
| 파일·배포 위치 | 성격 | brief 역할 | exact set 결정 방식 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `R/tests/fixtures/cases/<fixture_id>.json` | `FIXED-FAMILY` | 정상·경계 사건의 source locator, raw hash, expected artifact·issue·route·digest를 제공 | `regression_manifest.json`이 path/hash를 전수 봉인 |
|
||||||
|
| `R/tests/fixtures/mutations/<mutation_id>.json` | `FIXED-FAMILY` | 단일 결함을 주입하여 invariant와 expected reason code를 검증 | `regression_manifest.json`이 path/hash를 전수 봉인 |
|
||||||
|
|
||||||
|
## 2. 공유 계약·executor·handoff 고정 파일
|
||||||
|
|
||||||
|
| 번호 | 파일·배포 위치 | 성격 | brief 역할 | owner·S2_00 경계 |
|
||||||
|
|---:|---|---|---|---|
|
||||||
|
| 1 | `R/manifest/module_manifest.json` | `EXACT-SHARED` | 법률/data/workflow-contract/schema/test/profile closure; release hash를 내장하는 Agent/full mirror는 executor binding/receipt가 단독 결속하여 hash cycle 방지 | release build owner; `UPDATE` |
|
||||||
|
| 2 | `R/manifest/stage2_release.json` | `EXACT-SHARED` | Stage 1 dependency lock, executor binding, module/authority/corpus/coverage digest, admission | release operator; `UPDATE` |
|
||||||
|
| 3 | `R/deployment/stage2_code_executor_binding.yml` | `EXACT-SHARED` | S2_00 Agent/code/tool/localdocs allowlist exact binding | release operator; §1.0과 같은 physical file |
|
||||||
|
| 4 | `R/manifest/s2_00_inline_code_receipt.json` | `EXACT-SHARED` | unique-key parser가 반환한 code 문자열의 변경 없는 UTF-8 bytes, full mirror equality·compile/AST와 pure-core 별도 hash scope 증거 | release build owner; §1.0과 같은 physical file |
|
||||||
|
| 5 | `R/schemas/deployment.schema.json` | `EXACT-SHARED` | executor binding, outer result, inner receipt, logical publish receipt `$defs` | deployment schema owner; `UPDATE` |
|
||||||
|
| 6 | `R/schemas/review_status.schema.json` | `EXACT-SHARED` | `issue_ledger.base`, review normalization, impact scope와 downstream action을 검증 | review schema owner |
|
||||||
|
| 7 | `R/registry/cache/prompt_cache_policy.yml` | `EXACT-SHARED` | PII-free static prefix, canonical hash, key, cache miss와 release-integrity failure를 구분 | cache policy owner |
|
||||||
|
| 8 | `R/tests/fixtures/regression_manifest.json` | `EXACT-SHARED` | case/mutation fixture의 exact path/hash/seed/expected digest를 봉인 | regression owner |
|
||||||
|
| 9 | `R/workflows/S2_10_domain_relief_resolution_map.yml` | `EXACT-SHARED` | `executable_cluster_ids[]`가 지정한 immutable slice/bundle만 소비하도록 handoff를 고정 | S2_10 owner; raw Stage 1 재탐색 금지 |
|
||||||
|
| 10 | `R/workflows/S2_40_final_review_render_and_commit.yml` | `EXACT-SHARED` | diagnostic branch에서 S2_00의 정확한 5개 artifact만 받는 status-only entry를 제공 | S2_40 owner; final status single writer |
|
||||||
|
|
||||||
|
다음 두 파일은 물리적으로 보존하되 active S2_00 runtime closure에서는 제외한다.
|
||||||
|
|
||||||
|
| 파일 | disposition | 이유 |
|
||||||
|
|---|---|---|
|
||||||
|
| `R/release_ops/stage2_loader.py`와 `.txt` | `REFERENCE_ONLY_SUPERSEDED_FOR_S2_00` | host subprocess loader를 사용하지 않음 |
|
||||||
|
| `R/deployment/stage2_loader_binding.yml` | `REFERENCE_ONLY_SUPERSEDED_FOR_S2_00` | 새 executor binding이 active authority |
|
||||||
|
|
||||||
|
## 3. S2_10 bundle을 위해 S2_00이 검증할 고정 자산
|
||||||
|
|
||||||
|
S2_00은 아래 자산으로 법률판단을 수행하지 않는다. canary/production bundle의 static prefix와 active context subset을 선정하고 path/hash/PII/release closure만 검증한다.
|
||||||
|
|
||||||
|
따라서 아래 49개는 물리적 planned catalog 수이지 사건별 bundle 수가 아니다. 사건별 closure는 `P00 + P10 + authority registry/release + N_active_profiles + N_selected_captures`이며, release가 선택하지 않은 profile·capture를 bundle에 넣지 않는다.
|
||||||
|
|
||||||
|
### 3.1 static prompt·authority 4개와 capture family
|
||||||
|
|
||||||
|
| 번호 | 파일·배포 위치 | 성격 | brief 역할 | S2_00 처리 |
|
||||||
|
|---:|---|---|---|---|
|
||||||
|
| 1 | `R/prompts/P00_system_and_safety_contract.md` | `FIXED-RELEASE` | 공통 role, source hierarchy, provenance, security와 금지규칙 static prefix | canonical hash·PII 검사 후 bundle ref만 작성 |
|
||||||
|
| 2 | `R/prompts/P10_legal_resolution_contract.md` | `FIXED-RELEASE` | S2_10 판단 순서, typed output contract와 self-check static prefix | canonical hash·PII 검사 후 bundle ref만 작성 |
|
||||||
|
| 3 | `R/registry/authority/authority_registry.yml` | `FIXED-RELEASE` | 승인된 proposition, temporal scope, official locator와 후속 취급의 정본 | active common-authority row만 선택·봉인 |
|
||||||
|
| 4 | `R/manifest/authority_release.json` | `FIXED-RELEASE` | authority registry, capture와 law-value dependency의 exact set/hash seal | signed release와 cross-hash 검사 |
|
||||||
|
| family | `R/authority/source_captures/<authority_id>.json` | `FIXED-FAMILY` | 공식 원문 locator, 원문/발췌 hash, 시행·선고일과 temporal metadata | authority release가 선정한 capture만 검증; glob 금지 |
|
||||||
|
|
||||||
|
`P00` 또는 `P10`이 없는 단계에서는 사건 run을 executable로 표시하지 않고 `STRUCTURAL_FIXTURE`만 허용한다.
|
||||||
|
|
||||||
|
다음 두 law-value 파일은 모든 사건 bundle에 일괄 삽입하는 자산이 아니다. `authority_release.json`이 해당 사건의 approved common-authority ref가 사용하는 token을 exact dependency로 열거할 때만 hash closure를 검증하고 필요한 row ref만 bundle에 결속한다.
|
||||||
|
|
||||||
|
| 파일·배포 위치 | 성격 | brief 역할 | S2_00 처리 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `R/law_values/general_law_values.yml` | `CONDITIONAL` | 이율·기간·상한 등 output-affecting authority-bound token | authority release에 참조된 exact token/file hash만 검증; 전체 row ingest 금지 |
|
||||||
|
| `R/law_values/court_fee_values.yml` | `CONDITIONAL` | 소가·인지·송달비용의 시점별 authority-bound token | S2_10 static bundle이 참조할 때만 결속; 후단 전용이면 availability 범위에 머묾 |
|
||||||
|
|
||||||
|
### 3.2 substantive profile 22개
|
||||||
|
|
||||||
|
| 번호 | 파일·배포 위치 | brief 역할 |
|
||||||
|
|---:|---|---|
|
||||||
|
| 1 | `R/registry/substantive/EC-00_contract_general.yml` | 계약 공통 성립·효력·이행·해제·손해배상 context |
|
||||||
|
| 2 | `R/registry/substantive/E-01_juristic_act_validity.yml` | 법률행위 무효·취소·추인·원상회복 context |
|
||||||
|
| 3 | `R/registry/substantive/E-02_contract_money_claim.yml` | 대금·대여금 등 계약상 금전채권 context |
|
||||||
|
| 4 | `R/registry/substantive/E-03_parties_liability_succession.yml` | 보증·연대·승계·상호속용 등 책임주체 context |
|
||||||
|
| 5 | `R/registry/substantive/E-04_unjust_enrichment.yml` | 부당이득 유형·법률상 원인·반환범위 context |
|
||||||
|
| 6 | `R/registry/substantive/E-05_tort_general.yml` | 일반 불법행위·인과관계·손해·과실상계 context |
|
||||||
|
| 7 | `R/registry/substantive/E-06_professional_liability.yml` | 전문직 위임·주의의무·손해 context |
|
||||||
|
| 8 | `R/registry/substantive/E-07_construction_defect.yml` | 공사대금·추가공사·하자·감액 context |
|
||||||
|
| 9 | `R/registry/substantive/E-08_lease_deposit.yml` | 임대차보증금·명도·공제·동시이행 context |
|
||||||
|
| 10 | `R/registry/substantive/E-09_registry_transfer_claims.yml` | 소유권이전·말소·회복·경정 등기 context |
|
||||||
|
| 11 | `R/registry/substantive/E-10_secured_registry.yml` | 근저당·전세권·담보권 등기·말소 context |
|
||||||
|
| 12 | `R/registry/substantive/E-11_possession_vindication.yml` | 인도·명도·방해배제·점유 context |
|
||||||
|
| 13 | `R/registry/substantive/E-12_co_ownership_boundary.yml` | 공유물분할·경계·지분 관계 context |
|
||||||
|
| 14 | `R/registry/substantive/E-13_creditor_preservation.yml` | 채권자대위·사해행위취소·보전 context |
|
||||||
|
| 15 | `R/registry/substantive/E-14_execution_linked_claims.yml` | 배당·청구이의·집행 관련 본안 context |
|
||||||
|
| 16 | `R/registry/substantive/E-15_succession_family_property.yml` | 상속·유류분·상속재산 귀속 context |
|
||||||
|
| 17 | `R/registry/substantive/E-16_negotiable_instruments.yml` | 어음·수표 권리·항변 context |
|
||||||
|
| 18 | `R/registry/substantive/E-17_labor_wage_claims.yml` | 임금·수당·퇴직금 context |
|
||||||
|
| 19 | `R/registry/substantive/E-18_org_resolution_status.yml` | 회사·조합 결의·지위·청산 context |
|
||||||
|
| 20 | `R/registry/substantive/E-19_insurance_claims.yml` | 보험금·면책·대위·중복보험 context |
|
||||||
|
| 21 | `R/registry/substantive/E-20_ip_claims.yml` | 지식재산 침해·금지·손해배상 context |
|
||||||
|
| 22 | `R/registry/substantive/E-21_media_personality_rights.yml` | 명예·인격권·보도 관련 구제 context |
|
||||||
|
|
||||||
|
### 3.3 crosscut profile 5개
|
||||||
|
|
||||||
|
| 번호 | 파일·배포 위치 | brief 역할 |
|
||||||
|
|---:|---|---|
|
||||||
|
| 1 | `R/profiles/crosscut/E-00_residual_unrouted.yml` | 기존 profile로 routing되지 않는 이슈를 보존하고 review로 전달 |
|
||||||
|
| 2 | `R/profiles/crosscut/X1_notice_lifecycle.yml` | 최고·통지·해제·송달의 actor·도달·효과 lifecycle |
|
||||||
|
| 3 | `R/profiles/crosscut/X2_asset_identity_lineage.yml` | 목적물 동일성·변동·등기·점유 lineage |
|
||||||
|
| 4 | `R/profiles/crosscut/X3_procedure_standing_relief.yml` | 당사자적격·소의 이익·필수당사자·구제 적합성 |
|
||||||
|
| 5 | `R/profiles/crosscut/X4_response_admission_defense.yml` | 항변·재항변·불리한 사실·자백위험·선진술 |
|
||||||
|
|
||||||
|
### 3.4 special-law profile 13개
|
||||||
|
|
||||||
|
| 번호 | 파일·배포 위치 | brief 역할 |
|
||||||
|
|---:|---|---|
|
||||||
|
| 1 | `R/profiles/special_law/SL-AUTO_motor_vehicle.yml` | 자동차손해 책임·보험·과실 context |
|
||||||
|
| 2 | `R/profiles/special_law/SL-INDUSTRIAL_ACCIDENT.yml` | 산업재해·보상·손해 조정 context |
|
||||||
|
| 3 | `R/profiles/special_law/SL-PRODUCT_LIABILITY.yml` | 제조물책임 요건·면책·손해 context |
|
||||||
|
| 4 | `R/profiles/special_law/SL-RESIDENTIAL_LEASE.yml` | 주택임대차 대항력·우선변제·승계 context |
|
||||||
|
| 5 | `R/profiles/special_law/SL-COMMERCIAL_LEASE.yml` | 상가임대차 대항력·갱신·권리금 context |
|
||||||
|
| 6 | `R/profiles/special_law/SL-LABOR.yml` | 노동관계 강행규정·지위·기간 context |
|
||||||
|
| 7 | `R/profiles/special_law/SL-STATE_LIABILITY.yml` | 국가배상 특별요건·공법경계·review trigger |
|
||||||
|
| 8 | `R/profiles/special_law/SL-IP-PATENT.yml` | 특허·실용신안 권리범위·침해·구제 context |
|
||||||
|
| 9 | `R/profiles/special_law/SL-IP-COPYRIGHT.yml` | 저작권·저작인격권 침해·구제 context |
|
||||||
|
| 10 | `R/profiles/special_law/SL-IP-OTHER.yml` | 상표·디자인·부정경쟁·영업비밀 context |
|
||||||
|
| 11 | `R/profiles/special_law/SL-MEDIA.yml` | 정정·반론·추후보도와 명예구제 context |
|
||||||
|
| 12 | `R/profiles/special_law/SL-TRANSPORT_MARITIME.yml` | 운송·해상 책임기간·면책·책임제한 context |
|
||||||
|
| 13 | `R/profiles/special_law/SL-CONSUMER_CONTRACT.yml` | 소비자성·거래방식·철회·무효·환급 context |
|
||||||
|
|
||||||
|
### 3.5 ACTIO overlay 5개
|
||||||
|
|
||||||
|
| 번호 | 파일·배포 위치 | brief 역할 |
|
||||||
|
|---:|---|---|
|
||||||
|
| 1 | `R/profiles/overlays/ACTIO-MORTGAGE.yml` | 기존 담보부 목적물 양도·실채무·공동담보 overlay |
|
||||||
|
| 2 | `R/profiles/overlays/ACTIO-MORTGAGE-CREATION.yml` | 사해적 담보권 설정·배당 route overlay |
|
||||||
|
| 3 | `R/profiles/overlays/ACTIO-ENCUMBERED-TRANSFER.yml` | 부담부 소유권이전·가액배상 overlay |
|
||||||
|
| 4 | `R/profiles/overlays/ACTIO-PRESERVED-CLAIM-BUNDLE.yml` | 복수 피보전채권·이자·변제·담보부족 overlay |
|
||||||
|
| 5 | `R/profiles/overlays/ACTIO-DEFENSE-MAP.yml` | 충분담보·상계·수익자·전득자 선의·재항변 overlay |
|
||||||
|
|
||||||
|
45개 profile은 모두 `FIXED-RELEASE`·`LLM-CONTEXT` 자산이다. S2_00은 전체를 사건 bundle에 넣지 않고 Stage 1 activation·release manifest가 지정한 active subset만 exact path/hash로 결속한다.
|
||||||
|
|
||||||
|
### 3.6 후단 availability만 확인할 manifest 2개
|
||||||
|
|
||||||
|
| 파일·배포 위치 | 성격 | brief 역할 | S2_00 경계 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `R/manifest/case_type_coverage.json` | `EXACT-SHARED` | 137 row별 rule·renderer·corpus·profile·계산·review coverage와 sign-off | digest/availability만 확인; catalog·rule 본문 ingest 금지 |
|
||||||
|
| `R/manifest/corpus_release.json` | `EXACT-SHARED` | Weaviate collection/schema/snapshot/chunk/crosswalk의 exact set/hash seal | digest/availability만 확인; raw corpus ingest 금지 |
|
||||||
|
|
||||||
|
## 4. Stage 1 배포 계약 source
|
||||||
|
|
||||||
|
### 4.1 exact deployment file 3개
|
||||||
|
|
||||||
|
| 번호 | 파일·배포 위치 | 성격 | brief 역할 | 처리 |
|
||||||
|
|---:|---|---|---|---|
|
||||||
|
| 1 | `D/runtime_manifest.json` | `UPSTREAM-DEPLOYMENT` | Stage 1 배포 asset의 path/hash integrity closure | referenced closure만 read-only 검증; historical status를 사건 상태로 복사 금지 |
|
||||||
|
| 2 | `D/domains/_registry_index.json` | `UPSTREAM-DEPLOYMENT` | domain ID, config path와 config hash의 정본 index | expected/active set과 비교 |
|
||||||
|
| 3 | `D/signals/signal_registry.v2.json` | `UPSTREAM-DEPLOYMENT` | SG-01~SG-13 filename, schema와 consumer registry | signal manifest coverage와 비교 |
|
||||||
|
|
||||||
|
### 4.2 현행 domain config closure 26개
|
||||||
|
|
||||||
|
아래는 현재 `_registry_index.json`이 열거하는 concrete path다. 모두 `UPSTREAM-DEPLOYMENT`이며, element/opposing/defense slot, calculation binding와 emitted signal 계약을 제공한다.
|
||||||
|
|
||||||
|
| 번호 | 파일·배포 위치 | brief 역할 |
|
||||||
|
|---:|---|---|
|
||||||
|
| 1 | `D/domains/E-00/domain_config.json` | residual/unrouted domain slot·signal 계약 |
|
||||||
|
| 2 | `D/domains/E-01/domain_config.json` | 법률행위 유효성 domain 계약 |
|
||||||
|
| 3 | `D/domains/E-02/domain_config.json` | 계약상 금전채권 domain 계약 |
|
||||||
|
| 4 | `D/domains/E-03/domain_config.json` | 당사자 책임·승계 domain 계약 |
|
||||||
|
| 5 | `D/domains/E-04/domain_config.json` | 부당이득 domain 계약 |
|
||||||
|
| 6 | `D/domains/E-05/domain_config.json` | 일반 불법행위 domain 계약 |
|
||||||
|
| 7 | `D/domains/E-06/domain_config.json` | 전문직 책임 domain 계약 |
|
||||||
|
| 8 | `D/domains/E-07/domain_config.json` | 공사·하자 domain 계약 |
|
||||||
|
| 9 | `D/domains/E-08/domain_config.json` | 임대차·보증금 domain 계약 |
|
||||||
|
| 10 | `D/domains/E-09/domain_config.json` | 소유권·등기 domain 계약 |
|
||||||
|
| 11 | `D/domains/E-10/domain_config.json` | 담보·제한물권 등기 domain 계약 |
|
||||||
|
| 12 | `D/domains/E-11/domain_config.json` | 점유·인도·명도 domain 계약 |
|
||||||
|
| 13 | `D/domains/E-12/domain_config.json` | 공유·경계 domain 계약 |
|
||||||
|
| 14 | `D/domains/E-13/domain_config.json` | 채권자 보전·사해행위취소 domain 계약 |
|
||||||
|
| 15 | `D/domains/E-14/domain_config.json` | 집행 연계 domain 계약 |
|
||||||
|
| 16 | `D/domains/E-15/domain_config.json` | 상속·가족재산 domain 계약 |
|
||||||
|
| 17 | `D/domains/E-16/domain_config.json` | 어음·수표 domain 계약 |
|
||||||
|
| 18 | `D/domains/E-17/domain_config.json` | 임금·퇴직금 domain 계약 |
|
||||||
|
| 19 | `D/domains/E-18/domain_config.json` | 회사·조합 결의·지위 domain 계약 |
|
||||||
|
| 20 | `D/domains/E-19/domain_config.json` | 보험 domain 계약 |
|
||||||
|
| 21 | `D/domains/E-20/domain_config.json` | 지식재산 domain 계약 |
|
||||||
|
| 22 | `D/domains/E-21/domain_config.json` | 언론·인격권 domain 계약 |
|
||||||
|
| 23 | `D/domains/EC-00/domain_config.json` | 계약 공통 lifecycle domain 계약 |
|
||||||
|
| 24 | `D/domains/X1/domain_config.json` | 통지·최고 lifecycle crosscut 계약 |
|
||||||
|
| 25 | `D/domains/X2/domain_config.json` | 목적물 identity/lineage crosscut 계약 |
|
||||||
|
| 26 | `D/domains/X3/domain_config.json` | 절차·당사자적격·구제 crosscut 계약 |
|
||||||
|
|
||||||
|
### 4.3 현행 platform schema closure 9개
|
||||||
|
|
||||||
|
| 번호 | 파일·배포 위치 | S2_00 관련성 | brief 역할 |
|
||||||
|
|---:|---|---|---|
|
||||||
|
| 1 | `D/platform/schemas/client_goal_domain_profiles.schema.json` | direct projection anchor | `client_goal.json` 중 domain-profile projection shape; 전체 client-goal schema로 오인 금지 |
|
||||||
|
| 2 | `D/platform/schemas/domain_fanout_plan.schema.json` | build/adapter evidence | activation 이후 domain fanout plan shape |
|
||||||
|
| 3 | `D/platform/schemas/domain_seed_output.schema.v3.json` | build/adapter evidence | domain별 seed output v3 shape |
|
||||||
|
| 4 | `D/platform/schemas/domain_slice.schema.v2.json` | build/adapter evidence | domain slice v2 shape와 source linkage |
|
||||||
|
| 5 | `D/platform/schemas/fact_exception_pack.schema.json` | review/adapter evidence | fact exception·review pack shape |
|
||||||
|
| 6 | `D/platform/schemas/fact_ledger_base.schema.json` | direct input anchor | `Fact_Ledger_base.json`의 typed fact·domain effect·calculation request shape |
|
||||||
|
| 7 | `D/platform/schemas/fact_ledger_candidate_bundle.schema.json` | build/adapter evidence | final ledger 전 candidate bundle shape |
|
||||||
|
| 8 | `D/platform/schemas/legal_effect_structures.schema.json` | direct input anchor | `legal_effect_structures.json`의 route·BO reverse index·calculation request shape |
|
||||||
|
| 9 | `D/platform/schemas/structure_seed_bundle.schema.json` | build/adapter evidence | LES 생성 전 structure-seed bundle shape |
|
||||||
|
|
||||||
|
`build/adapter evidence` schema는 사건별 파일을 별도로 allowlist에 추가한다는 뜻이 아니다. direct input의 현행 producer lineage와 adapter를 검증하기 위한 배포 closure다.
|
||||||
|
|
||||||
|
### 4.4 현행 signal-support schema closure 17개
|
||||||
|
|
||||||
|
| 번호 | 파일·배포 위치 | brief 역할 |
|
||||||
|
|---:|---|---|
|
||||||
|
| 1 | `D/signals/_common/evidence_slot_status.schema.json` | signal evidence-slot status 공통 enum·shape |
|
||||||
|
| 2 | `D/signals/_common/signal_item.schema.json` | SG record 공통 identity·source·detail shape |
|
||||||
|
| 3 | `D/signals/schemas/domain_activation_manifest.schema.json` | SG-01 activation compatibility payload shape |
|
||||||
|
| 4 | `D/signals/schemas/procedural_posture_relief_signals.schema.json` | SG-02 절차·구제 signal shape |
|
||||||
|
| 5 | `D/signals/schemas/party_capacity_standing_signals.schema.json` | SG-03 당사자능력·적격 signal shape |
|
||||||
|
| 6 | `D/signals/schemas/governing_law_version_signals.schema.json` | SG-04 준거법·법령버전 signal shape |
|
||||||
|
| 7 | `D/signals/schemas/legal_relation_lifecycle_signals.schema.json` | SG-05 법률관계 lifecycle signal shape |
|
||||||
|
| 8 | `D/signals/schemas/timeline_notice_condition_signals.schema.json` | SG-06 기한·최고·통지·조건 signal shape |
|
||||||
|
| 9 | `D/signals/schemas/asset_right_state_signals.schema.json` | SG-07 자산·권리상태 signal shape |
|
||||||
|
| 10 | `D/signals/schemas/liability_causation_damage_signals.schema.json` | SG-08 책임·인과관계·손해 signal shape |
|
||||||
|
| 11 | `D/signals/schemas/defense_exception_signals.schema.json` | SG-09 항변·예외 signal shape |
|
||||||
|
| 12 | `D/signals/schemas/evidence_proof_conflict_signals.schema.json` | SG-10 증거·증명·충돌 signal shape |
|
||||||
|
| 13 | `D/signals/schemas/calculation_requirements.schema.json` | SG-11 계산 필요사항 signal shape |
|
||||||
|
| 14 | `D/signals/schemas/remedy_enforcement_signals.schema.json` | SG-12 구제·집행 signal shape |
|
||||||
|
| 15 | `D/signals/schemas/legal_effect_routes.schema.json` | SG-13 법률효과 route signal shape |
|
||||||
|
| 16 | `D/signals/schemas/domain_signal_envelope.schema.v2.json` | active domain별 signal envelope v2 shape |
|
||||||
|
| 17 | `D/signals/schemas/signal_manifest.schema.json` | signal transaction, `files[]`, downstream read-set shape |
|
||||||
|
|
||||||
|
§4.2~§4.4의 52개 path와 §4.1의 3개 anchor가 현행 55-file closure다. 이는 2026-08-29 snapshot의 명시적 inventory이며 영구적인 glob 계약이 아니다. Phase S00-0에서 `_registry_index.json`, `runtime_manifest.json`, `signal_registry.v2.json`이 지정하는 각 path/hash/schema ID를 `stage2_release.json` dependency lock에 다시 materialize한다. release에서 빠진 파일을 directory scan으로 보충하거나 `*.json` glob으로 읽어서는 안 된다.
|
||||||
|
|
||||||
|
## 5. Stage 1 runtime input allowlist
|
||||||
|
|
||||||
|
아래 파일은 S2_00이 새로 생성하는 고정 자산이 아니다. 다만 logical path와 producer contract가 고정되어 있으므로 구현 allowlist에 반드시 포함한다.
|
||||||
|
|
||||||
|
| 번호 | logical 파일·위치 | 성격 | producer | brief 역할·처분 경계 |
|
||||||
|
|---:|---|---|---|---|
|
||||||
|
| 1 | `U/evidence_indexed.json` | `RUNTIME-LOCATOR` | Stage 1 Part 1 | evidence universe와 provenance; gap은 affected scope로 보존 |
|
||||||
|
| 2 | `U/evidence_event_candidates.json` | `RUNTIME-LOCATOR` | Stage 1 Part 1 | 이행기·최고·도달·해제·제공·거절 event 후보 |
|
||||||
|
| 3 | `U/client_goal.json` | `RUNTIME-LOCATOR` | Stage 1 Part 1 Task A | 의뢰인 목표·제약·당사자·회수정보; 부재를 청구권 부존재로 해석 금지 |
|
||||||
|
| 4 | `U/routing/domain_screening.json` | `RUNTIME-LOCATOR` | Stage 1 Part 1 | screening hash와 activation lineage anchor |
|
||||||
|
| 5 | `U/routing/domain_activation_manifest.json` | `RUNTIME-LOCATOR` | Stage 1 Part 1 D0 | canonical expected/active domain과 config refs |
|
||||||
|
| 6 | `U/quality_gates/B1_evidence_indexed_gate.json` | `RUNTIME-LOCATOR` | Stage 1 Part 1 | evidence-index gate와 assertion 제한 근거 |
|
||||||
|
| 7 | `U/quality_gates/B2_event_candidates_gate.json` | `RUNTIME-LOCATOR` | Stage 1 Part 1 | event-candidate gate와 assertion 제한 근거 |
|
||||||
|
| 8 | `U/quality_gates/stage1_part1_soft_gate_handoff.json` | `RUNTIME-LOCATOR` | Stage 1 Part 1 | flat seven-key digest guard와 review handoff |
|
||||||
|
| 9 | `U/BO.json` | `RUNTIME-LOCATOR` | Stage 1 Part 2 | BO identity backbone과 source occurrence |
|
||||||
|
| 10 | `U/signals/signal_manifest.json` | `RUNTIME-LOCATOR` | Stage 1 Part 2 | signal transaction, `files[]`와 Stage 2 `ALL` read-set 정본 |
|
||||||
|
| 11 | `U/quality_gates/stage1_part2_review_handoff.json` | `RUNTIME-LOCATOR` | Stage 1 Part 2 | flat review universe; 존재하지 않는 공통 count field 요구 금지 |
|
||||||
|
| 12 | `U/legal_effect_structures.json` | `RUNTIME-LOCATOR` | Stage 1 Part 3 | LES domain/type/route, BO reverse index와 calculation request |
|
||||||
|
| 13 | `U/quality_gates/stage1_part3_review_handoff.json` | `RUNTIME-LOCATOR` | Stage 1 Part 3 | wrapper-aware LES review handoff |
|
||||||
|
| 14 | `U/Fact_Ledger_base.json` | `RUNTIME-LOCATOR` | Stage 1 Part 4 | `fact_id`·`source_bo_id` anchor; current-v8 row의 `domain_effects`·`calculation_requests` 필수 |
|
||||||
|
| 15 | `U/stage1_tmp/fact_ledger/fact_ledger_writer_report.json` | `RUNTIME-LOCATOR` | Stage 1 Part 4 | ledger hash, conservation, gate firing와 calculation readiness receipt |
|
||||||
|
| 16 | `U/quality_gates/stage1_part4_review_handoff.json` | `RUNTIME-LOCATOR` | Stage 1 Part 4 | wrapper-aware final review handoff |
|
||||||
|
| family | `U/signals/<signal_manifest.files[i].path>` | `RUNTIME-LOCATOR` | Stage 1 Part 2 signal compiler | manifest가 열거한 canonical/domain/compatibility payload; `U/signals/` prefix를 붙여 읽음 |
|
||||||
|
|
||||||
|
signal family에는 `U/signals/domain_activation_manifest.json` SG-01이 포함된다. 이는 `U/routing/domain_activation_manifest.json`과 raw-byte 동일 파일이 아니므로 각각의 raw hash를 보존하고 승인된 semantic projection만 비교한다.
|
||||||
|
|
||||||
|
다음 둘은 별도 파일이 아니라 `client_goal.json` 내부 optional field다.
|
||||||
|
|
||||||
|
| JSON Pointer | brief 역할 | 금지사항 |
|
||||||
|
|---|---|---|
|
||||||
|
| `U/client_goal.json#/defendant_target_matrix` | 피고별 제소 유지·제외 의사와 현재 회수위험을 분리 | `defendant_target_matrix.json` 생성·탐색 금지 |
|
||||||
|
| `U/client_goal.json#/parties/defendants/*/asset_status` | 피고별 현재 재산·집행 관련 사실을 보존 | `asset_status.json` 생성·탐색 금지 |
|
||||||
|
|
||||||
|
## 6. 조건부 relocation 계약
|
||||||
|
|
||||||
|
| 파일·ref | 성격 | brief 역할 | 사용 조건 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `stage2_release.json#/dependency_locks/stage1/contract_manifest_ref → <exact path>/stage1_contract_manifest.json` | `CONDITIONAL` | 승인된 logical input의 logical→physical path·schema·producer·raw hash mapping | 기본 exact path와 다른 배포에서만 release operator가 제공 |
|
||||||
|
|
||||||
|
이 파일은 Stage 1 제5 production task의 산출물이 아니며 기본 run input도 아니다. 새 logical artifact kind를 추가하거나 directory scan·basename fallback을 허용할 수 없다.
|
||||||
|
|
||||||
|
## 7. 별도 파일 경로가 아직 닫히지 않은 고정 계약
|
||||||
|
|
||||||
|
아래 항목은 SOW가 구현 전에 고정하도록 요구하지만 독립 physical file을 새로 만들라고 지시하지 않는다. 기존 named file의 row·`$defs`·runtime adapter로 귀속해야 하며, 별도 파일이 필요하면 먼저 SOW와 inventory를 개정한다.
|
||||||
|
|
||||||
|
| 계약 | 우선 귀속할 기존 파일 | brief 역할·미결사항 |
|
||||||
|
|---|---|---|
|
||||||
|
| `ALL` expansion·dual SG-01 projection rule | `ingress.schema.json`, `s2_00_ingress.py`, `stage2_release.json` | exact field set과 adapter version을 Phase S00-0에서 봉인 |
|
||||||
|
| P1~P4 review normalization mapping | `review_status.schema.json`, `s2_00_ingress.py` | source status→normalized status의 closed mapping과 `UNMAPPED` 처리 |
|
||||||
|
| BO/evidence/event producer adapter | `ingress.schema.json`, `s2_00_ingress.py` | accepted wrapper/shape와 schema gap을 versioned adapter로 고정 |
|
||||||
|
| domain config v1/v2 adapter | `ingress.schema.json`, `context.schema.json`, `s2_00_ingress.py` | undeclared slot과 config-version 처리 |
|
||||||
|
| Fact Ledger current-v8/legacy 경계 | `ingress.schema.json`, `s2_00_ingress.py` | current-v8 두 확장키는 필수; legacy는 별도 adapter가 있을 때만 지원 |
|
||||||
|
| `retry_policy_id` closed policy | S2_00 workflow row와 `stage2_release.json` | 허용 횟수·backoff·retryable code의 canonical owner/JSON Pointer 확정 필요 |
|
||||||
|
| approved producer-alias table | `module_manifest.json` 또는 `stage2_release.json` | schema writer const와 orchestration producer label의 signed closed mapping 필요 |
|
||||||
|
| canary/production signed admission | `stage2_release.json#/release_evidence[]` | release class·scope·signer·input digest를 봉인 |
|
||||||
|
| Code Executor secret injection·live receipt | `stage2_code_executor_binding.yml`, `deployment.schema.json`, `s2_00_inline_code_receipt.json` | plaintext 없이 AgentBackend가 `run_code`를 호출하고 outer/inner receipt를 검증하는 증거 필요 |
|
||||||
|
| executor timeout/request-size | `stage2_code_executor_binding.yml`, `regression_manifest.json` | representative S2_00가 300초와 request-size 상한 안에서 끝나는 live benchmark 필요 |
|
||||||
|
| external release trust anchor | `Stage_2_S2_00.yml` code constant, `stage2_code_executor_binding.yml`, outer receipt | hash-admitted Agent가 `expected_stage2_release_sha256`를 보유하고 backend가 Agent/code/release digest를 workspace 밖 trust root로 검증 |
|
||||||
|
| runtime/dependency pin | `stage2_code_executor_binding.yml`, `s2_00_inline_code_receipt.json` | `httpx==0.28.1`과 Python/runtime image digest를 결속; image pin 미지원 시 live gate 미통과 |
|
||||||
|
| localdocs logical publish | `ingress.schema.json`, `test_failure_and_atomic_publish.py` | status-last barrier, read-back hash, partial-write non-consumption을 봉인 |
|
||||||
|
| Stage 1 bounded snapshot stability | `stage2_release.json` dependency lock와 `ingress.schema.json` | 현행 allowlist 전체를 최대 2회 binary read하여 path별 raw-hash map equality를 검사; 신규 completion seal 요구 0 |
|
||||||
|
|
||||||
|
사건별 `stage2_control/s2_00_request.json`은 `schema_version`, `workflow_id=S2_00`, `request_id`, `attempt_id`, `stage1_run_root_ref`, `stage1_deployment_root_ref`를 전달하는 runtime input이다. 고정 배포 자산 수에 포함하지 않으며 inline code는 이 exact path 외 request 파일을 탐색하지 않는다. canonical output root는 request가 아니라 `run_binding_digest`에서 파생한다.
|
||||||
|
|
||||||
|
## 8. 설계·감사용 고정 provenance
|
||||||
|
|
||||||
|
아래 파일은 S2_00 계약의 근거를 확인하거나 구현을 감사할 때 사용한다. 배포 runtime이 import·실행·prompt/context로 소비하는 의존성은 아니다.
|
||||||
|
|
||||||
|
| 파일·위치 | brief 역할 | runtime 경계 |
|
||||||
|
|---|---|---|
|
||||||
|
| `M/S_00_SOW_v.1.md` | S2_00 범위·IO·inline execution·불변식·시험의 직접 작업명세 | build/audit only; runtime read 금지 |
|
||||||
|
| `M/stage_2_optimal_update_strategy_v.5.md` | Stage 2 전체 DAG와 Code Executor 실행경계 | design only |
|
||||||
|
| `M/stage_2_optimal_update_strategy_v.5_workflow.svg` | v5 direct-MCP·named-barrier workflow의 사람이 읽는 시각화 | documentation only |
|
||||||
|
| `M/assets_for_stage_2_v.4.md` | 전체 Stage 2 자산/source 계획과 소유권 위치 | planning/audit only |
|
||||||
|
| `P/YAML_Prompts/1. Stage_1/v.8/stage_1_part_1_v.8.yml` | Part 1 producer·logical path·handoff 계약 조사 근거 | runtime prompt/context로 사용 금지 |
|
||||||
|
| `P/YAML_Prompts/1. Stage_1/v.8/stage_1_part_2_v.8.yml` | Part 2 BO·signal producer 계약 조사 근거 | runtime prompt/context로 사용 금지 |
|
||||||
|
| `P/YAML_Prompts/1. Stage_1/v.8/stage_1_part_3_v.8.yml` | Part 3 LES producer·wrapper 계약 조사 근거 | runtime prompt/context로 사용 금지 |
|
||||||
|
| `P/YAML_Prompts/1. Stage_1/v.8/stage_1_part_4_v.8.yml` | Part 4 Fact Ledger·writer receipt 계약 조사 근거 | runtime prompt/context로 사용 금지 |
|
||||||
|
| `P/YAML_Prompts/1. Stage_1/v.7/extension_research/Default_Agent/signals/compiler/signal_compiler.py` | `ALL`, SG-01, payload path와 manifest 보존식의 구현 조사 근거 | 신규 Stage 2 runtime이 import·호출 금지 |
|
||||||
|
|
||||||
|
## 9. 고정 자산 목록에서 제외할 항목
|
||||||
|
|
||||||
|
| 제외 대상 | 이유 |
|
||||||
|
|---|---|
|
||||||
|
| `O/ingress/*.json`, `O/context/*.json`, `O/review/issue_ledger.base.json` | 사건별 S2_00 run output이며 고정 배포 파일이 아님 |
|
||||||
|
| `O/context/cluster_slices/<cluster_id>.json` | 사건별 cluster 수에 따라 생기는 동적 output family |
|
||||||
|
| Code Executor temporary hydration tree | task 내부 transient data이며 localdocs 고정 자산·published output이 아님 |
|
||||||
|
| `O/control/run_status.json` | S2_40이 단독 작성하는 사건별 상태 파일 |
|
||||||
|
| `R/prompts/P30_joint_drafting_contract.md` | S2_30 전용 prompt이며 S2_00 bundle 직접 의존이 아님 |
|
||||||
|
| `R/rules/relief/<case_type_id>.md`, raw corpus 전체 | S2_20/S2_30 후단 자산; S2_00이 직접 ingest하거나 bundle에 복제하는 것이 금지됨 |
|
||||||
|
| calculation·renderer·relief-rule 본문 | S2_00은 `stage2_release`를 통한 downstream availability만 확인 |
|
||||||
|
| §8의 Stage 1 v.8 Part 1~4 YAML 본문 | producer/path/shape 조사용 design provenance이며 사건 runtime context가 아님 |
|
||||||
|
| §8의 Stage 1 signal compiler source | `ALL`·SG-01 계약 감사용 provenance이며 신규 runtime이 import·실행하지 않음 |
|
||||||
|
| 구 Stage 2 v.0~v.3 YAML·prompt·loader·중간산출물 | runtime dependency와 fallback이 명시적으로 금지됨 |
|
||||||
|
|
||||||
|
## 10. 생성·결속 순서
|
||||||
|
|
||||||
|
1. §1 core schema와 fixture golden shape를 먼저 작성한다.
|
||||||
|
2. §4~§6의 Stage 1 exact producer/path/schema contract를 실측하고 §7의 미결 계약을 기존 named file에 귀속한다.
|
||||||
|
3. inline full code/localdocs adapter와 test 7축을 구현하고 authoring YAML의 code를 compile한다.
|
||||||
|
4. projection builder는 version-free Agent projection, full mirror, inline-code receipt만 생성한다. 그 결과 hash를 받은 release build owner가 executor binding과 module/release/regression manifest를 별도로 exact-hash 결속한다.
|
||||||
|
5. offline fixture를 통과한 후 backend secret·request-size·300초·binary byte equality를 live DEV smoke로 검증한다.
|
||||||
|
6. P00/P10·active profile·authority closure가 있는 signed canary에서만 executable bundle handoff를 시험한다.
|
||||||
|
7. S2_10 immutable-slice handoff와 S2_40 diagnostic 5-input handoff를 각각 검증한다.
|
||||||
|
|
||||||
|
이 순서가 끝나기 전에는 파일이 directory에 존재한다는 사실만으로 S2_00을 implementation-complete, executable 또는 production-ready라고 선언하지 않는다.
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+1585
File diff suppressed because it is too large
Load Diff
+79
@@ -0,0 +1,79 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1440" height="1040" viewBox="0 0 1440 1040" role="img" aria-labelledby="title desc">
|
||||||
|
<title id="title">Stage 2 v5 detailed workflow DAG</title>
|
||||||
|
<desc id="desc">Stage 1 current outputs and distributed integrity receipts are independently verified by S2_00, then flow through three inline Code Executor deterministic stages and two LLM stages. Each deterministic stage publishes only after its named status-last logical barrier.</desc>
|
||||||
|
<defs>
|
||||||
|
<marker id="arrow" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
|
||||||
|
<polygon points="0 0, 10 3.5, 0 7" fill="#334155"/>
|
||||||
|
</marker>
|
||||||
|
<style>
|
||||||
|
.title{font:700 28px -apple-system,BlinkMacSystemFont,"Apple SD Gothic Neo",sans-serif;fill:#0f172a}
|
||||||
|
.sub{font:500 15px -apple-system,BlinkMacSystemFont,"Apple SD Gothic Neo",sans-serif;fill:#475569}
|
||||||
|
.h{font:700 18px -apple-system,BlinkMacSystemFont,"Apple SD Gothic Neo",sans-serif;fill:#0f172a}
|
||||||
|
.t{font:500 14px -apple-system,BlinkMacSystemFont,"Apple SD Gothic Neo",sans-serif;fill:#1e293b}
|
||||||
|
.small{font:500 12px -apple-system,BlinkMacSystemFont,"Apple SD Gothic Neo",sans-serif;fill:#475569}
|
||||||
|
.det{fill:#e0f2fe;stroke:#0284c7;stroke-width:2}
|
||||||
|
.llm{fill:#f3e8ff;stroke:#9333ea;stroke-width:2}
|
||||||
|
.io{fill:#f8fafc;stroke:#64748b;stroke-width:1.5}
|
||||||
|
.review{fill:#fff7ed;stroke:#ea580c;stroke-width:2}
|
||||||
|
.edge{fill:none;stroke:#334155;stroke-width:2;marker-end:url(#arrow)}
|
||||||
|
.dash{fill:none;stroke:#64748b;stroke-width:1.5;stroke-dasharray:7 6;marker-end:url(#arrow)}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<text x="720" y="45" text-anchor="middle" class="title">Stage 2 v5 — Direct MCP Inline Code Executor DAG</text>
|
||||||
|
<text x="720" y="72" text-anchor="middle" class="sub">외부 loader/.py runtime dependency 0 · deterministic stage별 exactly-one run_code · final status/barrier last</text>
|
||||||
|
|
||||||
|
<rect x="470" y="95" width="500" height="78" rx="12" class="io"/>
|
||||||
|
<text x="720" y="125" text-anchor="middle" class="h">Stage 1 current outputs</text>
|
||||||
|
<text x="720" y="150" text-anchor="middle" class="t">16 direct inputs + signal family + release-bound deployment closure</text>
|
||||||
|
|
||||||
|
<path d="M720 173 L720 210" class="edge"/>
|
||||||
|
<rect x="330" y="210" width="780" height="135" rx="14" class="det"/>
|
||||||
|
<text x="720" y="239" text-anchor="middle" class="h">S2_00 — deterministic ingress (one code-executor.run_code)</text>
|
||||||
|
<text x="720" y="265" text-anchor="middle" class="t">localdocs binary two-pass stability → C00 source lock → C05 conservation</text>
|
||||||
|
<text x="720" y="289" text-anchor="middle" class="t">C10 context/identity → C15 claim-neutral cluster & bundle plan</text>
|
||||||
|
<text x="720" y="317" text-anchor="middle" class="small">non-status writes → read-back hash verification → ingress_status.json last</text>
|
||||||
|
|
||||||
|
<path d="M720 345 L720 382" class="edge"/>
|
||||||
|
<rect x="330" y="382" width="780" height="105" rx="14" class="llm"/>
|
||||||
|
<text x="720" y="412" text-anchor="middle" class="h">S2_10 — LLM legal resolution map</text>
|
||||||
|
<text x="720" y="438" text-anchor="middle" class="t">immutable cluster slice + active legal profiles + authority excerpt</text>
|
||||||
|
<text x="720" y="462" text-anchor="middle" class="small">typed claim options and normalized signatures; case name alone never activates a claim</text>
|
||||||
|
|
||||||
|
<path d="M720 487 L720 524" class="edge"/>
|
||||||
|
<rect x="250" y="524" width="940" height="155" rx="14" class="det"/>
|
||||||
|
<text x="720" y="554" text-anchor="middle" class="h">S2_20 — deterministic reduce & freeze (one code-executor.run_code)</text>
|
||||||
|
<text x="720" y="580" text-anchor="middle" class="t">option reduce · calculations · compatibility · recovery deduplication</text>
|
||||||
|
<text x="720" y="604" text-anchor="middle" class="t">case-type predicate → case_type_id → sub-rule predicate → sub_rule_id</text>
|
||||||
|
<text x="720" y="628" text-anchor="middle" class="t">release-bound read-only Weaviate retrieval → approved requirement-fact pack</text>
|
||||||
|
<text x="720" y="653" text-anchor="middle" class="small">freeze plan and context; plan/plan_publish_status.json last</text>
|
||||||
|
|
||||||
|
<path d="M720 679 L720 716" class="edge"/>
|
||||||
|
<rect x="330" y="716" width="780" height="105" rx="14" class="llm"/>
|
||||||
|
<text x="720" y="746" text-anchor="middle" class="h">S2_30 — LLM joint drafting</text>
|
||||||
|
<text x="720" y="772" text-anchor="middle" class="t">group-parallel relief/cause/worknote atoms from frozen plan and selected context</text>
|
||||||
|
<text x="720" y="796" text-anchor="middle" class="small">no new claim · no arithmetic · no commit</text>
|
||||||
|
|
||||||
|
<path d="M720 821 L720 858" class="edge"/>
|
||||||
|
<rect x="250" y="858" width="940" height="132" rx="14" class="det"/>
|
||||||
|
<text x="720" y="888" text-anchor="middle" class="h">S2_40 — deterministic review/render/commit (one code-executor.run_code)</text>
|
||||||
|
<text x="720" y="914" text-anchor="middle" class="t">part reduce → closed render → V01–V18 → review receipt/seal</text>
|
||||||
|
<text x="720" y="938" text-anchor="middle" class="t">content-addressed write → read-back → control/run_status.json last</text>
|
||||||
|
<text x="720" y="963" text-anchor="middle" class="small">final status single writer; partial writes are never consumable</text>
|
||||||
|
|
||||||
|
<rect x="35" y="228" width="245" height="100" rx="12" class="review"/>
|
||||||
|
<text x="157" y="258" text-anchor="middle" class="h">S2_00 impossible</text>
|
||||||
|
<text x="157" y="284" text-anchor="middle" class="t">technical diagnostic only</text>
|
||||||
|
<text x="157" y="307" text-anchor="middle" class="small">route to S2_40 status-only</text>
|
||||||
|
<path d="M330 278 L280 278" class="dash"/>
|
||||||
|
<path d="M157 328 L157 924 L250 924" class="dash"/>
|
||||||
|
|
||||||
|
<rect x="1215" y="550" width="190" height="100" rx="12" class="io"/>
|
||||||
|
<text x="1310" y="580" text-anchor="middle" class="h">Weaviate</text>
|
||||||
|
<text x="1310" y="606" text-anchor="middle" class="t">release-bound tenant</text>
|
||||||
|
<text x="1310" y="629" text-anchor="middle" class="small">read-only exact filter</text>
|
||||||
|
<path d="M1215 600 L1190 600" class="dash"/>
|
||||||
|
|
||||||
|
<text x="720" y="1021" text-anchor="middle" class="small">Offline compile/parity does not establish live secret binding, request-size/timeout admission, legal sign-off, or production readiness.</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 6.1 KiB |
@@ -0,0 +1,142 @@
|
|||||||
|
# Stage 2 v5 MCP Code Executor 전환
|
||||||
|
|
||||||
|
## Objective
|
||||||
|
|
||||||
|
Stage 2 v4의 5-workflow 법률·IO 구조를 보존하면서 deterministic task의 실행 방식을
|
||||||
|
외부 native `.py` adapter(O-06)에서 AgentBackend가 이미 지원하는 MCP Code Executor
|
||||||
|
inline Python으로 전환한다. `stage_2_optimal_update_strategy_v.5.md`,
|
||||||
|
`S_00_SOW_v.1.md`, `S2_00_assets_v.1.md`, `Stage_2_S2_00_v.1.yml`과 필요한
|
||||||
|
`Default_Agent/Stage_2_Clean/` 자산을 일관되게 생성·개정한다.
|
||||||
|
|
||||||
|
## Deliverables
|
||||||
|
|
||||||
|
- `YAML_Prompts/2. Stage_2/stage_2_optimal_update_strategy_v.5.md`
|
||||||
|
- `YAML_Prompts/2. Stage_2/S_00_SOW_v.1.md`
|
||||||
|
- `YAML_Prompts/2. Stage_2/S2_00_assets_v.1.md`
|
||||||
|
- `YAML_Prompts/2. Stage_2/Stage_2_S2_00_v.1.yml`
|
||||||
|
- 위 inventory가 지정하는 version-free `Default_Agent/Stage_2_Clean/` 개정·신규 자산
|
||||||
|
- `YAML_Prompts/2. Stage_2/MEMORY.md` 압축 기록
|
||||||
|
|
||||||
|
## Scope and Non-Scope
|
||||||
|
|
||||||
|
- 범위: S2_00 inline deterministic code, localdocs session/격리, release/hash contract,
|
||||||
|
schema·manifest·test·workflow 자산, S2_10/S2_40 handoff 계약.
|
||||||
|
- 비범위: S2_10 이후 LLM 법률판단의 실제 실행, 외부 Code Executor live 호출,
|
||||||
|
Stage 1 live case run, canary/production 서명·법률가 승인.
|
||||||
|
- 기존 v4/SOW/assets/YAML은 변경하지 않고 새 버전 파일을 만든다.
|
||||||
|
- 기존 Stage 2 v.0~v.3 runtime dependency는 계속 0으로 유지한다.
|
||||||
|
|
||||||
|
## Known Inputs
|
||||||
|
|
||||||
|
- `YAML_Prompts/2. Stage_2/stage_2_optimal_update_strategy_v.4.md`
|
||||||
|
- `YAML_Prompts/2. Stage_2/S_00_SOW.md`
|
||||||
|
- `YAML_Prompts/2. Stage_2/S2_00_assets.md`
|
||||||
|
- `YAML_Prompts/2. Stage_2/Stage_2_S2_00.yml`
|
||||||
|
- `YAML_Prompts/2. Stage_2/S2_00_yaml_구현_QA.txt`
|
||||||
|
- `YAML_Prompts/2. Stage_2/SKILL.md`
|
||||||
|
- `YAML_Prompts/2. Stage_2/test_code_executor.ipynb`
|
||||||
|
- `YAML_Prompts/1. Stage_1/v.8/stage_1_part_{1..4}_v.8.yml`
|
||||||
|
- `YAML_Prompts/2. Stage_2/Default_Agent/Stage_2_Clean/`
|
||||||
|
|
||||||
|
## Material Assumptions
|
||||||
|
|
||||||
|
- 사용자의 이번 지시는 기존 SOW의 inline code-executor 기각 결정을 명시적으로
|
||||||
|
대체한다.
|
||||||
|
- AgentBackend가 문서화한 `mcp: code-executor`, `tool_name: run_code` task는
|
||||||
|
실행 가능한 승인 primitive로 취급한다.
|
||||||
|
- localdocs 접근은 `{{__user_hash__}}`와 `{{__workspace_hash__}}`를 포함한
|
||||||
|
`SKILL.md` 전체 boilerplate를 사용한다.
|
||||||
|
- 외부 live MCP 실행은 별도 네트워크·운영 권한과 실제 workspace 입력이 필요하므로
|
||||||
|
이번 완료 조건은 strict static validation과 local/mock regression이다.
|
||||||
|
|
||||||
|
## Questions That Could Change the Outcome
|
||||||
|
|
||||||
|
- inline source를 단일 task에 완전 삽입할지 C00/C05/C10/C15 여러 task로 나눌지는
|
||||||
|
Stage 1 실물 grammar, output 전달 한계와 atomic publish 계약을 대조한 뒤 확정한다.
|
||||||
|
- 기존 `stage2_loader.py`와 `runtime/s2_00_ingress.py`는 runtime dependency에서
|
||||||
|
제거하되, test oracle/migration source로 유지할지 manifest에서 완전히 제외할지
|
||||||
|
자산 delta 조사 후 결정한다.
|
||||||
|
|
||||||
|
## Workstreams and Dependencies
|
||||||
|
|
||||||
|
1. Stage 1 code-executor grammar와 현행 S2_00 계약·자산 delta 조사.
|
||||||
|
2. v5 → SOW_v1/assets_v1 순서의 문서 초안.
|
||||||
|
3. 서로 다른 evaluator 2개를 사용한 문서 평가→main 개정, 정확히 2회.
|
||||||
|
4. inventory 기반 자산 구현과 실행 YAML 구현을 병렬 진행.
|
||||||
|
5. 생성·개정 자산별 독립 검증 정확히 1회; YAML 독립 검증→개정 정확히 2회.
|
||||||
|
6. syntax/schema/mock/regression/hash/경로/금지 edge 검증과 MEMORY 기록.
|
||||||
|
|
||||||
|
## Source and Tool Plan
|
||||||
|
|
||||||
|
- 로컬 정본과 실제 Stage 1 YAML을 우선한다.
|
||||||
|
- `rg`, strict YAML/JSON parser, AST compile, existing unittest, mock localdocs harness를
|
||||||
|
사용한다.
|
||||||
|
- 파일 편집은 `apply_patch`만 사용한다.
|
||||||
|
- sub-agent는 독립 조사·평가·자산별 검증에만 사용하고 최종 설계 병합은 main이 한다.
|
||||||
|
|
||||||
|
## Validation Plan
|
||||||
|
|
||||||
|
- YAML duplicate-key parse와 AgentBackend grammar/task DAG 확인
|
||||||
|
- 모든 code-executor task의 `language/python`, full code, timeout, network,
|
||||||
|
user/workspace binding, stdout single-JSON 확인
|
||||||
|
- inline Python 추출 후 AST compile 및 금지 import/call 검사
|
||||||
|
- localdocs mock을 통한 read/write/overwrite/path/NFC/SSE/Extra-data 처리 검사
|
||||||
|
- S2_00 16+1 ingress, C00→C05→C10→C15, normal 12/diagnostic 5,
|
||||||
|
route mutual exclusion, S2_40 single-writer 보존
|
||||||
|
- manifest/schema/hash closure와 구 v.0~v.3 dependency 0 확인
|
||||||
|
- 기존 `tests/s2_00` 및 신규 inline adapter regression 실행
|
||||||
|
|
||||||
|
## Approval Boundaries
|
||||||
|
|
||||||
|
- 요청된 로컬 문서·YAML·Default_Agent 자산만 변경한다.
|
||||||
|
- commit/push, 외부 MCP 호출, production admission·서명은 수행하지 않는다.
|
||||||
|
- dirty worktree의 무관 변경은 건드리지 않는다.
|
||||||
|
|
||||||
|
## Progress
|
||||||
|
|
||||||
|
- [x] MEMORY·AGENTS·PLANS·SKILL·notebook 확인
|
||||||
|
- [ ] Stage 1 grammar와 현행 asset delta 조사
|
||||||
|
- [ ] v5/SOW_v1/assets_v1 초안
|
||||||
|
- [ ] 문서 평가·개정 Round 1
|
||||||
|
- [ ] 문서 평가·개정 Round 2
|
||||||
|
- [ ] Default_Agent 자산 구현
|
||||||
|
- [ ] Stage_2_S2_00_v.1 YAML 구현
|
||||||
|
- [ ] 자산 1회·YAML 2회 검증
|
||||||
|
- [ ] 최종 회귀검사·MEMORY
|
||||||
|
|
||||||
|
## Decision Log
|
||||||
|
|
||||||
|
- 2026-08-30: v5 execution TCB is `AgentBackend -> code-executor.run_code -> static inline Python -> user/workspace-bound localdocs`; O-06 native loader is superseded.
|
||||||
|
- 2026-08-30: One Code Executor task owns C00→C05→C10→C15. Splitting is deferred because intermediate artifacts would enlarge transport and weaken one-receipt semantics.
|
||||||
|
- 2026-08-30: The current filesystem core is retained as a test/parity oracle only. Runtime execution must not read/import `runtime/s2_00_ingress.py` or call `stage2_loader.py`.
|
||||||
|
- 2026-08-30: localdocs lacks a documented atomic directory rename contract. v1 uses byte write/read-back plus `ingress_status.json` last as a logical commit barrier.
|
||||||
|
- 2026-08-30: New YAML must not duplicate the plaintext bearer credential found in historical Stage 1/notebook sources. Live status remains pending backend secret binding.
|
||||||
|
- 2026-08-30: Timeout target is 300 seconds, subject to live representative benchmark and request-size admission.
|
||||||
|
|
||||||
|
| Date/Stage | Decision | Basis | Consequence |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 2026-08-30 / 착수 | O-06 native adapter 방식을 MCP Code Executor inline task로 대체 | 사용자 명시적 목표와 `SKILL.md`의 지원 grammar | v5가 이전 SOW의 inline-code 기각을 명시적으로 supersede해야 함 |
|
||||||
|
| 2026-08-30 / 착수 | 외부 live 실행과 production-ready 주장은 완료 범위에서 제외 | 실제 user workspace와 운영 Code Executor admission 증거 부재 | static/mock 검증과 live E2E 경계를 문서마다 명시 |
|
||||||
|
|
||||||
|
## Evidence Ledger
|
||||||
|
|
||||||
|
| Claim/Issue | Source or Test | Status | Notes |
|
||||||
|
|---|---|---|---|
|
||||||
|
| AgentBackend inline task grammar | `SKILL.md` §3.3 | 확인 | `mcp: code-executor`, `tool_name: run_code`, `parameters.code` |
|
||||||
|
| workspace 격리 | `SKILL.md` §5·§5.2 | 확인 | hash template 포함 full localdocs init 필요 |
|
||||||
|
| prior inline rejection | `S_00_SOW.md` §14 | superseded 예정 | 사용자 지시로 v1에서 대체 |
|
||||||
|
| current YAML non-executable | `Stage_2_S2_00.yml` | 확인 | `tasks: []`, O-06 OPEN |
|
||||||
|
| fixed runtime 규모 | `runtime/s2_00_ingress.py` | 확인 | 5,134행; inline 구조 최적화 필요 |
|
||||||
|
|
||||||
|
## Risks and Failure Modes
|
||||||
|
|
||||||
|
- YAML source와 기존 `.py`가 이중 정본이 되는 drift
|
||||||
|
- code-executor가 local filesystem을 공유한다고 잘못 가정하는 오류
|
||||||
|
- localdocs text transport가 raw-byte digest 의미를 바꾸는 오류
|
||||||
|
- YAML parser/template renderer가 Python braces를 오인하는 오류
|
||||||
|
- 인라인 코드가 너무 커져 AgentBackend payload·timeout 한도를 넘는 위험
|
||||||
|
- mock PASS를 live Code Executor E2E로 과장하는 위험
|
||||||
|
|
||||||
|
## Results and Residual Uncertainty
|
||||||
|
|
||||||
|
작업 완료 시 파일 수·hash·검증 결과와 live 경계를 기록한다.
|
||||||
Reference in New Issue
Block a user