jinwon-int

nclex-a2a-content-pipeline

Drive a jinwon-int/nclex content PR through the full narrow-gate A2A pipeline — terminology lane dispatch, result-preservation rerun on verdict fail, coverage review projection, CI recovery (stale base.sha), content 2-lane dispatch with keyring-trusted worker routing, signed receipt posting, and a2a/receipts verification. Includes the sanctioned task watcher (watch-task.sh, #1389) for polling broker tasks with the correct terminal vocabulary. Use when authoring or shepherding an NCLEX 출제 PR (new_case/rewrite/remediation), when a terminology or content lane must be dispatched, when a lane returns review_verdict_failed, when polling an A2A lane task, or when receipts must be projected to the PR. 확립 2026-08-13 RRP-003(#249)·PA-006(#251) 2회 완주.

jinwon-int 0 Updated 2w ago

Resources

1
GitHub

Install

npx skillscat add jinwon-int/ccc-node/nclex-a2a-content-pipeline

Install via the SkillsCat registry.

SKILL.md

nclex A2A content pipeline (좁은 문 규율)

콘텐츠 PR은 A2A exact-head PASS 전 머지 금지. 전체 흐름:
출제 PR → ① terminology 레인 → 검토 기록 커밋 → ② CI green(자동 요청서) →
③ content 2-lane → ④ receipt 게시 → a2a/receipts green → 비작성자 승인·머지.

머지·A2A 디스패치·크로스계정 승인은 각각 사용자 승인을 받는다.

0. 전제

  • 로컬 클론(예: /root/work/nclex), 디스패치 작업장 /root/work/nclex-dispatch/
    (기존 manifest들이 템플릿 — manifest-pr249-*.json, manifest-pr251-*.json).
  • 디스패치 CLI: a2a-nexus 체크아웃의 scripts/a2a-dispatch-round.mjs
    (--manifest F --dry-run → --verify). edge secret은 브로커가 있는 노드의
    로컬 env에서만 읽는다
    — T1(로컬 브로커)=브로커 노드 env의
    BROKER_EDGE_SECRET(로컬 터널 http://127.0.0.1:<port>), T2(원격 브로커)=브로커
    호스트 a2a-nexus 체크아웃 packages/broker/.env의 EDGE_SECRET(브로커
    호스트에서 SSH로 실행
    , 값 이동·출력 금지).
  • 신뢰 워커는 keyring refs/a2a-public-keyring.json 등록 노드만 — 레인별 등록
    목록은 키링 파일이 정본이고 저자 노드는 구조적 제척. 미등록 노드의 PASS는
    a2a/receipts에서 무효
    — 디스패치 전 등록 여부를 확인한다.
  • PR 본문에 기계 판독 8필드 필수(값=머지된 claim 레코드와 정확 일치):
    - TASK_ID/TASK_KIND/AUTHOR_NODE/TARGET_IDS/SOURCE_PACKET/REFS_MANIFEST_SHA256/BASE_SHA/RISK_CLASS.

1. 해시 규약 (자가검증 완료)

  • targetContentHash = sha256(RFC8785-JCS 정규화한 케이스 객체).
    카탈로그 로드 순서는 items.js 먼저, cases- 정렬* (tools/commercial_readiness.js dataFiles).
  • diffHash = sha256(git diff <baseSha> <headSha> 바이트).
  • intentHash(terminology) = sha256(intentContract에서 intentHash 제외,
    json sort_keys+compact, ensure_ascii=False).
  • content 레인의 intentHash는 CI 자동 요청서 값을 그대로 사용
    (PR 본문 8필드 블록의 sha256 — gate a2a-dispatch job이 게시).
  • facts 생성기 스크립트 원형: 세션 기록 /root/work/nclex-dispatch/ 참고
    (facts*.json을 만든 node 스크립트 — fingerprint·diffHash·changedRecords 75쌍 추출).

2. terminology 레인 (ko_coverage 신규쌍 해소의 유일 경로)

  1. 신규 용어 후보(coverage 레코드 candidates)를 terms.json에 KMA 근거로 먼저 등록
    → node tools/ko_coverage.js --build → 후보 0 확인 → 커밋.
  2. manifest: 스키마 nclex.terminology-bilingual-review.v1, lane
    terminology_bilingual, reviewer는 저자 아닌 keyring 등록 신뢰 워커,
    sourceBundle.files[0].content에 계약+changedRecords(75쌍)+relevantRules+
    machineGate 내장. terminalBrief.notificationOwnership 필수.
  3. dry-run → 디스패치 → 브로커 감시는 스킬 동봉 워처 watch-task.sh(같은
    디렉터리)로 한다. 즉석 폴링 스크립트 작성 금지 — #1389 사고(워처가
    completed|cancelled 등 존재하지 않는 상태를 기다려 38회 state=succeeded
    로그를 남기고도 타임아웃, 결과 파일 미저장)의 재발 방지 계약:
    • 종료 상태는 브로커 TaskStatus 어휘 그대로 succeeded|failed|canceled
      (비종료: blocked|queued|claimed|running; 원전 a2a-nexus
      packages/broker/src/core/broker-status-predicates.ts).
    • 첫 폴링에서 실측 status를 출력해 어휘를 검증한다.
    • 어휘 밖 상태가 3회 연속 관측되면 unknown_state로 즉시 종료·결과 저장
      (성공을 놓치는 것보다 오탐 종료가 안전).
    • 모든 종료 경로(타임아웃·네트워크 실패 포함)에 결과 파일을 쓴다 —
      결과 파일이 없으면 워처는 끝까지 살아 있었던 것이 아니다.
    • secret은 --secret-file(argv·출력 노출 없음, env 스타일 파일은
      FILE:VAR_NAME 추출)로만 전달한다.
      예: watch-task.sh --broker "$BROKER_URL" --task-id "$LANE_ID" \ --requester-id <manifest requester.id> --requester-role <requester.role> \ --secret-file <env-file>:BROKER_EDGE_SECRET \ --out /root/nclex-dispatch/watch-<lane>.json
  4. review_verdict_failed이면 먼저 GET /tasks/:id 와 /tasks/:id/diagnostics를 본다.
    (review_verdict_failed는 status가 아니라 result/error의 코드다 — 그때
    status는 failed로 온다. 상태 어휘와 판정 코드를 혼합하지 않는 것도 #1389 교훈.)
    sameSourceRedispatch.action=skip 이거나 negativeVerdictEvidence에
    소견(findings/note)이 있으면 같은 소스 diagnostic/결과보존 재생을 하지 않는다
    (#1815 item 5, #1878 + redispatch classifier). merge gate는 그대로 fail-closed.
    증거가 비었거나 generic ack일 때만 review: {required:false} 재생으로 회수.
    회수 후 수정(수정 세대 1회) → 새 head로 정식 레인 재디스패치.
  5. PASS 후 coverage.json에 review 객체 투영(각 레코드 stage=reviewed +
    review{reviewer:"a2a:<node>", reviewed_at, note(태스크 id·resultHash 인용), evidence:["kuksiwon-item-writing-2018:…", "kma-terminology-6x:search=<개념>"…]})
    → node tools/ko_coverage.js --check --base origin/main PASS 확인 → 커밋·푸시.

3. CI 함정 2개

  • base.sha 고착: pull_request 이벤트의 base.sha가 PR 생성 시점에 고정되어
    content_process 경로 검증이 오탐하면 gh api -X PUT repos/<r>/pulls/<n>/update-branch로 base 갱신(merge 커밋 추가, 콘텐츠 불변).
  • gh run rerun은 옛 이벤트를 재생한다(수정된 PR 본문·base 미반영) —
    본문 수정 후에는 빈 커밋 푸시 또는 update-branch로 새 이벤트를 만든다.

4. content 2-lane

  1. CI green이면 gate가 자동 요청서(A2A-EVALUATION-REQUEST 코멘트) 게시 —
    head FROZEN 선언·diffHash·intentHash·레인 목록(standard 2 / high 3). 이 값이 정본.
  2. packet: 스키마 nclex.content-pr-review.v1 — contract(요청서 값+reviewerNodeId)
    • intentContract(요청서 intentHash) + currentTargets(전체 케이스 객체) +
      structuredEvidence + evidenceExcerpts(원문 인용) + machineGate +
      terminologyReview(서명 태스크 참조) + exactHeadTextIntegrity(파일 sha256).
  3. 라우팅: reviewer가 T2면 manifest를 브로커 호스트로 scp → 브로커 호스트에서
    CLI 실행, T1이면 로컬 실행. 레인별 reviewer는 서로 다르고 저자 제척·keyring 등록 필수.
  4. 핸들러 크래시(handler_exit_nonzero)는 판정이 아니다 — 동일 packet 재시도 1회,
    반복 시 다른 신뢰 워커로 재라우팅. 워커가 클레임을 안 하면 워커 노드에서
    a2a-hermes-worker 저널 확인(취소 태스크 zombie heartbeat면 서비스 재시작 —
    fleet-service 자율 범위).

4-1. review lineage 부착 (T2, a2a-nexus #2362 · #2274 scorecard)

T2 레인은 역할마다 lineage 1개에 묶는다(PR×역할). lineage는 리뷰어 1명의
흐름이라 같은 head의 역할 레인끼리 공유하면 첫 보고만 반영되고 나머지는
report_out_of_state로 거부된다. 도구는 a2a-nexus main의
scripts/lib/nclex-lineage-spec.mjs(#2363)와 scripts/lib/review-lineage-client.mjs.
브로커 호스트에서 실행(edge secret은 A2A_EDGE_SECRET env로만, 0절 규칙 동일)하고,
그 호스트의 nclex 체크아웃에 base·head 커밋을 fetch해 --repo로 준다.

  • shallow 사본은 --repo로 쓸 수 없다 — git -C <체크아웃> rev-parse --is-shallow-repository가 true면 full clone 체크아웃을 쓴다.
  • PR head는 브랜치명이 아니라 git -C <체크아웃> fetch origin pull/<n>/head로 받는다(받은 뒤 git -C <체크아웃> rev-parse FETCH_HEAD가 요청서 headSha와 같은지 확인).
  1. 첫 head 디스패치 전, 레인 역할마다:
    node scripts/lib/nclex-lineage-spec.mjs --pr <n> --role <content_clinical|evidence_adversarial|high_risk_safety> \
      --base <요청서 baseSha> --head <요청서 headSha> --body-file <PR 본문> --repo <nclex 체크아웃> \
      --broker-url <T2 broker> --requester-id <manifest requester.id> --out spec-pr<n>-<role>.json
    A2A_EDGE_SECRET=... node scripts/lib/review-lineage-client.mjs create --spec spec-pr<n>-<role>.json \
      --out lineage-pr<n>-<role>.json --dry-run   # 확인 후 --dry-run 제거(HTTP 201 applied)
    lineageId는 nclex-pr<n>-<role>로 결정적이다 — 이미 있으면 새로 만들지 않고 기존 record를 쓴다.
  2. manifest의 해당 역할 레인에 "reviewLineageRecord": "lineage-pr<n>-<role>.json"을
    적는다(상대 경로는 manifest 디렉터리 기준, 레인 1개 = record 1개). a2a-dispatch-round.mjs --dry-run이 binding 오류를
    provider 호출 전에 막는다.
  3. 저자 수정으로 새 head가 오면 lineage를 새로 만들지 않는다:
    review-lineage-client.mjs correct --record lineage-pr<n>-<role>.json --generation-ref <수정 ref> --head <new head> --repo <nclex 체크아웃> 후 재리뷰 레인에 같은 record를 적는다.
  4. bind 금지: 같은 (PR, head, 역할)에서 review-report가 이미 나간 뒤의 재실행
    (결과 보존 재실행 등). 다시 bind하면 reviewer run이 부풀어 #2274 예산 데이터가
    왜곡된다 — 그 레인은 reviewLineageRecord 없이 보낸다. 핸들러 크래시처럼 보고가
    없었던 재시도는 같은 record로 bind해도 된다.
  5. 확인: 워커 로그 "event":"review_lineage_report"(outcome reported 정상,
    skipped|rejected|failed는 운영 노드의 일일 lineage 재측정이 알림) · GET /review-lineages/<lineageId>.
    lineage는 관측(record mode) 전용이라 a2a/receipts·머지 판정에 영향이 없다.

5. receipt 게시와 마감

  1. 각 PASS 태스크의 전체 JSON에서 receipt 구성:
    {task_id, head_sha, signed_head_prefix(=head 전체), lane, reviewer_node, author_node, result(<브로커 result 원본, provenance 포함>)}.

  2. PR 코멘트에 ```json 펜스로 게시 → a2a-receipts 워크플로가 keyring 검증 후
    commit status a2a/receipts를 exact-head에 투영. 확인:
    gh api repos/<r>/commits/<head>/status.

  3. success: "signed receipts complete: N lane(s), N reviewer(s), exact-head"
    확인 후 머지는 별도 사용자 승인. main에 머지 큐가 활성돼 있어(ruleset
    merge-queue-main, 2026-09-13) 승인 후 enqueue로 착지한다 — gh pr merge --squash와 relay merge helper는 "The merge strategy for main is set by the
    merge queue"로 거부된다:

    pr_id="$(gh pr view <n> --repo <owner>/<repo> --json id --jq .id)"
    gh api graphql \
      -f query='mutation($id:ID!){enqueuePullRequest(input:{pullRequestId:$id}){clientMutationId}}' \
      -f id="$pr_id"
    gh pr view <n> --repo <owner>/<repo> --json state   # MERGED 폴링(실패 시 큐에서 축출)

    a2a/receipts는 group head에도 재투영된다(a2a-receipts.yml merge_group 분기,
    #535) — 큐가 이를 기다린 뒤 squash 착지. exact-head 원칙 불변: 승인 시점 head ==
    enqueue 시점 head(그 사이 푸시되면 재승인). 인프라 경로(.github 등)는 gate가
    아니라 ruleset이 관리하며 실패 시 ruleset 삭제가 롤백이다.

안전 규칙

  • edge secret·토큰 값 출력/복사/이동 금지. 원격 브로커 작업은 그 노드에서 실행.
  • head 동결 후 sync push 금지(모든 PASS 무효). 리베이스는 디스패치 전에만.
  • 이 파이프라인은 GitHub 머지 승인·면허 RN 검토를 대체하지 않으며
    source.verified=false·paid-pool 자격 불변.