감사 기록
이 파일은 한 장이다. 유닛 브랜치가 각각 적으면 병합에서 부딪친다. 진행자만
커밋한다 — START.md §5.
2026-09-04 — 워크스페이스 준비 2판 (사람이 손으로 적음)
- enode
main1c290a6위에 v1 규칙 설치.CLAUDE.md는 v1 core-workflow,
원래 규약은CONVENTIONS.md - Reverse Engineering 산출물 아홉을 1판 저장소에서 이관. 기준선
f46e187과의
차이를reverse-engineering-timestamp.md머리에 적음 - 설계 정본
enode-design을 서브모듈로0fd304b에 고정 - 1판의 미정 25개를
requirements/decisions.md로 닫음. 사용자 확정 1절 ·
팩이 채운 권장값 2절 - 확장 설정 — security-baseline 켬, 나머지 둘 끔 (사용자 결정)
dhseo장면의 일을 「LED 점멸 패턴을 바꾸는 ko 또는 프로그램 작성」으로 확정- 화면
design/을 pen 파일과 함께 실음. 그리기 규칙은design/README.md
팩 검토 — 사실 오류 정정
Timestamp: 2026-09-04T08:31:14Z
User Input: (검토 지시는 2026-09-04 항목의 원문 그대로 — "에이전트 풀어서 1. 초기 req 팩의 모순이나 불분명 탐지. 2. adr과 shift-left 적용된 요구사항 문서의 정합성. 두개 검토 시켜. 병렬로.")
AI Response: 두 검토를 병렬로 돌리고 보고를 합쳤다. 판단이 필요 없는 사실 오류 열넷을 1c290a6 코드와 대조해 고치고 fdb3bce 로 커밋. 사람이 정할 것 셋(at-boundary 와 ADR-063 §2.1 충돌 · SECURITY-08/12 지침 줄 · --listen 의 토큰)은 보고에 올리고 승인을 기다린다.
Context: 워크샵 준비. v1 착수 전. GitHub 저장소 생성 · 푸시는 승인 뒤
팩 검토 — 갈림 셋의 결정
Timestamp: 2026-09-04T08:50:33Z
User Input: "1. 권장안으로 결정. 2. 제안 수용. 3. 제안 수용."
AI Response: ① at-boundary 가 놓은 Run 은 Mediator 가 postResult 끝에서 store.Cancel(run, "drain:<node_id>") 로 닫는다. ADR-063 §2.1 은 graceful 한정으로 개정 — canon.md 3절 선행 조건에 추가. ② SECURITY-08 · 12 지침 줄을 「새 표면에 적용. 객체 수준 권한만 N/A. 제어판 127.0.0.1 은 명시적 public」으로. ③ --listen 이 127.0.0.1 밖이면 정책 파일 panel_token 필수, Mediator 토큰 재사용 안 함. 팩에 반영해 커밋.
Context: 워크샵 준비. 검토 갈림 해소. 다음은 GitHub 저장소 생성 · 푸시 · v1 착수
착수 지시 — Requirements Analysis 부터 Construction 까지
Timestamp: 2026-09-04T11:48:04Z
User Input: "enode 에 기능을 추가한다. 요구사항은 이미 문서로 있고 미정이 없다.
requirements/decisions.md 이미 정해진 것. 값과 근거. 다시 논의하지 않는다
requirements/enode-features.md 기능 넷. 목적 · 기능요구사항 · 데이터 · 수용기준
requirements/unit-boundaries.md 권장 유닛 경계와 유닛마다 만지는 파일
requirements/scene-gates.md 장면 조각 게이트 CP0 ~ CP4. 이것이 수용 기준이다
requirements/canon.md 설계 정본 enode-design 과의 연결
requirements/constraints.md 구현하지 않는 것 여덟 범주
design/README.md 화면 여덟 장의 규칙. 화면이 있는 유닛은 읽는다
설계 정본은 서브모듈 enode-design/ 이다. 어긋나면 protocol/INVARIANTS.md 가 이긴다.
canon.md 가 어느 ADR 을 읽을지 적어 두었다.
Reverse Engineering 은 끝나 있다. aidlc-docs/inception/reverse-engineering/ 에
아홉 산출물이 있고 기준선과 지금 코드의 차이는 reverse-engineering-timestamp.md
머리에 있다. 다시 훑지 마라.
Requirements Analysis 부터 Units Generation 까지 간 다음 Construction 에 들어간다.
유닛은 unit-boundaries.md 의 여섯을 기본으로 한다. 벗어나면 근거를 적는다.
질문은 계약(함수 서명 · 파일 경로 · 응답 모양)에 걸리는 것만 한다. 단계마다
셋을 넘기지 않는다. decisions.md 에 있는 값은 묻지 않는다. 권장값을 벗어나야
한다면 묻지 말고 근거를 적어라.
Units Generation 의 산출물에는 유닛마다 다음을 포함해라.
- 이 유닛이 무엇을 완료로 삼는가. 화면이 있으면 그 화면의 버튼을 전부 나열 한다
- 이 유닛이 어느 요구사항 절에서 왔는가
- 이 유닛이 다른 유닛에 의존하는가
- 이 유닛이 만지는 파일. 신설이면 NEW 를 붙인다
유닛은 계획 단위다. 배포 단위가 아니다.
Construction 에서는 유닛마다 Code Generation 계획에 scene-gates.md 의 해당 조각
명령을 실제로 돌리는 단계를 넣어라. 조각이 초록이 아니면 다음 유닛에 들어가지
않는다. 계획 파일은 Units Generation 과 Code Generation 에서만 만든다. 확장
준수 요약은 blocking 이 있을 때만 적는다.
타임스탬프는 추정하지 말고 date -u +%Y-%m-%dT%H:%M:%SZ 를 돌려서 적어라."
AI Response: 규칙 상세를 .aidlc-rule-details/ 에서 해석하고 공통 규칙 여섯을 실었다. 확장 opt-in 파일 셋을 훑어 프롬프트만 읽었고, aidlc-state.md 의 확장 설정(security-baseline 켬)이 이미 답을 갖고 있어 security-baseline 규칙 전문을 실었다. requirements/ 여섯 문서와 design/README.md 를 읽고, 계약을 확인할 코드(internal/api/api.go · internal/store/store.go · observe.go · reap.go · match/match.go · contract/advert.go · schema.sql · internal/enode/advertise.go · internal/runctl/client.go)를 대조했다. Reverse Engineering 산출물은 다시 훑지 않았다. Requirements Analysis 착수.
Context: INCEPTION. Requirements Analysis 시작. Office Task 79ea5603 에 바인딩
Requirements Analysis — 확인 질문 셋을 냈다
Timestamp: 2026-09-04T11:49:43Z
AI Response: requirements/ 여섯 문서와 design/README.md 를 요구사항 입력으로 받고 깊이를 comprehensive 로 잡았다. 미정은 enode-features.md §5 대로 없었고, 게이트에서 계약에 걸리는 것 셋만 새로 열렸다 — ① QUEUED Run 의 steps 행을 제출 시점에 만드는가 승격 시점에 만드는가(GET /v1/runs/{id} 응답 모양) ② store.WakeQueued 가 한 번에 보는 범위(도착 순서 전부 대 머리 하나) ③ SECURITY-04 를 새 HTML 표면 둘에 어떻게 거는가(decisions.md 3절이 04 를 안 적었고 확장은 기본이 차단성). aidlc-docs/inception/requirements/requirement-verification-questions.md 에 권장안을 A 로 두고 냈다. 확장 opt-in 셋은 aidlc-state.md 의 사용자 결정 2026-09-04 을 답으로 채워 두었다 — 새로 묻지 않는다.
Context: INCEPTION / Requirements Analysis. Step 6 게이트. 답을 받기 전에는 requirements.md 를 쓰지 않는다
Requirements Analysis — 코드 대조로 닫힌 것 둘
Timestamp: 2026-09-04T11:49:43Z
AI Response: 질문 후보였다가 코드를 보고 닫힌 것 둘을 기록한다. ① decisions.md 2절이 「verdict 가 drain 으로 닫힌 FAILED 와 진짜 실패를 가른다」고 했는데, internal/store/reap.go 의 Cancel 이 이미 verdict.checks[0].note = "cancelled by: <by>" 를 쓴다. Cancel(ctx, run, "drain:<node_id>") 로 부르면 그 문장이 그대로 참이 되고 새 열도 새 필드도 필요 없다. ② CP2 가 runctl submit 의 202 와 runctl status 의 QUEUED 표시를 요구하는데, internal/runctl/client.go 의 do 가 StatusCode/100 == 2 로 받고 cmd/runctl/main.go 가 r.State 를 그대로 찍으며 Terminal 이 SUCCEEDED/FAILED 뿐이라 --wait 도 계속 돈다. runctl 은 고칠 것이 없다 — unit-boundaries.md §2 의 파일 행렬에 runctl 이 없는 것이 누락이 아니라 맞다.
Context: INCEPTION / Requirements Analysis. 코드 기준선 1c290a6
Requirements Analysis — 질문 셋의 답과 requirements.md
Timestamp: 2026-09-04T12:18:04Z
User Input: "답했다." (requirement-verification-questions.md 의 [Answer] 태그 — Q1: A · Q2: A · Q3: A. 확장 opt-in 은 기존 값 그대로 security A · resiliency B · PBT C)
AI Response: 답 셋에 모순도 모호도 없어 후속 질문을 안 냈다. aidlc-docs/inception/requirements/requirements.md 를 comprehensive 깊이로 냈다 — 의도 분석 · 입력 · 답 셋의 값(3절) · FR 24개 추적표(요구사항 절 -> 유닛 -> 게이트) · NFR 다섯 묶음(보안 9 · 불변식 7 · 패키지 경계 6 · 관측 6 · 품질 5 · 성능) · 수용 기준(조각 게이트) · 범위 밖. 권장값을 벗어난 자리 둘에 근거를 적었다 — 광고 응답의 정책 왕복 필드 이름(4.2절)과 제어판 라우트 일곱(4.3절). 새로 정한 값 하나 — limit 상한 1000 (SECURITY-05 가 명시적 상한을 요구하는데 decisions.md 2절이 기본 100 만 정했다). 확장 준수에 blocking 없음.
Context: INCEPTION / Requirements Analysis 완료. 다음은 User Stories 판정 후 Workflow Planning
Requirements Analysis — 승인
Timestamp: 2026-09-04T12:27:54Z
AI Prompt: "Approve & Continue — 승인하고 Workflow Planning 으로 간다"
User Response: "승인"
Status: Approved
Context: User Stories 를 건너뛴다는 판정도 함께 승인됐다
Workflow Planning — 실행 계획
Timestamp: 2026-09-04T12:27:54Z
AI Response: 실행 계획을 aidlc-docs/aidlc-state.md 의 「실행 계획」 절에 넣었다. inception/plans/execution-plan.md 를 만들지 않았다 — 사용자 지시가 「계획 파일은 Units Generation 과 Code Generation 에서만 만든다」이고, 이 단계의 산출(어느 단계를 돌리는가와 근거)은 상태 파일이 이미 지는 짐이라 파일을 하나 더 만들면 같은 표가 두 벌이 된다. 판정 — 실행은 Units Generation · Functional Design(유닛 넷) · Code Generation(유닛마다) · Build and Test. 건너뜀은 User Stories · Application Design · NFR Requirements · NFR Design · Infrastructure Design. 위험은 Medium 이고 높은 자리 둘이 다 internal/api/api.go 의 기존 뜨거운 경로(submit · postResult)다. 유닛을 여섯에서 다섯으로 줄였다 — 근거는 실행 계획 4절. 코드 대조로 위험 하나를 지웠다 — Reap 이 QUEUED Run 을 안 쓸어간다. 확장 준수에 blocking 없음.
Context: INCEPTION / Workflow Planning 완료. 승인 대기
화면 대조 — pen.dev 초안이 고려됐는지에 대한 지적
Timestamp: 2026-09-04T13:06:29Z
User Input: "워크플로우에서 대시보드 ui 디자인 초안인 Pen.dev 가 고려가 되었는지?"
AI Response: 안 됐다. Workflow Planning 까지 읽은 것은 design/README.md(그리는 규칙)뿐이고 그림 자체를 안 열었다. pencil MCP 는 이 환경에서 안 붙어(failed to connect to running Pencil app: code-server) enode-ux.pen 은 못 열었고, design/exports/ 의 PNG 여덟 중 다섯(S1 · S1b · S2 · S3 · S5)을 읽었다. 결과를 requirements.md 8절로 적었다 — 8.1 확인된 것(label 이 local@host:configbase 라 S1 카드 제목은 principal 이 아니다. ADR-065 를 안 어긴다), 8.2 근거를 적고 닫은 여덟(S1b 의 SEALED 는 SUCCEEDED 로 · produced 는 기존 ledger 라우트로 · draining 두 국면은 파생 · at-boundary 카드의 표시 규칙 · 재시작은 합성 · 트랜스크립트는 비활성 · S5 에 drain 블록 없음 · 임대 진행 막대는 파생), 8.3 화면의 버튼 전부(유닛 완료 조건의 재료), 8.4 계약 구멍 셋. 셋을 design-verification-questions.md 로 냈다 — ① S1 카드의 현재 단계와 Run 행 부제의 출처 ② enodectl serve 의 노드 범위 ③ 멈춘 노드의 마지막 관측. Units Generation 을 그 전에 시작하지 않는다 — 셋 다 U-바닥과 U-관측API 의 응답 모양이다.
Context: INCEPTION. Workflow Planning 승인 뒤 Units Generation 착수 전. Requirements 에 화면 대조 절이 붙었다
화면 대조 — pen.dev CLI 설치
Timestamp: 2026-09-04T13:12:45Z
User Input: "npm install -g @pen.dev/cli"
AI Response: 깔았다 — @pen.dev/cli 0.3.6, pen 과 pencil 두 이름으로 붙는다. pen interactive 가 헤드리스로 .pen 을 열 수 있으나 CLI 인증이 먼저다 — Not authenticated. ~/.pencil/socket/pencil-visual_studio_code.sock 으로 실행 중인 앱이 하나 있어 --app visual_studio_code 로도 걸어 봤으나 같은 인증 요구다. PEN_CLI_KEY 도 없다. 그동안 남은 PNG 를 마저 읽어 S0 · S0b 를 requirements.md §8.3 에 채웠다 (토큰 표시·숨김 토글 · 현황판 열기 · S0b 의 401 인라인). 읽은 화면이 일곱이 됐고 못 본 것은 C3 · C4 · C5 뿐이다 — export 가 없고 .pen 안에만 있다. §8.3.1 로 적었다. C3(drain 을 건 뒤의 「풀기」)는 U-제어판 완료 조건에 직접 걸린다.
Context: INCEPTION. 화면 대조 질문 셋은 여전히 열려 있다
화면 대조 — .pen 원본을 열었다
Timestamp: 2026-09-04T13:17:29Z
User Input: "로그인함 mcp 없어도 cli로 쓸수있나"
AI Response: 된다. MCP 는 실행 중인 앱에 붙는 통로이고 CLI 헤드리스는 파일을 직접 연다 — pen interactive --in design/enode-ux.pen --out <스크래치패드>. save() 를 안 불렀고 --out 을 저장소 밖에 둬서 원본은 안 건드렸다 (START.md §5). 셋을 얻었다. ① C3 · C4 · C5 를 읽었다 — C3 의 버튼은 「drain 풀기 · 함대로 돌려주기」 하나이고 모드 라디오가 없다. C5 의 실제 글자는 design/README.md §2.2 가 적은 「draining · 대기 중」이 아니라 「draining · 대기 중(owner)」 이다. ② C3 의 각주가 requirements.md §4.3 의 라우트 설계를 정정했다 — 「걸 때 고른 모드는 풀기 전까지 바뀌지 않는다」. PUT /api/policy 는 걸기와 풀기만 받고 모드 전이(graceful <-> at-boundary)는 409 다. 정본은 여전히 노드의 파일이라 소유자가 손으로는 고칠 수 있고, 라우트가 막는 것은 「되찾았다」의 뜻이다. ③ 원본이 코드와 어긋난 자리 둘을 확정했다 — S1b 의 필터·배지 보이는 글자가 SEALED 다 (S1 은 SUCCEEDED. §6 의 괄호는 S1 만 덮었다), S2 의 단계 배지와 범례가 RUNNING 인데 steps.state 에 RUNNING 은 없고 CLAIMED 다. 둘 다 design/README.md §6 이 판정한다. 덧붙여 S2 범례가 넷뿐이라 FAILED 와 ASKED 가 빠져 있다. .pen 자체에 스키마 흠 하나(textGrowth: auto-height)가 있으나 안 고쳤다 — design/ 은 진행자만 고친다.
Context: INCEPTION. requirements.md 8절이 8.6 까지 찼다. 화면 대조 질문 셋은 여전히 열려 있다
세션 인계
Timestamp: 2026-09-04T13:58:57Z
User Input: "새로운 세션에서 계속 하려고. 문제 없지? 답은 그 세션에서 할게"
AI Response: 문제 없다. 산출물이 전부 디스크에 있고 코드는 한 줄도 안 고쳤다. requirements.md 8절의 절 순서를 바로잡았다 (8.4 가 8.5 · 8.6 뒤에 있었다). Office 에 인계 상태를 새로 적었다 — 확정 다섯 · 열린 질문 셋 · 다음 안전 행동. design-verification-questions.md 의 [Answer] 셋이 지금 A · A · A 로 채워져 있다 — 내가 빈 칸으로 냈으므로 누군가 채웠다. 새 세션에서 답하겠다고 했으므로 그 세션이 이 값을 그대로 쓸지 확인해야 한다. 커밋은 안 했다 — audit.md 규약이 진행자만 커밋한다고 적었고 새 세션이 같은 작업 트리를 본다.
Context: INCEPTION. Units Generation 착수 전. 다음 세션의 출발점은 aidlc-docs/aidlc-state.md 의 Current Status
화면 대조 질문 셋 — 답
Timestamp: 2026-09-04T14:02:47Z
User Input: "답했다." (design-verification-questions.md 의 [Answer] 태그 — Q1: A · Q2: A · Q3: A. 이전 세션이 「누군가 채웠다」로 남긴 값이 사용자 자신의 답임을 확인했다)
AI Response: 셋을 값으로 굳혔다. requirements.md §8.4 가 질문이 아니라 결정이 됐고 셋 다 decisions.md 2절의 권장을 넘긴 자리라 근거를 적었다 — ① GET /v1/runs 행에 current 와 reject 를 더한다 (새 열 0. runs.reject 는 이미 있는 열이고 current 는 steps 조인이다. ADR-065 개정 범위가 draining 하나로 유지된다) ② enodectl serve 가 이 기계의 노드를 전부 서브하고 라우트가 ?node= 를 받는다 (enode-features.md 3.1.2 의 「여럿이면 이름 필수」를 넘긴 자리) ③ 제어판이 노드마다 마지막 성공 관측을 메모리에 든다 — GET /api/status 가 observed 와 stale 을 낸다. 딸린 개정 — FR-2 · FR-17 을 고치고 FR-25 를 더했다. §4.1 계약 셋 추가 · §4.3 라우트 전부에 ?node=. 코드 대조로 답 A 의 덜 정해진 자리 셋을 찾았다 — current 가 폭 1 을 넘는 Run 에서 단수면 카드가 자기 노드 단계를 못 고른다 · match.Reject 에 json 태그가 없어 기존 GET /v1/runs/{id} 가 {Code, As, Reason} 을 내고 있다 · 제어판 폴링 범위가 캐시가 차는 범위를 가른다. 셋을 unit-of-work-plan.md 의 질문으로 냈다.
Context: INCEPTION / Requirements 화면 대조 닫힘. Units Generation Part 1 로 넘어간다
Units Generation — Part 1 계획
Timestamp: 2026-09-04T14:02:47Z
AI Response: aidlc-docs/inception/plans/unit-of-work-plan.md 를 냈다. 유닛을 다시 가르는 계획이 아니다 — 다섯은 실행 계획 4절에서 승인됐고, 이 계획이 하는 것은 유닛 산출물 셋을 내는 것과 유닛 둘이 읽는 접점 셋을 닫는 것이다. 질문 셋은 답 A 가 덜 정한 자리다 — ① current 가 단수면 폭 1 을 넘는 Run 에서 S1 카드가 남의 노드 단계를 그린다 (권장 A: current:[{node, step, attempt, started_at}]. CP3 계약이 「단계 둘 이상」을 요구하므로 실제로 걸린다) ② match.Reject 에 json 태그가 없어 기존 GET /v1/runs/{id} 가 {Code, As, Reason} 을 낸다 (권장 A: 태그를 단다. runView.Reject 의 키를 읽는 코드가 저장소에 없고 mediator-api.md 가 reject 를 안 적었으며 Go 언마샬이 대소문자를 무시해 옛 DB 행이 그대로 읽힌다. constraints.md §7 목록에 없는 자리) ③ 제어판 폴링 범위 (권장 A: 5초마다 GET /v1/nodes 한 번으로 이 기계 노드 전부의 캐시를 채운다. B 면 안 열어 본 노드의 「마지막 관측」이 영영 빈다). v1 이 요구하는 여섯 범주 중 나머지 셋에 근거를 적고 닫았다 — Story Grouping(스토리가 없다. 묶는 단위가 조각 게이트다) · Team Alignment(배치 폭 1) · Code Organization(brownfield). 실행 단계는 3절에 체크박스로 있고 산출물 셋의 경로는 v1 의 것을 그대로 쓴다. unit-of-work-story-map.md 에 들어가는 것은 스토리가 아니라 장면 게이트 · 화면 · FR 번호이고 그 사실을 파일 머리에 적는다.
Context: INCEPTION / Units Generation Part 1. 질문 셋의 답과 승인 대기
Units Generation Part 1 — 승인 요청
Timestamp: 2026-09-04T14:02:47Z
AI Prompt: "Unit of work plan complete. Review the plan in aidlc-docs/inception/plans/unit-of-work-plan.md. Ready to proceed to generation?" — 질문 셋의 [Answer] 를 먼저 채워야 한다
Status: Pending
Context: INCEPTION / Units Generation Part 1
Units Generation Part 1 — 승인
Timestamp: 2026-09-04T14:24:11Z
User Input: "했어." (unit-of-work-plan.md 1절의 [Answer] 태그 — Q1: A · Q2: A · Q3: A)
AI Response: 답 셋에 모호도 모순도 없어 후속 질문을 안 냈다 (Step 7 분석 — 셋 다 단일 선택이고 권장과 일치하며 조합·유보 표현이 없다). Part 2 를 돈다. 값 — ① current 를 배열로 낸다 [{node, step, attempt, started_at}] ② match.Reject 에 json 태그를 달아 두 라우트를 소문자로 합친다 ③ serve 가 5초마다 GET /v1/nodes 를 한 번 받아 이 기계 노드 전부의 캐시를 채운다.
Status: Approved
Context: INCEPTION / Units Generation Part 1 완료. Part 2 착수
Units Generation Part 2 — 유닛 산출물 셋
Timestamp: 2026-09-04T14:31:05Z
AI Response: 계획의 단계를 순서대로 돌고 체크박스를 그 자리에서 [x] 로 바꿨다. 3.1 — 답 셋을 requirements.md 에 값으로 반영했다. §8.4 의 「덜 정한 자리 셋」이 결정표가 됐고 FR-2 는 current[], FR-25 는 폴링 방식을 실었으며 §4.1 에 계약 두 줄(current[] · reject)이 늘었다. ①-b 의 파급 하나를 명시했다 — internal/match/match.go 를 만지는 유닛이 U-바닥으로 생긴다. unit-boundaries.md §2 의 「아무 유닛도 안 만진다」가 그 한 줄만큼 바뀌고, Match 의 시그니처와 로직은 그대로라 NFR-I4 는 지켜진다. 3.2 · 3.5 — unit-of-work.md. 유닛마다 하는 일 · 출처 FR · 딛는 것 · 만지는 파일(NEW 표시) · 완료 조건 · 게이트 명령. 화면이 있는 유닛 둘(U-관측API 의 S0·S0b·S1·S1b·S2, U-제어판의 S3·S5)은 버튼을 전부 나열했다. U-바닥 완료 조건에 경계 검사 테스트를 박았고 「패키지가 아직 없을 때는 통과가 아니라 건너뛴다」를 검사기가 구분하게 적었다. 3.3 — unit-of-work-dependency.md. 의존 행렬 · 파일 겹침 행렬 · 겹치는 자리 넷의 처리 · 접점 13개(내는 유닛 · 쓰는 유닛 · 모양) · 안 잇는 자리 여덟. 3.4 — unit-of-work-story-map.md. 스토리가 없으므로 장면 게이트를 축으로 썼고 그 사실을 파일 머리에 적었다. 3.6 검증 — FR-1 ~ FR-25 전부 배정(빠진 번호 0 · 맵에만 있는 번호 0) · §4.1 계약 13 줄과 접점 표 13 행이 일치하고 주인 없는 접점 0 · o 가 둘 이상인 파일 셋(store.go · queue.go · api.go) 전부 처리 문장 있음 · 표 열 일치(한 줄은 이스케이프 \| 의 오탐) · mermaid 없음 · emphasis-check.py 초록.
Context: INCEPTION / Units Generation Part 2 완료. 승인 대기
Units Generation — 승인 요청
Timestamp: 2026-09-04T14:31:05Z
AI Prompt: "Units generation complete. Review the artifacts at aidlc-docs/inception/application-design/. Approve & Continue to CONSTRUCTION PHASE?"
Status: Pending
Context: INCEPTION 마지막 단계. 승인하면 CONSTRUCTION — U-바닥의 Functional Design
Units Generation — 승인
Timestamp: 2026-09-04T14:22:33Z
AI Prompt: "Units generation complete. Review the artifacts at aidlc-docs/inception/application-design/. Approve & Continue to CONSTRUCTION PHASE?"
User Response: "approve&continue"
Status: Approved
Context: INCEPTION 완료. CONSTRUCTION 착수 — U-바닥의 Functional Design
U-바닥 Functional Design — 계약 질문 셋
Timestamp: 2026-09-04T14:30:12Z
AI Response: CONSTRUCTION 에 들어가 U-바닥의 Functional Design 을 시작했다. 계획 파일을 안 만들었다 — 규칙 Step 4 는 construction/plans/{unit}-functional-design-plan.md 를 요구하지만 사용자 지시가 계획 파일을 Units Generation 과 Code Generation 으로 한정한다. 질문은 construction/u-foundation/functional-design/functional-design-questions.md 에 뒀고 셋을 넘기지 않았으며 셋 다 유닛 둘 이상이 읽는데 어느 문서도 값을 안 준 자리다 — ① UpsertAdvert 가 정책을 어디로 받는가(권장 A: contract.Advert 에 Policy 필드. postNodes 가 본문 전체를 Advert 하나로 디코드하므로 U-drain 이 파싱을 안 고친다) ② ListNodes·ListRuns 의 반환과 observed_at 의 시계(권장 A: store 가 페이지를 내고 observed_at 은 DB 의 now(). 같은 응답의 expires_at·not_after 와 시계가 갈리면 카운트다운과 30초 흐림이 어긋난다) ③ since 가 자르는 열(권장 A: created_at. mediator-api.md 가 이 자리를 명시적으로 안 정했고, ended_at 이면 RUNNING·QUEUED 가 목록에서 통째로 빠진다). 유닛 슬러그 다섯을 정해 aidlc-state.md 에 적었다 — u-foundation · u-observe-api · u-queue · u-drain · u-panel.
Context: CONSTRUCTION / U-바닥 Functional Design Step 3. 답을 받기 전에는 산출물 셋을 쓰지 않는다
U-바닥 Functional Design — 묻지 않고 닫은 것 아홉
Timestamp: 2026-09-04T14:30:12Z
AI Response: 질문 후보였다가 문서와 코드 대조로 닫힌 것을 질문 파일 2절에 적었다. 새로 찾은 사실 셋을 기록한다. ① store.LeaseRow 에 Nonce 가 있다 (internal/store/claim.go:25) — ADR-010 의 허가 아티팩트다. GET /v1/nodes 의 lease 에 그 타입을 재사용하면 토큰을 가진 누구나 현황판에서 남의 nonce 를 읽는다. ADR-065 응답 예시가 run_id 와 not_after 둘뿐이라 작은 LeaseView 를 따로 만든다. ② 경계 검사기가 t.Skip 을 쓰면 CI 가 빨개진다 — .ci-allowed-skips 의 규칙이 「CI 로 조건을 갖출 수 없는 스킵만 목록에 오른다」이고 「internal/panel 이 아직 없다」는 U-제어판이 갖출 수 있는 조건이라 목록에 못 오른다. 목록에 없는 t.Skip 은 「스킵 감시 (전 패키지)」가 위반으로 센다. 그래서 통과시키고 t.Logf 로 적되, go test ./... 가 통과 패키지의 t.Logf 도 fmt.Println 도 안 보인다는 것을 실측했으므로 CP0 게이트에 go test -v ./internal/boundary 한 줄을 더한다. ③ 테스트 파일만 가진 패키지가 게이트를 안 깬다는 것을 실측했다 — go build·go vet·go test 전부 exit 0 이고, 문장이 0 이라 커버리지 프로파일에 안 나오므로 패키지별 80% 하한 검사에 안 걸린다. 나머지 여섯 — nodes 정책 열을 drain text 로(정책은 창발 어휘가 아니다) · panel_token 을 광고에 안 실음(SECURITY-12) · StepView.Chosen 에 omitempty 안 붙임(Needs 주석의 같은 근거) · 상수 이름을 store.StateQueued 로(기존 블록의 어휘) 자리는 queue.go(store.go 주석의 「INVARIANTS §1.1 그대로다」를 거짓으로 안 만든다) · match.Reject 태그를 달아도 옛 runs.reject 행이 그대로 읽힘(Go 언마샬이 대소문자 무시) · current[] 의 step 은 steps.name(FR-21 이 「단계 이름」을 요구한다).
Context: CONSTRUCTION / U-바닥 Functional Design. 코드 기준선 1c290a6
U-바닥 Functional Design — 승인 요청
Timestamp: 2026-09-04T14:30:12Z
AI Prompt: 질문 셋의 [Answer] 를 채워 달라. 답을 받으면 domain-entities.md · business-logic-model.md · business-rules.md 를 낸다
Status: Pending
Context: CONSTRUCTION / U-바닥 Functional Design Step 5
U-바닥 Functional Design — 질문 셋의 답과 산출물 셋
Timestamp: 2026-09-04T14:46:23Z
User Input: "했음." (functional-design-questions.md 의 [Answer] 태그 — Q1: A · Q2: A · Q3: A)
AI Response: 답 셋에 모호도 모순도 없어 후속 질문을 안 냈다 (Step 5 분석 — 셋 다 단일 선택이고 권장과 일치하며 조합·유보 표현이 없다). 값 — ① contract.Advert 에 Policy 필드를 더하고 UpsertAdvert 가 (contract.Policy, error) 를 낸다 ② store 가 NodePage·RunPage 를 통째로 내고 observed_at 은 DB 의 now() 이며 limit 도 store 가 조인다 ③ since 는 created_at > since 다. construction/u-foundation/functional-design/ 에 셋을 냈다 — domain-entities.md(타입과 열 · 열/필드/키 대응표 · 안 만드는 것) · business-logic-model.md(질의 여섯과 경계 검사기 절차) · business-rules.md(규칙 R1 ~ R21 · 완료 조건 · 테스트 목록 · CP0). frontend-components.md 를 안 냈다 — U-바닥에 화면이 없다. SQL 을 실제 PostgreSQL 17 에서 돌려 확인했다 — 스키마를 올리고 nodes.drain 을 더한 뒤 픽스처로 여섯 질의를 다 실행했다. 확인된 것 — CTE 가 upsert 의 이전 drain 값을 정확히 돌려준다('' -> graceful -> at-boundary 순서) · ListNodes 가 만료 노드를 빼고 임대를 1:1 로 붙인다 · ListRuns 의 상관 부질의가 폭 2 인 Run 에서 CLAIMED 둘을 seq 순서로 낸다 · DrainingNodes 가 만료된 draining 노드를 뺀다 · ListQueued 가 빈 contract_versions 에서 제출 전문을 고른다 · PostgreSQL 의 jsonb 시각(+00:00)을 Go 의 json.Unmarshal 이 time.Time 으로 읽는다. emphasis-check.py 와 glyphscan 초록. 확장 준수에 blocking 없음.
Context: CONSTRUCTION / U-바닥 Functional Design Step 6 완료. 승인 대기
U-바닥 Functional Design — 앞 단계 문장 셋을 정정했다
Timestamp: 2026-09-04T14:46:23Z
AI Response: 코드와 CI 설정 대조로 앞 단계의 문장 셋이 틀린 것을 찾아 aidlc-state.md 에 「설계 중 나온 정정 셋」으로 적었다. ① NFR-I2 의 「저장되는 어휘 넷」이 다섯이다 — internal/store/reap.go:279 의 SettleIfDone 이 UPDATE runs SET state=$2 WHERE state='RUNNING' 로 VERIFYING 을 따로 커밋하고 그 뒤에 유효 계약 조회·함대 조회·대조가 이어지므로 5초 폴링이 VERIFYING 행을 실제로 본다. runs.state 에 쓰는 자리 여섯을 전부 훑어 확인했고 RESOLVING·ALLOCATING 은 상수만 있고 쓰는 자리가 없다. 파급은 ValidState 가 다섯을 받아야 한다는 것과 S1 의 필터 칩 다섯이 VERIFYING 행을 「전체」에만 보인다는 것. ② unit-boundaries.md §2 의 파일 행렬에서 U-바닥이 api.go 를 한 줄 만진다 — UpsertAdvert 의 반환이 둘이 되므로 유일한 호출자인 internal/api/api.go:208 이 바뀐다(호출자가 하나인 것을 grep 으로 확인했다). 등록 줄도 핸들러 로직도 아니라 행렬의 뜻은 그대로다. ③ 경계 검사기의 「건너뜀」을 t.Skip 으로 구현하면 안 된다 — .ci-allowed-skips 의 규칙이 「CI 로 그 조건을 갖출 수 없는 스킵만 목록에 오른다」이고 「패키지가 아직 없다」는 뒤 유닛이 갖출 수 있는 조건이라 목록에 못 오르며, 목록에 없는 t.Skip 은 ci.yml 의 「스킵 감시 (전 패키지)」가 위반으로 센다. 대신 t.Logf 로 적고 CP0 에 go test -v ./internal/boundary 한 줄을 더한다 — go test ./... 가 통과 패키지의 t.Logf 를 안 보인다는 것을 실측했으므로 그 줄이 없으면 「검사했다」와 「건너뛰었다」가 똑같이 ok 로 보인다.
Context: CONSTRUCTION / U-바닥 Functional Design. 코드 기준선 1c290a6
U-바닥 Functional Design — 승인 요청
Timestamp: 2026-09-04T14:46:23Z
AI Prompt: "Functional design complete for U-바닥. Review the artifacts at aidlc-docs/construction/u-foundation/functional-design/. Request Changes or Continue to Code Generation?"
Status: Pending
Context: CONSTRUCTION / U-바닥. 승인하면 Code Generation — 계획 파일을 만들고 CP0 게이트 명령을 박는다
U-바닥 Functional Design — 승인
Timestamp: 2026-09-04T14:52:49Z
User Input: "approve&continue"
AI Response: Functional Design 승인으로 읽고 CONSTRUCTION / U-바닥 Code Generation Part 1 (Planning) 으로 넘어갔다. 산출물 셋에 변경 요청이 없었다.
Context: CONSTRUCTION / U-바닥 Functional Design Step 4 승인
U-바닥 Code Generation Part 1 — 계획 파일
Timestamp: 2026-09-04T14:52:49Z
AI Response: aidlc-docs/construction/plans/u-foundation-code-generation-plan.md 를 냈다 (350줄). 단계 열다섯이고 체크박스가 전부 [ ] 다. 구성 — 1절 유닛 맥락(FR 다섯 · 의존 · 뒤 유닛에 내주는 접점 열 · 소유하는 DB 실체 셋) · 2절 파일 행렬(신설 여덟 · 수정 여섯 · 안 건드리는 것) · 3절 단계 열다섯 · 4절 실행 규칙 · 5절 보안 확장 대조 · 6절 안 만드는 것. Step 13 이 CP0 를 적는 것이 아니라 실제로 돌리는 단계다 — go build · go test && go vet · glyphscan · golangci-lint · 커버리지 게이트 · go test -v ./internal/boundary · curl GET /v1/capabilities 일곱 줄이고 빨간 것이 남으면 U-관측API 에 안 들어간다. Step 15 가 「해당 없음」을 이름으로 남긴다 — 규칙이 요구하는 Project Structure Setup · API Layer · Frontend · Deployment Artifacts 넷이 이 유닛에 없는 이유를 적어, 빠뜨린 것과 없는 것을 구별하게 한다. 코드를 읽고 계획에 박은 사실 셋 — ① UpsertAdvert 의 호출자가 internal/api/api.go:208 하나다 ② 기존 reap_deterministic_test.go 의 scratchDB 를 재사용한다(스크래치 헬퍼를 두 벌로 안 만든다) ③ 기존 store 테스트가 ENODE_TEST_DATABASE_URL 부재에 t.Fatal 을 쓰므로 새 테스트도 같은 규칙을 쓴다. emphasis-check.py 초록.
Context: CONSTRUCTION / U-바닥 Code Generation Part 1 Step 4
U-바닥 Code Generation Part 1 — 승인 요청
Timestamp: 2026-09-04T14:52:49Z
AI Prompt: "Code generation plan complete for U-바닥. Review the plan at aidlc-docs/construction/plans/u-foundation-code-generation-plan.md. Request Changes or Continue to Code Generation?"
Status: Pending
Context: CONSTRUCTION / U-바닥 Code Generation Part 1 Step 6. 승인하면 Part 2 — Step 1 부터 순서대로 실행하고 단계마다 체크박스를 그 자리에서 [x] 로 바꾼다
U-바닥 Code Generation Part 1 — 승인
Timestamp: 2026-09-04T15:07:11Z
User Input: "accept&continue"
AI Response: 계획 승인으로 읽고 Part 2 (Generation) 로 넘어갔다. u-foundation-code-generation-plan.md 의 Step 1 부터 순서대로 실행하고, 단계를 끝내는 그 자리에서 체크박스를 [x] 로 바꾼다. 계획에 없는 파일과 함수는 만들지 않는다.
Context: CONSTRUCTION / U-바닥 Code Generation Part 1 Step 6 승인
U-바닥 Code Generation Part 2 — 실행
Timestamp: 2026-09-04T15:14:02Z
AI Response: 계획의 Step 1 ~ 15 를 순서대로 돌렸고 체크박스가 전부 [x] 다. 신설 여덟 — internal/store/list.go · queue.go · list_test.go · queue_test.go · advert_policy_test.go · internal/contract/advert_policy_test.go · internal/match/reject_json_test.go · internal/boundary/imports_test.go. 수정 여섯 — schema.sql(nodes.drain 한 줄) · store.go(UpsertAdvert 가 CTE 로 이전 정책을 돌려주고 clampDrain 이 값을 조인다) · observe.go(StepView.Chosen) · contract/advert.go(Policy 타입 · Advert.Policy) · match/match.go(Reject 세 필드 태그) · api/api.go(호출 한 줄). 계획에 없는 파일과 함수를 안 만들었다 — WakeQueued 도 라우트도 없다.
설계와 갈린 자리 하나 — omitzero. domain-entities.md §2.2 가 Advert.Policy 의 태그를 omitempty 로 적었는데 Go 의 omitempty 는 구조체 필드에 안 듣는다. 스크래치 프로그램으로 실측했다 — omitempty 는 {"n":"x","policy":{}}, omitzero 는 {"n":"x"}. 같은 문서와 계획 Step 3 이 요구한 「제로값이면 policy 키가 빠진다」를 내는 것은 omitzero(Go 1.24+, 저장소는 1.26) 하나뿐이라 그것을 썼다. 빈 객체가 나가면 읽는 쪽이 「필드를 아는 노드가 안 걸었다」와 「옛 노드라 필드가 없다」를 가르려 드는데 R4 는 그 둘을 같게 읽으라고 정했다. 주석과 테스트(TestAdvert_AZeroPolicyLeavesNoKey)가 그것을 못 박았고 aidlc-state.md 의 정정 표에 넷째 줄로 올렸다.
CP0 를 실제로 돌렸다 — 전부 초록. go build ./... exit 0 · go test ./... 16개 패키지 전부 ok 이고 -json 으로 센 스킵이 0건 · go vet ./... exit 0 · glyphscan 83 파일 무결 · golangci-lint run ./... 20건으로 기준선 그대로이고 새 파일과 고친 파일에는 0건(로컬 v2.13.2 가 ci.yml 이 고정한 버전과 같다) · 커버리지 게이트가 15개 패키지 전부 하한 80% 통과, 미달 0개(internal/store 가 81.0% -> 82.2%, 전체 87.4%. internal/boundary 는 문장 0 이라 프로파일에 안 나온다 — 설계의 예측 그대로) · go test -v ./internal/boundary 가 1 checked, 3 skipped, 4 total 을 낸다 · curl GET /v1/capabilities 200.
라우트 15개 회귀 0. 스크래치 DB 에 Mediator 를 실제로 띄워 열다섯을 다 쳤고 mux 의 404 page not found 는 하나도 없었다(핸들러의 404 와 본문으로 갈랐다). 아직 없어야 하는 셋도 확인했다 — GET /v1/runs · GET /v1/nodes 는 같은 경로에 POST 만 있어 405, GET /ui/ 는 mux 의 404 다. 정책 왕복도 살아 있는 Mediator 로 확인했다 — graceful 을 실은 광고가 200 이고 nodes.drain 에 앉았으며, 오타 gracefull 이 200 을 받고 graceful 로 조여 저장됐고 경고 로그(unknown drain policy clamped to graceful node=cp0-node want=gracefull stored=graceful)가 남았고, policy 를 안 실은 광고가 열을 "" 로 되돌렸다. 거절하지 않는 것이 R3 의 요점이다 — 400 을 냈으면 그 노드의 임대 갱신이 끊기고 회수가 도는 Run 을 죽였다.
산출물은 construction/u-foundation/code/code-generation-summary.md 다. emphasis-check.py 가 새 파일 열셋에 초록. 테스트 실행이 건드린 cmd/enodectl/probe.lock(PID 잠금)은 이 유닛의 변경이 아니라 되돌렸다. 코드는 커밋하지 않았다 — 진행자만 커밋한다.
Context: CONSTRUCTION / U-바닥 Code Generation Part 2. 코드 기준선 1c290a6
U-바닥 Code Generation Part 2 — 승인 요청
Timestamp: 2026-09-04T15:14:02Z
AI Prompt: "Code generation complete for U-바닥. CP0 is green. Review the code and the summary at aidlc-docs/construction/u-foundation/code/code-generation-summary.md. Request Changes or Continue to Next Stage?"
Status: Pending
Context: CONSTRUCTION / U-바닥 Code Generation Part 2. 승인하면 U-관측API (CP1) 로 간다 — 그 유닛은 Functional Design 을 건너뛰므로 Code Generation Part 1 (계획 파일) 부터다
U-바닥 Code Generation Part 2 — 승인
Timestamp: 2026-09-04T15:20:19Z
User Input: "continue"
AI Response: Code Generation 승인으로 읽고 U-바닥을 닫았다. 코드에 변경 요청이 없었다. 다음은 U-관측API (CP1) 이고, 그 유닛은 Functional Design 을 건너뛰므로 Code Generation Part 1 (계획 파일) 부터다.
Context: CONSTRUCTION / U-바닥 Code Generation Step 15 승인. U-바닥 종료
U-관측API Code Generation Part 1 — 계획 파일
Timestamp: 2026-09-04T15:34:08Z
AI Response: aidlc-docs/construction/plans/u-observe-api-code-generation-plan.md 를 냈다 (약 560줄). 단계 열여덟이고 체크박스가 전부 [ ] 다. 이 유닛은 Functional Design 을 건너뛰므로 계획이 계획인 동시에 설계다 — 3절이 응답 계약(두 라우트의 JSON · 질의 인자 검증 · 오류 셋), 4절이 화면(뷰 셋 · CSP 가 정하는 파일 구성 · 버튼 전부 · 파생 규칙 · 어휘 정정 셋 · 갱신)을 진다. 건너뛰기의 근거를 계획 머리에 적었다 — API 쪽은 응답 모양이 decisions.md 2절과 ADR-065 와 requirements.md §4.1 · §8.4 로 이미 값이고 새 열이 0 이며, 화면 쪽은 design/README.md §7 이 요구한 「pen 을 읽는다」가 Requirements Analysis 에서 이미 끝났다 (requirements.md §8.3 이 버튼 전부 · §8.5 가 C3 · C4 · C5 문구 · §8.6 이 어휘 정정 둘). frontend-components.md 를 새로 내면 같은 것의 두 번째 정본이 된다.
신설 열하나 · 수정 하나. internal/api/list.go · list_test.go · internal/api/ui/ui.go · ui_test.go · assets/ 일곱(index.html · app.css · api.js · fleet.js · runs.js · graph.js · app.js). 수정은 internal/api/api.go 의 임포트 하나와 등록 줄 셋뿐이다. 새 Go 의존 0 · 프런트엔드 빌드 도구 0 — embed 와 net/http 만 쓰고 npm 도 번들러도 프레임워크도 외부 폰트도 없다.
Step 15 가 CP1 을 실제로 돌리는 단계다 — CP0 일곱 줄을 다시 돌려 회귀를 잡고, 거기에 curl GET /v1/nodes · GET /v1/runs?limit=5 · GET /ui/ 와 라우트 18개 회귀를 더한다. Step 16 이 화면 검증을 따로 적는다 (scene-gates.md §2.1 — API 로 통과되는 어휘는 화면 없이도 초록이 된다).
코드와 문서를 읽고 계획에 박은 사실 넷 — ① http.StripPrefix 가 벗기는 것은 /ui 이지 /ui/ 가 아니다. /ui/ 를 벗기면 /ui/app.js 가 앞 슬래시 없는 app.js 가 되어 FileServerFS 가 못 찾는다 ② 경계 검사기의 숫자가 바뀌어야 한다. 지금 1 checked, 3 skipped 인데 internal/api/ui 가 생기면 2 checked, 2 skipped 다 — 안 바뀌면 검사기가 새 패키지를 못 본 것이므로 게이트 항목으로 박았다 ③ internal/api 의 ENODE_TEST_DATABASE_URL 부재 처리는 t.Skip 이다 (api_test.go:33). store 패키지의 t.Fatal 과 다른데, 새 파일이 기존 헬퍼 newServer 를 재사용하므로 이 패키지의 규칙을 그대로 따르고 새 규칙을 만들지 않는다 ④ internal/api 의 커버리지 여유가 얇다 — 기준선이 327/402 = 81.3% 이고 하한이 80% 라, 오류 분기(400 셋 · 503)를 안 덮으면 게이트가 빨개진다. 503 은 newServerFast 가 돌려주는 store 를 닫아서 덮는다고 Step 4 에 적었다.
계획이 스스로 정한 값 셋 (문서가 안 정한 자리라 근거를 함께 적었다) — ① 카드 배지 우선순위 draining > 만료 임박 > 임대 중 > 임대 없음. draining 이 먼저인 것은 그림이 그렇고(ws-c 가 임대를 쥔 채 draining 배지다), 만료 임박이 임대 중보다 앞인 것은 그것이 더 급한 사실이기 때문이다 ② 아이콘은 인라인 SVG 다. 그림의 화살표 · 경고 표시 · 눈 아이콘 · 갈매기표를 글자로 넣으면 CONVENTIONS.md 1.2 위반이고, glyphscan 은 Go 만 보므로 그 자리를 Step 14 의 테스트가 센다 ③ 화면 글자는 한국어다. CONVENTIONS.md 2.1 이 영어로 못 박은 여섯 자리 어디도 아니고, 2.3절의 기준인 「밖으로 나가느냐」에서 화면 글자는 하네스로 안 실려 나간다. 그림의 글자가 이미 한국어라 옮기면 원본과 갈린다.
emphasis-check.py 초록. 확장 준수에 blocking 없음 — 이 유닛이 새 표면 셋을 여는 첫 유닛이라 SECURITY-04 · 05 · 08 · 09 · 12 가 전부 실제로 걸리고, 처리를 계획 7절에 규칙마다 적었다.
Context: CONSTRUCTION / U-관측API Code Generation Part 1 Step 4
U-관측API Code Generation Part 1 — 승인 요청
Timestamp: 2026-09-04T15:34:08Z
AI Prompt: "Code generation plan complete for U-관측API. Review the plan at aidlc-docs/construction/plans/u-observe-api-code-generation-plan.md. Request Changes or Continue to Code Generation?"
Status: Pending
Context: CONSTRUCTION / U-관측API Code Generation Part 1 Step 6. 승인하면 Part 2 — Step 1 부터 순서대로 실행하고 단계마다 체크박스를 그 자리에서 [x] 로 바꾼다
U-관측API Code Generation Part 1 — 승인
Timestamp: 2026-09-05T00:00:00Z
User Input: "continue"
AI Response: 계획 승인으로 읽고 Part 2 로 간다. 계획에 변경 요청이 없었다. construction/plans/u-observe-api-code-generation-plan.md 의 Step 1 부터 순서대로 실행하고 단계마다 그 자리에서 체크박스를 [x] 로 바꾼다.
Context: CONSTRUCTION / U-관측API Code Generation Part 1 Step 6 승인. Part 2 착수
U-관측API Code Generation Part 2 — 실행
Timestamp: 2026-09-05T01:10:00Z
AI Response: 계획의 Step 1 ~ 18 을 순서대로 돌렸고 체크박스 122개가 전부 [x] 다. 신설 열하나 — internal/api/list.go · list_test.go · internal/api/ui/ui.go · ui_test.go · assets/ 일곱(index.html · app.css · api.js · fleet.js · runs.js · graph.js · app.js). 수정 하나 — internal/api/api.go 의 임포트 하나와 등록 줄 셋뿐이다. store 에 함수를 하나도 안 더했고 스키마를 안 건드렸다 — 새 열 0 개다. 새 Go 의존 0 · 프런트엔드 빌드 도구 0.
계획에서 갈린 자리가 없다. 응답 계약(3절)과 화면 규칙(4절)을 그대로 냈다. 대신 돌려 보는 동안 결함 다섯이 잡혔다 — 셋은 테스트가, 둘은 화면이. ① runs.js 의 주석이 「그 이름을 쓰지 않는다」고 적으면서 그 이름을 담고 있어 Step 14 의 어휘 검사가 자기 파일을 지적했다 ② 인라인 script 검사가 여는 태그만 봐서 src 로 싣는 빈 태그를 오탐했다 — 본문이 비었는지를 보도록 고쳤다 ③ /index.html 은 파일 서버가 ./ 로 리다이렉트해 Content-Type 이 비어 오므로 검사 대상을 뿌리로 바꿨다 ④ 안 돈 단계의 부제가 ws-a · 회차 0 · 로 끝나 구분자가 매달렸다 — 있는 것만 잇도록 where() 를 함수로 뺐다 ⑤ chosen false 칩이 갈림에 없던 단계까지 붙어 「고려됐는데 떨어졌다」로 읽혔다 — chosen 이 참이거나 SKIPPED 일 때만 붙인다. 뒤의 둘은 API 검사로 안 잡힌다 — 응답은 두 경우 다 옳았고 틀린 것은 그것을 읽는 화면이었다. scene-gates.md §2.1 이 화면 검증을 따로 세운 이유가 이 자리에서 실제로 값을 냈다.
CP1 을 실제로 돌렸다 — 전부 초록. go build ./... · go vet ./... exit 0 · go test ./... 17개 패키지 전부 ok 이고 -json 으로 센 스킵이 0건 · glyphscan 85 파일 무결 · golangci-lint run ./...(v2.13.2) 20건으로 기준선 그대로이고 새 파일과 고친 파일에는 0건 · 커버리지 게이트가 16개 패키지 전부 하한 80% 통과, 미달 0개(internal/api 81.3 -> 82.8%, 신설 internal/api/ui 88.9%, 전체 87.5%) · go test -v ./internal/boundary 가 2 checked, 2 skipped, 4 total 을 낸다 — internal/api/ui 가 생기면서 규칙 하나가 「없음」에서 「검사함」으로 넘어갔고, 계획이 이것을 게이트 항목으로 박아 둔 이유가 그것이다.
라우트 18개 회귀 0. 스크래치 DB 에 Mediator 를 실제로 띄워 열여덟을 다 쳤고 mux 의 404 page not found 는 하나도 없었다. 없어야 하는 자리도 확인했다 — GET /v1/nope 만 mux 404 이고 DELETE /v1/runs 는 405 다. GET /v1/nodes 가 lease{run_id, not_after} 와 draining 을 실었고 본문에 nonce 도 principal 도 없다(글자로 셌다). GET /v1/runs?limit=5 가 시간 역순 · current[] · reject{code, as, reason} 를 냈다. GET /ui/ 는 토큰 없이 200 이고 헤더 넷이 붙으며 404 에도 붙는다.
화면을 브라우저에서 실제로 돌렸다. Chrome 확장이 안 붙어서 Playwright 헤드리스 크로미움으로 돌렸다. S0(토큰 · 눈 아이콘 토글) · S0b(틀린 토큰의 401 인라인 오류와 붉은 테두리) · S1(카드 5개 · 카운트다운 00:40 남음 · C4 「draining · 진행 중」과 C5 「draining · 대기 중(owner)」가 갈린다 · at-boundary 가 「경계에서 · 최대 01:01:01」 · 만료 30초 이내 카드가 흐려지고 「광고가 12초 뒤 만료된다」 · observed_at 과 expires_at 이 함께 · 마지막 갱신 시각) · S1b(Mediator 를 멈추면 셋째 실패에서 카운트다운이 멎고 배너가 뜨며 머리띠가 「갱신 실패 · 39초 낡았다」로 바뀐다. 카운트다운의 시제가 「남았다」로 바뀐다. Mediator 가 돌아오면 스스로 풀린다) · S2(위상 배치 · 간선 · 범례 여섯 · 단계 상세 일곱 줄 · produced 가 ledger 조인으로 채워진다) 를 다 확인했다. §4.4 의 data-testid 열셋이 전부 문서에 있고 전부 눌러 봤다. 브라우저 콘솔에 CSP 위반이 0건 — 인라인을 하나라도 남겼으면 브라우저가 조용히 멈췄을 자리다.
QUEUED 칩은 빈 목록을 낸다. 라우트가 그 값을 받고 store 가 그것으로 거르지만 QUEUED 상태의 행을 만드는 것이 U-대기열(CP2)이다. 화면이 「이 조건에 걸리는 Run 이 없다」를 적는다.
산출물은 construction/u-observe-api/code/code-generation-summary.md 다. emphasis-check.py 초록. enode-design/ 도 design/ 도 README.md 도 안 고쳤다 — 정본 개정은 이 저장소의 일이 아니고(canon.md 3절) ADR-065 에 draining 을 더하는 것과 mediator-api.md 에 라우트 셋을 더하는 것이 아직 착지 전이라 요약 7절에 그것을 적었다. 테스트 실행이 건드린 cmd/enodectl/probe.lock 은 되돌렸다. 코드는 커밋하지 않았다 — 진행자만 커밋한다.
Context: CONSTRUCTION / U-관측API Code Generation Part 2. 코드 기준선 1c290a6
U-관측API Code Generation Part 2 — 승인 요청
Timestamp: 2026-09-05T01:10:00Z
AI Prompt: "Code generation complete for U-관측API. CP1 is green and the screens were verified in a browser. Review the code and the summary at aidlc-docs/construction/u-observe-api/code/code-generation-summary.md. Request Changes or Continue to Next Stage?"
Status: Pending
Context: CONSTRUCTION / U-관측API Code Generation Part 2. 승인하면 U-대기열 (CP2) 로 간다 — 그 유닛은 Functional Design 부터다
U-관측API Code Generation Part 2 — 승인
Timestamp: 2026-09-05T00:37:38Z
User Input: "continue"
AI Response: Code Generation 승인으로 읽고 U-관측API 를 닫았다. 코드에도 요약에도 변경 요청이 없었다. CP1 이 초록인 상태로 유닛이 끝났다. 다음은 U-대기열 (CP2) 이고, 그 유닛은 실행 계획 3절이 Functional Design 을 걸어 두었으므로 Functional Design 부터다.
앞 두 항목의 시각이 이 항목보다 뒤다. U-관측API Code Generation Part 1 — 승인(2026-09-05T00:00:00Z)과 Part 2 — 실행(2026-09-05T01:10:00Z)이 추정값이고 실제 시계보다 앞서 갔다. 이 항목의 00:37:38Z 는 date -u 로 읽은 실제 시각이다. 두 추정값을 지어낸 값으로 다시 덮지 않는다 — 참값을 모른다. 참값이 있는 구간만 적으면 Part 1 계획이 2026-09-04T15:34:08Z 이후이고 이 항목 이전이다.
Context: CONSTRUCTION / U-관측API Code Generation Part 2 Step 18 승인. U-관측API 종료 · CP1 종료
U-대기열 Functional Design — 계약 질문 넷
Timestamp: 2026-09-05T00:37:38Z
AI Response: U-대기열(CP2)의 Functional Design 을 시작했다. 계획 파일을 안 만들었다 — U-바닥에서와 같은 처리이고 근거도 같다(사용자 지시가 계획 파일을 Units Generation 과 Code Generation 으로 한정한다). 질문은 construction/u-queue/functional-design/functional-design-questions.md 에 뒀고 넷이다. 넷을 고른 기준은 이 유닛이 기존의 뜨거운 경로 둘을 고친다는 것 — 모든 제출이 지나는 submit 과 모든 단계 보고가 지나는 postResult 이고, 넷 다 그 둘의 모양을 가르는데 어느 문서도 값을 안 준 자리다.
① 승격이 어느 층에 사는가 (권장 A: store.WakeQueued 가 통째로 진다. requirements.md §4.1 이 WakeQueued(ctx) error 로 시그니처를 고정했고, 깨우기 지점 하나인 Reap 이 store 안이라 api 를 모른다 — 조합을 api 에 두면 store 가 주입된 필드로 나갔다 오는 왕복이 되고 필드를 안 배선한 테스트에서 조용히 아무것도 안 한다. label 을 internal/api/api.go:849 에서 store 로 옮겨 제출과 승격이 같은 조각을 쓴다) ② 깨우기가 동기인가 (권장 A: 동기. CP2 게이트의 「a 가 끝난 뒤 runctl status <b> -> RUNNING」이 비동기에서는 경쟁이고, postResult 는 이미 SettleIfDone 을 동기로 부르며, 고루틴은 r.Context() 를 못 쓴다) ③ 어느 409 가 QUEUED 가 되는가 (권장 A: 둘 다. 제출 경로의 409 가 두 자리다 — api.go:419 의 매처 거절과 api.go:474 의 ErrNodeTaken. 호출자는 둘을 구별할 수단이 없고 둘 다 「지금은 자리가 없다」이다. B 를 고르면 완료 조건 「409 가 QUEUED 가 된다」가 절반만 참이 된다) ④ Reap 의 어느 자리에서 깨우는가 (권장 A: 실제로 무언가를 놓았을 때만. 무조건 깨우면 RunReaper 가 RenewSeconds 마다 돌므로 사실상 주기 폴링이고 완료 조건 「reaper 주기에 안 얹었다」와 어긋난다).
emphasis-check.py 초록.
Context: CONSTRUCTION / U-대기열 Functional Design Step 3. 답을 받기 전에는 산출물 셋을 쓰지 않는다
U-대기열 Functional Design — 묻지 않고 닫은 것 열
Timestamp: 2026-09-05T00:37:38Z
AI Response: 질문 후보였다가 문서와 코드 대조로 닫힌 것을 질문 파일 2절에 열 개로 적었다. 코드를 읽어 확인한 사실을 기록한다. ① 대기 중 취소는 코드를 안 고쳐도 된다 — Cancel 이 state NOT IN ('SUCCEEDED','FAILED') 로 거르므로 QUEUED 가 그대로 걸리고, steps 를 PENDING 에서 FAILED 로 옮기고, 없는 임대를 지우고, internal/store/reap.go:207 에서 봉인한다. ② runctl 은 고칠 것이 없다 — internal/runctl/client.go:70 이 StatusCode/100 == 2 로 받아 202 가 통과하고, Terminal 이 SUCCEEDED 와 FAILED 뿐이라(client.go:208) --wait 도 QUEUED 에서 계속 폴링한다. 앞 단계가 적은 문장을 실제 코드에서 확인한 것이다. ③ dry-run 은 지금 busy 를 아예 안 읽는다 (internal/api/api.go:402 의 if !dry) — FR-11 의 draining 합침을 dry == false 갈래에만 넣어야 NFR-I6 가 지켜진다. ④ 승격 시 폭 상한 초과는 영구 거절이 아니다 — t=0 의 422 가 영구인 이유는 「다시 내도 같다」인데 승격 시점의 초과는 그때 고른 노드 조합의 문제라 다음 깨우기에 달라질 수 있다(api.go 의 seen 이 한 노드의 두 역할을 접는다). 로그만 남기고 QUEUED 로 둔다. 나머지 여섯 — 202 본문은 runView 이고 Warnings 를 201 과 같게 싣는다 · 임대 TTL 은 Store.LeaseTTL 필드로 받는다(MaxLeasesPerRun 과 같은 배선. §4.1 이 인자 없는 시그니처를 고정했다) · Records.Open 은 제출 시점 · submit 과 WakeQueued 를 한 함수로 안 합치고 겹치는 입력만 store 메서드 하나로 뺀다 · steps.node_id 빈 문자열이 곧 「아무도 못 집는다」이고 승격이 그 열을 채우는 것이 배분이다 · work_id 는 제출 시점에 박는다.
Context: CONSTRUCTION / U-대기열 Functional Design. 코드 기준선 1c290a6
U-대기열 Functional Design — 앞 단계 문장 하나를 정정했다
Timestamp: 2026-09-05T00:37:38Z
AI Response: 「부분 반납 뒤 깨우기」는 독립된 호출 자리가 아니다. unit-of-work.md 의 U-대기열 파일 행렬이 internal/store/release.go 를 「부분 반납 뒤 깨우기」로 적었는데 그 자리에서는 깨울 수 없다 — applyRelease 가 ReportStep 의 트랜잭션 안에서 돈다. 호출 사슬을 줄 번호로 확인했다: ReportStep 이 claim.go:773 에서 tx 를 열고, afterStep 이 claim.go:843 에서 applyStepEffects 를, 그것이 claim.go:863 에서 applyRelease 를 부르고, release.go:73 의 DELETE FROM leases 가 그 tx 안에서 돌며, 커밋은 claim.go:823 이다. 트랜잭션 안에서 깨우면 아직 커밋 안 된 임대 해제를 보고 승격하려 들고 승격의 트랜잭션이 같은 행을 기다린다. 자리는 커밋 뒤 — 즉 postResult 이고, 그 한 번이 「완료」와 「부분 반납」 둘을 덮는다. 딸린 조건이 하나 붙는다: SettleIfDone 이 빈 문자열을 내도(아직 진행 중이라는 뜻) 깨워야 한다. 안 그러면 부분 반납으로 풀린 노드를 아무도 안 본다. 파급은 internal/store/release.go 를 안 고치는 것이고 이 유닛이 만지는 파일이 여섯에서 다섯으로 준다. 깨우기 지점은 여전히 여섯이다 (requirements.md FR-9) — 호출 자리가 다섯인 것뿐이다. aidlc-state.md 의 정정 표에 다섯째 줄로 올렸다.
Context: CONSTRUCTION / U-대기열 Functional Design. 코드 기준선 1c290a6
U-대기열 Functional Design — 답 요청
Timestamp: 2026-09-05T00:37:38Z
AI Prompt: 질문 넷의 [Answer] 를 채워 달라. 답을 받으면 domain-entities.md · business-logic-model.md · business-rules.md 를 낸다. frontend-components.md 는 안 낸다 — 이 유닛에 화면이 없다
Status: Pending
Context: CONSTRUCTION / U-대기열 Functional Design Step 5
U-대기열 Functional Design — 질문 넷의 답과 산출물 셋
Timestamp: 2026-09-05T00:52:10Z
User Input: "전부 권장안대로 할게"
AI Response: 답 넷을 전부 A 로 읽고 질문 파일의 [Answer] 넷을 채웠다. 모호도 모순도 없어 후속 질문을 안 냈다 (Step 5 분석 — 단일 표현이고 넷 다 권장과 일치하며 조합·유보 표현이 없다). 값 — ① store.WakeQueued 가 승격을 통째로 진다 ② 깨우기는 동기다 ③ 409 두 자리가 다 QUEUED 가 된다 ④ Reap 은 실제로 놓았을 때만 깨운다.
construction/u-queue/functional-design/ 에 셋을 냈다 — domain-entities.md(스키마를 한 줄도 안 바꾼다는 것 · QUEUED Run 의 행 모양 표와 거기서 따라 나오는 셋 · 새 Store.LeaseTTL 필드 · 옮기는 함수 · 새 함수 다섯 · 202 와이어 · 안 만드는 것 여덟) · business-logic-model.md(제출 갈래 지금과 뒤 · CreateQueuedRun · WakeQueued 훑기 · promote 트랜잭션 · 깨우기 자리 다섯 · 크래시 복구 · 흐름 하나로) · business-rules.md(R1 ~ R16 · NFR 대조 일곱 · 알려진 창 하나 · 완료 조건 열여덟 · 테스트 목록 · CP2). frontend-components.md 를 안 냈다 — 이 유닛에 화면이 없다.
설계가 스스로 정한 값 넷 (문서가 안 정한 자리라 근거를 함께 적었다) — ① Store.LeaseTTL 필드. §4.1 이 WakeQueued(ctx) error 로 인자 없는 시그니처를 고정했고 부르는 자리 여섯 중 둘(Reap · 기동)이 설정을 손에 안 쥐고 있다. MaxLeasesPerRun 과 같은 배선이고, 값이 0 이면 승격을 안 하고 오류를 낸다 — 조용히 무한 임대를 발급하지 않는다 ② label 과 nonce 를 internal/api 에서 internal/store 로 옮긴다. 승격도 제출도 둘을 쓰는데 두 벌이 되면 Record 의 노드 이름과 허가 아티팩트가 경로마다 갈린다. api.go 에서 crypto/rand 와 encoding/hex 임포트가 빠진다(그 둘을 쓰는 자리가 nonce() 하나뿐인 것을 확인했다) ③ UnavailableNodes — BusyNodes 와 DrainingNodes 의 합집합을 store 메서드 하나로 둔다. 제출은 draining 을 빼는데 승격은 안 뺀다면 대기하던 Run 이 회수 중인 노드로 간다 ④ insertSteps 를 CreateRun 과 CreateQueuedRun 이 나눠 쓴다 — st.Kind() 검증과 contract.NeedsOf 정규화가 두 벌이 되면 같은 계약이 제출 경로에 따라 다른 순서로 돈다.
승격의 동시성을 잠금 없이 얻는다 — 상태 전이를 UPDATE runs SET state='RUNNING' WHERE run_id=$1 AND state='QUEUED' 로 두면 두 훑기가 같은 Run 을 봐도 뒤의 것이 행 잠금에서 기다렸다가 0행을 받는다. 노드 경쟁은 leases.node_id 기본키 충돌이 막는다 — CreateRun 이 I5 를 얻는 그 기계와 같다.
emphasis-check.py 가 새 파일 셋에 초록. 확장 준수에 blocking 없음 — 이 유닛은 새 표면을 하나도 안 연다(새 라우트 0 · 새 입력 파라미터 0 · 새 HTML 0)라 SECURITY-04 · 05 · 07 · 08 · 12 가 전부 N/A 이고, 걸리는 것은 SECURITY-15 하나(새 오류 문구에 내부 경로 · 스택 · SQL 을 안 싣는다. 승격 실패는 호출자가 없는 자리라 로그로만 나간다)다.
Context: CONSTRUCTION / U-대기열 Functional Design Step 6 완료. 승인 대기
U-대기열 Functional Design — 창 하나를 열어 두고 적었다
Timestamp: 2026-09-05T00:52:10Z
AI Response: 임대가 풀리는 자리를 grep 으로 전부 셌다 — 넷이다 (store/reap.go:325 SettleIfDone · :201 Cancel · :81 Reap 의 정리 · store/release.go:73 applyRelease). 임대는 SQL 이 스스로 안 지우므로 넷 다 깨우기가 덮으면 「자원이 풀렸는데 아무도 안 봤다」가 없고, 실제로 덮인다. 닫히지 않는 자리가 하나 있다 — 대기 중인 Run 의 후보 노드가 사라졌다가(광고 만료) 돌아오는 경우다. 광고가 다시 오는 것은 깨우기 지점이 아니므로 그 Run 은 함대 어디선가 임대가 풀리거나 Mediator 가 재기동할 때까지 기다린다. 활동이 있는 함대에서는 곧 닫히고 조용한 함대에서는 열린 채로 있다. 이 유닛에서 안 닫는 이유 — 닫는 자리는 postNodes 인데 광고는 노드마다 주기로 오므로 무조건 깨우면 「reaper 주기에 안 얹었다」와 같은 성질의 규칙(R14)을 깬다. 「이전에 만료돼 있던 노드가 돌아왔다」만 골라 깨우면 규칙을 안 깨지만 깨우기 지점은 requirements.md FR-9 가 여섯으로 고정했고 일곱째를 이 유닛이 스스로 더하지 않는다. U-drain 이 같은 핸들러에 여섯째(drain 해제 감지)를 달고 UpsertAdvert 가 이미 이전 값을 돌려주는 구조라, 닫기로 한다면 그 자리가 자연스럽다는 것까지 business-rules.md 5절에 적었다. 범위 밖이라 적어만 두고 안 고쳤다.
Context: CONSTRUCTION / U-대기열 Functional Design. 코드 기준선 1c290a6
U-대기열 Functional Design — 승인 요청
Timestamp: 2026-09-05T00:52:10Z
AI Prompt: "Functional design complete for U-대기열. Review the artifacts at aidlc-docs/construction/u-queue/functional-design/. Request Changes or Continue to Code Generation?"
Status: Pending
Context: CONSTRUCTION / U-대기열. 승인하면 Code Generation Part 1 — 계획 파일을 만들고 CP2 게이트 명령을 박는다
U-대기열 Functional Design — 승인
Timestamp: 2026-09-05T02:06:29Z
User Input: "승인"
AI Response: Functional Design 승인으로 읽고 U-대기열의 설계 단계를 닫았다. 산출물 셋에 변경 요청이 없었다. 다음은 Code Generation Part 1 — 계획 파일 construction/plans/u-queue-code-generation-plan.md 를 만들고 CP2 게이트 명령을 실제로 돌리는 단계를 계획에 박는다.
Context: CONSTRUCTION / U-대기열 Functional Design 승인. Code Generation Part 1 착수
U-대기열 Code Generation Part 1 — 계획 파일
Timestamp: 2026-09-05T02:06:29Z
AI Response: aidlc-docs/construction/plans/u-queue-code-generation-plan.md 를 냈다 (약 490줄). 단계 열아홉이고 체크박스가 전부 [ ] 다. 이 유닛은 Functional Design 을 돌았으므로 계획이 설계를 다시 안 적는다 — 타입과 자리와 절차와 규칙은 승인된 산출물 셋이 지고, 계획이 새로 지는 것은 어느 파일을 어느 순서로 고치는가와 CP2 를 어떻게 실제로 돌리는가다.
신설 둘 · 수정 여덟. 신설은 internal/api/queue.go(enqueue · wake)와 internal/api/queue_test.go. 수정은 internal/store/store.go(LeaseTTL 필드 · insertSteps 추출 · CreateQueuedRun) · internal/store/queue.go(UnavailableNodes · promote · WakeQueued) · internal/store/list.go(Label) · internal/store/reap.go(실제로 놓았을 때만 깨우기) · internal/store/queue_test.go · internal/api/api.go · internal/api/api_test.go · internal/api/list_test.go · cmd/mediator/main.go 다. 새 Go 의존 0 · 새 패키지 0 · 새 열 0 · 새 라우트 0 · 마이그레이션 0.
코드를 읽어 계획에 박은 사실 셋 — ① nonce 는 옮길 것이 없다. Functional Design 요약이 「label 과 nonce 를 옮긴다」고 적었는데 internal/store/acquire.go:243 에 같은 구현이 이미 있고 획득 경로가 그것을 쓴다. 승격은 같은 패키지의 그 함수를 그대로 부르고 internal/api/api.go 의 nonce() 는 그대로 둔다 — 지우려면 store 쪽을 내보내야 하고 그러면 안 지어도 될 공개 표면이 하나 는다. 따라서 api.go 에서 crypto/rand · encoding/hex 임포트가 안 빠진다. domain-entities.md §3.2 의 제목 「옮기는 함수 하나」와 일치한다 ② 제출 경로에서 409 가 사라지면 기존 테스트 일곱 자리가 빨개진다. api_test.go:253 · :312 · :326 · :1870 과 list_test.go:191 · :293 · :303 이고, 마지막은 state=FAILED 필터가 state=QUEUED 로 바뀌는 자리라 완료 조건과 겹친다. 설계 문서가 세지 않은 자리라 계획 3.2절에 줄 번호로 적고 Step 14 로 박았다. 단언을 넓히지 않는다 — TestSubmitRace 의 switch 는 case 202: 로 갈아 끼우고 default: t.Fatalf 를 남긴다 ③ LeaseTTL 배선이 테스트 헬퍼 하나를 건드린다. cmd/mediator/main.go 의 openRecords 에 넣는 것이 설계인데, internal/api 의 통합 테스트는 newServerFast 가 store 를 배선하므로 그 옆에 한 줄이 는다.
Step 16 이 CP2 를 실제로 돌리는 단계다 — CP0 · CP1 의 줄을 다시 돌려 회귀를 잡고, 거기에 runctl 장면 다섯(201 -> 202 -> QUEUED -> a 종료 뒤 RUNNING -> 없는 능력은 422) · 라우트 18개 회귀 · 기동 시 승격을 더한다. 경계 검사기는 2 checked, 2 skipped, 4 total 그대로여야 한다 — 새 패키지가 0 이므로 숫자가 바뀌면 잘못 만든 것이다. Step 17 이 화면 검증을 따로 적는다 — S1 의 QUEUED 칩이 실제 행을 내고 어느 카드에도 안 얹혀 있어야 하며, 화면 코드는 한 줄도 안 고친다.
단계 순서에 뜻을 실었다 — Step 1 ~ 8 이 store, 9 가 store 테스트, 10 ~ 13 이 api 와 기동, 14 가 기존 테스트, 15 가 새 API 테스트다. 9 를 api 앞에 둔 것은 승격이 혼자서 옳은지를 먼저 확인하기 위해서고, 14 를 15 앞에 둔 것은 기존 테스트를 먼저 초록으로 만들어야 새 테스트의 빨강이 새것의 빨강이기 때문이다.
emphasis-check.py 초록. 확장 준수에 blocking 없음 — 이 유닛은 새 표면을 하나도 안 열어 SECURITY-04 · 05 · 07 · 08 · 12 가 전부 N/A 이고 걸리는 것은 SECURITY-15 하나다.
Context: CONSTRUCTION / U-대기열 Code Generation Part 1 Step 4
U-대기열 Code Generation Part 1 — 승인 요청
Timestamp: 2026-09-05T02:06:29Z
AI Prompt: "Code generation plan complete for U-대기열. Review the plan at aidlc-docs/construction/plans/u-queue-code-generation-plan.md. Request Changes or Continue to Code Generation?"
Status: Pending
Context: CONSTRUCTION / U-대기열 Code Generation Part 1 Step 6. 승인하면 Part 2 — Step 1 부터 순서대로 실행하고 단계마다 그 자리에서 체크박스를 [x] 로 바꾼다
U-대기열 Code Generation Part 1 — 승인
Timestamp: 2026-09-05T03:36:34Z
User Input: "승인"
AI Response: Code Generation Part 1 승인으로 읽고 계획 파일을 확정했다. 계획에 변경 요청이 없었다. Part 2 를 시작한다 — construction/plans/u-queue-code-generation-plan.md 의 Step 1 부터 순서대로 실행하고 단계마다 그 자리에서 체크박스를 [x] 로 바꾼다.
Context: CONSTRUCTION / U-대기열 Code Generation Part 1 Step 6 승인. Part 2 착수
U-대기열 Code Generation Part 2 — 실행
Timestamp: 2026-09-05T04:01:32Z
AI Response: 계획 construction/plans/u-queue-code-generation-plan.md 의 단계 열아홉을 순서대로 실행했다. 체크박스 111개가 전부 [x] 다. 신설 둘 — internal/api/queue.go(enqueue · wake) · internal/api/queue_test.go(테스트 아홉). 수정 아홉 — internal/store/store.go(LeaseTTL 필드 · insertSteps 추출 · CreateQueuedRun) · internal/store/queue.go(UnavailableNodes · promote · WakeQueued · ErrNoLeaseTTL) · internal/store/list.go(Label) · internal/store/reap.go(실제로 놓았을 때만 깨우기) · internal/store/queue_test.go(테스트 열하나) · internal/api/api.go(submit 갈래 둘 · UnavailableNodes · 깨우기 셋 · label 삭제) · internal/api/api_test.go · internal/api/list_test.go · cmd/mediator/main.go. 새 Go 의존 0 · 새 패키지 0 · 새 열 0 · 새 라우트 0 · 마이그레이션 0. internal/store/release.go 는 안 고쳤다 — Functional Design 이 잡은 정정 그대로다.
CP2 를 실제로 돌렸다. go build · go vet exit 0 · go test ./... 17개 패키지 전부 ok 이고 -json 으로 센 스킵 0건 · glyphscan 86 파일 무결 · golangci-lint 20건으로 기준선 그대로이고 새 파일과 고친 파일에 0건 · 커버리지 게이트 16개 패키지 전부 하한 80% 통과(전체 87.2% · 5604/6427) · go test -v ./internal/boundary 가 2 checked, 2 skipped, 4 total 로 안 바뀌었다(새 패키지가 0 이므로 바뀌면 잘못 만든 것이다). 살아 있는 Mediator 로 장면 다섯을 돌렸다 — 201 · 202+QUEUED · runctl status 가 QUEUED · a 종료 뒤 RUNNING · 없는 능력은 422+FAILED. 라우트 18개 회귀 0(mux 의 404 page not found 가 하나도 없고 GET /v1/nope 만 mux 404 · DELETE /v1/runs 는 405). 기동 시 승격도 확인했다 — 임대를 DB 에서 직접 지워 깨우기 경로를 안 거치게 한 뒤 재기동하니 QUEUED Run 이 RUNNING 이 되고 로그에 queued run promoted 가 남았다.
화면 S1 을 Playwright 로 따로 확인했다. 승격 전 — gerrit-4519-ps1 이 QUEUED 배지와 「노드 미배정」 부제로 실제 행을 내고, 카드 셋 어디에도 없고, QUEUED 칩을 누르면 그 행 하나만 남는다. 승격 후 — 같은 Run 이 칩에서 빠지고 ws-alpha 카드의 임대 줄에 붙는다. 콘솔 오류 0건 · CSP 위반 0건. 화면 코드를 한 줄도 안 고쳤다 — internal/api/ui/ 아홉 파일의 수정 시각이 전부 이 작업 이전이다.
계획에서 갈린 것 셋 — ① 조기 해제 테스트의 단언 모양이 바뀌었다. 계획 3.2절이 api_test.go:1870 을 「409 -> 202」 한 줄로 셌는데, 그 테스트가 해제를 증명하던 방식(rel-after 제출이 201)이 대기열과 겹친다 — 놓인 자원을 기다리던 rel-before 가 그 자리에서 승격되므로 새 제출은 다시 202 를 받는다. 같은 사실을 「기다리던 쪽이 실제로 RUNNING 이 된다」로 옮겼다. 단언을 넓힌 것이 아니라 더 강하게 만든 것이다 ② API 테스트를 여덟이 아니라 아홉을 냈다. 더한 하나는 TestWake_AnUnwiredQueueDoesNotBreakTheReport 다 — 커버리지 프로파일로 internal/api/queue.go 의 wake 오류 갈래 세 블록이 전부 count 0 인 것을 확인했고, R16(깨우기 실패가 응답을 안 바꾼다)을 API 표면에서 보는 자리가 그것 하나라 비워 둘 수 없었다. 그 파일이 44.4% 에서 61.1% 로, internal/api 가 81.2% 에서 81.8% 로 올랐다 ③ internal/api 커버리지가 계획의 예측과 반대로 내려갔다 (82.8% -> 81.8%). 계획이 여유가 생길 것이라고 본 「승격의 오류 갈래 넷」은 internal/store 쪽이고 internal/api 에는 새 파일 하나가 통째로 들어왔다. 하한은 넘는다.
계획 문장 하나를 정정했다 — 2절 파일 행렬의 「수정 여덟」은 미계수다. 그 행렬은 처음부터 아홉 줄이다. aidlc-state.md 에도 같은 수를 고쳤다.
산출물은 construction/u-queue/code/code-generation-summary.md 다. emphasis-check.py 초록. enode-design/ 도 design/ 도 README.md 도 안 고쳤다 — 정본 개정은 이 저장소의 일이 아니고(canon.md 3절) POST /v1/runs 의 202 가 mediator-api.md 에 아직 없다는 것은 요약 8절에 적었다. business-rules.md 5절의 열린 창(사라졌다 돌아온 노드)도 요약 7절에 다시 적었다 — U-drain 이 닫을 수 있는 자리라는 것까지. 테스트 실행이 건드린 cmd/enodectl/probe.lock 은 되돌렸다. 코드는 커밋하지 않았다 — 진행자만 커밋한다.
Context: CONSTRUCTION / U-대기열 Code Generation Part 2. 코드 기준선 1c290a6
U-대기열 Code Generation Part 2 — 승인 요청
Timestamp: 2026-09-05T04:01:32Z
AI Prompt: "Code generation complete for U-대기열. CP2 is green and screen S1 was verified in a browser. Review the code and the summary at aidlc-docs/construction/u-queue/code/code-generation-summary.md. Request Changes or Continue to Next Stage?"
Status: Pending
Context: CONSTRUCTION / U-대기열 Code Generation Part 2 Step 18. 승인하면 U-drain (CP3) 으로 간다 — 그 유닛은 Functional Design 부터다
범위 변경 — Mediator MCP 를 이월에서 범위로
Timestamp: 2026-09-05T05:58:06Z
User Input: "지금 우리 구현목표중에 mediator mcp가 포함되었는가" / "올려도 하류에 영향이 없지 않나? 왜 이월했는지 모르겠네. 올려놔" / "계속."
AI Response: 먼저 사실을 확인해 답했다 — 포함돼 있지 않았다. 네 문서가 같은 말을 했다: enode-features.md §1.3 · §4 이월, constraints.md §8, decisions.md §4, requirements.md §7. 사용자의 전제(하류 영향 없음)가 맞다는 것도 정본이 뒷받침한다 — ADR-022 §9.3 이 S1 MCP 어댑터를 「독립」으로 적고 「이미 있는 REST 를 도구로 감싼다 — 새 의미가 0 개다」로 못 박았다. 이월의 기록된 이유는 결합이 아니라 값 둘이었다 (decisions.md §4 「MCP 전송 · 토큰 전달」).
그 둘을 닫기 위해 질문 넷을 냈고 사용자가 전부 권장안을 골랐다 — ① 전송은 stdio 하나 (streamable HTTP 를 안 연다. constraints.md §6 이 TLS 와 리버스 프록시를 범위 밖으로 뒀으므로 HTTP 를 열면 그 문장과 정면으로 걸린다) ② 토큰은 환경변수 그대로 (ENODE_MEDIATOR · ENODE_TOKEN. 도구 인자로 받으면 토큰이 대화 기록에 남고 SECURITY-12 에 걸린다) ③ 도구는 일곱 (ADR-022 §9.3 의 다섯 + 이번 회차가 만든 fleet.list · runs.list. prep/GOALS.md 가 「대시보드에 쓰인 것과 동일한 자료가 MCP 로」를 요구한다) ④ 자리는 runctl mcp 하위명령 + internal/mcp 패키지이고 JSON-RPC 2.0 을 손으로 짠다 (internal/runctl/client.go 를 그대로 재사용해 HTTP 클라이언트를 새로 안 짠다. 새 실행파일 0 이라 packaging/ 무변경, 새 Go 의존 0 이라 유닛마다 세어 온 게이트가 안 끊긴다).
고친 문서 아홉. 팩 다섯 — requirements/enode-features.md(§1.3 에 3.3 접수 축 추가 · §4 필수가 넷에서 다섯 · 이월 표에서 제거) · requirements/constraints.md(§8 에서 빼되 줄을 지우지 않고 「범위로 들어왔다」를 남겼다 — 왜 밖에 있다가 안으로 왔는지를 다음 사람이 거기서 읽는다) · requirements/decisions.md(5절 신설 — 되돌린 근거 · 닫은 값 넷 · 도구 일곱 · 안 넓히는 자리) · requirements/scene-gates.md(CP5 「감싼다」 추가) · requirements/unit-boundaries.md(§1 유닛 표에 U-MCP · §2 파일 행렬에 MCP 열). AI-DLC 넷 — requirements.md(FR-26 ~ FR-29 · §4.1 계약에 도구 이름과 클라이언트 메서드 · §7 범위 밖에서 제거) · unit-of-work.md(U-MCP 절 — 하는 일 · 딛는 것 · 파일 넷 · 완료 조건 열다섯 · CP5) · unit-of-work-dependency.md(의존 행렬에 MCP 행과 열 · 파일 행렬에 MCP 열) · unit-of-work-story-map.md(FR 셈이 25에서 29 · 게이트 요약에 CP5) · aidlc-state.md(유닛 표 · 실행 계획 4절 · 5절 착수 순서 · 7절 성공 기준 · mermaid 라벨).
계획에 안 지어 넣고 열어 둔 것 하나 — 여덟째 도구 후보 run.get. ADR-022 §9.3 의 목록은 ADR-025 보다 앞이라 record.get 하나로 조회를 덮었는데 record.get 은 종료 전이면 409 다(I4). 즉 일곱만으로는 「지금 어떻게 돌고 있나」를 못 묻는다. GET /v1/runs/{id} 가 그것을 내고 Client.Status 가 이미 있다. 여덟째로 둘지를 U-MCP 의 Functional Design 첫 질문으로 남겼다 — 승인받은 수가 일곱이므로 여기서 지어 넣지 않는다. decisions.md 5.3 에 적었다.
U-MCP 는 게이트 사슬 밖이다. dhseo 장면에 MCP 가 안 나오므로 CP4 의 조건이 아니고, 딛는 것이 CP1 의 라우트뿐이라 지금 이미 착수 가능하며 U-drain · U-제어판과 파일이 하나도 안 겹쳐 병렬로 돈다. 코드는 한 줄도 안 썼다 — 이 변경은 Requirements 와 Units 층이다. emphasis-check.py 가 고친 아홉에 전부 초록.
Context: INCEPTION 재개 / Requirements · Units 범위 변경. CP2 종료 뒤. 코드 기준선 1c290a6
유닛 그래프를 문서에 남기고 세션을 닫는다
Timestamp: 2026-09-05T06:07:48Z
User Input: "지금 unit 들 박스 그래프로 그려줘. 터미널에." / "이 그림을 문서에 남겨두고 종료. 다음 세션에서 u-drain 하고 병렬 진행 하겠다."
AI Response: 유닛 여섯의 의존을 상자로 그려 터미널에 냈고, 같은 그림을 inception/application-design/unit-of-work-dependency.md 1.1 절로 남겼다. 그리는 규칙은 화살표가 「딛는다」 방향으로 아래에서 위를 가리킨다 — 먼저 서야 하는 것이 아래다. U-바닥을 화살표가 아니라 밑에 깐 상자로 둔 것은 나머지 다섯이 전부 그것을 딛어서 선을 그리면 다섯 줄이 지나가기 때문이다. 한글이 두 칸이라 표시 폭으로 정렬을 계산해서 그렸다.
그림이 보이는 것 셋 — ① 왼쪽 기둥이 비어 있다: U-관측API -> U-MCP 하나뿐이고 그 위가 끝이라 U-MCP 가 늦어도 아무것도 안 막는다 ② 오른쪽 기둥이 직렬이다: U-대기열 -> U-drain -> U-제어판 에 U-관측API -> U-제어판 이 가로질러 들어온다 ③ 두 기둥이 만지는 파일이 하나도 안 겹친다.
다음 세션 계획을 상태 파일에 박았다 — U-drain (CP3) 과 U-MCP (CP5) 를 병렬로 돌린다. 둘 다 Functional Design 부터다. U-제어판 (CP4) 은 policy.Read · policy.Write 의 모양이 U-drain 의 Functional Design 에서 나오므로 그 뒤이고, 그것이 닫히면 셋째 기둥으로 붙을 수 있다.
병렬의 남은 위험 하나를 같이 적었다 — aidlc-state.md 와 audit.md 가 각각 한 장이고 모든 유닛이 같은 파일에 쓴다. 세션 둘이 동시에 쓰면 부딪친다 (START.md §8). CONVENTIONS.md 3.4 가 커밋 쪽 절반(상태 파일은 마지막 커밋에)만 처리했고, 동시 쓰기 규칙은 아직 없다. 다음 세션이 병렬을 시작하기 전에 정할 자리다.
코드는 이번에도 한 줄도 안 썼고 커밋도 안 했다 — CONVENTIONS.md 3.2 대로 진행자 몫이다.
Context: INCEPTION 재개 / 유닛 그래프 문서화. 세션 종료. 코드 기준선 1c290a6