release(v4.14.0): multi-agent-mux-reset skill purges and recreates resident agents; registrations refuse during a reset; lsof holders in purge hints; codex purges its capability dir (Issue #78) - archive the review reports, bump the lockstep version
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
This commit is contained in:
1 parent
b49314c3d0
commit
d400add841
24 files changed
+491
-32
No files matched your search
@@ -0,0 +1,163 @@
|
||||
# PR #82 / Issue #78 — b49314c 리뷰: PASS
|
||||
|
||||
잡 `035792f2` 리뷰 결과다. 이전 리뷰의 **공유 live fence 덮어쓰기(R1)**와 **미생성 control_plane registry 누락(R2)**는 모두 해소됐다. 원래 실패를 일으켰던 공개 등록 CLI 탐침으로 확인했고, 이번 검토에서 차단 결함을 찾지 못했다.
|
||||
|
||||
## Identity·검토 범위
|
||||
|
||||
- Loop `loop-20261029T184637Z-6f4228`, round 3.
|
||||
- Base `a3364e82e1f9e9c6ac504eef377e8dbefba02b8d`.
|
||||
- Head `b49314c3d039027d3f9478cacecfda587fdc8abc`.
|
||||
- Tree `d49ff60bbf89437e997f7da21e2fae8df570456b`.
|
||||
- 검증 경로 `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261029T184637Z-6f4228/3`.
|
||||
- 기준은 읽기 전용 `tea issues 78 --comments`, `tea pulls 82`, `INSTRUCTION.md`이다. 누적 22파일과 직전 `568385c..HEAD`의 6파일 변경을 실제 snapshot에서 확인했다. 이전에 대조한 누적 reset/adapter/배포 코드와 이번 변경을 함께 검토했으며, truncated summary만으로 판단하지 않았다.
|
||||
- 파일:줄은 검증 경로 기준이다. creator 작업 트리에서 실행·수정하거나 커밋/브랜치 변경을 하지 않았다.
|
||||
|
||||
## 이전 차단 사항의 판정
|
||||
|
||||
### R1 — 공유 live fence 덮어쓰기: 해소
|
||||
|
||||
- `.agents/skills/lib_py/delegation/registry.py:97`의 reset_marker가 absent/stale/live/unknown 판정을 공유한다.
|
||||
- `.agents/skills/lib_py/reset.py:507`–`:518`은 각 registry_lock 안에서 기존 marker를 확인한다. 다른 PID의 live 또는 unknown owner이면 FenceBusy를 발생시키고 기존 marker를 교체하지 않는다.
|
||||
- `reset.sh:154`–`:163`은 fence가 성공한 경로와 거부 사유를 구분한다. EXIT cleanup(:117–124)은 이미 얻은 자신의 marker만 해제한다.
|
||||
- 체크인 테스트 `test_other_reset_cannot_take_a_live_fence[False/True]`는 B에 대상이 없는 경우/있는 경우를 모두 검증한다. A의 fence bytes 유지, B의 행·대화 보존, B marker cleanup, B 종료 후 외부 등록 거부 및 A 정상 완료를 확인한다.
|
||||
|
||||
**별도 재현:** [probe_shared_reset.py](evidence/probe_shared_reset.py), [shared.log](evidence/shared.log).
|
||||
|
||||
임시 A의 stop을 마지막 job 검사 뒤 gate에서 대기시킨 채, 같은 MAM_JOBS_DIR를 쓰는 독립된 임시 B에서 원본 reset.sh --yes를 실행했다. B rc=1, `fenced by a reset of A` 사유, 공유 marker bytes 동일, A는 계속 살아 있다. 이어 외부 public registry.py register도 rc=1로 거부돼 잡이 생기지 않았고 A는 rc=0으로 정상 완료했다. 이전 탐침에서 B가 rc0으로 marker를 제거하던 동작이 사라졌다.
|
||||
|
||||
추가로 ps 판정 불가를 주입한 탐침에서 신규 register/API와 새 fence 획득 모두 거부됐고 기존 foreign marker bytes가 보존됐다([unknown.log](evidence/unknown.log)). stale marker 교체 및 registry lock barrier는 체크인 테스트로 확인했다.
|
||||
|
||||
### R2 — 아직 없는 행 지정 registry가 fence에서 빠짐: 해소
|
||||
|
||||
- `.agents/skills/lib_py/update.py:150`–`:163`의 jobs_dirs는 존재 여부와 무관하게 canonical·MAM_JOBS_DIR·행의 control_plane.mam_jobs_dir를 realpath 기준으로 중복 제거하고 addressed_only 범위를 반환한다.
|
||||
- `reset.py:507`–`:509`가 같은 jobs_dirs를 사용해 없는 디렉터리도 만들고 lock/fence를 획득한다.
|
||||
- `update.py:166`–`:184`의 open_jobs도 같은 열거 함수를 사용한다. 읽기 검사는 없는 디렉터리를 계속 건너뛰고, 기존 unreadable-directory/record 거부 및 타 제어 평면의 addressed-only 의미를 보존한다.
|
||||
|
||||
**별도 재현:** [probe_absent_row_registry.py](evidence/probe_absent_row_registry.py), [absent.log](evidence/absent.log).
|
||||
|
||||
reset 시작 전에 target 행에 아직 없는 `<임시 WS>/elsewhere/jobs`를 control_plane.mam_jobs_dir로 기록하고, MAM_JOBS_DIR는 canonical 그대로 뒀다. 마지막 target 검사 뒤 같은 public CLI로 그 경로에 등록을 시도했다. 이번에는 rc=1, `a reset of ... is in progress`로 거부돼 새 잡이 없고 reset은 rc=0으로 끝났다. 중간 행 변경이나 raw 잡 JSON 쓰기를 사용하지 않았다.
|
||||
|
||||
체크인 `test_registry_cli_is_refused_after_the_target_recheck[row-absent]`와 fence의 absent 경로 생성·stale 교체·realpath dedupe 테스트도 통과했다. update의 open/terminal/dry-run, unreadable jobs directory, 타 control-plane 대상 필터 회귀 3건도 통과했다.
|
||||
|
||||
위 두 탐침의 `old_transcript_exists=false`는 외부 등록이 거부된 뒤 사용자가 승인한 정상 reset이 완료된 결과다. pending 잡을 허용한 상태로 대화를 지우던 이전 실패와 구분된다.
|
||||
|
||||
### 이전 라운드 보호의 유지
|
||||
|
||||
- canonical/alias/custom 공개 등록 거부는 체크인 매개변수 테스트로 재검증했다.
|
||||
- `reset.py:522`의 target_id_owned는 post-discovered ID의 cwd 소유권을 기존 verify_session_uuid(revalidate)로 확인한다. `reset.sh:283`에서 false이면 purge 대신 skip한다.
|
||||
- 이전 foreign Codex 탐침 재실행 결과는 **A 검증 false / B 검증 true / 외부 파일 보존 / reset rc4 / purge 명령 없음**이다([foreign.log](evidence/foreign.log)). own/foreign 체크인 대조도 통과했다.
|
||||
- strict spec 필드·타입·agent·workspace·중복 이름 검사, row agent/server 비교, pre-assigned ID 없는 행 거부, captured old ID 저장, created/done 미재purge, 시그널·중간 실패·rc4·rc5 재실행 보호가 유지됐다.
|
||||
|
||||
## 제안 1–6 및 SKILL.md 주장 대조
|
||||
|
||||
| 기준/문서 줄 | 실행 코드·테스트 근거 | 판정 |
|
||||
|---|---|---|
|
||||
| 제안1, SKILL:3·23 — root running, worktree/non-running/orchestrator 제외, metadata 유지 | reset.py:105–178 select_targets; reset.sh:306–317 create 인자; 선택/realpath/제외 행/2-target 정상 E2E | 충족 |
|
||||
| 제안2, SKILL:25 — 열린 잡·loop·update·자신·invalid agent/role·ID/spec 사전 거부 | reset.py:424–473; reset.sh:38–40·98–174; 각 거부·동시 admission 테스트 | 충족 |
|
||||
| 제안3, SKILL:17–18·24 — parser·확인·dry-run 무변경 | reset.sh:27–79·177–203; 비TTY rc3, dry-run .mam 파일무변경 | 충족. TTY 해석은 아래 사용자 확인 사항 |
|
||||
| 제안4, SKILL:27 — 첫 stop 전에 원자 spec, 재개 | reset.py:183–272 mkstemp/fsync/replace·strict schema; :275–353 record_old_id/classify_entry; reset.sh:203·228–230·337 | 충족. 5-state, 같은 ID resume, 중단 후 재실행 |
|
||||
| 제안4, SKILL:28 — 순차 stop → recorded/owned ID → purge → create/onboard | reset.sh:217–323, reset.py:522–537; 기존 stop/create 재사용 | 충족, 병렬 create 없음 |
|
||||
| 제안5, SKILL:29 — changed/open job/no ID/unverified/in-use skip, 외부 holder 미종료 | reset.sh:233–302, reset.py SKIP_RESULTS; skip·파일/행 보존·rerun 테스트 | 충족 |
|
||||
| 제안6, SKILL:33–39 — 결과 표와 exit0/1/2/3/4/5, INT/TERM/HUP | reset.py:599–683 build_table, reset.sh trap:126–134·결과:325–349 | 충족. 원본 Mac signal 테스트 사용 |
|
||||
| SKILL:26·47 — update admission → loop/reset marker → release; run_loop/update/다른 reset 거부; configured registry/alias·owner onboarding·stale 표식 | reset.sh:98–174·317; registry.py:97–135·232 이후; reset.py:492–519; canonical/alias/custom/absent·shared live fence·barrier·owner/stale | 충족. 이번 R1/R2 해소 |
|
||||
| 함께 고칠 것, SKILL:44 — lsof PID/명령줄·기존 ps 안내·서비스 제외·외부 미종료 | agents/base.py:89–115·385–403, agy in_use_paths, opencode _is_shared_service/_holder_counts | 충족. lsof 부재·timeout·holder·서비스 제외, 실제 Mac lsof 탐침 |
|
||||
| 함께 고칠 것 — Codex capability UUID leaf만 삭제 | adapters/codex.py:134–179, plausible ID/leaf/symlink 처리, delete 성공·실패 및 타 UUID 보존 | 충족 |
|
||||
| SKILL:43 — 비가역·먼저 dry-run | 도움말/동의 및 dry-run 분리 | 운영 권고. 과거 dry-run 실행 이력을 강제하는 규칙은 아님 |
|
||||
| SKILL:45 — Grok shared memory 보존 | 기존 Grok의 세션별 제거 유지, purge/adapter 회귀 | 충족. 메모리 삭제 옵션 추가 없음 |
|
||||
| SKILL:46 — AGY edit hook | hooks/loop_delegation_guard.sh:51과 reset.sh:110이 같은 loop marker 사용 | 코드 일치 |
|
||||
| SKILL:48 — 같은 종류 post-discovered 순차 ID 대기 | create의 기존 MAM_ID_ORDER_WAIT_S/adapter ordering, #63 회귀 | 코드 일치 |
|
||||
|
||||
명령 예시, 옵션/동작 표, exit 표, safety notes를 코드와 대조했다. 순서·사전 거부·원자 저장·잠금·정리는 스크립트에 있고 테스트로 고정된다. 이번 검토에서 SKILL에만 있고 실행 코드에 빠진 기계적 규칙을 찾지 못했다. 수기 잡 파일 쓰기는 등록 프로토콜 밖이므로 문서대로 target 재검사 때 이미 보이는 기록만 검출한다. 이 문구를 임의 파일 쓰기의 원자적 admission 보장으로 확대하지 않았다.
|
||||
|
||||
## 일반 registration·remote·update 영향과 단순화
|
||||
|
||||
- marker 없는 registry에서 register_job 10회 + remote mirror/replay 2회 동안 subprocess.run을 금지한 탐침이 통과했다. **일반 경로의 ps/subprocess 호출은 0회**다([fast.log](evidence/fast.log)).
|
||||
- live fence에서 remote.mirror_job은 ResetInProgressError를 처리해 None을 반환하고 잡 JSON/brief 디렉터리를 만들지 않는다. 기존 registry stdout/CLI/import/환경 계약과 remote finished/newer attempt 재전송도 통과했다.
|
||||
- 기존과 달리 live marker의 owner 예외·타 session 범위 판단 전에 reset_marker가 liveness를 조회한다. 따라서 marker가 존재하는 registry에서는 최대 두 ps 호출이 있을 수 있다. reset이 없는 보통 등록의 비용 증가와 구분해야 하며 차단 사유는 아니다.
|
||||
- addressed-only 표식이 있는 타 control-plane registry에서 covered session은 거부하고 unrelated session은 등록되는 별도 탐침도 통과했다([unknown.log](evidence/unknown.log)).
|
||||
- 이전 위험한 중복이었던 registry 경로 열거가 jobs_dirs 한 함수로 합쳐졌다. reset은 없는 경로에 보호를 설치하고 update는 읽기만 하는 차이는 각 호출부에 남아 적절하다. 순서/필터를 바꾸는 불필요한 재설계는 아니다.
|
||||
- reset.py 934줄/reset.sh 349줄이라는 양 자체를 결함으로 세지 않았다. 기존 stop/create·verify_session_uuid·open_jobs 재사용이 유지된다. reset.py:54–100의 read-only state 로더는 실제 SQLite -shm까지 바꾸지 않는 dry-run 요구 때문에 복사본을 읽는다. 환경 JSON만 읽는 update._state(:375)로 단순 치환할 수 없으며 추가 추상화는 승인 조건이 아니다.
|
||||
|
||||
## 패키징·버전
|
||||
|
||||
install.sh의 일반 .agents 복사(:367)와 reset 도움말(:673), remove.sh:96 fallback, gitea-ci.yml:45 ShellCheck 대상, 두 README:60, code-block inventory 및 10-skill layout을 확인했다. detached snapshot에 실제 .claude/skills가 없어 관련 1건은 skipped이며 합성 layout 검사는 통과했다. SKILL version과 lib.sh MAM_VERSION은 4.13.0으로 일치한다. test_version_consistency 목록과 VERSIONS 매트릭스는 브리프대로 릴리스 때 오케스트레이터가 넣을 사항으로 확인했고 누락 결함으로 세지 않았다.
|
||||
|
||||
## Mac 실행·정적 검사
|
||||
|
||||
Darwin arm64, /bin/bash 3.2.57, 지정 제어 루트 venv Python 3.9.6. 검증 tree에는 venv가 없어 브리프 상단의 명시적 인터프리터를 썼다. 모든 pytest cwd는 검증 경로다. 전체 스위트는 실행하지 않았다.
|
||||
|
||||
공통 명령:
|
||||
```bash
|
||||
env -u HERDR_SOCKET_PATH -u HERDR_PANE_ID -u HERDR_TAB_ID \
|
||||
-u HERDR_WORKSPACE_ID -u HERDR_SESSION_NAME -u HERDR_SESSION \
|
||||
-u WORKSPACE_ROOT -u MAM_ENV_FILE -u MAM_JOBS_DIR \
|
||||
-u DELEGATE_JOB_LOGS_DIR -u AGENT_SESSIONS_YAML \
|
||||
PYTHONDONTWRITEBYTECODE=1 \
|
||||
/Users/godopu16/PuKi/laa/canary_projects/multi-agent-mux/.venv/bin/python \
|
||||
-m pytest -p no:cacheprovider --basetemp=<TMPDIR/아래 이름> -q <선택>
|
||||
```
|
||||
|
||||
| basetemp 이름 (TMPDIR 아래) | 선택 파일/옵션 | 결과 |
|
||||
|---|---|---|
|
||||
| mam-eval-035792f2-main | tests/test_issue78_reset.py tests/test_issue78_hints_codex.py tests/test_issue42_skill_blocks.py | **84 passed, 1 skipped, 1 session-teardown error / 700.03s**. [main.log](evidence/main.log) |
|
||||
| mam-eval-035792f2-purge | tests/test_issue10_13_purge.py tests/test_issue31_purge_residual.py tests/test_a4_adapter_contract.py tests/test_issue63_same_cwd_ids.py | **150 passed / 282.73s**. [purge.log](evidence/purge.log) |
|
||||
| mam-eval-035792f2-contracts | 아래 9 node, 등록 node 양 mode 포함 | **10 passed / 16.38s**. [contracts.log](evidence/contracts.log) |
|
||||
| mam-eval-035792f2-shared | -s -p probe_shared_reset tests/test_issue78_reset.py | **1 passed / 33.96s**. [shared.log](evidence/shared.log) |
|
||||
| mam-eval-035792f2-absent | -s -p probe_absent_row_registry tests/test_issue78_reset.py | **1 passed / 32.64s**. [absent.log](evidence/absent.log) |
|
||||
| mam-eval-035792f2-foreign | -s -p probe_foreign_id tests/test_issue78_reset.py | **1 passed / 20.60s**. [foreign.log](evidence/foreign.log) |
|
||||
| mam-eval-035792f2-fast | -s -p probe_registry_paths tests/test_issue78_reset.py | **2 passed / 0.19s**. [fast.log](evidence/fast.log) |
|
||||
| mam-eval-035792f2-native | -s -p probe_native_holders tests/test_issue78_reset.py | **2 passed / 0.28s**. [native.log](evidence/native.log) |
|
||||
| mam-eval-035792f2-unknown | -s -p probe_unknown_fence tests/test_issue78_reset.py | **2 passed / 0.16s**. [unknown.log](evidence/unknown.log) |
|
||||
|
||||
일반 선택 회귀는 **244 passed, 1 skipped, 1 session-teardown error**, 별도 외부 탐침은 **9 passed**다. 같은 사례를 반복 실행해 합산하지 않았다.
|
||||
|
||||
종료 오류 분석용 재실행(중복 합산 안 함):
|
||||
|
||||
- `mam-eval-035792f2-layout-rerun`: `tests/test_issue42_skill_blocks.py::test_claude_skills_layouts[broken_entry-False]` — **1 passed / 0.04s**.
|
||||
- `mam-eval-035792f2-agy-rerun`: `tests/test_issue78_hints_codex.py::test_in_use_hints_list_lsof_holders[agy]`, `tests/test_issue78_reset.py::test_post_discovered_without_recorded_id_8b_and_exit_5` — **2 passed / 64.39s**.
|
||||
|
||||
|
||||
contracts의 node 목록:
|
||||
```text
|
||||
tests/test_issue5_phase1_delegation.py::test_c05_submit_job_stdout_is_exactly_the_job_id
|
||||
tests/test_issue5_phase1_delegation.py::test_registry_cli_contract_snapshot
|
||||
tests/test_issue5_phase1_delegation.py::test_delegation_package_imports_no_sibling_lib_py_modules
|
||||
tests/test_issue5_phase1_delegation.py::test_importing_delegation_does_not_mutate_environment
|
||||
tests/test_issue18_liveness_contract.py::test_remote_runner_ignores_a_resent_dispatch_of_a_finished_attempt
|
||||
tests/test_issue18_liveness_contract.py::test_remote_runner_never_rolls_a_newer_attempt_back
|
||||
tests/test_issue72_update.py::test_open_jobs_refuse_and_terminal_or_dry_run_do_not
|
||||
tests/test_issue72_update.py::test_a_jobs_directory_that_cannot_be_listed_refuses
|
||||
tests/test_issue72_update.py::test_other_control_planes_count_only_jobs_addressed_to_this_registry
|
||||
```
|
||||
|
||||
외부 플러그인은 명령 앞에 `PYTHONPATH=<evidence 절대 디렉터리>`를 더하고 `-s -p <probe 모듈> tests/test_issue78_reset.py`로 선택한다. probe 파일은 collection을 해당 탐침으로 바꾸며 snapshot의 fixtures/helpers를 그대로 쓴다. shared 탐침은 이전 실패 때의 B rc0 기대만 이번 보호 정책인 rc1/동일 marker bytes 기대로 바꿨고, 외부 register·실제 reset 동작은 유지했다. 제품 코드/체크인 테스트는 수정하지 않았다.
|
||||
|
||||
변경 Python 11파일 ast.parse(feature_version=(3,9)), lib.sh/reset.sh/delegate wrapper 각각 /bin/bash -n, git diff --check가 통과했다([static.json](evidence/static.json)). 새 reset 경로에 mapfile/declare -A/flock(1)/setsid(1)/proc, GNU 전용 sed -i/stat -c/readlink -f 의존을 추가하지 않았다. 빈 배열·trap은 native 동작 테스트로 확인했다. 실제 Mac lsof가 자신이 만든 holder PID를 찾았고, timeout 주입 시 10초 상한과 빈 추가 힌트를 확인했다. ShellCheck는 설치되어 있지 않아 실행하지 않았다.
|
||||
|
||||
## 세션 종료 guard 오류와 판정 한계
|
||||
|
||||
main 실행은 기능 단언 **84 passed, 1 skipped**였지만 세션 종료의 기존 agent_home_leak_guard가 **1 error**를 냈다. 따라서 이 명령의 exit code는 **1**이며, 오류 없는 전체 스위트 통과라고 보고하지 않는다([main.log](evidence/main.log)).
|
||||
|
||||
감시기는 실제 AGY 홈에서 `637fea1e-9288-4a72-857e-7d23e04bb12d.db-wal`과 `.db-shm`의 새 디렉터리 항목을 발견했다. 기존 conftest.py:1582–1598도 병행 실제 agent가 새 파일을 만들면 false positive가 가능하다고 설명한다. 오류는 마지막 레이아웃 사례에 붙었지만 그 사례는 tmp_path의 symlink layout만 만든다.
|
||||
|
||||
- DB를 SQLite로 열거나 수정하지 않고 lsof의 PID와 ps의 실행 파일·경과 시간만 확인했다. 보유자는 실제 `agy` PID **66608**(약 11시간 실행), **91891**(약 34분 실행)으로, 11분 40초의 main 실행보다 먼저 시작한 외부 프로세스다([ambient-holder.json](evidence/ambient-holder.json)).
|
||||
- main basetemp 안에는 해당 UUID의 artifact가 없었다([ambient-basetemp.json](evidence/ambient-basetemp.json)). AGY reset 시험은 UA 상수와 임시 HOME을 쓰며, hints 시험은 임시 home/HOME_DIR/ctx와 mock lsof를 쓴다.
|
||||
- 마지막 레이아웃 node 단독 재실행은 **1 passed / 0.04s**, 종료 guard도 통과했다([layout-rerun.log](evidence/layout-rerun.log)).
|
||||
- AGY hints와 post-discovered stop/purge/create/재실행 경로의 선택 재실행: **2 passed / 64.39s, 종료 guard 통과**([agy-rerun.log](evidence/agy-rerun.log)). 재실행 수는 일반 회귀 합계에 중복 합산하지 않았다.
|
||||
|
||||
위 프로세스·fixture·재실행 증거로 이 종료 오류는 **외부 AGY 동시 활동에 따른 환경 감시 false positive로 판단한다(추론)**. 테스트 누수라고 단정하거나 실제 홈 파일을 제거하는 방식으로 감시기를 통과시키지 않았다. 제품의 이번 변경에 의한 기능 실패는 없어 PASS지만, main 명령 자체의 종료 오류는 그대로 증거와 함께 남긴다.
|
||||
|
||||
## 발견 사항·사용자 확인 필요
|
||||
|
||||
- **차단 사항 없음.** 이전 R1/R2는 해소됐으며 재계획이 필요하지 않다.
|
||||
- 비차단: marker가 남아 있는 경로의 liveness 조회 비용은 위 설명과 같다. 추가 작업을 승인 조건으로 삼지 않는다.
|
||||
- 순차 create, Grok 메모리 옵션 없음, stopped 초기 대상 제외는 작성자의 선택대로 검토했다.
|
||||
- 이슈의 “--yes 없이는 실행하지 않는다”와 TTY 명시적 y/yes 동의 방식의 차이는 이전 판정처럼 사용자 확인 사항이다. 동의를 받는다는 의미로 검토했으며 flag 자체 강제를 요구한 것으로 보아 차단하지 않았다.
|
||||
|
||||
## 격리·종료 확인
|
||||
|
||||
실제 작업 공간에 reset/dry-run/purge를 실행하지 않았다. 파괴적 동작은 임시 HOME·자체 .mam·mock herdr 안에서만 수행했다. 실제 .mam.env 값 출력/export, 실제 herdr 서버/설정 변경, SQLite로 실제 에이전트 홈 DB 열기, 실제 opencode 서비스 제어를 하지 않았다. guard 오류 분석의 실제 홈 확인은 위 PID/실행 파일·나이 메타데이터뿐이며 DB 내용은 읽지 않았다. pkill/killall/패턴 kill을 쓰거나 실제 모니터에 신호를 보내지 않았다. native holder 탐침은 자신이 만든 Popen만 회수했다.
|
||||
|
||||
이번 basetemp에 속한 테스트 잔류 프로세스는 0개, 실제 모니터는 전후 PID 3714로 같았다([process-audit.json](evidence/process-audit.json)). 시작·종료 HEAD/tree는 위 identity와 일치했고 git status는 빈 출력이다. 소스/브랜치/이슈·PR 상태 변경, push/merge는 없었다. 제어 평면 쓰기는 승인된 lifecycle 이벤트와 리포트·증거뿐이다.
|
||||
|
||||
[VERDICT: PASS]
|
||||
Reference in new issue
Block a user