Compare commits
4
Commits
112be9ed57
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
23f5134e89 | ||
|
|
234b65d7c7 | ||
|
|
546bad4d4d | ||
|
|
48fd0435a8 |
+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 구조를 현행 구조로 소급하지 않았다.
|
||||||
+314
@@ -0,0 +1,314 @@
|
|||||||
|
# Stage_2_S2_00.yml(v6) 분석서
|
||||||
|
|
||||||
|
분석 기준일: 2026-10-02(KST). 분석 대상은 이 문서와 같은 폴더의 [Stage_2_S2_00.yml](./Stage_2_S2_00.yml)이며, Agent 이름은 `Stage_2_S2_00_v6`, Agent version은 `6.0.0`, algorithm version은 `s2_00_direct_ingress/6.0.0`이다. 대상 YAML의 SHA-256은 `63b5158eb2bb77524401ca3d15bb21c3740644c14ffa4728564354ff0db5b4a3`이다.
|
||||||
|
|
||||||
|
분석의 우선 근거는 현행 YAML의 실제 코드·상수·task procedure이다. 실행 근거는 앞선 workspace 실행에서 다운로드한 v6 결과 5개를 이번 분석에서 다시 읽고 hash를 확인한 자료와 [Stage 2 작업 기록](../../../MEMORY.md)의 v6 항목이다. 기존 분석서나 개정 전략의 목표를 현행 구현으로 간주하지 않는다. S2_10~40의 YAML·자산 schema는 분석 기준에 포함하지 않았다. 이번 분석에서는 YAML 수정이나 workspace 재실행을 수행하지 않았다.
|
||||||
|
|
||||||
|
## Executive Summary
|
||||||
|
|
||||||
|
S2_00 v6은 Stage 1 사건 결과의 root와 배포 자산의 root를 직접 받아, 동일 workspace의 원본을 읽고 검증한 뒤 후속 작업이 읽을 수 있는 사건 context와 review 원장을 저장하는 단일 비 LLM Agent이다. 기본 사건 root는 `.`, 배포 root는 `Default_Agent`이다. 별도 request 파일이나 `request_id`·`attempt_id`를 만들지 않고, 독립 실행된 Stage 1 결과를 `prev`로 찾는 방식에도 의존하지 않는다.
|
||||||
|
|
||||||
|
YAML에 등록된 실행 task는 `Task_S2_00_deterministic_ingress` 하나다. 이 task가 Code Executor의 `run_code`에 inline Python을 전달하고, Python이 Localdocs MCP를 통해 파일을 읽고 쓴다. C00·C05·C10·C15는 코드 안에서 수행하는 기능 구분이며 별도 task·LLM prompt·MCP 호출 단위가 아니다.
|
||||||
|
|
||||||
|
주요 결과는 입력 목록, 입력 검증 보고서, review·issue 원장, 사건 context, 마지막 완료 상태의 5개 JSON이다. 기본 저장 위치는 workspace Root 기준 `stage2_runs/from-stage1/s2_00/v6/`이다. 차단 상태에서는 사건 context 대신 기술 진단 파일을 저장한다. 정상·차단 결과 모두 마지막 status를 완료 표지로 사용하며, 기존 완료 결과와 내용이 정확히 같을 때만 재사용한다.
|
||||||
|
|
||||||
|
앞선 `10월_1일_구성` workspace 실행은 task `COMPLETED`, `exit_code=0`, `READY_WITH_ISSUES`, `PUBLISHED_STATUS_LAST`로 종료됐다. 다운로드한 결과의 검증 항목은 PASS 19개, FAIL 0개, UNEVALUABLE 2개다. 사건 context에는 증거 30개, 이벤트 71개, BO 57개, fact 57개, LES 47개, 신호 occurrence 475개와 cluster 46개가 들어 있다. review occurrence 1,128개를 보존했고, 이 중 1,126개는 `UNRESOLVED`, 2개는 `CONDITIONAL`이다.
|
||||||
|
|
||||||
|
이 성공은 원본을 읽어 검증·구조화하고 결과를 저장하는 workspace 테스트의 성공이다. YAML의 허용 모드는 `WORKSPACE_EXECUTION_TEST`, 정책 분류는 `DEV_FIXTURE_RELEASE`로 유지되어 있다. producer 미확인 WARNING 11건, 원본 seal의 확인 불가, 신호 schema 검사 조건, 동일 출력 root의 동시 writer, 후속 Agent와의 연결은 별도 확인 범위로 남아 있다.
|
||||||
|
|
||||||
|
근거: `run_inline_mcp`, `execute_ingress`, `publish_result` 및 다운로드한 `ingress_status.json`·`intake_report.json`·`case_context.json`·`issue_ledger.base.json`.
|
||||||
|
|
||||||
|
## 전체 작업 DAG와 책임 경계
|
||||||
|
|
||||||
|
### YAML에 등록된 실제 DAG
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
IN["IN"] --> T["Task_S2_00_deterministic_ingress<br/>Code Executor run_code"]
|
||||||
|
T --> OUT["OUT"]
|
||||||
|
```
|
||||||
|
|
||||||
|
Stage는 `S2_00` 하나이며 `prevs: []`, `nexts: []`이다. task procedure의 `IN.nexts`가 단일 task를 가리키고, task는 `IN`을 기다린 뒤 `OUT`으로 연결된다. `OUT`은 이 task를 기다린다. `IN`·`OUT`은 procedure 경계 노드이고 별도 코드 실행 task가 아니다. Stage 1이나 S2_10을 자동 실행시키는 DAG edge는 없다.
|
||||||
|
|
||||||
|
### 단일 task 내부의 처리 흐름
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
R["사건·배포 root 및 실행 모드 검증"] --> A["backend workspace context로 Localdocs initialize"]
|
||||||
|
A --> H["Stage 1 원본·신호·선택된 배포 자산 hydration"]
|
||||||
|
H --> V["입력 계약·schema·hash·SG01·보존 관계 검증"]
|
||||||
|
V --> N["review occurrence 정규화 및 처리 상태 판정"]
|
||||||
|
N --> B{"BLOCKED인가"}
|
||||||
|
B -- "아니오" --> C["원본 member·명시적 관계·cluster·bundle 구성"]
|
||||||
|
B -- "예" --> D["technical_diagnostic 구성"]
|
||||||
|
C --> O["상태별 결과 5개 및 provenance·출력 계약 검증"]
|
||||||
|
D --> O
|
||||||
|
O --> S["읽은 원격 원본을 재독해 최초 bytes와 비교"]
|
||||||
|
S --> P["기존 출력 충돌 확인"]
|
||||||
|
P --> W["비 status 파일 쓰기·read-back 후 status 기록<br/>또는 동일 완료 결과 재사용"]
|
||||||
|
W --> E["stdout receipt 및 exit code"]
|
||||||
|
```
|
||||||
|
|
||||||
|
위 흐름의 역할은 다음처럼 나뉜다. C00~C15는 이해를 위한 대응표이며, 실행 순서가 네 개의 독립 task로 분리되는 것은 아니다.
|
||||||
|
|
||||||
|
| 기능 구분 | 실제 책임 | 주요 구현 |
|
||||||
|
|---|---|---|
|
||||||
|
| C00 | 원본 확보, 고정 입력·정책·schema·producer·transaction·hash·gate·보존 관계 확인 | `hydrate_stage1`, `validate_ingress_contracts`, `expand_stage2_signal_all`, `verify_cross_artifact_seals`, `check_conservation` |
|
||||||
|
| C05 | 원본의 정확한 배열 위치 판독, JSON pointer·값 digest·원문 projection 보존, review 정규화 | `_source_record_locations`, `_record_locations_for_signal`, `_provenance`, `normalize_review_items` |
|
||||||
|
| C10 | 명시적인 원본 참조로 member를 연결하고 claim-neutral cluster 및 후보 의존 관계 구성 | `compile_case_context`, `_tarjan_scc` |
|
||||||
|
| C15 | cluster bundle·scheduling wave·결과 파일 구성, 원본 재독, 출력 검증·발행 | `compile_case_context`, `validate_output_files`, `verify_remote_stability`, `publish_result` |
|
||||||
|
|
||||||
|
### 시스템별 책임 경계
|
||||||
|
|
||||||
|
| 구성 요소 | 담당 책임 | S2_00 코드가 대신 수행하지 않는 사항 |
|
||||||
|
|---|---|---|
|
||||||
|
| Stage 1 | 사건 원본·증거·BO·LES·fact·signal·review 결과 작성 | S2_00은 Stage 1 자료를 수정하거나 Stage 1 task를 재실행하지 않는다 |
|
||||||
|
| Agent backend | YAML task 실행, backend 변수 치환, workspace 실행 context 제공 | S2_00은 사용자 입력 문장에서 사건 root를 추론하지 않는다 |
|
||||||
|
| Code Executor | inline Python 실행, 지정 requirements·network·timeout 적용 | YAML 자체에 여러 Python 작업을 병렬 실행하는 task fan-out은 없다 |
|
||||||
|
| Localdocs | workspace 파일 읽기·쓰기와 디렉터리 저장 처리 | Python 임시 디렉터리와 원격 출력의 동시성 제어는 다른 문제다 |
|
||||||
|
| S2_00 Python | 원본 해석·검증, 구조화, 자체 출력 계약, status-last 발행 | 청구권 확정·증거의 법적 평가·소장 작성·다음 Agent dispatch는 수행하지 않는다 |
|
||||||
|
| 후속 소비자 | 저장된 상태·hash·context·review를 읽고 후속 작업에 사용 | 어떤 후속 Agent가 어떻게 소비하는지는 이 YAML에 연결되어 있지 않다 |
|
||||||
|
|
||||||
|
근거: YAML의 `Stages`·`task_procedure`, `run_inline_mcp`, `execute_ingress`, `compile_case_context`.
|
||||||
|
|
||||||
|
## 실제 실행단위와 prompt 구성
|
||||||
|
|
||||||
|
### 실행 설정
|
||||||
|
|
||||||
|
| 항목 | 현행 값·동작 |
|
||||||
|
|---|---|
|
||||||
|
| Agent / Stage / task | `Stage_2_S2_00_v6` / `S2_00` / `Task_S2_00_deterministic_ingress` |
|
||||||
|
| 실행 클래스 | `NON-LLM-DETERMINISTIC` |
|
||||||
|
| 외부 실행 도구 | `mcp: code-executor`, `tool_name: run_code` |
|
||||||
|
| 언어·requirements | `python`, `httpx==0.28.1` |
|
||||||
|
| 실행 network·timeout | `agent-network`, 300초 |
|
||||||
|
| Code Executor endpoint | `https://code-executor.mcp.eroomai.com/mcp` |
|
||||||
|
| Localdocs endpoint | `http://mcp-localdocs:8012/mcp` |
|
||||||
|
| 실행 entrypoint | `raise SystemExit(run_inline_mcp())` |
|
||||||
|
| 구현 크기 | 분석 시점 YAML 3,064행, inline Python 2,992행 |
|
||||||
|
|
||||||
|
### prompt의 실제 형태
|
||||||
|
|
||||||
|
이 YAML에는 LLM에게 사건 내용을 전달하는 `system_prompt`·`user_prompt`·모델 선택 설정이 없다. Agent·Stage·task의 `description`은 작업 설명이고, 작업의 실제 명세는 `parameters.code`의 Python 및 그 안의 `SOURCE_POLICY`다. 따라서 자연어 사용자 입력을 바꾼다고 Python의 기본 root나 출력 계약이 바뀌지 않는다. root 변경은 inline 상수 또는 `run_inline_mcp(run_root, deployment_root)`의 인자를 통해 이루어진다.
|
||||||
|
|
||||||
|
`SOURCE_POLICY`는 16개 고정 원본과 신호 payload family, 배포 의존 hash pin, adapter 결정, review 상태 매핑, 정책 분류·크기 제한을 inline JSON으로 보유한다. 외부 Stage 2 release 파일을 읽어 이 정책을 대체하는 흐름은 없다. YAML의 metadata는 설명·계약 표시이고, 실제 파일 선택·판정·저장 동작은 코드에 구현되어 있다.
|
||||||
|
|
||||||
|
Localdocs 세션은 backend가 치환한 `{{__user_hash__}}`·`{{__workspace_hash__}}`를 `initialize.clientInfo`에 전달해 구성한다. 코드가 새 token이나 workspace 식별값을 발급하는 것은 아니다. 값이 미치환 상태이거나 hash 형식이 맞지 않으면 초기화 전에 실패한다. MCP protocol은 `2025-03-26`을 요구하며 session ID를 유지한 뒤 `notifications/initialized`를 보낸다.
|
||||||
|
|
||||||
|
파일 접근은 `read_binary_doc`와 `write_binary_file`로 수행한다. 읽기 응답은 binary envelope의 Base64를 원본 bytes로 복원한다. JSON/SSE 응답과 일반 MCP 오류 외에, Localdocs가 정상 content block으로 돌려주는 `Error: Document not found: ...`도 구별한다. 누락 파일과 도구 실패를 JSON 파싱 오류로 혼동하지 않는 것이 v6의 실제 실행을 가능하게 한 수정 중 하나다.
|
||||||
|
|
||||||
|
직접 LLM 호출이 없으므로 S2_00 자체의 사건 추론 토큰은 발생시키지 않는다. 다만 backend가 큰 inline 코드를 어떻게 전달·기록·과금하는지, requirements 준비 비용 및 전체 파이프라인 토큰 절감 효과는 이 YAML 분석만으로 확정할 수 없다.
|
||||||
|
|
||||||
|
근거: task `parameters`, `SOURCE_POLICY`, `_InlineLocaldocs`, `_inline_tool_text`, `_context_hash`, `run_inline_mcp`.
|
||||||
|
|
||||||
|
## In & Out 설명
|
||||||
|
|
||||||
|
### In: 직접 전달되는 두 root와 실행 context
|
||||||
|
|
||||||
|
| 입력 | 기본값 | 의미 |
|
||||||
|
|---|---|---|
|
||||||
|
| `STAGE1_RUN_ROOT` | `.` | 인증된 workspace Root에 있는 Stage 1 사건 결과의 기준 경로 |
|
||||||
|
| `STAGE1_DEPLOYMENT_ROOT` | `Default_Agent` | Stage 1 배포 registry·schema·domain config의 기준 경로 |
|
||||||
|
| backend user/workspace hash | backend 치환값 | Localdocs에서 접근할 user·workspace context |
|
||||||
|
| `EXECUTION_MODE` | `WORKSPACE_EXECUTION_TEST` | 현행 YAML에서 허용하는 유일한 실행 모드 |
|
||||||
|
|
||||||
|
root는 검증된 상대 경로로 취급하며 절대 경로·상위 경로 이동·미치환 템플릿을 허용하지 않는다. 사건 root `.`는 workspace 전체를 기준으로 삼기 위한 예외다. 실제 읽기는 고정 입력 경로와 signal manifest가 지정한 경로에 한정된다. 배포 root와 출력 경로의 겹침은 거절한다.
|
||||||
|
|
||||||
|
### In: 고정 Stage 1 원본 16개
|
||||||
|
|
||||||
|
아래 경로는 모두 사건 root에 상대적이다. 논리 입력 이름은 코드가 원본을 찾는 내부 key이며 새로운 요청 ID가 아니다.
|
||||||
|
|
||||||
|
| 논리 입력 | 파일 경로 | 주요 용도·판독 구조 |
|
||||||
|
|---|---|---|
|
||||||
|
| `evidence_indexed` | `evidence_indexed.json` | `items[]`의 증거; 기존 ID는 `evidence_index` |
|
||||||
|
| `evidence_event_candidates` | `evidence_event_candidates.json` | `items[]/event_candidates[]`의 이벤트; 기존 ID는 `candidate_id` |
|
||||||
|
| `client_goal` | `client_goal.json` | 목표·제약·당사자 등 사건 운영 context |
|
||||||
|
| `domain_screening` | `routing/domain_screening.json` | domain screening 결과 |
|
||||||
|
| `domain_activation_manifest` | `routing/domain_activation_manifest.json` | 활성·지원·감시 domain 등 routing 근거 |
|
||||||
|
| `b1_evidence_indexed_gate` | `quality_gates/B1_evidence_indexed_gate.json` | 증거 gate·review·진행 허용 여부 |
|
||||||
|
| `b2_event_candidates_gate` | `quality_gates/B2_event_candidates_gate.json` | 이벤트 gate 및 최종 item·candidate 개수 |
|
||||||
|
| `stage1_part1_soft_gate_handoff` | `quality_gates/stage1_part1_soft_gate_handoff.json` | flat review handoff와 7개 digest guard |
|
||||||
|
| `bo` | `BO.json` | 최상위 배열; `BO_ID`와 source provenance |
|
||||||
|
| `signal_manifest` | `signals/signal_manifest.json` | 신호 파일 목록·hash·kind·count 및 `stage2: ["ALL"]` |
|
||||||
|
| `stage1_part2_review_handoff` | `quality_gates/stage1_part2_review_handoff.json` | flat review handoff |
|
||||||
|
| `legal_effect_structures` | `legal_effect_structures.json` | `structure_records[]`, BO 참조 및 역색인 |
|
||||||
|
| `stage1_part3_review_handoff` | `quality_gates/stage1_part3_review_handoff.json` | 동명 wrapper 안의 review·`counts.review_items`·finalizer |
|
||||||
|
| `fact_ledger_base` | `Fact_Ledger_base.json` | 최상위 배열; `fact_id`·`source_bo_id`·domain effects·calculation requests |
|
||||||
|
| `fact_ledger_writer_report` | `stage1_tmp/fact_ledger/fact_ledger_writer_report.json` | fact 개수·domain coverage·calculation readiness·최종 raw hash |
|
||||||
|
| `stage1_part4_review_handoff` | `quality_gates/stage1_part4_review_handoff.json` | 동명 wrapper 안의 review·`counts.review_items`·finalizer |
|
||||||
|
|
||||||
|
목록의 모든 고정 입력을 요구한다. 누락 입력별 criticality·impact scope를 기록하지만, 현행 `execute_ingress`는 고정 입력 집합이 완전하지 않으면 전역 `BLOCKED`로 처리한다. 일부 cluster만 남겨 정상 context를 발행하는 경로는 없다.
|
||||||
|
|
||||||
|
### In: signal manifest로 확장하는 입력
|
||||||
|
|
||||||
|
`downstream_read_sets.stage2`는 정확히 `["ALL"]`이어야 한다. `files[i].path`는 `signals/` 접두사가 없는 상대 경로이며, 실제 원본은 `<사건 root>/signals/<files[i].path>`에서 읽는다. 파일 순서와 occurrence를 보존하며 중복 파일 행, 미승인 kind, hash·개수 불일치는 검출한다.
|
||||||
|
|
||||||
|
| 신호 kind·형태 | 처리 방식 |
|
||||||
|
|---|---|
|
||||||
|
| `canonical` | 의미 자료로 읽는다. SG01은 `domain_activation_manifest.domain_entries[]`, 일반 정본은 `records[]`를 occurrence로 취급한다 |
|
||||||
|
| `domain_signal` | `domain_signal_envelope` 안의 `element_fact_candidates[]`·`opposing_fact_candidates[]`·`defense_candidates[]`를 순서대로 펼친다 |
|
||||||
|
| `compatibility_view` | 파일·hash·개수·registry 관계를 검증하지만 의미 occurrence에 다시 합치지 않는다 |
|
||||||
|
|
||||||
|
성공한 실행에서는 신호 파일 30개를 읽었다. 정본 13개, domain signal 14개, compatibility view 3개이며, 의미 occurrence는 475개다. `USED`·`UNUSED`·`UNMAPPED` 모두 보존한다. SG01의 routing 원본과 signal 원본은 별도로 읽고, 승인된 17개 필드의 의미 projection을 비교한다.
|
||||||
|
|
||||||
|
### Out: 결과 파일과 저장 위치
|
||||||
|
|
||||||
|
기본 출력 root는 다음과 같다.
|
||||||
|
|
||||||
|
```text
|
||||||
|
workspace Root/
|
||||||
|
└── stage2_runs/from-stage1/s2_00/v6/
|
||||||
|
├── ingress/
|
||||||
|
│ ├── stage1_input_manifest.json
|
||||||
|
│ ├── intake_report.json
|
||||||
|
│ └── ingress_status.json
|
||||||
|
├── review/
|
||||||
|
│ └── issue_ledger.base.json
|
||||||
|
└── context/
|
||||||
|
└── case_context.json
|
||||||
|
```
|
||||||
|
|
||||||
|
사건 root가 `.` 이외의 상대 폴더이면 `stage2_runs/from-stage1/<stage1_run_root_ref>/s2_00/v6/`에 저장한다. `v6`는 고정 구현 개정 폴더이며 실행마다 새로 발급하는 ID가 아니다.
|
||||||
|
|
||||||
|
| 결과물 | 포함 내용 |
|
||||||
|
|---|---|
|
||||||
|
| `ingress/stage1_input_manifest.json` | 고정 입력·동적 신호의 논리 이름, 원본 경로·raw hash·byte length·검증 상태와 읽은 배포 자산 목록 |
|
||||||
|
| `ingress/intake_report.json` | 보존·join·digest 검증 결과, 발견한 issues, 입력별 계약 판정 |
|
||||||
|
| `review/issue_ledger.base.json` | review의 원문 content·정확한 출처·원본 status/severity·정규화 partition·blocking, 보존 상태, 기술 issues |
|
||||||
|
| `context/case_context.json` | 원본 member와 관계, 후보 의존·미해결 참조, cluster·bundle·wave, 목표·routing·신호·review 참조, 객체·당사자·slot 관측 참조, 활성 profile |
|
||||||
|
| `ingress/ingress_status.json` | 처리 status·두 root·algorithm/schema·실행 모드·정책, 읽은 원본·배포 파일의 hash, 나머지 4개 결과의 hash·byte length와 완료 표지 |
|
||||||
|
|
||||||
|
`BLOCKED`이면 `context/case_context.json`을 발행하지 않고 `ingress/technical_diagnostic.json`으로 대체한다. 이 경우에도 파일 수는 5개다. root·인증·hydration·발행 단계의 예외로 `FAILED`가 되면 이 5개 파일을 반드시 만든다는 보장은 없다.
|
||||||
|
|
||||||
|
### 상태·stdout·재실행
|
||||||
|
|
||||||
|
| 상태·발행 값 | 실제 의미 |
|
||||||
|
|---|---|
|
||||||
|
| `READY` | 차단 사유 없이 context를 만들 수 있고 코드가 집계하는 issues·미해결 review·원본 seal 미확인이 없는 상태 |
|
||||||
|
| `READY_WITH_ISSUES` | context와 결과를 발행할 수 있으나 WARNING·미해결 review·확인 불가 seal 등이 남음 |
|
||||||
|
| `BLOCKED` | 필수 입력·검증·상위 gate/review·context 구성에서 차단; 정상 context 없이 진단 결과를 발행 |
|
||||||
|
| `FAILED` | 실행 wrapper가 잡은 runtime·transport·root·인증·발행 등 예외; stdout error로 종료 |
|
||||||
|
| `PUBLISHED_STATUS_LAST` | 비 status 파일 4개를 write/read-back한 후 status를 마지막에 기록 |
|
||||||
|
| `REUSED_COMPLETED_OUTPUT` | 기존 status와 결과 전체가 새 계산 결과와 byte 단위로 동일함을 확인해 재사용 |
|
||||||
|
|
||||||
|
`READY`·`READY_WITH_ISSUES` receipt는 `ok=true`, exit code 0이다. `BLOCKED`는 진단 파일이 저장되어도 `ok=false`, exit code 2이며 `FAILED`도 exit code 2다. receipt에는 status·output root·publication·status hash 등이 들어가고, 별도 request ID는 없다. JSON 결과는 canonical 직렬화 후 newline을 붙여 동일성 비교에 사용한다.
|
||||||
|
|
||||||
|
완료 status가 있으면 status bytes와 결과 파일 전체를 비교한다. 다른 입력·버전·내용은 `EXISTING_OUTPUT_CONFLICT`, 결과 손상은 `EXISTING_OUTPUT_CORRUPT`로 거절한다. status 없이 일부 결과가 있으면 `PARTIAL_OUTPUT_CONFLICT`로 거절한다. 동일 사건 root에서 입력을 변경한 재실행은 자동 overwrite나 새 실행 폴더 생성으로 처리되지 않는다.
|
||||||
|
|
||||||
|
근거: `DEFAULT_SOURCE_CONTRACTS`, `validate_direct_roots`, `expand_stage2_signal_all`, `execute_ingress`, `validate_output_files`, `publish_result`, `run_inline_mcp`.
|
||||||
|
|
||||||
|
## 작업용 고정 자산
|
||||||
|
|
||||||
|
### YAML 내부에 고정된 자산
|
||||||
|
|
||||||
|
| 자산 | 역할·경계 |
|
||||||
|
|---|---|
|
||||||
|
| inline Python | 파일 접근·검증·정규화·graph·발행의 실제 구현. 별도 Python 모듈을 배포 폴더에서 import하지 않는다 |
|
||||||
|
| `SOURCE_POLICY.stage1_sources` | 16개 고정 입력과 `signal_payload_family`의 정확한 집합·경로·adapter·schema·producer 기대값 |
|
||||||
|
| `SOURCE_POLICY.dependency_locks.stage1` | Stage 1 자산 55개에 대한 경로·SHA-256 pin의 허용 목록 |
|
||||||
|
| `adapter_decisions` | 원본 envelope, signal ALL, dual SG01, P1~P4 handoff, BO·fact, domain config, review mapping, 제한된 signal writer alias의 계약 |
|
||||||
|
| `ROW_KEYS`·review key·관계 kind 상수 | 허용 배열 위치와 review 탐색 범위, 명시적 관계·후보 의존의 판독 범위 |
|
||||||
|
| 자체 출력 validator | 상태별 5개 파일, 상위 필드 집합·버전·root·artifact hash 등의 계약 |
|
||||||
|
|
||||||
|
현재 `SOURCE_POLICY`는 `DEV_FIXTURE_RELEASE`이며, 배포 lock의 snapshot date는 `2026-08-29`이다. 55개는 전체 Stage 1 runtime release를 증명하는 목록이 아니라 이 구현이 참조할 수 있도록 고정한 범위다. lock에는 `full_stage1_runtime_release_status: STAGE1_NOT_RELEASE_READY`도 남아 있다. 이 표시는 현행 workspace 실행 성공과 서로 다른 수준의 정보다.
|
||||||
|
|
||||||
|
### Stage 1 배포 root에서 읽는 자산
|
||||||
|
|
||||||
|
| 자산군 | lock 범위 | 실제 선택 방식 |
|
||||||
|
|---|---|---|
|
||||||
|
| registry·manifest | `runtime_manifest.json`, `domains/_registry_index.json`, `signals/signal_registry.v2.json`의 3개 | registry 2개는 기본으로 읽음. `runtime_manifest.json`은 pin 목록에 있으나 기본 hydration의 필수 읽기 대상은 아님 |
|
||||||
|
| domain config | `domains/<domain_id>/domain_config.json` 26개 | activation의 `active_domain_ids`에 들어 있는 config만 읽음 |
|
||||||
|
| platform·signal schema | schema 및 공통 schema 26개 | 4개 고정 입력 schema ref, signal registry가 선언한 schema·domain envelope, 그들의 외부 `$ref`를 따라 선택 |
|
||||||
|
|
||||||
|
26개 config의 domain은 `E-00~E-21`, `EC-00`, `X1~X3`다. 기본으로 schema ref가 있는 고정 입력은 routing activation, signal manifest, LES, fact ledger의 4개다. 나머지 고정 입력은 adapter의 필수 shape·key 확인과 개별 보존 검증을 사용한다.
|
||||||
|
|
||||||
|
선택된 모든 배포 자산은 inline pin과 raw hash가 같아야 한다. 필요한 자산이 허용 목록에 없거나 hash가 다르면 hydration에서 실패한다. schema의 외부 참조는 이 로컬 허용 목록 안에서 하나의 자산으로 결속하며 외부 URL에서 schema를 내려받지 않는다.
|
||||||
|
|
||||||
|
성공한 실행의 배포 자산은 35개였다. registry 2개, 활성 domain config 14개, schema 19개(공통 schema 2개 포함)이다. 사건 원본 16개와 신호 파일 30개를 합해 읽은 고유 파일은 81개다. 선언된 55개 전체를 매번 읽는 구조가 아니다.
|
||||||
|
|
||||||
|
Stage 2의 별도 prompt library·renderer·dispatch schema·S2_10~40 자산은 읽지 않는다. domain config는 `active_profiles`로 보존되지만, config의 element·defense·calculation slot을 법적 판단으로 채워 넣는 실행기는 이 YAML에 없다.
|
||||||
|
|
||||||
|
### 크기·보존 경계
|
||||||
|
|
||||||
|
원본 한 파일은 32 MiB, hydration의 고유 입력 합계는 256 MiB까지 허용한다. strict JSON 판독은 중복 key·비표준 상수·잘못된 UTF-8 등을 거절하고, 중첩 깊이 96·item 한도 1,000,000을 사용한다. 로컬 snapshot은 경로·symlink·파일 상태 변경을 검사하고 raw SHA-256을 보유한다.
|
||||||
|
|
||||||
|
코드 실행별 `TemporaryDirectory`는 hydration을 격리하고 종료 시 정리된다. 임시 폴더의 고유 이름은 출력 root나 요청 ID로 노출하지 않는다. 이 임시 작업 격리는 원격 저장소의 동일 root 동시 발행 문제를 해결하는 장치는 아니다.
|
||||||
|
|
||||||
|
근거: `SOURCE_POLICY`, `hydrate_stage1`, `_schema_dependencies`, `load_json_strict`, `open_bounded_snapshot`.
|
||||||
|
|
||||||
|
## Upstream/Downstream 설명
|
||||||
|
|
||||||
|
### Upstream: Stage 1 결과·배포와의 결속
|
||||||
|
|
||||||
|
| Stage 1 영역 | 주로 넘겨받는 자료 | S2_00이 확인하는 연결 |
|
||||||
|
|---|---|---|
|
||||||
|
| Part 1 | 목표·screening·activation·증거·이벤트·B1/B2 gate·P1 handoff | P1의 7개 raw digest, 증거·이벤트 참조, B2 최종 item·candidate 개수, 진행 허용 여부 |
|
||||||
|
| Part 2 | BO·signal manifest 및 payload·P2 handoff | BO↔fact source BO multiset, BO provenance의 event 참조, 신호 파일·occurrence 보존, registry 및 SG01 대응 |
|
||||||
|
| Part 3 | LES·P3 handoff | LES의 BO join·중복 ID·역색인·route count, P3 review count·finalizer, 기존 signal transaction 참조 |
|
||||||
|
| Part 4 | fact ledger·writer report·P4 handoff | fact ID 순서, v8 확장 필드, writer report의 개수·coverage·readiness·최종 hash, P4 review count·finalizer |
|
||||||
|
|
||||||
|
이 연결은 같은 workspace에서 파일을 읽는 자료 의존이다. YAML의 `prevs`가 Stage 1을 기다리거나 Stage 1의 실행 완료를 자동 입증하지 않는다. producer 기대값은 정책에 기록되어 있으며 원본의 제공값과 비교한다. P3·P4는 `created_by`가 최초 작성자이고 `finalized_by`가 최종 writer이므로 wrapper의 producer 판독에서 후자를 우선한다. signal writer alias는 승인된 한 쌍에만 적용한다.
|
||||||
|
|
||||||
|
원본에 producer가 없으면 원본에 값을 보충하지 않고 `PRODUCER_ID_UNEVALUABLE` WARNING으로 남긴다. 고정 입력의 `raw_hash_source`는 현행 정책에서 모두 `UNAVAILABLE_DEV`이므로 별도 완료 seal로 전부 입증했다고 해석할 수 없다. 대신 실제 제공된 P1 digest·신호 manifest hash·fact writer hash를 각 기능에서 검증하고, 읽은 bytes 자체도 출력 manifest와 status에 남긴다.
|
||||||
|
|
||||||
|
### Downstream: 저장된 파일을 통한 자체 handoff
|
||||||
|
|
||||||
|
후속 소비자는 `ingress_status.json`의 상태와 artifact 목록·hash를 확인한 뒤, `context/case_context.json`과 `review/issue_ledger.base.json`을 읽을 수 있다. 이 문장의 소비 순서는 status-last 계약에 따른 권장 해석이며, 실제 후속 Agent가 이 순서로 읽는다는 것은 이 YAML에서 검증하지 않았다.
|
||||||
|
|
||||||
|
context의 member는 BO·FACT·LES·EVIDENCE·EVENT다. 각 member는 기존 Stage 1 ID, 원문 projection, 필드별 provenance를 갖는다. 출처는 `logical_artifact_id`·정확한 `json_pointer`·원본 값의 canonical digest로 연결되며, 파일 경로·raw file hash는 입력 manifest에서 찾는다. projection은 schema·producer·metadata 등 일부 상위 key를 제외하고 나머지 값을 담으며, 제외된 값은 원본 pointer로 접근한다. 원본 전체를 output 폴더에 복제하는 방식은 아니다. `active_profiles.sha256`도 파싱된 config의 canonical digest이며, 배포 원본 bytes의 hash는 manifest의 `deployment_sources`와 구분해 읽어야 한다.
|
||||||
|
|
||||||
|
cluster는 원본 참조 graph의 연결 성분이다. fact→BO, fact→evidence/event, LES→BO, BO→event, event→evidence 및 명시적인 fact 관계를 사용한다. 단순히 같은 domain이나 같은 당사자라는 이유만으로 새로운 edge를 만들지는 않는다. 다만 같은 증거에 연결된 여러 fact는 한 cluster로 합쳐질 수 있으므로 cluster를 독립 청구권이나 청구별 최종 그룹으로 해석해서는 안 된다.
|
||||||
|
|
||||||
|
각 cluster의 `bundle`은 `member_refs`·`signal_indexes`·`review_refs`를 제공한다. 후보 관계 중 허용된 kind만 cluster 의존 edge로 사용하며, SCC를 묶어 scheduling wave를 계산한다. 이는 실행 순서를 설명하는 자료이지 backend task를 실제로 생성·병렬 dispatch하는 기능이 아니다.
|
||||||
|
|
||||||
|
기존 원본 ID·transaction ID는 재사용한다. `CL-001` 형태의 cluster 참조와 signal occurrence용 파생 참조는 결과 내부 자료를 연결하는 값이다. 새 request·attempt·run ID를 생성하는 것과 구별된다. 소비자가 기존 S2_10~40 schema를 반드시 요구하는지, 변환이 필요한지 또는 새 소비 계약을 사용할지는 이번 분석의 확인 범위 밖이다.
|
||||||
|
|
||||||
|
근거: `validate_ingress_contracts`, `check_conservation`, `bind_signal_occurrences`, `compile_case_context`, `publish_result`.
|
||||||
|
|
||||||
|
## 구현과 계약 사이 확인사항 및 잔여 한계
|
||||||
|
|
||||||
|
### 확인된 실행 근거와 그 범위
|
||||||
|
|
||||||
|
| 확인 항목 | 확인 내용·범위 |
|
||||||
|
|---|---|
|
||||||
|
| 현행 구현 식별 | YAML hash `63b5158e…b5b4a3`, Agent v6, algorithm 6.0.0을 다시 확인 |
|
||||||
|
| 실제 workspace 실행 | 앞선 실행에서 `10월_1일_구성`의 단일 task COMPLETED, exit 0, READY_WITH_ISSUES, PUBLISHED_STATUS_LAST |
|
||||||
|
| 실행 시간 | 앞선 UI 전체 시간 5.1초, Code Executor 반환 시간 약 2.402초. 일반 성능 보장은 아님 |
|
||||||
|
| 결과 파일 | 당시 내려받은 5개 결과를 재열람하고 status와 나머지 4개 artifact hash·byte length를 이번 분석에서 재검증 |
|
||||||
|
| 최종 status hash | `7ea037548ea729275ce4f791d7ae2ada2d381427f1971ea6f30ccc3e4041eed3` |
|
||||||
|
| 검증 집계 | PASS 19, FAIL 0, UNEVALUABLE 2. 후자는 LES 선언 총수·event disposition으로, 원본 미제공 값을 추정하지 않음 |
|
||||||
|
| context 보존 | member 262개 = BO 57 + FACT 57 + LES 47 + EVIDENCE 30 + EVENT 71; 관계 385개, 미해결 참조 0 |
|
||||||
|
| 신호·review | 신호 475개 = USED 240 + UNUSED 27 + UNMAPPED 208; review 1,128개 = UNRESOLVED 1,126 + CONDITIONAL 2 |
|
||||||
|
| scheduling | cluster 46개, 후보 의존 관계 0개, wave 1개. scheduling 기능 전체의 다양한 graph 사례를 이 한 실행이 입증하지 않음 |
|
||||||
|
| 앞선 회귀 검증 | 수정 세션 기록상 기존·실제 MCP 응답 시험 51건과 캡처 원본·변조 거부 시험 7건 통과. 이번 문서 작성에서 이 시험을 새로 실행한 것은 아님 |
|
||||||
|
|
||||||
|
### 계약 문구와 실제 실행을 구별해야 하는 사항
|
||||||
|
|
||||||
|
| 확인사항 | 현행 구현·관측 근거 | 의미와 잔여 범위 |
|
||||||
|
|---|---|---|
|
||||||
|
| 전역 완료 seal | `execute_ingress`는 별도 `completion_seal`·`contract_manifest`를 주입하지 않으며 고정 입력 16개 seal 상태는 모두 UNEVALUABLE | P1·신호·writer의 개별 hash 검증 및 원본 재독과 전체 run의 승인·완료 seal은 다르다 |
|
||||||
|
| 신호별 JSON Schema 실행 조건 | registry schema는 읽지만, payload 검사는 manifest 행의 `schema`·`schema_path`가 문자열일 때 실행 | 성공한 manifest의 30개 행에는 이 두 필드가 없어 그 루프의 payload schema 검사는 수행되지 않았다. hash·count·registry·SG01 검증 성공을 모든 신호의 schema 검증 성공으로 확대하면 안 된다 |
|
||||||
|
| schema·adapter 검사의 강도 | 고정 입력 4개는 schema ref를 사용하고 나머지는 shape·key와 별도 검증 사용; inline validator는 구현된 JSON Schema keyword 범위를 처리 | 모든 원본에 동일한 full schema 검사가 적용되지 않는다. 범용 JSON Schema 표준 전체 준수나 모든 의미 규칙 검증을 입증한 것은 아니다 |
|
||||||
|
| review 보존과 해결 | 같은 내용도 원본 경로·pointer가 다르면 별도 occurrence로 보존; 현재 1,126건 UNRESOLVED | 1,128건은 고유 법률 쟁점 수가 아니다. handoff의 FINALIZED를 review 해결로 바꾸지 않으며 사건 판단·review 해소는 남아 있다 |
|
||||||
|
| review unknown·blocking 표현 | 미등록 status/severity는 UNMAPPED로 보존하나 해당 매핑만으로 개별 issue를 추가하지는 않음. explicit blocking은 blocked 배열·boolean·BLOCKED status 및 BLOCKING/CRITICAL/FATAL severity 등 특정 표현으로 판정 | 정책의 unknown-value issue 문구와 실제 issue 생성은 구분해야 한다. `BLOCK` severity나 `blocks_final_drafting`만으로 S2_00 전역 차단을 자동 판정한다고 볼 수 없다 |
|
||||||
|
| 신호 UNMAPPED | `signal_id`가 없는 occurrence는 참조 binding이 있어도 UNMAPPED로 분류; 실제 208개 보존 | 파일·개수 보존 성공과 모든 신호의 의미 식별·활용 완료는 다르다. 새 signal ID를 임의 발급해 해결하지 않는다 |
|
||||||
|
| graph·slot 범위 | 명시적 참조 graph와 관측된 slot만 보존. profile은 담지만 slot 판정 엔진은 없음 | 공유 증거 연결을 청구권 동일성으로 볼 수 없고, 추론 관계·요건 충족·반박 slot·법적 선후 의존을 새로 확정하지 않는다 |
|
||||||
|
| 자산 lock의 범위 | 허용 55개 중 필요한 35개를 읽은 실행; config는 active domain에 한정 | 전체 Stage 1 배포 봉인, 비활성 profile 검증, supporting/monitor config 전체 로딩의 성공을 의미하지 않는다 |
|
||||||
|
| 출력 schema | 닫힌 상위 필드·버전·root·상태별 파일 집합·hash를 검사하고 provenance를 재검증 | 모든 중첩 출력 값에 독립적인 외부 JSON Schema 검증을 적용하는 구조는 아니다 |
|
||||||
|
| 결과 버전 표시 | Agent·algorithm은 v6/6.0.0, 결과의 `schema_version`은 `stage2_s2_00_direct.v4` | algorithm 개정과 결과 구조 버전은 분리되어 있다. schema_version만 보고 실행 구현을 v4로 오인하면 안 된다 |
|
||||||
|
| 전략과 현행 코드 차이 | 전략 v3의 `prev` 연결 및 개정 폴더 없는 출력 예시와 달리 현행은 직접 Localdocs 읽기·고정 `v6` 폴더 사용 | 실제 실행·경로의 판단 기준은 현행 YAML이다. `v6`는 이전 진단 보존을 위한 고정 개정 경로이며 새 실행 ID가 아니다 |
|
||||||
|
|
||||||
|
### 실행·발행의 잔여 한계
|
||||||
|
|
||||||
|
1. **workspace 테스트 모드만 허용한다.** 실행 성공 이후에도 정책은 DEV이며 생산 모드가 허용된 것이 아니다. Agent metadata의 `IMPLEMENTED_OFFLINE_VERIFIED`는 inline 표시이고, 실제 live 성공은 별도 실행 자료가 입증한다.
|
||||||
|
2. **status-last는 논리적 완료 경계다.** 각 파일을 write/read-back한 뒤 status를 쓰지만, 여러 파일을 한 번에 원격 atomic transaction으로 commit하지 않는다. status 기록 뒤 read-back이 실패하면 원격 status가 존재하면서 wrapper는 FAILED를 반환할 수도 있다.
|
||||||
|
3. **같은 출력 root의 동시 writer는 미검증이다.** 코드에 원격 lock·CAS·transaction이 없고, 존재 확인과 쓰기 사이의 경합을 임시 디렉터리만으로 막지 않는다.
|
||||||
|
4. **재실행 복구는 자동화하지 않는다.** 동일 완료 결과는 재사용하지만 변경 입력·충돌·부분 결과는 거절한다. 정리·복구·별도 출력 위치 결정은 현행 코드의 자동 처리 범위가 아니다.
|
||||||
|
5. **원본 재독은 시간 구간 전체의 snapshot을 보장하지 않는다.** 최초 bytes와 발행 직전 재독 bytes를 비교하지만, 재독 이후의 변경이나 서로 다른 파일 사이의 동일 시점 일관성을 원격 transaction으로 보장하지 않는다.
|
||||||
|
6. **오류 위치에 따라 진단 파일이 없을 수 있다.** `execute_ingress` 내부에서 처리된 차단은 5개 진단 결과를 만들지만 hydration·인증·일부 입력 계약 함수·출력 검증·발행에서 바깥 wrapper로 전파된 예외는 FAILED stdout으로 끝날 수 있다.
|
||||||
|
7. **Downstream 통합은 별도 확인 대상이다.** `nexts: []`이며 task dispatch나 소비자 schema binding이 없다. 결과가 저장됐다는 사실만으로 다음 Stage 2 Agent의 실행 가능성을 확정할 수 없다.
|
||||||
|
8. **시간·토큰 경제성은 한 실행의 수치로 일반화하지 않는다.** 직접 파일 재사용과 단일 비 LLM task는 불필요한 전달층을 줄인다. 그러나 이 YAML은 큰 inline 정책·코드, 원본 2회 읽기, 검증·provenance·review projection을 유지하므로 비용·latency 전체를 별도 측정해야 한다.
|
||||||
|
|
||||||
|
분석 결론은 현행 v6이 Stage 1 직접 인계 원칙을 구현하여, 관측한 workspace에서 원본 검증·구조화·자체 결과 발행을 성공적으로 수행했다는 것이다. 동시에 저장된 미해결 항목, 조건부로 수행되는 계약 검사, 원격 발행·후속 소비의 경계는 그대로 남아 있다.
|
||||||
|
|
||||||
|
주요 코드 위치: `validate_ingress_contracts`(YAML 1019행), `expand_stage2_signal_all`(1347행), `check_conservation`(1701행), `hydrate_stage1`(2574행), `normalize_review_items`(2679행), `compile_case_context`(2738행), `execute_ingress`(2897행), `validate_output_files`(2978행), `publish_result`(3007행), `run_inline_mcp`(3025행). 행 번호는 위 SHA-256으로 식별한 분석 시점 YAML을 기준으로 한다.
|
||||||
+730
-3914
File diff suppressed because one or more lines are too long
+6502
File diff suppressed because it is too large
Load Diff
+3057
File diff suppressed because one or more lines are too long
+3023
File diff suppressed because one or more lines are too long
+3034
File diff suppressed because one or more lines are too long
+6248
File diff suppressed because it is too large
Load Diff
+6248
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -71,7 +71,7 @@ Agent:
|
|||||||
|
|
||||||
WORKFLOW_ID = "S2_20"
|
WORKFLOW_ID = "S2_20"
|
||||||
ALGORITHM_VERSION = "s2_20_relief_plan/1.0.0"
|
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"
|
LOCALDOCS_URL = "http://mcp-localdocs:8012/mcp"
|
||||||
WEAVIATE_MCP_URL = "https://weaviate.eroomai.com/mcp"
|
WEAVIATE_MCP_URL = "https://weaviate.eroomai.com/mcp"
|
||||||
MCP_PROTOCOL_VERSION = "2025-03-26"
|
MCP_PROTOCOL_VERSION = "2025-03-26"
|
||||||
|
|||||||
+1
-1
@@ -73,7 +73,7 @@ Agent:
|
|||||||
from typing import Any, Mapping
|
from typing import Any, Mapping
|
||||||
|
|
||||||
|
|
||||||
EXPECTED_STAGE2_RELEASE_SHA256 = "9fa85bb94c5f14f675dcdaa0cf94d06745b21675d97797539ffc14015dcc9f53"
|
EXPECTED_STAGE2_RELEASE_SHA256 = "2363f1166ee8a3ea2b38350cde61fccfc9852a413c6b9f14a6ed6606af15a590"
|
||||||
LOCALDOCS_URL = "http://mcp-localdocs:8012/mcp"
|
LOCALDOCS_URL = "http://mcp-localdocs:8012/mcp"
|
||||||
MCP_PROTOCOL_VERSION = "2025-03-26"
|
MCP_PROTOCOL_VERSION = "2025-03-26"
|
||||||
INLINE_USER_HASH = "{{__user_hash__}}"
|
INLINE_USER_HASH = "{{__user_hash__}}"
|
||||||
|
|||||||
+1
-1
@@ -68,7 +68,7 @@ Agent:
|
|||||||
|
|
||||||
WORKFLOW_ID = "S2_40"
|
WORKFLOW_ID = "S2_40"
|
||||||
ALGORITHM_VERSION = "s2_40_finalizer/1.0.0"
|
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"
|
LOCALDOCS_URL = "http://mcp-localdocs:8012/mcp"
|
||||||
MCP_PROTOCOL_VERSION = "2025-03-26"
|
MCP_PROTOCOL_VERSION = "2025-03-26"
|
||||||
INLINE_USER_HASH = "{{__user_hash__}}"
|
INLINE_USER_HASH = "{{__user_hash__}}"
|
||||||
|
|||||||
+260
@@ -0,0 +1,260 @@
|
|||||||
|
# Stage_2_S2_00.yml 개정 전략 v1
|
||||||
|
|
||||||
|
작성일: 2026-10-02. 상태: **개정 전략 작성 완료 / YAML·실행 자산 개정 및 live 실행 미수행**.
|
||||||
|
|
||||||
|
## 1. 권고안과 개정 범위
|
||||||
|
|
||||||
|
**Stage 1의 사건 run root와 배포 root를 S2_00 호출에 직접 전달하고, 유일한 `Task_S2_00_deterministic_ingress`가 입력 검증·hydration·C00–C15·결과 발행을 한 번에 수행하도록 개정한다.** `Task_S2_00_prepare_request`, 고정 `stage2_control/s2_00_request.json` 생성·저장·재읽기, 호출자의 `request_id`·`attempt_id` 부여를 실행 경로에서 제거한다. Stage 1 결과물의 파일명·상대경로·원본 bytes는 그대로 사용한다.
|
||||||
|
|
||||||
|
사용자 지시 중 “사건·배포 경로 전달 및 `request_id`·`attempt_id` 부여 방식 폐기”는 **이를 별도의 request 준비 절차로 구성하는 방식을 폐기**한다는 의미로 적용한다. 경로 자체는 직접 인계에 필요한 값이다. ID는 호출자의 필수 입력에서 제거하고, 기존 출력 schema와 downstream 호환에 필요한 내부 값만 S2_00이 처리한다. 출력의 ID 필드까지 전면 삭제하는 것은 본 최소 변경안에 포함하지 않는다.
|
||||||
|
|
||||||
|
개정의 성공 기준은 단일 task 실행 자체가 아니라, Stage 1 원본을 읽어 검증한 뒤 기존 정상 또는 진단 산출물과 마지막 status barrier를 정확히 발행하는 것이다. 기존 구현의 의미 인계 공백 때문에 정상 실행이 성립하지 않는 경우에는 해당 Stage 2 adapter·projection만 보완한다.
|
||||||
|
|
||||||
|
본 문서는 전략서다. 아래 함수 signature, 입력 객체, ID 결정 규칙과 변경 순서는 **개정 제안**이며 현재 구현이나 backend 지원 사실을 뜻하지 않는다. 이번 작성 작업의 산출물은 본 문서와 지정된 Stage 2 `MEMORY.md` 요약뿐이다.
|
||||||
|
|
||||||
|
## 2. 기준 자료와 실제 확인 결과
|
||||||
|
|
||||||
|
판단 순서는 현행 배포 YAML → 현행 release·배포/빌드 계약 → 현행 분석서 → 과거 YAML·분석서다. IO 표의 역사적 행번호를 현행 YAML의 행번호로 사용하지 않는다.
|
||||||
|
|
||||||
|
| 자료 | 확인 내용과 전략상 역할 |
|
||||||
|
|---|---|
|
||||||
|
| [현행 Stage_2_S2_00.yml](Stage_2_S2_00.yml) | `Stage_2_S2_00_v2`, version `1.2.0`; prepare와 ingress의 두 task. 현행 분석의 최우선 기준이다. |
|
||||||
|
| [Analysis_Stage_2_S2_00.md](Analysis_Stage_2_S2_00.md) | 현행 두-task 구조, 외부 인자 결속 미검증, C00–C15·입출력·운영 경계를 설명한다. |
|
||||||
|
| [Stage_2_00_IO_info.md](../../../Stage_2_00_IO_info.md) | §3.2의 사건 입력 16개, §3.4의 Stage 1 배포 55개, §3.5의 Stage 2 직접 의존 49개와 출력 목록을 사용한다. 상단 현행 IO 보충과 보존본 기반 본문을 구별한다. |
|
||||||
|
| [Stage_2_S2_00_outdated_9_09.yml](Stage_2_S2_00_outdated_9_09.yml) | 구형은 단일 ingress task지만 외부 request 파일이 필요하다. 단일 task 구조의 비교 기준이며 그대로 복원할 개정 정본은 아니다. |
|
||||||
|
| [Stage_2_10_Analysis_v1.md](../../../Stage_2_10_Analysis_v1.md) | 사용자 context의 설명과 달리 실제 제목·본문·정본은 **S2_10 분석**이다. S2_00 산출물을 소비하는 downstream 계약 확인에 사용한다. |
|
||||||
|
| [Stage_2_00_Analysis_v1.md](../../../Stage_2_00_Analysis_v1.md) | 본문이 구형 S2_00을 분석하고 상단에 현행 보충이 있다. §8의 입력·projection 제한을 교차 확인한다. |
|
||||||
|
| [stage2_release.json](../manifest/stage2_release.json) | `/stage1_sources`, `/dependency_locks/stage1`, `/dependency_locks/stage2_direct`, `/bundle`이 실제 입력·자산 계약이다. |
|
||||||
|
| [s2_00_inline_code_receipt.json](../manifest/s2_00_inline_code_receipt.json)·[builder](../offline_build/build_s2_00_inline_projection.py) | authoring 정본은 `Stage_2_S2_00_v.2.yml`이다. main 폴더의 version 없는 동명 YAML을 개정 정본으로 혼동하지 않는다. |
|
||||||
|
|
||||||
|
작성 시 확인한 현행 배포 YAML SHA-256은 `07bc9e235d211f3d4e6886bca2faeeb1f112d525fa29fe111bd7c50c42ff9849`, 구형 `outdated_9_09` SHA-256은 `94c2744d71d222cfb5cc1b2a0d33a6cda20079439823de431eef378a2fa5c943`이다. 현행 배포본과 authoring `Stage_2_S2_00_v.2.yml`은 byte-identical이다.
|
||||||
|
|
||||||
|
현행 ingress inline code와 `outdated_9_09`의 ingress inline code를 비교하면 차이는 `EXPECTED_STAGE2_RELEASE_SHA256` 상수 하나다. 따라서 request 앞단이 추가되었어도 기존 C00–C15의 의미 인계 제한이 해소되었다고 볼 수 없다. 최신 ingress를 기반으로 직접 입력 경계만 바꾸고, 필요한 의미 보완을 제한적으로 추가하는 것이 효율적이다.
|
||||||
|
|
||||||
|
현재 prepare의 `__main__`은 `PREPARE_ARGUMENT_BINDING_UNVERIFIED`로 종료한다. release는 `DEV_FIXTURE_RELEASE / DRAFT_NOT_EXECUTABLE`, bundle은 `STRUCTURAL_FIXTURE`, `selected_context_refs=[]`다. 직접 인계 개정이 이 상태를 자동으로 실행 적격 또는 승인 완료로 바꾸지는 않는다.
|
||||||
|
|
||||||
|
## 3. 목표 실행 흐름과 책임
|
||||||
|
|
||||||
|
```text
|
||||||
|
호출자: Stage 1 완료 결과의 사건 root + 그 결과에 대응하는 배포 root
|
||||||
|
backend: 인증된 user/workspace 세션 + 직접 입력을 executor에 전달
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
IN → Task_S2_00_deterministic_ingress → OUT
|
||||||
|
단일 code-executor.run_code
|
||||||
|
① 직접 입력 형태·NFC 상대경로·workspace 결속 검증
|
||||||
|
② 고정 Stage 2 release pin 검증
|
||||||
|
③ 16개 사건 파일 + manifest 신호 + 55개 배포 + 49개 직접 자산
|
||||||
|
+ module manifest/schema/조건부 release 입력의 exact closure 확정
|
||||||
|
④ 동일 원격 closure 2회 읽기·bytes/hash 대조·임시 hydration
|
||||||
|
⑤ C00 → C05 → run binding → C10 → C15
|
||||||
|
⑥ 정상 11+E 또는 진단 5개 출력 검증·발행
|
||||||
|
⑦ ingress_status.json 마지막 write/read-back → stdout receipt
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
외부 orchestrator: barrier·정확한 산출물 집합·route 검증 → S2_10 또는 S2_40
|
||||||
|
```
|
||||||
|
|
||||||
|
C00는 입력 계약·producer·identity·seal·signal ALL 검증, C05는 review 정규화·보존검사, C10은 사건·증거·객체·당사자·slot 문맥, C15는 cluster·slice·bundle·route 구성이다. 이들은 **동일 task 내부의 메모리 처리 단계**로 유지한다. 단계마다 중간 JSON 파일을 새로 만들어 저장·재읽기하는 방식은 도입하지 않는다.
|
||||||
|
|
||||||
|
별도 prepare 성공 뒤 ingress를 시작하는 task 간 게이트는 없어지고, 직접 입력 검증 실패 시 같은 Python 실행 안에서 후속 처리를 중단한다. 외부 실행자의 S2_10/S2_40 dispatch 책임과 status barrier 소비 책임은 유지한다.
|
||||||
|
|
||||||
|
## 4. 최소 직접 인계 계약
|
||||||
|
|
||||||
|
### 4.1 호출자가 제공할 값
|
||||||
|
|
||||||
|
직접 입력은 아래 **두 문자열 필드의 닫힌 객체**로 제안한다. 이는 파일 생성 지시가 아니라 호출 데이터 예시다.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"stage1_run_root_ref": "<workspace-relative Stage 1 사건 결과 root>",
|
||||||
|
"stage1_deployment_root_ref": "<workspace-relative 해당 Stage 1 배포 root>"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| 정보 | 공급·검증 책임 | 최소화 이유 |
|
||||||
|
|---|---|---|
|
||||||
|
| `stage1_run_root_ref` | 호출자가 완료된 Stage 1 결과의 실제 root를 공급; S2_00 검증 | 16개 개별 파일 경로를 수동 작성하지 않고 release의 상대경로를 결합한다. |
|
||||||
|
| `stage1_deployment_root_ref` | 호출자가 해당 Stage 1 결과에 대응하는 실제 배포 root를 공급; S2_00이 55개 잠금 검증 | 사건 자료와 배포 config/schema를 구별한다. 추측한 `Default_Agent` 경로로 대체하지 않는다. |
|
||||||
|
| user/workspace 인증·hash | 기존 backend/localdocs 세션 결속 | 일반 호출 입력으로 workspace를 임의 지정하지 않는다. |
|
||||||
|
| Stage 2 자산 root·release pin | 기존 inline 상수·빌드/배포 계약 | 호출자가 release나 output 경로를 자유롭게 바꾸지 않도록 유지한다. |
|
||||||
|
| `request_id`·`attempt_id` | 호출 입력에서 제거; §4.3에 따른 내부 호환 처리 | 별도 ID 부여 task·request 준비 파일이 필요하지 않다. |
|
||||||
|
|
||||||
|
`W/`는 localdocs 논리 workspace root다. `U=W/<stage1_run_root_ref>/`, `D=W/<stage1_deployment_root_ref>/`, `A=W/Default_Agent/Stage_2_Clean/`다. Dropbox 절대경로나 Code Executor의 로컬 mount와 동일하다고 가정하지 않는다.
|
||||||
|
|
||||||
|
두 root는 이미 NFC인 canonical 상대경로여야 한다. 빈 값·절대경로·`..`·역슬래시·NUL·비정규 경로·알 수 없는 필드는 거절한다. Stage 1 완료 여부는 폴더 존재만으로 판정하지 않고 기존 handoff·writer report·identity/seal 검증으로 확인한다.
|
||||||
|
|
||||||
|
### 4.2 executor에 실제 전달하는 방식
|
||||||
|
|
||||||
|
**직접 인계를 함수 signature에만 적고 현재처럼 실제 진입점에서 인자가 없는 상태로 남겨서는 안 된다.** 개정 계약은 호출 데이터가 유일한 `run_code` 실행의 `run_inline_mcp`에 실제 도달하는 지점까지 포함한다.
|
||||||
|
|
||||||
|
권고 함수 경계는 다음과 같다. 아래는 설계용 signature이며 완성 코드가 아니다.
|
||||||
|
|
||||||
|
```python
|
||||||
|
run_inline_mcp(direct_handoff, *, client=None)
|
||||||
|
_inline_validate_direct_handoff(direct_handoff)
|
||||||
|
_inline_hydrate(localdocs, temp_root, validated_handoff)
|
||||||
|
```
|
||||||
|
|
||||||
|
backend가 지원하는 기존 task input binding이 확인되면 두 root를 위 진입점에 넘긴다. 다만 현재 YAML의 `run_code` 파라미터에는 `language`, `requirements`, `network`, `timeout`, `code`만 있고 임의 `arguments`나 환경변수 입력 계약은 확인되지 않았다. `{{stage1_run_root_ref}}` 같은 템플릿 지원을 사실로 가정하지 않는다.
|
||||||
|
|
||||||
|
별도 인자 binding이 없고 backend가 실행 요청의 `code`를 조립하는 기능을 제공한다면, **정적 ingress 본문과 canonical JSON을 base64로 인코딩한 데이터 literal, 고정 진입점 호출**로 한 번의 `run_code` 요청을 구성하는 방식을 대안으로 명세한다. 원시 경로를 Python 코드에 문자열 치환하지 않고 decode 후 동일한 닫힌 입력 검증을 적용한다. 본문·입력 데이터·최종 실행 code hash를 구별하고, 조립기는 임의 코드 변형을 허용하지 않는다. 현재 workflow의 `dynamic_code_allowed:false`와 inline parity 규칙에 대한 이 제한된 데이터 결속 예외도 함께 명시·검증해야 한다.
|
||||||
|
|
||||||
|
두 방식은 실제 backend capability에 따라 **하나만 채택**한다. 이 문서는 어느 방식도 현 플랫폼에서 이미 지원된다고 확정하지 않는다. 구현 착수 시 확인할 외부 정보는 “호출의 두 문자열이 현재 `run_code` 진입점에 어떻게 전달되는가”로 좁힌다. 새로운 request 파일·prepare task를 그 확인의 우회책으로 되살리지 않는다.
|
||||||
|
|
||||||
|
### 4.3 ID는 출력 호환에 필요한 내부 값으로 처리
|
||||||
|
|
||||||
|
현행 `request_id`는 `run_binding_receipt`와 stdout, S2_10 인계 계약에 존재한다. `attempt_id`는 `publish_atomically`의 임시 staging 이름에 사용된다. 두 값을 전면 삭제하면 ingress·downstream schema와 검증기 변경이 늘어나므로 **외부 부여만 폐기하고 내부 호환 필드는 유지**한다.
|
||||||
|
|
||||||
|
최소 구현안은 core 진입 전 adapter에서 다음과 같이 값을 결정하는 것이다.
|
||||||
|
|
||||||
|
```text
|
||||||
|
request_id = "S2REQ-" + canonical_digest({
|
||||||
|
user_context_sha256,
|
||||||
|
workspace_context_sha256,
|
||||||
|
stage1_run_root_ref,
|
||||||
|
stage1_deployment_root_ref,
|
||||||
|
expected_stage2_release_sha256,
|
||||||
|
pass_1_raw_hash_map_digest
|
||||||
|
})
|
||||||
|
|
||||||
|
attempt_id = "ATT-" + executor 내부에서 생성한 UUID hex
|
||||||
|
```
|
||||||
|
|
||||||
|
`pass_1_raw_hash_map_digest`는 request 파일을 제외한 실제 파일 closure를 두 read-pass에서 검증한 기존 hydration receipt의 값이다. 두 pass가 일치한 뒤 기존 `canonical_digest`로 `request_id`를 결정하므로 추가 원격 read나 새 입력 manifest가 필요하지 않다. UUID에는 Python 표준 라이브러리를 사용한다. 임시 디렉터리마다 격리되므로 원격 attempt 파일도 추가하지 않는다.
|
||||||
|
|
||||||
|
`attempt_id`·현재 시각·랜덤값은 canonical output이나 run binding에 넣지 않는다. `request_id` 결정은 동일 workspace·동일 root·동일 파일 closure·동일 release에서 재현 가능해야 한다. 입력 root가 이동하면 hydration receipt의 logical path도 달라지므로, 경로가 다른 실행까지 output byte 동일성을 보장한다고 확대하지 않는다.
|
||||||
|
|
||||||
|
이 내부 값은 기존 `execute_ingress(..., run_id=request_id, attempt_id=attempt_id, ...)`에 전달할 수 있어 core signature와 출력 ID 필드를 대체로 유지할 수 있다. 최종 `run_id`와 output root는 계속 기존 `run_binding_digest`에서 결정한다. 현행 binding digest의 핵심 재료는 `input_set_digest`, `stage2_release_digest`, `algorithm_digest`, `release_class`이며 request/attempt ID를 추가하지 않는다.
|
||||||
|
|
||||||
|
## 5. Stage 1 원본과 배포 자산 재사용
|
||||||
|
|
||||||
|
### 5.1 사건 고정 입력 16개
|
||||||
|
|
||||||
|
다음은 IO 문서 §3.2의 경로를 `U/` 기준으로 묶은 목록이다. 파일 수는 그룹을 전개하면 정확히 16개다.
|
||||||
|
|
||||||
|
| 묶음 | 정확한 상대경로 |
|
||||||
|
|---|---|
|
||||||
|
| P1 root 3개 | `evidence_indexed.json`, `evidence_event_candidates.json`, `client_goal.json` |
|
||||||
|
| P1 routing 2개 | `routing/domain_screening.json`, `routing/domain_activation_manifest.json` |
|
||||||
|
| P1 gate/handoff 3개 | `quality_gates/B1_evidence_indexed_gate.json`, `quality_gates/B2_event_candidates_gate.json`, `quality_gates/stage1_part1_soft_gate_handoff.json` |
|
||||||
|
| P2 3개 | `BO.json`, `signals/signal_manifest.json`, `quality_gates/stage1_part2_review_handoff.json` |
|
||||||
|
| P3 2개 | `legal_effect_structures.json`, `quality_gates/stage1_part3_review_handoff.json` |
|
||||||
|
| P4 3개 | `Fact_Ledger_base.json`, `stage1_tmp/fact_ledger/fact_ledger_writer_report.json`, `quality_gates/stage1_part4_review_handoff.json` |
|
||||||
|
|
||||||
|
구현의 source-of-truth는 release의 `/stage1_sources`다. 전략 표를 별도의 고정 경로 목록으로 코드에 중복 작성하지 않는다. 원본 JSON을 새 Stage 1 계약으로 변환해 저장하거나 Stage 1에 새 handoff/seal 파일을 요구하지 않는다. 필요한 Stage 2 내부 adapter는 원본 raw hash·pointer를 보존하면서 메모리 객체를 만든다.
|
||||||
|
|
||||||
|
### 5.2 고정 16개 밖의 필수·조건부 입력
|
||||||
|
|
||||||
|
| 입력군 | 현재 범위 | 개정 원칙 |
|
||||||
|
|---|---|---|
|
||||||
|
| signal payload family | `U/signals/<signal_manifest.files[i].path>` 전체 | `files[]`에서만 전개하고 행 순서·hash·semantic/integrity 구분을 유지한다. 폴더 scan으로 대체하지 않는다. |
|
||||||
|
| 두 activation | `U/routing/domain_activation_manifest.json` 및 manifest에 포함되는 `U/signals/domain_activation_manifest.json` | 별개 경로의 원본을 유지하고 기존 semantic projection 비교를 수행한다. |
|
||||||
|
| Stage 1 고정 배포 | `/dependency_locks/stage1/concrete_paths`의 55개 | `D/`의 runtime manifest·registry·domain config·schema를 exact path/hash로 읽는다. 사건 입력과 혼동하지 않는다. |
|
||||||
|
| Stage 2 직접 의존 | `/dependency_locks/stage2_direct`의 49개 | 49개 전부의 기존 closure 검증을 유지한다. |
|
||||||
|
| Stage 2 메타데이터·schema | release, module manifest, ingress/context/review schema 3개 | 위 49개와 별도로 기존 hydration에 포함한다. |
|
||||||
|
| contract manifest·completion seal | release가 유효한 참조를 선택한 경우 | 현재 미설정 참조를 새 Stage 1 필수 파일로 승격하지 않는다. |
|
||||||
|
|
||||||
|
49개 직접 자산은 S2_10 Agent·LLM binding·authority registry/release **4개**, substantive **22개**, crosscut **5개**, special-law **13개**, overlay **5개**다. 각 filename·위치는 [IO 문서 §3.5](../../../Stage_2_00_IO_info.md)와 release가 정의한다. 모든 자산을 읽어 검증하는 것과 그 본문을 모두 LLM에게 전달하는 것은 다른 작업이다.
|
||||||
|
|
||||||
|
이번 개정에서는 49개를 선택된 것만 읽도록 축소하거나 공유 cache를 새로 도입하지 않는다. 그러려면 release closure·cohort·검증 정책까지 바뀐다. 가장 큰 절감은 기존 자산 재사용과 request 전달층 제거에서 얻는다. `selected_context_refs=[]`를 임의로 채우거나 49개 전체를 prompt에 넣어서 정상 handoff를 흉내 내지 않는다.
|
||||||
|
|
||||||
|
## 6. 실제 변경 지점과 최소 영향 범위
|
||||||
|
|
||||||
|
### 6.1 Agent와 ingress adapter
|
||||||
|
|
||||||
|
| 변경 지점 | 최소 개정 내용 | 재사용하는 부분 |
|
||||||
|
|---|---|---|
|
||||||
|
| Agent tasks·DAG | prepare task 제거; `IN → Task_S2_00_deterministic_ingress → OUT`; ingress `wait_until: [IN]` | ingress task 이름·executor·300초·`httpx==0.28.1`·network·비 LLM 실행 |
|
||||||
|
| task pointer | ingress의 `/Agent/Stages/0/tasks/1/parameters/code`를 `/Agent/Stages/0/tasks/0/parameters/code`로 갱신 | 단일 inline code 실행 원칙 |
|
||||||
|
| 직접 입력 검사 | `_inline_validate_request(raw)`의 파일/6필드 계약을 두 root의 `_inline_validate_direct_handoff`로 대체 | strict JSON 처리, NFC·상대경로 검증 helper |
|
||||||
|
| hydration | `_inline_hydrate`가 전달받은 validated handoff 사용; `INLINE_REQUEST_PATH` read·read-pass 등록 제거 | release pin, 크기·개수 상한, exact materialization plan, 두 read-pass, 원본 bytes 복제 |
|
||||||
|
| 경로 전개 | `_inline_release_materialization_plan`의 인자를 handoff로 변경; 두 root key는 그대로 사용 | 16개·manifest 신호·55개·49개 상대경로 전개 |
|
||||||
|
| core 호출·receipt | §4.3의 내부 ID를 기존 core에 전달; stdout도 같은 request ID 사용 | C00–C15, run binding, output schema, 정확한 artifact set 검증 |
|
||||||
|
| 원격 발행 | request read/write 제거 외에는 기존 status-last 발행 유지 | non-status write/read-back, status 마지막 write/read-back, 기존 barrier 전체 bytes 비교 |
|
||||||
|
|
||||||
|
검증과 ingress의 통합은 **하나의 task에서 차례로 처리한다**는 의미다. 실제 Stage 1/자산의 두 read-pass는 변경 중인 원본을 검출하므로 유지한다. 이를 “검증을 한 번만 한다”는 이유로 단일 read로 줄이지 않는다.
|
||||||
|
|
||||||
|
고정 request 경로를 없애면 그 경로의 실행 간 덮어쓰기와 prepare→ingress 사이 직렬화 문제는 사라진다. 그러나 같은 `run_binding_digest`로 동시에 발행하는 문제까지 없어지지는 않는다. 같은 workspace/binding의 publication에는 기존 host 단일 실행 통제를 적용·확인한다. 현재 remote writer는 overwrite와 논리 barrier이므로 이를 원자적 CAS라고 부르지 않는다. 신규 lock JSON을 만드는 방식으로 실행 보장을 대신하지 않는다.
|
||||||
|
|
||||||
|
### 6.2 함께 갱신할 계약·파생물
|
||||||
|
|
||||||
|
아래는 **향후 YAML 개정 시** 필요한 변경 범위다. 이번 전략 작성에서는 변경하지 않는다. `R/`는 본 `Stage_2_Clean/` 패키지이고 authoring은 Stage 2 main 폴더 기준이다.
|
||||||
|
|
||||||
|
| 파일/묶음 | 필요한 조치 |
|
||||||
|
|---|---|
|
||||||
|
| `Stage_2_S2_00_v.2.yml` → `R/agent_scripts/Stage_2_S2_00.yml` | receipt와 builder가 지정한 authoring부터 개정하고 배포본을 재투영한다. 동명 구 authoring으로 덮어쓰지 않는다. |
|
||||||
|
| `R/runtime/s2_00_ingress.py`, `.txt` | 단일 inline code와 byte parity를 재생성한다. runtime import 자산으로 전환하지 않는다. |
|
||||||
|
| `R/workflows/S2_00_stage1_ingress_normalize_and_bundle_compile.yml` | 두-task/고정 request 계약을 direct handoff 계약으로 변경하고 task pointer·root/ID 책임을 정정한다. |
|
||||||
|
| `R/deployment/stage2_code_executor_binding.yml` | S2_00 row의 prepare 계약·fixed request read allowlist 제거; direct input 전달·단일 task hash와 pointer 갱신. 다른 Stage의 request 계약은 유지한다. |
|
||||||
|
| `R/offline_build/build_s2_00_inline_projection.py`, `.txt` | `EXACTLY_TWO_TASKS_REQUIRED`·prepare 전제·두 pointer/receipt 생성을 단일 ingress 계약으로 변경한다. |
|
||||||
|
| `R/manifest/s2_00_inline_code_receipt.json` | authoring/projection·단일 code·mirror·DAG hash와 pointer를 다시 기록한다. |
|
||||||
|
| `R/schemas/deployment.schema.json` | 실제 S2_00 binding의 prepare 필수 규칙을 직접 인계 규칙으로 제한 변경한다. |
|
||||||
|
| `R/schemas/ingress.schema.json` | 출력·run binding schema는 유지한다. 기존 `execution_request` 정의는 runtime 미사용으로 두어 불필요한 schema/hash 변경을 줄이고, 새 직접 입력의 runtime 닫힌 검증은 adapter에 둔다. |
|
||||||
|
| `R/runtime/s2_00_prepare_request.py`, `.txt` | 신규 build·manifest·실행 참조에서 제외한다. 역사 파일의 물리 삭제를 개정 전제 작업으로 삼지 않는다. |
|
||||||
|
| 영향받는 module/parent/child manifests·receipts | 변경 파일을 실제 참조하는 hash·pointer·algorithm 계약만 기존 build 순서로 재봉인한다. unrelated 본문 수정이나 전체 package 재설계는 하지 않는다. |
|
||||||
|
| 기존 S2_00 시험·mirror | 두-task·request file 전제와 직접 입력/ID 시험을 제한 수정하고 §9의 필요한 실패·보존 검증을 수행한다. |
|
||||||
|
|
||||||
|
공유 executor binding bytes가 바뀌면 다른 Stage가 그 binding hash를 참조할 수 있다. “단일 YAML만 수정”이나 “항상 몇 개 파일만 수정”이라고 미리 확정하지 않고 실제 역참조 closure를 확인한다. parent/module/self-hash의 기존 비순환 규칙을 유지하며, 직접 입력 정책의 의미 변경은 알고리즘 계약/버전에 반영한다. 빌드 산출 hash 변경은 법률 규칙 내용 변경과 구별한다.
|
||||||
|
|
||||||
|
## 7. 기존 목표 달성을 위한 최소 의미 보완
|
||||||
|
|
||||||
|
직접 인계는 전송 경계의 단순화다. 현행 C00–C15가 완전한 S2_10 문맥을 이미 만든다는 전제는 채택하지 않는다. [구형 분석 §8](../../../Stage_2_00_Analysis_v1.md)의 제한과 현재 inline 비교를 근거로, 다음 사항을 목표 달성 검증에 포함한다.
|
||||||
|
|
||||||
|
| 제한 | 가장 작은 Stage 2 내부 보완 | 완료 증거 |
|
||||||
|
|---|---|---|
|
||||||
|
| 00-L2 signal adapter ID 불일치 | release의 `S2A-SIGNAL-ALL-V1`과 core의 family 검사 계약을 하나로 정합화한다. 검증을 제거하거나 모든 문자열을 허용하지 않는다. | 실제 release row에서 family mismatch가 없고 signal ALL hash·개수·activation 검사가 작동한다. |
|
||||||
|
| 00-L4/L7 raw pointer·slice provenance | 기존 원본 adapter가 실제 배열/wrapper 위치를 결정하고, 기존 source-ref helper로 원본 row의 정확한 pointer·raw-value hash를 projection에 결속한다. | fact/BO/LES/EVENT ref를 원본 bytes에서 dereference해 같은 값·hash를 얻는다. |
|
||||||
|
| 00-L5 fact·client goal 의미 축약 | 기존 FACT/case payload projection에 법률판단에 필요한 원문 사실·domain effects·목표 지시를 해당 cluster 범위로 전달한다. | 중요한 원문 사실·목표 지시를 S2_10용 문맥에서 원본 참조와 함께 확인한다. |
|
||||||
|
| 00-L6/L9 signal·review·profile 전달 공백 | 기존 slice projection 배열을 해당 cluster의 명시적 source 관계로 채우고, mapped review의 내용·범위·blocking 정보도 보존한다. | 원본 occurrence와 output occurrence의 대응이 확인되고 unresolved review가 유실되지 않는다. |
|
||||||
|
| 00-L8 slot skeleton | 원본에 존재하는 slot/fact/evidence 연결만 기존 crosswalk에 반영한다. 근거가 없으면 `UNEVALUABLE`을 유지한다. | 빈 skeleton을 완전한 연결로 오인하지 않고, 원본의 명시적 연결은 누락 없이 인계한다. |
|
||||||
|
|
||||||
|
보완은 기존 adapter·source-ref·projection 함수와 기존 산출물에 집중한다. Stage 1 원본 재작성, 별도 Stage 1 규격 도입, LLM task 추가는 필요하지 않다. 기존 schema로 원문과 출처를 담을 수 없는 경우에만 해당 projection schema와 직접 소비 계약을 함께 제한 변경한다. 확인되지 않은 연결을 추론하여 채우지는 않는다.
|
||||||
|
|
||||||
|
먼저 직접 인계 경계만 개정해 비교 가능한 기준을 만들고, 원본을 보존한 fixture에서 위 공백을 검사한다. **정상 ingress를 가로막거나 필요한 의미를 유실하는 항목은 완료 판정 전에 보완한다.** 기존 review/status-only 기능을 지우거나 release guard를 꺼서 시험을 통과시키지 않는다. S2_10 자체의 adapter/echo/multi-wave 미결 문제는 직접 인계 성공으로 해결된 것으로 기록하지 않는다.
|
||||||
|
|
||||||
|
## 8. 효율적인 개정 순서
|
||||||
|
|
||||||
|
1. **정본과 입력 계약 고정:** 현행 authoring/배포 hash, 두 root 공급 위치, backend의 실제 직접 입력 전달 방식을 확인한다. Stage 1 결과의 bytes와 경로는 고정한다.
|
||||||
|
2. **단일 task와 adapter 경계 개정:** prepare/DAG 제거, 직접 입력 validator·hydration 인자·내부 ID·실제 진입점을 함께 변경한다. request 파일과 새 prepare 기능은 만들지 않는다.
|
||||||
|
3. **원본 재사용·의미 인계 확인:** 16개+신호+55개+49개 closure를 그대로 사용해 C00–C15를 수행하고 §7에서 실제 드러나는 최소 adapter/projection 결함을 보완한다.
|
||||||
|
4. **계약·projection 일괄 갱신:** authoring을 기준으로 builder·workflow·S2_00 binding·mirror·receipt·영향받는 hash closure를 한 번에 재생성한다. 중간마다 unrelated 자산을 재작성하지 않는다.
|
||||||
|
5. **필요한 검증 종료:** §9의 정적·offline 결과를 확정하고, 허용된 release와 실제 backend가 준비된 때 동일 입력 경계의 live 검증을 수행한다. 정적 PASS와 live 성공은 따로 기록한다.
|
||||||
|
|
||||||
|
작업량은 executor task **2→1**, 별도 제어 파일 **1→0**, 호출자가 부여하는 ID **2→0**으로 줄어든다. request 저장/read-back·후행 재읽기와 prepare 세션도 제거된다. 총 시간·비용 절감률은 측정하지 않았으며, 16/55/49개와 동적 입력의 핵심 검증 비용은 유지한다.
|
||||||
|
|
||||||
|
## 9. 수용 기준과 검증 계획
|
||||||
|
|
||||||
|
아래는 향후 구현의 검증 계획이며 이번 작성 작업에서 실행한 시험 결과가 아니다.
|
||||||
|
|
||||||
|
| 검증 면 | 필수 수용 기준 |
|
||||||
|
|---|---|
|
||||||
|
| 실행 구조 | S2_00 Agent task와 `run_code`가 정확히 하나; ingress pointer가 `tasks/0`; IN/OUT edge와 workflow·binding·receipt가 일치한다. |
|
||||||
|
| 실제 직접 입력 | 호출에서 주어진 두 root가 executor 진입점에 그대로 도달한다. 누락·미치환·unknown key·비NFC·절대/탈출 경로는 원본 읽기·output write 전에 거절한다. |
|
||||||
|
| request 제거 | S2_00 실행에 `stage2_control/s2_00_request.json` read/write가 없고, 호출자가 ID를 공급할 필요가 없다. 기존 파일이 남아 있어도 소비하지 않는다. |
|
||||||
|
| 원본·closure | Stage 1 원본의 변경은 0; 16개·55개·49개와 manifest 신호·조건부 입력을 release대로 전개한다. 한 파일 hash 불일치·원본 read-pass 변경을 검출한다. |
|
||||||
|
| ID·재실행 | 동일 직접 입력/closure/release의 내부 request ID가 동일하고 attempt 값은 canonical 결과를 바꾸지 않는다. 기존 output root를 바꾸지 않으며 동일 bytes는 재사용하고 충돌은 거절한다. |
|
||||||
|
| 문맥 보존 | §7의 실제 source pointer를 dereference하고 사실·목표·신호·review·명시적 slot 관계의 필요한 내용이 후속 slice에서 확인된다. 원본 미상은 미상으로 남는다. |
|
||||||
|
| 정상 route | `TO_S2_10`/`TO_S2_10_WITH_ISSUES`; 아래 고정 11개+실행 가능 slice E개가 schema·artifact set/hash 검증을 통과한다. |
|
||||||
|
| 진단 route | `TO_S2_40_STATUS_ONLY`; 아래 5개만 발행하고 정상 context를 만들지 않는다. 직접 입력 오류가 항상 진단 5파일을 만든다고 가정하지 않는다. |
|
||||||
|
| 발행 실패·동시 실행 | non-status write/read-back 실패 시 유효한 새 barrier를 게시하지 않는다. 기존 barrier와 잔여 파일을 실제 확인하고 동일 binding 동시 writer 통제를 검증한다. |
|
||||||
|
| 빌드·release | authoring↔projection↔inline/mirror parity, task pointer와 참조 hash closure, algorithm 의미 계약이 일치한다. 기존 DEV guard와 pending admission을 보존한다. |
|
||||||
|
| live | 직접 입력 전달·인증 세션·closure 읽기·실제 발행·downstream barrier 소비를 실환경에서 확인한다. offline 함수 성공만으로 live 완료를 선언하지 않는다. |
|
||||||
|
|
||||||
|
정상 고정 출력 11개는 `ingress/stage1_input_manifest.json`, `ingress/intake_report.json`, `review/issue_ledger.base.json`, `ingress/ingress_status.json`의 공통 4개와 `context/case_context.json`, `context/evidence_inventory.json`, `context/object_registry.json`, `context/party_and_title_context.json`, `context/slot_crosswalk.json`, `context/cluster_plan.json`, `context/bundle_plan.json`의 7개다. 가변 출력은 `context/cluster_slices/<cluster_id>.json`이다.
|
||||||
|
|
||||||
|
진단 5개는 공통 4개와 `ingress/technical_diagnostic.json`이다. 모든 최종 파일은 `W/stage2_runs/by-binding/<run_binding_digest>/`에 저장하고 `ingress/ingress_status.json`을 마지막에 발행한다. stdout receipt와 임시 hydration 파일은 이 출력 개수에 포함하지 않는다.
|
||||||
|
|
||||||
|
단일 task, 직접 입력 validator, 원본 closure, C00–C15 의미 보존, 분기 출력과 barrier가 함께 충족되어야 S2_00 개정 목표 달성으로 판정한다. 법률적 판단의 타당성, S2_10 플랫폼/model/legal admission 또는 전체 Stage 2 production readiness는 별도의 확인 대상이다.
|
||||||
|
|
||||||
|
## 10. 결정·검증 기록
|
||||||
|
|
||||||
|
| 결정 | 근거 | 효과 |
|
||||||
|
|---|---|---|
|
||||||
|
| 직접 인계와 ingress를 유일 task에 통합 | 사용자 개정 방향; 현행 prepare 인자 결속 미검증 | 별도 제어 파일과 task 사이 전달을 제거한다. |
|
||||||
|
| 두 root만 호출 데이터로 사용 | 기존 release가 16/55/49 및 신호 경로를 정의 | 개별 파일 경로·ID 준비 작업을 줄이고 Stage 1 변경을 피한다. |
|
||||||
|
| ID 출력 필드는 내부 호환 유지 | 현행 run binding/stdout 및 S2_10 계약 | downstream 전면 변경을 피한다. |
|
||||||
|
| 두 read-pass·직접 자산 49개 유지 | 현행 hydration·release closure 계약 | 전송층 단순화가 무결성 검증 축소로 이어지지 않도록 한다. |
|
||||||
|
| 필요한 의미 보완은 Stage 2 adapter/projection에 한정 | 구형 분석 §8; 현행/구형 ingress code 차이는 release pin 하나 | 입력 통합과 실제 목표 달성을 구별하면서 원본을 보존한다. |
|
||||||
|
|
||||||
|
문서 작성 중 현행/구형 YAML, 두 분석 종류, IO 표, release의 55/49 경로 수, authoring 정본 및 inline code 차이를 정적으로 대조했다. 저장 후 확인에서 로컬 출처 링크 11개, Markdown fence 8개, UTF-8·종결 LF, release와 일치하는 사건 입력 16개 및 MEMORY 요약 위치·유일성이 통과했다. 참조한 원본 문서·YAML·자산 15개의 SHA-256은 작업 전후 동일했다. 이는 문서 정적 확인 결과다. YAML 개정, builder 실행·재봉인, 회귀시험, MCP/backend/live 실행, 배포·commit은 이번 작업에서 수행하지 않았다.
|
||||||
+196
@@ -0,0 +1,196 @@
|
|||||||
|
# Stage_2_S2_00.yml 개정 전략 v2
|
||||||
|
|
||||||
|
작성일: 2026-10-02. 상태: **전략서 작성 완료 / YAML 개정·실행 시험 미수행**.
|
||||||
|
|
||||||
|
## 1. 목표·산출물·범위
|
||||||
|
|
||||||
|
**Stage 1 사건·배포 root를 S2_00의 단일 ingress task에 직접 전달하고, Stage 1 원본을 그대로 읽어 검증·보존·정규화·묶음 구성·발행하는 것으로 S2_00을 완결한다. 별도의 `request_id`는 외부 입력, 내부 생성, 출력 모두에서 제거한다.**
|
||||||
|
|
||||||
|
이번 작업의 산출물은 이 전략서와 Stage 2 `MEMORY.md`의 작업 요약뿐이다. 실제 YAML·schema·runtime·manifest·builder·배포 자산은 수정하지 않는다. 향후 구현의 유일한 개정 대상도 이 폴더의 [Stage_2_S2_00.yml](Stage_2_S2_00.yml)이다. 다른 authoring 파일부터 수정하고 이 YAML을 재생성하는 v1의 절차는 이번 범위에 적용하지 않는다.
|
||||||
|
|
||||||
|
`Stage_2_S2_10.yml`, `Stage_2_S2_20.yml`, `Stage_2_S2_30.yml`, `Stage_2_S2_40.yml`의 자산 사용 schema, 인계 필드, route, 출력 수, admission·hash 호환 요구는 설계 기준과 수용 기준에서 제외한다. 해당 파일을 분석하거나 개정하는 작업도 포함하지 않는다. S2_00의 자체 입력·출력 계약과 검증은 개정 YAML의 metadata 및 inline code 안에 둔다.
|
||||||
|
|
||||||
|
S2_00의 현행 기능 목표인 C00 입력 검증, C05 보존·정규화, C10 claim-neutral cluster 구성, C15 묶음·결과 발행은 유지한다. 후속 Agent에 맞추기 위한 구조·필드의 보존은 목표에서 제거한다. 이 문서의 함수 signature, 출력 구조와 변경 순서는 구현 제안이며 현 플랫폼의 지원 사실이나 실행 결과가 아니다.
|
||||||
|
|
||||||
|
## 2. 확인한 기준과 v1에서 폐기할 전제
|
||||||
|
|
||||||
|
| 기준 자료 | 현재 확인 내용 | v2에서의 사용 |
|
||||||
|
|---|---|---|
|
||||||
|
| [현행 S2_00 YAML](Stage_2_S2_00.yml) | Agent `Stage_2_S2_00_v2`, version `1.2.0`; prepare·ingress 두 task; inline C00–C15 | S2_00 내부 변경 위치와 보존할 처리 기능의 기준 |
|
||||||
|
| [현행 S2_00 분석서](Analysis_Stage_2_S2_00.md) | 직접 인자 결속 미검증, 고정 request 파일, 정상/진단 발행, DEV release 제한 | 현재 구현과 미확인 운영 경계 구별 |
|
||||||
|
| [개정 전략 v1](stage_2_s2_00_revision_strategy_v1.md) | 내부 request ID 유지, 기존 downstream 출력/schema와 49개 자산 유지, 여러 파일 재봉인 계획 | 사용자 원칙에 맞지 않는 전제를 식별하는 비교 자료 |
|
||||||
|
| [현행 release](../manifest/stage2_release.json) | Stage 1 고정 사건 입력 16개와 동적 signal family; Stage 1 배포 잠금 55개; Stage 2 직접 잠금 49개; `DEV_FIXTURE_RELEASE` | Stage 1 경로·adapter·검증 근거의 확인 자료. Stage 2 전체 의존 목록을 v2에 자동 승계하지 않음 |
|
||||||
|
|
||||||
|
v1의 다음 결정은 v2에서 대체한다.
|
||||||
|
|
||||||
|
- 내부 `request_id = "S2REQ-" + digest(...)` 생성과 stdout·receipt 출력은 전면 폐기한다.
|
||||||
|
- `attempt_id` UUID를 생성해 기존 core signature에 맞추는 방식도 폐기한다. 임시 작업 격리는 실행별 임시 디렉터리로 해결한다.
|
||||||
|
- 기존 `run_binding_receipt`, `S2RUN-...` 생성, `by-binding/<digest>` 출력 경로를 유지할 호환 의무를 제거한다. 무결성 hash는 실제 검증에 필요한 용도로만 사용한다.
|
||||||
|
- 기존 정상 11+E개·진단 5개 출력, S2_10용 bundle schema, S2_40용 status-only route를 수용 기준으로 삼지 않는다.
|
||||||
|
- Stage 2 직접 자산 49개 일괄 읽기와 다른 Stage의 hash closure 재봉인은 개정 절차에서 제외한다.
|
||||||
|
|
||||||
|
## 3. 절대 원칙과 완료 기준
|
||||||
|
|
||||||
|
1. 호출 데이터는 `stage1_run_root_ref`, `stage1_deployment_root_ref` 두 값이다. 사건 자료와 배포 자료의 실제 위치를 추정하거나 새 request 파일로 중계하지 않는다.
|
||||||
|
2. Stage 1 원본의 bytes·파일명·상대경로·원문 의미를 보존한다. 기존 사건/transaction 식별자가 원본에 있으면 출처 검증에 그대로 사용하며 새 요청 식별자로 재포장하지 않는다.
|
||||||
|
3. prepare task, 고정 request 파일 read/write, 별도의 ID 발급·저장·출력을 제거한다. `request_id`를 이름만 바꾼 `handoff_id`·`execution_id`도 만들지 않는다.
|
||||||
|
4. S2_00은 비 LLM deterministic task 하나로 수행한다. 입력 본문 전체를 prompt에 복사하거나 LLM task를 추가하지 않는다.
|
||||||
|
5. 검증은 S2_00의 실제 입력·연산·출력에 필요한 것으로 한정한다. 원본의 무결성·출처·필수 내용·blocking review를 생략하여 단순화하지 않는다.
|
||||||
|
6. 정상 결과는 원본을 추적할 수 있고 미해결 사항을 보존해야 한다. 입력 전달 성공만으로 S2_00의 기능 목표 달성을 선언하지 않는다.
|
||||||
|
|
||||||
|
완료는 **두 root의 실제 인자 결속 → 원본 읽기와 필요한 검증 → 의미 보존·정규화·cluster/bundle 구성 → 자체 출력 검증 → 마지막 status 발행**이 한 task에서 성립하는 것으로 판정한다. 기존 S2_10~40에서 읽을 수 있는지는 판정 대상이 아니다.
|
||||||
|
|
||||||
|
## 4. 직접 입력과 실행 경계
|
||||||
|
|
||||||
|
호출 데이터의 닫힌 형태는 다음과 같다. 이는 파일로 저장할 request가 아니라 실행 인자다.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"stage1_run_root_ref": "<Stage 1 사건 결과의 workspace 상대 root>",
|
||||||
|
"stage1_deployment_root_ref": "<그 결과에 대응하는 Stage 1 배포의 workspace 상대 root>"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`W/`는 인증된 localdocs 논리 workspace root, `U=W/<stage1_run_root_ref>/`, `D=W/<stage1_deployment_root_ref>/`다. Dropbox 절대경로나 executor 임시 mount와 동일한 것으로 취급하지 않는다. 인증 정보는 기존 backend 세션을 사용하며 호출자가 임의 workspace를 지정하게 하지 않는다.
|
||||||
|
|
||||||
|
권고 경계는 다음과 같다.
|
||||||
|
|
||||||
|
```python
|
||||||
|
run_inline_mcp(stage1_run_root_ref, stage1_deployment_root_ref, *, client=None)
|
||||||
|
validate_direct_roots(stage1_run_root_ref, stage1_deployment_root_ref)
|
||||||
|
hydrate_stage1(localdocs, temp_root, validated_roots)
|
||||||
|
execute_ingress(stage1_root, stage1_deployment_root, *, output_dir, source_checks)
|
||||||
|
publish_result(localdocs, output_root, files, status)
|
||||||
|
```
|
||||||
|
|
||||||
|
별도의 request 객체를 core에 요구하지 않는다. core의 `run_id="S2-00-REQUEST"`, `attempt_id` 필수 인자와 ID 정규식 검사는 삭제하고 필요한 경로·검증 객체만 전달한다. 내부 함수 분리는 유지할 수 있으나 추가 executor task로 나누지 않는다.
|
||||||
|
|
||||||
|
두 root는 canonical NFC 상대경로인지 검사한다. 누락·빈 값·미치환 템플릿·절대경로·`..`·NUL·경로 탈출·알 수 없는 입력 필드는 원본 읽기나 출력 쓰기 전에 거절한다. Stage 1 완료 여부는 폴더 존재만으로 판단하지 않고 기존 writer report·gate/handoff와 원본 간 결속을 확인한다. 기존 완료/seal 정보가 없으면 없다고 기록하며 새 Stage 1 파일을 요구하지 않는다.
|
||||||
|
|
||||||
|
실제 backend가 이 두 문자열을 `run_code` 진입점에 어떻게 전달하는지는 아직 미확인이다. 지원되는 기존 task input binding을 우선 확인한다. 실행 code 조립을 지원하는 경우에만 canonical JSON의 인코딩된 데이터 literal과 고정 호출로 두 값을 전달하는 제한된 대안을 검토한다. 원시 문자열을 Python 코드에 직접 치환하지 않는다. `{{prev...}}`의 cross-run 지원, 임의 `arguments` 파라미터, 환경변수 주입 기능을 가정하지 않는다.
|
||||||
|
|
||||||
|
실제 전달 방식이 YAML 한 파일 개정으로 표현되지 않으면 **직접 입력 live 연결 미확인**으로 남긴다. 이를 이유로 prepare/request 파일을 되살리거나 backend·다른 자산의 개정을 전략 범위에 추가하지 않는다. 함수와 offline fixture 검증은 이 운영 미확인 사항과 구별하여 수행할 수 있다.
|
||||||
|
|
||||||
|
## 5. Stage 1 원본 재사용과 필요한 자산만 읽기
|
||||||
|
|
||||||
|
현행 고정 사건 입력 16개를 그대로 사용한다. 아래 경로는 모두 `U/` 기준이다.
|
||||||
|
|
||||||
|
| 묶음 | 원본 상대경로 |
|
||||||
|
|---|---|
|
||||||
|
| P1 3개 | `evidence_indexed.json`, `evidence_event_candidates.json`, `client_goal.json` |
|
||||||
|
| P1 routing 2개 | `routing/domain_screening.json`, `routing/domain_activation_manifest.json` |
|
||||||
|
| P1 gate/handoff 3개 | `quality_gates/B1_evidence_indexed_gate.json`, `quality_gates/B2_event_candidates_gate.json`, `quality_gates/stage1_part1_soft_gate_handoff.json` |
|
||||||
|
| P2 3개 | `BO.json`, `signals/signal_manifest.json`, `quality_gates/stage1_part2_review_handoff.json` |
|
||||||
|
| P3 2개 | `legal_effect_structures.json`, `quality_gates/stage1_part3_review_handoff.json` |
|
||||||
|
| P4 3개 | `Fact_Ledger_base.json`, `stage1_tmp/fact_ledger/fact_ledger_writer_report.json`, `quality_gates/stage1_part4_review_handoff.json` |
|
||||||
|
|
||||||
|
signal payload는 기존 `signal_manifest.files[]`의 경로·hash·조건을 따라 읽는다. 폴더 전체 scan이나 추측한 파일 목록으로 대체하지 않는다. routing activation과 signal activation은 서로 다른 원본으로 취급하며 실제 의미 일치 여부를 확인한다.
|
||||||
|
|
||||||
|
Stage 1 배포의 현행 55개 잠금 목록은 검증 근거의 기준선이다. v2에서는 각 자산이 사건 JSON 해석, schema 검증, producer/alias·domain 연결, 원본 identity 검증 중 어디에서 필요한지 S2_00 내부에서 추적한다. 필요 자산과 그 검증 의존은 유지하고, 미사용임이 확인된 자산만 읽기 대상에서 제외한다. 검토 전부터 특정 감소 개수를 약속하지 않는다. Stage 1 원본이나 배포 파일을 고쳐 개정에 맞추지 않는다.
|
||||||
|
|
||||||
|
Stage 2의 현행 49개 직접 의존은 후속 작업을 위한 결속까지 포함하므로 전체를 유지하지 않는다. S2_10 YAML·LLM binding과 후속 authority/retrieval 선택용 자산은 S2_00 의존에서 제거한다. registry/config도 C05~C10에서 실제 사용하는 내용만 남기고, 남기는 이유·경로·검증 hash를 YAML 내부의 단일 자산 표에 둔다. 필요한 비실행 데이터는 읽기 전용으로 참조할 수 있으나 그 파일을 개정하거나 새 외부 schema를 만들지 않는다. 문서 안의 법률 규칙을 새로 작성하는 작업도 포함하지 않는다.
|
||||||
|
|
||||||
|
자체 입력 adapter·출력 validator·경로 목록의 정본은 개정 YAML 안에서 하나로 관리한다. 기존 전체 release·module·downstream schema를 그대로 validator에 주입해 제거한 필드를 다시 요구하게 해서는 안 된다. 외부 참조가 있는 기존 schema는 실제 S2_00에 필요한 규칙과 참조 의존만 inline 자체 계약에 반영한다. 이것은 후속 호환 검사 제거이며 Stage 1 원본 검증의 면제는 아니다.
|
||||||
|
|
||||||
|
## 6. C00–C15의 자체 기능 목표
|
||||||
|
|
||||||
|
| 논리 단계 | 유지할 작업 | 단순화·보완 방향 |
|
||||||
|
|---|---|---|
|
||||||
|
| C00 | 사건·배포 원본 읽기, 필수 입력·hash·producer/transaction·gate/review 확인 | 직접 root로 전개. request/ID 검증과 downstream 자산 admission은 제거. hash 근거가 없는 입력은 무근거 상태를 보존 |
|
||||||
|
| C05 | 증거·사실·객체·당사자·목표·LES·signal의 원본 보존과 필요한 정규화 | 원문을 덮어쓰지 않고 필요한 view만 생성. source path·정확한 JSON pointer·raw hash로 원문 연결 |
|
||||||
|
| C10 | 원본의 명시적 관계에 따른 claim-neutral cluster와 작업 범위 구성 | 확인된 fact/evidence/BO/LES/signal 관계만 사용. 빈 slot skeleton이나 근거 없는 관계를 채우지 않음 |
|
||||||
|
| C15 | cluster별 입력 묶음·미해결 항목·결과 검증과 발행 | 후속 Agent prompt·dispatch schema 대신 자체 bundle 표현. S2_10/40 route를 자체 처리 상태로 대체 |
|
||||||
|
|
||||||
|
adapter가 wrapper/배열 위치를 잘못 해석하여 원문 pointer가 틀리거나 사실·client goal·signal·review를 축약해 의미를 잃는 문제는 S2_00 inline code 안에서 보완한다. 원문 사실, 목표 제약, 반대 자료, blocking/unresolved review, 기존 명시적 연결을 보존한다. `UNEVALUABLE`·미상은 원본 근거 없이 해결된 상태로 변경하지 않는다.
|
||||||
|
|
||||||
|
원본의 fact/evidence/object/transaction ID는 의미와 참조를 구성하는 기존 값이므로 유지한다. cluster의 구조상 식별값이 필요한 경우에도 해당 묶음 참조 용도에 한정한다. 이러한 자료 식별값을 요청 ID나 실행 ID 발급의 근거로 사용하지 않는다.
|
||||||
|
|
||||||
|
## 7. ID 없는 출력·재실행·임시 작업
|
||||||
|
|
||||||
|
최종 출력 root는 원본 사건 root를 재사용하여 다음처럼 결정한다.
|
||||||
|
|
||||||
|
```text
|
||||||
|
O = W/stage2_runs/from-stage1/<stage1_run_root_ref>/s2_00/
|
||||||
|
```
|
||||||
|
|
||||||
|
검증된 상대 root의 경로 구성을 그대로 아래에 결합한다. 새 요청 ID, UUID, `run_binding_digest`로 출력 폴더를 만들지 않는다. `O/`가 `U/` 또는 `D/`와 겹치거나 어느 한쪽 안에 놓이는 경우는 쓰기 전에 거절한다. Stage 1 원본 트리는 읽기 전용으로 유지한다.
|
||||||
|
|
||||||
|
권고 정상 출력은 다음 5개이며 cluster 수가 크거나 부분 읽기가 필요한 경우에만 가변 slice를 분리한다. 기존 11+E개 파일을 모두 보존하기 위한 빈 파일은 만들지 않는다.
|
||||||
|
|
||||||
|
| 파일 | 필요한 내용 |
|
||||||
|
|---|---|
|
||||||
|
| `ingress/stage1_input_manifest.json` | 두 root, 읽은 원본 상대경로·raw hash·검증 근거, 기존 원본 identity가 있으면 그 값 |
|
||||||
|
| `ingress/intake_report.json` | 필수 입력·무결성·gate 확인 결과와 누락/미검증 항목 |
|
||||||
|
| `review/issue_ledger.base.json` | 원본 review의 내용·범위·blocking 여부·출처와 S2_00 처리 중 발견한 문제 |
|
||||||
|
| `context/case_context.json` | 정규화 view·원본 참조, 증거/객체/당사자/LES/사실/목표/signal 관계, cluster 목록과 bundle별 원본 참조. 분리 slice 사용 시 해당 경로 |
|
||||||
|
| `ingress/ingress_status.json` | `READY`, `READY_WITH_ISSUES`, `BLOCKED` 중 상태, 두 root, algorithm version, 실제 발행 파일 경로·hash. 마지막에 발행 |
|
||||||
|
|
||||||
|
가변 파일은 필요한 경우에만 `context/cluster_slices/<cluster_ref>.json`으로 분리한다. `<cluster_ref>`는 자료 묶음 참조이며 요청/실행 ID가 아니다. 한 내용은 context 또는 slice 중 한 곳에 담고 다른 곳에서는 참조하여 중복을 줄인다. 원본 전체 사본을 새 context에 복제하지 않는다.
|
||||||
|
|
||||||
|
안전하게 root를 확정하고 입력 검사를 수행한 뒤 발생한 차단은 `BLOCKED`로 기록한다. 이때 정상 context/slice는 발행하지 않으며 intake·issue ledger·technical diagnostic과 마지막 status를 발행한다. 입력 목록을 확정할 수 있을 때만 manifest도 발행한다. 입력 인자·인증 실패처럼 출력 위치를 안전하게 확정하지 못한 오류는 stdout 오류로 종료하고 결과 파일 발행을 강제하지 않는다.
|
||||||
|
|
||||||
|
모든 출력의 닫힌 필드·상태별 required/forbidden artifact 규칙은 inline validator에서 정의한다. 기존 `run_binding_receipt`와 출력 schema의 `request_id`·신규 `run_id` 필수 조건은 함께 제거한다. stdout은 처리 성공 여부·상태·출력 경로·오류·마지막 status hash 정도만 담는다. 별도 ID receipt는 만들지 않는다.
|
||||||
|
|
||||||
|
원본 raw hash와 산출물 hash는 변조·내용 동일성 확인을 위해 유지한다. 이 hash를 접두사와 결합하여 요청 식별자로 재출력하지 않는다. 동일 `O/`가 이미 있으면 기존 입력 경로·hash, 배포 근거, algorithm version과 발행 파일을 확인하고 일치하는 완료 결과만 재사용한다. 내용·버전이 다르거나 잔여 부분 파일이 충돌하면 덮어쓰지 않고 명시적으로 종료한다. 이번 전략은 여러 개정 버전의 결과를 같은 사건 root 아래 동시에 보관하는 새 체계를 도입하지 않는다.
|
||||||
|
|
||||||
|
실행별 `TemporaryDirectory` 안에서 hydration·로컬 결과·staging을 처리한다. 임시 디렉터리의 고유한 이름은 도구 내부 자원이며 호출 필드, canonical output, stdout에 ID로 노출하지 않는다. `publish_atomically(..., attempt_id=...)`의 의존도 함께 제거한다.
|
||||||
|
|
||||||
|
원격 발행은 비 status 파일 write/read-back 이후 status를 마지막에 기록하는 논리적 완료 경계로 유지한다. 임시 디렉터리가 원격 동시 writer 문제까지 해결한다고 주장하지 않는다. 같은 `O/`의 동시 writer는 기존 host 직렬화 기능을 사용할 수 있는지 확인하며, 미지원·미확인 상태에서는 동시 발행 지원을 수용 완료로 기록하지 않는다. 새 lock JSON·lock ID·CAS 지원을 가정하지 않는다.
|
||||||
|
|
||||||
|
## 8. YAML 내부의 실제 변경 위치와 순서
|
||||||
|
|
||||||
|
1. **범위 고정:** 현행 배포 YAML을 기준으로 두 root, S2_00 자체 목표·출력, 필수 검증과 필요한 읽기 전용 자산을 확정한다. 다른 Stage와의 호환 검토를 하지 않는다.
|
||||||
|
2. **단일 실행 구조:** `Task_S2_00_prepare_request`를 제거한다. `task_procedure`는 `IN → Task_S2_00_deterministic_ingress → OUT`으로 바꾸고 ingress pointer를 `tasks/0`으로 정리한다.
|
||||||
|
3. **직접 입력 진입점:** `run_inline_mcp`·`_inline_hydrate`·`_inline_release_materialization_plan`을 두 root 입력으로 변경한다. `_inline_validate_request`, request 파일의 두 read-pass, prepare 관련 상수·입출력 설명을 제거한다. 실제 원본의 두 read-pass 안정성 확인은 필요한 입력 집합에 대해 유지한다.
|
||||||
|
4. **ID 의존 제거:** `execute_ingress`의 request용 `run_id`와 `attempt_id`, `_make_run_binding_receipt`의 요청/실행 ID 생성, `canonical_run_id`, CLI ID 옵션, staging/remote publisher·stdout의 ID 의존을 제거한다. 빈 문자열·고정 가짜 ID로 기존 signature를 통과시키지 않는다.
|
||||||
|
5. **자체 계약·자산 의존:** Agent description·metadata와 inline code에서 전체 Stage 2 workflow/binding/schema를 런타임 필수 계약으로 연결하는 부분을 S2_00 자체 계약으로 정리한다. C00–C10에 실제 필요한 읽기 전용 원본/schema/config 검증은 남긴다. 다른 Stage 자산을 유지하려고 전체 49개 closure를 복원하지 않는다.
|
||||||
|
6. **의미 보존과 출력:** adapter·pointer·review 처리의 실제 공백을 보완하고 C15를 자체 bundle·상태·출력 root로 바꾼다. 출력 validator, 파일 수·artifact-set 검사, publish/read-back, stdout을 함께 정합화한다.
|
||||||
|
7. **S2_00 한정 검증:** 아래 수용 기준으로 inline 함수를 시험하고 실제 전달 가능한 환경에서 단일 task를 확인한다. 다른 Stage 시험과 패키지 전체 재봉인은 개정 범위에 넣지 않는다.
|
||||||
|
|
||||||
|
향후 수정 파일은 해당 `Stage_2_S2_00.yml` 하나다. `Stage_2_S2_00_v.2.yml`, runtime mirror, 외부 workflow/binding/schema, manifest, builder는 함께 수정하지 않는다. 그 결과 기존 package builder의 parity나 parent release 봉인을 충족한다고 주장할 수 없다. 기존 builder를 돌려 이 YAML을 다시 덮어쓰는 것도 하지 않는다. 전체 패키지의 배포 승인과 S2_00 자체 기능 구현은 구별한다.
|
||||||
|
|
||||||
|
## 9. 실행 허용 조건과 미확인 사항
|
||||||
|
|
||||||
|
현재 release는 `DEV_FIXTURE_RELEASE`이며 현행 core에는 실제 사건 발행을 금지하는 `DEV_FIXTURE_REAL_RUN_FORBIDDEN` 방어가 있다. 후속 Agent와 무관한 인증·원본 신뢰 근거·S2_00 실행 허용 조건은 보존한다. 기존 전체 Stage 2 admission을 제거하면서 이를 임의의 `active:true`나 새 self-certified receipt로 대체하지 않는다.
|
||||||
|
|
||||||
|
YAML 한 파일 안에서 기존 근거로 독립 검증 가능한 S2_00 실행 조건을 명시한다. 현재 DEV 상태만으로는 offline fixture 검증까지만 가능하다. 실제 사건 발행에 필요한 승인·backend 기능이 외부 패키지 변경을 요구한다면 그 부분은 **현재 범위에서 live 미완료**로 기록한다. 다른 Stage의 준비 상태를 S2_00 내용 설계의 제약으로 다시 가져오거나, guard를 꺼 실제 사건 시험을 통과시키는 방법은 채택하지 않는다.
|
||||||
|
|
||||||
|
미확인 사항은 두 root의 실제 backend 결속, 인증 치환, 필요한 Stage 1 신뢰 근거의 완비, 같은 출력 root의 writer 직렬화, S2_00 독립 실행 허용 조건이다. 전략 작성이나 offline 함수 성공만으로 어느 것도 해결되었다고 기록하지 않는다.
|
||||||
|
|
||||||
|
## 10. 수용 기준과 검증 계획
|
||||||
|
|
||||||
|
아래는 향후 YAML 구현의 검증 계획이며 이번 작업에서 실행한 시험 결과가 아니다. 검증용 fixture·임시 출력은 임시 디렉터리를 사용하며 저장소에 다른 시험 자산을 새로 만들 필요는 없다.
|
||||||
|
|
||||||
|
| 검증 면 | 수용 기준 |
|
||||||
|
|---|---|
|
||||||
|
| 개정 범위 | 실제 수정 YAML은 S2_00 배포본 하나. S2_10~40·authoring·외부 자산 수정 없음 |
|
||||||
|
| 구조 | Agent stage 하나, ingress task·`run_code` 하나. DAG와 `tasks/0` pointer 일치 |
|
||||||
|
| 직접 전달 | 호출한 두 root가 실행 진입점에 그대로 도달. 경로 누락·미치환·탈출·출력/원본 중첩은 read/write 전에 차단 |
|
||||||
|
| 군더더기 제거 | prepare·고정 request 파일 read/write 0; `request_id` 입력·생성·출력 0; request를 대체하는 새 ID 0; `attempt_id` 필수 인자·출력 0 |
|
||||||
|
| 원본·검증 | Stage 1 원본 변경 0. 고정 16개·동적 signal 및 필요한 배포/schema 검증 수행. hash 불일치, manifest 탈출, 두 read-pass 변경 검출 |
|
||||||
|
| 자산 범위 | 실제 S2_00 사용 근거가 있는 자산만 읽음. 후속 YAML·LLM binding·후속 schema/route 계약을 요구하지 않음 |
|
||||||
|
| 내용 보존 | source pointer dereference 결과와 hash 일치. 사실·목표 제약·증거·신호·review·명시적 관계 누락 없음. 미상·blocking 유지 |
|
||||||
|
| 정상·차단 | 자체 상태와 실제 artifact 목록 일치. 차단 결과를 정상 context로 표현하지 않음. 원본에 없는 관계를 생성하지 않음 |
|
||||||
|
| 재실행·발행 | 동일 완료 결과만 재사용. 불일치·부분 충돌은 거절. 비 status write/read-back 실패 시 새 정상 status를 발행하지 않음. 기존 status 존재는 별도 확인 |
|
||||||
|
| 임시·동시 실행 | 임시 작업 디렉터리는 분리되고 출력에 임시 ID 없음. 원격 동시 발행 보장은 실증 전 미확인으로 기록 |
|
||||||
|
| live | 허용된 환경에서 root 결속·인증·원본 읽기·결과 write/read-back·마지막 status 확인. offline PASS와 구별 |
|
||||||
|
|
||||||
|
검증은 먼저 경로·ID 제거·정상/차단·원본 보존·pointer·재실행·발행 실패를 다루는 한 차례의 종합 offline 검증으로 수행한다. 수정이나 새 실패가 생겼을 때 해당 영향만 재확인한다. S2_10~40과의 E2E·호환 시험, 법률 판단의 타당성 승인, 전체 패키지 production readiness는 수용 기준에 넣지 않는다.
|
||||||
|
|
||||||
|
## 11. 효율성과 남는 비용
|
||||||
|
|
||||||
|
계획상 executor task는 2→1, 고정 request 파일은 1→0, 별도 request ID 생성·출력은 0으로 줄인다. prepare 호출, request 저장·read-back·재읽기, ID 호환 처리, 후속 자산의 불필요한 읽기를 제거하는 것이 절감 지점이다. 이미 Stage 1 결과를 직접 읽는 방식에 추가 ID 처리를 붙이는 것이 더 효율적이라고 주장하지 않는다.
|
||||||
|
|
||||||
|
S2_00은 비 LLM 작업이므로 ID 제거 자체가 큰 모델 토큰 절감을 보장하지 않는다. inline code의 전달량·billing과 필요한 원본 검증 비용은 남는다. 속도·비용·토큰 절감률은 실제 실행 요청 크기, 읽기 횟수, task 시간과 청구 값을 측정하기 전까지 미측정으로 둔다.
|
||||||
|
|
||||||
|
## 12. 작성 작업의 결정·검증 기록
|
||||||
|
|
||||||
|
| 결정 | 근거 | 결과 |
|
||||||
|
|---|---|---|
|
||||||
|
| 두 Stage 1 root 직접 전달과 request ID 전면 제거 | 이번 사용자 절대 원칙 | v1 §4.3의 내부 호환 ID 정책 폐기 |
|
||||||
|
| S2_00 YAML 한 파일을 향후 개정 대상으로 한정 | 사용자 범위 제한 | downstream 호환·외부 자산 재봉인 계획 제외 |
|
||||||
|
| 원본 의미·검증 유지, 자체 출력 축소 | S2_00의 C00–C15 기능 목표 | 전송 단순화와 기능 완결을 함께 검증 |
|
||||||
|
| 기존 root·자료 참조·hash 재사용 | 추가 요청 식별자를 만들지 않는 원칙 | 원본 보존, 출처 확인, 재실행 충돌 판별 가능 |
|
||||||
|
| backend·live 허용 조건 미확인 유지 | 현행 S2_00 진입점과 DEV guard | 전략·offline·live·패키지 승인 상태 구별 |
|
||||||
|
|
||||||
|
이번에는 이 전략서 생성과 지정 MEMORY 요약만 수행했다. 문서의 정확한 저장 경로·UTF-8·종결 LF, fence 6개·로컬 링크 5개, 사용자 원칙 반영과 요약 위치를 정적으로 확인했다. 작업 전 기준선 610개 파일 중 MEMORY를 제외한 기존 609개는 SHA-256이 동일했고, MEMORY도 요약 삽입 이외의 기존 내용은 보존했다. 새 파일은 이 전략서 하나다. YAML 개정, inline 실행, builder·release 재봉인, MCP/backend/live 시험, 배포·commit은 수행하지 않았다.
|
||||||
+221
@@ -0,0 +1,221 @@
|
|||||||
|
# Stage_2_S2_00.yml 개정 전략 v3
|
||||||
|
|
||||||
|
작성일: 2026-10-02. 상태: **전략서 작성 완료 / YAML 개정·실행 시험 미수행**.
|
||||||
|
|
||||||
|
## 1. 목표·산출물·범위
|
||||||
|
|
||||||
|
**Stage 1 사건·배포 root를 S2_00의 단일 ingress task에 직접 전달하고, Stage 1 결과물은 backend Python 실행 환경이 지원하는 `{{prev.###}}` 참조로 그대로 사용한다. `###`는 Stage 1의 결과물 파일이다. 원본을 검증·보존·정규화·묶음 구성·발행하는 것으로 S2_00을 완결하며, 별도의 `request_id`는 외부 입력, 내부 생성, 출력 모두에서 제거한다.**
|
||||||
|
|
||||||
|
이번 작업의 산출물은 이 전략서와 Stage 2 `MEMORY.md`의 작업 요약뿐이다. 실제 YAML·schema·runtime·manifest·builder·배포 자산은 수정하지 않는다. 향후 구현의 유일한 개정 대상도 이 폴더의 [Stage_2_S2_00.yml](Stage_2_S2_00.yml)이다. 다른 authoring 파일부터 수정하고 이 YAML을 재생성하는 v1의 절차는 이번 범위에 적용하지 않는다.
|
||||||
|
|
||||||
|
`Stage_2_S2_10.yml`, `Stage_2_S2_20.yml`, `Stage_2_S2_30.yml`, `Stage_2_S2_40.yml`의 자산 사용 schema, 인계 필드, route, 출력 수, admission·hash 호환 요구는 설계 기준과 수용 기준에서 제외한다. 해당 파일을 분석하거나 개정하는 작업도 포함하지 않는다. S2_00의 자체 입력·출력 계약과 검증은 개정 YAML의 metadata 및 inline code 안에 둔다.
|
||||||
|
|
||||||
|
S2_00의 현행 기능 목표인 C00 입력 검증, C05 보존·정규화, C10 claim-neutral cluster 구성, C15 묶음·결과 발행은 유지한다. 후속 Agent에 맞추기 위한 구조·필드의 보존은 목표에서 제거한다. `{{prev.###}}` 사용을 backend Python 실행 환경이 허용한다는 점은 이번 사용자 추가 설명에 따른 설계 전제로 채택한다. 이 문서의 함수 signature, 자체 출력 구조와 변경 순서는 구현 제안이며 이번 작업에서 실제 YAML을 개정하거나 backend 실행을 시험한 결과는 아니다.
|
||||||
|
|
||||||
|
## 2. 개정 기준과 v2에서 달라지는 전제
|
||||||
|
|
||||||
|
| 기준 자료 | 기준 내용 | v3에서의 사용 |
|
||||||
|
|---|---|---|
|
||||||
|
| [현행 S2_00 YAML](Stage_2_S2_00.yml) | Agent `Stage_2_S2_00_v2`, version `1.2.0`; prepare·ingress 두 task; inline C00–C15 | S2_00 내부 변경 위치와 보존할 처리 기능의 기준 |
|
||||||
|
| [현행 S2_00 분석서](Analysis_Stage_2_S2_00.md) | v2 작성 시 기준선: 기존 직접 인자 결속 미검증, 고정 request 파일, 정상/진단 발행, DEV release 제한 | 기존 구현의 기록. 결과물 참조 지원 여부는 이번 사용자 설명을 우선 적용 |
|
||||||
|
| [개정 전략 v2](stage_2_s2_00_revision_strategy_v2.md) | root 직접 전달·ID 제거·S2_00 한정 개정; backend 결속은 미확인으로 기술 | 본 개정의 원본. 직접 전달 원칙을 유지하고 `{{prev.###}}` 지원 전제를 반영 |
|
||||||
|
| 사용자 추가 설명 | Stage 1 결과물 파일을 `{{prev.###}}`로 사용할 수 있고 backend Python 실행 환경이 이를 허용 | 결과물 접근 방식의 설계 근거. 지원 여부 재조사를 개정 선행 조건으로 두지 않음 |
|
||||||
|
| [현행 release](../manifest/stage2_release.json) | Stage 1 고정 사건 입력 16개와 동적 signal family; Stage 1 배포 잠금 55개; Stage 2 직접 잠금 49개; `DEV_FIXTURE_RELEASE` | Stage 1 경로·adapter·검증 근거의 확인 자료. Stage 2 전체 의존 목록을 v3에 자동 승계하지 않음 |
|
||||||
|
|
||||||
|
기존 YAML·release의 설명은 v2에 기록된 기준선을 승계한다. 이번 문서 개정의 변경 근거는 사용자 추가 설명이며, 이를 별도의 live 실증 결과로 표시하지 않는다.
|
||||||
|
|
||||||
|
v2의 직접 전달·ID 제거·S2_00 한정 원칙은 유지한다. v2 §4의 backend 전달 방식 탐색, code 조립 대안, 직접 입력 지원 여부를 미확인으로 두는 전제는 `{{prev.###}}`의 지원을 채택하는 것으로 대체한다. v1에서 이미 폐기한 다음 전제도 되살리지 않는다.
|
||||||
|
|
||||||
|
- 내부 `request_id = "S2REQ-" + digest(...)` 생성과 stdout·receipt 출력은 전면 폐기한다.
|
||||||
|
- `attempt_id` UUID를 생성해 기존 core signature에 맞추는 방식도 폐기한다. 임시 작업 격리는 실행별 임시 디렉터리로 해결한다.
|
||||||
|
- 기존 `run_binding_receipt`, `S2RUN-...` 생성, `by-binding/<digest>` 출력 경로를 유지할 호환 의무를 제거한다. 무결성 hash는 실제 검증에 필요한 용도로만 사용한다.
|
||||||
|
- 기존 정상 11+E개·진단 5개 출력, S2_10용 bundle schema, S2_40용 status-only route를 수용 기준으로 삼지 않는다.
|
||||||
|
- Stage 2 직접 자산 49개 일괄 읽기와 다른 Stage의 hash closure 재봉인은 개정 절차에서 제외한다.
|
||||||
|
|
||||||
|
## 3. 절대 원칙과 완료 기준
|
||||||
|
|
||||||
|
1. 사건·배포 위치는 `stage1_run_root_ref`, `stage1_deployment_root_ref`로 직접 전달하고, Stage 1 결과물은 `{{prev.###}}`로 직접 사용한다. 기존 실행 정보와 결과 참조를 활용하며 호출자에게 별도 request나 결과 사본을 만들게 하지 않는다.
|
||||||
|
2. Stage 1 원본의 bytes·파일명·상대경로·원문 의미를 보존한다. 기존 사건/transaction 식별자가 원본에 있으면 출처 검증에 그대로 사용하며 새 요청 식별자로 재포장하지 않는다.
|
||||||
|
3. prepare task, 고정 request 파일 read/write, 별도의 ID 발급·저장·출력을 제거한다. `request_id`를 이름만 바꾼 `handoff_id`·`execution_id`도 만들지 않는다.
|
||||||
|
4. S2_00은 비 LLM deterministic task 하나로 수행한다. 입력 본문 전체를 prompt에 복사하거나 LLM task를 추가하지 않는다.
|
||||||
|
5. 검증은 S2_00의 실제 입력·연산·출력에 필요한 것으로 한정한다. 원본의 무결성·출처·필수 내용·blocking review를 생략하여 단순화하지 않는다.
|
||||||
|
6. 정상 결과는 원본을 추적할 수 있고 미해결 사항을 보존해야 한다. 입력 전달 성공만으로 S2_00의 기능 목표 달성을 선언하지 않는다.
|
||||||
|
|
||||||
|
완료는 **두 root 직접 전달·`{{prev.###}}` 결과 참조의 실제 해석 → 원본 사용과 필요한 검증 → 의미 보존·정규화·cluster/bundle 구성 → 자체 출력 검증 → 마지막 status 발행**이 한 task에서 성립하는 것으로 판정한다. 기존 S2_10~40에서 읽을 수 있는지는 판정 대상이 아니다.
|
||||||
|
|
||||||
|
## 4. `{{prev.###}}` 결과 사용과 직접 실행 경계
|
||||||
|
|
||||||
|
### 4.1 backend 지원을 채택하는 설계 전제
|
||||||
|
|
||||||
|
Stage 2 S2_00은 Stage 1 결과물을 아래 형식으로 사용할 수 있다.
|
||||||
|
|
||||||
|
```text
|
||||||
|
{{prev.###}}
|
||||||
|
### = Stage 1의 결과물 파일
|
||||||
|
```
|
||||||
|
|
||||||
|
**이 사용은 backend의 Python code 실행 환경이 허용한다.** 이는 사용자 추가 설명을 반영한 설계 전제다. 따라서 v2의 “`prev` 지원을 가정하지 않는다”, “backend 전달 방식을 먼저 확인한다”, “별도 code 조립 대안을 검토한다”는 접근은 적용하지 않는다. 지원 여부 확인을 개정 착수의 장애나 prepare task 부활의 이유로 삼지 않는다.
|
||||||
|
|
||||||
|
단순 파일명에 적용하면 `{{prev.evidence_indexed.json}}`, `{{prev.BO.json}}`, `{{prev.Fact_Ledger_base.json}}`과 같은 형태다. 이 예시는 사용자가 제시한 형식에 실제 파일명을 대입한 것이다. `###`를 task 이름·새 ID로 바꾸지 않는다. 하위 폴더 파일과 동적 signal의 경우 실제 Stage 1 결과물에 등록된 파일 참조를 그대로 사용하고, 문서에서 임의의 별칭이나 새로운 참조 문법을 만들지 않는다.
|
||||||
|
|
||||||
|
`prev` 표현식을 일반 Python 문법으로 직접 평가하는 것은 아니다. backend가 해당 참조를 해석하여 Python 실행에 제공하는 결과물을 ingress에서 사용하는 경계다. 이 문서의 예시만으로 `prev`라는 Python 객체·신규 resolver API·임의 `run_code.arguments` 파라미터를 구현 사실로 가정하지 않는다.
|
||||||
|
|
||||||
|
### 4.2 두 root와 파일 참조의 역할
|
||||||
|
|
||||||
|
두 root는 원본 위치·배포 검증·출처 기록·출력 위치 결정에 그대로 사용한다. 위치 정보의 형태는 다음과 같다. 이는 파일로 저장할 request가 아니라 기존 실행 정보의 직접 전달 값이다.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"stage1_run_root_ref": "<Stage 1 사건 결과의 workspace 상대 root>",
|
||||||
|
"stage1_deployment_root_ref": "<그 결과에 대응하는 Stage 1 배포의 workspace 상대 root>"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`{{prev.###}}`는 이 위치 정보와 함께 실제 Stage 1 결과물 파일을 사용하는 방식이다. 파일 참조 사용을 위해 별도 요청 ID를 발급하거나 caller에게 파일 목록·본문을 새 request로 재작성하게 하지 않는다. backend에서 제공된 원본 결과를 직접 사용하고, root에서 파일을 다시 찾는 별도 중계 절차로 되돌리지 않는다.
|
||||||
|
|
||||||
|
`W/`는 인증된 localdocs 논리 workspace root, `U=W/<stage1_run_root_ref>/`, `D=W/<stage1_deployment_root_ref>/`다. Dropbox 절대경로나 executor 임시 mount와 동일한 것으로 취급하지 않는다. 인증 정보는 기존 backend 세션을 사용하며 호출자가 임의 workspace를 지정하게 하지 않는다. 배포 자산은 `D/`에서 필요한 항목만 읽기 전용으로 검증한다.
|
||||||
|
|
||||||
|
### 4.3 단일 Python ingress의 내부 경계
|
||||||
|
|
||||||
|
권고 내부 함수 경계는 다음과 같다. `stage1_results`는 backend가 `{{prev.###}}`로 제공한 결과를 ingress 안에서 다루는 메모리 값이며 별도 request 파일·새 호출자 준비 산출물이 아니다.
|
||||||
|
|
||||||
|
```python
|
||||||
|
run_inline_mcp(stage1_run_root_ref, stage1_deployment_root_ref, *, stage1_results, client=None)
|
||||||
|
validate_direct_inputs(stage1_run_root_ref, stage1_deployment_root_ref, stage1_results)
|
||||||
|
hydrate_stage1(localdocs, temp_root, validated_roots, stage1_results)
|
||||||
|
execute_ingress(stage1_root, stage1_deployment_root, *, output_dir, source_checks)
|
||||||
|
publish_result(localdocs, output_root, files, status)
|
||||||
|
```
|
||||||
|
|
||||||
|
backend에서 제공되는 실제 결과 표현에 따라 기존 adapter를 연결한다. 파일 경로/참조가 제공되면 해당 원본 파일을 사용하고, 파일 bytes나 파싱된 결과가 제공되면 그 값을 재사용한다. 표현 형식을 문서에서 하나로 단정하지 않는다. 파싱된 객체만으로 원본 raw bytes hash가 검증되었다고 주장하지 않으며, 검증에 필요한 원본 bytes가 없을 때만 기존 원본 경로에서 보완한다. 이미 제공된 원본을 불필요하게 다시 읽거나 직렬화하여 사본을 만들지 않는다.
|
||||||
|
|
||||||
|
별도의 request 객체를 core에 요구하지 않는다. core의 `run_id="S2-00-REQUEST"`, `attempt_id` 필수 인자와 ID 정규식 검사는 삭제하고 필요한 경로·검증 객체만 전달한다. 내부 함수 분리는 유지할 수 있으나 추가 executor task로 나누지 않는다. 원문 결과에 따옴표·개행이 포함되어도 데이터로 보존하며 원문을 Python 소스 문자열에 직접 끼워 넣지 않는다.
|
||||||
|
|
||||||
|
두 root는 canonical NFC 상대경로인지 검사한다. 누락·빈 값·절대경로·`..`·NUL·경로 탈출·알 수 없는 입력 필드는 원본 읽기나 출력 쓰기 전에 거절한다. 지원되는 `{{prev.###}}` 표현식 자체를 오류로 판단하지 않는다. backend 해석 이후에도 필수 참조가 미치환 상태이거나 결과물이 누락·잘못 대응된 경우에는 원본으로 사용하지 않고 진단한다. Stage 1 완료 여부는 폴더 존재만으로 판단하지 않고 기존 writer report·gate/handoff와 실제 제공된 결과 간 결속을 확인한다. 기존 완료/seal 정보가 없으면 없다고 기록하며 새 Stage 1 파일을 요구하지 않는다.
|
||||||
|
|
||||||
|
참조 지원은 채택한 전제이며, 각 결과물의 정확한 결속·원본 동일성·한 task 내 발행 성공은 향후 구현 검증 항목이다. 이번 전략서 작성은 그 실행 검증을 수행하지 않는다. prepare/request 파일·새 ID·backend 개정을 추가하지 않고 기존 실행 환경 안에서 S2_00 YAML만 개정한다.
|
||||||
|
|
||||||
|
## 5. Stage 1 원본 재사용과 필요한 자산만 읽기
|
||||||
|
|
||||||
|
현행 고정 사건 입력 16개를 `{{prev.###}}`로 그대로 사용한다. 아래 경로는 원본의 `U/` 기준 상대경로이며, 실제 backend 파일 참조와 원본 출처를 대응하는 기준이다. 이 표를 새 request 파일이나 결과 사본으로 저장하지 않는다.
|
||||||
|
|
||||||
|
| 묶음 | 원본 상대경로 |
|
||||||
|
|---|---|
|
||||||
|
| P1 3개 | `evidence_indexed.json`, `evidence_event_candidates.json`, `client_goal.json` |
|
||||||
|
| P1 routing 2개 | `routing/domain_screening.json`, `routing/domain_activation_manifest.json` |
|
||||||
|
| P1 gate/handoff 3개 | `quality_gates/B1_evidence_indexed_gate.json`, `quality_gates/B2_event_candidates_gate.json`, `quality_gates/stage1_part1_soft_gate_handoff.json` |
|
||||||
|
| P2 3개 | `BO.json`, `signals/signal_manifest.json`, `quality_gates/stage1_part2_review_handoff.json` |
|
||||||
|
| P3 2개 | `legal_effect_structures.json`, `quality_gates/stage1_part3_review_handoff.json` |
|
||||||
|
| P4 3개 | `Fact_Ledger_base.json`, `stage1_tmp/fact_ledger/fact_ledger_writer_report.json`, `quality_gates/stage1_part4_review_handoff.json` |
|
||||||
|
|
||||||
|
signal manifest 자체를 Stage 1 결과 참조로 사용하고, payload는 기존 `signal_manifest.files[]`의 경로·hash·조건에 해당하는 실제 결과물 참조를 따라 사용한다. 필요한 원본 bytes 접근만 보완한다. 폴더 전체 scan이나 추측한 파일 목록으로 대체하지 않는다. routing activation과 signal activation은 서로 다른 원본으로 취급하며 실제 의미 일치 여부를 확인한다.
|
||||||
|
|
||||||
|
Stage 1 배포의 현행 55개 잠금 목록은 검증 근거의 기준선이다. v3에서는 각 자산이 사건 JSON 해석, schema 검증, producer/alias·domain 연결, 원본 identity 검증 중 어디에서 필요한지 S2_00 내부에서 추적한다. 필요 자산과 그 검증 의존은 유지하고, 미사용임이 확인된 자산만 읽기 대상에서 제외한다. 검토 전부터 특정 감소 개수를 약속하지 않는다. Stage 1 원본이나 배포 파일을 고쳐 개정에 맞추지 않는다.
|
||||||
|
|
||||||
|
Stage 2의 현행 49개 직접 의존은 후속 작업을 위한 결속까지 포함하므로 전체를 유지하지 않는다. S2_10 YAML·LLM binding과 후속 authority/retrieval 선택용 자산은 S2_00 의존에서 제거한다. registry/config도 C05~C10에서 실제 사용하는 내용만 남기고, 남기는 이유·경로·검증 hash를 YAML 내부의 단일 자산 표에 둔다. 필요한 비실행 데이터는 읽기 전용으로 참조할 수 있으나 그 파일을 개정하거나 새 외부 schema를 만들지 않는다. 문서 안의 법률 규칙을 새로 작성하는 작업도 포함하지 않는다.
|
||||||
|
|
||||||
|
자체 입력 adapter·출력 validator·경로 목록의 정본은 개정 YAML 안에서 하나로 관리한다. 기존 전체 release·module·downstream schema를 그대로 validator에 주입해 제거한 필드를 다시 요구하게 해서는 안 된다. 외부 참조가 있는 기존 schema는 실제 S2_00에 필요한 규칙과 참조 의존만 inline 자체 계약에 반영한다. 이것은 후속 호환 검사 제거이며 Stage 1 원본 검증의 면제는 아니다.
|
||||||
|
|
||||||
|
## 6. C00–C15의 자체 기능 목표
|
||||||
|
|
||||||
|
| 논리 단계 | 유지할 작업 | 단순화·보완 방향 |
|
||||||
|
|---|---|---|
|
||||||
|
| C00 | Stage 1 결과 참조·배포 원본, 필수 입력·hash·producer/transaction·gate/review 확인 | 결과물은 `{{prev.###}}`로 사용하고 두 root로 출처·배포를 확인. request/ID 검증과 downstream 자산 admission은 제거. hash 근거가 없는 입력은 무근거 상태를 보존 |
|
||||||
|
| C05 | 증거·사실·객체·당사자·목표·LES·signal의 원본 보존과 필요한 정규화 | 원문을 덮어쓰지 않고 필요한 view만 생성. source path·정확한 JSON pointer·raw hash로 원문 연결 |
|
||||||
|
| C10 | 원본의 명시적 관계에 따른 claim-neutral cluster와 작업 범위 구성 | 확인된 fact/evidence/BO/LES/signal 관계만 사용. 빈 slot skeleton이나 근거 없는 관계를 채우지 않음 |
|
||||||
|
| C15 | cluster별 입력 묶음·미해결 항목·결과 검증과 발행 | 후속 Agent prompt·dispatch schema 대신 자체 bundle 표현. S2_10/40 route를 자체 처리 상태로 대체 |
|
||||||
|
|
||||||
|
adapter가 wrapper/배열 위치를 잘못 해석하여 원문 pointer가 틀리거나 사실·client goal·signal·review를 축약해 의미를 잃는 문제는 S2_00 inline code 안에서 보완한다. 원문 사실, 목표 제약, 반대 자료, blocking/unresolved review, 기존 명시적 연결을 보존한다. `UNEVALUABLE`·미상은 원본 근거 없이 해결된 상태로 변경하지 않는다.
|
||||||
|
|
||||||
|
원본의 fact/evidence/object/transaction ID는 의미와 참조를 구성하는 기존 값이므로 유지한다. cluster의 구조상 식별값이 필요한 경우에도 해당 묶음 참조 용도에 한정한다. 이러한 자료 식별값을 요청 ID나 실행 ID 발급의 근거로 사용하지 않는다.
|
||||||
|
|
||||||
|
## 7. ID 없는 출력·재실행·임시 작업
|
||||||
|
|
||||||
|
최종 출력 root는 원본 사건 root를 재사용하여 다음처럼 결정한다.
|
||||||
|
|
||||||
|
```text
|
||||||
|
O = W/stage2_runs/from-stage1/<stage1_run_root_ref>/s2_00/
|
||||||
|
```
|
||||||
|
|
||||||
|
검증된 상대 root의 경로 구성을 그대로 아래에 결합한다. 새 요청 ID, UUID, `run_binding_digest`로 출력 폴더를 만들지 않는다. `O/`가 `U/` 또는 `D/`와 겹치거나 어느 한쪽 안에 놓이는 경우는 쓰기 전에 거절한다. Stage 1 원본 트리는 읽기 전용으로 유지한다.
|
||||||
|
|
||||||
|
권고 정상 출력은 다음 5개이며 cluster 수가 크거나 부분 읽기가 필요한 경우에만 가변 slice를 분리한다. 기존 11+E개 파일을 모두 보존하기 위한 빈 파일은 만들지 않는다.
|
||||||
|
|
||||||
|
| 파일 | 필요한 내용 |
|
||||||
|
|---|---|
|
||||||
|
| `ingress/stage1_input_manifest.json` | 두 root, 읽은 원본 상대경로·raw hash·검증 근거, 기존 원본 identity가 있으면 그 값 |
|
||||||
|
| `ingress/intake_report.json` | 필수 입력·무결성·gate 확인 결과와 누락/미검증 항목 |
|
||||||
|
| `review/issue_ledger.base.json` | 원본 review의 내용·범위·blocking 여부·출처와 S2_00 처리 중 발견한 문제 |
|
||||||
|
| `context/case_context.json` | 정규화 view·원본 참조, 증거/객체/당사자/LES/사실/목표/signal 관계, cluster 목록과 bundle별 원본 참조. 분리 slice 사용 시 해당 경로 |
|
||||||
|
| `ingress/ingress_status.json` | `READY`, `READY_WITH_ISSUES`, `BLOCKED` 중 상태, 두 root, algorithm version, 실제 발행 파일 경로·hash. 마지막에 발행 |
|
||||||
|
|
||||||
|
가변 파일은 필요한 경우에만 `context/cluster_slices/<cluster_ref>.json`으로 분리한다. `<cluster_ref>`는 자료 묶음 참조이며 요청/실행 ID가 아니다. 한 내용은 context 또는 slice 중 한 곳에 담고 다른 곳에서는 참조하여 중복을 줄인다. 원본 전체 사본을 새 context에 복제하지 않는다.
|
||||||
|
|
||||||
|
안전하게 root를 확정하고 입력 검사를 수행한 뒤 발생한 차단은 `BLOCKED`로 기록한다. 이때 정상 context/slice는 발행하지 않으며 intake·issue ledger·technical diagnostic과 마지막 status를 발행한다. 입력 목록을 확정할 수 있을 때만 manifest도 발행한다. 입력 인자·인증 실패처럼 출력 위치를 안전하게 확정하지 못한 오류는 stdout 오류로 종료하고 결과 파일 발행을 강제하지 않는다.
|
||||||
|
|
||||||
|
모든 출력의 닫힌 필드·상태별 required/forbidden artifact 규칙은 inline validator에서 정의한다. 기존 `run_binding_receipt`와 출력 schema의 `request_id`·신규 `run_id` 필수 조건은 함께 제거한다. stdout은 처리 성공 여부·상태·출력 경로·오류·마지막 status hash 정도만 담는다. 별도 ID receipt는 만들지 않는다.
|
||||||
|
|
||||||
|
원본 raw hash와 산출물 hash는 변조·내용 동일성 확인을 위해 유지한다. 이 hash를 접두사와 결합하여 요청 식별자로 재출력하지 않는다. 동일 `O/`가 이미 있으면 기존 입력 경로·hash, 배포 근거, algorithm version과 발행 파일을 확인하고 일치하는 완료 결과만 재사용한다. 내용·버전이 다르거나 잔여 부분 파일이 충돌하면 덮어쓰지 않고 명시적으로 종료한다. 이번 전략은 여러 개정 버전의 결과를 같은 사건 root 아래 동시에 보관하는 새 체계를 도입하지 않는다.
|
||||||
|
|
||||||
|
실행별 `TemporaryDirectory` 안에서 hydration·로컬 결과·staging을 처리한다. 임시 디렉터리의 고유한 이름은 도구 내부 자원이며 호출 필드, canonical output, stdout에 ID로 노출하지 않는다. `publish_atomically(..., attempt_id=...)`의 의존도 함께 제거한다.
|
||||||
|
|
||||||
|
원격 발행은 비 status 파일 write/read-back 이후 status를 마지막에 기록하는 논리적 완료 경계로 유지한다. 임시 디렉터리가 원격 동시 writer 문제까지 해결한다고 주장하지 않는다. 같은 `O/`의 동시 writer는 기존 host 직렬화 기능을 사용할 수 있는지 확인하며, 미지원·미확인 상태에서는 동시 발행 지원을 수용 완료로 기록하지 않는다. 새 lock JSON·lock ID·CAS 지원을 가정하지 않는다.
|
||||||
|
|
||||||
|
## 8. YAML 내부의 실제 변경 위치와 순서
|
||||||
|
|
||||||
|
1. **범위 고정:** 두 root 직접 전달과 backend가 허용하는 `{{prev.###}}` 결과 사용을 전제로 S2_00 자체 목표·출력, 필수 검증과 필요한 읽기 전용 자산을 확정한다. 다른 Stage와의 호환 검토를 하지 않는다.
|
||||||
|
2. **단일 실행 구조:** `Task_S2_00_prepare_request`를 제거한다. `task_procedure`는 `IN → Task_S2_00_deterministic_ingress → OUT`으로 바꾸고 ingress pointer를 `tasks/0`으로 정리한다.
|
||||||
|
3. **직접 결과 사용 진입점:** `run_inline_mcp`·`_inline_hydrate`·`_inline_release_materialization_plan`을 두 root와 backend가 제공한 `{{prev.###}}` 결과를 사용하도록 변경한다. 파일 참조와 원본 source pointer를 대응시킨다. `_inline_validate_request`, request 파일의 두 read-pass, prepare 관련 상수·입출력 설명을 제거한다. 실제 원본의 두 read-pass 안정성 확인은 필요한 입력 집합에 대해 유지하되 결과 참조의 해석을 두 번 수행하는 것으로 대체하지 않는다.
|
||||||
|
4. **ID 의존 제거:** `execute_ingress`의 request용 `run_id`와 `attempt_id`, `_make_run_binding_receipt`의 요청/실행 ID 생성, `canonical_run_id`, CLI ID 옵션, staging/remote publisher·stdout의 ID 의존을 제거한다. 빈 문자열·고정 가짜 ID로 기존 signature를 통과시키지 않는다.
|
||||||
|
5. **자체 계약·자산 의존:** Agent description·metadata와 inline code에서 전체 Stage 2 workflow/binding/schema를 런타임 필수 계약으로 연결하는 부분을 S2_00 자체 계약으로 정리한다. C00–C10에 실제 필요한 읽기 전용 원본/schema/config 검증은 남긴다. 다른 Stage 자산을 유지하려고 전체 49개 closure를 복원하지 않는다.
|
||||||
|
6. **의미 보존과 출력:** adapter·pointer·review 처리의 실제 공백을 보완하고 C15를 자체 bundle·상태·출력 root로 바꾼다. 출력 validator, 파일 수·artifact-set 검사, publish/read-back, stdout을 함께 정합화한다.
|
||||||
|
7. **S2_00 한정 검증:** 아래 수용 기준으로 inline 함수를 시험하고 실제 전달 가능한 환경에서 단일 task를 확인한다. 다른 Stage 시험과 패키지 전체 재봉인은 개정 범위에 넣지 않는다.
|
||||||
|
|
||||||
|
향후 수정 파일은 해당 `Stage_2_S2_00.yml` 하나다. `Stage_2_S2_00_v.2.yml`, runtime mirror, 외부 workflow/binding/schema, manifest, builder는 함께 수정하지 않는다. 그 결과 기존 package builder의 parity나 parent release 봉인을 충족한다고 주장할 수 없다. 기존 builder를 돌려 이 YAML을 다시 덮어쓰는 것도 하지 않는다. 전체 패키지의 배포 승인과 S2_00 자체 기능 구현은 구별한다.
|
||||||
|
|
||||||
|
## 9. 실행 허용 조건과 미확인 사항
|
||||||
|
|
||||||
|
현재 release는 `DEV_FIXTURE_RELEASE`이며 현행 core에는 실제 사건 발행을 금지하는 `DEV_FIXTURE_REAL_RUN_FORBIDDEN` 방어가 있다. 후속 Agent와 무관한 인증·원본 신뢰 근거·S2_00 실행 허용 조건은 보존한다. 기존 전체 Stage 2 admission을 제거하면서 이를 임의의 `active:true`나 새 self-certified receipt로 대체하지 않는다.
|
||||||
|
|
||||||
|
YAML 한 파일 안에서 기존 근거로 독립 검증 가능한 S2_00 실행 조건을 명시한다. 현재 DEV 상태만으로는 offline fixture 검증까지만 가능하다. 실제 사건 발행에 필요한 승인·참조 지원 이외의 실행 조건이 외부 패키지 변경을 요구한다면 그 부분은 **현재 범위에서 live 미완료**로 기록한다. 다른 Stage의 준비 상태를 S2_00 내용 설계의 제약으로 다시 가져오거나, guard를 꺼 실제 사건 시험을 통과시키는 방법은 채택하지 않는다.
|
||||||
|
|
||||||
|
`{{prev.###}}` 결과물 사용을 backend Python 실행 환경이 허용한다는 점은 사용자 설명으로 채택했으므로 미지원·지원 여부 미확인으로 기록하지 않는다. 이번 작성에서 실행 확인하지 않은 항목은 실제 파일 참조·두 root의 올바른 대응, 인증 치환, 필요한 Stage 1 신뢰 근거의 완비, 같은 출력 root의 writer 직렬화, S2_00 독립 실행 허용 조건이다. 이는 지원 기능의 존재와 그 기능을 사용한 개정 YAML의 실행 검증을 구별한 것이다.
|
||||||
|
|
||||||
|
## 10. 수용 기준과 검증 계획
|
||||||
|
|
||||||
|
아래는 향후 YAML 구현의 검증 계획이며 이번 작업에서 실행한 시험 결과가 아니다. 검증용 fixture·임시 출력은 임시 디렉터리를 사용하며 저장소에 다른 시험 자산을 새로 만들 필요는 없다.
|
||||||
|
|
||||||
|
| 검증 면 | 수용 기준 |
|
||||||
|
|---|---|
|
||||||
|
| 개정 범위 | 실제 수정 YAML은 S2_00 배포본 하나. S2_10~40·authoring·외부 자산 수정 없음 |
|
||||||
|
| 구조 | Agent stage 하나, ingress task·`run_code` 하나. DAG와 `tasks/0` pointer 일치 |
|
||||||
|
| 직접 전달·결과 참조 | 두 root가 실행 진입점에 그대로 도달하고 `{{prev.###}}`가 의도한 Stage 1 결과물로 해석됨. 지원 표현식은 허용하며 해석 후 필수 미치환·누락·잘못된 파일 대응·경로 탈출·출력/원본 중첩은 사용·쓰기 전에 차단 |
|
||||||
|
| 군더더기 제거 | prepare·고정 request 파일 read/write 0; `request_id` 입력·생성·출력 0; request를 대체하는 새 ID 0; `attempt_id` 필수 인자·출력 0 |
|
||||||
|
| 원본·검증 | Stage 1 원본 변경 0. 참조로 제공된 고정 16개·동적 signal과 원본 경로/pointer의 대응, 필요한 배포/schema 검증 수행. 원문 따옴표·개행 보존, raw hash 불일치·manifest 탈출·필요한 두 read-pass 변경 검출 |
|
||||||
|
| 자산 범위 | 실제 S2_00 사용 근거가 있는 자산만 읽음. 후속 YAML·LLM binding·후속 schema/route 계약을 요구하지 않음 |
|
||||||
|
| 내용 보존 | source pointer dereference 결과와 hash 일치. 사실·목표 제약·증거·신호·review·명시적 관계 누락 없음. 미상·blocking 유지 |
|
||||||
|
| 정상·차단 | 자체 상태와 실제 artifact 목록 일치. 차단 결과를 정상 context로 표현하지 않음. 원본에 없는 관계를 생성하지 않음 |
|
||||||
|
| 재실행·발행 | 동일 완료 결과만 재사용. 불일치·부분 충돌은 거절. 비 status write/read-back 실패 시 새 정상 status를 발행하지 않음. 기존 status 존재는 별도 확인 |
|
||||||
|
| 임시·동시 실행 | 임시 작업 디렉터리는 분리되고 출력에 임시 ID 없음. 원격 동시 발행 보장은 실증 전 미확인으로 기록 |
|
||||||
|
| live | 기존 backend Python 실행 환경에서 `{{prev.###}}` 결과 사용·두 root 대응·인증·원본 검증·결과 write/read-back·마지막 status 확인. 채택된 지원 전제, offline PASS, 실제 실행 결과를 구별 |
|
||||||
|
|
||||||
|
검증은 먼저 경로·결과 참조 대응·참조 누락/미치환·ID 제거·정상/차단·원본 보존·pointer·재실행·발행 실패를 다루는 한 차례의 종합 offline 검증으로 수행한다. 수정이나 새 실패가 생겼을 때 해당 영향만 재확인한다. S2_10~40과의 E2E·호환 시험, 법률 판단의 타당성 승인, 전체 패키지 production readiness는 수용 기준에 넣지 않는다.
|
||||||
|
|
||||||
|
## 11. 효율성과 남는 비용
|
||||||
|
|
||||||
|
계획상 executor task는 2→1, 고정 request 파일은 1→0, 별도 request ID 생성·출력은 0으로 줄인다. prepare 호출, request 저장·read-back·재읽기, ID 호환 처리, 후속 자산의 불필요한 읽기를 제거하고 `{{prev.###}}`로 이미 제공된 Stage 1 결과를 재사용하는 것이 절감 지점이다. 이미 Stage 1 결과를 직접 읽는 방식에 추가 ID 처리를 붙이는 것이 더 효율적이라고 주장하지 않는다.
|
||||||
|
|
||||||
|
S2_00은 비 LLM 작업이므로 ID 제거 자체가 큰 모델 토큰 절감을 보장하지 않는다. inline code의 전달량·billing과 필요한 원본 검증 비용은 남는다. 속도·비용·토큰 절감률은 실제 실행 요청 크기, 읽기 횟수, task 시간과 청구 값을 측정하기 전까지 미측정으로 둔다.
|
||||||
|
|
||||||
|
## 12. 작성 작업의 결정·검증 기록
|
||||||
|
|
||||||
|
| 결정 | 근거 | 결과 |
|
||||||
|
|---|---|---|
|
||||||
|
| 두 Stage 1 root 직접 전달과 request ID 전면 제거 | 이번 사용자 절대 원칙 | v1 §4.3의 내부 호환 ID 정책 폐기 |
|
||||||
|
| S2_00 YAML 한 파일을 향후 개정 대상으로 한정 | 사용자 범위 제한 | downstream 호환·외부 자산 재봉인 계획 제외 |
|
||||||
|
| 원본 의미·검증 유지, 자체 출력 축소 | S2_00의 C00–C15 기능 목표 | 전송 단순화와 기능 완결을 함께 검증 |
|
||||||
|
| 기존 root·자료 참조·hash 재사용 | 추가 요청 식별자를 만들지 않는 원칙 | 원본 보존, 출처 확인, 재실행 충돌 판별 가능 |
|
||||||
|
| `{{prev.###}}`와 backend Python 실행 허용 채택 | 이번 사용자 추가 설명 | v2의 지원 여부 미확인·전달 방식 탐색 전제를 대체 |
|
||||||
|
| 참조·root 대응과 live 허용 조건은 구현 시 검증 | 실제 YAML 개정·실행 시험은 이번 범위 밖 | 지원 전제·전략·offline·live·패키지 승인 상태 구별 |
|
||||||
|
|
||||||
|
이번에는 v2를 보존하고 이 v3 전략서를 생성하며 지정 MEMORY에 요약을 삽입했다. v2의 직접 전달·ID 제거·S2_00 한정 원칙을 유지하고, `{{prev.###}}` 사용 및 backend Python 실행 환경의 허용을 목표·입력 경계·원본 사용·변경 순서·수용 기준·상태 기록에 반영했다. 저장 후 UTF-8·종결 LF·fence 8개·로컬 링크 5개, 이전 미확인 전제의 제거와 관련 절 간 정합성, 요약 위치를 정적으로 확인했다. 작업 전 기존 파일 611개 중 MEMORY를 제외한 610개의 SHA-256이 동일하며, MEMORY도 이번 요약 삽입 외의 내용은 보존했다. 새 파일은 이 전략서 하나다. 실제 YAML 개정, inline 실행, builder·release 재봉인, MCP/backend/live 시험, 배포·commit은 수행하지 않았다.
|
||||||
+37
-25
@@ -9,30 +9,42 @@ stage_bindings:
|
|||||||
agent_script_ref:
|
agent_script_ref:
|
||||||
asset_id: AGENT-S2_00-INLINE
|
asset_id: AGENT-S2_00-INLINE
|
||||||
path: agent_scripts/Stage_2_S2_00.yml
|
path: agent_scripts/Stage_2_S2_00.yml
|
||||||
sha256: 94c2744d71d222cfb5cc1b2a0d33a6cda20079439823de431eef378a2fa5c943
|
sha256: 07bc9e235d211f3d4e6886bca2faeeb1f112d525fa29fe111bd7c50c42ff9849
|
||||||
schema_id: liti_agent_yaml.v1
|
schema_id: liti_agent_yaml.v1
|
||||||
binding_status: BOUND
|
binding_status: BOUND
|
||||||
workflow_contract_ref:
|
workflow_contract_ref:
|
||||||
asset_id: WF-S2_00
|
asset_id: WF-S2_00
|
||||||
path: workflows/S2_00_stage1_ingress_normalize_and_bundle_compile.yml
|
path: workflows/S2_00_stage1_ingress_normalize_and_bundle_compile.yml
|
||||||
sha256: 3d7ceb86b236668c8c372136f1d1b43a5d0a79d3fb05fd1ef4342bb638190f68
|
sha256: 9b606ef4ddc7859591d9455fd9cdf9dfe6ad2533824636deb727b82e035f503a
|
||||||
schema_id: stage2_workflow_contract.v1.2
|
schema_id: stage2_workflow_contract.v1.2
|
||||||
binding_status: BOUND
|
binding_status: BOUND
|
||||||
inline_code_receipt_ref:
|
inline_code_receipt_ref:
|
||||||
asset_id: RECEIPT-S2_00-INLINE-CODE
|
asset_id: RECEIPT-S2_00-INLINE-CODE
|
||||||
path: manifest/s2_00_inline_code_receipt.json
|
path: manifest/s2_00_inline_code_receipt.json
|
||||||
sha256: 36af21949e5ef53a3d39df8ea9491c64da43abcc4f5e42e13db9bb391da843b8
|
sha256: be044ab40ee86e7c3a1be6c421b2c5d36ee67fc2fd2870f59a3d9d911fef6c1a
|
||||||
schema_id: stage2_s2_00_inline_code_receipt.v2
|
schema_id: stage2_s2_00_inline_code_receipt.v2
|
||||||
binding_status: BOUND
|
binding_status: BOUND
|
||||||
stage2_release_ref:
|
stage2_release_ref:
|
||||||
asset_id: RELEASE-STAGE2-CLEAN
|
asset_id: RELEASE-STAGE2-CLEAN
|
||||||
path: manifest/stage2_release.json
|
path: manifest/stage2_release.json
|
||||||
sha256: 9fa85bb94c5f14f675dcdaa0cf94d06745b21675d97797539ffc14015dcc9f53
|
sha256: 2363f1166ee8a3ea2b38350cde61fccfc9852a413c6b9f14a6ed6606af15a590
|
||||||
schema_id: stage2_release.v2
|
schema_id: stage2_release.v2
|
||||||
binding_status: BOUND
|
binding_status: BOUND
|
||||||
expected_release_sha256: 9fa85bb94c5f14f675dcdaa0cf94d06745b21675d97797539ffc14015dcc9f53
|
expected_release_sha256: 2363f1166ee8a3ea2b38350cde61fccfc9852a413c6b9f14a6ed6606af15a590
|
||||||
agent_script_sha256: 94c2744d71d222cfb5cc1b2a0d33a6cda20079439823de431eef378a2fa5c943
|
agent_script_sha256: 07bc9e235d211f3d4e6886bca2faeeb1f112d525fa29fe111bd7c50c42ff9849
|
||||||
canonical_code_sha256: e3f87725d5a6496189e7c411ef940dd8a7b3a9ff5b00535803bf98fa0cc4373e
|
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
|
mcp_server_id: code-executor
|
||||||
tool_name: run_code
|
tool_name: run_code
|
||||||
language: python
|
language: python
|
||||||
@@ -86,7 +98,7 @@ stage_bindings:
|
|||||||
agent_script_ref:
|
agent_script_ref:
|
||||||
asset_id: AGENT-S2_20-INLINE
|
asset_id: AGENT-S2_20-INLINE
|
||||||
path: agent_scripts/Stage_2_S2_20.yml
|
path: agent_scripts/Stage_2_S2_20.yml
|
||||||
sha256: 3c4e6a7404f5b6a6b2403824c3cd9e71db566d4f2865bd4de3e6fd9d07824bd6
|
sha256: eb82f84df10c1eb4402e5e0f87fbe97a12e58aaa04f60c9535d66076300a5fdb
|
||||||
schema_id: liti_agent_yaml.v1
|
schema_id: liti_agent_yaml.v1
|
||||||
binding_status: BOUND
|
binding_status: BOUND
|
||||||
workflow_contract_ref:
|
workflow_contract_ref:
|
||||||
@@ -98,18 +110,18 @@ stage_bindings:
|
|||||||
inline_code_receipt_ref:
|
inline_code_receipt_ref:
|
||||||
asset_id: RECEIPT-S2_20-INLINE-CODE
|
asset_id: RECEIPT-S2_20-INLINE-CODE
|
||||||
path: manifest/s2_20_inline_code_receipt.json
|
path: manifest/s2_20_inline_code_receipt.json
|
||||||
sha256: 65e29f10f065cdd77281aa47acaf341edb38b0595aab0aa9f04bbee3a4718608
|
sha256: 64fd3711bd1128b6d29d3b8ff7a6af07228764da9604e0b5506562c5420e83e9
|
||||||
schema_id: stage2_s2_20_inline_code_receipt.v1
|
schema_id: stage2_s2_20_inline_code_receipt.v1
|
||||||
binding_status: BOUND
|
binding_status: BOUND
|
||||||
stage2_release_ref:
|
stage2_release_ref:
|
||||||
asset_id: RELEASE-STAGE2-CLEAN
|
asset_id: RELEASE-STAGE2-CLEAN
|
||||||
path: manifest/stage2_release.json
|
path: manifest/stage2_release.json
|
||||||
sha256: 9fa85bb94c5f14f675dcdaa0cf94d06745b21675d97797539ffc14015dcc9f53
|
sha256: 2363f1166ee8a3ea2b38350cde61fccfc9852a413c6b9f14a6ed6606af15a590
|
||||||
schema_id: stage2_release.v2
|
schema_id: stage2_release.v2
|
||||||
binding_status: BOUND
|
binding_status: BOUND
|
||||||
expected_release_sha256: 9fa85bb94c5f14f675dcdaa0cf94d06745b21675d97797539ffc14015dcc9f53
|
expected_release_sha256: 2363f1166ee8a3ea2b38350cde61fccfc9852a413c6b9f14a6ed6606af15a590
|
||||||
agent_script_sha256: 3c4e6a7404f5b6a6b2403824c3cd9e71db566d4f2865bd4de3e6fd9d07824bd6
|
agent_script_sha256: eb82f84df10c1eb4402e5e0f87fbe97a12e58aaa04f60c9535d66076300a5fdb
|
||||||
canonical_code_sha256: 6f1f503378242cf64cf8f0838af2164ab1103bc7031c8a6873f4ee89be66f7fd
|
canonical_code_sha256: 91feb9e2ced624a2a4ea36e2b83bd17ed20bb059a42206fad01660800adeb515
|
||||||
mcp_server_id: code-executor
|
mcp_server_id: code-executor
|
||||||
tool_name: run_code
|
tool_name: run_code
|
||||||
language: python
|
language: python
|
||||||
@@ -237,7 +249,7 @@ stage_bindings:
|
|||||||
agent_script_ref:
|
agent_script_ref:
|
||||||
asset_id: AGENT-S2_40-INLINE
|
asset_id: AGENT-S2_40-INLINE
|
||||||
path: agent_scripts/Stage_2_S2_40.yml
|
path: agent_scripts/Stage_2_S2_40.yml
|
||||||
sha256: 5cbe2ac9aa73d95a5fa4e6cf71d7d8f0ad83aa05536f573c13e42505ef5d46f7
|
sha256: a628f7fe944e21f77b9276cdb3d048189dcec958c7ed5abdcd9638b53a909a68
|
||||||
schema_id: liti_agent_yaml.v1
|
schema_id: liti_agent_yaml.v1
|
||||||
binding_status: BOUND
|
binding_status: BOUND
|
||||||
workflow_contract_ref:
|
workflow_contract_ref:
|
||||||
@@ -249,18 +261,18 @@ stage_bindings:
|
|||||||
inline_code_receipt_ref:
|
inline_code_receipt_ref:
|
||||||
asset_id: RECEIPT-S2_40-INLINE-CODE
|
asset_id: RECEIPT-S2_40-INLINE-CODE
|
||||||
path: manifest/s2_40_inline_code_receipt.json
|
path: manifest/s2_40_inline_code_receipt.json
|
||||||
sha256: 9bd0cfaee8d9e78480ab80a2fa541414c7ad0565439fa0f4661701fcdfde6526
|
sha256: 38fa1f5d8fc930ee3f7bbff7c73e1a52efc4995d14126ae035323ff74f1abf3d
|
||||||
schema_id: stage2_s2_40_inline_code_receipt.v1
|
schema_id: stage2_s2_40_inline_code_receipt.v1
|
||||||
binding_status: BOUND
|
binding_status: BOUND
|
||||||
stage2_release_ref:
|
stage2_release_ref:
|
||||||
asset_id: RELEASE-STAGE2-CLEAN
|
asset_id: RELEASE-STAGE2-CLEAN
|
||||||
path: manifest/stage2_release.json
|
path: manifest/stage2_release.json
|
||||||
sha256: 9fa85bb94c5f14f675dcdaa0cf94d06745b21675d97797539ffc14015dcc9f53
|
sha256: 2363f1166ee8a3ea2b38350cde61fccfc9852a413c6b9f14a6ed6606af15a590
|
||||||
schema_id: stage2_release.v2
|
schema_id: stage2_release.v2
|
||||||
binding_status: BOUND
|
binding_status: BOUND
|
||||||
expected_release_sha256: 9fa85bb94c5f14f675dcdaa0cf94d06745b21675d97797539ffc14015dcc9f53
|
expected_release_sha256: 2363f1166ee8a3ea2b38350cde61fccfc9852a413c6b9f14a6ed6606af15a590
|
||||||
agent_script_sha256: 5cbe2ac9aa73d95a5fa4e6cf71d7d8f0ad83aa05536f573c13e42505ef5d46f7
|
agent_script_sha256: a628f7fe944e21f77b9276cdb3d048189dcec958c7ed5abdcd9638b53a909a68
|
||||||
canonical_code_sha256: e521e1a65294a769cd6bf20edb159f8720c105b60cb769885aa416c8c763dcef
|
canonical_code_sha256: 12f6b83e1e6793ad871033c6e6b840fc1e31f9fb6695b1a15f0406978fe841f7
|
||||||
mcp_server_id: code-executor
|
mcp_server_id: code-executor
|
||||||
tool_name: run_code
|
tool_name: run_code
|
||||||
language: python
|
language: python
|
||||||
@@ -323,7 +335,7 @@ embedded_task_bindings:
|
|||||||
agent_script_ref:
|
agent_script_ref:
|
||||||
asset_id: AGENT-S2_30-INLINE
|
asset_id: AGENT-S2_30-INLINE
|
||||||
path: agent_scripts/Stage_2_S2_30.yml
|
path: agent_scripts/Stage_2_S2_30.yml
|
||||||
sha256: d0804526941207395c9b64a314b124ff8e919c6852e828d829dc45a9feb3ccdd
|
sha256: 8ff81d9a9db6b80216bf453634b93881f1c2ef7220962f077e44f703fb1457c3
|
||||||
schema_id: liti_agent_yaml.v1
|
schema_id: liti_agent_yaml.v1
|
||||||
binding_status: BOUND
|
binding_status: BOUND
|
||||||
workflow_contract_ref:
|
workflow_contract_ref:
|
||||||
@@ -335,18 +347,18 @@ embedded_task_bindings:
|
|||||||
inline_code_receipt_ref:
|
inline_code_receipt_ref:
|
||||||
asset_id: RECEIPT-S2_30-INLINE-CODE
|
asset_id: RECEIPT-S2_30-INLINE-CODE
|
||||||
path: manifest/s2_30_inline_code_receipt.json
|
path: manifest/s2_30_inline_code_receipt.json
|
||||||
sha256: 8d1618771cf1f72aa3c6a5a4544fe6b6e949ccc7ecfef56f166561971beee896
|
sha256: a5e4e3161fc16f3e44ec9ae2c05d2c4706eca02099d844520973f76475343998
|
||||||
schema_id: stage2_s2_30_inline_code_receipt.v1
|
schema_id: stage2_s2_30_inline_code_receipt.v1
|
||||||
binding_status: BOUND
|
binding_status: BOUND
|
||||||
stage2_release_ref:
|
stage2_release_ref:
|
||||||
asset_id: RELEASE-STAGE2-CLEAN
|
asset_id: RELEASE-STAGE2-CLEAN
|
||||||
path: manifest/stage2_release.json
|
path: manifest/stage2_release.json
|
||||||
sha256: 9fa85bb94c5f14f675dcdaa0cf94d06745b21675d97797539ffc14015dcc9f53
|
sha256: 2363f1166ee8a3ea2b38350cde61fccfc9852a413c6b9f14a6ed6606af15a590
|
||||||
schema_id: stage2_release.v2
|
schema_id: stage2_release.v2
|
||||||
binding_status: BOUND
|
binding_status: BOUND
|
||||||
expected_release_sha256: 9fa85bb94c5f14f675dcdaa0cf94d06745b21675d97797539ffc14015dcc9f53
|
expected_release_sha256: 2363f1166ee8a3ea2b38350cde61fccfc9852a413c6b9f14a6ed6606af15a590
|
||||||
agent_script_sha256: d0804526941207395c9b64a314b124ff8e919c6852e828d829dc45a9feb3ccdd
|
agent_script_sha256: 8ff81d9a9db6b80216bf453634b93881f1c2ef7220962f077e44f703fb1457c3
|
||||||
canonical_code_sha256: 1f7f3b842afbd3f1034f094d6341288effc9804a03920ada4bf8cfd512464e4c
|
canonical_code_sha256: 17e66a89befdeb3ac4d7901eda2dad57869db809baf5faf16434cd7a7adb067a
|
||||||
mcp_server_id: code-executor
|
mcp_server_id: code-executor
|
||||||
tool_name: run_code
|
tool_name: run_code
|
||||||
language: python
|
language: python
|
||||||
|
|||||||
+4
-4
@@ -2,7 +2,7 @@ schema_version: stage2_s2_30_llm_binding.v1
|
|||||||
binding_status: PENDING_EXTERNAL_PLATFORM_BINDING
|
binding_status: PENDING_EXTERNAL_PLATFORM_BINDING
|
||||||
agent_ref:
|
agent_ref:
|
||||||
path: agent_scripts/Stage_2_S2_30.yml
|
path: agent_scripts/Stage_2_S2_30.yml
|
||||||
sha256: d0804526941207395c9b64a314b124ff8e919c6852e828d829dc45a9feb3ccdd
|
sha256: 8ff81d9a9db6b80216bf453634b93881f1c2ef7220962f077e44f703fb1457c3
|
||||||
workflow_ref:
|
workflow_ref:
|
||||||
path: workflows/S2_30_claim_group_draft_map.yml
|
path: workflows/S2_30_claim_group_draft_map.yml
|
||||||
sha256: b266e7b7ae8939b087bfdb7b34fff8b9c6011c835560ab690991d856ced0cc15
|
sha256: b266e7b7ae8939b087bfdb7b34fff8b9c6011c835560ab690991d856ced0cc15
|
||||||
@@ -13,7 +13,7 @@ schema_ref:
|
|||||||
size_bytes: 68470
|
size_bytes: 68470
|
||||||
planner_ref:
|
planner_ref:
|
||||||
path: runtime/s2_30_dispatch_planner.py
|
path: runtime/s2_30_dispatch_planner.py
|
||||||
sha256: 1f7f3b842afbd3f1034f094d6341288effc9804a03920ada4bf8cfd512464e4c
|
sha256: 17e66a89befdeb3ac4d7901eda2dad57869db809baf5faf16434cd7a7adb067a
|
||||||
model:
|
model:
|
||||||
provider: openai
|
provider: openai
|
||||||
model_id: gpt-5.6-sol
|
model_id: gpt-5.6-sol
|
||||||
@@ -30,10 +30,10 @@ prompt_contract:
|
|||||||
- S30
|
- S30
|
||||||
inline_common_prompt_sha256: a90ac95f0b24ea938a16699f34ef7f17eb870edcbd8964ba4f6709e36e1bbcc0
|
inline_common_prompt_sha256: a90ac95f0b24ea938a16699f34ef7f17eb870edcbd8964ba4f6709e36e1bbcc0
|
||||||
inline_static_prompt_sha256: 577efa2c3334298ec60188b9f0066f834e072dc99f785b3c6ab4d05a47fac18a
|
inline_static_prompt_sha256: 577efa2c3334298ec60188b9f0066f834e072dc99f785b3c6ab4d05a47fac18a
|
||||||
projection_receipt_sha256: 1aa650d3c389ba27ce43e514295c7c2ce629583dc96220618fd97dea399d0757
|
projection_receipt_sha256: 00b66f3495de5d1ff1e6e508eae2939cf862fdfc799c9cd01544a7979f769c42
|
||||||
runtime_external_prompt_read_allowed: false
|
runtime_external_prompt_read_allowed: false
|
||||||
planner_contract:
|
planner_contract:
|
||||||
inline_code_receipt_sha256: 8d1618771cf1f72aa3c6a5a4544fe6b6e949ccc7ecfef56f166561971beee896
|
inline_code_receipt_sha256: a5e4e3161fc16f3e44ec9ae2c05d2c4706eca02099d844520973f76475343998
|
||||||
legal_reasoning_allowed: false
|
legal_reasoning_allowed: false
|
||||||
persistent_write_allowed: false
|
persistent_write_allowed: false
|
||||||
native_adapter:
|
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",
|
"agent_scripts/Stage_2_S2_00.yml",
|
||||||
"runtime/s2_00_ingress.py",
|
"runtime/s2_00_ingress.py",
|
||||||
"runtime/s2_00_ingress.txt",
|
"runtime/s2_00_ingress.txt",
|
||||||
|
"runtime/s2_00_prepare_request.py",
|
||||||
|
"runtime/s2_00_prepare_request.txt",
|
||||||
"manifest/s2_00_inline_code_receipt.json",
|
"manifest/s2_00_inline_code_receipt.json",
|
||||||
"agent_scripts/Stage_2_S2_20.yml",
|
"agent_scripts/Stage_2_S2_20.yml",
|
||||||
"runtime/s2_20_reduce.py",
|
"runtime/s2_20_reduce.py",
|
||||||
|
|||||||
+2
@@ -33,6 +33,8 @@ FORBIDDEN_PARENT_MEMBERS = frozenset(
|
|||||||
"agent_scripts/Stage_2_S2_00.yml",
|
"agent_scripts/Stage_2_S2_00.yml",
|
||||||
"runtime/s2_00_ingress.py",
|
"runtime/s2_00_ingress.py",
|
||||||
"runtime/s2_00_ingress.txt",
|
"runtime/s2_00_ingress.txt",
|
||||||
|
"runtime/s2_00_prepare_request.py",
|
||||||
|
"runtime/s2_00_prepare_request.txt",
|
||||||
"manifest/s2_00_inline_code_receipt.json",
|
"manifest/s2_00_inline_code_receipt.json",
|
||||||
"agent_scripts/Stage_2_S2_20.yml",
|
"agent_scripts/Stage_2_S2_20.yml",
|
||||||
"runtime/s2_20_reduce.py",
|
"runtime/s2_20_reduce.py",
|
||||||
|
|||||||
+87
-16
@@ -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"
|
PROJECTION_PATH = DEPLOYMENT_ROOT / "agent_scripts" / "Stage_2_S2_00.yml"
|
||||||
MIRROR_PY_PATH = DEPLOYMENT_ROOT / "runtime" / "s2_00_ingress.py"
|
MIRROR_PY_PATH = DEPLOYMENT_ROOT / "runtime" / "s2_00_ingress.py"
|
||||||
MIRROR_TXT_PATH = DEPLOYMENT_ROOT / "runtime" / "s2_00_ingress.txt"
|
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"
|
RECEIPT_PATH = DEPLOYMENT_ROOT / "manifest" / "s2_00_inline_code_receipt.json"
|
||||||
BINDING_PATH = DEPLOYMENT_ROOT / "deployment" / "stage2_code_executor_binding.yml"
|
BINDING_PATH = DEPLOYMENT_ROOT / "deployment" / "stage2_code_executor_binding.yml"
|
||||||
|
|
||||||
EXPECTED_TASK_NAME = "Task_S2_00_deterministic_ingress"
|
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_NAME = "Stage_2_S2_00_v2"
|
||||||
EXPECTED_AGENT_VERSION = "1.2.0"
|
EXPECTED_AGENT_VERSION = "1.2.0"
|
||||||
EXPECTED_PARAMETERS = {
|
EXPECTED_PARAMETERS = {
|
||||||
@@ -252,6 +255,20 @@ def select_s2_00_binding(document: Mapping[str, Any]) -> dict[str, Any]:
|
|||||||
"expected": None,
|
"expected": None,
|
||||||
"observed": row.get("external_mcp_contract"),
|
"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:
|
if drift:
|
||||||
raise ProjectionError(
|
raise ProjectionError(
|
||||||
"S2_00_BINDING_SEMANTICS_DRIFT",
|
"S2_00_BINDING_SEMANTICS_DRIFT",
|
||||||
@@ -280,7 +297,7 @@ def _require_list(value: Any, code: str) -> list[Any]:
|
|||||||
return value
|
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")
|
agent = _require_mapping(document.get("Agent"), "AGENT_OBJECT_REQUIRED")
|
||||||
if agent.get("name") != EXPECTED_AGENT_NAME or agent.get("version") != EXPECTED_AGENT_VERSION:
|
if agent.get("name") != EXPECTED_AGENT_NAME or agent.get("version") != EXPECTED_AGENT_VERSION:
|
||||||
raise ProjectionError(
|
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")
|
tasks = _require_list(stage.get("tasks"), "TASKS_ARRAY_REQUIRED")
|
||||||
if len(tasks) != 1:
|
if len(tasks) != 2:
|
||||||
raise ProjectionError("EXACTLY_ONE_TASK_REQUIRED", str(len(tasks)))
|
raise ProjectionError("EXACTLY_TWO_TASKS_REQUIRED", str(len(tasks)))
|
||||||
task = _require_mapping(tasks[0], "TASK_OBJECT_REQUIRED")
|
prepare_task = _require_mapping(tasks[0], "PREPARE_TASK_OBJECT_REQUIRED")
|
||||||
|
task = _require_mapping(tasks[1], "INGRESS_TASK_OBJECT_REQUIRED")
|
||||||
run_code_tasks = [
|
run_code_tasks = [
|
||||||
row
|
row
|
||||||
for row in tasks
|
for row in tasks
|
||||||
@@ -334,16 +352,20 @@ def extract_run_code_task(document: Mapping[str, Any]) -> tuple[dict[str, Any],
|
|||||||
and row.get("mcp") == "code-executor"
|
and row.get("mcp") == "code-executor"
|
||||||
and row.get("tool_name") == "run_code"
|
and row.get("tool_name") == "run_code"
|
||||||
]
|
]
|
||||||
if len(run_code_tasks) != 1:
|
if len(run_code_tasks) != 2:
|
||||||
raise ProjectionError("EXACTLY_ONE_RUN_CODE_REQUIRED", str(len(run_code_tasks)))
|
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:
|
if task.get("task_name") != EXPECTED_TASK_NAME:
|
||||||
raise ProjectionError("TASK_NAME_MISMATCH", repr(task.get("task_name")))
|
raise ProjectionError("TASK_NAME_MISMATCH", repr(task.get("task_name")))
|
||||||
parameters = _require_mapping(task.get("parameters"), "TASK_PARAMETERS_REQUIRED")
|
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"}
|
expected_parameter_keys = set(EXPECTED_PARAMETERS) | {"code"}
|
||||||
if set(parameters) != expected_parameter_keys:
|
if set(parameters) != expected_parameter_keys:
|
||||||
raise ProjectionError(
|
raise ProjectionError(
|
||||||
"RUN_CODE_PARAMETER_SET_MISMATCH",
|
"RUN_CODE_PARAMETER_SET_MISMATCH", repr(sorted(parameters)),
|
||||||
repr(sorted(parameters)),
|
|
||||||
)
|
)
|
||||||
for key, expected in EXPECTED_PARAMETERS.items():
|
for key, expected in EXPECTED_PARAMETERS.items():
|
||||||
if parameters.get(key) != expected:
|
if parameters.get(key) != expected:
|
||||||
@@ -362,15 +384,22 @@ def extract_run_code_task(document: Mapping[str, Any]) -> tuple[dict[str, Any],
|
|||||||
|
|
||||||
procedure = _require_mapping(stage.get("task_procedure"), "TASK_PROCEDURE_REQUIRED")
|
procedure = _require_mapping(stage.get("task_procedure"), "TASK_PROCEDURE_REQUIRED")
|
||||||
expected_procedure = {
|
expected_procedure = {
|
||||||
"IN": {"nexts": [EXPECTED_TASK_NAME], "wait_until": []},
|
"IN": {"nexts": [EXPECTED_PREPARE_TASK_NAME], "wait_until": []},
|
||||||
EXPECTED_TASK_NAME: {"nexts": ["OUT"], "wait_until": ["IN"]},
|
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]},
|
"OUT": {"nexts": [], "wait_until": [EXPECTED_TASK_NAME]},
|
||||||
}
|
}
|
||||||
if procedure != expected_procedure:
|
if procedure != expected_procedure:
|
||||||
raise ProjectionError("TASK_PROCEDURE_MISMATCH", repr(procedure)[:500])
|
raise ProjectionError("TASK_PROCEDURE_MISMATCH", repr(procedure)[:500])
|
||||||
if stage.get("prevs") != [] or stage.get("nexts") != []:
|
if stage.get("prevs") != [] or stage.get("nexts") != []:
|
||||||
raise ProjectionError("STANDALONE_STAGE_EDGES_MUST_BE_EMPTY", repr(stage))
|
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:
|
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")
|
code_bytes = code.encode("utf-8")
|
||||||
try:
|
try:
|
||||||
compile(code, "<Stage_2_S2_00.parameters.code>", "exec", dont_inherit=True)
|
compile(code, "<Stage_2_S2_00.parameters.code>", "exec", dont_inherit=True)
|
||||||
@@ -443,7 +472,14 @@ def validate_inline_code(code: str) -> dict[str, Any]:
|
|||||||
except (SyntaxError, ValueError, UnicodeError) as exc:
|
except (SyntaxError, ValueError, UnicodeError) as exc:
|
||||||
raise ProjectionError("INLINE_CODE_COMPILE_FAILED", str(exc)) from 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]
|
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(
|
missing_tokens.extend(
|
||||||
"|".join(alternatives)
|
"|".join(alternatives)
|
||||||
for alternatives in REQUIRED_CODE_TOKEN_ALTERNATIVES
|
for alternatives in REQUIRED_CODE_TOKEN_ALTERNATIVES
|
||||||
@@ -651,6 +687,8 @@ def _canonical_task_semantics(document: Mapping[str, Any], stage: Mapping[str, A
|
|||||||
"MCP_SERVERS_OBJECT_REQUIRED",
|
"MCP_SERVERS_OBJECT_REQUIRED",
|
||||||
)
|
)
|
||||||
parameters = _require_mapping(task.get("parameters"), "TASK_PARAMETERS_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 {
|
return {
|
||||||
"agent": {"name": agent.get("name"), "version": agent.get("version")},
|
"agent": {"name": agent.get("name"), "version": agent.get("version")},
|
||||||
"stage": {
|
"stage": {
|
||||||
@@ -663,6 +701,16 @@ def _canonical_task_semantics(document: Mapping[str, Any], stage: Mapping[str, A
|
|||||||
for name, value in sorted(servers.items())
|
for name, value in sorted(servers.items())
|
||||||
if isinstance(value, dict)
|
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": {
|
||||||
"task_name": task.get("task_name"),
|
"task_name": task.get("task_name"),
|
||||||
"mcp": task.get("mcp"),
|
"mcp": task.get("mcp"),
|
||||||
@@ -681,7 +729,10 @@ def expected_outputs(
|
|||||||
authoring_raw: bytes,
|
authoring_raw: bytes,
|
||||||
document: Mapping[str, Any],
|
document: Mapping[str, Any],
|
||||||
) -> tuple[dict[Path, bytes], dict[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")
|
parameters = _require_mapping(task.get("parameters"), "TASK_PARAMETERS_REQUIRED")
|
||||||
code = parameters["code"]
|
code = parameters["code"]
|
||||||
validation = validate_inline_code(code)
|
validation = validate_inline_code(code)
|
||||||
@@ -709,12 +760,30 @@ def expected_outputs(
|
|||||||
"canonical_task_semantics_sha256": semantics_sha256,
|
"canonical_task_semantics_sha256": semantics_sha256,
|
||||||
},
|
},
|
||||||
"canonical_code": {
|
"canonical_code": {
|
||||||
"yaml_pointer": "/Agent/Stages/0/tasks/0/parameters/code",
|
"yaml_pointer": "/Agent/Stages/0/tasks/1/parameters/code",
|
||||||
"encoding": "UTF-8",
|
"encoding": "UTF-8",
|
||||||
"extraction_transform": "NONE",
|
"extraction_transform": "NONE",
|
||||||
**validation,
|
**validation,
|
||||||
},
|
},
|
||||||
|
"prepare_code": {
|
||||||
|
"yaml_pointer": "/Agent/Stages/0/tasks/0/parameters/code",
|
||||||
|
"encoding": "UTF-8",
|
||||||
|
"extraction_transform": "NONE",
|
||||||
|
**prepare_validation,
|
||||||
|
},
|
||||||
"full_code_mirrors": [
|
"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),
|
"path": _logical_path(MIRROR_PY_PATH),
|
||||||
"sha256": validation["code_sha256"],
|
"sha256": validation["code_sha256"],
|
||||||
@@ -733,6 +802,8 @@ def expected_outputs(
|
|||||||
}
|
}
|
||||||
outputs = {
|
outputs = {
|
||||||
PROJECTION_PATH: authoring_raw,
|
PROJECTION_PATH: authoring_raw,
|
||||||
|
PREPARE_MIRROR_PY_PATH: prepare_bytes,
|
||||||
|
PREPARE_MIRROR_TXT_PATH: prepare_bytes,
|
||||||
MIRROR_PY_PATH: code_bytes,
|
MIRROR_PY_PATH: code_bytes,
|
||||||
MIRROR_TXT_PATH: code_bytes,
|
MIRROR_TXT_PATH: code_bytes,
|
||||||
RECEIPT_PATH: canonical_json_bytes(receipt),
|
RECEIPT_PATH: canonical_json_bytes(receipt),
|
||||||
|
|||||||
+87
-16
@@ -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"
|
PROJECTION_PATH = DEPLOYMENT_ROOT / "agent_scripts" / "Stage_2_S2_00.yml"
|
||||||
MIRROR_PY_PATH = DEPLOYMENT_ROOT / "runtime" / "s2_00_ingress.py"
|
MIRROR_PY_PATH = DEPLOYMENT_ROOT / "runtime" / "s2_00_ingress.py"
|
||||||
MIRROR_TXT_PATH = DEPLOYMENT_ROOT / "runtime" / "s2_00_ingress.txt"
|
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"
|
RECEIPT_PATH = DEPLOYMENT_ROOT / "manifest" / "s2_00_inline_code_receipt.json"
|
||||||
BINDING_PATH = DEPLOYMENT_ROOT / "deployment" / "stage2_code_executor_binding.yml"
|
BINDING_PATH = DEPLOYMENT_ROOT / "deployment" / "stage2_code_executor_binding.yml"
|
||||||
|
|
||||||
EXPECTED_TASK_NAME = "Task_S2_00_deterministic_ingress"
|
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_NAME = "Stage_2_S2_00_v2"
|
||||||
EXPECTED_AGENT_VERSION = "1.2.0"
|
EXPECTED_AGENT_VERSION = "1.2.0"
|
||||||
EXPECTED_PARAMETERS = {
|
EXPECTED_PARAMETERS = {
|
||||||
@@ -252,6 +255,20 @@ def select_s2_00_binding(document: Mapping[str, Any]) -> dict[str, Any]:
|
|||||||
"expected": None,
|
"expected": None,
|
||||||
"observed": row.get("external_mcp_contract"),
|
"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:
|
if drift:
|
||||||
raise ProjectionError(
|
raise ProjectionError(
|
||||||
"S2_00_BINDING_SEMANTICS_DRIFT",
|
"S2_00_BINDING_SEMANTICS_DRIFT",
|
||||||
@@ -280,7 +297,7 @@ def _require_list(value: Any, code: str) -> list[Any]:
|
|||||||
return value
|
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")
|
agent = _require_mapping(document.get("Agent"), "AGENT_OBJECT_REQUIRED")
|
||||||
if agent.get("name") != EXPECTED_AGENT_NAME or agent.get("version") != EXPECTED_AGENT_VERSION:
|
if agent.get("name") != EXPECTED_AGENT_NAME or agent.get("version") != EXPECTED_AGENT_VERSION:
|
||||||
raise ProjectionError(
|
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")
|
tasks = _require_list(stage.get("tasks"), "TASKS_ARRAY_REQUIRED")
|
||||||
if len(tasks) != 1:
|
if len(tasks) != 2:
|
||||||
raise ProjectionError("EXACTLY_ONE_TASK_REQUIRED", str(len(tasks)))
|
raise ProjectionError("EXACTLY_TWO_TASKS_REQUIRED", str(len(tasks)))
|
||||||
task = _require_mapping(tasks[0], "TASK_OBJECT_REQUIRED")
|
prepare_task = _require_mapping(tasks[0], "PREPARE_TASK_OBJECT_REQUIRED")
|
||||||
|
task = _require_mapping(tasks[1], "INGRESS_TASK_OBJECT_REQUIRED")
|
||||||
run_code_tasks = [
|
run_code_tasks = [
|
||||||
row
|
row
|
||||||
for row in tasks
|
for row in tasks
|
||||||
@@ -334,16 +352,20 @@ def extract_run_code_task(document: Mapping[str, Any]) -> tuple[dict[str, Any],
|
|||||||
and row.get("mcp") == "code-executor"
|
and row.get("mcp") == "code-executor"
|
||||||
and row.get("tool_name") == "run_code"
|
and row.get("tool_name") == "run_code"
|
||||||
]
|
]
|
||||||
if len(run_code_tasks) != 1:
|
if len(run_code_tasks) != 2:
|
||||||
raise ProjectionError("EXACTLY_ONE_RUN_CODE_REQUIRED", str(len(run_code_tasks)))
|
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:
|
if task.get("task_name") != EXPECTED_TASK_NAME:
|
||||||
raise ProjectionError("TASK_NAME_MISMATCH", repr(task.get("task_name")))
|
raise ProjectionError("TASK_NAME_MISMATCH", repr(task.get("task_name")))
|
||||||
parameters = _require_mapping(task.get("parameters"), "TASK_PARAMETERS_REQUIRED")
|
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"}
|
expected_parameter_keys = set(EXPECTED_PARAMETERS) | {"code"}
|
||||||
if set(parameters) != expected_parameter_keys:
|
if set(parameters) != expected_parameter_keys:
|
||||||
raise ProjectionError(
|
raise ProjectionError(
|
||||||
"RUN_CODE_PARAMETER_SET_MISMATCH",
|
"RUN_CODE_PARAMETER_SET_MISMATCH", repr(sorted(parameters)),
|
||||||
repr(sorted(parameters)),
|
|
||||||
)
|
)
|
||||||
for key, expected in EXPECTED_PARAMETERS.items():
|
for key, expected in EXPECTED_PARAMETERS.items():
|
||||||
if parameters.get(key) != expected:
|
if parameters.get(key) != expected:
|
||||||
@@ -362,15 +384,22 @@ def extract_run_code_task(document: Mapping[str, Any]) -> tuple[dict[str, Any],
|
|||||||
|
|
||||||
procedure = _require_mapping(stage.get("task_procedure"), "TASK_PROCEDURE_REQUIRED")
|
procedure = _require_mapping(stage.get("task_procedure"), "TASK_PROCEDURE_REQUIRED")
|
||||||
expected_procedure = {
|
expected_procedure = {
|
||||||
"IN": {"nexts": [EXPECTED_TASK_NAME], "wait_until": []},
|
"IN": {"nexts": [EXPECTED_PREPARE_TASK_NAME], "wait_until": []},
|
||||||
EXPECTED_TASK_NAME: {"nexts": ["OUT"], "wait_until": ["IN"]},
|
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]},
|
"OUT": {"nexts": [], "wait_until": [EXPECTED_TASK_NAME]},
|
||||||
}
|
}
|
||||||
if procedure != expected_procedure:
|
if procedure != expected_procedure:
|
||||||
raise ProjectionError("TASK_PROCEDURE_MISMATCH", repr(procedure)[:500])
|
raise ProjectionError("TASK_PROCEDURE_MISMATCH", repr(procedure)[:500])
|
||||||
if stage.get("prevs") != [] or stage.get("nexts") != []:
|
if stage.get("prevs") != [] or stage.get("nexts") != []:
|
||||||
raise ProjectionError("STANDALONE_STAGE_EDGES_MUST_BE_EMPTY", repr(stage))
|
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:
|
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")
|
code_bytes = code.encode("utf-8")
|
||||||
try:
|
try:
|
||||||
compile(code, "<Stage_2_S2_00.parameters.code>", "exec", dont_inherit=True)
|
compile(code, "<Stage_2_S2_00.parameters.code>", "exec", dont_inherit=True)
|
||||||
@@ -443,7 +472,14 @@ def validate_inline_code(code: str) -> dict[str, Any]:
|
|||||||
except (SyntaxError, ValueError, UnicodeError) as exc:
|
except (SyntaxError, ValueError, UnicodeError) as exc:
|
||||||
raise ProjectionError("INLINE_CODE_COMPILE_FAILED", str(exc)) from 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]
|
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(
|
missing_tokens.extend(
|
||||||
"|".join(alternatives)
|
"|".join(alternatives)
|
||||||
for alternatives in REQUIRED_CODE_TOKEN_ALTERNATIVES
|
for alternatives in REQUIRED_CODE_TOKEN_ALTERNATIVES
|
||||||
@@ -651,6 +687,8 @@ def _canonical_task_semantics(document: Mapping[str, Any], stage: Mapping[str, A
|
|||||||
"MCP_SERVERS_OBJECT_REQUIRED",
|
"MCP_SERVERS_OBJECT_REQUIRED",
|
||||||
)
|
)
|
||||||
parameters = _require_mapping(task.get("parameters"), "TASK_PARAMETERS_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 {
|
return {
|
||||||
"agent": {"name": agent.get("name"), "version": agent.get("version")},
|
"agent": {"name": agent.get("name"), "version": agent.get("version")},
|
||||||
"stage": {
|
"stage": {
|
||||||
@@ -663,6 +701,16 @@ def _canonical_task_semantics(document: Mapping[str, Any], stage: Mapping[str, A
|
|||||||
for name, value in sorted(servers.items())
|
for name, value in sorted(servers.items())
|
||||||
if isinstance(value, dict)
|
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": {
|
||||||
"task_name": task.get("task_name"),
|
"task_name": task.get("task_name"),
|
||||||
"mcp": task.get("mcp"),
|
"mcp": task.get("mcp"),
|
||||||
@@ -681,7 +729,10 @@ def expected_outputs(
|
|||||||
authoring_raw: bytes,
|
authoring_raw: bytes,
|
||||||
document: Mapping[str, Any],
|
document: Mapping[str, Any],
|
||||||
) -> tuple[dict[Path, bytes], dict[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")
|
parameters = _require_mapping(task.get("parameters"), "TASK_PARAMETERS_REQUIRED")
|
||||||
code = parameters["code"]
|
code = parameters["code"]
|
||||||
validation = validate_inline_code(code)
|
validation = validate_inline_code(code)
|
||||||
@@ -709,12 +760,30 @@ def expected_outputs(
|
|||||||
"canonical_task_semantics_sha256": semantics_sha256,
|
"canonical_task_semantics_sha256": semantics_sha256,
|
||||||
},
|
},
|
||||||
"canonical_code": {
|
"canonical_code": {
|
||||||
"yaml_pointer": "/Agent/Stages/0/tasks/0/parameters/code",
|
"yaml_pointer": "/Agent/Stages/0/tasks/1/parameters/code",
|
||||||
"encoding": "UTF-8",
|
"encoding": "UTF-8",
|
||||||
"extraction_transform": "NONE",
|
"extraction_transform": "NONE",
|
||||||
**validation,
|
**validation,
|
||||||
},
|
},
|
||||||
|
"prepare_code": {
|
||||||
|
"yaml_pointer": "/Agent/Stages/0/tasks/0/parameters/code",
|
||||||
|
"encoding": "UTF-8",
|
||||||
|
"extraction_transform": "NONE",
|
||||||
|
**prepare_validation,
|
||||||
|
},
|
||||||
"full_code_mirrors": [
|
"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),
|
"path": _logical_path(MIRROR_PY_PATH),
|
||||||
"sha256": validation["code_sha256"],
|
"sha256": validation["code_sha256"],
|
||||||
@@ -733,6 +802,8 @@ def expected_outputs(
|
|||||||
}
|
}
|
||||||
outputs = {
|
outputs = {
|
||||||
PROJECTION_PATH: authoring_raw,
|
PROJECTION_PATH: authoring_raw,
|
||||||
|
PREPARE_MIRROR_PY_PATH: prepare_bytes,
|
||||||
|
PREPARE_MIRROR_TXT_PATH: prepare_bytes,
|
||||||
MIRROR_PY_PATH: code_bytes,
|
MIRROR_PY_PATH: code_bytes,
|
||||||
MIRROR_TXT_PATH: code_bytes,
|
MIRROR_TXT_PATH: code_bytes,
|
||||||
RECEIPT_PATH: canonical_json_bytes(receipt),
|
RECEIPT_PATH: canonical_json_bytes(receipt),
|
||||||
|
|||||||
+2
@@ -58,6 +58,8 @@ FORBIDDEN_PARENT_MEMBERS = frozenset(
|
|||||||
"agent_scripts/Stage_2_S2_00.yml",
|
"agent_scripts/Stage_2_S2_00.yml",
|
||||||
"runtime/s2_00_ingress.py",
|
"runtime/s2_00_ingress.py",
|
||||||
"runtime/s2_00_ingress.txt",
|
"runtime/s2_00_ingress.txt",
|
||||||
|
"runtime/s2_00_prepare_request.py",
|
||||||
|
"runtime/s2_00_prepare_request.txt",
|
||||||
"manifest/s2_00_inline_code_receipt.json",
|
"manifest/s2_00_inline_code_receipt.json",
|
||||||
"agent_scripts/Stage_2_S2_20.yml",
|
"agent_scripts/Stage_2_S2_20.yml",
|
||||||
"runtime/s2_20_reduce.py",
|
"runtime/s2_20_reduce.py",
|
||||||
|
|||||||
+2
@@ -58,6 +58,8 @@ FORBIDDEN_PARENT_MEMBERS = frozenset(
|
|||||||
"agent_scripts/Stage_2_S2_00.yml",
|
"agent_scripts/Stage_2_S2_00.yml",
|
||||||
"runtime/s2_00_ingress.py",
|
"runtime/s2_00_ingress.py",
|
||||||
"runtime/s2_00_ingress.txt",
|
"runtime/s2_00_ingress.txt",
|
||||||
|
"runtime/s2_00_prepare_request.py",
|
||||||
|
"runtime/s2_00_prepare_request.txt",
|
||||||
"manifest/s2_00_inline_code_receipt.json",
|
"manifest/s2_00_inline_code_receipt.json",
|
||||||
"agent_scripts/Stage_2_S2_20.yml",
|
"agent_scripts/Stage_2_S2_20.yml",
|
||||||
"runtime/s2_20_reduce.py",
|
"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.
|
# literal placeholders in the offline parity mirror and its unit tests.
|
||||||
INLINE_USER_HASH = "{{__user_hash__}}"
|
INLINE_USER_HASH = "{{__user_hash__}}"
|
||||||
INLINE_WORKSPACE_HASH = "{{__workspace_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_REQUEST_PATH = "stage2_control/s2_00_request.json"
|
||||||
INLINE_STAGE2_ASSET_ROOT = "Default_Agent/Stage_2_Clean"
|
INLINE_STAGE2_ASSET_ROOT = "Default_Agent/Stage_2_Clean"
|
||||||
INLINE_STAGE2_RELEASE_PATH = (
|
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.
|
# literal placeholders in the offline parity mirror and its unit tests.
|
||||||
INLINE_USER_HASH = "{{__user_hash__}}"
|
INLINE_USER_HASH = "{{__user_hash__}}"
|
||||||
INLINE_WORKSPACE_HASH = "{{__workspace_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_REQUEST_PATH = "stage2_control/s2_00_request.json"
|
||||||
INLINE_STAGE2_ASSET_ROOT = "Default_Agent/Stage_2_Clean"
|
INLINE_STAGE2_ASSET_ROOT = "Default_Agent/Stage_2_Clean"
|
||||||
INLINE_STAGE2_RELEASE_PATH = (
|
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"
|
WORKFLOW_ID = "S2_20"
|
||||||
ALGORITHM_VERSION = "s2_20_relief_plan/1.0.0"
|
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"
|
LOCALDOCS_URL = "http://mcp-localdocs:8012/mcp"
|
||||||
WEAVIATE_MCP_URL = "https://weaviate.eroomai.com/mcp"
|
WEAVIATE_MCP_URL = "https://weaviate.eroomai.com/mcp"
|
||||||
MCP_PROTOCOL_VERSION = "2025-03-26"
|
MCP_PROTOCOL_VERSION = "2025-03-26"
|
||||||
|
|||||||
+1
-1
@@ -27,7 +27,7 @@ from typing import Any, Iterable, Mapping, Sequence
|
|||||||
|
|
||||||
WORKFLOW_ID = "S2_20"
|
WORKFLOW_ID = "S2_20"
|
||||||
ALGORITHM_VERSION = "s2_20_relief_plan/1.0.0"
|
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"
|
LOCALDOCS_URL = "http://mcp-localdocs:8012/mcp"
|
||||||
WEAVIATE_MCP_URL = "https://weaviate.eroomai.com/mcp"
|
WEAVIATE_MCP_URL = "https://weaviate.eroomai.com/mcp"
|
||||||
MCP_PROTOCOL_VERSION = "2025-03-26"
|
MCP_PROTOCOL_VERSION = "2025-03-26"
|
||||||
|
|||||||
+1
-1
@@ -18,7 +18,7 @@ import sys
|
|||||||
from typing import Any, Mapping
|
from typing import Any, Mapping
|
||||||
|
|
||||||
|
|
||||||
EXPECTED_STAGE2_RELEASE_SHA256 = "9fa85bb94c5f14f675dcdaa0cf94d06745b21675d97797539ffc14015dcc9f53"
|
EXPECTED_STAGE2_RELEASE_SHA256 = "2363f1166ee8a3ea2b38350cde61fccfc9852a413c6b9f14a6ed6606af15a590"
|
||||||
LOCALDOCS_URL = "http://mcp-localdocs:8012/mcp"
|
LOCALDOCS_URL = "http://mcp-localdocs:8012/mcp"
|
||||||
MCP_PROTOCOL_VERSION = "2025-03-26"
|
MCP_PROTOCOL_VERSION = "2025-03-26"
|
||||||
INLINE_USER_HASH = "{{__user_hash__}}"
|
INLINE_USER_HASH = "{{__user_hash__}}"
|
||||||
|
|||||||
+1
-1
@@ -18,7 +18,7 @@ import sys
|
|||||||
from typing import Any, Mapping
|
from typing import Any, Mapping
|
||||||
|
|
||||||
|
|
||||||
EXPECTED_STAGE2_RELEASE_SHA256 = "9fa85bb94c5f14f675dcdaa0cf94d06745b21675d97797539ffc14015dcc9f53"
|
EXPECTED_STAGE2_RELEASE_SHA256 = "2363f1166ee8a3ea2b38350cde61fccfc9852a413c6b9f14a6ed6606af15a590"
|
||||||
LOCALDOCS_URL = "http://mcp-localdocs:8012/mcp"
|
LOCALDOCS_URL = "http://mcp-localdocs:8012/mcp"
|
||||||
MCP_PROTOCOL_VERSION = "2025-03-26"
|
MCP_PROTOCOL_VERSION = "2025-03-26"
|
||||||
INLINE_USER_HASH = "{{__user_hash__}}"
|
INLINE_USER_HASH = "{{__user_hash__}}"
|
||||||
|
|||||||
+1
-1
@@ -25,7 +25,7 @@ from typing import Any, Iterable, Mapping, Sequence
|
|||||||
|
|
||||||
WORKFLOW_ID = "S2_40"
|
WORKFLOW_ID = "S2_40"
|
||||||
ALGORITHM_VERSION = "s2_40_finalizer/1.0.0"
|
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"
|
LOCALDOCS_URL = "http://mcp-localdocs:8012/mcp"
|
||||||
MCP_PROTOCOL_VERSION = "2025-03-26"
|
MCP_PROTOCOL_VERSION = "2025-03-26"
|
||||||
INLINE_USER_HASH = "{{__user_hash__}}"
|
INLINE_USER_HASH = "{{__user_hash__}}"
|
||||||
|
|||||||
+1
-1
@@ -25,7 +25,7 @@ from typing import Any, Iterable, Mapping, Sequence
|
|||||||
|
|
||||||
WORKFLOW_ID = "S2_40"
|
WORKFLOW_ID = "S2_40"
|
||||||
ALGORITHM_VERSION = "s2_40_finalizer/1.0.0"
|
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"
|
LOCALDOCS_URL = "http://mcp-localdocs:8012/mcp"
|
||||||
MCP_PROTOCOL_VERSION = "2025-03-26"
|
MCP_PROTOCOL_VERSION = "2025-03-26"
|
||||||
INLINE_USER_HASH = "{{__user_hash__}}"
|
INLINE_USER_HASH = "{{__user_hash__}}"
|
||||||
|
|||||||
+76
-5
@@ -186,6 +186,20 @@
|
|||||||
"expected_release_sha256": {"$ref": "#/$defs/bindable_sha256"},
|
"expected_release_sha256": {"$ref": "#/$defs/bindable_sha256"},
|
||||||
"agent_script_sha256": {"$ref": "#/$defs/bindable_sha256"},
|
"agent_script_sha256": {"$ref": "#/$defs/bindable_sha256"},
|
||||||
"canonical_code_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"},
|
"mcp_server_id": {"const": "code-executor"},
|
||||||
"tool_name": {"const": "run_code"},
|
"tool_name": {"const": "run_code"},
|
||||||
"language": {"const": "python"},
|
"language": {"const": "python"},
|
||||||
@@ -692,6 +706,7 @@
|
|||||||
"authoring",
|
"authoring",
|
||||||
"deployment_projection",
|
"deployment_projection",
|
||||||
"canonical_code",
|
"canonical_code",
|
||||||
|
"prepare_code",
|
||||||
"full_code_mirrors",
|
"full_code_mirrors",
|
||||||
"task_contract",
|
"task_contract",
|
||||||
"parity_status"
|
"parity_status"
|
||||||
@@ -768,12 +783,55 @@
|
|||||||
},
|
},
|
||||||
"placeholder_count": {"const": 0},
|
"placeholder_count": {"const": 0},
|
||||||
"plaintext_secret_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"}
|
"yaml_pointer": {"const": "/Agent/Stages/0/tasks/0/parameters/code"}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"full_code_mirrors": {
|
"full_code_mirrors": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"prefixItems": [
|
"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",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
@@ -798,13 +856,13 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"items": false,
|
"items": false,
|
||||||
"minItems": 2,
|
"minItems": 4,
|
||||||
"maxItems": 2
|
"maxItems": 4
|
||||||
},
|
},
|
||||||
"task_contract": {
|
"task_contract": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"required": ["agent", "mcp_servers", "stage", "task", "task_procedure"],
|
"required": ["agent", "mcp_servers", "stage", "prepare_task", "task", "task_procedure"],
|
||||||
"properties": {
|
"properties": {
|
||||||
"agent": {"const": {"name": "Stage_2_S2_00_v2", "version": "1.2.0"}},
|
"agent": {"const": {"name": "Stage_2_S2_00_v2", "version": "1.2.0"}},
|
||||||
"mcp_servers": {
|
"mcp_servers": {
|
||||||
@@ -814,6 +872,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"stage": {"const": {"name": "S2_00", "nexts": [], "prevs": []}},
|
"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": {
|
"task": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
@@ -835,8 +905,9 @@
|
|||||||
},
|
},
|
||||||
"task_procedure": {
|
"task_procedure": {
|
||||||
"const": {
|
"const": {
|
||||||
"IN": {"nexts": ["Task_S2_00_deterministic_ingress"], "wait_until": []},
|
"IN": {"nexts": ["Task_S2_00_prepare_request"], "wait_until": []},
|
||||||
"Task_S2_00_deterministic_ingress": {"nexts": ["OUT"], "wait_until": ["IN"]},
|
"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"]}
|
"OUT": {"nexts": [], "wait_until": ["Task_S2_00_deterministic_ingress"]}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-2
@@ -1313,7 +1313,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "tests/s2_00/test_cluster_bundle_compile.py",
|
"path": "tests/s2_00/test_cluster_bundle_compile.py",
|
||||||
"sha256": "b643d2d4018ebe3349c64304063edf7ba0194fc92fb1fe9337eca3f384ae794a",
|
"sha256": "1b7a58ae53e7465cd39f0c911c4fab8e96b9e89df707eb9423e8a22fabc0a54b",
|
||||||
"status": "DEV_HASH_BOUND"
|
"status": "DEV_HASH_BOUND"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1328,7 +1328,12 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "tests/s2_00/test_inline_code_parity.py",
|
"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"
|
"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"], binding["stage2_release_ref"]["sha256"])
|
||||||
self.assertEqual(binding["expected_release_sha256"], hashlib.sha256(release_path.read_bytes()).hexdigest())
|
self.assertEqual(binding["expected_release_sha256"], hashlib.sha256(release_path.read_bytes()).hexdigest())
|
||||||
agent = yaml.safe_load(agent_path.read_text(encoding="utf-8"))
|
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(hashlib.sha256(code.encode("utf-8")).hexdigest(), binding["canonical_code_sha256"])
|
||||||
self.assertEqual(binding["agent_script_sha256"], binding["agent_script_ref"]["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"], binding["stage2_release_ref"]["sha256"])
|
||||||
self.assertEqual(binding["expected_release_sha256"], hashlib.sha256(release_path.read_bytes()).hexdigest())
|
self.assertEqual(binding["expected_release_sha256"], hashlib.sha256(release_path.read_bytes()).hexdigest())
|
||||||
agent = yaml.safe_load(agent_path.read_text(encoding="utf-8"))
|
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(hashlib.sha256(code.encode("utf-8")).hexdigest(), binding["canonical_code_sha256"])
|
||||||
self.assertEqual(binding["agent_script_sha256"], binding["agent_script_ref"]["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:
|
def authoring_yaml(code: str | None = None) -> bytes:
|
||||||
source = valid_inline_code() if code is None else code
|
source = valid_inline_code() if code is None else code
|
||||||
indented = "\n".join(f" {line}" for line in source.split("\n"))
|
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 (
|
return (
|
||||||
"Agent:\n"
|
"Agent:\n"
|
||||||
" name: Stage_2_S2_00_v2\n"
|
" name: Stage_2_S2_00_v2\n"
|
||||||
@@ -53,6 +65,16 @@ def authoring_yaml(code: str | None = None) -> bytes:
|
|||||||
" type: streamable-http\n"
|
" type: streamable-http\n"
|
||||||
" url: https://code-executor.mcp.eroomai.com/mcp\n"
|
" url: https://code-executor.mcp.eroomai.com/mcp\n"
|
||||||
" tasks:\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"
|
" - task_name: Task_S2_00_deterministic_ingress\n"
|
||||||
" mcp: code-executor\n"
|
" mcp: code-executor\n"
|
||||||
" tool_name: run_code\n"
|
" tool_name: run_code\n"
|
||||||
@@ -65,11 +87,14 @@ def authoring_yaml(code: str | None = None) -> bytes:
|
|||||||
f"{indented}\n"
|
f"{indented}\n"
|
||||||
" task_procedure:\n"
|
" task_procedure:\n"
|
||||||
" IN:\n"
|
" IN:\n"
|
||||||
" nexts: [Task_S2_00_deterministic_ingress]\n"
|
" nexts: [Task_S2_00_prepare_request]\n"
|
||||||
" wait_until: []\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"
|
" Task_S2_00_deterministic_ingress:\n"
|
||||||
" nexts: [OUT]\n"
|
" nexts: [OUT]\n"
|
||||||
" wait_until: [IN]\n"
|
" wait_until: [Task_S2_00_prepare_request]\n"
|
||||||
" OUT:\n"
|
" OUT:\n"
|
||||||
" nexts: []\n"
|
" nexts: []\n"
|
||||||
" wait_until: [Task_S2_00_deterministic_ingress]\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.PROJECTION_PATH], raw)
|
||||||
self.assertEqual(outputs[builder.MIRROR_PY_PATH], valid_inline_code().encode("utf-8"))
|
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.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])
|
parsed_receipt = json.loads(outputs[builder.RECEIPT_PATH])
|
||||||
self.assertEqual(parsed_receipt, receipt)
|
self.assertEqual(parsed_receipt, receipt)
|
||||||
self.assertEqual(parsed_receipt["parity_status"], "PASS")
|
self.assertEqual(parsed_receipt["parity_status"], "PASS")
|
||||||
@@ -233,6 +259,8 @@ class InlineCodeProjectionUnitTests(unittest.TestCase):
|
|||||||
projection = root / "agent_scripts" / "Stage_2_S2_00.yml"
|
projection = root / "agent_scripts" / "Stage_2_S2_00.yml"
|
||||||
mirror_py = root / "runtime" / "s2_00_ingress.py"
|
mirror_py = root / "runtime" / "s2_00_ingress.py"
|
||||||
mirror_txt = root / "runtime" / "s2_00_ingress.txt"
|
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"
|
receipt = root / "manifest" / "s2_00_inline_code_receipt.json"
|
||||||
root.mkdir(parents=True)
|
root.mkdir(parents=True)
|
||||||
original = authoring_yaml()
|
original = authoring_yaml()
|
||||||
@@ -244,6 +272,8 @@ class InlineCodeProjectionUnitTests(unittest.TestCase):
|
|||||||
mock.patch.object(builder, "PROJECTION_PATH", projection),
|
mock.patch.object(builder, "PROJECTION_PATH", projection),
|
||||||
mock.patch.object(builder, "MIRROR_PY_PATH", mirror_py),
|
mock.patch.object(builder, "MIRROR_PY_PATH", mirror_py),
|
||||||
mock.patch.object(builder, "MIRROR_TXT_PATH", mirror_txt),
|
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),
|
mock.patch.object(builder, "RECEIPT_PATH", receipt),
|
||||||
):
|
):
|
||||||
status, payload = builder.run(check=False)
|
status, payload = builder.run(check=False)
|
||||||
@@ -253,6 +283,7 @@ class InlineCodeProjectionUnitTests(unittest.TestCase):
|
|||||||
self.assertEqual(projection.read_bytes(), original)
|
self.assertEqual(projection.read_bytes(), original)
|
||||||
self.assertEqual(mirror_py.read_bytes(), valid_inline_code().encode("utf-8"))
|
self.assertEqual(mirror_py.read_bytes(), valid_inline_code().encode("utf-8"))
|
||||||
self.assertEqual(mirror_py.read_bytes(), mirror_txt.read_bytes())
|
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()
|
receipt_before = receipt.read_bytes()
|
||||||
status, payload = builder.run(check=True)
|
status, payload = builder.run(check=True)
|
||||||
self.assertEqual(status, 0)
|
self.assertEqual(status, 0)
|
||||||
|
|||||||
+33
-2
@@ -38,6 +38,18 @@ def valid_inline_code() -> str:
|
|||||||
def authoring_yaml(code: str | None = None) -> bytes:
|
def authoring_yaml(code: str | None = None) -> bytes:
|
||||||
source = valid_inline_code() if code is None else code
|
source = valid_inline_code() if code is None else code
|
||||||
indented = "\n".join(f" {line}" for line in source.split("\n"))
|
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 (
|
return (
|
||||||
"Agent:\n"
|
"Agent:\n"
|
||||||
" name: Stage_2_S2_00_v2\n"
|
" name: Stage_2_S2_00_v2\n"
|
||||||
@@ -53,6 +65,16 @@ def authoring_yaml(code: str | None = None) -> bytes:
|
|||||||
" type: streamable-http\n"
|
" type: streamable-http\n"
|
||||||
" url: https://code-executor.mcp.eroomai.com/mcp\n"
|
" url: https://code-executor.mcp.eroomai.com/mcp\n"
|
||||||
" tasks:\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"
|
" - task_name: Task_S2_00_deterministic_ingress\n"
|
||||||
" mcp: code-executor\n"
|
" mcp: code-executor\n"
|
||||||
" tool_name: run_code\n"
|
" tool_name: run_code\n"
|
||||||
@@ -65,11 +87,14 @@ def authoring_yaml(code: str | None = None) -> bytes:
|
|||||||
f"{indented}\n"
|
f"{indented}\n"
|
||||||
" task_procedure:\n"
|
" task_procedure:\n"
|
||||||
" IN:\n"
|
" IN:\n"
|
||||||
" nexts: [Task_S2_00_deterministic_ingress]\n"
|
" nexts: [Task_S2_00_prepare_request]\n"
|
||||||
" wait_until: []\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"
|
" Task_S2_00_deterministic_ingress:\n"
|
||||||
" nexts: [OUT]\n"
|
" nexts: [OUT]\n"
|
||||||
" wait_until: [IN]\n"
|
" wait_until: [Task_S2_00_prepare_request]\n"
|
||||||
" OUT:\n"
|
" OUT:\n"
|
||||||
" nexts: []\n"
|
" nexts: []\n"
|
||||||
" wait_until: [Task_S2_00_deterministic_ingress]\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.PROJECTION_PATH], raw)
|
||||||
self.assertEqual(outputs[builder.MIRROR_PY_PATH], valid_inline_code().encode("utf-8"))
|
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.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])
|
parsed_receipt = json.loads(outputs[builder.RECEIPT_PATH])
|
||||||
self.assertEqual(parsed_receipt, receipt)
|
self.assertEqual(parsed_receipt, receipt)
|
||||||
self.assertEqual(parsed_receipt["parity_status"], "PASS")
|
self.assertEqual(parsed_receipt["parity_status"], "PASS")
|
||||||
@@ -233,6 +259,8 @@ class InlineCodeProjectionUnitTests(unittest.TestCase):
|
|||||||
projection = root / "agent_scripts" / "Stage_2_S2_00.yml"
|
projection = root / "agent_scripts" / "Stage_2_S2_00.yml"
|
||||||
mirror_py = root / "runtime" / "s2_00_ingress.py"
|
mirror_py = root / "runtime" / "s2_00_ingress.py"
|
||||||
mirror_txt = root / "runtime" / "s2_00_ingress.txt"
|
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"
|
receipt = root / "manifest" / "s2_00_inline_code_receipt.json"
|
||||||
root.mkdir(parents=True)
|
root.mkdir(parents=True)
|
||||||
original = authoring_yaml()
|
original = authoring_yaml()
|
||||||
@@ -244,6 +272,8 @@ class InlineCodeProjectionUnitTests(unittest.TestCase):
|
|||||||
mock.patch.object(builder, "PROJECTION_PATH", projection),
|
mock.patch.object(builder, "PROJECTION_PATH", projection),
|
||||||
mock.patch.object(builder, "MIRROR_PY_PATH", mirror_py),
|
mock.patch.object(builder, "MIRROR_PY_PATH", mirror_py),
|
||||||
mock.patch.object(builder, "MIRROR_TXT_PATH", mirror_txt),
|
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),
|
mock.patch.object(builder, "RECEIPT_PATH", receipt),
|
||||||
):
|
):
|
||||||
status, payload = builder.run(check=False)
|
status, payload = builder.run(check=False)
|
||||||
@@ -253,6 +283,7 @@ class InlineCodeProjectionUnitTests(unittest.TestCase):
|
|||||||
self.assertEqual(projection.read_bytes(), original)
|
self.assertEqual(projection.read_bytes(), original)
|
||||||
self.assertEqual(mirror_py.read_bytes(), valid_inline_code().encode("utf-8"))
|
self.assertEqual(mirror_py.read_bytes(), valid_inline_code().encode("utf-8"))
|
||||||
self.assertEqual(mirror_py.read_bytes(), mirror_txt.read_bytes())
|
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()
|
receipt_before = receipt.read_bytes()
|
||||||
status, payload = builder.run(check=True)
|
status, payload = builder.run(check=True)
|
||||||
self.assertEqual(status, 0)
|
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
|
- name: S2_00
|
||||||
description: >-
|
description: >-
|
||||||
C00, C05, C10, C15의 IO·순서·분기·배리어를 선언하는 NON-LLM 계약.
|
C00, C05, C10, C15의 IO·순서·분기·배리어를 선언하는 NON-LLM 계약.
|
||||||
실행 Python은 이 문서가 아니라 Agent YAML의 exactly-one
|
실행 Python은 Agent YAML의 request 준비와 ingress 두
|
||||||
code-executor.run_code task에만 존재한다.
|
code-executor.run_code task에 존재한다.
|
||||||
prevs: []
|
prevs: []
|
||||||
nexts:
|
nexts:
|
||||||
- S2_10
|
- S2_10
|
||||||
@@ -102,8 +102,13 @@ Agent:
|
|||||||
requirements: "httpx==0.28.1"
|
requirements: "httpx==0.28.1"
|
||||||
network_profile: agent-network
|
network_profile: agent-network
|
||||||
timeout_seconds: 300
|
timeout_seconds: 300
|
||||||
inline_task_count: 1
|
inline_task_count: 2
|
||||||
executable_task_owner_ref: agent_scripts/Stage_2_S2_00.yml#/Agent/Stages/0/tasks/0
|
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_id: S2-RETRY-TRANSIENT-READ-V1
|
||||||
retry_policy_ref: manifest/stage2_release.json#/retry_policies/0
|
retry_policy_ref: manifest/stage2_release.json#/retry_policies/0
|
||||||
idempotence_key_material:
|
idempotence_key_material:
|
||||||
@@ -122,6 +127,14 @@ Agent:
|
|||||||
|
|
||||||
request_contract:
|
request_contract:
|
||||||
fixed_path: stage2_control/s2_00_request.json
|
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
|
schema_ref: schemas/ingress.schema.json#/$defs/execution_request
|
||||||
directory_scan_allowed: false
|
directory_scan_allowed: false
|
||||||
alternate_request_path_allowed: false
|
alternate_request_path_allowed: false
|
||||||
@@ -383,7 +396,7 @@ Agent:
|
|||||||
llm_calls_allowed: false
|
llm_calls_allowed: false
|
||||||
llm_configuration_present: false
|
llm_configuration_present: false
|
||||||
inline_python_allowed_in_workflow_contract: 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
|
inline_schema_allowed: false
|
||||||
external_internet_access_allowed: false
|
external_internet_access_allowed: false
|
||||||
mcp_endpoint_allowlist:
|
mcp_endpoint_allowlist:
|
||||||
|
|||||||
@@ -16,6 +16,88 @@
|
|||||||
- 하위 에이전트(subagents)를 사용하여 핵심 테마와 교훈을 식별하고 MEMORY.md에 섹션으로 저장하라.
|
- 하위 에이전트(subagents)를 사용하여 핵심 테마와 교훈을 식별하고 MEMORY.md에 섹션으로 저장하라.
|
||||||
- 향후 세션 시작 시 MEMORY.md의 이전 섹션을 참조하라.
|
- 향후 세션 시작 시 MEMORY.md의 이전 섹션을 참조하라.
|
||||||
|
|
||||||
|
## 2026-10-02 — S2_00 v6 현행 코드·입출력·책임 경계 분석서
|
||||||
|
|
||||||
|
한 줄 요약: `agent_scripts/Analysis_Stage_2_S2_00_v.1.md`에 요청한 7개 목차로 현행 v6을 분석했다. 작업 DAG·자산·상태·검증 범위는 전략의 선언보다 실제 호출 경로를 기준으로 판독해야 한다.
|
||||||
|
|
||||||
|
- 단일 task 안의 C00~C15, 16개 고정 입력·manifest 신호, 배포 pin 55개와 선택 로딩, 자체 결과 5개 및 status-last·동일 완료본 재사용을 정리했다. 당시 다운로드한 실행 결과의 hash를 다시 대조했고 YAML·복제본의 hash는 보존했다.
|
||||||
|
- 성공한 manifest에는 `schema/schema_path`가 없어 신호별 payload schema 루프가 실행되지 않는 점, 고정 입력 16개 seal의 UNEVALUABLE, 미해결 review·UNMAPPED 신호 보존, config·slot·cluster의 의미 범위, 동시 writer·복구·후속 소비 계약의 미검증을 명시했다. 문서 7개 상위 목차·코드 행 번호·로컬 링크·fence를 확인했으며 YAML 변경이나 workspace 재실행은 하지 않았다.
|
||||||
|
|
||||||
|
## 2026-10-02 — S2_00 v6 실제 MCP·Stage 1 원본 판독 오류 수정 및 workspace 실행 성공
|
||||||
|
|
||||||
|
한 줄 요약: 자체 fixture가 실제 Localdocs 오류 문자열과 Stage 1 v.8 배열·count 구조를 반영하지 않아 파싱 실패·오차단이 발생했다. 실제 서버 함수와 다운로드한 원본으로 재현·수정하고 workspace에서 결과 파일까지 대조해야 실행 성공을 확인할 수 있다.
|
||||||
|
|
||||||
|
- Localdocs `read_binary_doc`의 `isError=false`인 `Error: Document not found: ...`를 JSON으로 파싱하던 오류를 수정했다. 누락은 해당 경로의 `LOCALDOCS_NOT_FOUND`로 구분하고 일반 도구 오류·잘못된 binary envelope는 도구명·경로를 포함해 보고한다. 이후 증거 `evidence_index`, `/items/*/event_candidates/*`·`candidate_id`, SG01 `domain_entries`, domain signal 3개 candidate 배열, compatibility view prefix, P3·P4 `counts.review_items`·`finalized_by`를 실제 Stage 1 writer 계약에 맞췄다. BO→event→evidence 연결과 B2 선언 개수 검증을 추가했으며 hash·누락·중복·review 차단 검증은 유지했다.
|
||||||
|
- 기존·실제 서버 응답 시험 51건 및 캡처한 Stage 1 원본·변조 거부 시험 7건, YAML·Python 3.11/3.12 문법 검증을 통과했다. `10월_1일_구성`에 현행 YAML을 `Stage_2_S2_00_v6`로 등록·실행하여 task `COMPLETED`, `exit_code=0`, `READY_WITH_ISSUES`, `PUBLISHED_STATUS_LAST`를 확인했다(전체 5.1초, code executor 2.402초). 고정 출력 `stage2_runs/from-stage1/s2_00/v6/`의 5개 파일을 다운로드했고 status SHA-256 `7ea037548ea729275ce4f791d7ae2ada2d381427f1971ea6f30ccc3e4041eed3`, 나머지 4개 artifact hash·byte length, 읽은 원본·배포 파일 81개 hash를 모두 대조했다. 검증은 PASS 19·FAIL 0·UNEVALUABLE 2이며 미제공 LES 선언 총수·event disposition은 추정하지 않았다. producer 정보가 원본에 없는 11건은 WARNING으로 남기고 기존 review 1,128건을 그대로 보존한다. context는 증거 30·event 71·BO 57·fact 57·LES 47, 신호 475, cluster 46, 미해결 참조 0이다. 실행 성공은 이 workspace 테스트에 한정한다.
|
||||||
|
- v4 원본은 `agent_scripts/Stage_2_S2_00_10_02_v.2.yml`, 중간 v5는 `agent_scripts/Stage_2_S2_00_10_02_v.3.yml`로 보존했다. 현행 `agent_scripts/Stage_2_S2_00.yml`은 overwrite했고 복제본은 본 폴더 `Stage_2_S2_00_v.6.yml`이다. v5 원격 진단을 보존하기 위해 출력만 고정 개정 폴더 `v6`로 구분했으며 별도 request/attempt ID를 만들지 않았다. Stage 1 원본·배포 및 S2_10~40은 수정하지 않았다.
|
||||||
|
|
||||||
|
## 2026-10-02 — S2_00 YAML v4 실제 입력 연결 개정
|
||||||
|
|
||||||
|
한 줄 요약: v3의 미치환 `prev` root·파일 참조를 제거하고, `run_inline_mcp`에 사건·배포 root를 직접 전달하여 인증된 localdocs에서 Stage 1 원본을 읽는 v4로 개정했다. 선행 task 없는 standalone 실행에 `prev` 결과가 자동 연결된다고 가정하지 않는 것이 핵심이다.
|
||||||
|
|
||||||
|
- Stage 1 v.8의 상대 저장 경로에 맞춰 기본 사건 root는 `.`(인증된 workspace), 배포 root는 `Default_Agent`로 명시한다. 다른 위치는 inline 상수 또는 함수 인자로 직접 지정한다. backend 치환은 문서화된 `__user_hash__`·`__workspace_hash__`만 사용하며, root·인증 미치환 오류는 해당 입력 이름을 표시한다. request/attempt ID·준비 task·control 파일을 추가하지 않았다.
|
||||||
|
- DEV 정책은 그대로 기록하되 `WORKSPACE_EXECUTION_TEST`만 허용하여 실행 테스트 산출물에 모드·정책 분류를 남긴다. 생산 모드는 계속 차단하며 원본·배포 pin·schema·signal/review 보존·두 read-pass·status-last·충돌 시 덮어쓰기 금지 검증을 유지한다. 기본 인자 그대로의 JSON/SSE 모의 실행을 포함한 44건과 YAML/DAG·Python 3.11/3.12 문법을 통과했다. 실제 backend 재등록·원격 실행·생산 배포 승인은 수행하지 않았다.
|
||||||
|
- 직전 v3는 `Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00_10_02_v.1.yml`로 byte-identical 보존하고 현행 YAML을 overwrite했다. 개정 복제본은 본 폴더 `Stage_2_S2_00_v.4.yml`이다. 이번 변경은 지정된 YAML 3개·MEMORY에 한정하며 S2_10~40·전략서·SKILL·notebook·release/mirror는 보존한다.
|
||||||
|
|
||||||
|
## 2026-10-02 — S2_00 YAML v3 직접 인계 구현
|
||||||
|
|
||||||
|
한 줄 요약: 전략 v3·지정 SKILL·Code Executor notebook에 따라 S2_00을 단일 deterministic ingress로 개정했다. Stage 1 사건·배포 root와 `{{prev.###}}` 결과물을 직접 재사용하며 별도 request/attempt/run ID와 준비 task를 제거한다.
|
||||||
|
|
||||||
|
- C00–C15의 원본 계약·seal·hash·보존 검증, 원본 pointer 정규화, claim-neutral cluster·의존 관계·review/signal 보존을 유지한다. 필요한 Stage 1 배포 의존만 읽고 자체 5개 산출물을 검증한 뒤 status를 마지막에 기록하며, 완료본은 byte 일치 시 재사용하고 불완전·충돌 출력은 덮어쓰지 않는다. 기존 중첩 Counter 비교 오류와 원본 배열 pointer 불일치도 수정했다.
|
||||||
|
- 원본은 `Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00_10_02.yml`, 개정 복제본은 본 폴더 `Stage_2_S2_00_v.3.yml`로 byte-identical 저장했다. 오프라인·모의 시험 39건, YAML/DAG·Python 3.11/3.12 문법, Stage 1 승인 의존 55개 hash와 선택적 의존 로딩을 확인했다. 실제 backend 치환·MCP 실행·동일 root 동시 writer·배포 성공은 검증하지 않았다.
|
||||||
|
- 기존 `DEV_FIXTURE_RELEASE`의 실제 사건 발행 차단은 유지한다. S2_10~40·전략서·SKILL·notebook·schema·mirror·release는 변경하지 않았으며 builder/재봉인/live 실행/commit도 수행하지 않았다. 단일 YAML 구현 검증을 기존 패키지 전체 정합성이나 운영 승인으로 해석하지 않는 것이 핵심이다.
|
||||||
|
|
||||||
|
## 2026-10-02 — S2_00 Stage 1 결과 참조 반영 전략 v3
|
||||||
|
|
||||||
|
한 줄 요약: v2를 보존하고 `Default_Agent/Stage_2_Clean/agent_scripts/stage_2_s2_00_revision_strategy_v3.md`를 생성하여 `{{prev.###}}`(`###`는 Stage 1 결과물 파일)를 통한 원본 사용과 backend Python 실행 환경의 허용을 사용자 설명에 따른 설계 전제로 반영했다.
|
||||||
|
|
||||||
|
- Stage 1 사건·배포 root 직접 전달, request ID 전면 제거, S2_00 YAML 한 파일 개정·S2_10~40 계약 제외 원칙은 유지한다. v2의 참조 지원 여부 미확인·전달 방식 탐색 전제를 대체하고 결과 참조를 목표·입력 경계·원본 사용·변경 순서·수용 기준에 연결했다.
|
||||||
|
- 지원되는 기능의 존재와 개정 YAML에서의 실제 파일/root 대응·원본 동일성·발행 성공 검증을 구별한다. 문서 구조·로컬 링크 5개·미확인 전제 제거·요약 위치와 기존 610개 파일의 hash 보존을 정적으로 확인했다. MEMORY 기존 내용도 삽입 외에는 보존했다. 이번 작성은 v3 전략서·본 요약만 변경했으며 YAML/자산 개정·backend/live 시험·배포·commit은 수행하지 않았다.
|
||||||
|
|
||||||
|
## 2026-10-02 — S2_00 한정·request ID 제거 개정 전략 v2
|
||||||
|
|
||||||
|
한 줄 요약: `Default_Agent/Stage_2_Clean/agent_scripts/stage_2_s2_00_revision_strategy_v2.md`를 작성하여 Stage 1 사건·배포 root 직접 전달, 별도 `request_id` 입력·내부 생성·출력 제거, S2_00 배포 YAML 한 파일 개정을 원칙으로 확정했다.
|
||||||
|
|
||||||
|
- v1의 내부 호환 ID·downstream schema/route·49개 자산 일괄 유지·다중 파일 재봉인 전제를 대체한다. S2_10~40의 자산 사용 계약을 무시하고 C00–C15 자체 기능, 필요한 원본 검증·의미 보존·자체 출력과 마지막 status만 완료 기준으로 삼는다.
|
||||||
|
- 기존 사건 root·원본 식별값·검증 hash를 재사용하고 임시 디렉터리로 작업을 격리한다. 직접 전달에 ID 처리를 추가하는 것은 효율 향상 수단이 아니며, 불필요한 전달층·의존 읽기를 제거하는 것이 핵심 교훈이다. backend 인자 결속·원격 동시 writer·DEV guard에 따른 live 제한과 기존 패키지 봉인 미충족 가능성은 남긴다.
|
||||||
|
- 이번 작성은 전략서·본 요약만 변경했다. 저장 경로·로컬 링크 5개·UTF-8/fence·요약 위치와 기존 609개 파일의 hash 보존을 정적으로 확인했다. MEMORY 기존 내용도 삽입 외에는 보존했다. 실제 YAML/자산 개정·실행 시험·builder·재봉인·배포·commit은 수행하지 않았다. v1은 과거 전략으로 보존한다.
|
||||||
|
|
||||||
|
## 2026-10-02 — S2_00 직접 인계 개정 전략 v1
|
||||||
|
|
||||||
|
한 줄 요약: `Default_Agent/Stage_2_Clean/agent_scripts/stage_2_s2_00_revision_strategy_v1.md`에 두 Stage 1 root를 직접 받아 단일 ingress task에서 검증·C00–C15·발행을 수행하고, prepare·고정 request 파일·외부 ID 부여를 없애는 최소 변경 전략을 작성했다.
|
||||||
|
|
||||||
|
- Stage 1 원본과 16개+manifest 신호·배포 55개·Stage 2 직접 자산 49개, 두 read-pass·기존 출력/barrier를 재사용하며 ID는 내부 호환 값으로 처리한다. 입력 전달 단순화와 기존 slice·pointer·review 의미 보완을 구별하는 것이 핵심 교훈이다.
|
||||||
|
- 지정된 `Stage_2_10_Analysis_v1.md`는 실제 S2_10 분석이므로 구형 S2_00 YAML·`Stage_2_00_Analysis_v1.md`와 교차 확인했다. backend 직접 입력 binding과 DEV release 제한은 미해결로 명시했다. 전략서·본 요약만 작성했으며 YAML/자산 개정·재봉인·시험/live 실행·배포·commit은 수행하지 않았다.
|
||||||
|
- 문서 링크·16개 입력 목록·구조와 요약 위치를 정적으로 확인했고 참조 원본 15개의 SHA-256은 작업 전후 동일했다.
|
||||||
|
|
||||||
|
## 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 준비 계획 및 구현 경계 정정
|
||||||
|
|
||||||
|
한 줄 요약: S2_00 입출력 문서와 request 준비 계획·단순화 v1을 작성하고 원본 YAML을 보존했으며, request 생성 코드 구현과 backend 운영 연결 검증을 분리해야 한다는 판단으로 정정했다.
|
||||||
|
|
||||||
|
- `Stage_2_00_IO_info.md`: 배포 YAML·분석서·release를 대조하여 DAG, 단계별 파일명·형식·경로, Stage 1 고정 입력 16개·배포 의존 55개·Stage 2 직접 의존 49개, 정상 11+E/diagnostic 5개 출력 및 임시/최종 저장을 문서화했다. 사건별 root·signal 목록·digest·cluster ID는 실제 값이 아닌 결정 규칙으로 구분했다.
|
||||||
|
- `../../plans/s2-00-request-preparation.md` 및 `_v1.md`: 두-task request 준비 계획을 작성하고, v1은 동일 불변 외부 인자에서 request를 재구성하여 대조하는 방식으로 별도 준비 receipt 전달·중복 검증을 줄였다. 구현 범위는 v1의 Objective 이하이며, 기존 6필드·canonical JSON·검증·localdocs write/read-back·후행 실패 차단·동시 실행 통제·관련 hash 일관성을 유지한다.
|
||||||
|
- `Default_Agent/Stage_2_Clean/agent_scripts/Stage_2_S2_00_outdated_9_09.yml`에 기존 배포본을 byte-identical로 보존했다(SHA-256 `94c2744d71d222cfb5cc1b2a0d33a6cda20079439823de431eef378a2fa5c943`). 기존 builder `--check`는 `PARITY_PASS`, mismatches=[]였으며 변경 전 기준선만 검증했다. 활성 YAML·authoring/runtime/binding은 변경하지 않았고 request writer 및 신규 YAML 구현·live 실행은 미완료다.
|
||||||
|
- Stage 1 Default_Agent 자산, v.8 YAML 4개 및 분석 보고서 4개를 확인했다. task 의존·동적 확장·workspace 치환에 관한 단서는 있으나 S2_00 외부 인자 공급·success-only·workspace 직렬화의 backend 구현 전체를 입증하지는 않는다.
|
||||||
|
- **정정·후속 원칙:** backend 정보 미확인을 이유로 생성 코드 구현까지 중단한 것은 과도했다. 기존 Stage 2 방식과 일관된 생성·검증·저장 함수를 네 외부 인자를 받도록 구현하고 offline 시험하는 데 backend 전체 구현·명세는 필요하지 않다. 실제 인자 주입·실패 차단·동시 실행의 운영 연결은 필요한 backend 정보만 확보하여 별도로 완성·검증한다. 미지원 템플릿·환경변수·잠금 기능을 임의로 가정하거나 offline 성공을 live-ready로 표현하지 않는다.
|
||||||
|
|
||||||
|
## 2026-09-17 — Stage 2 외부 규칙 문서·Weaviate 절차에 관한 의견 평가
|
||||||
|
|
||||||
|
외부 문서 접근과 임베딩 검색 위임이 가능하다는 가정 아래 현행 배포 YAML 5종·분석서·관련 자산 및 Stage 1 인계 자료를 대조하여 `Evaluaton_on_hogyus_opinion.md`를 작성하였다. C25/C26의 사건종류·규칙 선택과 Markdown 원문 적재·해시 검증, C27~C29의 Weaviate `search_hybrid` 호출·요건사실 팩 구성은 존재하므로 “절차 부재”라는 평가는 정정하되, 승인된 규칙·검색 범위의 미충족, 규칙·청크 본문의 S2_30 전달 공백, 고정된 요건별 gap 자료, V12 필드와 S2_30→40 해시 대상 불일치는 접근 가능성만으로 해결되지 않음을 구분하였다. 외부 공용 자산의 부재를 추정하지 않았으며, 절차·출처 참조의 존재와 실제 내용 활용·요건 충족·실행 및 법률 검증을 분리하는 것이 핵심 교훈이다. 핵심 경로 독립 검토와 문서 링크·해시 확인을 수행하고 해시 전사 오류 1건을 수정하였으며, 실행 자산 변경·live LLM/MCP/Weaviate·E2E·법률검수·기존 회귀시험 재실행은 수행하지 않았다.
|
||||||
|
|
||||||
## 2026-09-09 — Stage 2 배포 YAML 5종 전문 분석과 단계 간 계약 차이 기록
|
## 2026-09-09 — Stage 2 배포 YAML 5종 전문 분석과 단계 간 계약 차이 기록
|
||||||
|
|
||||||
한 줄 요약: 배포 YAML 00/10/20/30/40 총 16,100행을 독립 병렬 분담해 전문 분석하고 각 분석서에 정확히 2회 검증·증분수정을 수행했으며, “구현/봉인/과거 offline PASS”와 “현재 의미 인계·live 실행·법률 승인”을 구별해야 한다는 교훈을 확인했다.
|
한 줄 요약: 배포 YAML 00/10/20/30/40 총 16,100행을 독립 병렬 분담해 전문 분석하고 각 분석서에 정확히 2회 검증·증분수정을 수행했으며, “구현/봉인/과거 offline PASS”와 “현재 의미 인계·live 실행·법률 승인”을 구별해야 한다는 교훈을 확인했다.
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
# Stage 2_00 작업분석서
|
# 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. 분석 범위와 결론
|
## 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 등은 이전 작업 기록이며 본 분석에서 새로 실행한 시험 결과가 아니다.
|
다만 현재 코드와 배포계약만으로 “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>/` |
|
| `O/` | `W/stage2_runs/by-binding/<run_binding_digest>/` |
|
||||||
| `T/` | Code Executor에서 `TemporaryDirectory(prefix="liti-s2-00-")`로 만드는 임시 root |
|
| `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
|
## 1. 전체 작업 DAG
|
||||||
|
|
||||||
@@ -95,13 +102,13 @@ stdout 단일 JSON inner receipt + exit 0
|
|||||||
(저장 이후 read-back/close 실패도 같은 label; 저장상태 재확인 필요)
|
(저장 이후 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. IO 구조
|
||||||
|
|
||||||
### 2.1 외부 제어 입력과 Stage 1 사건 입력
|
### 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 이후의 사용 |
|
| 입력 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/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 금지 |
|
| `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 출력 파일과 소비자
|
### 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 | 내용 및 직접/후속 소비 |
|
| 출력 경로 | 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의 문자열 필드**다.
|
`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. 작업용 자산과 정확한 배포 위치
|
## 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/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 책임과 구분 |
|
| `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개
|
### 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 또는 닫힌 전개 규칙 | 개수·사용 |
|
| lock ID 범위 | exact path 또는 닫힌 전개 규칙 | 개수·사용 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
@@ -218,7 +225,7 @@ signal_manifest.schema.json
|
|||||||
|
|
||||||
### 3.3 Stage 2 direct closure 49개
|
### 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 | 개수·역할 |
|
| path prefix | exact basename | 개수·역할 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
@@ -330,7 +337,7 @@ ACTIO-DEFENSE-MAP.yml
|
|||||||
|
|
||||||
### 4.1 C05 검사 세부 목록
|
### 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의 서로 다른 의미
|
### 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를 같은 값으로 취급하면 안 된다.
|
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 자료 의존
|
## 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 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 |
|
| 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. 결정성·발행·실패의 의미
|
## 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파일 자동 발행이 아님 |
|
| 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 |
|
| `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를 재확인해야 한다.
|
`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를 재확인해야 한다.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,344 @@
|
|||||||
|
# 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_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을 거절한다. 아래 출력은 해당 실행 경로가 정상적으로 완료되는 경우의 코드상 산출물이다.
|
||||||
|
|
||||||
|
| 기호 | 정확한 위치·결정 방식 |
|
||||||
|
|---|---|
|
||||||
|
| `M/` | `prompt_updates_sequential/Case_02_Comparison_Research/YAML_Prompts/2. Stage_2/` — 이 문서 저장 폴더 |
|
||||||
|
| `R/` | `M/Default_Agent/Stage_2_Clean/` — 로컬 배포 패키지 |
|
||||||
|
| `W/` | localdocs가 user/workspace session에 결속하는 논리 workspace root. 로컬 Dropbox 절대경로와 동일하다고 가정하지 않음 |
|
||||||
|
| `A/` | `W/Default_Agent/Stage_2_Clean/` — 실행 시 Stage 2 자산 source root. 로컬 대응 위치는 `R/` |
|
||||||
|
| `U/` | `W/<s2_00_request.json의 stage1_run_root_ref>/` — Stage 1 사건 입력 root |
|
||||||
|
| `D/` | `W/<s2_00_request.json의 stage1_deployment_root_ref>/` — Stage 1 고정 배포 source root |
|
||||||
|
| `O/` | `W/stage2_runs/by-binding/<run_binding_digest>/` — 최종 파일 저장 root |
|
||||||
|
| `T/` | Code Executor의 `TemporaryDirectory(prefix="liti-s2-00-")`로 생성한 임시 root |
|
||||||
|
|
||||||
|
`U/`, `D/`, `T/`, `<run_binding_digest>`, `<cluster_id>`의 실제 값은 실행 입력·환경·내용에 따라 결정된다. 분석 자료만으로 실제 사건 폴더명이나 digest를 확정할 수 없으므로, 임의 값 대신 정확한 결정식을 기재한다. 각 표의 **folder + file name**을 이어 붙이면 전체 경로가 된다. 대소문자와 점·밑줄은 원문대로 유지하였다.
|
||||||
|
|
||||||
|
근거: 분석서 §0.1·§2·§6, Y:194–227, 4544–4612, 5548–5591, 5957–5959, 6032–6059.
|
||||||
|
|
||||||
|
분석 YAML SHA-256: `94c2744d71d222cfb5cc1b2a0d33a6cda20079439823de431eef378a2fa5c943`. 분석서에 기록된 YAML hash와 일치한다. 분석서 SHA-256: `905abf5d93d67b7ab7f9f55e0197e320a69c2d909859dcba145e173a13b65253`.
|
||||||
|
|
||||||
|
## 2. 작업 DAG structure
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
IN[Agent IN] --> TASK[Task_S2_00_deterministic_ingress: 단일 code-executor.run_code]
|
||||||
|
TASK --> H0[MCP 세션 초기화 · request/release 읽기]
|
||||||
|
H0 --> H1[exact input closure 2회 읽기 · hash/bytes 대조 · 임시 파일 hydration]
|
||||||
|
H1 --> C00[C00: source/schema/producer/identity/seal · signal ALL 검증]
|
||||||
|
C00 --> C05[C05: review 정규화 · 보존검사]
|
||||||
|
C05 --> B[run binding · artifact header]
|
||||||
|
B --> C10[C10: case/evidence/object/party/slot context]
|
||||||
|
C10 --> C15A[C15: membership · cluster · SCC · scheduling wave]
|
||||||
|
C15A --> C15B[C15: slice · bundle/cohort · invariant]
|
||||||
|
C15B --> ROUTE[executable/residual 분할 · route · manifest/intake/issue/status 조립]
|
||||||
|
ROUTE --> N[정상: 공통 4 + context 7 + slice E]
|
||||||
|
ROUTE --> D[diagnostic: 공통 4 + technical diagnostic 1]
|
||||||
|
N --> L[원본 snapshot 재검사 결과 반영 · output schema 검사 · 임시 tree 저장/rename]
|
||||||
|
D --> L
|
||||||
|
L --> V[exact artifact set/hash 재검사]
|
||||||
|
V --> P[원격 non-status write/read-back · ingress_status 마지막 write/read-back]
|
||||||
|
P --> OUT[stdout JSON receipt · Agent OUT]
|
||||||
|
OUT --> EXT[외부 orchestrator: barrier 검증]
|
||||||
|
EXT --> S10[S2_10 또는 S2_10_WITH_ISSUES]
|
||||||
|
EXT --> S40[S2_40_STATUS_ONLY]
|
||||||
|
```
|
||||||
|
|
||||||
|
원본 snapshot 재검사는 실제 코드에서 route 결정 직후, manifest/intake/status 최종 조립 이전에 수행된다(Y:5114–5117). 도식의 저장 노드는 그 재검사 결과를 전제로 한다.
|
||||||
|
|
||||||
|
Agent의 실제 task edge는 `IN → Task_S2_00_deterministic_ingress → OUT` 하나다. C00·C05·C10·C15는 단일 Python 호출 내부의 논리 단계이다. **C00–C15 사이에는 중간 JSON 파일을 저장하고 다음 단계가 다시 읽는 구조가 없다.** 메모리 객체를 전달하고 마지막에 선택된 출력 집합을 일괄 저장한다. S2_10/S2_40 호출은 YAML 외부 orchestrator의 작업이다.
|
||||||
|
|
||||||
|
예외가 발생하면 `ok:false / FAILED_NO_BARRIER` receipt 및 exit 2로 빠질 수 있다. 이는 diagnostic 5파일이 자동 생성된다는 뜻도, 이미 저장한 원격 파일이 없다는 보장도 아니다.
|
||||||
|
|
||||||
|
근거: 분석서 §1·§4·§6, Y:4801–5243, 5896–6103, 6230–6248.
|
||||||
|
|
||||||
|
## 3. 전체 입력 파일 목록
|
||||||
|
|
||||||
|
### 3.1 제어·자산 메타데이터 및 출력 검증 schema
|
||||||
|
|
||||||
|
| ID | 정확한 file name | file format | source folder | 사용 단계 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| CTRL-1 | `s2_00_request.json` | JSON, UTF-8 | `W/stage2_control/` | H0/H1: 6개 request 필드와 source root 결정 |
|
||||||
|
| CTRL-2 | `stage2_release.json` | JSON | `A/manifest/` | H0/H1 및 C00–C15: raw hash pin·입력 및 bundle 계약 |
|
||||||
|
| META-1 | `module_manifest.json` | JSON | `A/manifest/` | H1: 아래 schema 3개 선택·hash 검증 |
|
||||||
|
| SCHEMA-1 | `ingress.schema.json` | JSON Schema (`.json`) | `A/schemas/` | hydration, manifest/intake/status/diagnostic 출력 검증 |
|
||||||
|
| SCHEMA-2 | `context.schema.json` | JSON Schema (`.json`) | `A/schemas/` | hydration, header 및 context/slice/bundle 검증 |
|
||||||
|
| SCHEMA-3 | `review_status.schema.json` | JSON Schema (`.json`) | `A/schemas/` | hydration, issue ledger 검증 |
|
||||||
|
|
||||||
|
request는 정확히 `schema_version`, `workflow_id`, `request_id`, `attempt_id`, `stage1_run_root_ref`, `stage1_deployment_root_ref`의 6필드이다. 임의 output root를 받지 않는다. YAML 실행 정의 자체는 `R/agent_scripts/Stage_2_S2_00.yml`(YAML)이며 backend가 그 안의 Python code를 실행한다. 별도 `.py` 파일을 사건 입력으로 읽는 방식이 아니다.
|
||||||
|
|
||||||
|
근거: Y:194–208, 637–682, 5548–5591, 5721–5753.
|
||||||
|
|
||||||
|
### 3.2 Stage 1 사건 고정 입력 16개
|
||||||
|
|
||||||
|
아래 모두 strict UTF-8 JSON이다. H1에서 전부 필수 읽기 및 임시 복제하고 C00에서 계약 검증한다. 단계별 의미 사용은 §5에 정리한다. source folder는 원격 `U/` 기준이며 core가 읽는 대응 폴더는 `T/stage1_run/`이다.
|
||||||
|
|
||||||
|
| ID | logical input ID | 정확한 file name | file format | source folder |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| I01 | `evidence_indexed` | `evidence_indexed.json` | JSON | `U/` |
|
||||||
|
| I02 | `evidence_event_candidates` | `evidence_event_candidates.json` | JSON | `U/` |
|
||||||
|
| I03 | `client_goal` | `client_goal.json` | JSON | `U/` |
|
||||||
|
| I04 | `domain_screening` | `domain_screening.json` | JSON | `U/routing/` |
|
||||||
|
| I05 | `domain_activation_manifest` | `domain_activation_manifest.json` | JSON | `U/routing/` |
|
||||||
|
| I06 | `b1_evidence_indexed_gate` | `B1_evidence_indexed_gate.json` | JSON | `U/quality_gates/` |
|
||||||
|
| I07 | `b2_event_candidates_gate` | `B2_event_candidates_gate.json` | JSON | `U/quality_gates/` |
|
||||||
|
| I08 | `stage1_part1_soft_gate_handoff` | `stage1_part1_soft_gate_handoff.json` | JSON | `U/quality_gates/` |
|
||||||
|
| I09 | `bo` | `BO.json` | JSON | `U/` |
|
||||||
|
| I10 | `signal_manifest` | `signal_manifest.json` | JSON | `U/signals/` |
|
||||||
|
| I11 | `stage1_part2_review_handoff` | `stage1_part2_review_handoff.json` | JSON | `U/quality_gates/` |
|
||||||
|
| I12 | `legal_effect_structures` | `legal_effect_structures.json` | JSON | `U/` |
|
||||||
|
| I13 | `stage1_part3_review_handoff` | `stage1_part3_review_handoff.json` | JSON | `U/quality_gates/` |
|
||||||
|
| I14 | `fact_ledger_base` | `Fact_Ledger_base.json` | JSON | `U/` |
|
||||||
|
| I15 | `fact_ledger_writer_report` | `fact_ledger_writer_report.json` | JSON | `U/stage1_tmp/fact_ledger/` |
|
||||||
|
| I16 | `stage1_part4_review_handoff` | `stage1_part4_review_handoff.json` | JSON | `U/quality_gates/` |
|
||||||
|
|
||||||
|
### 3.3 manifest 전개 입력·조건부 입력
|
||||||
|
|
||||||
|
| 입력 | 정확한 file name / path 결정 규칙 | file format | source folder | 현재 확정 범위 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| signal payload family | `basename(signal_manifest.files[i].path)` | JSON | `U/signals/<dirname(files[i].path)>/`(dirname이 비어 있으면 `U/signals/`) | `U/signals/signal_manifest.json`의 모든 `files[]`를 전개. 전체 파일명 확정에는 실제 사건 manifest 필요 |
|
||||||
|
| SG-01 activation | `domain_activation_manifest.json` | JSON | `U/signals/` | 위 family 내부에서 core가 명시적으로 찾는 파일. `U/routing/`의 동명 파일과 별개 |
|
||||||
|
| contract manifest | `basename(release.dependency_locks.stage1.contract_manifest_ref.path)` | JSON | `D/<dirname(path)>/` | 현 release는 path=null. 현재 고정 입력에는 추가하지 않음 |
|
||||||
|
| completion seal | `basename(release.dependency_locks.stage1.completion_seal_ref.path)` | JSON | `U/<dirname(path)>/` | 현 release는 ref 없음. 현재 고정 입력에는 추가하지 않음 |
|
||||||
|
|
||||||
|
signal의 정확한 전체 경로는 `U/signals/<files[i].path>`. `files[i].path` 자체에 `signals/` prefix를 포함하면 금지된다. canonical/domain_signal은 의미 처리, compatibility_view는 무결성 검증 대상이다. 디렉터리 scan이나 추측한 고정 파일 목록으로 대체하지 않는다.
|
||||||
|
|
||||||
|
근거: 분석서 §2.1, Y:1420–1591, 4895–4914, 5635–5720, 6162–6183.
|
||||||
|
|
||||||
|
### 3.4 Stage 1 고정 배포 입력 55개
|
||||||
|
|
||||||
|
source-of-truth: [stage2_release.json](Default_Agent/Stage_2_Clean/manifest/stage2_release.json)의 `/dependency_locks/stage1/concrete_paths`. H1에서 **전부** raw hash를 검증하고 `T/stage1_deployment/`의 같은 상대경로로 복제한다. C00 검증과 C10 slot 구성에서 사용한다. schema 파일도 직렬화 형식은 JSON이다. 근거: 분석서 §3.2, Y:4164–4242, 5670–5698.
|
||||||
|
|
||||||
|
| 번호 | 정확한 file name | file format | source folder |
|
||||||
|
|---|---|---|---|
|
||||||
|
| D01 | `runtime_manifest.json` | JSON | `D/` |
|
||||||
|
| D02 | `_registry_index.json` | JSON | `D/domains/` |
|
||||||
|
| D03 | `signal_registry.v2.json` | JSON | `D/signals/` |
|
||||||
|
| D04 | `domain_config.json` | JSON | `D/domains/E-00/` |
|
||||||
|
| D05 | `domain_config.json` | JSON | `D/domains/E-01/` |
|
||||||
|
| D06 | `domain_config.json` | JSON | `D/domains/E-02/` |
|
||||||
|
| D07 | `domain_config.json` | JSON | `D/domains/E-03/` |
|
||||||
|
| D08 | `domain_config.json` | JSON | `D/domains/E-04/` |
|
||||||
|
| D09 | `domain_config.json` | JSON | `D/domains/E-05/` |
|
||||||
|
| D10 | `domain_config.json` | JSON | `D/domains/E-06/` |
|
||||||
|
| D11 | `domain_config.json` | JSON | `D/domains/E-07/` |
|
||||||
|
| D12 | `domain_config.json` | JSON | `D/domains/E-08/` |
|
||||||
|
| D13 | `domain_config.json` | JSON | `D/domains/E-09/` |
|
||||||
|
| D14 | `domain_config.json` | JSON | `D/domains/E-10/` |
|
||||||
|
| D15 | `domain_config.json` | JSON | `D/domains/E-11/` |
|
||||||
|
| D16 | `domain_config.json` | JSON | `D/domains/E-12/` |
|
||||||
|
| D17 | `domain_config.json` | JSON | `D/domains/E-13/` |
|
||||||
|
| D18 | `domain_config.json` | JSON | `D/domains/E-14/` |
|
||||||
|
| D19 | `domain_config.json` | JSON | `D/domains/E-15/` |
|
||||||
|
| D20 | `domain_config.json` | JSON | `D/domains/E-16/` |
|
||||||
|
| D21 | `domain_config.json` | JSON | `D/domains/E-17/` |
|
||||||
|
| D22 | `domain_config.json` | JSON | `D/domains/E-18/` |
|
||||||
|
| D23 | `domain_config.json` | JSON | `D/domains/E-19/` |
|
||||||
|
| D24 | `domain_config.json` | JSON | `D/domains/E-20/` |
|
||||||
|
| D25 | `domain_config.json` | JSON | `D/domains/E-21/` |
|
||||||
|
| D26 | `domain_config.json` | JSON | `D/domains/EC-00/` |
|
||||||
|
| D27 | `domain_config.json` | JSON | `D/domains/X1/` |
|
||||||
|
| D28 | `domain_config.json` | JSON | `D/domains/X2/` |
|
||||||
|
| D29 | `domain_config.json` | JSON | `D/domains/X3/` |
|
||||||
|
| D30 | `client_goal_domain_profiles.schema.json` | JSON Schema | `D/platform/schemas/` |
|
||||||
|
| D31 | `domain_fanout_plan.schema.json` | JSON Schema | `D/platform/schemas/` |
|
||||||
|
| D32 | `domain_seed_output.schema.v3.json` | JSON Schema | `D/platform/schemas/` |
|
||||||
|
| D33 | `domain_slice.schema.v2.json` | JSON Schema | `D/platform/schemas/` |
|
||||||
|
| D34 | `fact_exception_pack.schema.json` | JSON Schema | `D/platform/schemas/` |
|
||||||
|
| D35 | `fact_ledger_base.schema.json` | JSON Schema | `D/platform/schemas/` |
|
||||||
|
| D36 | `fact_ledger_candidate_bundle.schema.json` | JSON Schema | `D/platform/schemas/` |
|
||||||
|
| D37 | `legal_effect_structures.schema.json` | JSON Schema | `D/platform/schemas/` |
|
||||||
|
| D38 | `structure_seed_bundle.schema.json` | JSON Schema | `D/platform/schemas/` |
|
||||||
|
| D39 | `evidence_slot_status.schema.json` | JSON Schema | `D/signals/_common/` |
|
||||||
|
| D40 | `signal_item.schema.json` | JSON Schema | `D/signals/_common/` |
|
||||||
|
| D41 | `domain_activation_manifest.schema.json` | JSON Schema | `D/signals/schemas/` |
|
||||||
|
| D42 | `procedural_posture_relief_signals.schema.json` | JSON Schema | `D/signals/schemas/` |
|
||||||
|
| D43 | `party_capacity_standing_signals.schema.json` | JSON Schema | `D/signals/schemas/` |
|
||||||
|
| D44 | `governing_law_version_signals.schema.json` | JSON Schema | `D/signals/schemas/` |
|
||||||
|
| D45 | `legal_relation_lifecycle_signals.schema.json` | JSON Schema | `D/signals/schemas/` |
|
||||||
|
| D46 | `timeline_notice_condition_signals.schema.json` | JSON Schema | `D/signals/schemas/` |
|
||||||
|
| D47 | `asset_right_state_signals.schema.json` | JSON Schema | `D/signals/schemas/` |
|
||||||
|
| D48 | `liability_causation_damage_signals.schema.json` | JSON Schema | `D/signals/schemas/` |
|
||||||
|
| D49 | `defense_exception_signals.schema.json` | JSON Schema | `D/signals/schemas/` |
|
||||||
|
| D50 | `evidence_proof_conflict_signals.schema.json` | JSON Schema | `D/signals/schemas/` |
|
||||||
|
| D51 | `calculation_requirements.schema.json` | JSON Schema | `D/signals/schemas/` |
|
||||||
|
| D52 | `remedy_enforcement_signals.schema.json` | JSON Schema | `D/signals/schemas/` |
|
||||||
|
| D53 | `legal_effect_routes.schema.json` | JSON Schema | `D/signals/schemas/` |
|
||||||
|
| D54 | `domain_signal_envelope.schema.v2.json` | JSON Schema | `D/signals/schemas/` |
|
||||||
|
| D55 | `signal_manifest.schema.json` | JSON Schema | `D/signals/schemas/` |
|
||||||
|
|
||||||
|
### 3.5 Stage 2 직접 의존 입력 49개
|
||||||
|
|
||||||
|
source-of-truth: release `/dependency_locks/stage2_direct`. H1이 아래 **49개 전부를 읽고** `T/stage2_asset/`의 같은 상대경로에 복제한다. C15가 S2_10 Agent/binding의 opaque hash와 release가 선택한 context를 결속한다. 읽은 모든 profile을 후속 LLM에 제공한다는 뜻은 아니다. 현 release의 `/bundle/selected_context_refs`는 빈 배열이다. 근거: 분석서 §3.3, Y:3283–3475, 5741–5753.
|
||||||
|
|
||||||
|
| 번호 | 정확한 file name | file format | source folder |
|
||||||
|
|---|---|---|---|
|
||||||
|
| A01 | `Stage_2_S2_10.yml` | YAML | `A/agent_scripts/` |
|
||||||
|
| A02 | `stage2_s2_10_llm_binding.yml` | YAML | `A/deployment/` |
|
||||||
|
| A03 | `authority_registry.yml` | YAML | `A/registry/authority/` |
|
||||||
|
| A04 | `authority_release.json` | JSON | `A/manifest/` |
|
||||||
|
| A05 | `EC-00_contract_general.yml` | YAML | `A/registry/substantive/` |
|
||||||
|
| A06 | `E-01_juristic_act_validity.yml` | YAML | `A/registry/substantive/` |
|
||||||
|
| A07 | `E-02_contract_money_claim.yml` | YAML | `A/registry/substantive/` |
|
||||||
|
| A08 | `E-03_parties_liability_succession.yml` | YAML | `A/registry/substantive/` |
|
||||||
|
| A09 | `E-04_unjust_enrichment.yml` | YAML | `A/registry/substantive/` |
|
||||||
|
| A10 | `E-05_tort_general.yml` | YAML | `A/registry/substantive/` |
|
||||||
|
| A11 | `E-06_professional_liability.yml` | YAML | `A/registry/substantive/` |
|
||||||
|
| A12 | `E-07_construction_defect.yml` | YAML | `A/registry/substantive/` |
|
||||||
|
| A13 | `E-08_lease_deposit.yml` | YAML | `A/registry/substantive/` |
|
||||||
|
| A14 | `E-09_registry_transfer_claims.yml` | YAML | `A/registry/substantive/` |
|
||||||
|
| A15 | `E-10_secured_registry.yml` | YAML | `A/registry/substantive/` |
|
||||||
|
| A16 | `E-11_possession_vindication.yml` | YAML | `A/registry/substantive/` |
|
||||||
|
| A17 | `E-12_co_ownership_boundary.yml` | YAML | `A/registry/substantive/` |
|
||||||
|
| A18 | `E-13_creditor_preservation.yml` | YAML | `A/registry/substantive/` |
|
||||||
|
| A19 | `E-14_execution_linked_claims.yml` | YAML | `A/registry/substantive/` |
|
||||||
|
| A20 | `E-15_succession_family_property.yml` | YAML | `A/registry/substantive/` |
|
||||||
|
| A21 | `E-16_negotiable_instruments.yml` | YAML | `A/registry/substantive/` |
|
||||||
|
| A22 | `E-17_labor_wage_claims.yml` | YAML | `A/registry/substantive/` |
|
||||||
|
| A23 | `E-18_org_resolution_status.yml` | YAML | `A/registry/substantive/` |
|
||||||
|
| A24 | `E-19_insurance_claims.yml` | YAML | `A/registry/substantive/` |
|
||||||
|
| A25 | `E-20_ip_claims.yml` | YAML | `A/registry/substantive/` |
|
||||||
|
| A26 | `E-21_media_personality_rights.yml` | YAML | `A/registry/substantive/` |
|
||||||
|
| A27 | `E-00_residual_unrouted.yml` | YAML | `A/profiles/crosscut/` |
|
||||||
|
| A28 | `X1_notice_lifecycle.yml` | YAML | `A/profiles/crosscut/` |
|
||||||
|
| A29 | `X2_asset_identity_lineage.yml` | YAML | `A/profiles/crosscut/` |
|
||||||
|
| A30 | `X3_procedure_standing_relief.yml` | YAML | `A/profiles/crosscut/` |
|
||||||
|
| A31 | `X4_response_admission_defense.yml` | YAML | `A/profiles/crosscut/` |
|
||||||
|
| A32 | `SL-AUTO_motor_vehicle.yml` | YAML | `A/profiles/special_law/` |
|
||||||
|
| A33 | `SL-INDUSTRIAL_ACCIDENT.yml` | YAML | `A/profiles/special_law/` |
|
||||||
|
| A34 | `SL-PRODUCT_LIABILITY.yml` | YAML | `A/profiles/special_law/` |
|
||||||
|
| A35 | `SL-RESIDENTIAL_LEASE.yml` | YAML | `A/profiles/special_law/` |
|
||||||
|
| A36 | `SL-COMMERCIAL_LEASE.yml` | YAML | `A/profiles/special_law/` |
|
||||||
|
| A37 | `SL-LABOR.yml` | YAML | `A/profiles/special_law/` |
|
||||||
|
| A38 | `SL-STATE_LIABILITY.yml` | YAML | `A/profiles/special_law/` |
|
||||||
|
| A39 | `SL-IP-PATENT.yml` | YAML | `A/profiles/special_law/` |
|
||||||
|
| A40 | `SL-IP-COPYRIGHT.yml` | YAML | `A/profiles/special_law/` |
|
||||||
|
| A41 | `SL-IP-OTHER.yml` | YAML | `A/profiles/special_law/` |
|
||||||
|
| A42 | `SL-MEDIA.yml` | YAML | `A/profiles/special_law/` |
|
||||||
|
| A43 | `SL-TRANSPORT_MARITIME.yml` | YAML | `A/profiles/special_law/` |
|
||||||
|
| A44 | `SL-CONSUMER_CONTRACT.yml` | YAML | `A/profiles/special_law/` |
|
||||||
|
| A45 | `ACTIO-MORTGAGE.yml` | YAML | `A/profiles/overlays/` |
|
||||||
|
| A46 | `ACTIO-MORTGAGE-CREATION.yml` | YAML | `A/profiles/overlays/` |
|
||||||
|
| A47 | `ACTIO-ENCUMBERED-TRANSFER.yml` | YAML | `A/profiles/overlays/` |
|
||||||
|
| A48 | `ACTIO-PRESERVED-CLAIM-BUNDLE.yml` | YAML | `A/profiles/overlays/` |
|
||||||
|
| A49 | `ACTIO-DEFENSE-MAP.yml` | YAML | `A/profiles/overlays/` |
|
||||||
|
|
||||||
|
## 4. 최종 출력 파일 목록
|
||||||
|
|
||||||
|
모든 출력 형식은 **canonical UTF-8 JSON**이다. 표의 `O/`는 최종 원격 저장 root이며, 같은 상대경로가 먼저 `T/core_output/` 아래에 저장된다. `E`는 cohort 검증 이후의 **최종 executable cluster 수**이다. 정상 branch(`TO_S2_10`, `TO_S2_10_WITH_ISSUES`)는 11+E개, diagnostic branch(`TO_S2_40_STATUS_ONLY`)는 정확히 5개다.
|
||||||
|
|
||||||
|
| ID | 정확한 file name | file format | 최종 저장 folder | branch | 생성 책임·내용 |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| O01 | `stage1_input_manifest.json` | JSON | `O/ingress/` | 공통 | C00 검사 결과·source snapshot·hydration receipt를 마지막에 조립 |
|
||||||
|
| O02 | `intake_report.json` | JSON | `O/ingress/` | 공통 | C00/C05 결과·source count·compact review/conservation report |
|
||||||
|
| O03 | `issue_ledger.base.json` | JSON | `O/review/` | 공통 | 누적 technical issue와 UNMAPPED review를 조립 |
|
||||||
|
| O04 | `ingress_status.json` | JSON | `O/ingress/` | 공통·마지막 write | route·run binding·exact artifact hash 목록·barrier |
|
||||||
|
| O05 | `case_context.json` | JSON | `O/context/` | 정상 | C10: fact/BO/LES/evidence/event/signal 등의 참조 context |
|
||||||
|
| O06 | `evidence_inventory.json` | JSON | `O/context/` | 정상 | C10: evidence-event-fact 연결·provenance |
|
||||||
|
| O07 | `object_registry.json` | JSON | `O/context/` | 정상 | C10: BO object occurrence·ID·label·lineage |
|
||||||
|
| O08 | `party_and_title_context.json` | JSON | `O/context/` | 정상 | C10: party occurrence와 title/role 등 context |
|
||||||
|
| O09 | `slot_crosswalk.json` | JSON | `O/context/` | 정상 | C10: domain config의 slot skeleton |
|
||||||
|
| O10 | `cluster_plan.json` | JSON | `O/context/` | 정상 | C15: cluster·SCC·wave·executable/residual |
|
||||||
|
| O11 | `bundle_plan.json` | JSON | `O/context/` | 정상 | C15: selected context·cohort·slice·S2_10 binding |
|
||||||
|
| O12 | `<cluster_id>.json` | JSON | `O/context/cluster_slices/` | 정상·E개 | C15: 최종 executable cluster별 immutable slice |
|
||||||
|
| O13 | `technical_diagnostic.json` | JSON | `O/ingress/` | diagnostic만 | route 조립: reason·source ref·context_published=false |
|
||||||
|
|
||||||
|
O12의 실제 basename은 코드가 계산한 `cluster_id`에 `.json`을 붙인 값이다. residual/non-executable cluster의 slice는 최종 출력 집합에 포함하지 않는다. diagnostic branch에는 O05–O12가 없다.
|
||||||
|
|
||||||
|
근거: 분석서 §2.2, Y:5180–5243.
|
||||||
|
|
||||||
|
## 5. DAG 각 단계별 Input / Output 연결
|
||||||
|
|
||||||
|
아래 ID는 §3·§4의 정확한 이름·형식·폴더 행을 참조한다. C00 이후의 '입력'은 원칙적으로 hydration된 파일을 파싱한 메모리 객체이다. '출력 Oxx'는 그 단계에서 준비하는 데이터의 **최종 파일 대응**이며 해당 단계 즉시 저장을 뜻하지 않는다.
|
||||||
|
|
||||||
|
| 단계·작업명 | Input file / 전달 객체 | Output file / 전달 객체와 저장 위치 | 근거 Y |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Agent IN·단일 executor 호출 | `R/agent_scripts/Stage_2_S2_00.yml`(YAML); user/workspace hash 및 인증·실행환경은 파일이 아닌 외부 주입 값 | Python 실행 시작. 별도 업무 출력 파일 없음 | 1–43, 6230–6248 |
|
||||||
|
| H0 MCP 초기화·제어 확정 | CTRL-1, CTRL-2 | request/release 메모리 객체. 별도 request 복사 파일 없음 | 5548–5591, 5757–5824 |
|
||||||
|
| H1 exact closure 2회 읽기·hydration | CTRL-1/2, META-1, SCHEMA-1–3, I01–I16, signal family, D01–D55, A01–A49; 조건이 충족되면 contract/completion | §6.1에 명시한 임시 파일 복제. `hydration_stability_receipt`는 메모리 객체, 이후 O01에 포함 | 5602–5893 |
|
||||||
|
| C00 source/schema/producer/identity | I01–I16, release, D01–D55 및 조건부 contract/completion | source contract rows·snapshots·issues. 이후 O01/O02/O03/O04로 조립, 이 단계 저장 없음 | 1084–1392, 4801–4888 |
|
||||||
|
| C00 signal ALL·activation | I10 `signal_manifest.json`, manifest payload 전부, I05 routing activation, `D/signals/signal_registry.v2.json`, 사건 documents | signal occurrence·semantic/integrity rows·activation 비교 결과. 별도 signal output 파일 없음 | 1420–1726, 4881–4923 |
|
||||||
|
| C00 cross-artifact seal | 사건 documents/snapshots, 특히 I04/I06/I07/I08 등의 P1 guard와 `D/domains/_registry_index.json`, P2–P4 및 ledger/report | seal 검사 결과·issues. 이후 O01/O02/O03에 반영 | 1740–1782, 4924–4931 |
|
||||||
|
| C05 review 정규화 | I08/I11/I13/I16(4개 review handoff), CTRL-2의 review mapping | normalized review occurrence·issues; 독립 `normalized_reviews.json`은 없음. compact receipt→O02, UNMAPPED issue→O03 | 1897–2065, 4932–4934 |
|
||||||
|
| C05 보존검사 | I01–I16 파싱 documents와 snapshots, signal ALL 결과, normalized reviews | conservation checks·issues. compact checks→O02, issue→O03 | 2068–2436, 4935–4941 |
|
||||||
|
| run binding·artifact header | source/signal/deployment snapshots, release, request/context 값, SCHEMA-2 | binding/header 메모리 객체. O04 내 run_binding_receipt 및 context header 등에 포함 | 4942–4971 |
|
||||||
|
| C10 case context | 사건 documents(주요 I05/I09/I12/I14 및 evidence/event/source refs), signal ALL, binding/header/issues | O05용 `case_context` 객체 → 최종 `O/context/case_context.json` | 2523–2630, 4988–4994 |
|
||||||
|
| C10 evidence inventory | I01 `evidence_indexed.json`, I02 `evidence_event_candidates.json` 등 documents·header | O06용 객체 → 최종 `O/context/evidence_inventory.json` | 2633–2685, 4995–4999 |
|
||||||
|
| C10 object registry | I09 `BO.json`, header | O07용 객체 → 최종 `O/context/object_registry.json` | 2701–2731, 5000–5004 |
|
||||||
|
| C10 party/title context | I09 `BO.json`, header | O08용 객체 → 최종 `O/context/party_and_title_context.json` | 2734–2779, 5005–5009 |
|
||||||
|
| C10 slot crosswalk | 사건 documents, D/ 아래 domain config 26개(E-00–E-21, EC-00, X1–X3), header | O09용 객체 → 최종 `O/context/slot_crosswalk.json`; 현재 fact/evidence 배열은 빈 skeleton | 2782–2831, 4972–4977, 5010–5015 |
|
||||||
|
| C15 membership·cluster·SCC·wave | I09 BO, I14 ledger, I12 LES, I01 evidence, I02 event의 명시 member/relation | O10용 `cluster_plan` 객체. 최종 executable/wave는 cohort 검사 후 갱신 | 2834–3129, 4339–4527, 5016–5017 |
|
||||||
|
| C15 bounded slice | cluster_plan 및 C10 context 객체, EVENT/LES 원문 row, signal/review/domain-config projection source map | O12용 slice 객체들. 최종 executable만 `O/context/cluster_slices/<cluster_id>.json` | 3132–3269, 5018–5054 |
|
||||||
|
| C15 bundle/cohort·invariant | cluster_plan·slice 객체, CTRL-2 bundle 계약, `A/agent_scripts/Stage_2_S2_10.yml`, `A/deployment/stage2_s2_10_llm_binding.yml`, release-selected context refs(현재 없음) | O11용 bundle_plan, O10 executable/residual/wave 갱신. 입력 Agent/binding은 hash 결속 대상 | 3283–3906, 5055–5113 |
|
||||||
|
| route·snapshot 재검사·최종 조립 | source rows, C00/C05 검사·review·issue, C10/C15 객체, 원본 임시 snapshot | 정상 O01–O11+O12 E개 또는 diagnostic O01–O04+O13를 메모리 artifact map으로 확정 | 5114–5243 |
|
||||||
|
| output schema 검사·로컬 tree 발행 | 선택된 artifact map, `T/stage2_asset/schemas/`의 SCHEMA-1–3 | 모든 선택 파일을 `T/.staging/core_output.<attempt_id>/`에 저장/fsync, status 마지막 저장, `T/core_output/`로 rename | 4015–4108, 5235–5242 |
|
||||||
|
| exact artifact set/hash 재검사 | `T/core_output/ingress/ingress_status.json` 및 해당 tree의 전체 파일 | 파일 bytes map. 새로운 별도 출력 파일 없음 | 5896–5931 |
|
||||||
|
| 원격 status-last 발행 | 검증된 local 파일 bytes; 기존 `O/ingress/ingress_status.json`이 있으면 기존 O 전체 파일도 읽어 대조 | 선택된 동일 파일 집합을 O에 저장/read-back; O04 마지막. 기존 집합과 bytes 동일하면 재사용 | 5934–6008 |
|
||||||
|
| 단일 receipt·Agent OUT | core 결과·logical publication receipt 또는 예외 | stdout JSON 객체 1개. 고정 파일명·저장 폴더 없음 | 6018–6103 |
|
||||||
|
| 외부 downstream dispatch | O04 barrier 및 O10/O11/O12 등 | S2_10 동적 item 또는 S2_40 status-only 호출. S2_00이 추가 파일을 쓰지 않음 | 분석서 §1·§2.2·§5 |
|
||||||
|
|
||||||
|
C15 표의 projection source map에 signal/review/profile이 준비된다는 것과 실제 slice에 모두 포함된다는 것은 다르다. 현 구현의 member 생성은 BO/FACT/LES/EVIDENCE/EVENT 중심이며, 이 문서는 전체 내용의 무손실 인계를 보증하지 않는다(분석서 §8).
|
||||||
|
|
||||||
|
## 6. 임시 저장·최종 저장·파일이 아닌 출력의 구별
|
||||||
|
|
||||||
|
### 6.1 hydration 파일의 source → 임시 output
|
||||||
|
|
||||||
|
| 원격 Input | 임시 Output 경로 | file name / format |
|
||||||
|
|---|---|---|
|
||||||
|
| `U/<상대경로>`: I01–I16 및 signal family, 조건부 completion | `T/stage1_run/<동일 상대경로>` | 원본 basename 및 JSON bytes 그대로 |
|
||||||
|
| `D/<상대경로>`: D01–D55, 조건부 contract manifest | `T/stage1_deployment/<동일 상대경로>` | 원본 basename 및 JSON bytes 그대로 |
|
||||||
|
| `A/<상대경로>`: release/module manifest·schema 3개·direct 49개 | `T/stage2_asset/<동일 상대경로>` | 원본 basename 및 JSON/YAML bytes 그대로 |
|
||||||
|
| `W/stage2_control/s2_00_request.json` | 임시 파일로 저장하지 않음 | 메모리 request 및 두 read-pass 비교에 사용 |
|
||||||
|
|
||||||
|
두 read-pass는 임시 복제 전에 같은 원격 경로의 bytes를 비교한다. 임시 폴더는 `TemporaryDirectory` 수명 내에서만 사용한다. 여기의 input 복제는 최종 업무 output 개수(11+E 또는 5)에 포함하지 않는다. 근거: Y:5602–5753, 5825–5893.
|
||||||
|
|
||||||
|
### 6.2 최종 저장 순서
|
||||||
|
|
||||||
|
1. 선택된 모든 output에 schema 검사를 수행한다.
|
||||||
|
2. `T/.staging/core_output.<attempt_id>/<출력 상대경로>`에 non-status를 저장/fsync하고 `ingress/ingress_status.json`을 마지막에 저장한다.
|
||||||
|
3. staging tree를 `T/core_output/`으로 rename한다.
|
||||||
|
4. exact file set와 raw hash를 다시 검사한다.
|
||||||
|
5. 기존 원격 barrier가 있으면 binding·status 및 전체 파일 bytes 일치를 검사한다. 일치하면 `IDEMPOTENT_SUCCESS`이다.
|
||||||
|
6. 기존 barrier가 없으면 `O/<출력 상대경로>`에 non-status 파일별 write/read-back을 마친 뒤 `O/ingress/ingress_status.json`을 마지막에 write/read-back한다.
|
||||||
|
|
||||||
|
로컬 rename과 원격 `STATUS_LAST_LOGICAL_COMMIT`은 서로 다른 저장 보장이다. 저장 후 read-back 또는 close 실패가 발생하면 실패 receipt와 일부 원격 파일이 함께 남을 수 있다. 근거: Y:4015–4108, 5896–6103.
|
||||||
|
|
||||||
|
### 6.3 별도 파일로 오인하면 안 되는 값
|
||||||
|
|
||||||
|
| 이름 | 실제 형태·위치 |
|
||||||
|
|---|---|
|
||||||
|
| `hydration_stability_receipt` | 메모리 객체 및 O01 내부 객체 |
|
||||||
|
| `run_binding_receipt` | O04 등의 내부 객체 |
|
||||||
|
| `context_materialization_receipt` | bundle 관련 내부 객체 |
|
||||||
|
| `output_barrier` | O04 내부 객체 |
|
||||||
|
| `logical_publish_receipt` | stdout inner receipt 내부 객체 |
|
||||||
|
| `stage2_s2_00_inner_receipt.v1` | stdout 객체의 schema_version; basename이 아님 |
|
||||||
|
| `selected_legal_context_json`, `cluster_case_payload_json` | 외부 orchestrator가 만드는 S2_10 item의 문자열 필드 |
|
||||||
|
|
||||||
|
S2_00은 `control/run_status.json`이나 최종 청구취지·청구원인 문안을 생성하지 않는다. 또한 YAML 안에 보존된 별도 `main(argv)` CLI의 `project_root/stage2_runs/<run_id>` 경로를 현재 inline MCP 실행의 O 경로와 혼동하면 안 된다. 실제 `__main__`은 `run_inline_mcp()`를 호출한다(Y:6190–6232).
|
||||||
|
|
||||||
|
## 7. 식별 결과와 검증 범위
|
||||||
|
|
||||||
|
- 16개 Stage 1 고정 사건 입력, 55개 Stage 1 배포 입력, 49개 Stage 2 직접 의존 입력의 이름·형식·source folder를 현재 release와 대조하였다.
|
||||||
|
- 제어 request/release, module manifest 및 출력 검증 schema 3개를 별도로 식별하였다.
|
||||||
|
- 정상·diagnostic 분기의 출력 목록과 개수, 임시 staging/core_output 및 최종 O 경로를 YAML의 artifact map·publication 코드와 대조하였다.
|
||||||
|
- signal family의 모든 basename, 조건부 입력의 실제 경로, U/D의 실제 사건 경로, output digest 및 cluster별 basename은 실제 request/manifest/run이 없으므로 값 자체는 미확정이다. 코드가 정한 결정 규칙을 명시하였다.
|
||||||
|
- `runtime/s2_00_ingress.py`, `.txt` mirror, workflow YAML, offline build/test 파일은 실행·검증 자산이지만 이 inline task가 별도 업무 입력으로 import/실행하는 파일이 아니다. `deployment.schema.json`, prompt cache policy, P00/P10 Markdown 등도 위 실제 hydration 집합에 포함하지 않았다(분석서 §3.1·§3.3).
|
||||||
|
- 원본 YAML·분석서·release 및 실행 자산은 수정하지 않았다. 문서의 경로·집합 대조는 정적 검증이며, MCP 실행 성공·후속 Agent 실행·법률적 적정성 검증을 뜻하지 않는다.
|
||||||
@@ -12,11 +12,12 @@ Agent:
|
|||||||
release_ref: Default_Agent/Stage_2_Clean/manifest/stage2_release.json
|
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
|
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
|
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:
|
Stages:
|
||||||
- name: S2_00
|
- name: S2_00
|
||||||
description: >-
|
description: >-
|
||||||
단일 Code Executor 호출 안에서 C00, C05, C10, C15를 순차 실행하고
|
첫 Code Executor task에서 고정 request를 준비하고, 다음 ingress
|
||||||
|
task의 한 호출 안에서 C00, C05, C10, C15를 순차 실행한다.
|
||||||
localdocs binary IO와 status-last 논리 배리어로 결과를 발행한다.
|
localdocs binary IO와 status-last 논리 배리어로 결과를 발행한다.
|
||||||
prevs: []
|
prevs: []
|
||||||
nexts: []
|
nexts: []
|
||||||
@@ -29,6 +30,254 @@ Agent:
|
|||||||
type: streamable-http
|
type: streamable-http
|
||||||
url: https://code-executor.mcp.eroomai.com/mcp
|
url: https://code-executor.mcp.eroomai.com/mcp
|
||||||
tasks:
|
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
|
- task_name: Task_S2_00_deterministic_ingress
|
||||||
description: >-
|
description: >-
|
||||||
고정 request와 release를 hydration하고 S2_00 pure core를 실행한 뒤
|
고정 request와 release를 hydration하고 S2_00 pure core를 실행한 뒤
|
||||||
@@ -192,7 +441,7 @@ Agent:
|
|||||||
# literal placeholders in the offline parity mirror and its unit tests.
|
# literal placeholders in the offline parity mirror and its unit tests.
|
||||||
INLINE_USER_HASH = "{{__user_hash__}}"
|
INLINE_USER_HASH = "{{__user_hash__}}"
|
||||||
INLINE_WORKSPACE_HASH = "{{__workspace_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_REQUEST_PATH = "stage2_control/s2_00_request.json"
|
||||||
INLINE_STAGE2_ASSET_ROOT = "Default_Agent/Stage_2_Clean"
|
INLINE_STAGE2_ASSET_ROOT = "Default_Agent/Stage_2_Clean"
|
||||||
INLINE_STAGE2_RELEASE_PATH = (
|
INLINE_STAGE2_RELEASE_PATH = (
|
||||||
@@ -6235,13 +6484,18 @@ Agent:
|
|||||||
task_procedure:
|
task_procedure:
|
||||||
IN:
|
IN:
|
||||||
nexts:
|
nexts:
|
||||||
- Task_S2_00_deterministic_ingress
|
- Task_S2_00_prepare_request
|
||||||
wait_until: []
|
wait_until: []
|
||||||
|
Task_S2_00_prepare_request:
|
||||||
|
nexts:
|
||||||
|
- Task_S2_00_deterministic_ingress
|
||||||
|
wait_until:
|
||||||
|
- IN
|
||||||
Task_S2_00_deterministic_ingress:
|
Task_S2_00_deterministic_ingress:
|
||||||
nexts:
|
nexts:
|
||||||
- OUT
|
- OUT
|
||||||
wait_until:
|
wait_until:
|
||||||
- IN
|
- Task_S2_00_prepare_request
|
||||||
OUT:
|
OUT:
|
||||||
nexts: []
|
nexts: []
|
||||||
wait_until:
|
wait_until:
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -71,7 +71,7 @@ Agent:
|
|||||||
|
|
||||||
WORKFLOW_ID = "S2_20"
|
WORKFLOW_ID = "S2_20"
|
||||||
ALGORITHM_VERSION = "s2_20_relief_plan/1.0.0"
|
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"
|
LOCALDOCS_URL = "http://mcp-localdocs:8012/mcp"
|
||||||
WEAVIATE_MCP_URL = "https://weaviate.eroomai.com/mcp"
|
WEAVIATE_MCP_URL = "https://weaviate.eroomai.com/mcp"
|
||||||
MCP_PROTOCOL_VERSION = "2025-03-26"
|
MCP_PROTOCOL_VERSION = "2025-03-26"
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ Agent:
|
|||||||
from typing import Any, Mapping
|
from typing import Any, Mapping
|
||||||
|
|
||||||
|
|
||||||
EXPECTED_STAGE2_RELEASE_SHA256 = "9fa85bb94c5f14f675dcdaa0cf94d06745b21675d97797539ffc14015dcc9f53"
|
EXPECTED_STAGE2_RELEASE_SHA256 = "2363f1166ee8a3ea2b38350cde61fccfc9852a413c6b9f14a6ed6606af15a590"
|
||||||
LOCALDOCS_URL = "http://mcp-localdocs:8012/mcp"
|
LOCALDOCS_URL = "http://mcp-localdocs:8012/mcp"
|
||||||
MCP_PROTOCOL_VERSION = "2025-03-26"
|
MCP_PROTOCOL_VERSION = "2025-03-26"
|
||||||
INLINE_USER_HASH = "{{__user_hash__}}"
|
INLINE_USER_HASH = "{{__user_hash__}}"
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ Agent:
|
|||||||
|
|
||||||
WORKFLOW_ID = "S2_40"
|
WORKFLOW_ID = "S2_40"
|
||||||
ALGORITHM_VERSION = "s2_40_finalizer/1.0.0"
|
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"
|
LOCALDOCS_URL = "http://mcp-localdocs:8012/mcp"
|
||||||
MCP_PROTOCOL_VERSION = "2025-03-26"
|
MCP_PROTOCOL_VERSION = "2025-03-26"
|
||||||
INLINE_USER_HASH = "{{__user_hash__}}"
|
INLINE_USER_HASH = "{{__user_hash__}}"
|
||||||
|
|||||||
@@ -0,0 +1,262 @@
|
|||||||
|
# S2_00 request 생성·검증·저장 선행 task 신설 계획
|
||||||
|
|
||||||
|
## Objective
|
||||||
|
|
||||||
|
`Stage_2_S2_00.yml`의 맨 앞에 `Task_S2_00_prepare_request`를 신설하여 외부 실행 인자를 검증하고 `stage2_control/s2_00_request.json`을 저장·read-back한 뒤, 성공한 동일 실행만 기존 `Task_S2_00_deterministic_ingress`로 진행하도록 한다. workspace 단위 동시 실행 통제, 두 task 간 request identity 결속, 빌드·배포·hash 계약 갱신을 하나의 변경으로 다룬다.
|
||||||
|
|
||||||
|
작성일: 2026-09-30. 상태: **구현 전 계획**. 이 문서 작성 과정에서는 실행 YAML·runtime·배포 자산을 변경하지 않았다.
|
||||||
|
|
||||||
|
## Deliverables
|
||||||
|
|
||||||
|
| 산출물 | 내용 |
|
||||||
|
|---|---|
|
||||||
|
| 외부 실행 인자·host 계약 | 인자 전달 방식, workspace 권한·경로 결속, 직렬화, task 실패 차단, receipt 전달 |
|
||||||
|
| 2-task authoring 및 배포 YAML | prepare → ingress 순서와 success-only 실행 |
|
||||||
|
| prepare 구현 및 runtime mirror | 입력 검증, request 생성, write/read-back, 준비 receipt |
|
||||||
|
| ingress 보강 | host가 전달한 준비 receipt의 request hash·실행 identity와 실제 입력 대조 |
|
||||||
|
| workflow/schema/binding/builder 갱신 | task별 쓰기 권한, 2-task 추출·검증·receipt·mirror 계약 |
|
||||||
|
| 회귀·통합 검증 증거 | mock, 동시 실행, 실제 backend를 구분한 결과 |
|
||||||
|
| 분석·IO 문서 개정 | 새로운 선행 단계, 외부 인자 및 실패·잠금 수명 반영 |
|
||||||
|
|
||||||
|
## Scope and Non-Scope
|
||||||
|
|
||||||
|
- 범위: request 준비와 기존 ingress를 연결하는 구조 및 이를 배포 가능한 상태로 검증하는 데 필요한 계약 변경.
|
||||||
|
- 유지: request 파일의 기존 6필드, 고정 경로, 기존 C00/C05/C10/C15의 업무 책임, 정상/diagnostic 산출물 집합.
|
||||||
|
- 제외: Stage 1 사건 데이터 수정, S2_10 이후 업무 로직 변경, 기존 DEV release의 자동 production 승격, 기존 보존·projection 결함의 일괄 수정.
|
||||||
|
- 계획 작성이 구현 또는 live 배포 승인을 뜻하지 않는다. 실행 제한 해제는 request 생성 구현과 별개의 작업이다.
|
||||||
|
|
||||||
|
## Known Inputs
|
||||||
|
|
||||||
|
`M = Case_02_Comparison_Research/YAML_Prompts/2. Stage_2/`, `R = M/Default_Agent/Stage_2_Clean/`, `W = localdocs가 결속한 논리 workspace root`.
|
||||||
|
|
||||||
|
| 확인한 현 상태 | 근거 |
|
||||||
|
|---|---|
|
||||||
|
| 현재 IN → ingress → OUT, 단일 task | `R/agent_scripts/Stage_2_S2_00.yml`: 17–43, 6230–6248 |
|
||||||
|
| request는 읽기 입력이며 6필드 폐쇄형 검증 | 같은 YAML: 5548–5591, 5757 이후 |
|
||||||
|
| 입력 closure를 두 번 읽고 bytes 비교 후 temp hydration | 같은 YAML: 5825–5893 |
|
||||||
|
| 빌더가 정확히 1 task·1 run_code·고정 DAG를 강제 | `R/offline_build/build_s2_00_inline_projection.py`: 283–378 |
|
||||||
|
| authoring 정본은 `M/Stage_2_S2_00_v.2.yml`; 배포 YAML과 mirror는 파생본 | 같은 builder: 30–38, 680–755 |
|
||||||
|
| 단일 code pointer와 code hash·mirror를 receipt에 기록 | 같은 builder: 680–755 |
|
||||||
|
| 현재 S2_00 쓰기 root는 `stage2_runs/by-binding/<run_binding_digest>/` | `R/deployment/stage2_code_executor_binding.yml`: 47–66 |
|
||||||
|
| localdocs 계약상 허용 도구는 read_binary_doc/write_binary_file | 같은 binding: 47–66. CAS·분산 잠금 기능이 있다는 근거는 없음 |
|
||||||
|
| 외부 인자 주입 및 성공 receipt의 task 간 전달에 필요한 backend 구체 API | 현재 확인 범위에서 미확정. 구현 1단계에서 확인해야 함 |
|
||||||
|
|
||||||
|
사용자 지정 기존 분석 자료: [Stage_2_00_Analysis_v1.md](../YAML_Prompts/2.%20Stage_2/Stage_2_00_Analysis_v1.md), [Stage_2_00_IO_info.md](../YAML_Prompts/2.%20Stage_2/Stage_2_00_IO_info.md).
|
||||||
|
|
||||||
|
## Material Assumptions
|
||||||
|
|
||||||
|
1. request는 LLM이 추론하거나 폴더를 scan해서 만드는 값이 아니다. host가 확정한 네 값과 코드상 두 상수로 생성한다.
|
||||||
|
2. task 프로세스·임시 디렉터리·메모리는 서로 공유된다고 가정하지 않는다. backend가 인증된 실행 context와 선행 receipt를 후행 task에 전달해야 한다.
|
||||||
|
3. `wait_until`은 실행 순서를 기술하지만, 실패한 선행 task의 후속 실행까지 자동 차단한다는 보장은 별도로 검증한다.
|
||||||
|
4. 파일 읽기→없으면 lock 파일 쓰기는 원자적 잠금이 아니다. read-back·hash 비교도 상호 배제를 대신하지 않는다.
|
||||||
|
5. 권고 기본안은 **host가 같은 user/workspace의 전체 S2_00 실행을 직렬화**하는 것이다. 최소 요구 구간인 request 저장 전부터 hydration 완료까지보다 길게 잠금을 유지하여, 초기 구현에서 중간 해제 프로토콜을 추가하지 않는다.
|
||||||
|
|
||||||
|
## Questions That Could Change the Outcome
|
||||||
|
|
||||||
|
구현 착수 시 아래를 backend 문서·코드·시험으로 먼저 확인한다. 답을 추측해 YAML 문법이나 도구 기능을 만들지 않는다.
|
||||||
|
|
||||||
|
| 확인 사항 | 결정 기준·미지원 시 처리 |
|
||||||
|
|---|---|
|
||||||
|
| 외부 structured 인자 전달 방식 | 실제 지원하는 executor 입력 채널 사용. 고정된 템플릿 문법·미지원 run_code parameter를 임의 추가하지 않음 |
|
||||||
|
| 선행 stdout receipt를 후행 입력으로 전달하는 방식 | backend가 실행 identity에 결속해 전달해야 함. 동일 고정 파일에 별도 receipt를 쓴다는 방식만으로 신뢰 경계를 대신하지 않음 |
|
||||||
|
| task 성공 판정 | transport 성공뿐 아니라 exit code·receipt schema·ok·실행 ID 일치 확인 가능해야 함 |
|
||||||
|
| workspace 직렬화와 취소·worker 종료 보장 | 여러 backend worker에 걸친 전역 보장 필요. 한 프로세스의 mutex만 있으면 불충분 |
|
||||||
|
| 저장 권한 강제 | prepare에 정확한 request 경로만 쓰기 허용; ingress 결과 쓰기 권한과 분리 가능한지 확인 |
|
||||||
|
|
||||||
|
미지원 항목이 있으면 해당 backend 기능을 선행 구현 대상으로 기록한다. 로컬/mock 작업은 계속할 수 있으나 backend 증거 없이 live-ready로 표시하지 않는다.
|
||||||
|
|
||||||
|
## Workstreams and Dependencies
|
||||||
|
|
||||||
|
### 1. 기준선 및 변경 영향 확정
|
||||||
|
|
||||||
|
- 관련 authoring·배포 YAML, runtime mirror, workflow, schema, binding, builder·tests의 hash와 Git 변경 상태를 기록한다. 기존 사용자 변경은 보존한다.
|
||||||
|
- 문자열뿐 아니라 task ID·code pointer·code hash를 참조하는 소비자를 찾아 변경 목록을 닫는다.
|
||||||
|
- 기존 검사 결과를 기준선으로 기록하여 이번 변경의 회귀와 기존 실패를 구분한다.
|
||||||
|
- 위 backend 확인 사항을 검증하고 실제 인자·receipt 주입 방식 및 잠금 구현 위치를 확정한다.
|
||||||
|
|
||||||
|
완료 기준: 정확한 변경 파일 목록, backend 연동 계약, 기존 실패 목록이 기록되어야 한다.
|
||||||
|
|
||||||
|
### 2. 외부 인자 및 request 계약 확정
|
||||||
|
|
||||||
|
request 출력은 다음 6필드로 고정한다.
|
||||||
|
|
||||||
|
| 필드 | 공급자·형식 | 검증 |
|
||||||
|
|---|---|---|
|
||||||
|
| `schema_version` | prepare 코드 상수 | `stage2_s2_00_execution_request.v1` |
|
||||||
|
| `workflow_id` | prepare 코드 상수 | `S2_00` |
|
||||||
|
| `request_id` | host 문자열 | `[A-Za-z0-9][A-Za-z0-9._-]{0,127}` |
|
||||||
|
| `attempt_id` | host 문자열 | 위와 동일. 재시도 정책에 따라 새 시도 식별자 부여 |
|
||||||
|
| `stage1_run_root_ref` | host 문자열 | W 아래 승인된 Stage 1 사건 root에 결속한 canonical 상대경로 |
|
||||||
|
| `stage1_deployment_root_ref` | host 문자열 | W 아래 승인된 Stage 1 배포 root에 결속한 canonical 상대경로 |
|
||||||
|
|
||||||
|
- 누락·추가 필드, 빈 값, 잘못된 타입, absolute path, `..`, 역슬래시, NUL, 비정규 NFC 경로 등을 거절한다. 기존 `_inline_relative_path`와 검증 의미를 맞춘다.
|
||||||
|
- 문법적으로 올바른 경로라는 사실과 해당 실행이 접근할 권한이 있다는 사실을 구별한다. host가 승인한 user/workspace 및 Stage 1 완료 실행과 두 root를 결속한다.
|
||||||
|
- request 값은 다른 업무 run에서 남은 파일, 작업 디렉터리 추측, LLM 판단으로 채우지 않는다.
|
||||||
|
- user/workspace·host execution ID·lock ownership·준비 receipt는 별도의 실행 envelope에 담는다. 기존 request JSON에 7번째 필드를 추가하지 않는다.
|
||||||
|
- structured 전달을 우선한다. 코드 템플릿에 전달해야 하는 환경이라면 검증된 직렬화·안전한 encoding을 사용하고, 입력 문자열을 Python source에 직접 삽입하지 않는다.
|
||||||
|
|
||||||
|
완료 기준: 허용·거절 예제와 인자 전달 계약이 양쪽 task 및 backend 검증에서 일치한다.
|
||||||
|
|
||||||
|
### 3. host 직렬화 및 실패 수명 구현
|
||||||
|
|
||||||
|
잠금 key는 인증된 `(user, workspace, S2_00 request 고정 경로)`로 정한다. 서로 다른 request_id라도 동일 workspace의 고정 파일을 공유하면 같은 key를 사용한다.
|
||||||
|
|
||||||
|
```text
|
||||||
|
host admission → workspace 실행 소유권 획득
|
||||||
|
→ prepare 시작 → request 저장/read-back
|
||||||
|
→ 성공 receipt를 동일 실행 ingress에 전달
|
||||||
|
→ ingress request 1차/2차 읽기 → temp hydration
|
||||||
|
→ core 실행·원격 출력 발행 → 종료/취소 정리
|
||||||
|
→ 두 task와 미완료 I/O의 종료 확인 → 소유권 해제
|
||||||
|
```
|
||||||
|
|
||||||
|
- 기본안은 전체 S2_00 종료까지 직렬화한다. 따라서 최소 보호 구간인 hydration 완료까지는 확실히 포함된다.
|
||||||
|
- prepare에서 획득한 프로세스 로컬 lock을 task 종료 때 해제하는 구조는 금지한다. 소유권은 host 실행 전체에 귀속된다.
|
||||||
|
- 모든 request writer와 재시도 경로가 같은 통제에 참여해야 한다. 통제 밖 writer는 권한으로 차단한다.
|
||||||
|
- timeout·취소·host 재시작 시 오래된 worker와 진행 중 쓰기가 끝났는지 확인한 후 다음 실행을 허용한다. TTL 만료만으로 새 소유자를 실행하지 않는다.
|
||||||
|
- lease 방식이 불가피하다면 stale owner의 쓰기/읽기를 실제로 거절하는 fencing 또는 동등한 host 보장을 구현한다. JSON에 token만 넣는 것은 fencing이 아니다.
|
||||||
|
- 이후 성능 최적화로 hydration 직후 해제하려면 authenticated hydration-complete 신호, 이후 request 재읽기 없음, 원본 Stage 1 run의 불변성 등을 별도 입증한다. 초기 버전에는 적용하지 않는다.
|
||||||
|
|
||||||
|
완료 기준: 두 host worker의 경쟁·지연·취소 시험에서도 request 교차 소비가 없어야 한다.
|
||||||
|
|
||||||
|
### 4. 선행 task 구현
|
||||||
|
|
||||||
|
신규 task ID: `Task_S2_00_prepare_request`.
|
||||||
|
|
||||||
|
1. 인증된 host envelope, 실행 소유권, 네 외부 인자를 검증한다.
|
||||||
|
2. 고정 두 상수를 추가하여 6필드 request 객체를 만든다.
|
||||||
|
3. strict JSON/schema 및 경로·ID 검증을 수행한다. schema 자산을 읽는 경우 release/module manifest의 hash 결속된 정확한 closure를 사용한다.
|
||||||
|
4. 기존 canonical JSON 규칙(UTF-8, 정렬 key, 고정 구분자, NaN 금지, 종료 newline)으로 직렬화하고 raw SHA-256을 계산한다.
|
||||||
|
5. 소유권을 유지한 상태에서 `W/stage2_control/s2_00_request.json`에 binary write를 수행한다. 현재 계약에 없는 원격 rename·원자적 파일 교체 기능을 가정하지 않는다.
|
||||||
|
6. 같은 경로를 binary read-back하여 bytes가 동일한지 확인하고, 다시 parse/schema 검증한다.
|
||||||
|
7. 성공일 때만 stdout 준비 receipt를 반환한다. 권고 schema는 신규 `stage2_s2_00_prepare_request_receipt.v1`이며 `ok`, request path/hash/byte length, request_id/attempt_id, host execution binding을 포함한다. 이것은 **신규 설계**이며 기존 파일이라고 표현하지 않는다.
|
||||||
|
8. 오류·write 응답 유실·read-back 불일치·close 실패 등은 성공 receipt로 취급하지 않는다. ingress는 실행하지 않는다. 부분 파일이 남아 있어도 성공한 것으로 간주하지 않는다.
|
||||||
|
|
||||||
|
동일 시도 재실행은 같은 bytes 및 host identity가 확인되면 재사용 가능하다. 새 시도/다른 request로 교체할 때에는 이전 실행 종료 및 새 소유권이 먼저 확인되어야 한다. 성공 후 무조건 request를 삭제하는 정리 코드는 두지 않는다.
|
||||||
|
|
||||||
|
완료 기준: 실제 저장 bytes와 receipt hash가 일치하고, 모든 실패 경로가 후행 실행을 차단한다.
|
||||||
|
|
||||||
|
### 5. ingress 결속 및 DAG 변경
|
||||||
|
|
||||||
|
```text
|
||||||
|
IN
|
||||||
|
→ Task_S2_00_prepare_request
|
||||||
|
→ [host: exit=0 + 유효한 ok:true receipt + 동일 실행 binding 확인]
|
||||||
|
→ Task_S2_00_deterministic_ingress
|
||||||
|
→ OUT
|
||||||
|
```
|
||||||
|
|
||||||
|
- YAML `task_procedure`에 prepare의 `wait_until: [IN]`, ingress의 `wait_until: [Task_S2_00_prepare_request]`를 설정하고 nexts를 대응시킨다. stage prevs/nexts는 기존 독립 stage 구조를 유지한다.
|
||||||
|
- host의 success-only 조건은 YAML edge와 별도로 계약 및 시험에 포함한다. 실패한 prepare가 '완료'되었다는 이유로 ingress를 시작하지 않는다.
|
||||||
|
- ingress는 host가 제공한 준비 receipt가 없거나 잘못되면 시작을 거절한다. 최초 request bytes와 expected hash, request_id/attempt_id를 대조한다.
|
||||||
|
- 기존 두 read-pass의 동등성 검사를 유지한다. 두 번째 request bytes도 준비 receipt hash와 일치해야 한다. 동일하게 바뀐 다른 request를 두 번 읽어 통과하는 상황까지 차단한다.
|
||||||
|
- 신뢰할 수 없는 stale receipt·다른 workspace receipt·다른 attempt의 receipt를 거절한다.
|
||||||
|
- 기존 C00–C15, output barrier·route 계약은 유지한다. wrapper 실패와 core diagnostic 분기를 혼동하지 않는다.
|
||||||
|
|
||||||
|
완료 기준: prepare와 ingress가 같은 request를 사용했다는 검증 가능한 연결이 생긴다.
|
||||||
|
|
||||||
|
### 6. authoring·빌드·배포 계약 갱신
|
||||||
|
|
||||||
|
| 파일·영역 | 작업 |
|
||||||
|
|---|---|
|
||||||
|
| `M/Stage_2_S2_00_v.2.yml` | authoring 정본에서 2-task 구조·설명·version·inline source 갱신 |
|
||||||
|
| `R/agent_scripts/Stage_2_S2_00.yml` | 수정한 builder로 배포본 생성. 배포본만 수작업 수정하지 않음 |
|
||||||
|
| `R/offline_build/build_s2_00_inline_projection.py/.txt` | '정확히 1개' 검증을 **정확히 두 개의 지정 task와 지정 순서**로 변경; 추가 임의 task는 거절 |
|
||||||
|
| 같은 builder의 code 검사 | 공통 보안·endpoint·import 검사는 유지; ingress 전용 token과 prepare 전용 token을 분리 |
|
||||||
|
| `R/runtime/s2_00_ingress.py/.txt` | ingress task code의 파생 mirror로 유지 |
|
||||||
|
| 신규 `R/runtime/s2_00_prepare_request.py/.txt` | prepare code의 파생 mirror 제안. 두 task를 결합한 실행 파일로 만들지 않음 |
|
||||||
|
| `R/manifest/s2_00_inline_code_receipt.json` | task별 code pointer·hash·mirror, 전체 task semantics hash로 확장; schema version/소비자 동시 갱신 |
|
||||||
|
| `R/workflows/S2_00_stage1_ingress_normalize_and_bundle_compile.yml` | 선행 단계, 외부 인자, receipt·success gate·직렬화 계약 반영 |
|
||||||
|
| `R/deployment/stage2_code_executor_binding.yml` | task별 executor 및 권한: prepare는 request 고정 경로, ingress는 기존 결과 root. schema/release 읽기 경로도 task별 명시 |
|
||||||
|
| `R/schemas/ingress.schema.json` | 기존 6필드 execution_request 유지, 신규 prepare/envelope receipt 검증 정의 |
|
||||||
|
| `R/schemas/deployment.schema.json` 및 관련 schema | 두 task·task별 code binding·receipt 계약과 실제 소비자 호환성 반영 |
|
||||||
|
| `R/manifest/module_manifest.json`, `stage2_release.json`, release validator | 변경 자산의 등록·hash와 추가된 계약 검증 반영 |
|
||||||
|
| `M/Stage_2_00_Analysis_v1.md`, `Stage_2_00_IO_info.md` | 구현 완료 후 실제 구조·IO·검증 상태로 개정 |
|
||||||
|
|
||||||
|
`task index=0`에 ingress가 있다고 가정하는 코드를 모두 확인한다. task_name으로 추출하고 receipt에는 실제 YAML pointer도 남긴다. version 값은 관련 schema와 소비자 호환성을 확인해 함께 올린다.
|
||||||
|
|
||||||
|
### 7. hash 재결속 및 패키지 확정
|
||||||
|
|
||||||
|
- 변경 자산 → module/release → inline release pin → authoring projection/mirror/receipt → executor binding/detached admission의 실제 참조 그래프를 먼저 만든다.
|
||||||
|
- 현재 builder는 projection 이후 binding 재결속을 전제로 한다. 기존 detached 설계를 유지하고, parent release에 자신을 포함한 code hash를 되먹이는 순환 의존을 새로 만들지 않는다.
|
||||||
|
- 참조 그래프의 위상 순서대로 재생성한다. 순환이 발견되면 hash를 반복 덮어쓰는 대신 detached boundary를 설계·검증한다.
|
||||||
|
- 현재 `expected_release_sha256`, task별 code hash, Agent raw hash, workflow/schema/module hash와 receipt pointer를 함께 대조한다.
|
||||||
|
- 재생성 뒤 빌더 `--check`, release validator, mirror parity를 실행한다. 두 번째 빌드에서 bytes 차이가 없어야 한다.
|
||||||
|
- 테스트용 release와 실제 배포 release를 분리한다. DEV guard를 삭제하거나 production 표기를 붙여 검사를 통과시키지 않는다.
|
||||||
|
|
||||||
|
완료 기준: 참조된 변경 자산의 hash가 모두 일치하고, 반복 빌드가 추가 변경을 만들지 않는다.
|
||||||
|
|
||||||
|
## Source and Tool Plan
|
||||||
|
|
||||||
|
로컬 authoring·builder·runtime·tests를 우선 읽는다. backend 관련 문서·코드는 필요한 범위에서 확인하고 지원 여부를 증거로 남긴다. 신규 의존성 설치나 서비스 호출은 이 계획 작성에 포함하지 않는다. 구현 시 기존 도구·검증 runner를 확인해 사용하고, 라이브 시험은 별도 격리 workspace와 비실사건 fixture로 수행한다.
|
||||||
|
|
||||||
|
## Validation Plan
|
||||||
|
|
||||||
|
| 시험 | 통과 기준 |
|
||||||
|
|---|---|
|
||||||
|
| 유효한 인자 | request 정확히 6필드, canonical bytes/hash/read-back 일치 |
|
||||||
|
| 누락·추가·타입·ID·경로 오류 | 저장 전 거절, ingress 호출 0회 |
|
||||||
|
| 권한 밖 경로·다른 workspace | host 결속 검증 실패, 업무 입력 읽기 불가 |
|
||||||
|
| 저장 실패·응답 유실·read-back mismatch | 성공 receipt 없음, ingress 호출 0회 |
|
||||||
|
| 선행 task exit 0이나 ok:false/깨진 receipt | host가 후행 task를 실행하지 않음 |
|
||||||
|
| 준비 후 request 변조 | 첫 읽기 expected hash 검증 또는 두 pass 비교에서 거절 |
|
||||||
|
| 두 pass 모두 동일한 타 요청으로 교체 | 준비 receipt hash/identity 검증에서 거절 |
|
||||||
|
| 같은 workspace 두 요청·두 worker | 단일 소유자만 진입, 다른 요청 bytes를 소비하지 않음 |
|
||||||
|
| 다른 workspace 병렬 실행 | 불필요한 전역 직렬화 없이 독립 진행 |
|
||||||
|
| cancel·timeout·host crash·오래된 worker 재개 | 이전 쓰기 가능성이 사라지기 전 새 owner 쓰기 금지; stale owner 차단 |
|
||||||
|
| 동일 시도 retry / 새 attempt | 정의한 재사용/교체 정책 준수, 기존 output idempotency 계약 유지 |
|
||||||
|
| task shape / builder | 지정 두 task와 순서만 허용; task 누락·추가·우회 edge 거절 |
|
||||||
|
| 두 code mirror·receipt pointer·hash | authoring에서 추출한 bytes와 정확히 일치 |
|
||||||
|
| 기존 core 정상·diagnostic 회귀 | fixture 기준 11+E/5 출력 및 barrier 계약 보존 |
|
||||||
|
| 실제 backend 통합 | 인자 주입·receipt 전달·failure gate·workspace 격리를 실제 환경에서 확인 |
|
||||||
|
|
||||||
|
mock 통과는 실제 host 잠금·권한 강제 증거가 아니다. `STATIC_PASS`, `OFFLINE_TEST_PASS`, `BACKEND_INTEGRATION_PASS`, `LIVE_ADMISSION_PENDING`을 구분해 보고한다. 현재 DEV release로 정상 core 성공을 주장하지 않는다.
|
||||||
|
|
||||||
|
## Approval Boundaries
|
||||||
|
|
||||||
|
현재 요청은 작업 계획 작성이다. 이 단계에서는 계획 문서만 생성한다. 이후 구현 요청 시 범위 내 로컬 수정·시험을 수행하고, 실제 서비스 배포·외부 쓰기·live activation은 그때의 승인 범위를 확인한다. backend 기능 미지원은 기술적 미완료 조건으로 기록하며 이를 단순 승인 문제로 대체하지 않는다.
|
||||||
|
|
||||||
|
## Progress
|
||||||
|
|
||||||
|
- [x] 현재 단일 task·request read·builder 제약 확인
|
||||||
|
- [x] 현재 write root와 localdocs 도구 계약 확인
|
||||||
|
- [x] 작업 순서·의존성·동시 실행·검증 계획 작성
|
||||||
|
- [ ] backend 전달·직렬화 기능 확인 및 연동 방식 확정
|
||||||
|
- [ ] schema·prepare·ingress·DAG 구현
|
||||||
|
- [ ] builder·binding·hash 재결속
|
||||||
|
- [ ] offline·동시 실행·backend 통합 검증
|
||||||
|
- [ ] 분석·IO 문서 갱신 및 배포 적격성 판정
|
||||||
|
|
||||||
|
## Decision Log
|
||||||
|
|
||||||
|
| 날짜 | 결정 | 근거 | 결과 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 2026-09-30 | request의 기존 6필드 유지 | 현재 strict validator와 사용자 요구 | 실행 보조 정보는 host envelope로 분리 |
|
||||||
|
| 2026-09-30 | 첫 버전은 전체 S2_00 host 직렬화 | 두 독립 task 사이 lock 수명 및 고정 request 경로 | hydration 완료 전 해제 위험과 중간 callback 의존 감소 |
|
||||||
|
| 2026-09-30 | 준비 receipt hash를 ingress에 전달 | 두 read-pass 비교만으로 다른 요청 교체를 모두 검출할 수 없음 | 소비할 request를 선행 실행에 결속 |
|
||||||
|
| 2026-09-30 | authoring부터 재생성 | builder가 정본 및 mirror parity를 관리 | 배포 YAML 단독 변경 방지 |
|
||||||
|
|
||||||
|
## Evidence Ledger
|
||||||
|
|
||||||
|
| 명제 | 확인 근거 | 상태 |
|
||||||
|
|---|---|---|
|
||||||
|
| 기존 builder는 두 task를 거절한다 | extract_run_code_task의 EXACTLY_ONE_TASK_REQUIRED·EXACTLY_ONE_RUN_CODE_REQUIRED | 확인 |
|
||||||
|
| 현재 request 저장은 쓰기 허용 root 밖이다 | S2_00 binding의 write_root_rule | 계약에서 확인; live 강제 여부는 미확인 |
|
||||||
|
| localdocs 원자적 lock/CAS 지원 | 현재 allowlist에 read/write만 있음 | 지원 근거 없음, 사용 가능하다고 가정하지 않음 |
|
||||||
|
| 선행 task를 현재 backend가 어떻게 인자 결속하는가 | 구체 backend 구현을 아직 확인하지 않음 | 미확정 |
|
||||||
|
| request writer의 생성 필요 | 현재 소비 코드 및 이전 폴더 전체 검색 결과 | 현 패키지에 실사용 writer 없음 |
|
||||||
|
|
||||||
|
## Risks and Failure Modes
|
||||||
|
|
||||||
|
- YAML만 바꾸면 builder·code pointer·권한·hash 계약에서 실패한다.
|
||||||
|
- `wait_until`만 추가하면 prepare 실패 후 ingress가 진행될 수 있다.
|
||||||
|
- 고정 request 파일에 write/read-back만 구현하면 동시 요청이 서로 덮어쓸 수 있다.
|
||||||
|
- lock TTL 이후 오래된 writer가 살아 있으면 새 요청을 오염시킬 수 있다.
|
||||||
|
- 기존 release 제한을 이번 변경의 실패로 혼동하거나, 이를 제거해 정상 실행을 과장할 수 있다.
|
||||||
|
- 다른 request가 같은 run binding으로 귀결될 때 기존 status bytes 일치 요건은 여전히 적용된다. 기존 출력 충돌을 자동 덮어쓰기로 해소하지 않는다.
|
||||||
|
|
||||||
|
롤백은 두 task 실행을 먼저 정지하고 진행 중 I/O 종료를 확인한 뒤, authoring·배포본·mirror·schema·binding·receipt를 검증된 동일 버전 묶음으로 복원한다. 이전 버전도 외부 request 공급 없이는 실행되지 않으므로 롤백 자체를 서비스 정상화로 보지 않는다. 사건 입력·이미 발행된 결과는 일괄 삭제하지 않는다.
|
||||||
|
|
||||||
|
## Results and Residual Uncertainty
|
||||||
|
|
||||||
|
계획은 작성 완료하였고, 구현은 미착수다. 가장 중요한 미확정 항목은 backend의 structured 인자/receipt 전달과 workspace 단위 직렬화·worker 종료 통제이다. 이 기능이 확인되거나 구현되어야 안전한 2-task 실행이 완성된다. 완료 판정은 request 생성 성공뿐 아니라 **동일 요청의 후행 소비, 실패 시 차단, 동시 실행 안전성, 빌드·hash 일치**를 모두 통과하는 것으로 한다. 실제 backend 시험 전에는 live-ready로 판정하지 않는다.
|
||||||
@@ -0,0 +1,206 @@
|
|||||||
|
# S2_00 request 준비 계획 v1 — 필수 조건을 유지한 단순화
|
||||||
|
|
||||||
|
작성일: 2026-09-30. 상태: **분석·계획 작성 완료 / 구현 미착수**.
|
||||||
|
원본: [s2-00-request-preparation.md](s2-00-request-preparation.md). 원본은 보존한다.
|
||||||
|
|
||||||
|
## 1. Occam's Razor 분석
|
||||||
|
|
||||||
|
**기존 계획은 안전성에 필요한 항목과 선택적인 구현을 함께 필수화하여, 가장 단순한 계획이라고 보기는 어렵다.** 같은 요구를 충족하면서 새 프로토콜·검증 중복·변경 범위를 줄일 수 있다. 다만 backend 기능이 미확정이므로 '절대적으로 가장 효율적'이라고 단정하지 않고, 확인된 코드와 사용자 제약 아래의 권고안을 제시한다. 여기서 효율성은 우선 구현·검증·유지보수 비용이며, 실행시간 개선 수치는 측정하지 않았다.
|
||||||
|
|
||||||
|
### 1.1 제약을 지키는 단순화와 구조 변경의 구분
|
||||||
|
|
||||||
|
| 대안 | 장점·비용 | 판단 |
|
||||||
|
|---|---|---|
|
||||||
|
| 원본의 독립 2-task + 준비 receipt 전달 | 별도 receipt schema·실행 envelope·후행 전달·task별 검증 필요 | 요청 identity 확인은 타당하지만 구체 수단을 줄일 수 있음 |
|
||||||
|
| **2-task + 동일한 불변 host 인자 공급** | 두 task가 같은 값으로 request를 재구성; 별도 준비 receipt 전달 불필요. 2-task 빌드 변경은 여전히 필요 | **v1 채택**: 기존 '선행 task 신설' 요구 유지 |
|
||||||
|
| 기존 단일 task 맨 앞의 준비 함수 | task 간 전달·실패 gate·2-task builder 변경을 제거할 수 있어 구조적으로 더 단순 | 독립 선행 task 요구를 완화하는 대안. v1에 몰래 적용하지 않음 |
|
||||||
|
| 외부 host에서 request 생성 | YAML 구조 변경이 작음 | 'YAML 맨 앞의 선행 task' 요구와 다르므로 기본안 제외 |
|
||||||
|
|
||||||
|
v1은 요청 목적을 바꾸지 않고 독립 선행 task를 유지한다. 단일 task 내부 함수 방식은 추가 구조를 가장 크게 줄이지만, 기존에 명시한 두-task 조건과 구별해야 한다.
|
||||||
|
|
||||||
|
### 1.2 줄일 수 있는 항목과 유지할 항목
|
||||||
|
|
||||||
|
| 원본 계획 | v1 결정 | 이유·적용 조건 |
|
||||||
|
|---|---|---|
|
||||||
|
| 신규 prepare receipt schema와 host→ingress receipt 전달 | **제거**. host가 확정한 동일 네 인자를 두 task에 제공하고 ingress가 expected bytes를 재구성 | 별도 파생 자료를 전달하지 않아도 요청 일치 검증 가능. host 인자는 실행 중 불변이어야 함 |
|
||||||
|
| 새로운 execution envelope·lock token을 task마다 검증 | **별도 형식 신설 안 함**. 기존 인증된 실행 context와 host 직렬화 사용 | 기존 기능의 실제 지원 여부부터 확인. 없는 기능을 있다고 가정하지 않음 |
|
||||||
|
| 검증한 bytes를 write/read-back한 뒤 동일 bytes를 다시 parse/schema 검사 | **반복 검사 제거** | 처음 검증한 canonical bytes와 read-back bytes의 완전 일치가 확인되면 같은 내용을 다시 검증할 필요 없음 |
|
||||||
|
| 선행 단계에서 schema/release closure 별도 hydration | **기본안에서 제외** | 이미 존재하는 폐쇄형 request validator와 schema의 일치를 offline test로 확인. ingress의 기존 release 검증은 유지 |
|
||||||
|
| 신규 분산 lock·lease·fencing 체계 가능성까지 초기 구현에 포함 | **기존 host 직렬화 재사용 우선**, 별도 체계는 실제 미지원일 때만 재설계 | 잠금 안전성은 필수이나 새 잠금 서비스를 만드는 것이 필수는 아님 |
|
||||||
|
| 동일 attempt 재사용 최적화 | **초기 제외**. 소유권 획득 후 전체 준비 작업 재실행 | 작은 JSON의 재쓰기보다 별도 재사용 분기의 관리 비용이 클 수 있음. 기존 결과 idempotency는 유지 |
|
||||||
|
| schema·manifest 전체 개정 | **실제 구조·hash 참조가 바뀌는 항목만 갱신** | 파일을 더 적게 수정하려고 필요한 hash/권한 갱신을 생략하지 않음 |
|
||||||
|
| 정확히 2 task·순서 및 두 code hash 검증 | **유지** | 현 builder가 1 task를 강제하므로 피할 수 없는 변경 |
|
||||||
|
| 저장 실패 시 차단·request 2회 읽기·host 직렬화 | **유지** | 요구된 실행 안전성. bytes 비교는 상호 배제를 대신하지 않음 |
|
||||||
|
|
||||||
|
특히 runtime의 `write_binary_verified()`에는 이미 write 후 read-back bytes 비교가 있다. 같은 일을 하는 새 IO 계층을 만들 필요가 없다. `_inline_validate_request()`도 이미 정확히 6필드·ID·경로 검증을 수행한다. 근거는 아래 Evidence Ledger에 기록한다.
|
||||||
|
|
||||||
|
## Objective
|
||||||
|
|
||||||
|
`IN → Task_S2_00_prepare_request → Task_S2_00_deterministic_ingress → OUT`을 구현하되, 새 준비 receipt 전달 프로토콜 없이 동일한 외부 인자와 기존 검증·IO 기능으로 request 생성 및 소비를 연결한다.
|
||||||
|
|
||||||
|
## Deliverables
|
||||||
|
|
||||||
|
- 기존 authoring을 정본으로 하는 2-task YAML·파생 배포본 및 필요한 code mirror/빌드 receipt 변경.
|
||||||
|
- 최소 준비 함수, ingress의 expected request bytes 대조, 외부 인자·직렬화·success-only 계약.
|
||||||
|
- 실제 변경된 binding·schema·manifest/hash, 관련 시험 및 분석·IO 문서 개정.
|
||||||
|
|
||||||
|
## Scope and Non-Scope
|
||||||
|
|
||||||
|
request의 6필드·고정 경로와 기존 ingress의 C00–C15·출력 계약을 유지한다. 신규 receipt 파일·범용 workflow framework·독립 lock 서비스·release 전체 재설계는 기본 범위에 넣지 않는다. 기존 DEV release 제한과 core 결함 수정은 별개다. 이 문서는 구현 계획이며 실행 자산을 변경하지 않는다.
|
||||||
|
|
||||||
|
## Known Inputs
|
||||||
|
|
||||||
|
`M = Case_02_Comparison_Research/YAML_Prompts/2. Stage_2/`, `R = M/Default_Agent/Stage_2_Clean/`, `W = 인증된 localdocs workspace`.
|
||||||
|
|
||||||
|
현재 authoring은 `M/Stage_2_S2_00_v.2.yml`, 배포본은 `R/agent_scripts/Stage_2_S2_00.yml`이다. 빌더는 1 task·1 code pointer를 전제로 하고, S2_00 binding은 결과 root만 쓰기 허용한다. request writer를 추가하려면 이 두 계약 변경은 불가피하다. backend의 실제 인자 전달·직렬화 구현은 **UNVERIFIED**다.
|
||||||
|
|
||||||
|
## Material Assumptions
|
||||||
|
|
||||||
|
- host가 권한을 확인한 네 인자를 실행 시작 때 확정하고 두 task에 동일하게 공급할 수 있어야 한다. YAML 문법·환경변수 이름·run_code parameter를 임의로 발명하지 않는다.
|
||||||
|
- 같은 `(user, workspace)`의 S2_00 실행은 host가 전역 직렬화한다. 단일 worker의 로컬 mutex만으로 다중 worker 안전성을 주장하지 않는다.
|
||||||
|
- task 간 메모리 공유는 가정하지 않는다. request bytes는 각 task가 동일한 규칙으로 재구성한다.
|
||||||
|
- 동일한 bytes의 request가 남아 있다는 사실만으로 prepare 성공을 대신하지 않는다. 현재 host 실행에서 prepare 성공 판정이 먼저 있어야 한다.
|
||||||
|
|
||||||
|
## Questions That Could Change the Outcome
|
||||||
|
|
||||||
|
구현 전에 **세 가지**만 먼저 확인한다: ① 동일 불변 인자를 두 task에 공급하는 실제 방식, ② exit code와 task 결과에 따른 success-only 후행 실행, ③ 취소·worker 장애·진행 중 I/O까지 포함한 workspace 직렬화. 세 조건이 지원되면 아래 최소안을 진행한다. 미지원이면 필요한 backend 보강을 명시하며, receipt나 lock 파일을 추가하는 것으로 문제가 해결되었다고 간주하지 않는다. 기존 지원만으로 가능하다는 현재의 실증 근거는 없다.
|
||||||
|
|
||||||
|
## Workstreams and Dependencies
|
||||||
|
|
||||||
|
### 1단계 — 외부 입력과 host 경계 확정
|
||||||
|
|
||||||
|
외부에서 공급하는 값은 다음 네 문자열이다.
|
||||||
|
|
||||||
|
| 필드 | 검증 |
|
||||||
|
|---|---|
|
||||||
|
| `request_id` | `[A-Za-z0-9][A-Za-z0-9._-]{0,127}` |
|
||||||
|
| `attempt_id` | 위와 동일; host 재시도 정책으로 결정 |
|
||||||
|
| `stage1_run_root_ref` | W 아래 승인된 Stage 1 사건 root의 NFC canonical 상대경로 |
|
||||||
|
| `stage1_deployment_root_ref` | W 아래 승인된 Stage 1 배포 root의 NFC canonical 상대경로 |
|
||||||
|
|
||||||
|
준비 task가 `schema_version=stage2_s2_00_execution_request.v1`, `workflow_id=S2_00`을 추가하여 정확히 6필드를 만든다. 누락·추가 외부 키와 잘못된 타입을 거절한다. 기존 path validator로 absolute path·`..`·역슬래시·NUL·비정규 경로를 거절하고, 접근 권한과 Stage 1 run 선택은 host가 결속한다. shell/Python source에 원문 인자를 직접 삽입하지 않는다.
|
||||||
|
|
||||||
|
host는 준비 task 시작 전 실행 소유권을 획득하여 **S2_00 전체 종료 및 미완료 I/O 종료까지** 유지한다. 이는 hydration 완료까지의 최소 보호 요구를 충족한다. 중간 해제 callback은 추가하지 않는다. 모든 writer·retry가 같은 통제에 참여해야 하며, 종료 여부가 불명확한 경우 다음 실행을 보류한다. TTL 만료만으로 takeover하지 않는다. host에 이 보장이 없으면 live-ready로 판정하지 않는다.
|
||||||
|
|
||||||
|
### 2단계 — 최소 준비 함수와 ingress 비교 구현
|
||||||
|
|
||||||
|
아래는 의미를 설명하는 pseudocode이며 현재 실행 가능한 API가 아니다.
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 양쪽 task에 host가 동일하게 공급한 네 값
|
||||||
|
expected = build_request(immutable_host_args) # 두 상수 추가, 정확한 외부 키 확인
|
||||||
|
expected_raw = canonical_json_bytes(expected)
|
||||||
|
_inline_validate_request(expected_raw)
|
||||||
|
|
||||||
|
# prepare task: 인증된 localdocs 세션 및 host 실행 소유권 아래 수행
|
||||||
|
localdocs.write_binary_verified(INLINE_REQUEST_PATH, expected_raw)
|
||||||
|
# 세션 종료까지 성공한 후 task 성공을 반환한다.
|
||||||
|
|
||||||
|
# ingress task: 현재 실행의 host_args로 expected_raw를 독립 재구성
|
||||||
|
actual_raw = localdocs.read_binary(INLINE_REQUEST_PATH) # 기존 첫 읽기에 통합
|
||||||
|
if actual_raw != expected_raw:
|
||||||
|
raise IngressError("RUN_REQUEST_INPUT_MISMATCH", "request differs from host input")
|
||||||
|
# 이후 기존 strict validation 및 2회 읽기/hydration을 계속한다.
|
||||||
|
```
|
||||||
|
|
||||||
|
- `build_request`는 네 값에 두 상수를 추가하는 작은 함수로 제한한다. 별도의 범용 serializer나 validator framework를 만들지 않는다.
|
||||||
|
- prepare는 기존 validator·canonical serializer·MCP IO helper의 동작을 재사용한다. 두 독립 inline payload에 필요한 helper는 authoring/build 시 포함한다. 외부 `.py` runtime import나 ingress 전체 실행 코드를 복제해 실행하는 방식은 피한다. 중복 포함된 helper의 일치는 좁은 parity 검사로 확인하며 새 공통 모듈 배포 체계까지 만들지 않는다.
|
||||||
|
- `write_binary_verified`가 bytes 일치를 확인하므로 추가 read-back/parse를 중복 수행하지 않는다. task 성공 결과에는 backend가 요구하는 최소 `ok`/error만 사용하며 새 버전 receipt schema·추가 파일은 만들지 않는다.
|
||||||
|
- ingress의 expected bytes 검사는 기존 첫 request 읽기에 결합한다. 이미 첫 읽기가 expected와 같고 두 번째가 첫 번째와 같은지 검사하므로 두 번째에서 동일한 expected hash를 다시 계산할 필요는 없다.
|
||||||
|
- 이 대조는 request ID만이 아니라 여섯 필드 전체를 확인한다. 서로 다른 요청으로 두 pass 모두 교체되더라도 거절한다. 다만 상호 배제는 여전히 host 책임이다.
|
||||||
|
- 실패 시 준비 작업 전체를 다시 실행한다. 남아 있는 파일을 신뢰해 ingress만 우회 실행하지 않는다. 정리 목적으로 request를 무조건 삭제하지 않는다.
|
||||||
|
|
||||||
|
### 3단계 — 두 task의 실행 순서와 실패 차단
|
||||||
|
|
||||||
|
```text
|
||||||
|
host: 동일 workspace 실행 소유권 확보, 네 인자 확정
|
||||||
|
→ IN
|
||||||
|
→ prepare: 검증 → 저장/read-back → 세션 종료 → 성공
|
||||||
|
→ host: 현재 실행의 prepare 성공일 때만 ingress 시작
|
||||||
|
→ ingress: expected bytes 일치 → 기존 2회 읽기/hydration → 기존 core/발행
|
||||||
|
→ OUT 또는 실패 정리
|
||||||
|
→ host: task와 미완료 I/O 종료 확인 후 소유권 해제
|
||||||
|
```
|
||||||
|
|
||||||
|
YAML의 nexts/wait_until은 위 순서를 정확히 기술한다. transport 응답 성공만으로 업무 성공을 판정하지 않는다. exit code 및 backend가 해석하는 task 결과에서 실패·잘못된 결과·timeout이면 ingress를 호출하지 않는다. 후행에 준비 receipt를 전달할 필요는 없지만, host의 task 성공 판정 자체는 생략할 수 없다. 직접 ingress 호출은 host admission으로 차단한다.
|
||||||
|
|
||||||
|
### 4단계 — 실제 영향 범위만 빌드·배포 갱신
|
||||||
|
|
||||||
|
| 항목 | 최소 변경 |
|
||||||
|
|---|---|
|
||||||
|
| `M/Stage_2_S2_00_v.2.yml` | prepare task와 지정 DAG, ingress expected bytes 검사 추가 |
|
||||||
|
| `R/offline_build/build_s2_00_inline_projection.py/.txt` | 정확히 두 task를 ID로 추출·검증. 0번 task가 ingress라는 가정 제거. 공통 코드 검사와 task별 필수 검사 분리 |
|
||||||
|
| `R/agent_scripts/Stage_2_S2_00.yml`, runtime mirror, `R/manifest/s2_00_inline_code_receipt.json` | 기존 파생 절차로 재생성. 두 code pointer/hash를 결속. 별도 runtime 준비 receipt와 혼동하지 않음 |
|
||||||
|
| prepare code mirror | 기존 mirror 정책을 유지하는 데 필요한 `runtime/s2_00_prepare_request.py/.txt`만 파생 생성. 새로운 독립 서비스는 아님 |
|
||||||
|
| workflow 및 executor binding | 외부 네 인자, success-only, 직렬화 책임과 prepare의 정확한 request 쓰기 경로 추가. ingress의 결과 root 쓰기 범위 유지 |
|
||||||
|
| 관련 schema | 2-task binding/빌드 receipt 표현이 바뀌어 기존 schema와 충돌하는 부분만 수정. 기존 execution_request의 6필드 schema는 유지 |
|
||||||
|
| module/release·code pin·Agent/workflow hash | 실제 변경 자산 및 이를 참조하는 hash만 재결속. 순환 결속을 새로 만들지 않고 기존 detached 절차 유지 |
|
||||||
|
| 기존 분석서·IO 문서 | 구현 완료 후 실제 두-task 구조, 인자 및 request 저장 시점을 반영 |
|
||||||
|
|
||||||
|
스키마를 안 바꾼다는 이유로 필요한 code hash를 누락하거나, 단순화를 이유로 기존 검증기를 무력화하지 않는다. 두 task 구성이 요구되는 한 builder·mirror·권한·hash 비용은 남는다. 별도의 준비 receipt schema와 전달 소비자만 제거하는 것이다. 빌드 재실행 시 동일 bytes인지 확인한다.
|
||||||
|
|
||||||
|
### 5단계 — 필요한 시험과 완료 판정
|
||||||
|
|
||||||
|
아래 Validation Plan을 실행하여 기준선에 있던 실패와 이번 변경의 회귀를 구분한다. host 지원 여부가 불확실하면 offline 구현·시험과 실제 운영 적격성을 별도로 보고한다. DEV guard를 해제해서 성공 결과를 만들지 않는다.
|
||||||
|
|
||||||
|
## Source and Tool Plan
|
||||||
|
|
||||||
|
원본 계획, 현재 authoring/runtime/builder/binding을 우선 근거로 삼는다. 외부 제품 권고나 일반론이 아닌 로컬 코드의 중복·의존성을 분석한다. 구현 시 backend 코드·문서로 세 선행 조건을 확인하고 기존 테스트 runner 및 빌드 `--check`를 사용한다. 실제 backend 통합 시험은 격리된 fixture 환경에서 수행한다.
|
||||||
|
|
||||||
|
## Validation Plan
|
||||||
|
|
||||||
|
| 시험 묶음 | 통과 기준 |
|
||||||
|
|---|---|
|
||||||
|
| 정상·잘못된 외부 입력 | 정상은 정확한 6필드 canonical bytes; 누락/추가/ID/path/type 오류는 저장 전 거절. 기존 request schema와 validator 수용 범위 일치 |
|
||||||
|
| 저장·read-back·종료 실패 | 후행 ingress 호출 0회. `ok:false`·비정상 exit·잘못된 성공 결과도 차단 |
|
||||||
|
| request 교체·잘못된 host 입력 결속 | 첫 읽기의 전체 bytes 대조 및 기존 두 pass 비교로 거절. stale request·다른 attempt·다른 roots 포함 |
|
||||||
|
| 경쟁·취소·재시도 | 같은 workspace의 두 worker는 직렬 실행; 이전 writer/I/O가 남은 동안 다음 실행 금지. 다른 workspace는 독립 실행 |
|
||||||
|
| 2-task 빌드·회귀 | 지정 두 task/순서만 허용, code/mirror/receipt/hash 일치, 반복 빌드 동일. 기존 정상 11+E/diagnostic 5파일 fixture 계약 유지 |
|
||||||
|
| backend 통합 | 동일 불변 인자 공급, success-only 실행, 전역 직렬화·권한 강제가 실제 작동 |
|
||||||
|
|
||||||
|
별도 준비 receipt, TTL/fencing 프로토콜, 재사용 최적화를 만들지 않으므로 이들의 전용 시험도 만들지 않는다. 단, worker 장애 이후 교차 쓰기가 없다는 기능 시험은 필수로 남는다. 신규 기능의 fixture 회귀와 실제 MCP/backend 실행 증거를 구분한다.
|
||||||
|
|
||||||
|
## Approval Boundaries
|
||||||
|
|
||||||
|
이번 요청 범위는 분석과 v1 계획 파일 작성이다. 원본 계획과 실행 자산을 보존한다. 이 문서 작성만으로 구현·배포·live 검증을 수행한 것으로 표현하지 않는다.
|
||||||
|
|
||||||
|
## Progress
|
||||||
|
|
||||||
|
- [x] 원본 계획과 현재 validator·IO·builder·binding 대조
|
||||||
|
- [x] 제약 유지안과 제약 완화 대안 구별
|
||||||
|
- [x] 중복 프로토콜·검증·최적화 제거 및 v1 작성
|
||||||
|
- [ ] backend 세 조건 확인
|
||||||
|
- [ ] 준비 task·ingress·빌드/배포 계약 구현
|
||||||
|
- [ ] 회귀·경쟁·backend 통합 검증 및 문서 갱신
|
||||||
|
|
||||||
|
## Decision Log
|
||||||
|
|
||||||
|
| 결정 | 근거·효과 |
|
||||||
|
|---|---|
|
||||||
|
| 독립 선행 task 유지 | 앞선 사용자 요구를 바꾸지 않음. 절대 최소 구조와 제약 내 최소안을 구분 |
|
||||||
|
| 동일 host 인자로 expected bytes 재구성 | 별도 준비 receipt·schema·전달을 제거하면서 전체 request 일치 검증 유지 |
|
||||||
|
| 기존 verified write/validator 재사용 | 현재 존재하는 동작을 새 계층으로 중복 구현하지 않음 |
|
||||||
|
| 전체 실행 직렬화 유지 | 중간 해제 신호보다 구현이 단순함. 동일 workspace 처리량 최적화는 이번 목표에서 제외 |
|
||||||
|
| 조건부 backend 확인 | 미확정 host 기능을 기정사실로 삼는 것이 가장 큰 숨은 복잡성이므로 먼저 검증 |
|
||||||
|
|
||||||
|
## Evidence Ledger
|
||||||
|
|
||||||
|
행번호는 2026-09-30 확인본 기준이다. runtime mirror를 구현 대조 자료로 사용했으며 authoring의 정본 지위를 바꾸지 않는다.
|
||||||
|
|
||||||
|
| 분석 명제 | 현재 확인 근거 | 판정 |
|
||||||
|
|---|---|---|
|
||||||
|
| 중복 없는 writer/validator 재사용 가능 | `R/runtime/s2_00_ingress.py`의 `write_binary_verified`(5492행), `_inline_validate_request`(5505행) | write/read-back 및 6필드 검증 존재 확인 |
|
||||||
|
| ingress 첫 읽기에 expected 대조 추가 가능 | 같은 runtime `_inline_hydrate`(5712행 이후) | 기존 첫 읽기 및 검증 순서 확인; 변경은 제안 |
|
||||||
|
| 2-task 빌드 변경은 생략 불가 | `R/offline_build/build_s2_00_inline_projection.py`: 326–378 | 단일 task·DAG 제약 확인 |
|
||||||
|
| 준비 receipt 제거의 전제 | 두 task가 동일한 인증된 불변 host 입력을 받는다는 새 계약 | 설계 제안; backend 실제 지원 UNVERIFIED |
|
||||||
|
| request 쓰기 권한 추가 필요 | `R/deployment/stage2_code_executor_binding.yml`: 47–66 | 기존 write root는 결과 폴더 |
|
||||||
|
|
||||||
|
## Risks and Failure Modes
|
||||||
|
|
||||||
|
직렬화가 실제로 보장되지 않으면 receipt를 제거하든 유지하든 고정 request 경로의 안전한 실행은 완성되지 않는다. task별 입력이 서로 다르거나 조작될 수 있다면 expected bytes 방식의 전제가 무너진다. 이 경우 host 입력 결속을 먼저 수정해야 한다. 인증된 인자·권한·잠금은 JSON을 단순하게 만드는 것과 별개의 필수 조건이다.
|
||||||
|
|
||||||
|
실패한 변경의 롤백은 진행 중 실행과 I/O 종료 후 검증된 기존 authoring·배포본·mirror·binding/hash 묶음으로 복원한다. 사건 자료·발행 결과는 삭제하지 않는다. 기존 버전도 request 외부 공급이 필요하므로 롤백이 자동 정상화를 뜻하지 않는다.
|
||||||
|
|
||||||
|
## Results and Residual Uncertainty
|
||||||
|
|
||||||
|
**기존 계획은 단순화할 수 있다.** v1은 두-task 요구를 유지하면서 신규 준비 receipt/전달 schema·독립 실행 envelope 설계·중복 read-back 검증·불필요한 사전 closure 읽기·재사용 최적화를 기본 범위에서 제거했다. 필수 인자 검증·저장 확인·후행 차단·동일 요청 소비·동시 실행 통제·hash 일치는 유지했다. 이는 코드 근거에 따른 설계 평가이며 성능 벤치마크 결과가 아니다. backend 세 조건의 실제 지원과 구현 완료 여부는 미확정·미착수다.
|
||||||
@@ -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