feat(stage2): prepare S2_00 request before ingress
Add the fixed six-field request writer and two-task S2_00 projection. Refresh bound release metadata, regression coverage, and the S2_00 analysis; backend argument binding and live admission remain pending.
This commit is contained in:
+103
@@ -0,0 +1,103 @@
|
||||
# Stage 2 - S2_00 분석
|
||||
|
||||
## Executive Summary
|
||||
|
||||
현행 배포본 `Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00.yml`은 Stage 1 Part 1–4의 사건 자료를 받아 S2_10용 claim-neutral 문맥과 전달 계획을 만드는 비 LLM ingress 명세다. `Agent.name`은 `Stage_2_S2_00_v2`, 버전은 `1.2.0`이다. 실제 Agent task는 **request 준비**와 **deterministic ingress** 두 개이며, C00·C05·C10·C15는 두 번째 task의 inline Python 안에서 수행되는 논리 단계다. 두 task 모두 `code-executor.run_code`를 사용하고 localdocs로 파일을 읽고 쓴다.
|
||||
|
||||
첫 task의 `prepare_request(...)` 함수는 외부 문자열 인자 네 개를 정확히 6필드의 `s2_00_request.json`으로 만들어 고정 경로에 저장하고 read-back을 확인하도록 구현되어 있다. 그러나 배포 YAML에는 backend가 그 네 인자를 함수에 주입하는 실행 결속이 없다. 현재 inline 코드의 직접 실행 진입점은 `PREPARE_ARGUMENT_BINDING_UNVERIFIED`를 출력하고 종료 코드 2로 실패한다. 따라서 이 분석서의 DAG와 산출물은 **구현된 함수 및 선언된 계약**을 설명하며, 현재 배포본의 사건별 실실행 성공을 의미하지 않는다. 더구나 현재 release는 `DEV_FIXTURE_RELEASE / DRAFT_NOT_EXECUTABLE`이고 ingress가 DEV fixture의 실제 사건 발행을 거부한다.
|
||||
|
||||
## 전체 작업 DAG와 책임 경계
|
||||
|
||||
```text
|
||||
외부 실행자: request_id, attempt_id, stage1_run_root_ref,
|
||||
stage1_deployment_root_ref 제공; workspace 호출·직렬화 책임
|
||||
│
|
||||
▼
|
||||
IN → Task_S2_00_prepare_request → Task_S2_00_deterministic_ingress → OUT
|
||||
│ 6필드 request 생성·검증 │ 고정 request/Stage 1/release·자산 hydration
|
||||
│ localdocs write/read-back ├─ C00: 입력 계약·출처/무결성 검증
|
||||
▼ ├─ C05: review 항목 정규화·보존 검사
|
||||
W/stage2_control/s2_00_request.json ├─ C10: 사건·증거·당사자·slot 문맥 구성
|
||||
└─ C15: bundle/route·결과 발행
|
||||
→ O/ingress/ingress_status.json 마지막 기록
|
||||
```
|
||||
|
||||
YAML의 `task_procedure`는 `IN → prepare → ingress → OUT`의 `nexts`/`wait_until` 순서를 선언한다. `Stages[0].prevs`와 `nexts`는 빈 배열이므로 다른 Stage Agent로의 호출은 이 YAML 안에 없다. ingress 내부의 C00–C15는 별도 MCP task나 각각의 저장 파일이 아니다. 준비 task가 성공해야 ingress가 시작되는 **backend success-only gate**, 그리고 동일 workspace의 고정 request 경로에 대한 **다른 실행의 덮어쓰기 차단**은 workflow·deployment 계약에서 모두 `UNVERIFIED`다.
|
||||
|
||||
여기서 `W/`는 localdocs의 사용자·workspace 논리 루트, `A/`는 `W/Default_Agent/Stage_2_Clean/`, `U/`는 `W/<stage1_run_root_ref>/`, `D/`는 `W/<stage1_deployment_root_ref>/`, `O/`는 `W/stage2_runs/by-binding/<run_binding_digest>/`를 뜻한다. 이들은 Dropbox 저장소의 물리 경로가 아니다. 배포 저장소의 대응 패키지 루트는 `Default_Agent/Stage_2_Clean/`이다. `<...>` 값은 사건·release·실행 입력으로 결정되며 현재 자료에서 특정 파일명으로 확정할 수 없다.
|
||||
|
||||
## 실제 실행단위와 prompt 구성
|
||||
|
||||
| 실제 Agent task | 호출·코드 위치 | 하는 일 | LLM prompt |
|
||||
|---|---|---|---|
|
||||
| `Task_S2_00_prepare_request` | `code-executor.run_code`; YAML `/Agent/Stages/0/tasks/0/parameters/code` | 네 인자를 검사하고 6필드 canonical JSON을 `W/stage2_control/s2_00_request.json`에 쓰고 동일 바이트를 다시 읽어 확인한다. | 없음 |
|
||||
| `Task_S2_00_deterministic_ingress` | `code-executor.run_code`; YAML `/Agent/Stages/0/tasks/1/parameters/code` | request·release·Stage 1 자료·잠금 자산을 읽고 C00–C15를 한 번의 Python 실행에서 처리한 뒤 결과를 발행한다. | 없음 |
|
||||
|
||||
두 task의 파라미터는 `language: python`, `requirements: httpx==0.28.1`, `network: agent-network`, `timeout: 300`이다. `localdocs` 서버는 `http://mcp-localdocs:8012/mcp`, `code-executor` 서버는 `https://code-executor.mcp.eroomai.com/mcp`로 선언되어 있다. YAML의 `description`과 주석은 실행 명세이며 system/user prompt, prompt template, 모델 호출 구성이 아니다. `run_code`에 전달되는 실제 본문은 각 task의 `parameters.code`에 내장된 Python이다. `.py`/`.txt` mirror는 빌드·검증용 대응본이지 런타임의 추가 import가 아니다.
|
||||
|
||||
첫 task의 `build_request`와 `prepare_request`는 호출 가능한 함수로 존재한다. ID 형식, NFC 상대경로, 정확한 필드 집합을 확인하고 UTF-8 canonical JSON과 끝의 LF를 만든다. localdocs `write_binary_file(overwrite=true)` 뒤 `read_binary_doc`를 비교하며 성공 시 SHA-256 등을 함수 반환값으로 제공한다. 다만 `__main__`은 외부 인자 결속이 없음을 명시적으로 실패 처리한다. ingress의 직접 진입점은 `run_inline_mcp()`이며 localdocs에서 필요한 바이너리를 읽어 code-executor 임시 디렉터리에 hydrate하고 core를 실행한다. 입력은 두 차례 읽어 바이트 일치를 검사하고, 원격 발행은 일반 산출물을 먼저 write/read-back한 다음 `ingress/ingress_status.json`을 마지막에 쓴다.
|
||||
|
||||
## In & Out 설명
|
||||
|
||||
모든 `.json` 입력·출력은 JSON 파일이다. `s2_00_request.json`과 발행 결과는 canonical UTF-8 JSON이며, 개별 Stage 1 원본의 바이트·스키마 검증 규칙은 release와 ingress 구현이 지배한다. 아래 `A/`, `U/`, `D/`, `O/` 표기는 앞 절의 **localdocs 논리 경로**다.
|
||||
|
||||
| 단계 | Input: 정확한 이름·형식·source | 역할 |
|
||||
|---|---|---|
|
||||
| prepare | `request_id`, `attempt_id`, `stage1_run_root_ref`, `stage1_deployment_root_ref`: 외부 문자열 인자, 파일 아님 | ID와 Stage 1 사건·배포 루트를 결속할 값. backend 공급 방식은 미검증. |
|
||||
| ingress | `W/stage2_control/s2_00_request.json`: JSON, 정확히 `schema_version`, `workflow_id`, `request_id`, `attempt_id`, `stage1_run_root_ref`, `stage1_deployment_root_ref` 6필드 | prepare의 고정 출력. 상수값은 `stage2_s2_00_execution_request.v1` 및 `S2_00`. |
|
||||
| ingress: Stage 1 P1 | `U/evidence_indexed.json`, `U/evidence_event_candidates.json`, `U/client_goal.json`, `U/routing/domain_screening.json`, `U/routing/domain_activation_manifest.json`, `U/quality_gates/B1_evidence_indexed_gate.json`, `U/quality_gates/B2_event_candidates_gate.json`, `U/quality_gates/stage1_part1_soft_gate_handoff.json`: 각 JSON | 증거·목표·도메인 routing 및 P1 gate/handoff. |
|
||||
| ingress: Stage 1 P2 | `U/BO.json`, `U/signals/signal_manifest.json`, `U/quality_gates/stage1_part2_review_handoff.json`: 각 JSON | BO·신호 목록·P2 handoff. |
|
||||
| ingress: Stage 1 P3 | `U/legal_effect_structures.json`, `U/quality_gates/stage1_part3_review_handoff.json`: 각 JSON | 법률효과 구조·P3 handoff. |
|
||||
| ingress: Stage 1 P4 | `U/Fact_Ledger_base.json`, `U/stage1_tmp/fact_ledger/fact_ledger_writer_report.json`, `U/quality_gates/stage1_part4_review_handoff.json`: 각 JSON | 사실 ledger·writer report·P4 handoff. |
|
||||
| ingress: 동적 신호 | `U/signals/<signal_manifest.files[i].path>`: 각 JSON | `signal_manifest.json` 행에서 확장하므로 정확한 basename·개수는 사건별 manifest가 있어야 확정된다. `U/routing/domain_activation_manifest.json`과 이름이 비슷한 신호 파일이 있더라도 별도 경로다. |
|
||||
| ingress: Stage 1 배포 | `D/<release.dependency_locks.stage1.concrete_paths[i].path>`: JSON/JSON Schema, 총 55개 고정 경로 | Stage 1 runtime manifest, registry/config/schema를 release의 경로·해시로 결속한다. 정확한 55개 항목은 `A/manifest/stage2_release.json#/dependency_locks/stage1/concrete_paths`가 정의한다. |
|
||||
| ingress: Stage 2 배포 | `A/manifest/stage2_release.json`, `A/manifest/module_manifest.json`, `A/schemas/ingress.schema.json`, `A/schemas/context.schema.json`, `A/schemas/review_status.schema.json` 및 `A/<release.dependency_locks.stage2_direct[i].path>` 49개: JSON·JSON Schema·YAML | release/모듈·출력 검증 기준 및 직접 의존 자산. 아래 고정 자산 표에 배포 경로를 적었다. |
|
||||
|
||||
| 단계·분기 | Output: 정확한 이름·형식·저장 folder | 의미 |
|
||||
|---|---|---|
|
||||
| prepare | `W/stage2_control/s2_00_request.json`: JSON; `W/stage2_control/` | ingress가 소비할 고정 제어 request. 성공 반환 객체는 함수 응답이지 별도 파일이 아니다. |
|
||||
| ingress: 공통 | `O/ingress/stage1_input_manifest.json`, `O/ingress/intake_report.json`, `O/review/issue_ledger.base.json`, `O/ingress/ingress_status.json`: 각 JSON; `O/ingress/`·`O/review/` | 입력 출처·수용 결과·review 이슈·route/barrier. `ingress_status.json`은 마지막에 기록한다. |
|
||||
| ingress: 정상 route | `O/context/case_context.json`, `evidence_inventory.json`, `object_registry.json`, `party_and_title_context.json`, `slot_crosswalk.json`, `cluster_plan.json`, `bundle_plan.json`: 각 JSON; `O/context/` | S2_10용 정규화 문맥과 cluster·bundle 계획. 공통 4개를 합쳐 고정 11개. |
|
||||
| ingress: 정상 route의 가변 파일 | `O/context/cluster_slices/<cluster_id>.json`: JSON; `O/context/cluster_slices/` | 실행 가능 cluster마다 1개. 최종 ID 집합은 해당 사건의 계획으로 정해지므로 정상 출력 총수는 `11 + E`(`E`=실행 가능 cluster 수). |
|
||||
| ingress: 진단 route | `O/ingress/technical_diagnostic.json`: JSON; `O/ingress/` | `TO_S2_40_STATUS_ONLY`일 때 공통 4개와 함께 총 5개. 정상 `context/` 파일은 이 분기에서 생성하지 않는다. |
|
||||
|
||||
ingress는 먼저 code-executor의 임시 `liti-s2-00-*` 디렉터리에 입력 및 `core_output/`을 구성한다. 최종 저장 장소는 위 `O/` localdocs 경로이며, 임시 경로는 배포 산출물 경로가 아니다. `run_binding_digest`는 입력 집합·Stage 2 release·알고리즘·release class 결속으로 정해져 `O/`를 결정한다. `attempt_id`만 바꾼다고 출력 루트가 바뀌는 계약은 아니다. `run_code`의 JSON stdout 결과도 출력 파일과 구별해야 한다.
|
||||
|
||||
## 작업용 고정 자산
|
||||
|
||||
아래 표에서 `A/<상대경로>`는 **배포 시 localdocs의 `Default_Agent/Stage_2_Clean/<상대경로>`**이고, 저장소에도 같은 상대경로의 파일이 있다. 쉼표로 열거한 basename은 앞의 배포 directory와 각각 결합한다. `R/` 표기는 혼동을 피하려고 사용하지 않는다. 런타임 hydration 자산과 빌드·결속 명세는 역할을 구분했다.
|
||||
|
||||
| 정확한 배포 경로 (`A/` 이하) | 형식·개수 | 역할·실행 시점 |
|
||||
|---|---:|---|
|
||||
| `agent_scripts/Stage_2_S2_00.yml` | YAML 1 | 이 Agent의 현행 두 task와 inline code. |
|
||||
| `manifest/stage2_release.json` | JSON 1 | release class·상태, Stage 1 입력 16+동적 항목, Stage 1 배포 잠금 55개, Stage 2 직접 잠금 49개, 알고리즘·출력 계약. ingress에서 읽고 hash 검증. |
|
||||
| `manifest/module_manifest.json` | JSON 1 | 모듈 고정 manifest; ingress hydration 대상. |
|
||||
| `schemas/ingress.schema.json`, `schemas/context.schema.json`, `schemas/review_status.schema.json` | JSON Schema 3 | request/입력·context·review/status 형태의 ingress 검증 기준; ingress hydration 대상. |
|
||||
| `agent_scripts/Stage_2_S2_10.yml`, `deployment/stage2_s2_10_llm_binding.yml`, `registry/authority/authority_registry.yml`, `manifest/authority_release.json` | YAML 3, JSON 1 | Stage 2 직접 잠금의 S2_10 Agent·LLM/authority 결속 4개. ingress가 경로·hash로 확인하나 S2_10 호출 자체는 하지 않는다. |
|
||||
| `registry/substantive/EC-00_contract_general.yml`, `E-01_juristic_act_validity.yml`, `E-02_contract_money_claim.yml`, `E-03_parties_liability_succession.yml`, `E-04_unjust_enrichment.yml`, `E-05_tort_general.yml`, `E-06_professional_liability.yml`, `E-07_construction_defect.yml`, `E-08_lease_deposit.yml`, `E-09_registry_transfer_claims.yml`, `E-10_secured_registry.yml`, `E-11_possession_vindication.yml`, `E-12_co_ownership_boundary.yml`, `E-13_creditor_preservation.yml`, `E-14_execution_linked_claims.yml`, `E-15_succession_family_property.yml`, `E-16_negotiable_instruments.yml`, `E-17_labor_wage_claims.yml`, `E-18_org_resolution_status.yml`, `E-19_insurance_claims.yml`, `E-20_ip_claims.yml`, `E-21_media_personality_rights.yml` | YAML 22 | Stage 2 직접 잠금 substantive registry. 각 basename의 전체 배포 경로는 `A/registry/substantive/<basename>`이다. |
|
||||
| `profiles/crosscut/E-00_residual_unrouted.yml`, `X1_notice_lifecycle.yml`, `X2_asset_identity_lineage.yml`, `X3_procedure_standing_relief.yml`, `X4_response_admission_defense.yml` | YAML 5 | Stage 2 직접 잠금 crosscut profile; 전체 배포 경로는 `A/profiles/crosscut/<basename>`. |
|
||||
| `profiles/special_law/SL-AUTO_motor_vehicle.yml`, `SL-INDUSTRIAL_ACCIDENT.yml`, `SL-PRODUCT_LIABILITY.yml`, `SL-RESIDENTIAL_LEASE.yml`, `SL-COMMERCIAL_LEASE.yml`, `SL-LABOR.yml`, `SL-STATE_LIABILITY.yml`, `SL-IP-PATENT.yml`, `SL-IP-COPYRIGHT.yml`, `SL-IP-OTHER.yml`, `SL-MEDIA.yml`, `SL-TRANSPORT_MARITIME.yml`, `SL-CONSUMER_CONTRACT.yml` | YAML 13 | Stage 2 직접 잠금 특별법 profile; 전체 배포 경로는 `A/profiles/special_law/<basename>`. |
|
||||
| `profiles/overlays/ACTIO-MORTGAGE.yml`, `ACTIO-MORTGAGE-CREATION.yml`, `ACTIO-ENCUMBERED-TRANSFER.yml`, `ACTIO-PRESERVED-CLAIM-BUNDLE.yml`, `ACTIO-DEFENSE-MAP.yml` | YAML 5 | Stage 2 직접 잠금 overlay; 전체 배포 경로는 `A/profiles/overlays/<basename>`. |
|
||||
| `workflows/S2_00_stage1_ingress_normalize_and_bundle_compile.yml` | YAML 1 | 두 task 순서·고정 request·외부 인자와 미검증 backend 항목을 선언하는 workflow 계약; ingress의 사건별 입력 파일은 아님. |
|
||||
| `deployment/stage2_code_executor_binding.yml` | YAML 1 | S2_00 code-executor 결속, task 포인터·자산 hash·localdocs allowlist·live admission 상태를 선언하는 배포 계약. |
|
||||
| `manifest/s2_00_inline_code_receipt.json` | JSON 1 | prepare/ingress inline code와 mirror의 정적 parity receipt. `PASS`는 live 실행 증거가 아니다. |
|
||||
| `runtime/s2_00_prepare_request.py`, `runtime/s2_00_prepare_request.txt`, `runtime/s2_00_ingress.py`, `runtime/s2_00_ingress.txt` | Python 2, text 2 | inline code의 빌드·검증 mirror. YAML 직접 실행은 이 파일을 import하지 않는다. |
|
||||
| `offline_build/build_s2_00_inline_projection.py`, `offline_build/build_s2_00_inline_projection.txt`, `schemas/deployment.schema.json` | Python 1, text 1, JSON Schema 1 | projection 생성·배포 계약 검사에 사용하는 offline 자산. 사건 ingress의 별도 실행 task가 아니다. |
|
||||
|
||||
`release.dependency_locks.stage1.concrete_paths`의 55개 파일은 위 Stage 2 패키지에 복제되는 것이 아니라 `D/`에서 참조되는 Stage 1 배포 자산이다. `stage2_direct`의 49개는 위 표의 S2_10/authority 4개 + substantive 22개 + crosscut 5개 + special_law 13개 + overlays 5개다. 이 결속은 자산 존재·hash 확인과 downstream 선택 가능성을 위한 것이며, 49개 본문 전체가 S2_10 prompt에 들어간다는 뜻은 아니다.
|
||||
|
||||
## Upstream/Downstream 설명
|
||||
|
||||
Upstream은 Stage 1의 P1–P4 사건 산출물 16개, `signal_manifest.json`에서 펼친 추가 신호 파일, Stage 1 배포 잠금 55개 및 네 외부 인자를 제공할 호출자다. 네 인자의 전달과 동일 workspace 동시 실행 제어는 외부 backend/orchestrator 경계에 있다. prepare가 고정 request를 쓰면 ingress가 그것을 다시 읽어 path/ID, release 및 원본 bytes와의 결속을 검증한다. `W/stage2_control/s2_00_request.json`은 Stage 1 고정 handoff 16개에 속하지 않는다.
|
||||
|
||||
Downstream에서 정상 route는 `TO_S2_10` 또는 `TO_S2_10_WITH_ISSUES`이고, 진단 route는 `TO_S2_40_STATUS_ONLY`다. S2_00은 status barrier와 bundle/cluster plan을 발행할 뿐 S2_10·S2_40 Agent를 이 YAML에서 직접 호출하지 않는다. 외부 실행자는 `O/ingress/ingress_status.json`과 해당 분기의 완전한 산출물 집합을 검증해 후속 호출을 조립해야 한다. S2_10용 선택 context·cluster case payload는 후속 handoff의 필드/구성 문제이지 S2_00이 고정 파일명으로 별도 발행하는 파일이라고 간주할 수 없다. 진단 route에는 정상 `context/` 산출물이 없으므로 S2_10으로 보내는 경로가 아니다.
|
||||
|
||||
## 구현과 계약 사이의 확인사항·잔여 한계
|
||||
|
||||
1. **네 인자·실행 환경 결속 부재:** 함수의 생성·검증·저장 구현과 offline 검증은 존재한다. 그러나 현행 task의 `__main__`은 항상 `PREPARE_ARGUMENT_BINDING_UNVERIFIED`로 종료한다. backend가 네 값을 함수에 넘기는 방식과 `{{__user_hash__}}`·`{{__workspace_hash__}}` 치환/인증의 실실행 증거가 없어, 현재 배포 YAML만으로 request 생성 완료나 두-task 실실행을 주장할 수 없다.
|
||||
2. **순서와 배타성 미검증:** `task_procedure.wait_until`은 순서 선언이다. prepare 실패 시 ingress 차단을 backend가 보장하는지, request 저장부터 ingress의 두 차례 읽기와 입력 hydration 완료까지 동일 workspace의 다른 실행이 고정 경로를 덮어쓰지 못하는지는 미확인이다. 두 번 읽어 일치를 검사해도 첫 읽기 전에 이미 덮어쓴 다른 실행의 request를 식별·차단하는 증명은 아니다.
|
||||
3. **현재 release 제한:** 배포 `stage2_release.json`은 `DEV_FIXTURE_RELEASE`와 `DRAFT_NOT_EXECUTABLE`이다. ingress 코드에는 `DEV_FIXTURE_REAL_RUN_FORBIDDEN` 방어가 있어 현재 release로 실제 사건 결과를 발행할 수 없다. bundle은 structural fixture 상태이며 `selected_context_refs`도 비어 있다.
|
||||
4. **발행 원자성의 범위:** 비 status 파일을 원격 write/read-back한 뒤 status를 마지막에 쓰는 *논리적* barrier다. localdocs 전체 디렉터리의 원자적 rename이나 backend의 downstream barrier 소비를 입증하지 않는다. status 기록 후 예외가 생긴 경우 실패 stdout의 `FAILED_NO_BARRIER` 표현만으로 원격 status의 부재를 증명할 수도 없다.
|
||||
5. **프로토콜·검증 경계:** prepare의 localdocs 응답 처리는 JSON `response.json()`에 의존하므로 서버가 SSE만 응답하는 환경과의 호환성은 확인되지 않았다. inline code receipt의 정적 parity, hash 잠금, 함수 수준 시험은 live MCP/backend 연결, Stage 1 실제 자료 완비, S2_10 전달 성공 또는 법률 내용의 타당성 검증을 대신하지 않는다.
|
||||
|
||||
근거 기준: 현행 `agent_scripts/Stage_2_S2_00.yml`의 두 `parameters.code`와 `task_procedure`, `manifest/stage2_release.json`, `workflows/S2_00_stage1_ingress_normalize_and_bundle_compile.yml`, `deployment/stage2_code_executor_binding.yml`, `manifest/s2_00_inline_code_receipt.json`을 대조했다. 과거 분석서나 `Stage_2_S2_00_outdated_10_01.yml`의 단일 task 구조를 현행 구조로 소급하지 않았다.
|
||||
+259
-5
@@ -12,11 +12,12 @@ Agent:
|
||||
release_ref: Default_Agent/Stage_2_Clean/manifest/stage2_release.json
|
||||
workflow_contract_ref: Default_Agent/Stage_2_Clean/workflows/S2_00_stage1_ingress_normalize_and_bundle_compile.yml
|
||||
deployment_binding_ref: Default_Agent/Stage_2_Clean/deployment/stage2_code_executor_binding.yml
|
||||
implementation_status: S2_00_CODE_AND_FIXTURE_OFFLINE_VERIFIED_S2_10_HYBRID_REIMPLEMENTATION_AND_RESEAL_PENDING_LIVE_ADMISSION_PENDING
|
||||
implementation_status: S2_00_PREPARE_OFFLINE_VERIFIED_BACKEND_ARGUMENT_GATE_AND_SERIALIZATION_UNVERIFIED_LIVE_ADMISSION_PENDING
|
||||
Stages:
|
||||
- name: S2_00
|
||||
description: >-
|
||||
단일 Code Executor 호출 안에서 C00, C05, C10, C15를 순차 실행하고
|
||||
첫 Code Executor task에서 고정 request를 준비하고, 다음 ingress
|
||||
task의 한 호출 안에서 C00, C05, C10, C15를 순차 실행한다.
|
||||
localdocs binary IO와 status-last 논리 배리어로 결과를 발행한다.
|
||||
prevs: []
|
||||
nexts: []
|
||||
@@ -29,6 +30,254 @@ Agent:
|
||||
type: streamable-http
|
||||
url: https://code-executor.mcp.eroomai.com/mcp
|
||||
tasks:
|
||||
- task_name: Task_S2_00_prepare_request
|
||||
description: >-
|
||||
네 외부 문자열 인자 request_id, attempt_id, stage1_run_root_ref,
|
||||
stage1_deployment_root_ref를 검증하고 고정 localdocs request 경로에
|
||||
저장·read-back한다. Backend 인자 결속 및 성공 게이트는 미검증이며
|
||||
인자가 결속되지 않은 직접 실행은 실패로 종료한다.
|
||||
mcp: code-executor
|
||||
tool_name: run_code
|
||||
parameters:
|
||||
language: python
|
||||
requirements: "httpx==0.28.1"
|
||||
network: agent-network
|
||||
timeout: 300
|
||||
code: |-
|
||||
#!/usr/bin/env python3
|
||||
"""Prepare the fixed S2_00 control request from four explicit caller values.
|
||||
|
||||
The backend argument-delivery contract is not bound. Direct execution fails
|
||||
closed; callers must invoke ``prepare_request`` with the four named values.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import PurePosixPath
|
||||
import re
|
||||
import sys
|
||||
import unicodedata
|
||||
from typing import Any, Mapping
|
||||
|
||||
|
||||
REQUEST_PATH = "stage2_control/s2_00_request.json"
|
||||
LOCALDOCS_URL = "http://mcp-localdocs:8012/mcp"
|
||||
MCP_PROTOCOL_VERSION = "2025-03-26"
|
||||
INLINE_USER_HASH = "{{__user_hash__}}"
|
||||
INLINE_WORKSPACE_HASH = "{{__workspace_hash__}}"
|
||||
REQUEST_KEYS = frozenset({
|
||||
"schema_version", "workflow_id", "request_id", "attempt_id",
|
||||
"stage1_run_root_ref", "stage1_deployment_root_ref",
|
||||
})
|
||||
ID_RE = re.compile(r"[A-Za-z0-9][A-Za-z0-9._-]{0,127}\Z")
|
||||
|
||||
|
||||
class PrepareError(ValueError):
|
||||
def __init__(self, code: str, message: str) -> None:
|
||||
super().__init__(message)
|
||||
self.code = code
|
||||
|
||||
|
||||
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 _relative_path(value: str, *, code: str) -> str:
|
||||
if not isinstance(value, str) or not value or "\x00" in value or "\\" in value:
|
||||
raise PrepareError(code, "logical path is empty or malformed")
|
||||
if unicodedata.normalize("NFC", value) != value:
|
||||
raise PrepareError(code, "logical path must already be NFC")
|
||||
path = PurePosixPath(value)
|
||||
if path.is_absolute() or any(part in {"", ".", ".."} for part in path.parts):
|
||||
raise PrepareError(code, "logical path must be a contained relative path")
|
||||
rendered = path.as_posix()
|
||||
if rendered != value:
|
||||
raise PrepareError(code, "logical path is not canonical")
|
||||
return rendered
|
||||
|
||||
|
||||
def build_request(
|
||||
request_id: str,
|
||||
attempt_id: str,
|
||||
stage1_run_root_ref: str,
|
||||
stage1_deployment_root_ref: str,
|
||||
) -> bytes:
|
||||
"""Return the exact six-field canonical request; do not access localdocs."""
|
||||
if not isinstance(request_id, str) or ID_RE.fullmatch(request_id) is None:
|
||||
raise PrepareError("REQUEST_ID_INVALID", "request_id contains forbidden characters")
|
||||
if not isinstance(attempt_id, str) or ID_RE.fullmatch(attempt_id) is None:
|
||||
raise PrepareError("ATTEMPT_ID_INVALID", "attempt_id contains forbidden characters")
|
||||
request = {
|
||||
"schema_version": "stage2_s2_00_execution_request.v1",
|
||||
"workflow_id": "S2_00",
|
||||
"request_id": request_id,
|
||||
"attempt_id": attempt_id,
|
||||
"stage1_run_root_ref": _relative_path(
|
||||
stage1_run_root_ref, code="STAGE1_RUN_ROOT_REF_INVALID"),
|
||||
"stage1_deployment_root_ref": _relative_path(
|
||||
stage1_deployment_root_ref, code="STAGE1_DEPLOYMENT_ROOT_REF_INVALID"),
|
||||
}
|
||||
if set(request) != REQUEST_KEYS:
|
||||
raise PrepareError("RUN_REQUEST_CLOSED_SHAPE", "request field set drifted")
|
||||
return canonical_json_bytes(request)
|
||||
|
||||
|
||||
class LocaldocsSession:
|
||||
"""Small localdocs JSON-RPC session with verified binary write/read-back."""
|
||||
|
||||
def __init__(self, user_hash: str, workspace_hash: str, *, client: Any = None) -> None:
|
||||
for name, value in (("user_hash", user_hash), ("workspace_hash", workspace_hash)):
|
||||
if not isinstance(value, str) or re.fullmatch(r"[a-f0-9]{64}", value) is None:
|
||||
raise PrepareError("CONTEXT_HASH_INVALID", f"{name} is not a SHA-256 digest")
|
||||
self.user_hash = user_hash
|
||||
self.workspace_hash = workspace_hash
|
||||
if client is None:
|
||||
try:
|
||||
import httpx
|
||||
except ImportError as exc:
|
||||
raise PrepareError("HTTPX_UNAVAILABLE", "httpx==0.28.1 is required") from exc
|
||||
client = httpx.Client(timeout=60)
|
||||
self.client = client
|
||||
self.headers = {"Content-Type": "application/json", "Accept": "application/json, text/event-stream"}
|
||||
self.session_id: str | None = None
|
||||
self.next_id = 10
|
||||
self.initialized = False
|
||||
|
||||
def close(self) -> None:
|
||||
self.client.close()
|
||||
|
||||
def _post(self, body: Mapping[str, Any], expected_id: int | None) -> Mapping[str, Any] | None:
|
||||
try:
|
||||
response = self.client.post(LOCALDOCS_URL, json=dict(body), headers=dict(self.headers))
|
||||
response.raise_for_status()
|
||||
except Exception as exc:
|
||||
raise PrepareError("MCP_TRANSPORT_ERROR", "localdocs transport failed") from exc
|
||||
session_id = response.headers.get("mcp-session-id")
|
||||
if session_id:
|
||||
if self.session_id is None and expected_id == 1:
|
||||
self.session_id = session_id
|
||||
elif session_id != self.session_id:
|
||||
raise PrepareError("MCP_SESSION_ID_CHANGED", "localdocs session changed")
|
||||
self.headers["mcp-session-id"] = session_id
|
||||
if expected_id is None:
|
||||
return None
|
||||
try:
|
||||
payload = response.json()
|
||||
except Exception as exc:
|
||||
raise PrepareError("MCP_RESPONSE_INVALID", "localdocs response is not JSON") from exc
|
||||
if not isinstance(payload, dict) or payload.get("jsonrpc") != "2.0" or payload.get("id") != expected_id:
|
||||
raise PrepareError("MCP_RESPONSE_INVALID", "localdocs response ID or shape mismatch")
|
||||
if "error" in payload or not isinstance(payload.get("result"), dict):
|
||||
raise PrepareError("MCP_TOOL_ERROR", "localdocs returned an error")
|
||||
return payload["result"]
|
||||
|
||||
def initialize(self) -> None:
|
||||
response = self._post({"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {
|
||||
"protocolVersion": MCP_PROTOCOL_VERSION, "capabilities": {}, "clientInfo": {
|
||||
"name": "liti-s2-00-prepare", "version": "1.0.0",
|
||||
"user_id": self.user_hash, "workspace_id": self.workspace_hash,
|
||||
}}}, 1)
|
||||
if response is None or response.get("protocolVersion") != MCP_PROTOCOL_VERSION or self.session_id is None:
|
||||
raise PrepareError("MCP_INITIALIZE_INVALID", "localdocs initialization failed")
|
||||
self._post({"jsonrpc": "2.0", "method": "notifications/initialized"}, None)
|
||||
self.initialized = True
|
||||
|
||||
def call(self, name: str, arguments: Mapping[str, Any]) -> Mapping[str, Any]:
|
||||
if not self.initialized:
|
||||
raise PrepareError("MCP_NOT_INITIALIZED", "localdocs is not initialized")
|
||||
message_id = self.next_id
|
||||
self.next_id += 1
|
||||
result = self._post({"jsonrpc": "2.0", "id": message_id, "method": "tools/call",
|
||||
"params": {"name": name, "arguments": dict(arguments)}}, message_id)
|
||||
if result is None or result.get("isError") is True:
|
||||
raise PrepareError("MCP_TOOL_ERROR", f"localdocs {name} failed")
|
||||
return result
|
||||
|
||||
def read_binary(self, logical_path: str) -> bytes:
|
||||
path = _relative_path(logical_path, code="LOCALDOCS_READ_PATH_INVALID")
|
||||
result = self.call("read_binary_doc", {"doc_name": path})
|
||||
content = result.get("content")
|
||||
if not isinstance(content, list) or len(content) != 1 or not isinstance(content[0], dict) or content[0].get("type") != "text":
|
||||
raise PrepareError("LOCALDOCS_READ_SHAPE", "invalid binary response")
|
||||
text = content[0].get("text")
|
||||
if not isinstance(text, str):
|
||||
raise PrepareError("LOCALDOCS_READ_SHAPE", "missing binary envelope")
|
||||
try:
|
||||
envelope = json.loads(text)
|
||||
if isinstance(envelope, dict) and "results" in envelope:
|
||||
rows = envelope["results"]
|
||||
if not isinstance(rows, list) or len(rows) != 1 or not isinstance(rows[0], dict):
|
||||
raise ValueError("binary result cardinality mismatch")
|
||||
inner = rows[0].get("content", rows[0].get("text"))
|
||||
envelope = json.loads(inner) if isinstance(inner, str) else inner
|
||||
encoded = envelope["content_base64"]
|
||||
if not isinstance(encoded, str):
|
||||
raise ValueError("binary content is not base64")
|
||||
payload = base64.b64decode(encoded, validate=True)
|
||||
size = envelope.get("byte_length", envelope.get("size"))
|
||||
if size is not None and (not isinstance(size, int) or size != len(payload)):
|
||||
raise ValueError("binary size mismatch")
|
||||
digest = envelope.get("sha256")
|
||||
if digest is not None and digest != hashlib.sha256(payload).hexdigest():
|
||||
raise ValueError("binary hash mismatch")
|
||||
return payload
|
||||
except (ValueError, KeyError, TypeError, base64.binascii.Error) as exc:
|
||||
raise PrepareError("LOCALDOCS_READ_SHAPE", "invalid binary envelope") from exc
|
||||
|
||||
def write_binary_verified(self, logical_path: str, payload: bytes) -> str:
|
||||
path = _relative_path(logical_path, code="LOCALDOCS_WRITE_PATH_INVALID")
|
||||
if path != REQUEST_PATH:
|
||||
raise PrepareError("LOCALDOCS_WRITE_PATH_INVALID", "prepare may write only the fixed request path")
|
||||
result = self.call("write_binary_file", {
|
||||
"path": path, "content_base64": base64.b64encode(payload).decode("ascii"), "overwrite": True,
|
||||
})
|
||||
if not isinstance(result.get("content"), list) or result.get("isError") is True:
|
||||
raise PrepareError("LOCALDOCS_WRITE_FAILED", "localdocs did not acknowledge write")
|
||||
observed = self.read_binary(path)
|
||||
if observed != payload:
|
||||
raise PrepareError("LOCALDOCS_WRITE_READBACK_MISMATCH", "request read-back differs")
|
||||
return hashlib.sha256(observed).hexdigest()
|
||||
|
||||
|
||||
def prepare_request(
|
||||
request_id: str,
|
||||
attempt_id: str,
|
||||
stage1_run_root_ref: str,
|
||||
stage1_deployment_root_ref: str,
|
||||
*,
|
||||
localdocs: Any = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Validate, write, read back, and close an authenticated localdocs session."""
|
||||
payload = build_request(request_id, attempt_id, stage1_run_root_ref, stage1_deployment_root_ref)
|
||||
session = localdocs if localdocs is not None else LocaldocsSession(INLINE_USER_HASH, INLINE_WORKSPACE_HASH)
|
||||
failure: Exception | None = None
|
||||
digest: str | None = None
|
||||
try:
|
||||
session.initialize()
|
||||
digest = session.write_binary_verified(REQUEST_PATH, payload)
|
||||
except Exception as exc:
|
||||
failure = exc
|
||||
try:
|
||||
session.close()
|
||||
except Exception as exc:
|
||||
if failure is None:
|
||||
failure = PrepareError("LOCALDOCS_CLOSE_FAILED", "localdocs session close failed")
|
||||
failure.__cause__ = exc
|
||||
if failure is not None:
|
||||
raise failure
|
||||
return {"ok": True, "workflow_id": "S2_00", "path": REQUEST_PATH,
|
||||
"request_sha256": digest}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.stdout.buffer.write(canonical_json_bytes({
|
||||
"ok": False, "error": {"code": "PREPARE_ARGUMENT_BINDING_UNVERIFIED",
|
||||
"message": "four caller values must be bound by the backend"}}))
|
||||
raise SystemExit(2)
|
||||
- task_name: Task_S2_00_deterministic_ingress
|
||||
description: >-
|
||||
고정 request와 release를 hydration하고 S2_00 pure core를 실행한 뒤
|
||||
@@ -192,7 +441,7 @@ Agent:
|
||||
# literal placeholders in the offline parity mirror and its unit tests.
|
||||
INLINE_USER_HASH = "{{__user_hash__}}"
|
||||
INLINE_WORKSPACE_HASH = "{{__workspace_hash__}}"
|
||||
EXPECTED_STAGE2_RELEASE_SHA256 = "9fa85bb94c5f14f675dcdaa0cf94d06745b21675d97797539ffc14015dcc9f53"
|
||||
EXPECTED_STAGE2_RELEASE_SHA256 = "2363f1166ee8a3ea2b38350cde61fccfc9852a413c6b9f14a6ed6606af15a590"
|
||||
INLINE_REQUEST_PATH = "stage2_control/s2_00_request.json"
|
||||
INLINE_STAGE2_ASSET_ROOT = "Default_Agent/Stage_2_Clean"
|
||||
INLINE_STAGE2_RELEASE_PATH = (
|
||||
@@ -6235,13 +6484,18 @@ Agent:
|
||||
task_procedure:
|
||||
IN:
|
||||
nexts:
|
||||
- Task_S2_00_deterministic_ingress
|
||||
- Task_S2_00_prepare_request
|
||||
wait_until: []
|
||||
Task_S2_00_prepare_request:
|
||||
nexts:
|
||||
- Task_S2_00_deterministic_ingress
|
||||
wait_until:
|
||||
- IN
|
||||
Task_S2_00_deterministic_ingress:
|
||||
nexts:
|
||||
- OUT
|
||||
wait_until:
|
||||
- IN
|
||||
- Task_S2_00_prepare_request
|
||||
OUT:
|
||||
nexts: []
|
||||
wait_until:
|
||||
|
||||
+6248
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -71,7 +71,7 @@ Agent:
|
||||
|
||||
WORKFLOW_ID = "S2_20"
|
||||
ALGORITHM_VERSION = "s2_20_relief_plan/1.0.0"
|
||||
EXPECTED_STAGE2_RELEASE_SHA256 = "9fa85bb94c5f14f675dcdaa0cf94d06745b21675d97797539ffc14015dcc9f53"
|
||||
EXPECTED_STAGE2_RELEASE_SHA256 = "2363f1166ee8a3ea2b38350cde61fccfc9852a413c6b9f14a6ed6606af15a590"
|
||||
LOCALDOCS_URL = "http://mcp-localdocs:8012/mcp"
|
||||
WEAVIATE_MCP_URL = "https://weaviate.eroomai.com/mcp"
|
||||
MCP_PROTOCOL_VERSION = "2025-03-26"
|
||||
|
||||
+1
-1
@@ -73,7 +73,7 @@ Agent:
|
||||
from typing import Any, Mapping
|
||||
|
||||
|
||||
EXPECTED_STAGE2_RELEASE_SHA256 = "9fa85bb94c5f14f675dcdaa0cf94d06745b21675d97797539ffc14015dcc9f53"
|
||||
EXPECTED_STAGE2_RELEASE_SHA256 = "2363f1166ee8a3ea2b38350cde61fccfc9852a413c6b9f14a6ed6606af15a590"
|
||||
LOCALDOCS_URL = "http://mcp-localdocs:8012/mcp"
|
||||
MCP_PROTOCOL_VERSION = "2025-03-26"
|
||||
INLINE_USER_HASH = "{{__user_hash__}}"
|
||||
|
||||
+1
-1
@@ -68,7 +68,7 @@ Agent:
|
||||
|
||||
WORKFLOW_ID = "S2_40"
|
||||
ALGORITHM_VERSION = "s2_40_finalizer/1.0.0"
|
||||
EXPECTED_STAGE2_RELEASE_SHA256 = "9fa85bb94c5f14f675dcdaa0cf94d06745b21675d97797539ffc14015dcc9f53"
|
||||
EXPECTED_STAGE2_RELEASE_SHA256 = "2363f1166ee8a3ea2b38350cde61fccfc9852a413c6b9f14a6ed6606af15a590"
|
||||
LOCALDOCS_URL = "http://mcp-localdocs:8012/mcp"
|
||||
MCP_PROTOCOL_VERSION = "2025-03-26"
|
||||
INLINE_USER_HASH = "{{__user_hash__}}"
|
||||
|
||||
+37
-25
@@ -9,30 +9,42 @@ stage_bindings:
|
||||
agent_script_ref:
|
||||
asset_id: AGENT-S2_00-INLINE
|
||||
path: agent_scripts/Stage_2_S2_00.yml
|
||||
sha256: 94c2744d71d222cfb5cc1b2a0d33a6cda20079439823de431eef378a2fa5c943
|
||||
sha256: 07bc9e235d211f3d4e6886bca2faeeb1f112d525fa29fe111bd7c50c42ff9849
|
||||
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: 3d7ceb86b236668c8c372136f1d1b43a5d0a79d3fb05fd1ef4342bb638190f68
|
||||
sha256: 9b606ef4ddc7859591d9455fd9cdf9dfe6ad2533824636deb727b82e035f503a
|
||||
schema_id: stage2_workflow_contract.v1.2
|
||||
binding_status: BOUND
|
||||
inline_code_receipt_ref:
|
||||
asset_id: RECEIPT-S2_00-INLINE-CODE
|
||||
path: manifest/s2_00_inline_code_receipt.json
|
||||
sha256: 36af21949e5ef53a3d39df8ea9491c64da43abcc4f5e42e13db9bb391da843b8
|
||||
sha256: be044ab40ee86e7c3a1be6c421b2c5d36ee67fc2fd2870f59a3d9d911fef6c1a
|
||||
schema_id: stage2_s2_00_inline_code_receipt.v2
|
||||
binding_status: BOUND
|
||||
stage2_release_ref:
|
||||
asset_id: RELEASE-STAGE2-CLEAN
|
||||
path: manifest/stage2_release.json
|
||||
sha256: 9fa85bb94c5f14f675dcdaa0cf94d06745b21675d97797539ffc14015dcc9f53
|
||||
sha256: 2363f1166ee8a3ea2b38350cde61fccfc9852a413c6b9f14a6ed6606af15a590
|
||||
schema_id: stage2_release.v2
|
||||
binding_status: BOUND
|
||||
expected_release_sha256: 9fa85bb94c5f14f675dcdaa0cf94d06745b21675d97797539ffc14015dcc9f53
|
||||
agent_script_sha256: 94c2744d71d222cfb5cc1b2a0d33a6cda20079439823de431eef378a2fa5c943
|
||||
canonical_code_sha256: e3f87725d5a6496189e7c411ef940dd8a7b3a9ff5b00535803bf98fa0cc4373e
|
||||
expected_release_sha256: 2363f1166ee8a3ea2b38350cde61fccfc9852a413c6b9f14a6ed6606af15a590
|
||||
agent_script_sha256: 07bc9e235d211f3d4e6886bca2faeeb1f112d525fa29fe111bd7c50c42ff9849
|
||||
canonical_code_sha256: 81e619b26560890f9a4066e4275f2f51e63910d7983ba453230d4dd6e90bfd6a
|
||||
prepare_request_contract:
|
||||
task_name: Task_S2_00_prepare_request
|
||||
yaml_pointer: /Agent/Stages/0/tasks/0/parameters/code
|
||||
required_external_arguments:
|
||||
- request_id
|
||||
- attempt_id
|
||||
- stage1_run_root_ref
|
||||
- stage1_deployment_root_ref
|
||||
write_path: stage2_control/s2_00_request.json
|
||||
argument_binding_status: UNVERIFIED
|
||||
success_gate_status: UNVERIFIED
|
||||
workspace_serialization_status: UNVERIFIED
|
||||
mcp_server_id: code-executor
|
||||
tool_name: run_code
|
||||
language: python
|
||||
@@ -86,7 +98,7 @@ stage_bindings:
|
||||
agent_script_ref:
|
||||
asset_id: AGENT-S2_20-INLINE
|
||||
path: agent_scripts/Stage_2_S2_20.yml
|
||||
sha256: 3c4e6a7404f5b6a6b2403824c3cd9e71db566d4f2865bd4de3e6fd9d07824bd6
|
||||
sha256: eb82f84df10c1eb4402e5e0f87fbe97a12e58aaa04f60c9535d66076300a5fdb
|
||||
schema_id: liti_agent_yaml.v1
|
||||
binding_status: BOUND
|
||||
workflow_contract_ref:
|
||||
@@ -98,18 +110,18 @@ stage_bindings:
|
||||
inline_code_receipt_ref:
|
||||
asset_id: RECEIPT-S2_20-INLINE-CODE
|
||||
path: manifest/s2_20_inline_code_receipt.json
|
||||
sha256: 65e29f10f065cdd77281aa47acaf341edb38b0595aab0aa9f04bbee3a4718608
|
||||
sha256: 64fd3711bd1128b6d29d3b8ff7a6af07228764da9604e0b5506562c5420e83e9
|
||||
schema_id: stage2_s2_20_inline_code_receipt.v1
|
||||
binding_status: BOUND
|
||||
stage2_release_ref:
|
||||
asset_id: RELEASE-STAGE2-CLEAN
|
||||
path: manifest/stage2_release.json
|
||||
sha256: 9fa85bb94c5f14f675dcdaa0cf94d06745b21675d97797539ffc14015dcc9f53
|
||||
sha256: 2363f1166ee8a3ea2b38350cde61fccfc9852a413c6b9f14a6ed6606af15a590
|
||||
schema_id: stage2_release.v2
|
||||
binding_status: BOUND
|
||||
expected_release_sha256: 9fa85bb94c5f14f675dcdaa0cf94d06745b21675d97797539ffc14015dcc9f53
|
||||
agent_script_sha256: 3c4e6a7404f5b6a6b2403824c3cd9e71db566d4f2865bd4de3e6fd9d07824bd6
|
||||
canonical_code_sha256: 6f1f503378242cf64cf8f0838af2164ab1103bc7031c8a6873f4ee89be66f7fd
|
||||
expected_release_sha256: 2363f1166ee8a3ea2b38350cde61fccfc9852a413c6b9f14a6ed6606af15a590
|
||||
agent_script_sha256: eb82f84df10c1eb4402e5e0f87fbe97a12e58aaa04f60c9535d66076300a5fdb
|
||||
canonical_code_sha256: 91feb9e2ced624a2a4ea36e2b83bd17ed20bb059a42206fad01660800adeb515
|
||||
mcp_server_id: code-executor
|
||||
tool_name: run_code
|
||||
language: python
|
||||
@@ -237,7 +249,7 @@ stage_bindings:
|
||||
agent_script_ref:
|
||||
asset_id: AGENT-S2_40-INLINE
|
||||
path: agent_scripts/Stage_2_S2_40.yml
|
||||
sha256: 5cbe2ac9aa73d95a5fa4e6cf71d7d8f0ad83aa05536f573c13e42505ef5d46f7
|
||||
sha256: a628f7fe944e21f77b9276cdb3d048189dcec958c7ed5abdcd9638b53a909a68
|
||||
schema_id: liti_agent_yaml.v1
|
||||
binding_status: BOUND
|
||||
workflow_contract_ref:
|
||||
@@ -249,18 +261,18 @@ stage_bindings:
|
||||
inline_code_receipt_ref:
|
||||
asset_id: RECEIPT-S2_40-INLINE-CODE
|
||||
path: manifest/s2_40_inline_code_receipt.json
|
||||
sha256: 9bd0cfaee8d9e78480ab80a2fa541414c7ad0565439fa0f4661701fcdfde6526
|
||||
sha256: 38fa1f5d8fc930ee3f7bbff7c73e1a52efc4995d14126ae035323ff74f1abf3d
|
||||
schema_id: stage2_s2_40_inline_code_receipt.v1
|
||||
binding_status: BOUND
|
||||
stage2_release_ref:
|
||||
asset_id: RELEASE-STAGE2-CLEAN
|
||||
path: manifest/stage2_release.json
|
||||
sha256: 9fa85bb94c5f14f675dcdaa0cf94d06745b21675d97797539ffc14015dcc9f53
|
||||
sha256: 2363f1166ee8a3ea2b38350cde61fccfc9852a413c6b9f14a6ed6606af15a590
|
||||
schema_id: stage2_release.v2
|
||||
binding_status: BOUND
|
||||
expected_release_sha256: 9fa85bb94c5f14f675dcdaa0cf94d06745b21675d97797539ffc14015dcc9f53
|
||||
agent_script_sha256: 5cbe2ac9aa73d95a5fa4e6cf71d7d8f0ad83aa05536f573c13e42505ef5d46f7
|
||||
canonical_code_sha256: e521e1a65294a769cd6bf20edb159f8720c105b60cb769885aa416c8c763dcef
|
||||
expected_release_sha256: 2363f1166ee8a3ea2b38350cde61fccfc9852a413c6b9f14a6ed6606af15a590
|
||||
agent_script_sha256: a628f7fe944e21f77b9276cdb3d048189dcec958c7ed5abdcd9638b53a909a68
|
||||
canonical_code_sha256: 12f6b83e1e6793ad871033c6e6b840fc1e31f9fb6695b1a15f0406978fe841f7
|
||||
mcp_server_id: code-executor
|
||||
tool_name: run_code
|
||||
language: python
|
||||
@@ -323,7 +335,7 @@ embedded_task_bindings:
|
||||
agent_script_ref:
|
||||
asset_id: AGENT-S2_30-INLINE
|
||||
path: agent_scripts/Stage_2_S2_30.yml
|
||||
sha256: d0804526941207395c9b64a314b124ff8e919c6852e828d829dc45a9feb3ccdd
|
||||
sha256: 8ff81d9a9db6b80216bf453634b93881f1c2ef7220962f077e44f703fb1457c3
|
||||
schema_id: liti_agent_yaml.v1
|
||||
binding_status: BOUND
|
||||
workflow_contract_ref:
|
||||
@@ -335,18 +347,18 @@ embedded_task_bindings:
|
||||
inline_code_receipt_ref:
|
||||
asset_id: RECEIPT-S2_30-INLINE-CODE
|
||||
path: manifest/s2_30_inline_code_receipt.json
|
||||
sha256: 8d1618771cf1f72aa3c6a5a4544fe6b6e949ccc7ecfef56f166561971beee896
|
||||
sha256: a5e4e3161fc16f3e44ec9ae2c05d2c4706eca02099d844520973f76475343998
|
||||
schema_id: stage2_s2_30_inline_code_receipt.v1
|
||||
binding_status: BOUND
|
||||
stage2_release_ref:
|
||||
asset_id: RELEASE-STAGE2-CLEAN
|
||||
path: manifest/stage2_release.json
|
||||
sha256: 9fa85bb94c5f14f675dcdaa0cf94d06745b21675d97797539ffc14015dcc9f53
|
||||
sha256: 2363f1166ee8a3ea2b38350cde61fccfc9852a413c6b9f14a6ed6606af15a590
|
||||
schema_id: stage2_release.v2
|
||||
binding_status: BOUND
|
||||
expected_release_sha256: 9fa85bb94c5f14f675dcdaa0cf94d06745b21675d97797539ffc14015dcc9f53
|
||||
agent_script_sha256: d0804526941207395c9b64a314b124ff8e919c6852e828d829dc45a9feb3ccdd
|
||||
canonical_code_sha256: 1f7f3b842afbd3f1034f094d6341288effc9804a03920ada4bf8cfd512464e4c
|
||||
expected_release_sha256: 2363f1166ee8a3ea2b38350cde61fccfc9852a413c6b9f14a6ed6606af15a590
|
||||
agent_script_sha256: 8ff81d9a9db6b80216bf453634b93881f1c2ef7220962f077e44f703fb1457c3
|
||||
canonical_code_sha256: 17e66a89befdeb3ac4d7901eda2dad57869db809baf5faf16434cd7a7adb067a
|
||||
mcp_server_id: code-executor
|
||||
tool_name: run_code
|
||||
language: python
|
||||
|
||||
+4
-4
@@ -2,7 +2,7 @@ schema_version: stage2_s2_30_llm_binding.v1
|
||||
binding_status: PENDING_EXTERNAL_PLATFORM_BINDING
|
||||
agent_ref:
|
||||
path: agent_scripts/Stage_2_S2_30.yml
|
||||
sha256: d0804526941207395c9b64a314b124ff8e919c6852e828d829dc45a9feb3ccdd
|
||||
sha256: 8ff81d9a9db6b80216bf453634b93881f1c2ef7220962f077e44f703fb1457c3
|
||||
workflow_ref:
|
||||
path: workflows/S2_30_claim_group_draft_map.yml
|
||||
sha256: b266e7b7ae8939b087bfdb7b34fff8b9c6011c835560ab690991d856ced0cc15
|
||||
@@ -13,7 +13,7 @@ schema_ref:
|
||||
size_bytes: 68470
|
||||
planner_ref:
|
||||
path: runtime/s2_30_dispatch_planner.py
|
||||
sha256: 1f7f3b842afbd3f1034f094d6341288effc9804a03920ada4bf8cfd512464e4c
|
||||
sha256: 17e66a89befdeb3ac4d7901eda2dad57869db809baf5faf16434cd7a7adb067a
|
||||
model:
|
||||
provider: openai
|
||||
model_id: gpt-5.6-sol
|
||||
@@ -30,10 +30,10 @@ prompt_contract:
|
||||
- S30
|
||||
inline_common_prompt_sha256: a90ac95f0b24ea938a16699f34ef7f17eb870edcbd8964ba4f6709e36e1bbcc0
|
||||
inline_static_prompt_sha256: 577efa2c3334298ec60188b9f0066f834e072dc99f785b3c6ab4d05a47fac18a
|
||||
projection_receipt_sha256: 1aa650d3c389ba27ce43e514295c7c2ce629583dc96220618fd97dea399d0757
|
||||
projection_receipt_sha256: 00b66f3495de5d1ff1e6e508eae2939cf862fdfc799c9cd01544a7979f769c42
|
||||
runtime_external_prompt_read_allowed: false
|
||||
planner_contract:
|
||||
inline_code_receipt_sha256: 8d1618771cf1f72aa3c6a5a4544fe6b6e949ccc7ecfef56f166561971beee896
|
||||
inline_code_receipt_sha256: a5e4e3161fc16f3e44ec9ae2c05d2c4706eca02099d844520973f76475343998
|
||||
legal_reasoning_allowed: false
|
||||
persistent_write_allowed: false
|
||||
native_adapter:
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1 +1 @@
|
||||
{"authoring":{"path":"Stage_2_S2_00_v.2.yml","sha256":"94c2744d71d222cfb5cc1b2a0d33a6cda20079439823de431eef378a2fa5c943","size_bytes":367573,"unique_key_parse":"PASS"},"authoring_rewritten":false,"build_kind":"OFFLINE_AUTHORING_PROJECTION","canonical_code":{"ast_status":"PASS","code_sha256":"e3f87725d5a6496189e7c411ef940dd8a7b3a9ff5b00535803bf98fa0cc4373e","code_size_bytes":283196,"compile_status":"PASS","encoding":"UTF-8","external_python_source_ref_count":0,"external_url_count":0,"extraction_transform":"NONE","forbidden_dynamic_call_count":0,"forbidden_import_count":0,"imports":["__future__","argparse","base64","binascii","collections","contextlib","dataclasses","hashlib","httpx","io","itertools","json","math","os","pathlib","re","shutil","stat","sys","tempfile","typing","unicodedata"],"placeholder_count":0,"plaintext_secret_count":0,"yaml_pointer":"/Agent/Stages/0/tasks/0/parameters/code"},"deployment_projection":{"byte_identical_to_authoring":true,"canonical_task_semantics_sha256":"a970635aa19b4ebca03819e30de32bb69f33ca455f7ecd6257a45636565b4e95","path":"Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00.yml","sha256":"94c2744d71d222cfb5cc1b2a0d33a6cda20079439823de431eef378a2fa5c943","size_bytes":367573},"full_code_mirrors":[{"byte_identical_to_canonical_code":true,"path":"Default_Agent/Stage_2_Clean/runtime/s2_00_ingress.py","sha256":"e3f87725d5a6496189e7c411ef940dd8a7b3a9ff5b00535803bf98fa0cc4373e","size_bytes":283196},{"byte_identical_to_canonical_code":true,"path":"Default_Agent/Stage_2_Clean/runtime/s2_00_ingress.txt","sha256":"e3f87725d5a6496189e7c411ef940dd8a7b3a9ff5b00535803bf98fa0cc4373e","size_bytes":283196}],"parity_status":"PASS","schema_version":"stage2_s2_00_inline_code_receipt.v2","source_of_truth":"Stage_2_S2_00_v.2.yml","task_contract":{"agent":{"name":"Stage_2_S2_00_v2","version":"1.2.0"},"mcp_servers":{"code-executor":{"type":"streamable-http","url":"https://code-executor.mcp.eroomai.com/mcp"},"localdocs":{"type":"streamable-http","url":"http://mcp-localdocs:8012/mcp"}},"stage":{"name":"S2_00","nexts":[],"prevs":[]},"task":{"code_sha256":"e3f87725d5a6496189e7c411ef940dd8a7b3a9ff5b00535803bf98fa0cc4373e","mcp":"code-executor","parameters":{"language":"python","network":"agent-network","requirements":"httpx==0.28.1","timeout":300},"task_name":"Task_S2_00_deterministic_ingress","tool_name":"run_code"},"task_procedure":{"IN":{"nexts":["Task_S2_00_deterministic_ingress"],"wait_until":[]},"OUT":{"nexts":[],"wait_until":["Task_S2_00_deterministic_ingress"]},"Task_S2_00_deterministic_ingress":{"nexts":["OUT"],"wait_until":["IN"]}}},"workflow_id":"S2_00"}
|
||||
{"authoring":{"path":"Stage_2_S2_00_v.2.yml","sha256":"07bc9e235d211f3d4e6886bca2faeeb1f112d525fa29fe111bd7c50c42ff9849","size_bytes":382438,"unique_key_parse":"PASS"},"authoring_rewritten":false,"build_kind":"OFFLINE_AUTHORING_PROJECTION","canonical_code":{"ast_status":"PASS","code_sha256":"81e619b26560890f9a4066e4275f2f51e63910d7983ba453230d4dd6e90bfd6a","code_size_bytes":283196,"compile_status":"PASS","encoding":"UTF-8","external_python_source_ref_count":0,"external_url_count":0,"extraction_transform":"NONE","forbidden_dynamic_call_count":0,"forbidden_import_count":0,"imports":["__future__","argparse","base64","binascii","collections","contextlib","dataclasses","hashlib","httpx","io","itertools","json","math","os","pathlib","re","shutil","stat","sys","tempfile","typing","unicodedata"],"placeholder_count":0,"plaintext_secret_count":0,"yaml_pointer":"/Agent/Stages/0/tasks/1/parameters/code"},"deployment_projection":{"byte_identical_to_authoring":true,"canonical_task_semantics_sha256":"0a0f84a21f30c607b1e0a63a037e43c373623a0c190cfdda7c7576e41c3c5bfc","path":"Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00.yml","sha256":"07bc9e235d211f3d4e6886bca2faeeb1f112d525fa29fe111bd7c50c42ff9849","size_bytes":382438},"full_code_mirrors":[{"byte_identical_to_canonical_code":true,"path":"Default_Agent/Stage_2_Clean/runtime/s2_00_prepare_request.py","sha256":"16a57fbf3840d20edee8590a80b7b8161fe0b56637f271e850a5aa6be1e1f2d4","size_bytes":11062},{"byte_identical_to_canonical_code":true,"path":"Default_Agent/Stage_2_Clean/runtime/s2_00_prepare_request.txt","sha256":"16a57fbf3840d20edee8590a80b7b8161fe0b56637f271e850a5aa6be1e1f2d4","size_bytes":11062},{"byte_identical_to_canonical_code":true,"path":"Default_Agent/Stage_2_Clean/runtime/s2_00_ingress.py","sha256":"81e619b26560890f9a4066e4275f2f51e63910d7983ba453230d4dd6e90bfd6a","size_bytes":283196},{"byte_identical_to_canonical_code":true,"path":"Default_Agent/Stage_2_Clean/runtime/s2_00_ingress.txt","sha256":"81e619b26560890f9a4066e4275f2f51e63910d7983ba453230d4dd6e90bfd6a","size_bytes":283196}],"parity_status":"PASS","prepare_code":{"ast_status":"PASS","code_sha256":"16a57fbf3840d20edee8590a80b7b8161fe0b56637f271e850a5aa6be1e1f2d4","code_size_bytes":11062,"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__","base64","hashlib","httpx","json","pathlib","re","sys","typing","unicodedata"],"placeholder_count":0,"plaintext_secret_count":0,"yaml_pointer":"/Agent/Stages/0/tasks/0/parameters/code"},"schema_version":"stage2_s2_00_inline_code_receipt.v2","source_of_truth":"Stage_2_S2_00_v.2.yml","task_contract":{"agent":{"name":"Stage_2_S2_00_v2","version":"1.2.0"},"mcp_servers":{"code-executor":{"type":"streamable-http","url":"https://code-executor.mcp.eroomai.com/mcp"},"localdocs":{"type":"streamable-http","url":"http://mcp-localdocs:8012/mcp"}},"prepare_task":{"code_sha256":"16a57fbf3840d20edee8590a80b7b8161fe0b56637f271e850a5aa6be1e1f2d4","mcp":"code-executor","parameters":{"language":"python","network":"agent-network","requirements":"httpx==0.28.1","timeout":300},"task_name":"Task_S2_00_prepare_request","tool_name":"run_code"},"stage":{"name":"S2_00","nexts":[],"prevs":[]},"task":{"code_sha256":"81e619b26560890f9a4066e4275f2f51e63910d7983ba453230d4dd6e90bfd6a","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_prepare_request"],"wait_until":[]},"OUT":{"nexts":[],"wait_until":["Task_S2_00_deterministic_ingress"]},"Task_S2_00_deterministic_ingress":{"nexts":["OUT"],"wait_until":["Task_S2_00_prepare_request"]},"Task_S2_00_prepare_request":{"nexts":["Task_S2_00_deterministic_ingress"],"wait_until":["IN"]}}},"workflow_id":"S2_00"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"agent_contract":{"agent_name":"Stage_2_S2_10","agent_version":"1.1.0","deterministic_task_count":0,"inline_common_prompt_sha256":"a90ac95f0b24ea938a16699f34ef7f17eb870edcbd8964ba4f6709e36e1bbcc0","inline_static_prompt_sha256":"894c30bedaa5f230aed117e81ef4cc06cac832928a1e29cd3ee8804cfd4dba1f","item_tokens":["{{item.selected_legal_context_json}}","{{item.cluster_case_payload_json}}"],"llm_task_count":1,"prompt_roles":["system","system","system","user"],"reduce_task_count":0,"stage_name":"S2_10","task_name":"Task_S2_10_resolve_cluster","tool_task_count":0},"authoring":{"path":"Stage_2_S2_10_v.1.yml","sha256":"122fe890efb340ebbac299afe058bf9ad0ed87d4d0ccf2456393e9e5796ec272","size_bytes":30984},"binding":{"path":"deployment/stage2_s2_10_llm_binding.yml","sha256":"9b5d69f1316a0b9fba7b41097ee92142cf42485c8deb0be876d9e9329c317a04"},"child_release":{"path":"manifest/s2_10_release.json","release_digest":"8cfc30a40ad53b35fb44c56e3c53f86605f777622399a4b1dcd0b2082eb6e197","sha256":"cb49a4501116e46d935933b739b6679b11d961ff8e36abefea35dab6b25508fb"},"deployment":{"byte_parity":"PASS","path":"agent_scripts/Stage_2_S2_10.yml","sha256":"122fe890efb340ebbac299afe058bf9ad0ed87d4d0ccf2456393e9e5796ec272","size_bytes":30984},"external_admission_status":"PENDING","inline_prompt_receipt":{"path":"manifest/s2_10_inline_prompt_projection_receipt.json","sha256":"b7fdef2ad05f251bdede9473d3b69c0f169ab7f4a059b393429ff76f739f703e","size_bytes":2659},"receipt_digest":"9bd92db101c0bf6f0c48764c71f5091e573dc65cce307ce3121b5d0271efc36c","receipt_status":"OFFLINE_AGENT_PROJECTION_VERIFIED","runtime_admission_effect":"NONE","schema":{"path":"schemas/s2_10.schema.json","sha256":"5233afde9ddb3c5868b9101816e8b3830a90b1399e3e5e89d9d7c7bf5beacaee","size_bytes":82703},"schema_version":"stage2_s2_10_agent_receipt.v2","workflow":{"path":"workflows/S2_10_domain_relief_resolution_map.yml","sha256":"f0fba48f1760cf4e233d7d698fd19090f96ffb89cabe4b2e2cae9af7c616be0f","size_bytes":15320}}
|
||||
{"agent_contract":{"agent_name":"Stage_2_S2_10","agent_version":"1.1.0","deterministic_task_count":0,"inline_common_prompt_sha256":"a90ac95f0b24ea938a16699f34ef7f17eb870edcbd8964ba4f6709e36e1bbcc0","inline_static_prompt_sha256":"894c30bedaa5f230aed117e81ef4cc06cac832928a1e29cd3ee8804cfd4dba1f","item_tokens":["{{item.selected_legal_context_json}}","{{item.cluster_case_payload_json}}"],"llm_task_count":1,"prompt_roles":["system","system","system","user"],"reduce_task_count":0,"stage_name":"S2_10","task_name":"Task_S2_10_resolve_cluster","tool_task_count":0},"authoring":{"path":"Stage_2_S2_10_v.1.yml","sha256":"122fe890efb340ebbac299afe058bf9ad0ed87d4d0ccf2456393e9e5796ec272","size_bytes":30984},"binding":{"path":"deployment/stage2_s2_10_llm_binding.yml","sha256":"9b5d69f1316a0b9fba7b41097ee92142cf42485c8deb0be876d9e9329c317a04"},"child_release":{"path":"manifest/s2_10_release.json","release_digest":"683fd83a4ffe3867c3c669f627c8ae7e719a6c9fe5f306267939e5d56b95e74a","sha256":"59cfa9d83497634e46f4f48ef691a9785a0b91228748fa65c9c23197e36fe105"},"deployment":{"byte_parity":"PASS","path":"agent_scripts/Stage_2_S2_10.yml","sha256":"122fe890efb340ebbac299afe058bf9ad0ed87d4d0ccf2456393e9e5796ec272","size_bytes":30984},"external_admission_status":"PENDING","inline_prompt_receipt":{"path":"manifest/s2_10_inline_prompt_projection_receipt.json","sha256":"b7fdef2ad05f251bdede9473d3b69c0f169ab7f4a059b393429ff76f739f703e","size_bytes":2659},"receipt_digest":"eaa6900976362c532b312759d9ba8b8823eb79d7cbc0a6ed7d979ff905c7fd17","receipt_status":"OFFLINE_AGENT_PROJECTION_VERIFIED","runtime_admission_effect":"NONE","schema":{"path":"schemas/s2_10.schema.json","sha256":"5233afde9ddb3c5868b9101816e8b3830a90b1399e3e5e89d9d7c7bf5beacaee","size_bytes":82703},"schema_version":"stage2_s2_10_agent_receipt.v2","workflow":{"path":"workflows/S2_10_domain_relief_resolution_map.yml","sha256":"f0fba48f1760cf4e233d7d698fd19090f96ffb89cabe4b2e2cae9af7c616be0f","size_bytes":15320}}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"binding_sha256":"9b5d69f1316a0b9fba7b41097ee92142cf42485c8deb0be876d9e9329c317a04","finding_ids":[],"inline_prompt_projection_sha256":"b7fdef2ad05f251bdede9473d3b69c0f169ab7f4a059b393429ff76f739f703e","receipt_digest":"18a29c299f5587993c17f5a40b1d2c60d03556e4b37306c23352d671c4dcbb7c","review_id":"S2_10-HYBRID-LEGAL-REVIEW-PENDING","review_scope_digest":"c8675e22ff50ba9599ab3ac3108f13718b412fc0de2425133f3f49981edf0735","reviewer_role":"KOREAN_ATTORNEY","s2_10_release_sha256":"cb49a4501116e46d935933b739b6679b11d961ff8e36abefea35dab6b25508fb","schema_version":"stage2_s2_10_legal_review_receipt.v2","signature_ref":null,"signed_at":null,"signed_by":null,"status":"PENDING_KOREAN_LAWYER_REVIEW"}
|
||||
{"binding_sha256":"9b5d69f1316a0b9fba7b41097ee92142cf42485c8deb0be876d9e9329c317a04","finding_ids":[],"inline_prompt_projection_sha256":"b7fdef2ad05f251bdede9473d3b69c0f169ab7f4a059b393429ff76f739f703e","receipt_digest":"f501b51861c6c5291ab3a2632cac239d362437afcada29274a57e33bc8bc1f00","review_id":"S2_10-HYBRID-LEGAL-REVIEW-PENDING","review_scope_digest":"59b98748330faaf05f894d340812f54434a547775c54403c5b3df4f7c7c15dc6","reviewer_role":"KOREAN_ATTORNEY","s2_10_release_sha256":"59cfa9d83497634e46f4f48ef691a9785a0b91228748fa65c9c23197e36fe105","schema_version":"stage2_s2_10_legal_review_receipt.v2","signature_ref":null,"signed_at":null,"signed_by":null,"status":"PENDING_KOREAN_LAWYER_REVIEW"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"benchmark_id":"S2_10-HYBRID-MODEL-BENCHMARK-PENDING","binding_sha256":"9b5d69f1316a0b9fba7b41097ee92142cf42485c8deb0be876d9e9329c317a04","cache_telemetry_observed":null,"endpoint":"responses","latency_p95_ms":null,"model":"gpt-5.6-sol","observed_fixture_refs":[],"reasoning_effort":"xhigh","receipt_digest":"61f34770c1ebbc4c84c53ea925b07416b92f4baf4f1740294423eacbc17895ce","s2_10_release_sha256":"cb49a4501116e46d935933b739b6679b11d961ff8e36abefea35dab6b25508fb","schema_pass_rate":null,"schema_version":"stage2_s2_10_model_benchmark_receipt.v2","signature_ref":null,"signed_at":null,"signed_by":null,"status":"PENDING_MODEL_BENCHMARK","usage_telemetry_observed":null,"verbosity":"medium"}
|
||||
{"benchmark_id":"S2_10-HYBRID-MODEL-BENCHMARK-PENDING","binding_sha256":"9b5d69f1316a0b9fba7b41097ee92142cf42485c8deb0be876d9e9329c317a04","cache_telemetry_observed":null,"endpoint":"responses","latency_p95_ms":null,"model":"gpt-5.6-sol","observed_fixture_refs":[],"reasoning_effort":"xhigh","receipt_digest":"8b1e3184937233eedb34f998d32a584d926407c87d3ce8300bcbc8a63498aa7e","s2_10_release_sha256":"59cfa9d83497634e46f4f48ef691a9785a0b91228748fa65c9c23197e36fe105","schema_pass_rate":null,"schema_version":"stage2_s2_10_model_benchmark_receipt.v2","signature_ref":null,"signed_at":null,"signed_by":null,"status":"PENDING_MODEL_BENCHMARK","usage_telemetry_observed":null,"verbosity":"medium"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"adapter_contract_version":"S2_10_NATIVE_RESULT_ADAPTER_V2","binding_sha256":"9b5d69f1316a0b9fba7b41097ee92142cf42485c8deb0be876d9e9329c317a04","capabilities":[{"activation_binding_ref":null,"capability_id":"HOST_ATOMIC_SINGLE_FLIGHT_CAS_V1","failure_reason_codes":["PENDING_EXTERNAL_PLATFORM_BINDING"],"handler_digest":null,"handler_version":null,"implementation_ref":null,"live_evidence_refs":[],"status":"PENDING_EXTERNAL_PLATFORM_BINDING"},{"activation_binding_ref":null,"capability_id":"AGENTBACKEND_MAP_SOURCE_ITEMS_V1","failure_reason_codes":["PENDING_EXTERNAL_PLATFORM_BINDING"],"handler_digest":null,"handler_version":null,"implementation_ref":null,"live_evidence_refs":[],"status":"PENDING_EXTERNAL_PLATFORM_BINDING"},{"activation_binding_ref":null,"capability_id":"AGENTBACKEND_NO_REDUCE_RESULT_ADAPTER_V1","failure_reason_codes":["PENDING_EXTERNAL_PLATFORM_BINDING"],"handler_digest":null,"handler_version":null,"implementation_ref":null,"live_evidence_refs":[],"status":"PENDING_EXTERNAL_PLATFORM_BINDING"},{"activation_binding_ref":null,"capability_id":"S2_10_STRICT_SCHEMA_AND_REF_VALIDATOR_V1","failure_reason_codes":["PENDING_EXTERNAL_PLATFORM_BINDING"],"handler_digest":null,"handler_version":null,"implementation_ref":null,"live_evidence_refs":[],"status":"PENDING_EXTERNAL_PLATFORM_BINDING"},{"activation_binding_ref":null,"capability_id":"S2_10_DETERMINISTIC_ID_AND_IMMUTABLE_PERSIST_V1","failure_reason_codes":["PENDING_EXTERNAL_PLATFORM_BINDING"],"handler_digest":null,"handler_version":null,"implementation_ref":null,"live_evidence_refs":[],"status":"PENDING_EXTERNAL_PLATFORM_BINDING"},{"activation_binding_ref":null,"capability_id":"S2_10_ITEM_RETRY_CONTROLLER_V1","failure_reason_codes":["PENDING_EXTERNAL_PLATFORM_BINDING"],"handler_digest":null,"handler_version":null,"implementation_ref":null,"live_evidence_refs":[],"status":"PENDING_EXTERNAL_PLATFORM_BINDING"}],"overall_status":"PENDING_EXTERNAL_PLATFORM_BINDING","parent_stage2_release_sha256":"9fa85bb94c5f14f675dcdaa0cf94d06745b21675d97797539ffc14015dcc9f53","receipt_digest":"3c3a005c2ff658a53c37631e82e5dc536e038bd357cacaaefbd37453b2a99245","receipt_id":"S2_10-HYBRID-PLATFORM-ADAPTER-PENDING","s2_10_release_sha256":"cb49a4501116e46d935933b739b6679b11d961ff8e36abefea35dab6b25508fb","schema_version":"stage2_s2_10_platform_adapter_receipt.v2","signature_ref":null,"signed_at":null,"signed_by":null}
|
||||
{"adapter_contract_version":"S2_10_NATIVE_RESULT_ADAPTER_V2","binding_sha256":"9b5d69f1316a0b9fba7b41097ee92142cf42485c8deb0be876d9e9329c317a04","capabilities":[{"activation_binding_ref":null,"capability_id":"HOST_ATOMIC_SINGLE_FLIGHT_CAS_V1","failure_reason_codes":["PENDING_EXTERNAL_PLATFORM_BINDING"],"handler_digest":null,"handler_version":null,"implementation_ref":null,"live_evidence_refs":[],"status":"PENDING_EXTERNAL_PLATFORM_BINDING"},{"activation_binding_ref":null,"capability_id":"AGENTBACKEND_MAP_SOURCE_ITEMS_V1","failure_reason_codes":["PENDING_EXTERNAL_PLATFORM_BINDING"],"handler_digest":null,"handler_version":null,"implementation_ref":null,"live_evidence_refs":[],"status":"PENDING_EXTERNAL_PLATFORM_BINDING"},{"activation_binding_ref":null,"capability_id":"AGENTBACKEND_NO_REDUCE_RESULT_ADAPTER_V1","failure_reason_codes":["PENDING_EXTERNAL_PLATFORM_BINDING"],"handler_digest":null,"handler_version":null,"implementation_ref":null,"live_evidence_refs":[],"status":"PENDING_EXTERNAL_PLATFORM_BINDING"},{"activation_binding_ref":null,"capability_id":"S2_10_STRICT_SCHEMA_AND_REF_VALIDATOR_V1","failure_reason_codes":["PENDING_EXTERNAL_PLATFORM_BINDING"],"handler_digest":null,"handler_version":null,"implementation_ref":null,"live_evidence_refs":[],"status":"PENDING_EXTERNAL_PLATFORM_BINDING"},{"activation_binding_ref":null,"capability_id":"S2_10_DETERMINISTIC_ID_AND_IMMUTABLE_PERSIST_V1","failure_reason_codes":["PENDING_EXTERNAL_PLATFORM_BINDING"],"handler_digest":null,"handler_version":null,"implementation_ref":null,"live_evidence_refs":[],"status":"PENDING_EXTERNAL_PLATFORM_BINDING"},{"activation_binding_ref":null,"capability_id":"S2_10_ITEM_RETRY_CONTROLLER_V1","failure_reason_codes":["PENDING_EXTERNAL_PLATFORM_BINDING"],"handler_digest":null,"handler_version":null,"implementation_ref":null,"live_evidence_refs":[],"status":"PENDING_EXTERNAL_PLATFORM_BINDING"}],"overall_status":"PENDING_EXTERNAL_PLATFORM_BINDING","parent_stage2_release_sha256":"2363f1166ee8a3ea2b38350cde61fccfc9852a413c6b9f14a6ed6606af15a590","receipt_digest":"e7f6d818b3ad2ab3093e1f9edb8ab2a06783d0e728b08bb91ddb6dc03755dd34","receipt_id":"S2_10-HYBRID-PLATFORM-ADAPTER-PENDING","s2_10_release_sha256":"59cfa9d83497634e46f4f48ef691a9785a0b91228748fa65c9c23197e36fe105","schema_version":"stage2_s2_10_platform_adapter_receipt.v2","signature_ref":null,"signed_at":null,"signed_by":null}
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1 +1 @@
|
||||
{"authoring":{"path":"Stage_2_S2_20.yml","sha256":"3c4e6a7404f5b6a6b2403824c3cd9e71db566d4f2865bd4de3e6fd9d07824bd6","size_bytes":254756},"canonical_code":{"ast_status":"PASS","code_sha256":"6f1f503378242cf64cf8f0838af2164ab1103bc7031c8a6873f4ee89be66f7fd","code_size_bytes":200269,"compile_status":"PASS","encoding":"UTF-8","endpoint_literals":["http://mcp-localdocs:8012/mcp","https://weaviate.eroomai.com/mcp"],"expected_parent_stage2_release_sha256":"9fa85bb94c5f14f675dcdaa0cf94d06745b21675d97797539ffc14015dcc9f53","external_python_source_ref_count":0,"extraction_transform":"NONE","forbidden_dynamic_call_count":0,"forbidden_import_count":0,"imports":["__future__","base64","binascii","concurrent","contextlib","datetime","decimal","hashlib","httpx","io","itertools","json","pathlib","re","sys","typing","unicodedata"],"plaintext_secret_count":0,"yaml_pointer":"/Agent/Stages/0/tasks/0/parameters/code"},"deployment_projection":{"path":"Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_20.yml","sha256":"3c4e6a7404f5b6a6b2403824c3cd9e71db566d4f2865bd4de3e6fd9d07824bd6","size_bytes":254756},"full_code_mirrors":["Default_Agent/Stage_2_Clean/runtime/s2_20_reduce.py","Default_Agent/Stage_2_Clean/runtime/s2_20_reduce.txt"],"parity_status":"PASS","schema_version":"stage2_s2_20_inline_code_receipt.v1","task_contract":{"agent":{"name":"Stage_2_S2_20","version":"1.0.0"},"authoring_rewritten":false,"canonical_task_semantics_sha256":"750bf364c9b73460349005590cbf65ae4c0235b5af0770937ccbbf669d159dd0","code_mirrors_byte_identical":true,"exactly_one_code_executor_run_code":true,"exactly_one_stage":true,"exactly_one_task":true,"expected_parent_stage2_release_sha256":"9fa85bb94c5f14f675dcdaa0cf94d06745b21675d97797539ffc14015dcc9f53","internal_dag":"(C20||C21)->C25->C26->(C22||(C27->C28->C29))->C30->C35","mcp_servers":{"code-executor":{"type":"streamable-http","url":"https://code-executor.mcp.eroomai.com/mcp"},"localdocs":{"type":"streamable-http","url":"http://mcp-localdocs:8012/mcp"}},"projection_byte_identical_to_authoring":true,"stage":{"name":"S2_20","nexts":[],"prevs":[],"skip_confirm":true},"task":{"code_sha256":"6f1f503378242cf64cf8f0838af2164ab1103bc7031c8a6873f4ee89be66f7fd","mcp":"code-executor","parameters":{"language":"python","network":"agent-network","requirements":"httpx==0.28.1","timeout":300},"task_name":"Task_S2_20_deterministic_relief_plan","tool_name":"run_code"},"task_procedure":{"IN":{"nexts":["Task_S2_20_deterministic_relief_plan"],"wait_until":[]},"OUT":{"nexts":[],"wait_until":["Task_S2_20_deterministic_relief_plan"]},"Task_S2_20_deterministic_relief_plan":{"nexts":["OUT"],"wait_until":["IN"]}}},"workflow_id":"S2_20"}
|
||||
{"authoring":{"path":"Stage_2_S2_20.yml","sha256":"eb82f84df10c1eb4402e5e0f87fbe97a12e58aaa04f60c9535d66076300a5fdb","size_bytes":254756},"canonical_code":{"ast_status":"PASS","code_sha256":"91feb9e2ced624a2a4ea36e2b83bd17ed20bb059a42206fad01660800adeb515","code_size_bytes":200269,"compile_status":"PASS","encoding":"UTF-8","endpoint_literals":["http://mcp-localdocs:8012/mcp","https://weaviate.eroomai.com/mcp"],"expected_parent_stage2_release_sha256":"2363f1166ee8a3ea2b38350cde61fccfc9852a413c6b9f14a6ed6606af15a590","external_python_source_ref_count":0,"extraction_transform":"NONE","forbidden_dynamic_call_count":0,"forbidden_import_count":0,"imports":["__future__","base64","binascii","concurrent","contextlib","datetime","decimal","hashlib","httpx","io","itertools","json","pathlib","re","sys","typing","unicodedata"],"plaintext_secret_count":0,"yaml_pointer":"/Agent/Stages/0/tasks/0/parameters/code"},"deployment_projection":{"path":"Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_20.yml","sha256":"eb82f84df10c1eb4402e5e0f87fbe97a12e58aaa04f60c9535d66076300a5fdb","size_bytes":254756},"full_code_mirrors":["Default_Agent/Stage_2_Clean/runtime/s2_20_reduce.py","Default_Agent/Stage_2_Clean/runtime/s2_20_reduce.txt"],"parity_status":"PASS","schema_version":"stage2_s2_20_inline_code_receipt.v1","task_contract":{"agent":{"name":"Stage_2_S2_20","version":"1.0.0"},"authoring_rewritten":false,"canonical_task_semantics_sha256":"32f3abd5bf05c546f200284339abcaee222912b698bebe6ffdae44ac511a987c","code_mirrors_byte_identical":true,"exactly_one_code_executor_run_code":true,"exactly_one_stage":true,"exactly_one_task":true,"expected_parent_stage2_release_sha256":"2363f1166ee8a3ea2b38350cde61fccfc9852a413c6b9f14a6ed6606af15a590","internal_dag":"(C20||C21)->C25->C26->(C22||(C27->C28->C29))->C30->C35","mcp_servers":{"code-executor":{"type":"streamable-http","url":"https://code-executor.mcp.eroomai.com/mcp"},"localdocs":{"type":"streamable-http","url":"http://mcp-localdocs:8012/mcp"}},"projection_byte_identical_to_authoring":true,"stage":{"name":"S2_20","nexts":[],"prevs":[],"skip_confirm":true},"task":{"code_sha256":"91feb9e2ced624a2a4ea36e2b83bd17ed20bb059a42206fad01660800adeb515","mcp":"code-executor","parameters":{"language":"python","network":"agent-network","requirements":"httpx==0.28.1","timeout":300},"task_name":"Task_S2_20_deterministic_relief_plan","tool_name":"run_code"},"task_procedure":{"IN":{"nexts":["Task_S2_20_deterministic_relief_plan"],"wait_until":[]},"OUT":{"nexts":[],"wait_until":["Task_S2_20_deterministic_relief_plan"]},"Task_S2_20_deterministic_relief_plan":{"nexts":["OUT"],"wait_until":["IN"]}}},"workflow_id":"S2_20"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"authoring_sha256":"d0804526941207395c9b64a314b124ff8e919c6852e828d829dc45a9feb3ccdd","binding_sha256":"7cf19e7dd8d3ad58251e20edc56e11fde3bffb3936997538eb09301da06189dc","byte_parity":"PASS","child_release_sha256":"e6dd6abe44b39de462ea01f5e78aa39c70bbf47e0b75e08d224615a297d2135f","deployment_sha256":"d0804526941207395c9b64a314b124ff8e919c6852e828d829dc45a9feb3ccdd","deterministic_helper_task_count":1,"llm_task_template_count":1,"receipt_digest":"fac154c1a47a050c1d8cac98a6df7abd92b0410e024c9cb26664dcef4006f13d","receipt_status":"OFFLINE_AGENT_PROJECTION_VERIFIED","reduce_task_count":0,"schema_version":"stage2_s2_30_agent_receipt.v1","task_template_count":2}
|
||||
{"authoring_sha256":"8ff81d9a9db6b80216bf453634b93881f1c2ef7220962f077e44f703fb1457c3","binding_sha256":"2a479395e3c4d829500097c296089781ebce13a2bc18aaaea0f4bf42b5fc494a","byte_parity":"PASS","child_release_sha256":"5ca1bdb3499794a770e41612d20890b002ab0edaf7bdd916c211efefc96f10f4","deployment_sha256":"8ff81d9a9db6b80216bf453634b93881f1c2ef7220962f077e44f703fb1457c3","deterministic_helper_task_count":1,"llm_task_template_count":1,"receipt_digest":"5ba94db0f02a029eef813bd2f71bb84896cb1ced75e1a02431393c50cbecd1b5","receipt_status":"OFFLINE_AGENT_PROJECTION_VERIFIED","reduce_task_count":0,"schema_version":"stage2_s2_30_agent_receipt.v1","task_template_count":2}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"ast_policy_status":"PASS","authoring_agent_sha256":"d0804526941207395c9b64a314b124ff8e919c6852e828d829dc45a9feb3ccdd","compile_status":"PASS","inline_code_sha256":"1f7f3b842afbd3f1034f094d6341288effc9804a03920ada4bf8cfd512464e4c","inline_code_size_bytes":67777,"planner_mirror_paths":["runtime/s2_30_dispatch_planner.py","runtime/s2_30_dispatch_planner.txt"],"receipt_digest":"fe592796579be30899bde4006a0a3ba1d51b97b51a5b7049a3f67539158c71c9","receipt_status":"OFFLINE_PROJECTION_VERIFIED","runtime_admission_effect":"NONE","runtime_external_python_import_count":0,"schema_version":"stage2_s2_30_inline_code_receipt.v1"}
|
||||
{"ast_policy_status":"PASS","authoring_agent_sha256":"8ff81d9a9db6b80216bf453634b93881f1c2ef7220962f077e44f703fb1457c3","compile_status":"PASS","inline_code_sha256":"17e66a89befdeb3ac4d7901eda2dad57869db809baf5faf16434cd7a7adb067a","inline_code_size_bytes":67777,"planner_mirror_paths":["runtime/s2_30_dispatch_planner.py","runtime/s2_30_dispatch_planner.txt"],"receipt_digest":"dcc0a29b9753fb8afd1ace9a0da1a34f1bd14b880b253d7d590e1ad03fff40ff","receipt_status":"OFFLINE_PROJECTION_VERIFIED","runtime_admission_effect":"NONE","runtime_external_python_import_count":0,"schema_version":"stage2_s2_30_inline_code_receipt.v1"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"authoring_agent":{"path":"Stage_2_S2_30.yml","sha256":"d0804526941207395c9b64a314b124ff8e919c6852e828d829dc45a9feb3ccdd","size_bytes":117060},"dynamic_item_tokens":["{{item.p32_common_authority_json}}","{{item.p31_rule_and_pack_json}}","{{item.s30_group_slice_json}}","{{item.compile_mode}}","{{item.s30_group_slice_sha256}}"],"inline_scalars":[{"parity":"PASS","raw_scalar_sha256":"a90ac95f0b24ea938a16699f34ef7f17eb870edcbd8964ba4f6709e36e1bbcc0","source":{"path":"prompts/P00_system_and_safety_contract.md","sha256":"c82cfc0b61adb1af7759fd321f78bcf7bebf7e5d2fc3a9c01533a80736c6d80e","size_bytes":7304},"wrapper":"stage_2_common_cache_prefix","yaml_pointer":"/Agent/Stages/0/tasks/1/prompts/0/content"},{"parity":"PASS","raw_scalar_sha256":"577efa2c3334298ec60188b9f0066f834e072dc99f785b3c6ab4d05a47fac18a","source":{"path":"prompts/P30_joint_drafting_contract.md","sha256":"5d790fccdacd1b7ce27e25cd52e4c301dbce24ba276def18bca608b5b16362aa","size_bytes":13681},"wrapper":"s2_30_joint_drafting_static_prompt","yaml_pointer":"/Agent/Stages/0/tasks/1/prompts/1/content"}],"message_order":["INLINE_COMMON_SYSTEM","INLINE_S2_30_STATIC_SYSTEM","P32_COMMON_AUTHORITY_SYSTEM_DATA","P31_RULE_AND_PACK_SYSTEM_DATA","S30_GROUP_CASE_USER_DATA"],"receipt_digest":"13b89691392616f6f70b2cb1a92a24ee4d891480b56c8c1b41b5bdabe88bbad1","receipt_status":"OFFLINE_PROJECTION_VERIFIED","runtime_admission_effect":"NONE","schema_version":"stage2_s2_30_inline_prompt_projection_receipt.v1"}
|
||||
{"authoring_agent":{"path":"Stage_2_S2_30.yml","sha256":"8ff81d9a9db6b80216bf453634b93881f1c2ef7220962f077e44f703fb1457c3","size_bytes":117060},"dynamic_item_tokens":["{{item.p32_common_authority_json}}","{{item.p31_rule_and_pack_json}}","{{item.s30_group_slice_json}}","{{item.compile_mode}}","{{item.s30_group_slice_sha256}}"],"inline_scalars":[{"parity":"PASS","raw_scalar_sha256":"a90ac95f0b24ea938a16699f34ef7f17eb870edcbd8964ba4f6709e36e1bbcc0","source":{"path":"prompts/P00_system_and_safety_contract.md","sha256":"c82cfc0b61adb1af7759fd321f78bcf7bebf7e5d2fc3a9c01533a80736c6d80e","size_bytes":7304},"wrapper":"stage_2_common_cache_prefix","yaml_pointer":"/Agent/Stages/0/tasks/1/prompts/0/content"},{"parity":"PASS","raw_scalar_sha256":"577efa2c3334298ec60188b9f0066f834e072dc99f785b3c6ab4d05a47fac18a","source":{"path":"prompts/P30_joint_drafting_contract.md","sha256":"5d790fccdacd1b7ce27e25cd52e4c301dbce24ba276def18bca608b5b16362aa","size_bytes":13681},"wrapper":"s2_30_joint_drafting_static_prompt","yaml_pointer":"/Agent/Stages/0/tasks/1/prompts/1/content"}],"message_order":["INLINE_COMMON_SYSTEM","INLINE_S2_30_STATIC_SYSTEM","P32_COMMON_AUTHORITY_SYSTEM_DATA","P31_RULE_AND_PACK_SYSTEM_DATA","S30_GROUP_CASE_USER_DATA"],"receipt_digest":"172d342643993049c83b928a8445bd4954d0e9449aeab91917dec8f7cd4b1e4b","receipt_status":"OFFLINE_PROJECTION_VERIFIED","runtime_admission_effect":"NONE","schema_version":"stage2_s2_30_inline_prompt_projection_receipt.v1"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"binding_sha256":"7cf19e7dd8d3ad58251e20edc56e11fde3bffb3936997538eb09301da06189dc","child_release_sha256":"e6dd6abe44b39de462ea01f5e78aa39c70bbf47e0b75e08d224615a297d2135f","evidence":[],"live_execution_attested":false,"parent_release_sha256":"9fa85bb94c5f14f675dcdaa0cf94d06745b21675d97797539ffc14015dcc9f53","receipt_digest":"6fc276d58d8613c0f7a2f4cb31dce0fc12c04e0239168a969f77e8a3b85437cb","runtime_admission_effect":"BLOCK_PRODUCTION","schema_version":"stage2_s2_30_legal_review_receipt.v1","status":"PENDING_KOREAN_LAWYER_REVIEW"}
|
||||
{"binding_sha256":"2a479395e3c4d829500097c296089781ebce13a2bc18aaaea0f4bf42b5fc494a","child_release_sha256":"5ca1bdb3499794a770e41612d20890b002ab0edaf7bdd916c211efefc96f10f4","evidence":[],"live_execution_attested":false,"parent_release_sha256":"2363f1166ee8a3ea2b38350cde61fccfc9852a413c6b9f14a6ed6606af15a590","receipt_digest":"0eb5248b6a0068e5b6c02473d251fb2efa1993106ec2eda84af5a123c17b7891","runtime_admission_effect":"BLOCK_PRODUCTION","schema_version":"stage2_s2_30_legal_review_receipt.v1","status":"PENDING_KOREAN_LAWYER_REVIEW"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"binding_sha256":"7cf19e7dd8d3ad58251e20edc56e11fde3bffb3936997538eb09301da06189dc","child_release_sha256":"e6dd6abe44b39de462ea01f5e78aa39c70bbf47e0b75e08d224615a297d2135f","evidence":[],"live_execution_attested":false,"parent_release_sha256":"9fa85bb94c5f14f675dcdaa0cf94d06745b21675d97797539ffc14015dcc9f53","receipt_digest":"d8d231359c086ae13fec794d233ebf0ba5854c207da569923ed72d8c6ca90abc","runtime_admission_effect":"BLOCK_PRODUCTION","schema_version":"stage2_s2_30_model_benchmark_receipt.v1","status":"PENDING_MODEL_BENCHMARK"}
|
||||
{"binding_sha256":"2a479395e3c4d829500097c296089781ebce13a2bc18aaaea0f4bf42b5fc494a","child_release_sha256":"5ca1bdb3499794a770e41612d20890b002ab0edaf7bdd916c211efefc96f10f4","evidence":[],"live_execution_attested":false,"parent_release_sha256":"2363f1166ee8a3ea2b38350cde61fccfc9852a413c6b9f14a6ed6606af15a590","receipt_digest":"37dc500196b6282805fc8a70207a300a3a86832119b4e6595f52071fecce89f6","runtime_admission_effect":"BLOCK_PRODUCTION","schema_version":"stage2_s2_30_model_benchmark_receipt.v1","status":"PENDING_MODEL_BENCHMARK"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"binding_sha256":"7cf19e7dd8d3ad58251e20edc56e11fde3bffb3936997538eb09301da06189dc","capabilities":[{"capability_id":"HOST_ATOMIC_GROUP_DISPATCH_CAS_V1","status":"PENDING_EXTERNAL_PLATFORM_BINDING"},{"capability_id":"AGENTBACKEND_TASK_PROCEDURE_WILDCARD_V1","status":"PENDING_EXTERNAL_PLATFORM_BINDING"},{"capability_id":"AGENTBACKEND_WILDCARD_RESULT_ADAPTER_V1","status":"PENDING_EXTERNAL_PLATFORM_BINDING"},{"capability_id":"S2_30_CONTEXT_MATERIALIZER_V1","status":"PENDING_EXTERNAL_PLATFORM_BINDING"},{"capability_id":"S2_30_CACHE_OWNER_SEQUENCE_V1","status":"PENDING_EXTERNAL_PLATFORM_BINDING"},{"capability_id":"S2_30_STRICT_SCHEMA_REF_PROVENANCE_VALIDATOR_V1","status":"PENDING_EXTERNAL_PLATFORM_BINDING"},{"capability_id":"S2_30_DETERMINISTIC_ATOM_ID_TWO_PASS_PERSIST_V1","status":"PENDING_EXTERNAL_PLATFORM_BINDING"},{"capability_id":"S2_30_GROUP_RETRY_CONTROLLER_V1","status":"PENDING_EXTERNAL_PLATFORM_BINDING"},{"capability_id":"S2_30_GROUP_BARRIER_COORDINATOR_V1","status":"PENDING_EXTERNAL_PLATFORM_BINDING"},{"capability_id":"S2_30_CANARY_AUTHORIZATION_SIGNATURE_VERIFY_V1","status":"PENDING_EXTERNAL_PLATFORM_BINDING"}],"child_release_sha256":"e6dd6abe44b39de462ea01f5e78aa39c70bbf47e0b75e08d224615a297d2135f","evidence":[],"live_execution_attested":false,"overall_status":"PENDING_EXTERNAL_PLATFORM_BINDING","parent_release_sha256":"9fa85bb94c5f14f675dcdaa0cf94d06745b21675d97797539ffc14015dcc9f53","receipt_digest":"31b81bdbef3bea6712c6ca2921af4e6b55e264f69b2ed341e1d83c770d3df1c5","runtime_admission_effect":"BLOCK_PRODUCTION","schema_version":"stage2_s2_30_platform_adapter_receipt.v1"}
|
||||
{"binding_sha256":"2a479395e3c4d829500097c296089781ebce13a2bc18aaaea0f4bf42b5fc494a","capabilities":[{"capability_id":"HOST_ATOMIC_GROUP_DISPATCH_CAS_V1","status":"PENDING_EXTERNAL_PLATFORM_BINDING"},{"capability_id":"AGENTBACKEND_TASK_PROCEDURE_WILDCARD_V1","status":"PENDING_EXTERNAL_PLATFORM_BINDING"},{"capability_id":"AGENTBACKEND_WILDCARD_RESULT_ADAPTER_V1","status":"PENDING_EXTERNAL_PLATFORM_BINDING"},{"capability_id":"S2_30_CONTEXT_MATERIALIZER_V1","status":"PENDING_EXTERNAL_PLATFORM_BINDING"},{"capability_id":"S2_30_CACHE_OWNER_SEQUENCE_V1","status":"PENDING_EXTERNAL_PLATFORM_BINDING"},{"capability_id":"S2_30_STRICT_SCHEMA_REF_PROVENANCE_VALIDATOR_V1","status":"PENDING_EXTERNAL_PLATFORM_BINDING"},{"capability_id":"S2_30_DETERMINISTIC_ATOM_ID_TWO_PASS_PERSIST_V1","status":"PENDING_EXTERNAL_PLATFORM_BINDING"},{"capability_id":"S2_30_GROUP_RETRY_CONTROLLER_V1","status":"PENDING_EXTERNAL_PLATFORM_BINDING"},{"capability_id":"S2_30_GROUP_BARRIER_COORDINATOR_V1","status":"PENDING_EXTERNAL_PLATFORM_BINDING"},{"capability_id":"S2_30_CANARY_AUTHORIZATION_SIGNATURE_VERIFY_V1","status":"PENDING_EXTERNAL_PLATFORM_BINDING"}],"child_release_sha256":"5ca1bdb3499794a770e41612d20890b002ab0edaf7bdd916c211efefc96f10f4","evidence":[],"live_execution_attested":false,"overall_status":"PENDING_EXTERNAL_PLATFORM_BINDING","parent_release_sha256":"2363f1166ee8a3ea2b38350cde61fccfc9852a413c6b9f14a6ed6606af15a590","receipt_digest":"dbcbe59cd79d6a368b235fc4c038434cbe6acff500defdf41f5b5ac6630f2434","runtime_admission_effect":"BLOCK_PRODUCTION","schema_version":"stage2_s2_30_platform_adapter_receipt.v1"}
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1 +1 @@
|
||||
{"authoring":{"path":"Stage_2_S2_40.yml","sha256":"5cbe2ac9aa73d95a5fa4e6cf71d7d8f0ad83aa05536f573c13e42505ef5d46f7","size_bytes":249967,"unique_key_parse":"PASS"},"authoring_rewritten":false,"build_kind":"OFFLINE_AUTHORING_PROJECTION","canonical_code":{"ast_status":"PASS","code_sha256":"e521e1a65294a769cd6bf20edb159f8720c105b60cb769885aa416c8c763dcef","code_size_bytes":200946,"compile_status":"PASS","encoding":"UTF-8","expected_parent_stage2_release_sha256":"9fa85bb94c5f14f675dcdaa0cf94d06745b21675d97797539ffc14015dcc9f53","external_python_source_ref_count":0,"external_url_count":0,"extraction_transform":"NONE","forbidden_dynamic_call_count":0,"forbidden_import_count":0,"imports":["__future__","base64","binascii","contextlib","datetime","hashlib","httpx","io","itertools","json","pathlib","re","sys","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":"a8d6450b5a40308b3c2af5227fd3bc0c4f575dd8ea17b10b5bb7d6687ebc9d9e","path":"Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_40.yml","sha256":"5cbe2ac9aa73d95a5fa4e6cf71d7d8f0ad83aa05536f573c13e42505ef5d46f7","size_bytes":249967},"expected_parent_stage2_release_sha256":"9fa85bb94c5f14f675dcdaa0cf94d06745b21675d97797539ffc14015dcc9f53","full_code_mirrors":[{"byte_identical_to_canonical_code":true,"path":"Default_Agent/Stage_2_Clean/runtime/s2_40_commit.py","sha256":"e521e1a65294a769cd6bf20edb159f8720c105b60cb769885aa416c8c763dcef","size_bytes":200946},{"byte_identical_to_canonical_code":true,"path":"Default_Agent/Stage_2_Clean/runtime/s2_40_commit.txt","sha256":"e521e1a65294a769cd6bf20edb159f8720c105b60cb769885aa416c8c763dcef","size_bytes":200946}],"parity_status":"PASS","schema_version":"stage2_s2_40_inline_code_receipt.v1","source_of_truth":"Stage_2_S2_40.yml","task_contract":{"agent":{"name":"Stage_2_S2_40","version":"1.0.0"},"authoring_rewritten":false,"canonical_task_semantics_sha256":"a8d6450b5a40308b3c2af5227fd3bc0c4f575dd8ea17b10b5bb7d6687ebc9d9e","code_mirrors_byte_identical":true,"exactly_one_code_executor_run_code":true,"exactly_one_stage":true,"exactly_one_task":true,"mcp_servers":{"code-executor":{"type":"streamable-http","url":"https://code-executor.mcp.eroomai.com/mcp"},"localdocs":{"type":"streamable-http","url":"http://mcp-localdocs:8012/mcp"}},"projection_byte_identical_to_authoring":true,"stage":{"name":"S2_40","nexts":[],"prevs":[]},"task":{"code_sha256":"e521e1a65294a769cd6bf20edb159f8720c105b60cb769885aa416c8c763dcef","mcp":"code-executor","parameters":{"language":"python","network":"agent-network","requirements":"httpx==0.28.1","timeout":300},"task_name":"Task_S2_40_deterministic_finalizer","tool_name":"run_code"},"task_procedure":{"IN":{"nexts":["Task_S2_40_deterministic_finalizer"],"wait_until":[]},"OUT":{"nexts":[],"wait_until":["Task_S2_40_deterministic_finalizer"]},"Task_S2_40_deterministic_finalizer":{"nexts":["OUT"],"wait_until":["IN"]}}},"workflow_id":"S2_40"}
|
||||
{"authoring":{"path":"Stage_2_S2_40.yml","sha256":"a628f7fe944e21f77b9276cdb3d048189dcec958c7ed5abdcd9638b53a909a68","size_bytes":249967,"unique_key_parse":"PASS"},"authoring_rewritten":false,"build_kind":"OFFLINE_AUTHORING_PROJECTION","canonical_code":{"ast_status":"PASS","code_sha256":"12f6b83e1e6793ad871033c6e6b840fc1e31f9fb6695b1a15f0406978fe841f7","code_size_bytes":200946,"compile_status":"PASS","encoding":"UTF-8","expected_parent_stage2_release_sha256":"2363f1166ee8a3ea2b38350cde61fccfc9852a413c6b9f14a6ed6606af15a590","external_python_source_ref_count":0,"external_url_count":0,"extraction_transform":"NONE","forbidden_dynamic_call_count":0,"forbidden_import_count":0,"imports":["__future__","base64","binascii","contextlib","datetime","hashlib","httpx","io","itertools","json","pathlib","re","sys","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":"de643093e087151fcc80b35be5518890ab64563f4c872260e4c56cbd00d09548","path":"Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_40.yml","sha256":"a628f7fe944e21f77b9276cdb3d048189dcec958c7ed5abdcd9638b53a909a68","size_bytes":249967},"expected_parent_stage2_release_sha256":"2363f1166ee8a3ea2b38350cde61fccfc9852a413c6b9f14a6ed6606af15a590","full_code_mirrors":[{"byte_identical_to_canonical_code":true,"path":"Default_Agent/Stage_2_Clean/runtime/s2_40_commit.py","sha256":"12f6b83e1e6793ad871033c6e6b840fc1e31f9fb6695b1a15f0406978fe841f7","size_bytes":200946},{"byte_identical_to_canonical_code":true,"path":"Default_Agent/Stage_2_Clean/runtime/s2_40_commit.txt","sha256":"12f6b83e1e6793ad871033c6e6b840fc1e31f9fb6695b1a15f0406978fe841f7","size_bytes":200946}],"parity_status":"PASS","schema_version":"stage2_s2_40_inline_code_receipt.v1","source_of_truth":"Stage_2_S2_40.yml","task_contract":{"agent":{"name":"Stage_2_S2_40","version":"1.0.0"},"authoring_rewritten":false,"canonical_task_semantics_sha256":"de643093e087151fcc80b35be5518890ab64563f4c872260e4c56cbd00d09548","code_mirrors_byte_identical":true,"exactly_one_code_executor_run_code":true,"exactly_one_stage":true,"exactly_one_task":true,"mcp_servers":{"code-executor":{"type":"streamable-http","url":"https://code-executor.mcp.eroomai.com/mcp"},"localdocs":{"type":"streamable-http","url":"http://mcp-localdocs:8012/mcp"}},"projection_byte_identical_to_authoring":true,"stage":{"name":"S2_40","nexts":[],"prevs":[]},"task":{"code_sha256":"12f6b83e1e6793ad871033c6e6b840fc1e31f9fb6695b1a15f0406978fe841f7","mcp":"code-executor","parameters":{"language":"python","network":"agent-network","requirements":"httpx==0.28.1","timeout":300},"task_name":"Task_S2_40_deterministic_finalizer","tool_name":"run_code"},"task_procedure":{"IN":{"nexts":["Task_S2_40_deterministic_finalizer"],"wait_until":[]},"OUT":{"nexts":[],"wait_until":["Task_S2_40_deterministic_finalizer"]},"Task_S2_40_deterministic_finalizer":{"nexts":["OUT"],"wait_until":["IN"]}}},"workflow_id":"S2_40"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"admission_status":"PENDING","backend_capability_receipt_sha256":"PENDING_SEQUENTIAL_BIND","embedded_task_admissions":[{"admission_scope":"CODE_EXECUTOR_HELPER_ONLY","binding_id":"S2-BINDING-S2_30-DISPATCH-PLANNER-V1","execution_unit_id":"S2_30::Task_S2_30_dispatch_planner","host_stage_id":"S2_30","inline_code_receipt_ref":{"asset_id":"RECEIPT-S2_30-INLINE-CODE","binding_status":"BOUND","path":"manifest/s2_30_inline_code_receipt.json","schema_id":"stage2_s2_30_inline_code_receipt.v1","sha256":"8d1618771cf1f72aa3c6a5a4544fe6b6e949ccc7ecfef56f166561971beee896"},"task_name":"Task_S2_30_dispatch_planner"}],"executor_binding_sha256":"a1062e9db242747c75a4362fb95eeba6c65f78ac647d4571ed774d2ee3af40f7","parent_release_sha256":"9fa85bb94c5f14f675dcdaa0cf94d06745b21675d97797539ffc14015dcc9f53","present_deterministic_stage_ids":["S2_00","S2_20","S2_40"],"schema_version":"stage2_deterministic_admission_receipt.v1","signature":"PENDING_EXTERNAL_SIGNATURE","signature_verification_status":"PENDING_EXTERNAL_SIGNATURE","signed_payload_sha256":"PENDING_SEQUENTIAL_BIND","stage_receipts":[{"asset_id":"RECEIPT-S2_00-INLINE-CODE","binding_status":"BOUND","path":"manifest/s2_00_inline_code_receipt.json","schema_id":"stage2_s2_00_inline_code_receipt.v2","sha256":"36af21949e5ef53a3d39df8ea9491c64da43abcc4f5e42e13db9bb391da843b8"},{"asset_id":"RECEIPT-S2_20-INLINE-CODE","binding_status":"BOUND","path":"manifest/s2_20_inline_code_receipt.json","schema_id":"stage2_s2_20_inline_code_receipt.v1","sha256":"65e29f10f065cdd77281aa47acaf341edb38b0595aab0aa9f04bbee3a4718608"},{"asset_id":"RECEIPT-S2_40-INLINE-CODE","binding_status":"BOUND","path":"manifest/s2_40_inline_code_receipt.json","schema_id":"stage2_s2_40_inline_code_receipt.v1","sha256":"9bd0cfaee8d9e78480ab80a2fa541414c7ad0565439fa0f4661701fcdfde6526"}]}
|
||||
{"admission_status":"PENDING","backend_capability_receipt_sha256":"PENDING_SEQUENTIAL_BIND","embedded_task_admissions":[{"admission_scope":"CODE_EXECUTOR_HELPER_ONLY","binding_id":"S2-BINDING-S2_30-DISPATCH-PLANNER-V1","execution_unit_id":"S2_30::Task_S2_30_dispatch_planner","host_stage_id":"S2_30","inline_code_receipt_ref":{"asset_id":"RECEIPT-S2_30-INLINE-CODE","binding_status":"BOUND","path":"manifest/s2_30_inline_code_receipt.json","schema_id":"stage2_s2_30_inline_code_receipt.v1","sha256":"a5e4e3161fc16f3e44ec9ae2c05d2c4706eca02099d844520973f76475343998"},"task_name":"Task_S2_30_dispatch_planner"}],"executor_binding_sha256":"bc9aad7a5b817b5f4933bb74cd54782f3d777397a077f64fb9cd9ade55e4c40d","parent_release_sha256":"2363f1166ee8a3ea2b38350cde61fccfc9852a413c6b9f14a6ed6606af15a590","present_deterministic_stage_ids":["S2_00","S2_20","S2_40"],"schema_version":"stage2_deterministic_admission_receipt.v1","signature":"PENDING_EXTERNAL_SIGNATURE","signature_verification_status":"PENDING_EXTERNAL_SIGNATURE","signed_payload_sha256":"PENDING_SEQUENTIAL_BIND","stage_receipts":[{"asset_id":"RECEIPT-S2_00-INLINE-CODE","binding_status":"BOUND","path":"manifest/s2_00_inline_code_receipt.json","schema_id":"stage2_s2_00_inline_code_receipt.v2","sha256":"be044ab40ee86e7c3a1be6c421b2c5d36ee67fc2fd2870f59a3d9d911fef6c1a"},{"asset_id":"RECEIPT-S2_20-INLINE-CODE","binding_status":"BOUND","path":"manifest/s2_20_inline_code_receipt.json","schema_id":"stage2_s2_20_inline_code_receipt.v1","sha256":"64fd3711bd1128b6d29d3b8ff7a6af07228764da9604e0b5506562c5420e83e9"},{"asset_id":"RECEIPT-S2_40-INLINE-CODE","binding_status":"BOUND","path":"manifest/s2_40_inline_code_receipt.json","schema_id":"stage2_s2_40_inline_code_receipt.v1","sha256":"38fa1f5d8fc930ee3f7bbff7c73e1a52efc4995d14126ae035323ff74f1abf3d"}]}
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+2
@@ -33,6 +33,8 @@ FORBIDDEN_PARENT_MEMBERS = frozenset(
|
||||
"agent_scripts/Stage_2_S2_00.yml",
|
||||
"runtime/s2_00_ingress.py",
|
||||
"runtime/s2_00_ingress.txt",
|
||||
"runtime/s2_00_prepare_request.py",
|
||||
"runtime/s2_00_prepare_request.txt",
|
||||
"manifest/s2_00_inline_code_receipt.json",
|
||||
"agent_scripts/Stage_2_S2_20.yml",
|
||||
"runtime/s2_20_reduce.py",
|
||||
|
||||
+2
@@ -33,6 +33,8 @@ FORBIDDEN_PARENT_MEMBERS = frozenset(
|
||||
"agent_scripts/Stage_2_S2_00.yml",
|
||||
"runtime/s2_00_ingress.py",
|
||||
"runtime/s2_00_ingress.txt",
|
||||
"runtime/s2_00_prepare_request.py",
|
||||
"runtime/s2_00_prepare_request.txt",
|
||||
"manifest/s2_00_inline_code_receipt.json",
|
||||
"agent_scripts/Stage_2_S2_20.yml",
|
||||
"runtime/s2_20_reduce.py",
|
||||
|
||||
+107
-36
@@ -32,10 +32,13 @@ AUTHORING_PATH = MAIN_WORKING_DIRECTORY / "Stage_2_S2_00_v.2.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"
|
||||
PREPARE_MIRROR_PY_PATH = DEPLOYMENT_ROOT / "runtime" / "s2_00_prepare_request.py"
|
||||
PREPARE_MIRROR_TXT_PATH = DEPLOYMENT_ROOT / "runtime" / "s2_00_prepare_request.txt"
|
||||
RECEIPT_PATH = DEPLOYMENT_ROOT / "manifest" / "s2_00_inline_code_receipt.json"
|
||||
BINDING_PATH = DEPLOYMENT_ROOT / "deployment" / "stage2_code_executor_binding.yml"
|
||||
|
||||
EXPECTED_TASK_NAME = "Task_S2_00_deterministic_ingress"
|
||||
EXPECTED_PREPARE_TASK_NAME = "Task_S2_00_prepare_request"
|
||||
EXPECTED_AGENT_NAME = "Stage_2_S2_00_v2"
|
||||
EXPECTED_AGENT_VERSION = "1.2.0"
|
||||
EXPECTED_PARAMETERS = {
|
||||
@@ -252,6 +255,20 @@ def select_s2_00_binding(document: Mapping[str, Any]) -> dict[str, Any]:
|
||||
"expected": None,
|
||||
"observed": row.get("external_mcp_contract"),
|
||||
}
|
||||
expected_prepare_contract = {
|
||||
"task_name": EXPECTED_PREPARE_TASK_NAME,
|
||||
"yaml_pointer": "/Agent/Stages/0/tasks/0/parameters/code",
|
||||
"required_external_arguments": ["request_id", "attempt_id", "stage1_run_root_ref", "stage1_deployment_root_ref"],
|
||||
"write_path": "stage2_control/s2_00_request.json",
|
||||
"argument_binding_status": "UNVERIFIED",
|
||||
"success_gate_status": "UNVERIFIED",
|
||||
"workspace_serialization_status": "UNVERIFIED",
|
||||
}
|
||||
if row.get("prepare_request_contract") != expected_prepare_contract:
|
||||
drift["prepare_request_contract"] = {
|
||||
"expected": expected_prepare_contract,
|
||||
"observed": row.get("prepare_request_contract"),
|
||||
}
|
||||
if drift:
|
||||
raise ProjectionError(
|
||||
"S2_00_BINDING_SEMANTICS_DRIFT",
|
||||
@@ -280,7 +297,7 @@ def _require_list(value: Any, code: str) -> list[Any]:
|
||||
return value
|
||||
|
||||
|
||||
def extract_run_code_task(document: Mapping[str, Any]) -> tuple[dict[str, Any], dict[str, Any]]:
|
||||
def extract_run_code_tasks(document: Mapping[str, Any]) -> tuple[dict[str, Any], 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(
|
||||
@@ -324,9 +341,10 @@ def extract_run_code_task(document: Mapping[str, Any]) -> tuple[dict[str, Any],
|
||||
)
|
||||
|
||||
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")
|
||||
if len(tasks) != 2:
|
||||
raise ProjectionError("EXACTLY_TWO_TASKS_REQUIRED", str(len(tasks)))
|
||||
prepare_task = _require_mapping(tasks[0], "PREPARE_TASK_OBJECT_REQUIRED")
|
||||
task = _require_mapping(tasks[1], "INGRESS_TASK_OBJECT_REQUIRED")
|
||||
run_code_tasks = [
|
||||
row
|
||||
for row in tasks
|
||||
@@ -334,43 +352,54 @@ def extract_run_code_task(document: Mapping[str, Any]) -> tuple[dict[str, Any],
|
||||
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 len(run_code_tasks) != 2:
|
||||
raise ProjectionError("EXACTLY_TWO_RUN_CODE_REQUIRED", str(len(run_code_tasks)))
|
||||
if prepare_task.get("task_name") != EXPECTED_PREPARE_TASK_NAME:
|
||||
raise ProjectionError("PREPARE_TASK_NAME_MISMATCH", repr(prepare_task.get("task_name")))
|
||||
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:
|
||||
for current in (prepare_task, task):
|
||||
if current.get("mcp") != "code-executor" or current.get("tool_name") != "run_code":
|
||||
raise ProjectionError("RUN_CODE_TOOL_MISMATCH", repr(current.get("task_name")))
|
||||
parameters = _require_mapping(current.get("parameters"), "TASK_PARAMETERS_REQUIRED")
|
||||
expected_parameter_keys = set(EXPECTED_PARAMETERS) | {"code"}
|
||||
if set(parameters) != expected_parameter_keys:
|
||||
raise ProjectionError(
|
||||
"RUN_CODE_PARAMETER_MISMATCH",
|
||||
f"{key}: expected {expected!r}, observed {parameters.get(key)!r}",
|
||||
"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",
|
||||
)
|
||||
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"]},
|
||||
"IN": {"nexts": [EXPECTED_PREPARE_TASK_NAME], "wait_until": []},
|
||||
EXPECTED_PREPARE_TASK_NAME: {"nexts": [EXPECTED_TASK_NAME], "wait_until": ["IN"]},
|
||||
EXPECTED_TASK_NAME: {"nexts": ["OUT"], "wait_until": [EXPECTED_PREPARE_TASK_NAME]},
|
||||
"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
|
||||
return stage, prepare_task, task
|
||||
|
||||
|
||||
def extract_run_code_task(document: Mapping[str, Any]) -> tuple[dict[str, Any], dict[str, Any]]:
|
||||
"""Compatibility accessor for the ingress task; validates the complete DAG."""
|
||||
stage, _prepare, ingress = extract_run_code_tasks(document)
|
||||
return stage, ingress
|
||||
|
||||
|
||||
def _import_root(name: str | None) -> str:
|
||||
@@ -435,7 +464,7 @@ def _is_localdocs_endpoint(node: ast.expr) -> bool:
|
||||
)
|
||||
|
||||
|
||||
def validate_inline_code(code: str) -> dict[str, Any]:
|
||||
def validate_inline_code(code: str, *, prepare: bool = False) -> dict[str, Any]:
|
||||
code_bytes = code.encode("utf-8")
|
||||
try:
|
||||
compile(code, "<Stage_2_S2_00.parameters.code>", "exec", dont_inherit=True)
|
||||
@@ -443,12 +472,19 @@ def validate_inline_code(code: str) -> dict[str, Any]:
|
||||
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)
|
||||
required_tokens = (
|
||||
(EXPECTED_LOCALDOCS_URL, "{{__user_hash__}}", "{{__workspace_hash__}}",
|
||||
"build_request", "prepare_request", "write_binary_verified",
|
||||
"read_binary_doc", "stage2_control/s2_00_request.json")
|
||||
if prepare else REQUIRED_CODE_TOKENS
|
||||
)
|
||||
missing_tokens = [token for token in required_tokens if token not in code]
|
||||
if not prepare:
|
||||
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):
|
||||
@@ -651,6 +687,8 @@ def _canonical_task_semantics(document: Mapping[str, Any], stage: Mapping[str, A
|
||||
"MCP_SERVERS_OBJECT_REQUIRED",
|
||||
)
|
||||
parameters = _require_mapping(task.get("parameters"), "TASK_PARAMETERS_REQUIRED")
|
||||
prepare_task = _require_list(stage.get("tasks"), "TASKS_ARRAY_REQUIRED")[0]
|
||||
prepare_parameters = _require_mapping(prepare_task.get("parameters"), "PREPARE_PARAMETERS_REQUIRED")
|
||||
return {
|
||||
"agent": {"name": agent.get("name"), "version": agent.get("version")},
|
||||
"stage": {
|
||||
@@ -663,6 +701,16 @@ def _canonical_task_semantics(document: Mapping[str, Any], stage: Mapping[str, A
|
||||
for name, value in sorted(servers.items())
|
||||
if isinstance(value, dict)
|
||||
},
|
||||
"prepare_task": {
|
||||
"task_name": prepare_task.get("task_name"),
|
||||
"mcp": prepare_task.get("mcp"),
|
||||
"tool_name": prepare_task.get("tool_name"),
|
||||
"parameters": {
|
||||
key: prepare_parameters.get(key)
|
||||
for key in ("language", "requirements", "network", "timeout")
|
||||
},
|
||||
"code_sha256": sha256_bytes(prepare_parameters["code"].encode("utf-8")),
|
||||
},
|
||||
"task": {
|
||||
"task_name": task.get("task_name"),
|
||||
"mcp": task.get("mcp"),
|
||||
@@ -681,7 +729,10 @@ def expected_outputs(
|
||||
authoring_raw: bytes,
|
||||
document: Mapping[str, Any],
|
||||
) -> tuple[dict[Path, bytes], dict[str, Any]]:
|
||||
stage, task = extract_run_code_task(document)
|
||||
stage, prepare_task, task = extract_run_code_tasks(document)
|
||||
prepare_code = _require_mapping(prepare_task.get("parameters"), "PREPARE_PARAMETERS_REQUIRED")["code"]
|
||||
prepare_validation = validate_inline_code(prepare_code, prepare=True)
|
||||
prepare_bytes = prepare_code.encode("utf-8")
|
||||
parameters = _require_mapping(task.get("parameters"), "TASK_PARAMETERS_REQUIRED")
|
||||
code = parameters["code"]
|
||||
validation = validate_inline_code(code)
|
||||
@@ -709,12 +760,30 @@ def expected_outputs(
|
||||
"canonical_task_semantics_sha256": semantics_sha256,
|
||||
},
|
||||
"canonical_code": {
|
||||
"yaml_pointer": "/Agent/Stages/0/tasks/0/parameters/code",
|
||||
"yaml_pointer": "/Agent/Stages/0/tasks/1/parameters/code",
|
||||
"encoding": "UTF-8",
|
||||
"extraction_transform": "NONE",
|
||||
**validation,
|
||||
},
|
||||
"prepare_code": {
|
||||
"yaml_pointer": "/Agent/Stages/0/tasks/0/parameters/code",
|
||||
"encoding": "UTF-8",
|
||||
"extraction_transform": "NONE",
|
||||
**prepare_validation,
|
||||
},
|
||||
"full_code_mirrors": [
|
||||
{
|
||||
"path": _logical_path(PREPARE_MIRROR_PY_PATH),
|
||||
"sha256": prepare_validation["code_sha256"],
|
||||
"size_bytes": len(prepare_bytes),
|
||||
"byte_identical_to_canonical_code": True,
|
||||
},
|
||||
{
|
||||
"path": _logical_path(PREPARE_MIRROR_TXT_PATH),
|
||||
"sha256": prepare_validation["code_sha256"],
|
||||
"size_bytes": len(prepare_bytes),
|
||||
"byte_identical_to_canonical_code": True,
|
||||
},
|
||||
{
|
||||
"path": _logical_path(MIRROR_PY_PATH),
|
||||
"sha256": validation["code_sha256"],
|
||||
@@ -733,6 +802,8 @@ def expected_outputs(
|
||||
}
|
||||
outputs = {
|
||||
PROJECTION_PATH: authoring_raw,
|
||||
PREPARE_MIRROR_PY_PATH: prepare_bytes,
|
||||
PREPARE_MIRROR_TXT_PATH: prepare_bytes,
|
||||
MIRROR_PY_PATH: code_bytes,
|
||||
MIRROR_TXT_PATH: code_bytes,
|
||||
RECEIPT_PATH: canonical_json_bytes(receipt),
|
||||
|
||||
+107
-36
@@ -32,10 +32,13 @@ AUTHORING_PATH = MAIN_WORKING_DIRECTORY / "Stage_2_S2_00_v.2.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"
|
||||
PREPARE_MIRROR_PY_PATH = DEPLOYMENT_ROOT / "runtime" / "s2_00_prepare_request.py"
|
||||
PREPARE_MIRROR_TXT_PATH = DEPLOYMENT_ROOT / "runtime" / "s2_00_prepare_request.txt"
|
||||
RECEIPT_PATH = DEPLOYMENT_ROOT / "manifest" / "s2_00_inline_code_receipt.json"
|
||||
BINDING_PATH = DEPLOYMENT_ROOT / "deployment" / "stage2_code_executor_binding.yml"
|
||||
|
||||
EXPECTED_TASK_NAME = "Task_S2_00_deterministic_ingress"
|
||||
EXPECTED_PREPARE_TASK_NAME = "Task_S2_00_prepare_request"
|
||||
EXPECTED_AGENT_NAME = "Stage_2_S2_00_v2"
|
||||
EXPECTED_AGENT_VERSION = "1.2.0"
|
||||
EXPECTED_PARAMETERS = {
|
||||
@@ -252,6 +255,20 @@ def select_s2_00_binding(document: Mapping[str, Any]) -> dict[str, Any]:
|
||||
"expected": None,
|
||||
"observed": row.get("external_mcp_contract"),
|
||||
}
|
||||
expected_prepare_contract = {
|
||||
"task_name": EXPECTED_PREPARE_TASK_NAME,
|
||||
"yaml_pointer": "/Agent/Stages/0/tasks/0/parameters/code",
|
||||
"required_external_arguments": ["request_id", "attempt_id", "stage1_run_root_ref", "stage1_deployment_root_ref"],
|
||||
"write_path": "stage2_control/s2_00_request.json",
|
||||
"argument_binding_status": "UNVERIFIED",
|
||||
"success_gate_status": "UNVERIFIED",
|
||||
"workspace_serialization_status": "UNVERIFIED",
|
||||
}
|
||||
if row.get("prepare_request_contract") != expected_prepare_contract:
|
||||
drift["prepare_request_contract"] = {
|
||||
"expected": expected_prepare_contract,
|
||||
"observed": row.get("prepare_request_contract"),
|
||||
}
|
||||
if drift:
|
||||
raise ProjectionError(
|
||||
"S2_00_BINDING_SEMANTICS_DRIFT",
|
||||
@@ -280,7 +297,7 @@ def _require_list(value: Any, code: str) -> list[Any]:
|
||||
return value
|
||||
|
||||
|
||||
def extract_run_code_task(document: Mapping[str, Any]) -> tuple[dict[str, Any], dict[str, Any]]:
|
||||
def extract_run_code_tasks(document: Mapping[str, Any]) -> tuple[dict[str, Any], 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(
|
||||
@@ -324,9 +341,10 @@ def extract_run_code_task(document: Mapping[str, Any]) -> tuple[dict[str, Any],
|
||||
)
|
||||
|
||||
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")
|
||||
if len(tasks) != 2:
|
||||
raise ProjectionError("EXACTLY_TWO_TASKS_REQUIRED", str(len(tasks)))
|
||||
prepare_task = _require_mapping(tasks[0], "PREPARE_TASK_OBJECT_REQUIRED")
|
||||
task = _require_mapping(tasks[1], "INGRESS_TASK_OBJECT_REQUIRED")
|
||||
run_code_tasks = [
|
||||
row
|
||||
for row in tasks
|
||||
@@ -334,43 +352,54 @@ def extract_run_code_task(document: Mapping[str, Any]) -> tuple[dict[str, Any],
|
||||
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 len(run_code_tasks) != 2:
|
||||
raise ProjectionError("EXACTLY_TWO_RUN_CODE_REQUIRED", str(len(run_code_tasks)))
|
||||
if prepare_task.get("task_name") != EXPECTED_PREPARE_TASK_NAME:
|
||||
raise ProjectionError("PREPARE_TASK_NAME_MISMATCH", repr(prepare_task.get("task_name")))
|
||||
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:
|
||||
for current in (prepare_task, task):
|
||||
if current.get("mcp") != "code-executor" or current.get("tool_name") != "run_code":
|
||||
raise ProjectionError("RUN_CODE_TOOL_MISMATCH", repr(current.get("task_name")))
|
||||
parameters = _require_mapping(current.get("parameters"), "TASK_PARAMETERS_REQUIRED")
|
||||
expected_parameter_keys = set(EXPECTED_PARAMETERS) | {"code"}
|
||||
if set(parameters) != expected_parameter_keys:
|
||||
raise ProjectionError(
|
||||
"RUN_CODE_PARAMETER_MISMATCH",
|
||||
f"{key}: expected {expected!r}, observed {parameters.get(key)!r}",
|
||||
"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",
|
||||
)
|
||||
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"]},
|
||||
"IN": {"nexts": [EXPECTED_PREPARE_TASK_NAME], "wait_until": []},
|
||||
EXPECTED_PREPARE_TASK_NAME: {"nexts": [EXPECTED_TASK_NAME], "wait_until": ["IN"]},
|
||||
EXPECTED_TASK_NAME: {"nexts": ["OUT"], "wait_until": [EXPECTED_PREPARE_TASK_NAME]},
|
||||
"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
|
||||
return stage, prepare_task, task
|
||||
|
||||
|
||||
def extract_run_code_task(document: Mapping[str, Any]) -> tuple[dict[str, Any], dict[str, Any]]:
|
||||
"""Compatibility accessor for the ingress task; validates the complete DAG."""
|
||||
stage, _prepare, ingress = extract_run_code_tasks(document)
|
||||
return stage, ingress
|
||||
|
||||
|
||||
def _import_root(name: str | None) -> str:
|
||||
@@ -435,7 +464,7 @@ def _is_localdocs_endpoint(node: ast.expr) -> bool:
|
||||
)
|
||||
|
||||
|
||||
def validate_inline_code(code: str) -> dict[str, Any]:
|
||||
def validate_inline_code(code: str, *, prepare: bool = False) -> dict[str, Any]:
|
||||
code_bytes = code.encode("utf-8")
|
||||
try:
|
||||
compile(code, "<Stage_2_S2_00.parameters.code>", "exec", dont_inherit=True)
|
||||
@@ -443,12 +472,19 @@ def validate_inline_code(code: str) -> dict[str, Any]:
|
||||
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)
|
||||
required_tokens = (
|
||||
(EXPECTED_LOCALDOCS_URL, "{{__user_hash__}}", "{{__workspace_hash__}}",
|
||||
"build_request", "prepare_request", "write_binary_verified",
|
||||
"read_binary_doc", "stage2_control/s2_00_request.json")
|
||||
if prepare else REQUIRED_CODE_TOKENS
|
||||
)
|
||||
missing_tokens = [token for token in required_tokens if token not in code]
|
||||
if not prepare:
|
||||
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):
|
||||
@@ -651,6 +687,8 @@ def _canonical_task_semantics(document: Mapping[str, Any], stage: Mapping[str, A
|
||||
"MCP_SERVERS_OBJECT_REQUIRED",
|
||||
)
|
||||
parameters = _require_mapping(task.get("parameters"), "TASK_PARAMETERS_REQUIRED")
|
||||
prepare_task = _require_list(stage.get("tasks"), "TASKS_ARRAY_REQUIRED")[0]
|
||||
prepare_parameters = _require_mapping(prepare_task.get("parameters"), "PREPARE_PARAMETERS_REQUIRED")
|
||||
return {
|
||||
"agent": {"name": agent.get("name"), "version": agent.get("version")},
|
||||
"stage": {
|
||||
@@ -663,6 +701,16 @@ def _canonical_task_semantics(document: Mapping[str, Any], stage: Mapping[str, A
|
||||
for name, value in sorted(servers.items())
|
||||
if isinstance(value, dict)
|
||||
},
|
||||
"prepare_task": {
|
||||
"task_name": prepare_task.get("task_name"),
|
||||
"mcp": prepare_task.get("mcp"),
|
||||
"tool_name": prepare_task.get("tool_name"),
|
||||
"parameters": {
|
||||
key: prepare_parameters.get(key)
|
||||
for key in ("language", "requirements", "network", "timeout")
|
||||
},
|
||||
"code_sha256": sha256_bytes(prepare_parameters["code"].encode("utf-8")),
|
||||
},
|
||||
"task": {
|
||||
"task_name": task.get("task_name"),
|
||||
"mcp": task.get("mcp"),
|
||||
@@ -681,7 +729,10 @@ def expected_outputs(
|
||||
authoring_raw: bytes,
|
||||
document: Mapping[str, Any],
|
||||
) -> tuple[dict[Path, bytes], dict[str, Any]]:
|
||||
stage, task = extract_run_code_task(document)
|
||||
stage, prepare_task, task = extract_run_code_tasks(document)
|
||||
prepare_code = _require_mapping(prepare_task.get("parameters"), "PREPARE_PARAMETERS_REQUIRED")["code"]
|
||||
prepare_validation = validate_inline_code(prepare_code, prepare=True)
|
||||
prepare_bytes = prepare_code.encode("utf-8")
|
||||
parameters = _require_mapping(task.get("parameters"), "TASK_PARAMETERS_REQUIRED")
|
||||
code = parameters["code"]
|
||||
validation = validate_inline_code(code)
|
||||
@@ -709,12 +760,30 @@ def expected_outputs(
|
||||
"canonical_task_semantics_sha256": semantics_sha256,
|
||||
},
|
||||
"canonical_code": {
|
||||
"yaml_pointer": "/Agent/Stages/0/tasks/0/parameters/code",
|
||||
"yaml_pointer": "/Agent/Stages/0/tasks/1/parameters/code",
|
||||
"encoding": "UTF-8",
|
||||
"extraction_transform": "NONE",
|
||||
**validation,
|
||||
},
|
||||
"prepare_code": {
|
||||
"yaml_pointer": "/Agent/Stages/0/tasks/0/parameters/code",
|
||||
"encoding": "UTF-8",
|
||||
"extraction_transform": "NONE",
|
||||
**prepare_validation,
|
||||
},
|
||||
"full_code_mirrors": [
|
||||
{
|
||||
"path": _logical_path(PREPARE_MIRROR_PY_PATH),
|
||||
"sha256": prepare_validation["code_sha256"],
|
||||
"size_bytes": len(prepare_bytes),
|
||||
"byte_identical_to_canonical_code": True,
|
||||
},
|
||||
{
|
||||
"path": _logical_path(PREPARE_MIRROR_TXT_PATH),
|
||||
"sha256": prepare_validation["code_sha256"],
|
||||
"size_bytes": len(prepare_bytes),
|
||||
"byte_identical_to_canonical_code": True,
|
||||
},
|
||||
{
|
||||
"path": _logical_path(MIRROR_PY_PATH),
|
||||
"sha256": validation["code_sha256"],
|
||||
@@ -733,6 +802,8 @@ def expected_outputs(
|
||||
}
|
||||
outputs = {
|
||||
PROJECTION_PATH: authoring_raw,
|
||||
PREPARE_MIRROR_PY_PATH: prepare_bytes,
|
||||
PREPARE_MIRROR_TXT_PATH: prepare_bytes,
|
||||
MIRROR_PY_PATH: code_bytes,
|
||||
MIRROR_TXT_PATH: code_bytes,
|
||||
RECEIPT_PATH: canonical_json_bytes(receipt),
|
||||
|
||||
+2
@@ -58,6 +58,8 @@ FORBIDDEN_PARENT_MEMBERS = frozenset(
|
||||
"agent_scripts/Stage_2_S2_00.yml",
|
||||
"runtime/s2_00_ingress.py",
|
||||
"runtime/s2_00_ingress.txt",
|
||||
"runtime/s2_00_prepare_request.py",
|
||||
"runtime/s2_00_prepare_request.txt",
|
||||
"manifest/s2_00_inline_code_receipt.json",
|
||||
"agent_scripts/Stage_2_S2_20.yml",
|
||||
"runtime/s2_20_reduce.py",
|
||||
|
||||
+2
@@ -58,6 +58,8 @@ FORBIDDEN_PARENT_MEMBERS = frozenset(
|
||||
"agent_scripts/Stage_2_S2_00.yml",
|
||||
"runtime/s2_00_ingress.py",
|
||||
"runtime/s2_00_ingress.txt",
|
||||
"runtime/s2_00_prepare_request.py",
|
||||
"runtime/s2_00_prepare_request.txt",
|
||||
"manifest/s2_00_inline_code_receipt.json",
|
||||
"agent_scripts/Stage_2_S2_20.yml",
|
||||
"runtime/s2_20_reduce.py",
|
||||
|
||||
+1
-1
@@ -149,7 +149,7 @@ _RAW_VALUE_UNSET = object()
|
||||
# literal placeholders in the offline parity mirror and its unit tests.
|
||||
INLINE_USER_HASH = "{{__user_hash__}}"
|
||||
INLINE_WORKSPACE_HASH = "{{__workspace_hash__}}"
|
||||
EXPECTED_STAGE2_RELEASE_SHA256 = "9fa85bb94c5f14f675dcdaa0cf94d06745b21675d97797539ffc14015dcc9f53"
|
||||
EXPECTED_STAGE2_RELEASE_SHA256 = "2363f1166ee8a3ea2b38350cde61fccfc9852a413c6b9f14a6ed6606af15a590"
|
||||
INLINE_REQUEST_PATH = "stage2_control/s2_00_request.json"
|
||||
INLINE_STAGE2_ASSET_ROOT = "Default_Agent/Stage_2_Clean"
|
||||
INLINE_STAGE2_RELEASE_PATH = (
|
||||
|
||||
+1
-1
@@ -149,7 +149,7 @@ _RAW_VALUE_UNSET = object()
|
||||
# literal placeholders in the offline parity mirror and its unit tests.
|
||||
INLINE_USER_HASH = "{{__user_hash__}}"
|
||||
INLINE_WORKSPACE_HASH = "{{__workspace_hash__}}"
|
||||
EXPECTED_STAGE2_RELEASE_SHA256 = "9fa85bb94c5f14f675dcdaa0cf94d06745b21675d97797539ffc14015dcc9f53"
|
||||
EXPECTED_STAGE2_RELEASE_SHA256 = "2363f1166ee8a3ea2b38350cde61fccfc9852a413c6b9f14a6ed6606af15a590"
|
||||
INLINE_REQUEST_PATH = "stage2_control/s2_00_request.json"
|
||||
INLINE_STAGE2_ASSET_ROOT = "Default_Agent/Stage_2_Clean"
|
||||
INLINE_STAGE2_RELEASE_PATH = (
|
||||
|
||||
+234
@@ -0,0 +1,234 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Prepare the fixed S2_00 control request from four explicit caller values.
|
||||
|
||||
The backend argument-delivery contract is not bound. Direct execution fails
|
||||
closed; callers must invoke ``prepare_request`` with the four named values.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import PurePosixPath
|
||||
import re
|
||||
import sys
|
||||
import unicodedata
|
||||
from typing import Any, Mapping
|
||||
|
||||
|
||||
REQUEST_PATH = "stage2_control/s2_00_request.json"
|
||||
LOCALDOCS_URL = "http://mcp-localdocs:8012/mcp"
|
||||
MCP_PROTOCOL_VERSION = "2025-03-26"
|
||||
INLINE_USER_HASH = "{{__user_hash__}}"
|
||||
INLINE_WORKSPACE_HASH = "{{__workspace_hash__}}"
|
||||
REQUEST_KEYS = frozenset({
|
||||
"schema_version", "workflow_id", "request_id", "attempt_id",
|
||||
"stage1_run_root_ref", "stage1_deployment_root_ref",
|
||||
})
|
||||
ID_RE = re.compile(r"[A-Za-z0-9][A-Za-z0-9._-]{0,127}\Z")
|
||||
|
||||
|
||||
class PrepareError(ValueError):
|
||||
def __init__(self, code: str, message: str) -> None:
|
||||
super().__init__(message)
|
||||
self.code = code
|
||||
|
||||
|
||||
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 _relative_path(value: str, *, code: str) -> str:
|
||||
if not isinstance(value, str) or not value or "\x00" in value or "\\" in value:
|
||||
raise PrepareError(code, "logical path is empty or malformed")
|
||||
if unicodedata.normalize("NFC", value) != value:
|
||||
raise PrepareError(code, "logical path must already be NFC")
|
||||
path = PurePosixPath(value)
|
||||
if path.is_absolute() or any(part in {"", ".", ".."} for part in path.parts):
|
||||
raise PrepareError(code, "logical path must be a contained relative path")
|
||||
rendered = path.as_posix()
|
||||
if rendered != value:
|
||||
raise PrepareError(code, "logical path is not canonical")
|
||||
return rendered
|
||||
|
||||
|
||||
def build_request(
|
||||
request_id: str,
|
||||
attempt_id: str,
|
||||
stage1_run_root_ref: str,
|
||||
stage1_deployment_root_ref: str,
|
||||
) -> bytes:
|
||||
"""Return the exact six-field canonical request; do not access localdocs."""
|
||||
if not isinstance(request_id, str) or ID_RE.fullmatch(request_id) is None:
|
||||
raise PrepareError("REQUEST_ID_INVALID", "request_id contains forbidden characters")
|
||||
if not isinstance(attempt_id, str) or ID_RE.fullmatch(attempt_id) is None:
|
||||
raise PrepareError("ATTEMPT_ID_INVALID", "attempt_id contains forbidden characters")
|
||||
request = {
|
||||
"schema_version": "stage2_s2_00_execution_request.v1",
|
||||
"workflow_id": "S2_00",
|
||||
"request_id": request_id,
|
||||
"attempt_id": attempt_id,
|
||||
"stage1_run_root_ref": _relative_path(
|
||||
stage1_run_root_ref, code="STAGE1_RUN_ROOT_REF_INVALID"),
|
||||
"stage1_deployment_root_ref": _relative_path(
|
||||
stage1_deployment_root_ref, code="STAGE1_DEPLOYMENT_ROOT_REF_INVALID"),
|
||||
}
|
||||
if set(request) != REQUEST_KEYS:
|
||||
raise PrepareError("RUN_REQUEST_CLOSED_SHAPE", "request field set drifted")
|
||||
return canonical_json_bytes(request)
|
||||
|
||||
|
||||
class LocaldocsSession:
|
||||
"""Small localdocs JSON-RPC session with verified binary write/read-back."""
|
||||
|
||||
def __init__(self, user_hash: str, workspace_hash: str, *, client: Any = None) -> None:
|
||||
for name, value in (("user_hash", user_hash), ("workspace_hash", workspace_hash)):
|
||||
if not isinstance(value, str) or re.fullmatch(r"[a-f0-9]{64}", value) is None:
|
||||
raise PrepareError("CONTEXT_HASH_INVALID", f"{name} is not a SHA-256 digest")
|
||||
self.user_hash = user_hash
|
||||
self.workspace_hash = workspace_hash
|
||||
if client is None:
|
||||
try:
|
||||
import httpx
|
||||
except ImportError as exc:
|
||||
raise PrepareError("HTTPX_UNAVAILABLE", "httpx==0.28.1 is required") from exc
|
||||
client = httpx.Client(timeout=60)
|
||||
self.client = client
|
||||
self.headers = {"Content-Type": "application/json", "Accept": "application/json, text/event-stream"}
|
||||
self.session_id: str | None = None
|
||||
self.next_id = 10
|
||||
self.initialized = False
|
||||
|
||||
def close(self) -> None:
|
||||
self.client.close()
|
||||
|
||||
def _post(self, body: Mapping[str, Any], expected_id: int | None) -> Mapping[str, Any] | None:
|
||||
try:
|
||||
response = self.client.post(LOCALDOCS_URL, json=dict(body), headers=dict(self.headers))
|
||||
response.raise_for_status()
|
||||
except Exception as exc:
|
||||
raise PrepareError("MCP_TRANSPORT_ERROR", "localdocs transport failed") from exc
|
||||
session_id = response.headers.get("mcp-session-id")
|
||||
if session_id:
|
||||
if self.session_id is None and expected_id == 1:
|
||||
self.session_id = session_id
|
||||
elif session_id != self.session_id:
|
||||
raise PrepareError("MCP_SESSION_ID_CHANGED", "localdocs session changed")
|
||||
self.headers["mcp-session-id"] = session_id
|
||||
if expected_id is None:
|
||||
return None
|
||||
try:
|
||||
payload = response.json()
|
||||
except Exception as exc:
|
||||
raise PrepareError("MCP_RESPONSE_INVALID", "localdocs response is not JSON") from exc
|
||||
if not isinstance(payload, dict) or payload.get("jsonrpc") != "2.0" or payload.get("id") != expected_id:
|
||||
raise PrepareError("MCP_RESPONSE_INVALID", "localdocs response ID or shape mismatch")
|
||||
if "error" in payload or not isinstance(payload.get("result"), dict):
|
||||
raise PrepareError("MCP_TOOL_ERROR", "localdocs returned an error")
|
||||
return payload["result"]
|
||||
|
||||
def initialize(self) -> None:
|
||||
response = self._post({"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {
|
||||
"protocolVersion": MCP_PROTOCOL_VERSION, "capabilities": {}, "clientInfo": {
|
||||
"name": "liti-s2-00-prepare", "version": "1.0.0",
|
||||
"user_id": self.user_hash, "workspace_id": self.workspace_hash,
|
||||
}}}, 1)
|
||||
if response is None or response.get("protocolVersion") != MCP_PROTOCOL_VERSION or self.session_id is None:
|
||||
raise PrepareError("MCP_INITIALIZE_INVALID", "localdocs initialization failed")
|
||||
self._post({"jsonrpc": "2.0", "method": "notifications/initialized"}, None)
|
||||
self.initialized = True
|
||||
|
||||
def call(self, name: str, arguments: Mapping[str, Any]) -> Mapping[str, Any]:
|
||||
if not self.initialized:
|
||||
raise PrepareError("MCP_NOT_INITIALIZED", "localdocs is not initialized")
|
||||
message_id = self.next_id
|
||||
self.next_id += 1
|
||||
result = self._post({"jsonrpc": "2.0", "id": message_id, "method": "tools/call",
|
||||
"params": {"name": name, "arguments": dict(arguments)}}, message_id)
|
||||
if result is None or result.get("isError") is True:
|
||||
raise PrepareError("MCP_TOOL_ERROR", f"localdocs {name} failed")
|
||||
return result
|
||||
|
||||
def read_binary(self, logical_path: str) -> bytes:
|
||||
path = _relative_path(logical_path, code="LOCALDOCS_READ_PATH_INVALID")
|
||||
result = self.call("read_binary_doc", {"doc_name": path})
|
||||
content = result.get("content")
|
||||
if not isinstance(content, list) or len(content) != 1 or not isinstance(content[0], dict) or content[0].get("type") != "text":
|
||||
raise PrepareError("LOCALDOCS_READ_SHAPE", "invalid binary response")
|
||||
text = content[0].get("text")
|
||||
if not isinstance(text, str):
|
||||
raise PrepareError("LOCALDOCS_READ_SHAPE", "missing binary envelope")
|
||||
try:
|
||||
envelope = json.loads(text)
|
||||
if isinstance(envelope, dict) and "results" in envelope:
|
||||
rows = envelope["results"]
|
||||
if not isinstance(rows, list) or len(rows) != 1 or not isinstance(rows[0], dict):
|
||||
raise ValueError("binary result cardinality mismatch")
|
||||
inner = rows[0].get("content", rows[0].get("text"))
|
||||
envelope = json.loads(inner) if isinstance(inner, str) else inner
|
||||
encoded = envelope["content_base64"]
|
||||
if not isinstance(encoded, str):
|
||||
raise ValueError("binary content is not base64")
|
||||
payload = base64.b64decode(encoded, validate=True)
|
||||
size = envelope.get("byte_length", envelope.get("size"))
|
||||
if size is not None and (not isinstance(size, int) or size != len(payload)):
|
||||
raise ValueError("binary size mismatch")
|
||||
digest = envelope.get("sha256")
|
||||
if digest is not None and digest != hashlib.sha256(payload).hexdigest():
|
||||
raise ValueError("binary hash mismatch")
|
||||
return payload
|
||||
except (ValueError, KeyError, TypeError, base64.binascii.Error) as exc:
|
||||
raise PrepareError("LOCALDOCS_READ_SHAPE", "invalid binary envelope") from exc
|
||||
|
||||
def write_binary_verified(self, logical_path: str, payload: bytes) -> str:
|
||||
path = _relative_path(logical_path, code="LOCALDOCS_WRITE_PATH_INVALID")
|
||||
if path != REQUEST_PATH:
|
||||
raise PrepareError("LOCALDOCS_WRITE_PATH_INVALID", "prepare may write only the fixed request path")
|
||||
result = self.call("write_binary_file", {
|
||||
"path": path, "content_base64": base64.b64encode(payload).decode("ascii"), "overwrite": True,
|
||||
})
|
||||
if not isinstance(result.get("content"), list) or result.get("isError") is True:
|
||||
raise PrepareError("LOCALDOCS_WRITE_FAILED", "localdocs did not acknowledge write")
|
||||
observed = self.read_binary(path)
|
||||
if observed != payload:
|
||||
raise PrepareError("LOCALDOCS_WRITE_READBACK_MISMATCH", "request read-back differs")
|
||||
return hashlib.sha256(observed).hexdigest()
|
||||
|
||||
|
||||
def prepare_request(
|
||||
request_id: str,
|
||||
attempt_id: str,
|
||||
stage1_run_root_ref: str,
|
||||
stage1_deployment_root_ref: str,
|
||||
*,
|
||||
localdocs: Any = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Validate, write, read back, and close an authenticated localdocs session."""
|
||||
payload = build_request(request_id, attempt_id, stage1_run_root_ref, stage1_deployment_root_ref)
|
||||
session = localdocs if localdocs is not None else LocaldocsSession(INLINE_USER_HASH, INLINE_WORKSPACE_HASH)
|
||||
failure: Exception | None = None
|
||||
digest: str | None = None
|
||||
try:
|
||||
session.initialize()
|
||||
digest = session.write_binary_verified(REQUEST_PATH, payload)
|
||||
except Exception as exc:
|
||||
failure = exc
|
||||
try:
|
||||
session.close()
|
||||
except Exception as exc:
|
||||
if failure is None:
|
||||
failure = PrepareError("LOCALDOCS_CLOSE_FAILED", "localdocs session close failed")
|
||||
failure.__cause__ = exc
|
||||
if failure is not None:
|
||||
raise failure
|
||||
return {"ok": True, "workflow_id": "S2_00", "path": REQUEST_PATH,
|
||||
"request_sha256": digest}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.stdout.buffer.write(canonical_json_bytes({
|
||||
"ok": False, "error": {"code": "PREPARE_ARGUMENT_BINDING_UNVERIFIED",
|
||||
"message": "four caller values must be bound by the backend"}}))
|
||||
raise SystemExit(2)
|
||||
+234
@@ -0,0 +1,234 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Prepare the fixed S2_00 control request from four explicit caller values.
|
||||
|
||||
The backend argument-delivery contract is not bound. Direct execution fails
|
||||
closed; callers must invoke ``prepare_request`` with the four named values.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import PurePosixPath
|
||||
import re
|
||||
import sys
|
||||
import unicodedata
|
||||
from typing import Any, Mapping
|
||||
|
||||
|
||||
REQUEST_PATH = "stage2_control/s2_00_request.json"
|
||||
LOCALDOCS_URL = "http://mcp-localdocs:8012/mcp"
|
||||
MCP_PROTOCOL_VERSION = "2025-03-26"
|
||||
INLINE_USER_HASH = "{{__user_hash__}}"
|
||||
INLINE_WORKSPACE_HASH = "{{__workspace_hash__}}"
|
||||
REQUEST_KEYS = frozenset({
|
||||
"schema_version", "workflow_id", "request_id", "attempt_id",
|
||||
"stage1_run_root_ref", "stage1_deployment_root_ref",
|
||||
})
|
||||
ID_RE = re.compile(r"[A-Za-z0-9][A-Za-z0-9._-]{0,127}\Z")
|
||||
|
||||
|
||||
class PrepareError(ValueError):
|
||||
def __init__(self, code: str, message: str) -> None:
|
||||
super().__init__(message)
|
||||
self.code = code
|
||||
|
||||
|
||||
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 _relative_path(value: str, *, code: str) -> str:
|
||||
if not isinstance(value, str) or not value or "\x00" in value or "\\" in value:
|
||||
raise PrepareError(code, "logical path is empty or malformed")
|
||||
if unicodedata.normalize("NFC", value) != value:
|
||||
raise PrepareError(code, "logical path must already be NFC")
|
||||
path = PurePosixPath(value)
|
||||
if path.is_absolute() or any(part in {"", ".", ".."} for part in path.parts):
|
||||
raise PrepareError(code, "logical path must be a contained relative path")
|
||||
rendered = path.as_posix()
|
||||
if rendered != value:
|
||||
raise PrepareError(code, "logical path is not canonical")
|
||||
return rendered
|
||||
|
||||
|
||||
def build_request(
|
||||
request_id: str,
|
||||
attempt_id: str,
|
||||
stage1_run_root_ref: str,
|
||||
stage1_deployment_root_ref: str,
|
||||
) -> bytes:
|
||||
"""Return the exact six-field canonical request; do not access localdocs."""
|
||||
if not isinstance(request_id, str) or ID_RE.fullmatch(request_id) is None:
|
||||
raise PrepareError("REQUEST_ID_INVALID", "request_id contains forbidden characters")
|
||||
if not isinstance(attempt_id, str) or ID_RE.fullmatch(attempt_id) is None:
|
||||
raise PrepareError("ATTEMPT_ID_INVALID", "attempt_id contains forbidden characters")
|
||||
request = {
|
||||
"schema_version": "stage2_s2_00_execution_request.v1",
|
||||
"workflow_id": "S2_00",
|
||||
"request_id": request_id,
|
||||
"attempt_id": attempt_id,
|
||||
"stage1_run_root_ref": _relative_path(
|
||||
stage1_run_root_ref, code="STAGE1_RUN_ROOT_REF_INVALID"),
|
||||
"stage1_deployment_root_ref": _relative_path(
|
||||
stage1_deployment_root_ref, code="STAGE1_DEPLOYMENT_ROOT_REF_INVALID"),
|
||||
}
|
||||
if set(request) != REQUEST_KEYS:
|
||||
raise PrepareError("RUN_REQUEST_CLOSED_SHAPE", "request field set drifted")
|
||||
return canonical_json_bytes(request)
|
||||
|
||||
|
||||
class LocaldocsSession:
|
||||
"""Small localdocs JSON-RPC session with verified binary write/read-back."""
|
||||
|
||||
def __init__(self, user_hash: str, workspace_hash: str, *, client: Any = None) -> None:
|
||||
for name, value in (("user_hash", user_hash), ("workspace_hash", workspace_hash)):
|
||||
if not isinstance(value, str) or re.fullmatch(r"[a-f0-9]{64}", value) is None:
|
||||
raise PrepareError("CONTEXT_HASH_INVALID", f"{name} is not a SHA-256 digest")
|
||||
self.user_hash = user_hash
|
||||
self.workspace_hash = workspace_hash
|
||||
if client is None:
|
||||
try:
|
||||
import httpx
|
||||
except ImportError as exc:
|
||||
raise PrepareError("HTTPX_UNAVAILABLE", "httpx==0.28.1 is required") from exc
|
||||
client = httpx.Client(timeout=60)
|
||||
self.client = client
|
||||
self.headers = {"Content-Type": "application/json", "Accept": "application/json, text/event-stream"}
|
||||
self.session_id: str | None = None
|
||||
self.next_id = 10
|
||||
self.initialized = False
|
||||
|
||||
def close(self) -> None:
|
||||
self.client.close()
|
||||
|
||||
def _post(self, body: Mapping[str, Any], expected_id: int | None) -> Mapping[str, Any] | None:
|
||||
try:
|
||||
response = self.client.post(LOCALDOCS_URL, json=dict(body), headers=dict(self.headers))
|
||||
response.raise_for_status()
|
||||
except Exception as exc:
|
||||
raise PrepareError("MCP_TRANSPORT_ERROR", "localdocs transport failed") from exc
|
||||
session_id = response.headers.get("mcp-session-id")
|
||||
if session_id:
|
||||
if self.session_id is None and expected_id == 1:
|
||||
self.session_id = session_id
|
||||
elif session_id != self.session_id:
|
||||
raise PrepareError("MCP_SESSION_ID_CHANGED", "localdocs session changed")
|
||||
self.headers["mcp-session-id"] = session_id
|
||||
if expected_id is None:
|
||||
return None
|
||||
try:
|
||||
payload = response.json()
|
||||
except Exception as exc:
|
||||
raise PrepareError("MCP_RESPONSE_INVALID", "localdocs response is not JSON") from exc
|
||||
if not isinstance(payload, dict) or payload.get("jsonrpc") != "2.0" or payload.get("id") != expected_id:
|
||||
raise PrepareError("MCP_RESPONSE_INVALID", "localdocs response ID or shape mismatch")
|
||||
if "error" in payload or not isinstance(payload.get("result"), dict):
|
||||
raise PrepareError("MCP_TOOL_ERROR", "localdocs returned an error")
|
||||
return payload["result"]
|
||||
|
||||
def initialize(self) -> None:
|
||||
response = self._post({"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {
|
||||
"protocolVersion": MCP_PROTOCOL_VERSION, "capabilities": {}, "clientInfo": {
|
||||
"name": "liti-s2-00-prepare", "version": "1.0.0",
|
||||
"user_id": self.user_hash, "workspace_id": self.workspace_hash,
|
||||
}}}, 1)
|
||||
if response is None or response.get("protocolVersion") != MCP_PROTOCOL_VERSION or self.session_id is None:
|
||||
raise PrepareError("MCP_INITIALIZE_INVALID", "localdocs initialization failed")
|
||||
self._post({"jsonrpc": "2.0", "method": "notifications/initialized"}, None)
|
||||
self.initialized = True
|
||||
|
||||
def call(self, name: str, arguments: Mapping[str, Any]) -> Mapping[str, Any]:
|
||||
if not self.initialized:
|
||||
raise PrepareError("MCP_NOT_INITIALIZED", "localdocs is not initialized")
|
||||
message_id = self.next_id
|
||||
self.next_id += 1
|
||||
result = self._post({"jsonrpc": "2.0", "id": message_id, "method": "tools/call",
|
||||
"params": {"name": name, "arguments": dict(arguments)}}, message_id)
|
||||
if result is None or result.get("isError") is True:
|
||||
raise PrepareError("MCP_TOOL_ERROR", f"localdocs {name} failed")
|
||||
return result
|
||||
|
||||
def read_binary(self, logical_path: str) -> bytes:
|
||||
path = _relative_path(logical_path, code="LOCALDOCS_READ_PATH_INVALID")
|
||||
result = self.call("read_binary_doc", {"doc_name": path})
|
||||
content = result.get("content")
|
||||
if not isinstance(content, list) or len(content) != 1 or not isinstance(content[0], dict) or content[0].get("type") != "text":
|
||||
raise PrepareError("LOCALDOCS_READ_SHAPE", "invalid binary response")
|
||||
text = content[0].get("text")
|
||||
if not isinstance(text, str):
|
||||
raise PrepareError("LOCALDOCS_READ_SHAPE", "missing binary envelope")
|
||||
try:
|
||||
envelope = json.loads(text)
|
||||
if isinstance(envelope, dict) and "results" in envelope:
|
||||
rows = envelope["results"]
|
||||
if not isinstance(rows, list) or len(rows) != 1 or not isinstance(rows[0], dict):
|
||||
raise ValueError("binary result cardinality mismatch")
|
||||
inner = rows[0].get("content", rows[0].get("text"))
|
||||
envelope = json.loads(inner) if isinstance(inner, str) else inner
|
||||
encoded = envelope["content_base64"]
|
||||
if not isinstance(encoded, str):
|
||||
raise ValueError("binary content is not base64")
|
||||
payload = base64.b64decode(encoded, validate=True)
|
||||
size = envelope.get("byte_length", envelope.get("size"))
|
||||
if size is not None and (not isinstance(size, int) or size != len(payload)):
|
||||
raise ValueError("binary size mismatch")
|
||||
digest = envelope.get("sha256")
|
||||
if digest is not None and digest != hashlib.sha256(payload).hexdigest():
|
||||
raise ValueError("binary hash mismatch")
|
||||
return payload
|
||||
except (ValueError, KeyError, TypeError, base64.binascii.Error) as exc:
|
||||
raise PrepareError("LOCALDOCS_READ_SHAPE", "invalid binary envelope") from exc
|
||||
|
||||
def write_binary_verified(self, logical_path: str, payload: bytes) -> str:
|
||||
path = _relative_path(logical_path, code="LOCALDOCS_WRITE_PATH_INVALID")
|
||||
if path != REQUEST_PATH:
|
||||
raise PrepareError("LOCALDOCS_WRITE_PATH_INVALID", "prepare may write only the fixed request path")
|
||||
result = self.call("write_binary_file", {
|
||||
"path": path, "content_base64": base64.b64encode(payload).decode("ascii"), "overwrite": True,
|
||||
})
|
||||
if not isinstance(result.get("content"), list) or result.get("isError") is True:
|
||||
raise PrepareError("LOCALDOCS_WRITE_FAILED", "localdocs did not acknowledge write")
|
||||
observed = self.read_binary(path)
|
||||
if observed != payload:
|
||||
raise PrepareError("LOCALDOCS_WRITE_READBACK_MISMATCH", "request read-back differs")
|
||||
return hashlib.sha256(observed).hexdigest()
|
||||
|
||||
|
||||
def prepare_request(
|
||||
request_id: str,
|
||||
attempt_id: str,
|
||||
stage1_run_root_ref: str,
|
||||
stage1_deployment_root_ref: str,
|
||||
*,
|
||||
localdocs: Any = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Validate, write, read back, and close an authenticated localdocs session."""
|
||||
payload = build_request(request_id, attempt_id, stage1_run_root_ref, stage1_deployment_root_ref)
|
||||
session = localdocs if localdocs is not None else LocaldocsSession(INLINE_USER_HASH, INLINE_WORKSPACE_HASH)
|
||||
failure: Exception | None = None
|
||||
digest: str | None = None
|
||||
try:
|
||||
session.initialize()
|
||||
digest = session.write_binary_verified(REQUEST_PATH, payload)
|
||||
except Exception as exc:
|
||||
failure = exc
|
||||
try:
|
||||
session.close()
|
||||
except Exception as exc:
|
||||
if failure is None:
|
||||
failure = PrepareError("LOCALDOCS_CLOSE_FAILED", "localdocs session close failed")
|
||||
failure.__cause__ = exc
|
||||
if failure is not None:
|
||||
raise failure
|
||||
return {"ok": True, "workflow_id": "S2_00", "path": REQUEST_PATH,
|
||||
"request_sha256": digest}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.stdout.buffer.write(canonical_json_bytes({
|
||||
"ok": False, "error": {"code": "PREPARE_ARGUMENT_BINDING_UNVERIFIED",
|
||||
"message": "four caller values must be bound by the backend"}}))
|
||||
raise SystemExit(2)
|
||||
+1
-1
@@ -27,7 +27,7 @@ from typing import Any, Iterable, Mapping, Sequence
|
||||
|
||||
WORKFLOW_ID = "S2_20"
|
||||
ALGORITHM_VERSION = "s2_20_relief_plan/1.0.0"
|
||||
EXPECTED_STAGE2_RELEASE_SHA256 = "9fa85bb94c5f14f675dcdaa0cf94d06745b21675d97797539ffc14015dcc9f53"
|
||||
EXPECTED_STAGE2_RELEASE_SHA256 = "2363f1166ee8a3ea2b38350cde61fccfc9852a413c6b9f14a6ed6606af15a590"
|
||||
LOCALDOCS_URL = "http://mcp-localdocs:8012/mcp"
|
||||
WEAVIATE_MCP_URL = "https://weaviate.eroomai.com/mcp"
|
||||
MCP_PROTOCOL_VERSION = "2025-03-26"
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ from typing import Any, Iterable, Mapping, Sequence
|
||||
|
||||
WORKFLOW_ID = "S2_20"
|
||||
ALGORITHM_VERSION = "s2_20_relief_plan/1.0.0"
|
||||
EXPECTED_STAGE2_RELEASE_SHA256 = "9fa85bb94c5f14f675dcdaa0cf94d06745b21675d97797539ffc14015dcc9f53"
|
||||
EXPECTED_STAGE2_RELEASE_SHA256 = "2363f1166ee8a3ea2b38350cde61fccfc9852a413c6b9f14a6ed6606af15a590"
|
||||
LOCALDOCS_URL = "http://mcp-localdocs:8012/mcp"
|
||||
WEAVIATE_MCP_URL = "https://weaviate.eroomai.com/mcp"
|
||||
MCP_PROTOCOL_VERSION = "2025-03-26"
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ import sys
|
||||
from typing import Any, Mapping
|
||||
|
||||
|
||||
EXPECTED_STAGE2_RELEASE_SHA256 = "9fa85bb94c5f14f675dcdaa0cf94d06745b21675d97797539ffc14015dcc9f53"
|
||||
EXPECTED_STAGE2_RELEASE_SHA256 = "2363f1166ee8a3ea2b38350cde61fccfc9852a413c6b9f14a6ed6606af15a590"
|
||||
LOCALDOCS_URL = "http://mcp-localdocs:8012/mcp"
|
||||
MCP_PROTOCOL_VERSION = "2025-03-26"
|
||||
INLINE_USER_HASH = "{{__user_hash__}}"
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ import sys
|
||||
from typing import Any, Mapping
|
||||
|
||||
|
||||
EXPECTED_STAGE2_RELEASE_SHA256 = "9fa85bb94c5f14f675dcdaa0cf94d06745b21675d97797539ffc14015dcc9f53"
|
||||
EXPECTED_STAGE2_RELEASE_SHA256 = "2363f1166ee8a3ea2b38350cde61fccfc9852a413c6b9f14a6ed6606af15a590"
|
||||
LOCALDOCS_URL = "http://mcp-localdocs:8012/mcp"
|
||||
MCP_PROTOCOL_VERSION = "2025-03-26"
|
||||
INLINE_USER_HASH = "{{__user_hash__}}"
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ from typing import Any, Iterable, Mapping, Sequence
|
||||
|
||||
WORKFLOW_ID = "S2_40"
|
||||
ALGORITHM_VERSION = "s2_40_finalizer/1.0.0"
|
||||
EXPECTED_STAGE2_RELEASE_SHA256 = "9fa85bb94c5f14f675dcdaa0cf94d06745b21675d97797539ffc14015dcc9f53"
|
||||
EXPECTED_STAGE2_RELEASE_SHA256 = "2363f1166ee8a3ea2b38350cde61fccfc9852a413c6b9f14a6ed6606af15a590"
|
||||
LOCALDOCS_URL = "http://mcp-localdocs:8012/mcp"
|
||||
MCP_PROTOCOL_VERSION = "2025-03-26"
|
||||
INLINE_USER_HASH = "{{__user_hash__}}"
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ from typing import Any, Iterable, Mapping, Sequence
|
||||
|
||||
WORKFLOW_ID = "S2_40"
|
||||
ALGORITHM_VERSION = "s2_40_finalizer/1.0.0"
|
||||
EXPECTED_STAGE2_RELEASE_SHA256 = "9fa85bb94c5f14f675dcdaa0cf94d06745b21675d97797539ffc14015dcc9f53"
|
||||
EXPECTED_STAGE2_RELEASE_SHA256 = "2363f1166ee8a3ea2b38350cde61fccfc9852a413c6b9f14a6ed6606af15a590"
|
||||
LOCALDOCS_URL = "http://mcp-localdocs:8012/mcp"
|
||||
MCP_PROTOCOL_VERSION = "2025-03-26"
|
||||
INLINE_USER_HASH = "{{__user_hash__}}"
|
||||
|
||||
+76
-5
@@ -186,6 +186,20 @@
|
||||
"expected_release_sha256": {"$ref": "#/$defs/bindable_sha256"},
|
||||
"agent_script_sha256": {"$ref": "#/$defs/bindable_sha256"},
|
||||
"canonical_code_sha256": {"$ref": "#/$defs/bindable_sha256"},
|
||||
"prepare_request_contract": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["task_name", "yaml_pointer", "required_external_arguments", "write_path", "argument_binding_status", "success_gate_status", "workspace_serialization_status"],
|
||||
"properties": {
|
||||
"task_name": {"const": "Task_S2_00_prepare_request"},
|
||||
"yaml_pointer": {"const": "/Agent/Stages/0/tasks/0/parameters/code"},
|
||||
"required_external_arguments": {"const": ["request_id", "attempt_id", "stage1_run_root_ref", "stage1_deployment_root_ref"]},
|
||||
"write_path": {"const": "stage2_control/s2_00_request.json"},
|
||||
"argument_binding_status": {"const": "UNVERIFIED"},
|
||||
"success_gate_status": {"const": "UNVERIFIED"},
|
||||
"workspace_serialization_status": {"const": "UNVERIFIED"}
|
||||
}
|
||||
},
|
||||
"mcp_server_id": {"const": "code-executor"},
|
||||
"tool_name": {"const": "run_code"},
|
||||
"language": {"const": "python"},
|
||||
@@ -692,6 +706,7 @@
|
||||
"authoring",
|
||||
"deployment_projection",
|
||||
"canonical_code",
|
||||
"prepare_code",
|
||||
"full_code_mirrors",
|
||||
"task_contract",
|
||||
"parity_status"
|
||||
@@ -768,12 +783,55 @@
|
||||
},
|
||||
"placeholder_count": {"const": 0},
|
||||
"plaintext_secret_count": {"const": 0},
|
||||
"yaml_pointer": {"const": "/Agent/Stages/0/tasks/1/parameters/code"}
|
||||
}
|
||||
},
|
||||
"prepare_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_prepare_request.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_prepare_request.txt"},
|
||||
"sha256": {"$ref": "#/$defs/sha256"},
|
||||
"size_bytes": {"type": "integer", "minimum": 1},
|
||||
"byte_identical_to_canonical_code": {"const": true}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
@@ -798,13 +856,13 @@
|
||||
}
|
||||
],
|
||||
"items": false,
|
||||
"minItems": 2,
|
||||
"maxItems": 2
|
||||
"minItems": 4,
|
||||
"maxItems": 4
|
||||
},
|
||||
"task_contract": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["agent", "mcp_servers", "stage", "task", "task_procedure"],
|
||||
"required": ["agent", "mcp_servers", "stage", "prepare_task", "task", "task_procedure"],
|
||||
"properties": {
|
||||
"agent": {"const": {"name": "Stage_2_S2_00_v2", "version": "1.2.0"}},
|
||||
"mcp_servers": {
|
||||
@@ -814,6 +872,18 @@
|
||||
}
|
||||
},
|
||||
"stage": {"const": {"name": "S2_00", "nexts": [], "prevs": []}},
|
||||
"prepare_task": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["task_name", "mcp", "tool_name", "parameters", "code_sha256"],
|
||||
"properties": {
|
||||
"task_name": {"const": "Task_S2_00_prepare_request"},
|
||||
"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": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
@@ -835,8 +905,9 @@
|
||||
},
|
||||
"task_procedure": {
|
||||
"const": {
|
||||
"IN": {"nexts": ["Task_S2_00_deterministic_ingress"], "wait_until": []},
|
||||
"Task_S2_00_deterministic_ingress": {"nexts": ["OUT"], "wait_until": ["IN"]},
|
||||
"IN": {"nexts": ["Task_S2_00_prepare_request"], "wait_until": []},
|
||||
"Task_S2_00_prepare_request": {"nexts": ["Task_S2_00_deterministic_ingress"], "wait_until": ["IN"]},
|
||||
"Task_S2_00_deterministic_ingress": {"nexts": ["OUT"], "wait_until": ["Task_S2_00_prepare_request"]},
|
||||
"OUT": {"nexts": [], "wait_until": ["Task_S2_00_deterministic_ingress"]}
|
||||
}
|
||||
}
|
||||
|
||||
+7
-2
@@ -1313,7 +1313,7 @@
|
||||
},
|
||||
{
|
||||
"path": "tests/s2_00/test_cluster_bundle_compile.py",
|
||||
"sha256": "b643d2d4018ebe3349c64304063edf7ba0194fc92fb1fe9337eca3f384ae794a",
|
||||
"sha256": "1b7a58ae53e7465cd39f0c911c4fab8e96b9e89df707eb9423e8a22fabc0a54b",
|
||||
"status": "DEV_HASH_BOUND"
|
||||
},
|
||||
{
|
||||
@@ -1328,7 +1328,12 @@
|
||||
},
|
||||
{
|
||||
"path": "tests/s2_00/test_inline_code_parity.py",
|
||||
"sha256": "8f0f55e8e1b970de572129946ab8bf34967c7fd84e316a9193509cab07a455ed",
|
||||
"sha256": "325e0ed4dc7a4562b87946f2ff68693865997bd33b55ce5d0a6c558920404a18",
|
||||
"status": "DEV_HASH_BOUND"
|
||||
},
|
||||
{
|
||||
"path": "tests/s2_00/test_prepare_request.py",
|
||||
"sha256": "db76d064f742c2003eacfcb4814ba0b9d0b36984622147485cbab1139d5deaeb",
|
||||
"status": "DEV_HASH_BOUND"
|
||||
},
|
||||
{
|
||||
|
||||
+4
-1
@@ -643,7 +643,10 @@ class ClusterAndBundleTests(unittest.TestCase):
|
||||
self.assertEqual(binding["expected_release_sha256"], binding["stage2_release_ref"]["sha256"])
|
||||
self.assertEqual(binding["expected_release_sha256"], hashlib.sha256(release_path.read_bytes()).hexdigest())
|
||||
agent = yaml.safe_load(agent_path.read_text(encoding="utf-8"))
|
||||
code = agent["Agent"]["Stages"][0]["tasks"][0]["parameters"]["code"]
|
||||
tasks = agent["Agent"]["Stages"][0]["tasks"]
|
||||
self.assertEqual(tasks[0]["task_name"], "Task_S2_00_prepare_request")
|
||||
self.assertEqual(tasks[1]["task_name"], "Task_S2_00_deterministic_ingress")
|
||||
code = tasks[1]["parameters"]["code"]
|
||||
self.assertEqual(hashlib.sha256(code.encode("utf-8")).hexdigest(), binding["canonical_code_sha256"])
|
||||
self.assertEqual(binding["agent_script_sha256"], binding["agent_script_ref"]["sha256"])
|
||||
|
||||
|
||||
+4
-1
@@ -643,7 +643,10 @@ class ClusterAndBundleTests(unittest.TestCase):
|
||||
self.assertEqual(binding["expected_release_sha256"], binding["stage2_release_ref"]["sha256"])
|
||||
self.assertEqual(binding["expected_release_sha256"], hashlib.sha256(release_path.read_bytes()).hexdigest())
|
||||
agent = yaml.safe_load(agent_path.read_text(encoding="utf-8"))
|
||||
code = agent["Agent"]["Stages"][0]["tasks"][0]["parameters"]["code"]
|
||||
tasks = agent["Agent"]["Stages"][0]["tasks"]
|
||||
self.assertEqual(tasks[0]["task_name"], "Task_S2_00_prepare_request")
|
||||
self.assertEqual(tasks[1]["task_name"], "Task_S2_00_deterministic_ingress")
|
||||
code = tasks[1]["parameters"]["code"]
|
||||
self.assertEqual(hashlib.sha256(code.encode("utf-8")).hexdigest(), binding["canonical_code_sha256"])
|
||||
self.assertEqual(binding["agent_script_sha256"], binding["agent_script_ref"]["sha256"])
|
||||
|
||||
|
||||
+33
-2
@@ -38,6 +38,18 @@ def valid_inline_code() -> str:
|
||||
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"))
|
||||
prepare_source = "\n".join(
|
||||
f" {line}" for line in (
|
||||
"from __future__ import annotations\n"
|
||||
"LOCALDOCS_URL = 'http://mcp-localdocs:8012/mcp'\n"
|
||||
"USER_HASH = '{{__user_hash__}}'\n"
|
||||
"WORKSPACE_HASH = '{{__workspace_hash__}}'\n"
|
||||
"READ_TOOL = 'read_binary_doc'\n"
|
||||
"REQUEST_PATH = 'stage2_control/s2_00_request.json'\n"
|
||||
"def build_request():\n return b'{}'\n"
|
||||
"def prepare_request():\n return 'write_binary_verified'"
|
||||
).split("\n")
|
||||
)
|
||||
return (
|
||||
"Agent:\n"
|
||||
" name: Stage_2_S2_00_v2\n"
|
||||
@@ -53,6 +65,16 @@ def authoring_yaml(code: str | None = None) -> bytes:
|
||||
" type: streamable-http\n"
|
||||
" url: https://code-executor.mcp.eroomai.com/mcp\n"
|
||||
" tasks:\n"
|
||||
" - task_name: Task_S2_00_prepare_request\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"{prepare_source}\n"
|
||||
" - task_name: Task_S2_00_deterministic_ingress\n"
|
||||
" mcp: code-executor\n"
|
||||
" tool_name: run_code\n"
|
||||
@@ -65,11 +87,14 @@ def authoring_yaml(code: str | None = None) -> bytes:
|
||||
f"{indented}\n"
|
||||
" task_procedure:\n"
|
||||
" IN:\n"
|
||||
" nexts: [Task_S2_00_deterministic_ingress]\n"
|
||||
" nexts: [Task_S2_00_prepare_request]\n"
|
||||
" wait_until: []\n"
|
||||
" Task_S2_00_prepare_request:\n"
|
||||
" nexts: [Task_S2_00_deterministic_ingress]\n"
|
||||
" wait_until: [IN]\n"
|
||||
" Task_S2_00_deterministic_ingress:\n"
|
||||
" nexts: [OUT]\n"
|
||||
" wait_until: [IN]\n"
|
||||
" wait_until: [Task_S2_00_prepare_request]\n"
|
||||
" OUT:\n"
|
||||
" nexts: []\n"
|
||||
" wait_until: [Task_S2_00_deterministic_ingress]\n"
|
||||
@@ -129,6 +154,7 @@ class InlineCodeProjectionUnitTests(unittest.TestCase):
|
||||
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])
|
||||
self.assertEqual(outputs[builder.PREPARE_MIRROR_PY_PATH], outputs[builder.PREPARE_MIRROR_TXT_PATH])
|
||||
parsed_receipt = json.loads(outputs[builder.RECEIPT_PATH])
|
||||
self.assertEqual(parsed_receipt, receipt)
|
||||
self.assertEqual(parsed_receipt["parity_status"], "PASS")
|
||||
@@ -233,6 +259,8 @@ class InlineCodeProjectionUnitTests(unittest.TestCase):
|
||||
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"
|
||||
prepare_py = root / "runtime" / "s2_00_prepare_request.py"
|
||||
prepare_txt = root / "runtime" / "s2_00_prepare_request.txt"
|
||||
receipt = root / "manifest" / "s2_00_inline_code_receipt.json"
|
||||
root.mkdir(parents=True)
|
||||
original = authoring_yaml()
|
||||
@@ -244,6 +272,8 @@ class InlineCodeProjectionUnitTests(unittest.TestCase):
|
||||
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, "PREPARE_MIRROR_PY_PATH", prepare_py),
|
||||
mock.patch.object(builder, "PREPARE_MIRROR_TXT_PATH", prepare_txt),
|
||||
mock.patch.object(builder, "RECEIPT_PATH", receipt),
|
||||
):
|
||||
status, payload = builder.run(check=False)
|
||||
@@ -253,6 +283,7 @@ class InlineCodeProjectionUnitTests(unittest.TestCase):
|
||||
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())
|
||||
self.assertEqual(prepare_py.read_bytes(), prepare_txt.read_bytes())
|
||||
receipt_before = receipt.read_bytes()
|
||||
status, payload = builder.run(check=True)
|
||||
self.assertEqual(status, 0)
|
||||
|
||||
+33
-2
@@ -38,6 +38,18 @@ def valid_inline_code() -> str:
|
||||
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"))
|
||||
prepare_source = "\n".join(
|
||||
f" {line}" for line in (
|
||||
"from __future__ import annotations\n"
|
||||
"LOCALDOCS_URL = 'http://mcp-localdocs:8012/mcp'\n"
|
||||
"USER_HASH = '{{__user_hash__}}'\n"
|
||||
"WORKSPACE_HASH = '{{__workspace_hash__}}'\n"
|
||||
"READ_TOOL = 'read_binary_doc'\n"
|
||||
"REQUEST_PATH = 'stage2_control/s2_00_request.json'\n"
|
||||
"def build_request():\n return b'{}'\n"
|
||||
"def prepare_request():\n return 'write_binary_verified'"
|
||||
).split("\n")
|
||||
)
|
||||
return (
|
||||
"Agent:\n"
|
||||
" name: Stage_2_S2_00_v2\n"
|
||||
@@ -53,6 +65,16 @@ def authoring_yaml(code: str | None = None) -> bytes:
|
||||
" type: streamable-http\n"
|
||||
" url: https://code-executor.mcp.eroomai.com/mcp\n"
|
||||
" tasks:\n"
|
||||
" - task_name: Task_S2_00_prepare_request\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"{prepare_source}\n"
|
||||
" - task_name: Task_S2_00_deterministic_ingress\n"
|
||||
" mcp: code-executor\n"
|
||||
" tool_name: run_code\n"
|
||||
@@ -65,11 +87,14 @@ def authoring_yaml(code: str | None = None) -> bytes:
|
||||
f"{indented}\n"
|
||||
" task_procedure:\n"
|
||||
" IN:\n"
|
||||
" nexts: [Task_S2_00_deterministic_ingress]\n"
|
||||
" nexts: [Task_S2_00_prepare_request]\n"
|
||||
" wait_until: []\n"
|
||||
" Task_S2_00_prepare_request:\n"
|
||||
" nexts: [Task_S2_00_deterministic_ingress]\n"
|
||||
" wait_until: [IN]\n"
|
||||
" Task_S2_00_deterministic_ingress:\n"
|
||||
" nexts: [OUT]\n"
|
||||
" wait_until: [IN]\n"
|
||||
" wait_until: [Task_S2_00_prepare_request]\n"
|
||||
" OUT:\n"
|
||||
" nexts: []\n"
|
||||
" wait_until: [Task_S2_00_deterministic_ingress]\n"
|
||||
@@ -129,6 +154,7 @@ class InlineCodeProjectionUnitTests(unittest.TestCase):
|
||||
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])
|
||||
self.assertEqual(outputs[builder.PREPARE_MIRROR_PY_PATH], outputs[builder.PREPARE_MIRROR_TXT_PATH])
|
||||
parsed_receipt = json.loads(outputs[builder.RECEIPT_PATH])
|
||||
self.assertEqual(parsed_receipt, receipt)
|
||||
self.assertEqual(parsed_receipt["parity_status"], "PASS")
|
||||
@@ -233,6 +259,8 @@ class InlineCodeProjectionUnitTests(unittest.TestCase):
|
||||
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"
|
||||
prepare_py = root / "runtime" / "s2_00_prepare_request.py"
|
||||
prepare_txt = root / "runtime" / "s2_00_prepare_request.txt"
|
||||
receipt = root / "manifest" / "s2_00_inline_code_receipt.json"
|
||||
root.mkdir(parents=True)
|
||||
original = authoring_yaml()
|
||||
@@ -244,6 +272,8 @@ class InlineCodeProjectionUnitTests(unittest.TestCase):
|
||||
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, "PREPARE_MIRROR_PY_PATH", prepare_py),
|
||||
mock.patch.object(builder, "PREPARE_MIRROR_TXT_PATH", prepare_txt),
|
||||
mock.patch.object(builder, "RECEIPT_PATH", receipt),
|
||||
):
|
||||
status, payload = builder.run(check=False)
|
||||
@@ -253,6 +283,7 @@ class InlineCodeProjectionUnitTests(unittest.TestCase):
|
||||
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())
|
||||
self.assertEqual(prepare_py.read_bytes(), prepare_txt.read_bytes())
|
||||
receipt_before = receipt.read_bytes()
|
||||
status, payload = builder.run(check=True)
|
||||
self.assertEqual(status, 0)
|
||||
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
from runtime import s2_00_ingress as ingress # noqa: E402
|
||||
from runtime import s2_00_prepare_request as prepare # noqa: E402
|
||||
|
||||
|
||||
ARGS = ("request-1", "attempt.1", "stage1/runs/matter-1", "stage1/deployment")
|
||||
|
||||
|
||||
class FakeLocaldocs:
|
||||
def __init__(self, *, fail_at: str | None = None) -> None:
|
||||
self.fail_at = fail_at
|
||||
self.initialized = False
|
||||
self.closed = False
|
||||
self.writes: list[tuple[str, bytes]] = []
|
||||
self.saved: bytes | None = None
|
||||
|
||||
def initialize(self) -> None:
|
||||
if self.fail_at == "initialize":
|
||||
raise RuntimeError("initialize failed")
|
||||
self.initialized = True
|
||||
|
||||
def write_binary_verified(self, path: str, payload: bytes) -> str:
|
||||
if self.fail_at == "write":
|
||||
raise RuntimeError("write failed")
|
||||
self.writes.append((path, payload))
|
||||
self.saved = payload
|
||||
if self.fail_at == "readback":
|
||||
raise prepare.PrepareError("LOCALDOCS_WRITE_READBACK_MISMATCH", "read-back differs")
|
||||
return prepare.hashlib.sha256(payload).hexdigest()
|
||||
|
||||
def close(self) -> None:
|
||||
self.closed = True
|
||||
if self.fail_at == "close":
|
||||
raise RuntimeError("close failed")
|
||||
|
||||
|
||||
class FakeResponse:
|
||||
def __init__(self, payload: dict, *, session_id: str = "session-1") -> None:
|
||||
self.payload = payload
|
||||
self.headers = {"mcp-session-id": session_id}
|
||||
|
||||
def raise_for_status(self) -> None:
|
||||
return None
|
||||
|
||||
def json(self) -> dict:
|
||||
return self.payload
|
||||
|
||||
|
||||
class FakeMcpClient:
|
||||
def __init__(self, *, corrupt_read: bool = False) -> None:
|
||||
self.corrupt_read = corrupt_read
|
||||
self.saved: bytes | None = None
|
||||
self.closed = False
|
||||
self.calls: list[str] = []
|
||||
|
||||
def post(self, url: str, *, json: dict, headers: dict) -> FakeResponse:
|
||||
if url != prepare.LOCALDOCS_URL:
|
||||
raise RuntimeError("unexpected endpoint")
|
||||
method = json["method"]
|
||||
self.calls.append(method)
|
||||
if method == "initialize":
|
||||
result = {"protocolVersion": prepare.MCP_PROTOCOL_VERSION}
|
||||
elif method == "notifications/initialized":
|
||||
return FakeResponse({})
|
||||
else:
|
||||
name = json["params"]["name"]
|
||||
arguments = json["params"]["arguments"]
|
||||
if name == "write_binary_file":
|
||||
if arguments["path"] != prepare.REQUEST_PATH:
|
||||
raise RuntimeError("wrong write path")
|
||||
self.saved = prepare.base64.b64decode(arguments["content_base64"])
|
||||
text = "{}"
|
||||
elif name == "read_binary_doc":
|
||||
payload = b"corrupt" if self.corrupt_read else self.saved
|
||||
text = prepare.json.dumps({"content_base64": prepare.base64.b64encode(payload).decode("ascii")})
|
||||
else:
|
||||
raise RuntimeError("wrong tool")
|
||||
result = {"content": [{"type": "text", "text": text}]}
|
||||
return FakeResponse({"jsonrpc": "2.0", "id": json["id"], "result": result})
|
||||
|
||||
def close(self) -> None:
|
||||
self.closed = True
|
||||
|
||||
|
||||
class PrepareRequestTests(unittest.TestCase):
|
||||
def test_exact_canonical_six_field_request_matches_existing_ingress(self) -> None:
|
||||
payload = prepare.build_request(*ARGS)
|
||||
value = json.loads(payload)
|
||||
self.assertEqual(set(value), prepare.REQUEST_KEYS)
|
||||
self.assertEqual(payload, ingress.canonical_json_bytes(value))
|
||||
self.assertEqual(ingress._inline_validate_request(payload), value)
|
||||
self.assertTrue(payload.endswith(b"\n"))
|
||||
|
||||
def test_invalid_inputs_never_write(self) -> None:
|
||||
variants = [
|
||||
(123, *ARGS[1:]),
|
||||
("bad/id", *ARGS[1:]),
|
||||
(ARGS[0], "bad id", *ARGS[2:]),
|
||||
(*ARGS[:2], "../escape", ARGS[3]),
|
||||
(*ARGS[:3], "/absolute"),
|
||||
(*ARGS[:2], "stage1//run", ARGS[3]),
|
||||
(*ARGS[:2], "stage1/e\u0301", ARGS[3]),
|
||||
]
|
||||
for values in variants:
|
||||
with self.subTest(values=values):
|
||||
fake = FakeLocaldocs()
|
||||
with self.assertRaises(prepare.PrepareError):
|
||||
prepare.prepare_request(*values, localdocs=fake)
|
||||
self.assertEqual(fake.writes, [])
|
||||
|
||||
def test_verified_write_and_close(self) -> None:
|
||||
fake = FakeLocaldocs()
|
||||
result = prepare.prepare_request(*ARGS, localdocs=fake)
|
||||
self.assertEqual(fake.writes, [(prepare.REQUEST_PATH, prepare.build_request(*ARGS))])
|
||||
self.assertTrue(fake.initialized)
|
||||
self.assertTrue(fake.closed)
|
||||
self.assertEqual(result["request_sha256"], prepare.hashlib.sha256(fake.saved).hexdigest())
|
||||
|
||||
def test_io_and_close_failures_do_not_return_success(self) -> None:
|
||||
for failure in ("initialize", "write", "readback", "close"):
|
||||
with self.subTest(failure=failure):
|
||||
fake = FakeLocaldocs(fail_at=failure)
|
||||
with self.assertRaises(Exception):
|
||||
prepare.prepare_request(*ARGS, localdocs=fake)
|
||||
self.assertTrue(fake.closed)
|
||||
|
||||
def test_localdocs_write_path_is_fixed(self) -> None:
|
||||
session = object.__new__(prepare.LocaldocsSession)
|
||||
with self.assertRaises(prepare.PrepareError):
|
||||
session.write_binary_verified("other/request.json", b"{}\n")
|
||||
|
||||
def test_real_session_adapter_writes_and_reads_back_through_mcp(self) -> None:
|
||||
client = FakeMcpClient()
|
||||
session = prepare.LocaldocsSession("a" * 64, "b" * 64, client=client)
|
||||
result = prepare.prepare_request(*ARGS, localdocs=session)
|
||||
self.assertEqual(client.saved, prepare.build_request(*ARGS))
|
||||
self.assertEqual(result["request_sha256"], prepare.hashlib.sha256(client.saved).hexdigest())
|
||||
self.assertEqual(client.calls, ["initialize", "notifications/initialized", "tools/call", "tools/call"])
|
||||
self.assertTrue(client.closed)
|
||||
|
||||
def test_real_session_adapter_rejects_readback_mismatch(self) -> None:
|
||||
client = FakeMcpClient(corrupt_read=True)
|
||||
session = prepare.LocaldocsSession("a" * 64, "b" * 64, client=client)
|
||||
with self.assertRaises(prepare.PrepareError) as raised:
|
||||
prepare.prepare_request(*ARGS, localdocs=session)
|
||||
self.assertEqual(raised.exception.code, "LOCALDOCS_WRITE_READBACK_MISMATCH")
|
||||
self.assertTrue(client.closed)
|
||||
|
||||
def test_ingress_is_not_called_after_prepare_failure_in_local_flow(self) -> None:
|
||||
calls: list[str] = []
|
||||
|
||||
def local_flow(fake: FakeLocaldocs) -> None:
|
||||
prepare.prepare_request(*ARGS, localdocs=fake)
|
||||
calls.append("ingress")
|
||||
|
||||
with self.assertRaises(RuntimeError):
|
||||
local_flow(FakeLocaldocs(fail_at="write"))
|
||||
self.assertEqual(calls, [])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
from runtime import s2_00_ingress as ingress # noqa: E402
|
||||
from runtime import s2_00_prepare_request as prepare # noqa: E402
|
||||
|
||||
|
||||
ARGS = ("request-1", "attempt.1", "stage1/runs/matter-1", "stage1/deployment")
|
||||
|
||||
|
||||
class FakeLocaldocs:
|
||||
def __init__(self, *, fail_at: str | None = None) -> None:
|
||||
self.fail_at = fail_at
|
||||
self.initialized = False
|
||||
self.closed = False
|
||||
self.writes: list[tuple[str, bytes]] = []
|
||||
self.saved: bytes | None = None
|
||||
|
||||
def initialize(self) -> None:
|
||||
if self.fail_at == "initialize":
|
||||
raise RuntimeError("initialize failed")
|
||||
self.initialized = True
|
||||
|
||||
def write_binary_verified(self, path: str, payload: bytes) -> str:
|
||||
if self.fail_at == "write":
|
||||
raise RuntimeError("write failed")
|
||||
self.writes.append((path, payload))
|
||||
self.saved = payload
|
||||
if self.fail_at == "readback":
|
||||
raise prepare.PrepareError("LOCALDOCS_WRITE_READBACK_MISMATCH", "read-back differs")
|
||||
return prepare.hashlib.sha256(payload).hexdigest()
|
||||
|
||||
def close(self) -> None:
|
||||
self.closed = True
|
||||
if self.fail_at == "close":
|
||||
raise RuntimeError("close failed")
|
||||
|
||||
|
||||
class FakeResponse:
|
||||
def __init__(self, payload: dict, *, session_id: str = "session-1") -> None:
|
||||
self.payload = payload
|
||||
self.headers = {"mcp-session-id": session_id}
|
||||
|
||||
def raise_for_status(self) -> None:
|
||||
return None
|
||||
|
||||
def json(self) -> dict:
|
||||
return self.payload
|
||||
|
||||
|
||||
class FakeMcpClient:
|
||||
def __init__(self, *, corrupt_read: bool = False) -> None:
|
||||
self.corrupt_read = corrupt_read
|
||||
self.saved: bytes | None = None
|
||||
self.closed = False
|
||||
self.calls: list[str] = []
|
||||
|
||||
def post(self, url: str, *, json: dict, headers: dict) -> FakeResponse:
|
||||
if url != prepare.LOCALDOCS_URL:
|
||||
raise RuntimeError("unexpected endpoint")
|
||||
method = json["method"]
|
||||
self.calls.append(method)
|
||||
if method == "initialize":
|
||||
result = {"protocolVersion": prepare.MCP_PROTOCOL_VERSION}
|
||||
elif method == "notifications/initialized":
|
||||
return FakeResponse({})
|
||||
else:
|
||||
name = json["params"]["name"]
|
||||
arguments = json["params"]["arguments"]
|
||||
if name == "write_binary_file":
|
||||
if arguments["path"] != prepare.REQUEST_PATH:
|
||||
raise RuntimeError("wrong write path")
|
||||
self.saved = prepare.base64.b64decode(arguments["content_base64"])
|
||||
text = "{}"
|
||||
elif name == "read_binary_doc":
|
||||
payload = b"corrupt" if self.corrupt_read else self.saved
|
||||
text = prepare.json.dumps({"content_base64": prepare.base64.b64encode(payload).decode("ascii")})
|
||||
else:
|
||||
raise RuntimeError("wrong tool")
|
||||
result = {"content": [{"type": "text", "text": text}]}
|
||||
return FakeResponse({"jsonrpc": "2.0", "id": json["id"], "result": result})
|
||||
|
||||
def close(self) -> None:
|
||||
self.closed = True
|
||||
|
||||
|
||||
class PrepareRequestTests(unittest.TestCase):
|
||||
def test_exact_canonical_six_field_request_matches_existing_ingress(self) -> None:
|
||||
payload = prepare.build_request(*ARGS)
|
||||
value = json.loads(payload)
|
||||
self.assertEqual(set(value), prepare.REQUEST_KEYS)
|
||||
self.assertEqual(payload, ingress.canonical_json_bytes(value))
|
||||
self.assertEqual(ingress._inline_validate_request(payload), value)
|
||||
self.assertTrue(payload.endswith(b"\n"))
|
||||
|
||||
def test_invalid_inputs_never_write(self) -> None:
|
||||
variants = [
|
||||
(123, *ARGS[1:]),
|
||||
("bad/id", *ARGS[1:]),
|
||||
(ARGS[0], "bad id", *ARGS[2:]),
|
||||
(*ARGS[:2], "../escape", ARGS[3]),
|
||||
(*ARGS[:3], "/absolute"),
|
||||
(*ARGS[:2], "stage1//run", ARGS[3]),
|
||||
(*ARGS[:2], "stage1/e\u0301", ARGS[3]),
|
||||
]
|
||||
for values in variants:
|
||||
with self.subTest(values=values):
|
||||
fake = FakeLocaldocs()
|
||||
with self.assertRaises(prepare.PrepareError):
|
||||
prepare.prepare_request(*values, localdocs=fake)
|
||||
self.assertEqual(fake.writes, [])
|
||||
|
||||
def test_verified_write_and_close(self) -> None:
|
||||
fake = FakeLocaldocs()
|
||||
result = prepare.prepare_request(*ARGS, localdocs=fake)
|
||||
self.assertEqual(fake.writes, [(prepare.REQUEST_PATH, prepare.build_request(*ARGS))])
|
||||
self.assertTrue(fake.initialized)
|
||||
self.assertTrue(fake.closed)
|
||||
self.assertEqual(result["request_sha256"], prepare.hashlib.sha256(fake.saved).hexdigest())
|
||||
|
||||
def test_io_and_close_failures_do_not_return_success(self) -> None:
|
||||
for failure in ("initialize", "write", "readback", "close"):
|
||||
with self.subTest(failure=failure):
|
||||
fake = FakeLocaldocs(fail_at=failure)
|
||||
with self.assertRaises(Exception):
|
||||
prepare.prepare_request(*ARGS, localdocs=fake)
|
||||
self.assertTrue(fake.closed)
|
||||
|
||||
def test_localdocs_write_path_is_fixed(self) -> None:
|
||||
session = object.__new__(prepare.LocaldocsSession)
|
||||
with self.assertRaises(prepare.PrepareError):
|
||||
session.write_binary_verified("other/request.json", b"{}\n")
|
||||
|
||||
def test_real_session_adapter_writes_and_reads_back_through_mcp(self) -> None:
|
||||
client = FakeMcpClient()
|
||||
session = prepare.LocaldocsSession("a" * 64, "b" * 64, client=client)
|
||||
result = prepare.prepare_request(*ARGS, localdocs=session)
|
||||
self.assertEqual(client.saved, prepare.build_request(*ARGS))
|
||||
self.assertEqual(result["request_sha256"], prepare.hashlib.sha256(client.saved).hexdigest())
|
||||
self.assertEqual(client.calls, ["initialize", "notifications/initialized", "tools/call", "tools/call"])
|
||||
self.assertTrue(client.closed)
|
||||
|
||||
def test_real_session_adapter_rejects_readback_mismatch(self) -> None:
|
||||
client = FakeMcpClient(corrupt_read=True)
|
||||
session = prepare.LocaldocsSession("a" * 64, "b" * 64, client=client)
|
||||
with self.assertRaises(prepare.PrepareError) as raised:
|
||||
prepare.prepare_request(*ARGS, localdocs=session)
|
||||
self.assertEqual(raised.exception.code, "LOCALDOCS_WRITE_READBACK_MISMATCH")
|
||||
self.assertTrue(client.closed)
|
||||
|
||||
def test_ingress_is_not_called_after_prepare_failure_in_local_flow(self) -> None:
|
||||
calls: list[str] = []
|
||||
|
||||
def local_flow(fake: FakeLocaldocs) -> None:
|
||||
prepare.prepare_request(*ARGS, localdocs=fake)
|
||||
calls.append("ingress")
|
||||
|
||||
with self.assertRaises(RuntimeError):
|
||||
local_flow(FakeLocaldocs(fail_at="write"))
|
||||
self.assertEqual(calls, [])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
+18
-5
@@ -22,8 +22,8 @@ Agent:
|
||||
- name: S2_00
|
||||
description: >-
|
||||
C00, C05, C10, C15의 IO·순서·분기·배리어를 선언하는 NON-LLM 계약.
|
||||
실행 Python은 이 문서가 아니라 Agent YAML의 exactly-one
|
||||
code-executor.run_code task에만 존재한다.
|
||||
실행 Python은 Agent YAML의 request 준비와 ingress 두
|
||||
code-executor.run_code task에 존재한다.
|
||||
prevs: []
|
||||
nexts:
|
||||
- S2_10
|
||||
@@ -102,8 +102,13 @@ Agent:
|
||||
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
|
||||
inline_task_count: 2
|
||||
prepare_task_owner_ref: agent_scripts/Stage_2_S2_00.yml#/Agent/Stages/0/tasks/0
|
||||
executable_task_owner_ref: agent_scripts/Stage_2_S2_00.yml#/Agent/Stages/0/tasks/1
|
||||
task_order: [Task_S2_00_prepare_request, Task_S2_00_deterministic_ingress]
|
||||
backend_argument_binding_status: UNVERIFIED
|
||||
backend_success_gate_status: UNVERIFIED
|
||||
workspace_serialization_status: UNVERIFIED
|
||||
retry_policy_id: S2-RETRY-TRANSIENT-READ-V1
|
||||
retry_policy_ref: manifest/stage2_release.json#/retry_policies/0
|
||||
idempotence_key_material:
|
||||
@@ -122,6 +127,14 @@ Agent:
|
||||
|
||||
request_contract:
|
||||
fixed_path: stage2_control/s2_00_request.json
|
||||
producer_task: Task_S2_00_prepare_request
|
||||
producer_write_path: stage2_control/s2_00_request.json
|
||||
external_argument_delivery_status: UNVERIFIED
|
||||
required_external_arguments:
|
||||
- request_id
|
||||
- attempt_id
|
||||
- stage1_run_root_ref
|
||||
- stage1_deployment_root_ref
|
||||
schema_ref: schemas/ingress.schema.json#/$defs/execution_request
|
||||
directory_scan_allowed: false
|
||||
alternate_request_path_allowed: false
|
||||
@@ -383,7 +396,7 @@ Agent:
|
||||
llm_calls_allowed: false
|
||||
llm_configuration_present: 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
|
||||
executable_inline_python_owner: agent_scripts/Stage_2_S2_00.yml#/Agent/Stages/0/tasks/1/parameters/code
|
||||
inline_schema_allowed: false
|
||||
external_internet_access_allowed: false
|
||||
mcp_endpoint_allowlist:
|
||||
|
||||
@@ -16,6 +16,19 @@
|
||||
- 하위 에이전트(subagents)를 사용하여 핵심 테마와 교훈을 식별하고 MEMORY.md에 섹션으로 저장하라.
|
||||
- 향후 세션 시작 시 MEMORY.md의 이전 섹션을 참조하라.
|
||||
|
||||
## 2026-10-01 — 현행 S2_00 배포 YAML 분석
|
||||
|
||||
한 줄 요약: `Default_Agent/Stage_2_Clean/agent_scripts/Analysis_Stage_2_S2_00.md`에 현행 두-task DAG, C00–C15의 단일 ingress 실행 경계, 고정·동적 입력과 분기별 출력, 배포 자산·후속 route를 기록했다. prepare 함수의 request 저장 구현과 배포 YAML의 직접 실행 가능성을 구분하는 것이 핵심이다. 현재 네 외부 인자 결속·실패 차단·workspace 직렬화는 미검증이고, 직접 실행은 fail closed하며 DEV fixture release는 실제 사건 발행을 금지한다.
|
||||
|
||||
## 2026-10-01 — S2_00 request 준비 코드·두 task YAML
|
||||
|
||||
한 줄 요약: 기존 배포 YAML을 `Stage_2_S2_00_outdated_10_01.yml`로 byte-identical 보존하고, 현행 S2_00 앞단에 네 인자→정확히 6필드 canonical JSON 생성·검증·localdocs 고정 경로 write/read-back을 수행하는 `Task_S2_00_prepare_request`를 추가했다. authoring/deployment YAML, 두 code mirror와 receipt, builder·workflow·binding·schema·회귀시험 및 영향받은 parent/child release hash 참조를 갱신했다. 함수·가짜 localdocs 및 정적 projection/release 검증은 통과했지만 backend의 네 인자 주입·prepare 실패 후 ingress 차단·동일 workspace 직렬화는 미확인이다. 직접 `run_code`는 인자 미결속 오류로 fail closed하며 DEV release도 계속 `DRAFT_NOT_EXECUTABLE`이다. S2_20 전체 시험의 case catalog 1건 실패는 별도 미추적 `Case_02_Comparison_Research/case_kinds.md`와 기존 registry의 불일치로 남겼고 해당 자료는 변경하지 않았다.
|
||||
|
||||
|
||||
|
||||
## 2026-10-01 — S2_00 request 준비 계획 v2
|
||||
|
||||
한 줄 요약: `plans/s2-00-request-preparation_v2.md`를 작성해, 네 인자 함수의 6필드 JSON 생성·검증·localdocs 저장/read-back과 Stage 2 작업명세서 첫 task 배치를 backend 확인 전에 구현·offline 시험하도록 순서를 개정했다. 실제 인자 주입·prepare 실패 차단·workspace 직렬화는 운영 연결 검증으로 남기며, v2 작성만으로 YAML·request 생성이나 live-ready 상태를 주장하지 않는다.
|
||||
|
||||
## 2026-09-30 — S2_00 IO 분석·request 준비 계획 및 구현 경계 정정
|
||||
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
# Stage 2_00 작업분석서
|
||||
|
||||
## 2026-10-01 현행 구현 보충
|
||||
|
||||
아래 2026-09-09 분석과 `Y:행번호`는 [보존된 당시 배포본](Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00_outdated_10_01.yml)에 관한 기록이다. [현행 배포본](Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00.yml)은 `IN → Task_S2_00_prepare_request → Task_S2_00_deterministic_ingress → OUT`의 두 task DAG로 바뀌었다. 첫 task의 `build_request(request_id, attempt_id, stage1_run_root_ref, stage1_deployment_root_ref)`는 기존 ID·NFC 상대경로 규칙으로 정확히 6필드의 canonical UTF-8 JSON bytes를 만들고, `prepare_request`는 localdocs의 `stage2_control/s2_00_request.json`에 binary write/read-back한 후 세션을 닫는다. ingress의 C00–C15 및 기존 출력 계약은 유지된다. 준비 code의 별도 `.py/.txt` mirror와 두 task pointer/hash는 inline receipt에 기록된다.
|
||||
|
||||
현재 inline `run_code`에 네 인자를 주입하는 backend 문법이 확인되지 않아 준비 task의 직접 실행은 `PREPARE_ARGUMENT_BINDING_UNVERIFIED`와 exit 2로 실패한다. YAML의 edge가 backend의 성공 조건·동일 workspace 직렬화를 실제로 강제하는지도 미검증이다. 생성 함수·격리 localdocs 시험과 배포본의 정적 결속은 완료했으나 실사건 Stage 2 실행 적격성은 입증하지 않았다. 아래 본문에서 “단일 task”, 외부 request producer, 행번호라고 적힌 부분은 **보존본 분석 시점의 사실**로 읽어야 한다.
|
||||
|
||||
|
||||
## 0. 분석 범위와 결론
|
||||
|
||||
분석 기준일은 2026-09-09이다. 분석 정본은 [배포 Stage_2_S2_00.yml](Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00.yml) **1–6,248행 전체**, SHA-256 `94c2744d71d222cfb5cc1b2a0d33a6cda20079439823de431eef378a2fa5c943`이다. 이 문서에서 `Y:번호`는 이 배포 YAML의 행번호이며, 각 절의 링크와 함께 읽는다. 실제 구현을 우선하고 [S2_00_SOW_v.2.md](S2_00_SOW_v.2.md), [S2_00_assets_v.2.md](S2_00_assets_v.2.md), [workflow 계약](Default_Agent/Stage_2_Clean/workflows/S2_00_stage1_ingress_normalize_and_bundle_compile.yml), [parent release](Default_Agent/Stage_2_Clean/manifest/stage2_release.json)는 의도·배포계약과의 대조에 사용했다. JSON이 한 행으로 저장된 release는 행번호 대신 JSON Pointer로 특정한다.
|
||||
분석 기준일은 2026-09-09이다. 분석 정본은 [배포 Stage_2_S2_00.yml](Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00_outdated_10_01.yml) **1–6,248행 전체**, SHA-256 `94c2744d71d222cfb5cc1b2a0d33a6cda20079439823de431eef378a2fa5c943`이다. 이 문서에서 `Y:번호`는 이 배포 YAML의 행번호이며, 각 절의 링크와 함께 읽는다. 실제 구현을 우선하고 [S2_00_SOW_v.2.md](S2_00_SOW_v.2.md), [S2_00_assets_v.2.md](S2_00_assets_v.2.md), [workflow 계약](Default_Agent/Stage_2_Clean/workflows/S2_00_stage1_ingress_normalize_and_bundle_compile.yml), [parent release](Default_Agent/Stage_2_Clean/manifest/stage2_release.json)는 의도·배포계약과의 대조에 사용했다. JSON이 한 행으로 저장된 release는 행번호 대신 JSON Pointer로 특정한다.
|
||||
|
||||
S2_00은 **Stage 1 자료를 받아 후속 법률판단에 사용할 입력을 구성하는 단일 deterministic ingress task**다. Stage 1의 사실·증거·구조·signal·review를 검사하고, context·claim-neutral cluster·slice·S2_10용 structured-context handoff를 만든다. 청구권의 성립, 청구취지, 청구원인 문안을 LLM으로 판단하지 않는다. C00·C05·C10·C15는 한 Python 호출 안의 논리적 구성요소이며 네 개 Agent task가 아니다. `Agent.Stages[0].prevs`와 `nexts`는 모두 빈 배열이므로 전체 Stage 2 연결은 YAML 내부 stage edge가 아니라 **출력 barrier의 route와 외부 orchestrator**로 성립한다. [근거: 배포 YAML](Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00.yml), Y:1–43, 6018–6103, 6230–6248.
|
||||
S2_00은 **Stage 1 자료를 받아 후속 법률판단에 사용할 입력을 구성하는 단일 deterministic ingress task**다. Stage 1의 사실·증거·구조·signal·review를 검사하고, context·claim-neutral cluster·slice·S2_10용 structured-context handoff를 만든다. 청구권의 성립, 청구취지, 청구원인 문안을 LLM으로 판단하지 않는다. C00·C05·C10·C15는 한 Python 호출 안의 논리적 구성요소이며 네 개 Agent task가 아니다. `Agent.Stages[0].prevs`와 `nexts`는 모두 빈 배열이므로 전체 Stage 2 연결은 YAML 내부 stage edge가 아니라 **출력 barrier의 route와 외부 orchestrator**로 성립한다. [근거: 배포 YAML](Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00_outdated_10_01.yml), Y:1–43, 6018–6103, 6230–6248.
|
||||
|
||||
다만 현재 코드와 배포계약만으로 “Stage 1 자료가 충분히 보존된 실행 가능한 S2_10 입력이 완성된다”고 단정할 수 없다. 현재 release는 `DEV_FIXTURE_RELEASE / DRAFT_NOT_EXECUTABLE`, bundle은 `STRUCTURAL_FIXTURE`, selected context는 빈 배열이다. 또한 source-family adapter 불일치, provenance pointer 불일치, 일부 자료의 slice 미편입 및 core와 MCP 진입경로의 실패처리 차이가 존재한다. 이들은 §8에 실제 구현의 제한으로 별도 기재했다. MEMORY의 240/240 등은 이전 작업 기록이며 본 분석에서 새로 실행한 시험 결과가 아니다.
|
||||
|
||||
@@ -20,7 +27,7 @@ S2_00은 **Stage 1 자료를 받아 후속 법률판단에 사용할 입력을
|
||||
| `O/` | `W/stage2_runs/by-binding/<run_binding_digest>/` |
|
||||
| `T/` | Code Executor에서 `TemporaryDirectory(prefix="liti-s2-00-")`로 만드는 임시 root |
|
||||
|
||||
`W/Default_Agent/Stage_2_Clean`가 live 자산 root다. 저장소의 `R/`는 그 배포 패키지의 로컬 위치다. request가 임의 output root나 release path를 전달할 수는 없다. [근거: 배포 YAML](Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00.yml), Y:194–205, 5548–5591, 5957–5959.
|
||||
`W/Default_Agent/Stage_2_Clean`가 live 자산 root다. 저장소의 `R/`는 그 배포 패키지의 로컬 위치다. request가 임의 output root나 release path를 전달할 수는 없다. [근거: 배포 YAML](Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00_outdated_10_01.yml), Y:194–205, 5548–5591, 5957–5959.
|
||||
|
||||
## 1. 전체 작업 DAG
|
||||
|
||||
@@ -95,13 +102,13 @@ stdout 단일 JSON inner receipt + exit 0
|
||||
(저장 이후 read-back/close 실패도 같은 label; 저장상태 재확인 필요)
|
||||
```
|
||||
|
||||
위 도식의 C00–C15는 작업 책임을 묶은 것이다. `execute_ingress`는 마지막에 manifest/intake/issue/status를 조립해 일괄 발행하므로 C00 또는 C10 진입 직후 해당 파일이 이미 외부에 저장되는 것은 아니다. 로컬 `os.rename`과 원격 `STATUS_LAST_LOGICAL_COMMIT`은 서로 다른 보장이다. [근거: 배포 YAML](Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00.yml), Y:4015–4108, 4801–5243, 5896–6103.
|
||||
위 도식의 C00–C15는 작업 책임을 묶은 것이다. `execute_ingress`는 마지막에 manifest/intake/issue/status를 조립해 일괄 발행하므로 C00 또는 C10 진입 직후 해당 파일이 이미 외부에 저장되는 것은 아니다. 로컬 `os.rename`과 원격 `STATUS_LAST_LOGICAL_COMMIT`은 서로 다른 보장이다. [근거: 배포 YAML](Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00_outdated_10_01.yml), Y:4015–4108, 4801–5243, 5896–6103.
|
||||
|
||||
## 2. IO 구조
|
||||
|
||||
### 2.1 외부 제어 입력과 Stage 1 사건 입력
|
||||
|
||||
모든 아래 파일은 JSON이다. 이 표의 producer는 현 parent release `/stage1_sources`에 선언된 expected producer이며, 실제 Stage 1 사건 실행 사실을 확인했다는 의미가 아니다. raw source는 strict UTF-8, duplicate JSON key 및 NaN/Infinity 거부, 기본 파일당 32 MiB/run 256 MiB/depth 96/items 1,000,000 제한을 적용한다. [근거: 배포 YAML](Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00.yml), Y:83–87, 211–227, 299–329, 1084–1392.
|
||||
모든 아래 파일은 JSON이다. 이 표의 producer는 현 parent release `/stage1_sources`에 선언된 expected producer이며, 실제 Stage 1 사건 실행 사실을 확인했다는 의미가 아니다. raw source는 strict UTF-8, duplicate JSON key 및 NaN/Infinity 거부, 기본 파일당 32 MiB/run 256 MiB/depth 96/items 1,000,000 제한을 적용한다. [근거: 배포 YAML](Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00_outdated_10_01.yml), Y:83–87, 211–227, 299–329, 1084–1392.
|
||||
|
||||
| 입력 exact path | producer/공급자 | C00 이후의 사용 |
|
||||
|---|---|---|
|
||||
@@ -125,13 +132,13 @@ stdout 단일 JSON inner receipt + exit 0
|
||||
| `U/quality_gates/stage1_part4_review_handoff.json` | `Task_C_FL_F2_final_fact_ledger_gate_and_writer` | wrapped review 정규화·count |
|
||||
| `U/signals/<signal_manifest.files[i].path>` | signal compiler, alias `PA-SG-COMPILER-001` | 유일한 manifest-expanded 가변 input family. `signals/` 중복 prefix 금지 |
|
||||
|
||||
Stage 1의 16개 고정 입력과 signal family를 구분한다. manifest row의 `canonical`·`domain_signal`은 의미 입력, `compatibility_view`는 파일 무결성만 검사하는 입력이다. signal ID 하나로 deduplicate하지 않고 `(transaction, file_path, record_ordinal, signal_id)` 발생 단위를 보존한다. `USED/UNUSED/UNMAPPED` 판정은 source의 명시적 ID/ref 존재 여부에 따른다. [근거: 배포 YAML](Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00.yml), Y:1420–1591, 1612–1669.
|
||||
Stage 1의 16개 고정 입력과 signal family를 구분한다. manifest row의 `canonical`·`domain_signal`은 의미 입력, `compatibility_view`는 파일 무결성만 검사하는 입력이다. signal ID 하나로 deduplicate하지 않고 `(transaction, file_path, record_ordinal, signal_id)` 발생 단위를 보존한다. `USED/UNUSED/UNMAPPED` 판정은 source의 명시적 ID/ref 존재 여부에 따른다. [근거: 배포 YAML](Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00_outdated_10_01.yml), Y:1420–1591, 1612–1669.
|
||||
|
||||
조건부 추가 입력은 release `/dependency_locks/stage1/contract_manifest_ref`가 지정한 `D/<path>`와 `/completion_seal_ref`가 지정한 `U/<path>`다. 현재는 contract path가 null이고 completion ref도 없다. 따라서 새 Stage 1 completion 파일의 상시 생성을 요구하는 구조로 해석하지 않는다. core resolver는 release-bound `path_overrides`만 인정하지만 실제 inline hydration는 먼저 release의 원래 fixed path를 읽으므로 relocation 실행 호환성에는 §8의 제한이 있다. [근거: 배포 YAML](Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00.yml), Y:791–820, 4141–4161, 5700–5720, 6162–6183.
|
||||
조건부 추가 입력은 release `/dependency_locks/stage1/contract_manifest_ref`가 지정한 `D/<path>`와 `/completion_seal_ref`가 지정한 `U/<path>`다. 현재는 contract path가 null이고 completion ref도 없다. 따라서 새 Stage 1 completion 파일의 상시 생성을 요구하는 구조로 해석하지 않는다. core resolver는 release-bound `path_overrides`만 인정하지만 실제 inline hydration는 먼저 release의 원래 fixed path를 읽으므로 relocation 실행 호환성에는 §8의 제한이 있다. [근거: 배포 YAML](Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00_outdated_10_01.yml), Y:791–820, 4141–4161, 5700–5720, 6162–6183.
|
||||
|
||||
### 2.2 출력 파일과 소비자
|
||||
|
||||
모든 persisted 출력은 canonical UTF-8 JSON이며 S2_00이 단독 writer다. `E`는 **최종 executable cluster 수**이다. 정상 branch는 아래 공통 4개 + context 7개 + slice E개, 즉 **11 + E개**다. diagnostic branch는 공통 4개 + technical diagnostic 1개, 정확히 5개다. [근거: 배포 YAML](Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00.yml), Y:172–186, 5109–5243.
|
||||
모든 persisted 출력은 canonical UTF-8 JSON이며 S2_00이 단독 writer다. `E`는 **최종 executable cluster 수**이다. 정상 branch는 아래 공통 4개 + context 7개 + slice E개, 즉 **11 + E개**다. diagnostic branch는 공통 4개 + technical diagnostic 1개, 정확히 5개다. [근거: 배포 YAML](Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00_outdated_10_01.yml), Y:172–186, 5109–5243.
|
||||
|
||||
| 출력 경로 | branch | 내용 및 직접/후속 소비 |
|
||||
|---|---|---|
|
||||
@@ -151,7 +158,7 @@ Stage 1의 16개 고정 입력과 signal family를 구분한다. manifest row의
|
||||
|
||||
`context_materialization_receipt`, `run_binding_receipt`, `hydration_stability_receipt`, `output_barrier`는 위 파일 안의 **embedded object**이며 별도 고정 파일이 아니다. `selected_legal_context_json`과 `cluster_case_payload_json`은 S2_00 output file이 아니라 **외부 orchestrator가 생성할 S2_10 item의 문자열 필드**다.
|
||||
|
||||
stdout에는 `stage2_s2_00_inner_receipt.v1` 한 객체만 출력한다. 성공은 `ok:true`, `SUCCEEDED` 또는 `DIAGNOSTIC_PUBLISHED`, route/run/hash와 `logical_publish_receipt`를 담는다. 예외는 `ok:false`, `FAILED_NO_BARRIER`, error와 exit 2다. stdout receipt를 `control/run_status.json`이나 최종 소송문안으로 오인하면 안 된다. [근거: 배포 YAML](Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00.yml), Y:6018–6103.
|
||||
stdout에는 `stage2_s2_00_inner_receipt.v1` 한 객체만 출력한다. 성공은 `ok:true`, `SUCCEEDED` 또는 `DIAGNOSTIC_PUBLISHED`, route/run/hash와 `logical_publish_receipt`를 담는다. 예외는 `ok:false`, `FAILED_NO_BARRIER`, error와 exit 2다. stdout receipt를 `control/run_status.json`이나 최종 소송문안으로 오인하면 안 된다. [근거: 배포 YAML](Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00_outdated_10_01.yml), Y:6018–6103.
|
||||
|
||||
## 3. 작업용 자산과 정확한 배포 위치
|
||||
|
||||
@@ -177,11 +184,11 @@ stdout에는 `stage2_s2_00_inner_receipt.v1` 한 객체만 출력한다. 성공
|
||||
| `R/release_ops/stage2_loader.py` 및 `R/release_ops/stage2_loader.txt` | Python/TXT | 과거 loader 보존본. S2_00 live 실행 dependency가 아님 |
|
||||
| `R/manifest/stage2_deterministic_admission_receipt.json` | JSON | 후속 구현과 공유하는 detached admission 증거. S2_00 inline에서 직접 검증하지 않으므로 외부 admission 책임과 구분 |
|
||||
|
||||
[근거: 배포 YAML](Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00.yml), Y:8–15, 194–208, 637–645, 5721–5753; [자산 inventory](S2_00_assets_v.2.md) §1.2·§3·§7; [workflow 계약](Default_Agent/Stage_2_Clean/workflows/S2_00_stage1_ingress_normalize_and_bundle_compile.yml) `orchestration_contract`.
|
||||
[근거: 배포 YAML](Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00_outdated_10_01.yml), Y:8–15, 194–208, 637–645, 5721–5753; [자산 inventory](S2_00_assets_v.2.md) §1.2·§3·§7; [workflow 계약](Default_Agent/Stage_2_Clean/workflows/S2_00_stage1_ingress_normalize_and_bundle_compile.yml) `orchestration_contract`.
|
||||
|
||||
### 3.2 Stage 1 고정 배포 closure 55개
|
||||
|
||||
각 경로는 `D/` 기준이며 모두 JSON이다. source-of-truth는 [parent release](Default_Agent/Stage_2_Clean/manifest/stage2_release.json) `/dependency_locks/stage1/concrete_paths`이다. 전체 Stage 1 YAML/runtime를 import하는 것이 아니라 **열거된 55개 source 계약 파일만** raw hash로 결속해 읽는다. `expected_concrete_path_count=55`이며 선언 개수·중복 경로·hash·strict JSON을 검사한다. [근거: 배포 YAML](Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00.yml), Y:4164–4242, 5670–5698.
|
||||
각 경로는 `D/` 기준이며 모두 JSON이다. source-of-truth는 [parent release](Default_Agent/Stage_2_Clean/manifest/stage2_release.json) `/dependency_locks/stage1/concrete_paths`이다. 전체 Stage 1 YAML/runtime를 import하는 것이 아니라 **열거된 55개 source 계약 파일만** raw hash로 결속해 읽는다. `expected_concrete_path_count=55`이며 선언 개수·중복 경로·hash·strict JSON을 검사한다. [근거: 배포 YAML](Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00_outdated_10_01.yml), Y:4164–4242, 5670–5698.
|
||||
|
||||
| lock ID 범위 | exact path 또는 닫힌 전개 규칙 | 개수·사용 |
|
||||
|---|---|---|
|
||||
@@ -218,7 +225,7 @@ signal_manifest.schema.json
|
||||
|
||||
### 3.3 Stage 2 direct closure 49개
|
||||
|
||||
현재 [parent release](Default_Agent/Stage_2_Clean/manifest/stage2_release.json) `/dependency_locks/stage2_direct`에는 아래 **49개**가 있다. inline hydration는 이 목록 전부를 읽고 raw hash 검증한다. selected context의 의미적 선택은 `/bundle/selected_context_refs`에 별도로 기록되며 현재 빈 배열이다. 따라서 “활성 profile만 원격 읽는다”와 “읽은 모든 profile을 LLM에게 준다”는 설명 모두 실제 코드와 다르다. [근거: 배포 YAML](Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00.yml), Y:3283–3475, 5741–5753.
|
||||
현재 [parent release](Default_Agent/Stage_2_Clean/manifest/stage2_release.json) `/dependency_locks/stage2_direct`에는 아래 **49개**가 있다. inline hydration는 이 목록 전부를 읽고 raw hash 검증한다. selected context의 의미적 선택은 `/bundle/selected_context_refs`에 별도로 기록되며 현재 빈 배열이다. 따라서 “활성 profile만 원격 읽는다”와 “읽은 모든 profile을 LLM에게 준다”는 설명 모두 실제 코드와 다르다. [근거: 배포 YAML](Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00_outdated_10_01.yml), Y:3283–3475, 5741–5753.
|
||||
|
||||
| path prefix | exact basename | 개수·역할 |
|
||||
|---|---|---|
|
||||
@@ -330,7 +337,7 @@ ACTIO-DEFENSE-MAP.yml
|
||||
|
||||
### 4.1 C05 검사 세부 목록
|
||||
|
||||
`check_conservation`은 `BO_FACT_MULTISET`, `FACT_ID_SEQUENCE`, `CURRENT_V8_LEDGER_EXTENSIONS`, `LES_BO_JOIN`, `LES_DECLARED_ACTUAL_COUNT`, `LES_REVERSE_INDEX`, `EVIDENCE_REFERENCE_CONSERVATION`, `EVENT_REFERENCE_CONSERVATION`, `EVENT_DISPOSITION_CONSERVATION`, `FACT_LEDGER_WRITER_REPORT_CONNECTION`, `SIGNAL_FILE_ROW_CONSERVATION`, `SIGNAL_RECORD_OCCURRENCE_CONSERVATION`, `REVIEW_OCCURRENCE_CONSERVATION`을 구성한다. BO와 source_bo_id의 Counter 동일성, fact 배열의 `F-001..F-N` 순서·유일성, LES attach/reverse index, 참조 universe, writer-report 실측 재계산을 구별한다. 비교 자료가 없는 일부 검사에는 `UNEVALUABLE`이 가능하며 이를 PASS와 동일시하지 않는다. [근거: 배포 YAML](Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00.yml), Y:2068–2436.
|
||||
`check_conservation`은 `BO_FACT_MULTISET`, `FACT_ID_SEQUENCE`, `CURRENT_V8_LEDGER_EXTENSIONS`, `LES_BO_JOIN`, `LES_DECLARED_ACTUAL_COUNT`, `LES_REVERSE_INDEX`, `EVIDENCE_REFERENCE_CONSERVATION`, `EVENT_REFERENCE_CONSERVATION`, `EVENT_DISPOSITION_CONSERVATION`, `FACT_LEDGER_WRITER_REPORT_CONNECTION`, `SIGNAL_FILE_ROW_CONSERVATION`, `SIGNAL_RECORD_OCCURRENCE_CONSERVATION`, `REVIEW_OCCURRENCE_CONSERVATION`을 구성한다. BO와 source_bo_id의 Counter 동일성, fact 배열의 `F-001..F-N` 순서·유일성, LES attach/reverse index, 참조 universe, writer-report 실측 재계산을 구별한다. 비교 자료가 없는 일부 검사에는 `UNEVALUABLE`이 가능하며 이를 PASS와 동일시하지 않는다. [근거: 배포 YAML](Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00_outdated_10_01.yml), Y:2068–2436.
|
||||
|
||||
### 4.2 cluster·slice·bundle의 서로 다른 의미
|
||||
|
||||
@@ -338,7 +345,7 @@ cluster는 청구권 또는 claim group의 확정값이 아니다. 같은 BO, LE
|
||||
|
||||
slice는 cluster별 content/ref snapshot이다. projection당 최대 262,144 UTF-8 bytes, slice content 합계 최대 2,097,152 bytes이며 넘으면 잘라내지 않고 exception을 발생시킨다. 모든 projection은 source ID에 결속된 하나의 raw source hash를 요구한다. `slice_digest`는 그 필드를 넣기 전 body의 canonical digest, bundle의 `member_slices[].sha256`는 digest 필드까지 포함한 최종 slice body의 canonical digest다. 두 hash를 같은 값으로 취급하면 안 된다.
|
||||
|
||||
cohort는 현재 compiler에서 동일 release-selected context를 쓰는 valid slice 전체를 묶는다. cluster별 profile을 별도로 법률추론하여 골라주는 알고리즘이 아니다. missing slice는 별도의 non-executable cohort로 보존한다. selected context 허용 kind는 `ACTIVE_PROFILE`, `APPROVED_COMMON_AUTHORITY`, `LAW_VALUE_TOKEN`, `AUTHORITY_PROPOSITION`; 여기의 법률 context에 주민번호·이메일·한국 휴대전화 패턴이 있으면 PII 오류를 남긴다. 이 제한을 사건별 dynamic payload의 PII 전면 금지로 확대 해석하지 않는다. [근거: 배포 YAML](Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00.yml), Y:2877–3279, 3283–3906.
|
||||
cohort는 현재 compiler에서 동일 release-selected context를 쓰는 valid slice 전체를 묶는다. cluster별 profile을 별도로 법률추론하여 골라주는 알고리즘이 아니다. missing slice는 별도의 non-executable cohort로 보존한다. selected context 허용 kind는 `ACTIVE_PROFILE`, `APPROVED_COMMON_AUTHORITY`, `LAW_VALUE_TOKEN`, `AUTHORITY_PROPOSITION`; 여기의 법률 context에 주민번호·이메일·한국 휴대전화 패턴이 있으면 PII 오류를 남긴다. 이 제한을 사건별 dynamic payload의 PII 전면 금지로 확대 해석하지 않는다. [근거: 배포 YAML](Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00_outdated_10_01.yml), Y:2877–3279, 3283–3906.
|
||||
|
||||
## 5. upstream–downstream 자료 의존
|
||||
|
||||
@@ -354,13 +361,13 @@ cohort는 현재 compiler에서 동일 release-selected context를 쓰는 valid
|
||||
| S2_00 → S2_40 status-only | 법률판단 입력이 성립하지 않는 run의 상태 보존 | 정확히 status·technical_diagnostic·manifest·intake·base issue 5개. 청구취지·청구원인 생성을 우회 |
|
||||
| S2_00 → S2_40 정상 검증 | 최종 문안이 원래 자료·run에 연결되는지 대조 | S2_00 barrier/manifest/source digest를 상류 검증 기준으로 사용. 최종 상태 writer는 S2_40 |
|
||||
|
||||
[근거: 배포 YAML](Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00.yml), Y:211–227, 4801–5243; [SOW](S2_00_SOW_v.2.md) §8.3; [S2_10 Agent](Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_10.yml), [S2_40 Agent](Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_40.yml)는 후단 소비 명세의 위치이며 해당 전체 분석은 각각의 별도 작업분석서를 참조한다.
|
||||
[근거: 배포 YAML](Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00_outdated_10_01.yml), Y:211–227, 4801–5243; [SOW](S2_00_SOW_v.2.md) §8.3; [S2_10 Agent](Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_10.yml), [S2_40 Agent](Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_40.yml)는 후단 소비 명세의 위치이며 해당 전체 분석은 각각의 별도 작업분석서를 참조한다.
|
||||
|
||||
## 6. 결정성·발행·실패의 의미
|
||||
|
||||
`input_set_digest`는 fixed snapshot과 signal/deployment closure의 logical ID/path/raw hash로 계산한다. `run_binding_digest`는 `input_set_digest + parent release raw hash + ALGORITHM_SEMANTIC_DIGEST + release_class`의 canonical JSON digest다. `run_id=S2RUN-<digest>`, output root는 `by-binding/<digest>`로 파생한다. algorithm digest는 `ALGORITHM_VERSION` 문자열에 namespace를 붙여 해시한 의미 버전 digest이며 **Python 전체 source hash와는 다르다**. actual source hash는 projection/receipt 체계가 별도로 관리한다. attempt ID는 canonical binding에 포함되지 않는다. request ID와 user/workspace hash는 receipt에 보존되지만 binding formula에는 포함되지 않는다. [근거: 배포 YAML](Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00.yml), Y:79–82, 4544–4612.
|
||||
`input_set_digest`는 fixed snapshot과 signal/deployment closure의 logical ID/path/raw hash로 계산한다. `run_binding_digest`는 `input_set_digest + parent release raw hash + ALGORITHM_SEMANTIC_DIGEST + release_class`의 canonical JSON digest다. `run_id=S2RUN-<digest>`, output root는 `by-binding/<digest>`로 파생한다. algorithm digest는 `ALGORITHM_VERSION` 문자열에 namespace를 붙여 해시한 의미 버전 digest이며 **Python 전체 source hash와는 다르다**. actual source hash는 projection/receipt 체계가 별도로 관리한다. attempt ID는 canonical binding에 포함되지 않는다. request ID와 user/workspace hash는 receipt에 보존되지만 binding formula에는 포함되지 않는다. [근거: 배포 YAML](Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00_outdated_10_01.yml), Y:79–82, 4544–4612.
|
||||
|
||||
로컬 publish는 전 출력 schema 검사 → non-status write/fsync → status에 exact artifact list/digest 삽입 → status write/fsync → staging directory rename 순서다. 원격은 localdocs `write_binary_file(overwrite:true)`와 매 파일 read-back을 사용하고 barrier를 마지막에 쓴다. 원격 directory rename·OS fsync·단일 transaction이 보장된다는 뜻은 아니다. 기존 remote barrier가 있으면 run binding과 **status 전체 bytes** 및 모든 artifact bytes까지 동일해야 idempotent success다. 다른 request ID가 같은 input binding을 공유하면 status bytes가 달라질 수 있으므로 “input이 같으면 request ID와 무관하게 무조건 재사용”이라고 쓰지 않는다. [근거: 배포 YAML](Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00.yml), Y:4015–4108, 5934–6008.
|
||||
로컬 publish는 전 출력 schema 검사 → non-status write/fsync → status에 exact artifact list/digest 삽입 → status write/fsync → staging directory rename 순서다. 원격은 localdocs `write_binary_file(overwrite:true)`와 매 파일 read-back을 사용하고 barrier를 마지막에 쓴다. 원격 directory rename·OS fsync·단일 transaction이 보장된다는 뜻은 아니다. 기존 remote barrier가 있으면 run binding과 **status 전체 bytes** 및 모든 artifact bytes까지 동일해야 idempotent success다. 다른 request ID가 같은 input binding을 공유하면 status bytes가 달라질 수 있으므로 “input이 같으면 request ID와 무관하게 무조건 재사용”이라고 쓰지 않는다. [근거: 배포 YAML](Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00_outdated_10_01.yml), Y:4015–4108, 5934–6008.
|
||||
|
||||
| 실제 조건 | 구현 결과 |
|
||||
|---|---|
|
||||
@@ -371,7 +378,7 @@ cohort는 현재 compiler에서 동일 release-selected context를 쓰는 valid
|
||||
| hydration/transport/예외 등 core 정상 조립 이전 또는 도중 fatal 오류 | `FAILED_NO_BARRIER`, exit 2. diagnostic 5파일 자동 발행이 아님 |
|
||||
| `release_class=DEV_FIXTURE_RELEASE`로 `execute_ingress` 진입 | `DEV_FIXTURE_REAL_RUN_FORBIDDEN`; STRUCTURAL_FIXTURE는 별개 bundle mode |
|
||||
|
||||
core diagnostic code 집합은 `RELEASE_SOURCE_CONTRACT_MISSING`, `RELEASE_SOURCE_PATH_MISMATCH`, `RAW_HASH_MISMATCH`, `SCHEMA_HASH_MISMATCH`, `SCHEMA_ID_MISMATCH`, `RUN_IDENTITY_CONFLICT`, `TRANSACTION_IDENTITY_CONFLICT`, `BO_FACT_CONSERVATION_FAILED`, `FACT_ID_CONSERVATION_FAILED`, `CURRENT_V8_LEDGER_EXTENSION_MISSING`다. 모든 ERROR를 동일하게 전역 차단하지 않으며 모든 exception을 status-only로 회복하지도 않는다. [근거: 배포 YAML](Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00.yml), Y:4770–4830, 6087–6103.
|
||||
core diagnostic code 집합은 `RELEASE_SOURCE_CONTRACT_MISSING`, `RELEASE_SOURCE_PATH_MISMATCH`, `RAW_HASH_MISMATCH`, `SCHEMA_HASH_MISMATCH`, `SCHEMA_ID_MISMATCH`, `RUN_IDENTITY_CONFLICT`, `TRANSACTION_IDENTITY_CONFLICT`, `BO_FACT_CONSERVATION_FAILED`, `FACT_ID_CONSERVATION_FAILED`, `CURRENT_V8_LEDGER_EXTENSION_MISSING`다. 모든 ERROR를 동일하게 전역 차단하지 않으며 모든 exception을 status-only로 회복하지도 않는다. [근거: 배포 YAML](Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00_outdated_10_01.yml), Y:4770–4830, 6087–6103.
|
||||
|
||||
`FAILED_NO_BARRIER`는 반환 label이지 실제 barrier가 없다는 확인 결과가 아니다. Y:5989의 status write 뒤 5990의 read-back 실패, 또는 publish 성공 후 Y:6084–6086의 localdocs.close 실패도 generic catch에서 이 label을 반환한다. 이미 쓴 non-status 파일과 barrier를 rollback하지 않으므로 외부 실행 관리자는 실제 상태/hash를 재확인해야 한다.
|
||||
|
||||
|
||||
@@ -1,8 +1,20 @@
|
||||
# Stage_2_S2_00.yml 단계별 Input / Output 파일 정보
|
||||
|
||||
## 2026-10-01 현행 IO 변경
|
||||
|
||||
아래 표와 `Y:행번호`는 [보존된 기존 YAML](Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00_outdated_10_01.yml)의 정적 분석이다. [현행 YAML](Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00.yml)의 Agent DAG는 `IN → Task_S2_00_prepare_request → Task_S2_00_deterministic_ingress → OUT`이다.
|
||||
|
||||
| 단계 | 입력 파일·형식·source folder | 출력 파일·형식·저장 folder | 상태 |
|
||||
|---|---|---|---|
|
||||
| `Task_S2_00_prepare_request` | 고정 입력 파일 없음. 호출자가 공급하는 네 문자열 `request_id`, `attempt_id`, `stage1_run_root_ref`, `stage1_deployment_root_ref` 및 인증된 localdocs workspace 세션 | `s2_00_request.json` · canonical UTF-8 JSON, 정확히 6필드 · `W/stage2_control/` | 함수의 생성·검증·write/read-back은 offline 시험 완료. backend 인자 전달·성공 게이트·workspace 직렬화는 미검증이며 직접 `run_code` 실행은 fail closed |
|
||||
| `Task_S2_00_deterministic_ingress` | `W/stage2_control/s2_00_request.json` 및 아래 §3의 기존 Stage 1·release 입력 | 아래 §4의 기존 `O/` 정상 11+E 또는 diagnostic 5개 JSON | 기존 ingress 검증과 두 read-pass 유지. 실사건 실행은 미검증 |
|
||||
|
||||
`W/`는 동일 user/workspace의 localdocs 논리 root다. 첫 task의 성공 뒤에도 다른 실행이 고정 request 경로를 덮어쓰지 못한다는 보장은 backend 통합 전까지 없다. 아래 “외부 orchestrator가 request를 공급”이라는 producer 표기는 보존본 기준이며, 현행 코드상 producer는 새 prepare task다.
|
||||
|
||||
|
||||
## 1. 범위·기준 및 경로 표기
|
||||
|
||||
작성 기준일: 2026-09-30. 사용자 지정 [Stage_2_00_Analysis_v1.md](Stage_2_00_Analysis_v1.md)의 DAG·IO·자산 분석을 기준으로, [배포 YAML](Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00.yml)과 해당 YAML이 참조하는 release/module manifest를 대조하였다. main 폴더 바로 아래의 동명 YAML은 분석 대상이 아니다. `Y:행번호`는 배포 YAML의 행번호이다.
|
||||
작성 기준일: 2026-09-30. 사용자 지정 [Stage_2_00_Analysis_v1.md](Stage_2_00_Analysis_v1.md)의 DAG·IO·자산 분석을 기준으로, [배포 YAML](Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00_outdated_10_01.yml)과 해당 YAML이 참조하는 release/module manifest를 대조하였다. main 폴더 바로 아래의 동명 YAML은 분석 대상이 아니다. `Y:행번호`는 배포 YAML의 행번호이다.
|
||||
|
||||
이 문서는 **파일 계약에 대한 정적 분석**이다. 사건 run이나 MCP 실행을 수행하여 파일 생성 성공을 확인한 문서가 아니다. 현재 release는 `DEV_FIXTURE_RELEASE / DRAFT_NOT_EXECUTABLE`이며 core의 DEV guard가 실제 run을 거절한다. 아래 출력은 해당 실행 경로가 정상적으로 완료되는 경우의 코드상 산출물이다.
|
||||
|
||||
|
||||
@@ -12,11 +12,12 @@ Agent:
|
||||
release_ref: Default_Agent/Stage_2_Clean/manifest/stage2_release.json
|
||||
workflow_contract_ref: Default_Agent/Stage_2_Clean/workflows/S2_00_stage1_ingress_normalize_and_bundle_compile.yml
|
||||
deployment_binding_ref: Default_Agent/Stage_2_Clean/deployment/stage2_code_executor_binding.yml
|
||||
implementation_status: S2_00_CODE_AND_FIXTURE_OFFLINE_VERIFIED_S2_10_HYBRID_REIMPLEMENTATION_AND_RESEAL_PENDING_LIVE_ADMISSION_PENDING
|
||||
implementation_status: S2_00_PREPARE_OFFLINE_VERIFIED_BACKEND_ARGUMENT_GATE_AND_SERIALIZATION_UNVERIFIED_LIVE_ADMISSION_PENDING
|
||||
Stages:
|
||||
- name: S2_00
|
||||
description: >-
|
||||
단일 Code Executor 호출 안에서 C00, C05, C10, C15를 순차 실행하고
|
||||
첫 Code Executor task에서 고정 request를 준비하고, 다음 ingress
|
||||
task의 한 호출 안에서 C00, C05, C10, C15를 순차 실행한다.
|
||||
localdocs binary IO와 status-last 논리 배리어로 결과를 발행한다.
|
||||
prevs: []
|
||||
nexts: []
|
||||
@@ -29,6 +30,254 @@ Agent:
|
||||
type: streamable-http
|
||||
url: https://code-executor.mcp.eroomai.com/mcp
|
||||
tasks:
|
||||
- task_name: Task_S2_00_prepare_request
|
||||
description: >-
|
||||
네 외부 문자열 인자 request_id, attempt_id, stage1_run_root_ref,
|
||||
stage1_deployment_root_ref를 검증하고 고정 localdocs request 경로에
|
||||
저장·read-back한다. Backend 인자 결속 및 성공 게이트는 미검증이며
|
||||
인자가 결속되지 않은 직접 실행은 실패로 종료한다.
|
||||
mcp: code-executor
|
||||
tool_name: run_code
|
||||
parameters:
|
||||
language: python
|
||||
requirements: "httpx==0.28.1"
|
||||
network: agent-network
|
||||
timeout: 300
|
||||
code: |-
|
||||
#!/usr/bin/env python3
|
||||
"""Prepare the fixed S2_00 control request from four explicit caller values.
|
||||
|
||||
The backend argument-delivery contract is not bound. Direct execution fails
|
||||
closed; callers must invoke ``prepare_request`` with the four named values.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import PurePosixPath
|
||||
import re
|
||||
import sys
|
||||
import unicodedata
|
||||
from typing import Any, Mapping
|
||||
|
||||
|
||||
REQUEST_PATH = "stage2_control/s2_00_request.json"
|
||||
LOCALDOCS_URL = "http://mcp-localdocs:8012/mcp"
|
||||
MCP_PROTOCOL_VERSION = "2025-03-26"
|
||||
INLINE_USER_HASH = "{{__user_hash__}}"
|
||||
INLINE_WORKSPACE_HASH = "{{__workspace_hash__}}"
|
||||
REQUEST_KEYS = frozenset({
|
||||
"schema_version", "workflow_id", "request_id", "attempt_id",
|
||||
"stage1_run_root_ref", "stage1_deployment_root_ref",
|
||||
})
|
||||
ID_RE = re.compile(r"[A-Za-z0-9][A-Za-z0-9._-]{0,127}\Z")
|
||||
|
||||
|
||||
class PrepareError(ValueError):
|
||||
def __init__(self, code: str, message: str) -> None:
|
||||
super().__init__(message)
|
||||
self.code = code
|
||||
|
||||
|
||||
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 _relative_path(value: str, *, code: str) -> str:
|
||||
if not isinstance(value, str) or not value or "\x00" in value or "\\" in value:
|
||||
raise PrepareError(code, "logical path is empty or malformed")
|
||||
if unicodedata.normalize("NFC", value) != value:
|
||||
raise PrepareError(code, "logical path must already be NFC")
|
||||
path = PurePosixPath(value)
|
||||
if path.is_absolute() or any(part in {"", ".", ".."} for part in path.parts):
|
||||
raise PrepareError(code, "logical path must be a contained relative path")
|
||||
rendered = path.as_posix()
|
||||
if rendered != value:
|
||||
raise PrepareError(code, "logical path is not canonical")
|
||||
return rendered
|
||||
|
||||
|
||||
def build_request(
|
||||
request_id: str,
|
||||
attempt_id: str,
|
||||
stage1_run_root_ref: str,
|
||||
stage1_deployment_root_ref: str,
|
||||
) -> bytes:
|
||||
"""Return the exact six-field canonical request; do not access localdocs."""
|
||||
if not isinstance(request_id, str) or ID_RE.fullmatch(request_id) is None:
|
||||
raise PrepareError("REQUEST_ID_INVALID", "request_id contains forbidden characters")
|
||||
if not isinstance(attempt_id, str) or ID_RE.fullmatch(attempt_id) is None:
|
||||
raise PrepareError("ATTEMPT_ID_INVALID", "attempt_id contains forbidden characters")
|
||||
request = {
|
||||
"schema_version": "stage2_s2_00_execution_request.v1",
|
||||
"workflow_id": "S2_00",
|
||||
"request_id": request_id,
|
||||
"attempt_id": attempt_id,
|
||||
"stage1_run_root_ref": _relative_path(
|
||||
stage1_run_root_ref, code="STAGE1_RUN_ROOT_REF_INVALID"),
|
||||
"stage1_deployment_root_ref": _relative_path(
|
||||
stage1_deployment_root_ref, code="STAGE1_DEPLOYMENT_ROOT_REF_INVALID"),
|
||||
}
|
||||
if set(request) != REQUEST_KEYS:
|
||||
raise PrepareError("RUN_REQUEST_CLOSED_SHAPE", "request field set drifted")
|
||||
return canonical_json_bytes(request)
|
||||
|
||||
|
||||
class LocaldocsSession:
|
||||
"""Small localdocs JSON-RPC session with verified binary write/read-back."""
|
||||
|
||||
def __init__(self, user_hash: str, workspace_hash: str, *, client: Any = None) -> None:
|
||||
for name, value in (("user_hash", user_hash), ("workspace_hash", workspace_hash)):
|
||||
if not isinstance(value, str) or re.fullmatch(r"[a-f0-9]{64}", value) is None:
|
||||
raise PrepareError("CONTEXT_HASH_INVALID", f"{name} is not a SHA-256 digest")
|
||||
self.user_hash = user_hash
|
||||
self.workspace_hash = workspace_hash
|
||||
if client is None:
|
||||
try:
|
||||
import httpx
|
||||
except ImportError as exc:
|
||||
raise PrepareError("HTTPX_UNAVAILABLE", "httpx==0.28.1 is required") from exc
|
||||
client = httpx.Client(timeout=60)
|
||||
self.client = client
|
||||
self.headers = {"Content-Type": "application/json", "Accept": "application/json, text/event-stream"}
|
||||
self.session_id: str | None = None
|
||||
self.next_id = 10
|
||||
self.initialized = False
|
||||
|
||||
def close(self) -> None:
|
||||
self.client.close()
|
||||
|
||||
def _post(self, body: Mapping[str, Any], expected_id: int | None) -> Mapping[str, Any] | None:
|
||||
try:
|
||||
response = self.client.post(LOCALDOCS_URL, json=dict(body), headers=dict(self.headers))
|
||||
response.raise_for_status()
|
||||
except Exception as exc:
|
||||
raise PrepareError("MCP_TRANSPORT_ERROR", "localdocs transport failed") from exc
|
||||
session_id = response.headers.get("mcp-session-id")
|
||||
if session_id:
|
||||
if self.session_id is None and expected_id == 1:
|
||||
self.session_id = session_id
|
||||
elif session_id != self.session_id:
|
||||
raise PrepareError("MCP_SESSION_ID_CHANGED", "localdocs session changed")
|
||||
self.headers["mcp-session-id"] = session_id
|
||||
if expected_id is None:
|
||||
return None
|
||||
try:
|
||||
payload = response.json()
|
||||
except Exception as exc:
|
||||
raise PrepareError("MCP_RESPONSE_INVALID", "localdocs response is not JSON") from exc
|
||||
if not isinstance(payload, dict) or payload.get("jsonrpc") != "2.0" or payload.get("id") != expected_id:
|
||||
raise PrepareError("MCP_RESPONSE_INVALID", "localdocs response ID or shape mismatch")
|
||||
if "error" in payload or not isinstance(payload.get("result"), dict):
|
||||
raise PrepareError("MCP_TOOL_ERROR", "localdocs returned an error")
|
||||
return payload["result"]
|
||||
|
||||
def initialize(self) -> None:
|
||||
response = self._post({"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {
|
||||
"protocolVersion": MCP_PROTOCOL_VERSION, "capabilities": {}, "clientInfo": {
|
||||
"name": "liti-s2-00-prepare", "version": "1.0.0",
|
||||
"user_id": self.user_hash, "workspace_id": self.workspace_hash,
|
||||
}}}, 1)
|
||||
if response is None or response.get("protocolVersion") != MCP_PROTOCOL_VERSION or self.session_id is None:
|
||||
raise PrepareError("MCP_INITIALIZE_INVALID", "localdocs initialization failed")
|
||||
self._post({"jsonrpc": "2.0", "method": "notifications/initialized"}, None)
|
||||
self.initialized = True
|
||||
|
||||
def call(self, name: str, arguments: Mapping[str, Any]) -> Mapping[str, Any]:
|
||||
if not self.initialized:
|
||||
raise PrepareError("MCP_NOT_INITIALIZED", "localdocs is not initialized")
|
||||
message_id = self.next_id
|
||||
self.next_id += 1
|
||||
result = self._post({"jsonrpc": "2.0", "id": message_id, "method": "tools/call",
|
||||
"params": {"name": name, "arguments": dict(arguments)}}, message_id)
|
||||
if result is None or result.get("isError") is True:
|
||||
raise PrepareError("MCP_TOOL_ERROR", f"localdocs {name} failed")
|
||||
return result
|
||||
|
||||
def read_binary(self, logical_path: str) -> bytes:
|
||||
path = _relative_path(logical_path, code="LOCALDOCS_READ_PATH_INVALID")
|
||||
result = self.call("read_binary_doc", {"doc_name": path})
|
||||
content = result.get("content")
|
||||
if not isinstance(content, list) or len(content) != 1 or not isinstance(content[0], dict) or content[0].get("type") != "text":
|
||||
raise PrepareError("LOCALDOCS_READ_SHAPE", "invalid binary response")
|
||||
text = content[0].get("text")
|
||||
if not isinstance(text, str):
|
||||
raise PrepareError("LOCALDOCS_READ_SHAPE", "missing binary envelope")
|
||||
try:
|
||||
envelope = json.loads(text)
|
||||
if isinstance(envelope, dict) and "results" in envelope:
|
||||
rows = envelope["results"]
|
||||
if not isinstance(rows, list) or len(rows) != 1 or not isinstance(rows[0], dict):
|
||||
raise ValueError("binary result cardinality mismatch")
|
||||
inner = rows[0].get("content", rows[0].get("text"))
|
||||
envelope = json.loads(inner) if isinstance(inner, str) else inner
|
||||
encoded = envelope["content_base64"]
|
||||
if not isinstance(encoded, str):
|
||||
raise ValueError("binary content is not base64")
|
||||
payload = base64.b64decode(encoded, validate=True)
|
||||
size = envelope.get("byte_length", envelope.get("size"))
|
||||
if size is not None and (not isinstance(size, int) or size != len(payload)):
|
||||
raise ValueError("binary size mismatch")
|
||||
digest = envelope.get("sha256")
|
||||
if digest is not None and digest != hashlib.sha256(payload).hexdigest():
|
||||
raise ValueError("binary hash mismatch")
|
||||
return payload
|
||||
except (ValueError, KeyError, TypeError, base64.binascii.Error) as exc:
|
||||
raise PrepareError("LOCALDOCS_READ_SHAPE", "invalid binary envelope") from exc
|
||||
|
||||
def write_binary_verified(self, logical_path: str, payload: bytes) -> str:
|
||||
path = _relative_path(logical_path, code="LOCALDOCS_WRITE_PATH_INVALID")
|
||||
if path != REQUEST_PATH:
|
||||
raise PrepareError("LOCALDOCS_WRITE_PATH_INVALID", "prepare may write only the fixed request path")
|
||||
result = self.call("write_binary_file", {
|
||||
"path": path, "content_base64": base64.b64encode(payload).decode("ascii"), "overwrite": True,
|
||||
})
|
||||
if not isinstance(result.get("content"), list) or result.get("isError") is True:
|
||||
raise PrepareError("LOCALDOCS_WRITE_FAILED", "localdocs did not acknowledge write")
|
||||
observed = self.read_binary(path)
|
||||
if observed != payload:
|
||||
raise PrepareError("LOCALDOCS_WRITE_READBACK_MISMATCH", "request read-back differs")
|
||||
return hashlib.sha256(observed).hexdigest()
|
||||
|
||||
|
||||
def prepare_request(
|
||||
request_id: str,
|
||||
attempt_id: str,
|
||||
stage1_run_root_ref: str,
|
||||
stage1_deployment_root_ref: str,
|
||||
*,
|
||||
localdocs: Any = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Validate, write, read back, and close an authenticated localdocs session."""
|
||||
payload = build_request(request_id, attempt_id, stage1_run_root_ref, stage1_deployment_root_ref)
|
||||
session = localdocs if localdocs is not None else LocaldocsSession(INLINE_USER_HASH, INLINE_WORKSPACE_HASH)
|
||||
failure: Exception | None = None
|
||||
digest: str | None = None
|
||||
try:
|
||||
session.initialize()
|
||||
digest = session.write_binary_verified(REQUEST_PATH, payload)
|
||||
except Exception as exc:
|
||||
failure = exc
|
||||
try:
|
||||
session.close()
|
||||
except Exception as exc:
|
||||
if failure is None:
|
||||
failure = PrepareError("LOCALDOCS_CLOSE_FAILED", "localdocs session close failed")
|
||||
failure.__cause__ = exc
|
||||
if failure is not None:
|
||||
raise failure
|
||||
return {"ok": True, "workflow_id": "S2_00", "path": REQUEST_PATH,
|
||||
"request_sha256": digest}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.stdout.buffer.write(canonical_json_bytes({
|
||||
"ok": False, "error": {"code": "PREPARE_ARGUMENT_BINDING_UNVERIFIED",
|
||||
"message": "four caller values must be bound by the backend"}}))
|
||||
raise SystemExit(2)
|
||||
- task_name: Task_S2_00_deterministic_ingress
|
||||
description: >-
|
||||
고정 request와 release를 hydration하고 S2_00 pure core를 실행한 뒤
|
||||
@@ -192,7 +441,7 @@ Agent:
|
||||
# literal placeholders in the offline parity mirror and its unit tests.
|
||||
INLINE_USER_HASH = "{{__user_hash__}}"
|
||||
INLINE_WORKSPACE_HASH = "{{__workspace_hash__}}"
|
||||
EXPECTED_STAGE2_RELEASE_SHA256 = "9fa85bb94c5f14f675dcdaa0cf94d06745b21675d97797539ffc14015dcc9f53"
|
||||
EXPECTED_STAGE2_RELEASE_SHA256 = "2363f1166ee8a3ea2b38350cde61fccfc9852a413c6b9f14a6ed6606af15a590"
|
||||
INLINE_REQUEST_PATH = "stage2_control/s2_00_request.json"
|
||||
INLINE_STAGE2_ASSET_ROOT = "Default_Agent/Stage_2_Clean"
|
||||
INLINE_STAGE2_RELEASE_PATH = (
|
||||
@@ -6235,13 +6484,18 @@ Agent:
|
||||
task_procedure:
|
||||
IN:
|
||||
nexts:
|
||||
- Task_S2_00_deterministic_ingress
|
||||
- Task_S2_00_prepare_request
|
||||
wait_until: []
|
||||
Task_S2_00_prepare_request:
|
||||
nexts:
|
||||
- Task_S2_00_deterministic_ingress
|
||||
wait_until:
|
||||
- IN
|
||||
Task_S2_00_deterministic_ingress:
|
||||
nexts:
|
||||
- OUT
|
||||
wait_until:
|
||||
- IN
|
||||
- Task_S2_00_prepare_request
|
||||
OUT:
|
||||
nexts: []
|
||||
wait_until:
|
||||
|
||||
@@ -71,7 +71,7 @@ Agent:
|
||||
|
||||
WORKFLOW_ID = "S2_20"
|
||||
ALGORITHM_VERSION = "s2_20_relief_plan/1.0.0"
|
||||
EXPECTED_STAGE2_RELEASE_SHA256 = "9fa85bb94c5f14f675dcdaa0cf94d06745b21675d97797539ffc14015dcc9f53"
|
||||
EXPECTED_STAGE2_RELEASE_SHA256 = "2363f1166ee8a3ea2b38350cde61fccfc9852a413c6b9f14a6ed6606af15a590"
|
||||
LOCALDOCS_URL = "http://mcp-localdocs:8012/mcp"
|
||||
WEAVIATE_MCP_URL = "https://weaviate.eroomai.com/mcp"
|
||||
MCP_PROTOCOL_VERSION = "2025-03-26"
|
||||
|
||||
@@ -73,7 +73,7 @@ Agent:
|
||||
from typing import Any, Mapping
|
||||
|
||||
|
||||
EXPECTED_STAGE2_RELEASE_SHA256 = "9fa85bb94c5f14f675dcdaa0cf94d06745b21675d97797539ffc14015dcc9f53"
|
||||
EXPECTED_STAGE2_RELEASE_SHA256 = "2363f1166ee8a3ea2b38350cde61fccfc9852a413c6b9f14a6ed6606af15a590"
|
||||
LOCALDOCS_URL = "http://mcp-localdocs:8012/mcp"
|
||||
MCP_PROTOCOL_VERSION = "2025-03-26"
|
||||
INLINE_USER_HASH = "{{__user_hash__}}"
|
||||
|
||||
@@ -68,7 +68,7 @@ Agent:
|
||||
|
||||
WORKFLOW_ID = "S2_40"
|
||||
ALGORITHM_VERSION = "s2_40_finalizer/1.0.0"
|
||||
EXPECTED_STAGE2_RELEASE_SHA256 = "9fa85bb94c5f14f675dcdaa0cf94d06745b21675d97797539ffc14015dcc9f53"
|
||||
EXPECTED_STAGE2_RELEASE_SHA256 = "2363f1166ee8a3ea2b38350cde61fccfc9852a413c6b9f14a6ed6606af15a590"
|
||||
LOCALDOCS_URL = "http://mcp-localdocs:8012/mcp"
|
||||
MCP_PROTOCOL_VERSION = "2025-03-26"
|
||||
INLINE_USER_HASH = "{{__user_hash__}}"
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
# S2_00 request 생성·저장 계획 v2
|
||||
|
||||
작성일: 2026-10-01. 상태: **계획 작성 완료 / 구현 미착수**. [v1](s2-00-request-preparation_v1.md)의 두 task 구상과 기존 request 계약을 계승한다. 이 문서는 `s2_00_request.json`의 생성 코드를 먼저 구현·시험할 수 있도록 작업 순서를 개정한 계획이다.
|
||||
|
||||
## Objective
|
||||
|
||||
`Stage_2_S2_00.yml` 작업명세서의 맨 앞에 `Task_S2_00_prepare_request`를 배치한다. 이 작업의 핵심 함수를 네 외부 인자를 받는 명시적 인터페이스로 구현하여, 기존 6필드 검증·canonical JSON 직렬화·localdocs binary 저장/read-back 방식으로 `stage2_control/s2_00_request.json`을 만든다. 함수와 task의 offline 검증은 backend의 인자 주입·스케줄링·직렬화 구현 정보가 없어도 진행한다. 실제 운영 연결은 확인된 backend 인터페이스에만 결속한다.
|
||||
|
||||
```text
|
||||
IN → Task_S2_00_prepare_request → Task_S2_00_deterministic_ingress → OUT
|
||||
4개 인자 검증 기존 request 읽기·2회 hydration
|
||||
6필드 JSON 구성
|
||||
localdocs 저장·read-back
|
||||
```
|
||||
|
||||
위 DAG는 작업명세서에서 의도하는 순서다. `nexts`·`wait_until` 선언만으로 prepare 실패 시 ingress가 실제로 차단되거나 두 실행 사이의 파일 경합이 해소되었다고 주장하지 않는다.
|
||||
|
||||
## Deliverables
|
||||
|
||||
1. 네 인자를 받아 정확히 6필드 request bytes를 생성·검증하는 작은 함수와, 검증된 bytes를 고정 경로에 write/read-back하는 준비 함수.
|
||||
2. 준비 task를 기존 ingress 앞에 둔 authoring YAML 및 빌더로 생성한 배포 YAML, 두 task의 code mirror·빌드 receipt.
|
||||
3. 변경으로 실제 영향을 받는 workflow·executor binding·schema·manifest/release·hash 참조와 정적·offline 시험.
|
||||
4. 구현 후 실제 상태에 맞춘 S2_00 분석서·IO 문서. 운영 연결이 검증되지 않았다면 그 상태를 명시한다.
|
||||
|
||||
## Scope and Non-Scope
|
||||
|
||||
현재 request의 **6필드와 고정 경로를 유지**한다. 기존 C00–C15의 자료 처리 및 정상/diagnostic 출력 계약은 변경하지 않는다. 새 request receipt 파일, 범용 실행 프레임워크, 임의 backend 변수·템플릿, 독립 잠금 서비스를 생성 코드의 선행 조건으로 두지 않는다. 기존 DEV release 제한이나 다른 Stage 2 결함의 해소는 이 작업의 완료 기준에 포함하지 않는다.
|
||||
|
||||
이 계획의 1차 완료는 **생성·검증·저장 코드와 offline 시험**이다. 같은 YAML이 실제 사건 run에서 네 인자를 어떻게 받는지, 실패 시 다음 task 호출을 어떻게 막는지, 고정 request 경로의 동시 쓰기를 어떻게 막는지는 별도의 운영 통합 완료 조건이다. 이 구분 때문에 운영 정보가 없어도 코드 작성을 중단하지 않으며, offline 결과를 live 실행 성공으로 표시하지 않는다.
|
||||
|
||||
## Known Inputs
|
||||
|
||||
`M/ = Case_02_Comparison_Research/YAML_Prompts/2. Stage_2/`, `R/ = M/Default_Agent/Stage_2_Clean/`, `W/ = localdocs가 결속한 논리 workspace root`.
|
||||
|
||||
- 정본: `M/Stage_2_S2_00_v.2.yml`; 현재 배포본: `R/agent_scripts/Stage_2_S2_00.yml`.
|
||||
- 기존 request 소비 코드는 `stage2_control/s2_00_request.json`을 먼저 읽고, 정확히 6필드·ID·canonical 상대경로를 검증하며, hydration에서 같은 원격 경로의 bytes를 두 번 비교한다.
|
||||
- 기존 runtime의 `canonical_json_bytes`, `_inline_validate_request`, `_inline_relative_path`, `write_binary_verified`가 각각 직렬화·검증·경로 검증·write/read-back 기준이다. 독립 task가 runtime `.py`를 외부 import한다고 가정하지 않는다.
|
||||
- 현재 S2_00 빌더는 정확히 1 task 및 1 code pointer를 요구하고 executor binding은 결과 root 쓰기를 명시한다. 준비 task 추가 시 관련 계약 변경이 필요하다.
|
||||
- backend의 네 인자 주입·성공 조건·workspace 직렬화 구현은 현재 확인되지 않았다. **코드 구현의 선행 차단 조건이 아니라 운영 연결의 미확인 항목**으로 둔다.
|
||||
|
||||
## Material Assumptions
|
||||
|
||||
- 네 값은 호출자가 지정한 문자열이며, 함수는 위치를 추측하거나 사건 폴더를 검색하지 않는다.
|
||||
- `request_id`와 `attempt_id`는 기존 정규식에 맞고, 두 root ref는 NFC canonical workspace 상대경로여야 한다. 문법 검증과 실제 Stage 1 run 선택·접근 권한 검증은 구별한다.
|
||||
- `write_binary_verified`의 성공은 그 호출 직후 read-back한 bytes의 일치를 뜻한다. 다른 실행의 동시 덮어쓰기까지 막는 보장은 아니다.
|
||||
- 독립 task 사이에 메모리 공유가 있다고 가정하지 않는다. 같은 인자를 후행 task에 공급할 수 있음이 확인되면 v1의 전체 expected-bytes 비교를 적용한다. 확인 전에는 그 비교를 운영상 완료했다고 주장하지 않는다.
|
||||
|
||||
## Questions That Could Change the Outcome
|
||||
|
||||
다음 사항은 **운영 연결 단계에서만** 확인한다: 실제 backend가 네 값을 task에 전달하는 형식, 실패한 prepare 이후 ingress 시작을 막는 판정 방식, 동일 workspace의 고정 경로를 보호하는 직렬화·취소 처리. 코드 작성 시 이 형식을 추측한 placeholder를 executable로 넣지 않는다. 운영 기능이 없다면 필요한 backend 변경을 기록하고 생성 코드·offline 검증 결과와 분리한다.
|
||||
|
||||
## Workstreams and Dependencies
|
||||
|
||||
### 1. 기존 계약에 맞는 순수 request 생성 함수
|
||||
|
||||
```python
|
||||
def build_request(
|
||||
request_id: str,
|
||||
attempt_id: str,
|
||||
stage1_run_root_ref: str,
|
||||
stage1_deployment_root_ref: str,
|
||||
) -> bytes:
|
||||
# 설명용 서명: 실제 함수명과 코드는 구현 시 기존 inline 규약에 맞춘다.
|
||||
...
|
||||
```
|
||||
|
||||
1. 네 인자의 타입·값을 검사한다. ID는 `[A-Za-z0-9][A-Za-z0-9._-]{0,127}`를, root ref는 기존 `_inline_relative_path`의 NFC·상대경로·구성요소 규칙을 따른다.
|
||||
2. `schema_version = "stage2_s2_00_execution_request.v1"`, `workflow_id = "S2_00"`을 코드 상수로 넣어 **정확히 여섯 필드**를 구성한다. caller가 추가 키를 request에 넣는 우회 경로를 만들지 않는다.
|
||||
3. 기존 `canonical_json_bytes`와 동일한 규칙으로 UTF-8 bytes를 만든다. 기존 `_inline_validate_request`의 수용 여부와 byte 직렬화 규칙을 대조한다. 별도 schema framework를 만들지 않는다.
|
||||
4. 이 함수는 localdocs를 호출하지 않아 정상·오류 입력의 단위 시험이 가능해야 한다. 함수가 root의 실재나 권한을 확인했다고 표시하지 않는다.
|
||||
|
||||
### 2. localdocs 저장 함수와 선행 task 배치
|
||||
|
||||
- 준비 함수는 위 bytes를 받아 인증된 localdocs 세션에서 **고정 경로** `stage2_control/s2_00_request.json`에 `write_binary_verified` 방식으로 쓴다. write 응답과 같은 경로의 read-back bytes가 일치해야 성공한다.
|
||||
- session 초기화·종료 및 오류 응답은 기존 S2_00 inline MCP 패턴을 따른다. 종료 실패까지 성공으로 표시하지 않는다. 독립 task 코드가 필요한 helper를 포함할 때 기존 구현과 parity를 검사한다.
|
||||
- 새 task를 authoring YAML의 첫 task로 놓고 `task_procedure`를 `IN → prepare → ingress → OUT`으로 지정한다. prepare의 입력 계약에는 네 문자열 인자와 localdocs 세션을 명시한다. **실제 backend가 지원하는 전달 문법을 확인하기 전에는 임의 `run_code` parameter·환경변수·치환 토큰을 작성하지 않는다.**
|
||||
- prepare는 성공 시 최소한의 기계 판독 가능 성공/실패 결과를 내도록 설계한다. 신규 저장 receipt나 별도 버전 schema는 만들지 않는다. 오류·read-back 불일치·session 종료 실패 시 실패를 반환한다.
|
||||
- 기존 ingress는 request의 strict 검증·두 read-pass를 유지한다. 같은 네 인자를 후행에도 공급하는 연결이 확인되면 첫 읽기의 전체 bytes를 재구성한 expected bytes와 비교하는 검사를 추가한다. 이 연결이 없는 offline 단계에서는 준비 함수와 ingress validator의 호환성을 직접 시험하고, 런타임의 동일 요청 소비는 미검증으로 표시한다.
|
||||
|
||||
### 3. 변경된 task 구조에 필요한 자산만 갱신
|
||||
|
||||
| 자산 | 필요한 변경 |
|
||||
|---|---|
|
||||
| `M/Stage_2_S2_00_v.2.yml` | 준비 task를 맨 앞에 배치하고 순서·입력·성공/실패 계약 기록 |
|
||||
| `R/offline_build/build_s2_00_inline_projection.py/.txt` | 정확한 두 task ID·순서·code pointer 검증; `tasks[0]`이 ingress라는 전제 제거 |
|
||||
| `R/agent_scripts/Stage_2_S2_00.yml` 및 runtime `.py/.txt` mirror | 정본으로부터 배포본과 두 task의 code mirror 재생성·parity 확인. 기존 파일 덮어쓰기 전 원본 보존 조건 적용 |
|
||||
| `R/manifest/s2_00_inline_code_receipt.json` | 두 code pointer·hash와 task DAG 결속; 빌드 receipt에만 반영, 새 실행 receipt 파일 없음 |
|
||||
| workflow·executor binding | 준비 단계의 네 인자 계약·고정 request 쓰기 경로와 기존 ingress 결과 경로를 구별. 실제 backend 강제 여부는 별도 표시 |
|
||||
| 관련 schema·module/release/hash | task 구조·참조가 실제 바뀐 파일만 갱신하고 기존 6필드 request schema는 유지. hash 참조 순서를 확인하여 순환 결속을 만들지 않음 |
|
||||
| `M/Stage_2_00_Analysis_v1.md`, `M/Stage_2_00_IO_info.md` | 구현·검증 후 선행 task와 생성 시점, offline/운영 상태의 경계를 반영 |
|
||||
|
||||
기존 `Stage_2_S2_00_outdated_9_09.yml` 보존본과 원본 v1 계획은 변경하지 않는다. 새 task를 추가했으므로 기존 단일 task 빌드 검사를 끄거나 결과만 수동 수정하는 방식은 사용하지 않는다.
|
||||
|
||||
### 4. offline 시험과 운영 연결의 순서
|
||||
|
||||
1. 순수 함수의 정상·오류 입력과 기존 validator 호환성을 시험한다.
|
||||
2. fake localdocs 또는 격리 fixture에서 write/read-back 일치, 쓰기·읽기·session 종료 실패, 고정 경로 외 쓰기 방지를 시험한다. 실패 결과 뒤 ingress를 호출하지 않는 **로컬 호출 흐름**도 확인한다.
|
||||
3. builder `--check`, 두 code mirror/hash, 변경 schema·release 참조, 기존 S2_00 정상/diagnostic fixture 회귀를 검증한다. 같은 입력으로 반복 빌드해 bytes 차이가 없는지 확인한다.
|
||||
4. 실제 backend 인자 전달·실패 차단·workspace 직렬화가 확인되면 YAML의 실행 연결을 결속하고, 동일 요청 소비·경쟁·취소 시험을 수행한다. 이 단계가 끝나기 전에는 실제 S2_00 run 적격성을 주장하지 않는다.
|
||||
|
||||
## Source and Tool Plan
|
||||
|
||||
현재 authoring YAML·배포 코드·request schema·builder·binding을 1차 근거로 사용한다. 구현의 초기 시험은 직접 함수 호출과 격리된 localdocs 대역으로 진행한다. 운영 연결에 필요한 정보만 실제 backend 코드·명세에서 확인한다. 이 문서 작성 자체는 코드를 실행·수정하지 않는다.
|
||||
|
||||
## Validation Plan
|
||||
|
||||
| 검증 | 통과 기준 | 상태 구분 |
|
||||
|---|---|---|
|
||||
| 4인자·6필드·canonical bytes | 정상 입력의 정확한 bytes 및 기존 validator 일치; 타입·ID·경로 오류는 저장 전 거절 | 함수 단위 |
|
||||
| 저장·read-back | 저장 경로 한정, bytes 일치; 오류·응답 불일치·close 실패는 실패 | offline IO |
|
||||
| task 구조·빌드 | prepare가 첫 task, 지정 DAG, 두 code/mirror/hash 일치, 반복 빌드 동일 | 정적·offline |
|
||||
| ingress 회귀 | 기존 strict request·두 read-pass 및 정상 11+E/diagnostic 5파일 계약 유지 | offline 회귀 |
|
||||
| 실제 인자 공급·실패 차단·동시 실행 | 같은 인자의 전체 bytes 대조, prepare 실패 시 ingress 미호출, workspace 실행 경합 없음 | **backend 통합 후에만** |
|
||||
|
||||
테스트용 fixture 통과를 실사건 실행, downstream handoff 완성, 법률 검증으로 해석하지 않는다. DEV release guard를 시험 편의상 제거하지 않는다.
|
||||
|
||||
## Approval Boundaries
|
||||
|
||||
이 요청은 **v2 계획 문서 작성**이다. 구현·배포·원격 실행은 수행하지 않는다. 이후 구현 요청에서는 승인된 범위의 로컬 수정과 offline 시험을 진행하고, 실제 운영 호출·외부 쓰기가 필요한 단계의 권한을 그때 확인한다.
|
||||
|
||||
## Progress
|
||||
|
||||
- [x] v1의 Objective 이하 검토 및 backend 비의존 구현 순서로 개정
|
||||
- [x] 새 task의 첫 배치, 네 인자 함수, 저장/read-back·검증 계획 작성
|
||||
- [ ] 생성·저장 함수와 준비 task 구현
|
||||
- [ ] builder·binding·hash 갱신 및 offline 검증
|
||||
- [ ] backend 연동·동시 실행 검증
|
||||
- [ ] 구현 상태에 맞춘 분석·IO 문서 개정
|
||||
|
||||
## Decision Log
|
||||
|
||||
| 결정 | 근거 | 결과 |
|
||||
|---|---|---|
|
||||
| 생성 코드와 운영 연결을 별도 완료 조건으로 둠 | 함수는 명시적 인자와 localdocs 대역으로 검증 가능 | backend 정보 미확인 상태에서도 구현 진행 |
|
||||
| 두 task 구조와 첫 task 배치 유지 | 사용자가 지정한 Stage 2 작업명세서 순서 | 기존 단일 task 빌드 계약은 실제 구현 시 갱신 |
|
||||
| 기존 validator·serializer·verified write 재사용 | 6필드·bytes·경로 계약이 이미 존재 | 중복 구현과 새 receipt 제거 |
|
||||
| 후행 expected bytes 대조는 인자 결속 확인 후 적용 | 별개 task의 인자 동일성을 가정할 수 없음 | offline 호환성과 실제 동일 요청 소비를 구별 |
|
||||
|
||||
## Evidence Ledger
|
||||
|
||||
| 명제 | 확인된 자료 | 상태 |
|
||||
|---|---|---|
|
||||
| 기존 request는 6필드 고정 계약 | `R/runtime/s2_00_ingress.py`의 `_inline_validate_request`; `R/schemas/ingress.schema.json` | 기존 구현 확인 |
|
||||
| 기존 직렬화·저장/read-back helper가 있음 | 같은 runtime의 `canonical_json_bytes`, `_InlineLocaldocs.write_binary_verified` | 기존 구현 확인 |
|
||||
| 단일 task 빌더·고정 output 쓰기 root 변경 필요 | `R/offline_build/build_s2_00_inline_projection.py`, `R/deployment/stage2_code_executor_binding.yml` | 기존 제약 확인 |
|
||||
| 실제 backend 인자 공급·성공 게이트·직렬화 | 현재 v1 및 관련 자료에서 확인되지 않음 | **UNVERIFIED; 생성 함수 구현을 막지 않음** |
|
||||
|
||||
## Risks and Failure Modes
|
||||
|
||||
- 두 task를 YAML에 나열하기만 해서는 외부 인자 공급이나 실패 시 후행 차단이 구현되지 않는다. backend 연결 확인 전에는 offline artifact로 표시한다.
|
||||
- write/read-back은 그 순간의 bytes를 확인하지만 고정 경로의 동시 덮어쓰기를 막지 못한다. 실제 운영에는 workspace 단위 통제가 별도로 필요하다.
|
||||
- 정본·배포본·mirror·receipt·binding의 hash를 따로 갱신하면 참조 불일치가 생긴다. 변경된 참조만 함께 재결속하고 반복 빌드로 확인한다.
|
||||
- 현 release의 `DEV_FIXTURE_RELEASE / DRAFT_NOT_EXECUTABLE` 상태는 request 생성 기능의 구현만으로 바뀌지 않는다.
|
||||
|
||||
## Results and Residual Uncertainty
|
||||
|
||||
v2는 backend 기능 확인을 **생성 코드 구현의 선행 조건에서 운영 연결의 완료 조건으로 이동**시켰다. 실행 가능한 함수와 offline 저장 시험을 먼저 만들고, Stage 2 작업명세서에서 이를 첫 task로 배치하는 변경을 계획한다. 실제 인자 주입·후행 차단·동시 실행 통제의 구현 여부는 아직 확인되지 않았고, 이 계획 작성으로 request 파일이나 새 YAML이 생성된 것은 아니다.
|
||||
Reference in New Issue
Block a user