U-대기열 — Code Generation 계획
이 파일이 U-대기열 Code Generation 의 정본이다. 여기 적힌 단계만
실행하고, 여기 없는 것은 만들지 않는다. 단계를 끝낼 때마다 그 자리에서
[x] 로 바꾼다.
이 유닛은 Functional Design 을 돌았다. 그래서 이 계획은 설계를 다시
적지 않는다 — 타입과 자리는 construction/u-queue/functional-design/domain-entities.md,
절차는 business-logic-model.md, 규칙과 완료 조건은 business-rules.md 에
있고 셋 다 승인됐다. 여기서 새로 지는 것은 어느 파일을 어느 순서로
고치는가와 CP2 를 어떻게 실제로 돌리는가다.
읽는 순서 — 2절이 파일, 3절이 계획이 확정한 값과 정정, 4절이 단계다.
1. 유닛 맥락
1.1 이 유닛이 지는 FR
| FR | 무엇 | 게이트 |
|---|---|---|
| FR-6 | 409 -> QUEUED, 제출 응답 202. 422 는 그대로 FAILED |
CP2 |
| FR-7 | QUEUED Run 의 steps 를 제출 시점에 만든다. runAcquires · raiseAsks 는 승격으로 미룬다 |
CP2 |
| FR-8 | 승격. store.WakeQueued 가 도착 순서로 전부 훑는다 |
CP2 |
| FR-9 | 깨우기 지점 여섯 중 다섯. 여섯째(drain 해제)는 U-drain 이 단다 | CP2 |
| FR-10 | 기동 시 WakeQueued 한 번 |
CP2 |
| FR-11 | 매칭에서 draining 노드 제외 — 중앙 쪽. submit 이 합쳐 넘긴다 |
CP3 |
1.2 딛는 것
U-바닥 store.StateQueued 상수 · ValidState 의 다섯째 값
store.ListQueued (created_at 오름차순)
store.DrainingNodes
nodes.drain 열 · UpsertAdvert 의 이전 정책 반환
U-관측API GET /v1/runs?state=QUEUED 가 이미 있다. 이 유닛은 그 값을 가진
행이 실제로 생기게 할 뿐이고 라우트도 화면도 안 고친다
1.3 뒤 유닛에 내주는 접점
U-drain 광고 처리에 여섯째 깨우기 지점을 단다. 이 유닛이 s.wake(ctx, why) 를
internal/api/queue.go 에 두므로 U-drain 은 그 조각을 부르기만 한다
DrainingNodes 가 UnavailableNodes 안으로 들어가 있다 — 정책이 붙으면
제출과 승격이 같은 자리에서 함께 빠진다
U-제어판 없다. 이 유닛은 라우트도 화면도 안 만든다
1.4 이 유닛이 소유하는 DB 실체
없다. 새 테이블 0 · 새 열 0 · 새 인덱스 0 · 마이그레이션 0 이다.
runs.state 에 CHECK 가 없어 QUEUED 가 마이그레이션 없이 들어간다
(domain-entities.md §1.1).
2. 파일 행렬
브라운필드다. 있는 파일은 제자리에서 고친다. _new · _modified 같은
사본을 만들지 않는다.
신설 internal/api/queue.go enqueue (202 조립) · wake (깨우기 조각)
internal/api/queue_test.go 제출 갈래와 깨우기의 API 테스트
수정 internal/store/store.go LeaseTTL 필드 · insertSteps 추출 · CreateQueuedRun
internal/store/queue.go UnavailableNodes · promote · WakeQueued
internal/store/list.go Label — api 의 label 을 옮겨 받는다
internal/store/reap.go 실제로 놓았을 때만 끝에서 한 번 깨운다
internal/store/queue_test.go store 테스트 열하나를 이어 붙인다
internal/api/api.go submit 갈래 둘 · busy 합침 · 깨우기 셋 · label 삭제
internal/api/api_test.go 409 기대 넷 · newServerFast 에 LeaseTTL
internal/api/list_test.go 409 기대 둘과 FAILED 기대 하나
cmd/mediator/main.go LeaseTTL 배선 · 기동 시 한 번
부르기만 internal/match/match.go Match 를 승격이 다시 부른다. 안 고친다
internal/store/acquire.go nonce() 가 이미 있다. 3.1절
internal/store/ask.go raiseAsks · PushAsks
안 건드림 internal/store/release.go applyRelease 는 ReportStep 의 트랜잭션 안이다
internal/store/schema.sql 새 열이 0 이다
internal/runctl · cmd/runctl 202 가 이미 통과한다 (client.go:70)
internal/api/ui · internal/api/list.go
internal/contract · internal/config · internal/enode · internal/record
cmd/ 의 나머지 넷 · packaging/
design/ 전부 (START.md 5절 — 진행자만 고친다)
enode-design/ 서브모듈 (canon.md — 정본 개정은 이 저장소의 일이 아니다)
새 Go 의존이 0 이다. go.mod 를 안 건드린다 — 쓰는 것이 전부
표준 라이브러리이거나 이 저장소의 기존 패키지다.
새 패키지가 0 이다. 그래서 경계 검사기의 숫자가 안 바뀐다 —
2 checked, 2 skipped, 4 total 그대로여야 하고, 바뀌면 무언가를 잘못 만든 것이다.
3. 계획이 확정하는 값
설계가 안 정했거나, 코드를 읽어 보니 설계 문장과 달랐던 자리다. 근거를
함께 적는다.
3.1 정정 — nonce 는 안 옮긴다
Functional Design 의 요약이 「label 과 nonce 를 internal/api 에서
internal/store 로 옮긴다」고 적었다. nonce 쪽은 옮길 것이 없다 —
internal/store/acquire.go:243 에 같은 구현이 이미 있고 획득 경로가 그것을
쓴다. 승격은 같은 패키지의 그 함수를 그대로 부른다.
internal/api/api.go 의 nonce() 는 그대로 둔다. 제출 경로가
LeaseGrant.Nonce 를 채우는 데 쓰고, 그것을 지우려면 store 쪽을 내보내야
하는데 그러면 이 유닛이 안 지어도 될 공개 표면을 하나 만든다. 그 결과
api.go 에서 crypto/rand · encoding/hex 임포트가 안 빠진다 — 요약의
그 문장도 여기서 정정된다.
두 벌로 두어도 갈리지 않는다. label 이 두 벌이면 Record 의 노드 이름이
경로마다 달라지지만, nonce 는 난수 16바이트라 두 구현이 「달라질」 내용이
없다. domain-entities.md §3.2 의 제목이 「옮기는 함수 하나」인 것과
일치한다.
3.2 기존 테스트 일곱 자리가 바뀐다 — 설계가 안 센 자리
제출 경로에서 409 가 사라지므로 그것을 기대하던 기존 테스트가 빨개진다.
세어서 적는다 — 실행 중에 놀라는 것과 계획에 있는 것은 다르다.
internal/api/api_test.go:253 TestSubmitRejectCodes 409 -> 202
internal/api/api_test.go:312 TestSubmitRace 409 -> 202 (경쟁의 진 쪽)
internal/api/api_test.go:326 TestSubmitRace 409 -> 202 (세 번째 요구)
internal/api/api_test.go:1870 부분 반납 테스트 409 -> 202
internal/api/list_test.go:191 TestRuns_TheNewestComesFirst 409 -> 202
internal/api/list_test.go:293 TestRuns_TheFiltersCut 409 -> 202
internal/api/list_test.go:303 TestRuns_TheFiltersCut state=FAILED -> state=QUEUED
마지막 줄이 완료 조건과 겹친다 — 「GET /v1/runs?state=QUEUED 가 그 행을
낸다」를 이미 있는 테스트가 자리를 옮겨 확인하게 된다.
단언의 뜻을 안 약하게 한다. 409 를 202 로 바꿀 뿐 「거절됐다」를
「아무 코드나 온다」로 넓히지 않는다. TestSubmitRace 의 switch 도
case 202: 로 갈아 끼우고 default: t.Fatalf 를 남긴다 — 셋째 코드가 오면
여전히 실패해야 한다.
주석도 함께 고친다. api_test.go:249 의 「일시적이므로 409」와
list_test.go:292 의 「409 로 거절되고 FAILED 로 남는다」가 코드와 어긋나면
다음에 읽는 사람이 테스트를 못 믿는다.
3.3 LeaseTTL 은 어디서 배선되나
cmd/mediator/main.go 의 openRecords — MaxLeasesPerRun 바로 옆이다
(domain-entities.md §3.1). api.New 에 안 넣는다. 그 함수가
st.AnswerPath 를 넣는 것은 라우트 경로가 HTTP 표면의 소유물이기 때문이고
(store.go 주석), 임대 수명은 설정의 값이라 성격이 다르다.
그 결과가 테스트 헬퍼 하나의 수정이다. newServerFast 가 이미
st.MaxLeasesPerRun 을 넣고 있으므로 그 옆에 한 줄이 는다. newServer
쪽은 안 넣는다 — 승격을 보는 테스트는 store 를 손에 쥐어야 하고, 그것을
주는 헬퍼가 newServerFast 다.
3.4 promote 가 []string 을 내는 자리의 처리
훑는 쪽이 그 노드를 blocked 에 더한다 (business-logic-model.md §3.1).
승격을 못 한 경우와 오류를 가른다 — 못 한 것은 nil, nil 이고 정상이라
로그를 안 남긴다. 오류만 로그로 나가고 훑기는 계속된다.
3.5 깨우기를 응답보다 먼저 부른다
postResult · postCancel · postNodes 셋 다 write(...) 앞에서 wake 를
부른다. CP2 의 「a 가 끝난 뒤 runctl status <b> -> RUNNING」이 응답을 받은
직후에 성립해야 하기 때문이다. 뒤에 두면 게이트가 경쟁이 된다 (R13).
4. 단계
Step 1 — internal/store/store.go 에 LeaseTTL 필드
-
Store구조체에LeaseTTL time.Duration을MaxLeasesPerRun옆에 더한다 - 주석에 적는 것 셋 — 인자가 아니라 필드인 이유(
WakeQueued가 인자를
안 받고 부르는 자리 둘이 설정을 손에 안 쥔다) · 값이cfg.Lease.TTLSeconds
라는 것 ·0이면 승격을 안 하고 오류를 낸다는 것 -
time이 이미 임포트돼 있는지 확인한다
Step 2 — insertSteps 를 CreateRun 에서 뽑는다
동작을 안 바꾸는 단계다. 여기서 테스트가 하나라도 빨개지면 추출이
틀린 것이다.
-
func (s *Store) insertSteps(ctx, tx pgx.Tx, runID string, steps []contract.Step, nodeOf map[string]string) error를 만든다 -
CreateRun의for i, st := range steps블록을 통째로 옮긴다 —
st.Kind()검증 ·contract.NeedsOf(steps, i)정규화 ·nodeOf[st.Uses] -
CreateRun이 그것을 부르게 고친다.nodeOf조립은CreateRun에 남긴다 - 주석에 적는다 — 두 벌이 되면 제출 경로마다 다른 정규화가 걸린다는 것 (R5)
-
go test ./internal/store ./internal/api로 무변경을 확인한다
Step 3 — CreateQueuedRun
-
func (s *Store) CreateQueuedRun(ctx context.Context, r Run, steps []contract.Step) error - 트랜잭션 하나 —
INSERT INTO runs (run_id, state, principal, contract, work_id)
로assigned를 안 넣고(NULL),insertSteps(tx, r.RunID, steps, map[string]string{}),
Records.Open(r.RunID), 커밋 - 상태는
StateQueued를 함수 안에서 박는다 — 이름이 그것을 약속한다 -
work_id는nullable(r.Contract.Work.Key())로 제출 시점에 박는다 - 안 부르는 것 셋을 주석으로 남긴다 — 임대 발급 ·
runAcquires·
raiseAsks. 없다고 조용히 빠지면 다음에 읽는 사람이 빠뜨린 것인지
뺀 것인지 모른다 (R4) -
Records.Open이 커밋 전인 것을CreateRun과 같은 근거로 적는다
Step 4 — label 을 internal/store/list.go 로 옮긴다
-
internal/store/list.go에func Label(as []match.Assignment, adverts []contract.Advert) []Assigned를 놓는다.
본문은internal/api/api.go:849의label그대로다 - 주석은 그대로 옮기고 한 줄을 더한다 — 제출과 승격이 같은 조각을 써야
Record 의 노드 이름이 경로마다 안 갈린다는 것 (domain-entities.md§3.2) -
internal/api/api.go에서label을 지우고 부르는 자리 둘(dry-run 응답 ·
CreateRun의Assigned)을store.Label(...)로 바꾼다 -
internal/store/list.go가match를 이미 임포트하는 것을 확인한다 -
internal/api에서match임포트가 아직 필요한지 확인한다 —
match.Reject·match.Match·match.CodeAllBusy가 남으므로 남는다
Step 5 — UnavailableNodes
-
internal/store/queue.go에func (s *Store) UnavailableNodes(ctx context.Context) (map[string]bool, error) -
BusyNodes와DrainingNodes를 부르고 합집합을 낸다. 질의를 새로 안 짠다 - 주석 — 제출과 승격이 같은 것을 봐야 한다는 것. 제출은 draining 을 빼는데
승격은 안 뺀다면 대기하던 Run 이 회수 중인 노드로 간다 (FR-11 중앙 쪽)
Step 6 — promote (비공개)
-
func (s *Store) promote(ctx context.Context, q QueuedRun, adverts []contract.Advert, blocked map[string]bool) ([]string, error) -
match.Match(q.Contract.Requires, adverts, blocked)— 거절이면nil, nil.
로그를 안 남긴다. 아직 자리가 없는 것은 정상이다 - 노드를 중복 없이 모은다 — 한 노드가 두 역할을 맡으면 임대는 하나다
-
MaxLeasesPerRun초과면 경고만 남기고nil, nil—FAILED로 안
만든다. 승격 시점의 초과는 영구가 아니다 (R10). 근거를 주석에 적는다 - 트랜잭션 —
UPDATE runs SET state=$2, assigned=$3 WHERE run_id=$1 AND state=$4.
0행이면 롤백하고nil, nil(남이 먼저 옮겼거나 취소됐다. 둘 다 정상) -
not_after는time.Now().Add(s.LeaseTTL). 노드마다INSERT INTO leases
하고 기본키 충돌이면 롤백하고nil, nil(다음 훑기에 다시 본다) -
UPDATE steps SET node_id=$3 WHERE run_id=$1 AND uses=$2— 역할마다.
노드는a.Nodes[0].Node다 (CreateRun과 같은 자리,count>1은 아직) -
runAcquires(tx, runID)->raiseAsks(tx, runID)-> 커밋 ->PushAsks.
순서가CreateRun과 같다 (R12). 근거를 주석에 적는다 - 성공하면 쥔 노드를 돌려준다
Step 7 — WakeQueued
-
func (s *Store) WakeQueued(ctx context.Context) error—requirements.md
§4.1 이 고정한 시그니처 그대로다 -
LeaseTTL이0이하면 맨 먼저 오류를 낸다. 기동 시 한 번 부르는
자리가 있으므로 배선 누락이 첫 부팅에 드러난다 -
ListQueued— 비면 여기서 끝난다.LiveAdverts와UnavailableNodes는
훑기마다 한 번만 읽는다 -
q마다ctx.Err()를 보고,promote를 부르고, 오류는 로그만 남기고
다음으로 간다 (R7 실패 격리), 성공한 노드를blocked에 더한다 - 주석 — 머리에서 안 멈추는 이유(R6) ·
blocked를 손으로 갱신하는 이유
(질의가2N이 되지 않게) · 재진입 안전의 근거(조건부 갱신과 기본키 충돌)
Step 8 — Reap 이 실제로 놓았을 때만 깨운다
-
Reap안에 「무언가를 놓았다」를 세는 값을 하나 둔다 - 세는 자리 셋 —
ExpireAsks뒤의SettleIfDone이 상태를 냈을 때 ·
만료 임대 회수가n > 0일 때 · 종료 Run 의 임대DELETE가 행을
건드렸을 때(지금 버리고 있는 태그를 받는다) - 셋 중 하나라도 참이면
return직전에WakeQueued를 한 번 부르고
실패는 로그만 남긴다 - 주석 — 무조건 깨우면
RunReaper가RenewSeconds마다 도므로 사실상
주기 폴링이고 R14 를 깬다는 것. 셋째를 세는 이유(Mediator 가 죽어 있는
동안 끝난 Run 의 임대가 거기서 풀리고 그 경로에는postResult가 없다)
Step 9 — internal/store/queue_test.go (DB 필요)
business-rules.md §6.1 의 열하나를 그대로 낸다. 파일은 U-바닥이 만들었고
이어 붙인다. 이름과 실패 메시지는 영어다.
-
WakeQueued가 빈 대기열에서 아무것도 안 한다 -
WakeQueued가 매칭되는 하나를RUNNING으로 옮긴다 — 임대 ·
steps.node_id·assigned를 다 본다 -
WakeQueued가 도착 순서를 지킨다 — 같은 노드를 다투는 둘 중 앞이 간다 -
WakeQueued가 앞이 못 가도 뒤를 보낸다 — 다른 능력을 기다리는 둘 -
WakeQueued가 두 번 불려도 같다 (재진입) -
WakeQueued가 draining 노드로 안 보낸다 -
WakeQueued가LeaseTTL0 에서 오류를 낸다 -
promote가 폭 상한 초과에서 Run 을QUEUED로 둔다 -
CreateQueuedRun이 임대를 안 만들고steps를 전부PENDING·
node_id빈 문자열로 넣는다 -
CreateQueuedRun뒤Cancel이QUEUED를FAILED로 만들고 봉인한다 -
UnavailableNodes가 busy 와 draining 의 합집합이다 - 픽스처는 기존
scratchStore·insertNode·insertRun·insertLease
를 쓴다. 새 헬퍼를 두 벌로 안 만든다
Step 10 — internal/api/queue.go 신설
-
func (s *Server) enqueue(w http.ResponseWriter, r *http.Request, c contract.Contract, warnings []string)—
store.Run을StateQueued로 만들고CreateQueuedRun을 부르고202를 낸다 - 유일 제약에 걸리면 다시 읽어
200으로 답한다 —CreateRun이 같은
경우를 처리하는 방식 그대로다 (R3) - 본문은
s.view(ctx, &run)에Warnings를 실은 것이다.assigned·
reject·verdict는omitempty라 빠진다 (domain-entities.md§4) -
func (s *Server) wake(ctx context.Context, why string)—WakeQueued를
부르고 실패는 로그만 남긴다. 응답 코드도 본문도 안 바꾼다 (R16) -
wake주석에 동기인 근거 셋을 적는다 — 게이트 ·r.Context()· 비용 (R13)
Step 11 — internal/api/api.go 의 submit
-
if !dry안의BusyNodes를UnavailableNodes로 바꾼다.
dry갈래는 안 건드린다 (NFR-I6).match.go도 안 만진다 (NFR-I4) - 매처 거절 갈래 —
!dry && rej.Code == match.CodeAllBusy면enqueue로
가고, 나머지(422)는 지금처럼CreateRejectedRun뒤fail이다 - 폭 상한 갈래는 안 건드린다 —
t=0의 초과는 여전히422다 (R1) -
ErrNodeTaken갈래를enqueue로 바꾼다. 주석에 안전한 이유를 적는다 —
기본키 충돌이라 트랜잭션이 통째로 롤백된 뒤에 오고 남은 행이 없다 (R2) - Step 4 에서 지운
label의 호출 자리 둘이store.Label로 바뀌어 있는지 본다 - 제출 경로에
409를 내는 자리가 하나도 안 남았는지 센다
Step 12 — internal/api/api.go 의 깨우기 세 자리
-
postResult—rolled면 안 깨운다.SettleIfDone뒤에 빈 문자열이어도
깨운다(부분 반납이 바로 그 경우다).write앞에서 부른다 (R15 · 3.5절) -
postCancel—Cancel이 성공한 뒤,write앞 -
postNodes—FailRestarted가 Run 을 실제로 냈을 때만. 광고는
노드마다 주기로 오므로 무조건 깨우면 주기 폴링이 된다 (R14) - 세 자리 다
why를 다르게 준다 — 로그에서 어느 사건이 깨웠는지 갈린다 -
internal/store/release.go를 안 고쳤는지 확인한다. 이 유닛이 만지는
파일이 여섯에서 다섯으로 줄었다는 것이 그 확인이다 (business-logic-model.md§4.2)
Step 13 — cmd/mediator/main.go
-
openRecords의st.MaxLeasesPerRun옆에
st.LeaseTTL = time.Duration(cfg.Lease.TTLSeconds) * time.Second -
run()의migrate뒤 · reaper 를 띄우기 전에st.WakeQueued(ctx)
를 한 번 부르고, 실패해도 기동을 안 막는다 (R16) - 주석 —
QUEUED는 아무것도 안 쥐므로 재기동 후에도 참이고 회수할 것도
복구할 것도 없다는 것 (business-logic-model.md§5)
Step 14 — 기존 테스트의 409 기대를 고친다
3.2절의 일곱 자리다. 하나도 빠뜨리지 않는다.
-
internal/api/api_test.go의newServerFast에st.LeaseTTL한 줄 -
api_test.go:253·:312·:326·:1870의 기대를202로 -
list_test.go:191·:293의 기대를202로,:303의 필터를
state=QUEUED로 - 딸린 주석 넷을 함께 고친다 (
:249·:322·:1863·list_test.go:292) -
TestSubmitRace의switch에default: t.Fatalf를 남긴다 — 단언을
넓히지 않는다
Step 15 — internal/api/queue_test.go 신설 (DB 필요)
business-rules.md §6.1 의 API 쪽 일곱이다. newServerFast 를 쓴다.
-
409상황의 제출이202와state QUEUED를 낸다. 본문에assigned도
reject도 없다 -
422상황의 제출은 그대로422·FAILED -
QUEUEDRun 재제출이200+ 기존 Run 이다 (NFR-I7) - dry-run 응답이 안 바뀐다 — draining 노드가 있어도 (NFR-I6)
-
GET /v1/runs?state=QUEUED가 그 행을 낸다 -
postResult뒤에 대기 Run 이RUNNING이 된다 -
postCancel뒤에 대기 Run 이RUNNING이 된다 -
QUEUEDRun 의steps가 전부PENDING이고node가 빈 문자열이다
(FR-7 을 API 표면에서 본다)
Step 16 — CP2 를 실제로 돌린다
이 단계가 이 계획의 게이트다. 명령을 적는 것이 아니라 돌려서 초록을
확인한다. 값은 business-rules.md 7절이고 CP0 · CP1 의 줄을 함께 다시
돌린다 — 회귀를 여기서 잡는다.
-
go build ./... -
go vet ./... -
go test ./...— 스킵 0건.-json으로 센다 -
go run ./scripts/glyphscan.go -
golangci-lint run ./...— 새 파일과 고친 파일에 0건. 총계는 기준선 그대로 - 커버리지 게이트 —
.coverage-contract.yml의 명령 그대로. 패키지 16개
전부 하한 80%. 여유가 얇은 자리 둘이internal/api와internal/store
이고, 승격의 오류 갈래 넷(0행 · 기본키 충돌 · 폭 초과 ·LeaseTTL0)이
덮이면 여유가 생긴다 -
go test -v ./internal/boundary—2 checked, 2 skipped, 4 total.
새 패키지가 0 이므로 숫자가 바뀌면 안 된다 - 장면 —
runctl submit a.json->201·RUNNING - 장면 —
runctl submit b.json(같은 노드) ->202·QUEUED - 장면 —
runctl status <b>->QUEUED - 장면 — a 가 끝난 뒤
runctl status <b>->RUNNING - 장면 —
runctl submit c.json(없는 능력) ->422·FAILED - 라우트 18개 회귀 0 — mux 의
404 page not found가 하나도 없어야 한다 - 기동 경로 — Mediator 를
QUEUED행이 있는 DB 로 재기동하면 그 Run 이
RUNNING이 된다 (FR-10)
빨간 것이 남으면 U-drain 에 안 들어간다 (scene-gates.md 4절).
Step 17 — 화면 검증 S1
scene-gates.md 2.1절이 CP2 에 건 것이다. API 검증과 따로 적는다 —
API 로 통과되는 어휘는 화면 없이도 초록이 된다.
- S1 의
QUEUED칩이 실제 행을 낸다 (U-관측API 시점에는 빈 목록이었다) - 그 행이 어느 카드에도 안 얹혀 있다 —
QUEUED는 노드를 안 쥔다 - 승격되면 같은 Run 이 칩에서 빠지고 카드에 붙는다
- 화면 코드를 한 줄도 안 고쳤다 — 고쳤다면 이 유닛의 범위를 넘은 것이다
Step 18 — 문서
-
aidlc-docs/construction/u-queue/code/code-generation-summary.md하나 —
만든 파일 · 고친 파일 · CP2 결과 · 화면 검증 결과 · 뒤 유닛에 내준 접점 ·
계획에서 갈린 자리 -
aidlc-docs/aidlc-state.md의 Current Status 와 Phase Status 를 갱신한다 -
enode-design/을 안 고친다.design/도README.md도 안 고친다 -
business-rules.md5절의 열린 창 하나를 요약에 다시 적는다 —
사라졌다 돌아온 노드. U-drain 이 닫을 수 있는 자리라는 것까지
Step 19 — 해당 없음을 적어 둔다
규칙이 요구하는 단계 중 이 유닛에 없는 것을 이름으로 남긴다. 없다고 조용히
빠지면 다음에 읽는 사람이 빠뜨린 것인지 없는 것인지 모른다.
- Project Structure Setup — 해당 없음 (브라운필드. 구조가 이미 있다)
- Frontend Components — 해당 없음. 이 유닛에 화면이 없다. S1 의
QUEUED
칩은 U-관측API 가 이미 그렸고 이 유닛은 그 칩에 들어갈 행을 만든다 - Database Migration Scripts — 해당 없음 (1.4절 — 새 열이 0 이다)
- API Layer 신규 라우트 — 해당 없음. 라우트를 하나도 안 만든다.
202는 기존POST /v1/runs의 새 응답 코드다 - Deployment Artifacts — 해당 없음 (새 실행파일 없음 ·
packaging/무변경)
5. 실행 규칙
계획대로만 여기 없는 파일을 안 만든다. 여기 없는 함수를 안 더한다
제자리에서 고친다 사본을 안 만든다. _new · _modified 금지
체크박스는 즉시 단계를 끝낸 그 응답에서 [x] 로 바꾼다
상태 파일도 같이 aidlc-state.md 의 Current Status 를 함께 갱신한다
코드는 저장소 뿌리 aidlc-docs/ 아래에 코드 파일을 안 만든다
커밋은 안 한다 진행자만 커밋한다
design/ 무변경 진행자만 고친다 (START.md 5절)
enode-design/ 무변경 정본 개정은 이 저장소의 일이 아니다 (canon.md)
표기와 언어 — CONVENTIONS.md 다. 주석은 한국어, 에러 문자열과 로그와
테스트 이름과 실패 메시지는 영어다. 코드 어디에도 장식 문자를 안 넣는다.
순서가 뜻을 갖는다. Step 1 ~ 8 이 store 이고 10 ~ 13 이 api 와 기동이다.
store 가 먼저인 것은 api 가 그것을 부르기 때문이고, Step 9(store 테스트)를
api 보다 앞에 둔 것은 승격이 혼자서 옳은지를 먼저 확인하기 위해서다.
Step 14 를 15 보다 앞에 두는 것은 기존 테스트를 먼저 초록으로 만들어야
새 테스트의 빨강이 새것의 빨강이기 때문이다.
6. 보안 확장 대조 — 이 유닛에서
security-baseline 이 켜져 있다. 이 유닛은 새 표면을 하나도 안 연다 —
새 라우트 0 · 새 입력 파라미터 0 · 새 HTML 0 이다.
SECURITY-04 전송 헤더 N/A. 새 HTML 표면이 없다
SECURITY-05 입력 검증 N/A. 새 입력 파라미터가 없다. 제출 본문의 검증은
기존 c.Validate() 가 그대로 진다
SECURITY-07 세션 N/A
SECURITY-08 접근 제어 N/A. POST /v1/runs 가 기존 auth 미들웨어 뒤에 있고
이 유닛이 그 배치를 안 바꾼다
SECURITY-12 자격증명 N/A. 202 본문은 runView 이고 nonce 도 principal 도
안 싣는다 (기존 형태 그대로다)
SECURITY-15 오류 노출 걸린다. 새 오류 문구에 내부 경로 · 스택 · SQL 을
안 싣는다. 호출자에게 나가는 것은 202 와 기존
갈래의 문구뿐이고, 승격 실패는 호출자가 없는
자리라 로그로만 나간다
7. 안 만드는 것
READY 상태 NFR-I2. QUEUED 하나만 실제 값이 된다
대기열 테이블 runs.state 가 대기열이다
우선순위 · 정책 틀 조건은 도착 순서 하나다 (constraints.md 2절)
새 라우트 제출도 조회도 이미 있다
새 타입 LeaseGrant · Run · QueuedRun 을 그대로 쓴다
대기 상한 없다. Reap 이 QUEUED 를 안 쓸어간다는 것이 코드 사실이다
깨우기 일곱째 지점 FR-9 가 여섯으로 고정했다. 사라졌다 돌아온 노드의 창은
열어 두고 business-rules.md 5절에 적어 둔 그대로 둔다
match.go 의 변경 NFR-I4
runctl 의 변경 client.go:70 이 202 를 통과시키고 Terminal 이 QUEUED 를
종료로 안 본다. 읽고 확인했다
release.go 의 변경 applyRelease 는 ReportStep 의 트랜잭션 안이다
store 의 새 공개 표면 Label · CreateQueuedRun · UnavailableNodes · WakeQueued
넷뿐이다. nonce 를 안 내보낸다 (3.1절)
화면의 변경 한 줄도. U-관측API 가 그린 칩이 이 유닛의 행을 받는다