From 81554b7256f4db2fc78dcb4ed78a3e7006a06c63 Mon Sep 17 00:00:00 2001 From: pureliture Date: Sun, 21 Jun 2026 22:01:10 +0900 Subject: [PATCH 1/7] =?UTF-8?q?salvage(admin-ux-catalog):=20codex=20worktr?= =?UTF-8?q?ee=20=EB=AF=B8=EC=BB=A4=EB=B0=8B=20docs=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?=EB=B3=B4=EC=A1=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 --- docs/README.md | 6 +++-- docs/_harness/doc-map.yml | 28 ++++++++++++++++++++++ docs/views/product-catalog.md | 45 +++++++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 docs/views/product-catalog.md diff --git a/docs/README.md b/docs/README.md index dc395d8..8bd393f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -26,8 +26,9 @@ 3. [소스 스캔 결과 NoSQL Schema](views/source-scan-results-nosql-schema.md) 4. [탐지 결과와 지표](views/secret-detection-results-and-metrics.md) 5. [Local Scan Orchestration 구조](views/local-scan-orchestration-target-architecture.md) -6. [시작하기](views/getting-started.md) -7. [progress dashboard](dashboards/progress.html) +6. [제품 카탈로그](views/product-catalog.md) +7. [시작하기](views/getting-started.md) +8. [progress dashboard](dashboards/progress.html) ## 공개 후보 문서 @@ -38,6 +39,7 @@ | [source-scan-results-nosql-schema.md](views/source-scan-results-nosql-schema.md) | 스캔 결과를 어떻게 저장하고 어떤 질문에 답하려는지 | | [secret-detection-results-and-metrics.md](views/secret-detection-results-and-metrics.md) | 어떤 결과를 공개할 수 있고, 지표를 어떻게 해석하는지 | | [local-scan-orchestration-target-architecture.md](views/local-scan-orchestration-target-architecture.md) | Local Scan Orchestration의 Python file 참조 관계와 runtime call chain | +| [product-catalog.md](views/product-catalog.md) | 관리자 UX와 운영자 시나리오 관점에서 현재 제품 표면과 미완성 gap | | [operations-transition-architecture.md](views/operations-transition-architecture.md) | 로컬 실행 경로에서 반복 실행과 확장 adapter로 넘어가는 기준 | | [research-and-technical-decisions.md](views/research-and-technical-decisions.md) | 왜 Gitleaks-first인지, 다른 도구는 어떤 위치인지 | | [getting-started.md](views/getting-started.md) | 이미 클론된 로컬 저장소로 첫 스캔을 끝까지 돌리는 절차 | diff --git a/docs/_harness/doc-map.yml b/docs/_harness/doc-map.yml index 85f9af9..790d9fb 100644 --- a/docs/_harness/doc-map.yml +++ b/docs/_harness/doc-map.yml @@ -116,6 +116,34 @@ human_views: - docs/assets/local-scan-target-runtime-flow.drawio - docs/assets/local-scan-target-scenario-seams.svg - docs/assets/local-scan-target-scenario-seams.drawio + - view_id: product-catalog + title: 제품 카탈로그 + path: docs/views/product-catalog.md + output_path: confluence://프로젝트-개요-및-추진-전략/제품-카탈로그 + generated_by: human_doc_curator + generated_at: 2026-06-21 + safety_class: public + source_docs: + - src/security_scanner/runtime/read_api.py + - src/security_scanner/cli/commands/read_api.py + - src/security_scanner/cli/commands/scan.py + - src/security_scanner/cli/commands/scan_health.py + - src/security_scanner/cli/commands/disposition.py + - tests/test_read_api.py + - tests/test_queue_status.py + - tests/test_cli_disposition.py + - docs/workbench/context/legacy-source/08-milestones.md + source_hashes: + src/security_scanner/runtime/read_api.py: 3cfc5536fc34e2412506a09ef53a985a09e63e2debe1f34eccbce4353b5f0a2f + src/security_scanner/cli/commands/read_api.py: 83f77aa0223619f00de6ccc82ecc7de70dc94d0016f492ba2f8701cc6ea9aa68 + src/security_scanner/cli/commands/scan.py: 2d03278acbb08dd56a0c79c7e390cb2375a7b41dbefde777ee7c4e79bdc274cd + src/security_scanner/cli/commands/scan_health.py: 255b6243d2f05a4b6c59e0f5056b0b27f1db15e52ac0d77219a97586d3621b61 + src/security_scanner/cli/commands/disposition.py: 200a9198f8bcf8d7c80afc77e103bb22bdcc4d1169441d5451375ce0caf36bb5 + tests/test_read_api.py: ac327e19be8dec2f525701a54115d74d707e2c553f768b31ca4e0b6663a840eb + tests/test_queue_status.py: 448b89ce7d8aba6abfa9a71206aba827814087b3b2c3c0d5413c11104ce82fc3 + tests/test_cli_disposition.py: 7c9027b821521b3336e2c0da4ce0c2bb498a2e80e038d2b4e137070f0998225e + docs/workbench/context/legacy-source/08-milestones.md: 121170acde8ccdb4ca8ba50e22faf7ae5ecd79c33b76294ba852e1e7a1675c1b + assets: [] - view_id: operations-transition-architecture title: 확장 경로와 운영 기준 path: docs/views/operations-transition-architecture.md diff --git a/docs/views/product-catalog.md b/docs/views/product-catalog.md new file mode 100644 index 0000000..8bc2cac --- /dev/null +++ b/docs/views/product-catalog.md @@ -0,0 +1,45 @@ +# 제품 카탈로그 + +`security-scanner`는 local-first Secret Detection 파이프라인을 운영자가 반복 실행, 관찰, 분류할 수 있게 만드는 제품입니다. 현재 제품 표면은 CLI와 read API 중심이며, 전체 관리자 콘솔보다는 dashboard data contract와 운영 판단 신호를 먼저 안정화하고 있습니다. + +## 관리자 UX/운영자 시나리오 + +관리자 UX의 목적은 "스캔이 돌았는가"를 넘어서, 어느 대상이 빠졌고, 어떤 finding이 아직 판단되지 않았으며, 운영자가 지금 어떤 조치를 해야 하는지 알려주는 것입니다. 현재 구현은 health, coverage, backlog, freshness, disposition-filtered findings를 읽는 기반을 제공합니다. 다음 단계는 그 신호를 ack, suppress, assign, dispatch, drilldown, RBAC가 있는 action UX로 묶는 일입니다. + +### 사용자 여정 단계 + +| 단계 | 운영자가 확인하는 질문 | 필요한 화면/정보 | 현재 상태 | +| --- | --- | --- | --- | +| 1. 범위 확인 | 어떤 repository가 관리 대상이고, 무엇이 빠졌는가? | 전체 대상 수, 포함/제외 수, covered count, coverage gap | `read-api` coverage panel이 org total, included, excluded, covered, gap을 제공합니다. | +| 2. 신선도 판단 | 최근 스캔이 SLA 안에 들어왔는가? | incremental/baseline breach count, evaluated time, repo별 stale signal | `scan-health`, `freshness-eval`, `read-api` freshness rollup이 집계 신호를 제공합니다. | +| 3. 작업량 파악 | 워커가 밀렸는가, 실패가 누적되는가? | pending, leased, completed, dead-letter, expired lease, backlog 추세 | `queue-status`와 `read-api` backlog panel이 queue 상태를 제공합니다. | +| 4. finding 분류 | 무엇이 verified, false positive, unreviewed인가? | disposition filter, severity, confidence, rule, repo, redacted location, secret hash | `read-api --include-findings --disposition ...`가 public-safe finding DTO를 제공합니다. | +| 5. 조치 기록 | 사람이 어떤 판단을 남겼는가? | true positive / false positive 판정, reason, actor/source, audit event | `disposition set`이 human/manual 판정을 `FINDING_STATE`와 `STATE_EVENT`에 기록합니다. | +| 6. 후속 작업 연결 | 누가 rotation, suppression, exception review를 맡는가? | assignee, due date, acknowledgement, dispatch target, reopen rule | 아직 제품 UX로 묶이지 않았습니다. | + +### 현재 UI 기능 + +- **Dashboard/read API**: `read-api`는 한 번의 dashboard refresh payload로 freshness, coverage, backlog를 반환하고, 요청 시 disposition-filtered findings를 포함합니다. +- **Health와 freshness**: `scan-health`는 stale repo가 있으면 fail-closed gate로 동작합니다. `freshness-eval`은 freshness breach를 평가하고 `BREACH_COUNTER` rollup을 materialize합니다. +- **Coverage**: catalog와 repo health를 비교해 포함 대상 중 아직 스캔되지 않은 coverage gap을 보여줍니다. +- **Queue 상태**: `queue-status`는 pending, leased, completed, dead-letter와 expired lease count를 사람이 읽는 형식으로 보여줍니다. read API backlog panel은 poll-friendly count를 제공합니다. +- **Finding disposition**: findings DTO는 raw secret과 scanner match를 노출하지 않고 repo, rule, severity, confidence, status, disposition, redacted location, salted secret hash만 제공합니다. `disposition set`은 human/manual true positive 또는 false positive 판정을 기록합니다. +- **Alert dispatch substrate**: freshness evaluator는 notification-log sink와 de-dup/re-notify window를 갖춘 alert dispatcher를 경유합니다. 실제 Slack, email, webhook 같은 delivery channel은 deploy-gated입니다. + +### 미완성 UX gap + +| Gap | 현재 한계 | 카탈로그상 의미 | +| --- | --- | --- | +| Action UX | read API는 상태를 보여주지만, dashboard 안에서 ack, suppress, reopen, bulk action을 수행하는 화면은 없습니다. | "관찰 가능" 단계이며 "운영 워크벤치"는 다음 제품 slice입니다. | +| Ack | alert나 stale 상태를 사람이 확인했다는 별도 acknowledgement state가 없습니다. | 반복 알림 억제는 시간 기반 de-dup이고, 사람의 확인 행위와 분리되어 있지 않습니다. | +| Suppress | false positive disposition과 match-key suppression 기반은 있으나, 만료일, 범위, 승인자, 사유 템플릿을 가진 suppression policy UX는 없습니다. | 억제 메커니즘은 존재하지만 exception management UX는 미완성입니다. | +| Dispatch | notification-log sink와 dispatcher seam은 있으나, 실제 외부 채널 routing, escalation, delivery status 화면은 deploy-gated입니다. | 알림 연결 지점은 준비됐고, 운영 채널 제품화는 별도 결정이 필요합니다. | +| Assign | finding이나 stale repo를 담당자에게 배정하는 assignee, due date, ownership model이 없습니다. | 개인 작업 관리가 아니라 scanner state 관찰에 머물러 있습니다. | +| Drilldown | public-safe finding row는 제공하지만, repo history, scan run, lifecycle event, queue job을 한 화면에서 연결하는 상세 화면은 없습니다. | 원인 추적은 CLI와 저장소 조회 조합에 의존합니다. | +| RBAC/Auth | read API는 loopback 기본값과 non-loopback auth gate만 검증합니다. 사용자, 역할, 권한, audit session 모델은 없습니다. | co-located admin UI 전제의 read-only contract이며 multi-user admin console은 아닙니다. | + +### 카탈로그용 concise copy + +관리자 UX는 local-first scanner를 운영자가 믿고 반복 실행할 수 있게 하는 관찰·판단 계층입니다. 현재 제품은 dashboard/read API로 freshness, coverage, queue backlog, disposition-filtered findings를 public-safe JSON으로 제공하고, `scan-health`, `freshness-eval`, `queue-status`, `disposition set`으로 운영자가 stale 상태와 queue 정체, human triage를 확인할 수 있게 합니다. + +아직 완성된 관리자 콘솔은 아닙니다. 다음 UX slice는 read-only dashboard를 action workspace로 확장해 ack, scoped suppression, alert dispatch, assignee, drilldown, RBAC를 연결하는 것입니다. 이 전환이 끝나면 운영자는 단순히 "문제가 있다"를 보는 데서 멈추지 않고, 누가 무엇을 판단했고 다음 조치가 어디에 걸려 있는지 한 화면에서 확인할 수 있습니다. From 8798ac51be2db5dd9fbb61087c8e69896630cf72 Mon Sep 17 00:00:00 2001 From: pureliture Date: Sun, 21 Jun 2026 22:01:10 +0900 Subject: [PATCH 2/7] =?UTF-8?q?salvage(finding-lifecycle-catalog):=20codex?= =?UTF-8?q?=20worktree=20=EB=AF=B8=EC=BB=A4=EB=B0=8B=20docs=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20=EB=B3=B4=EC=A1=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 --- .../secret-detection-results-and-metrics.md | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/docs/views/secret-detection-results-and-metrics.md b/docs/views/secret-detection-results-and-metrics.md index eec65f4..10dc761 100644 --- a/docs/views/secret-detection-results-and-metrics.md +++ b/docs/views/secret-detection-results-and-metrics.md @@ -32,6 +32,64 @@ Verifier는 detector가 아닙니다. Finding을 지우지 않고 triage 상태만 보조해야 합니다. Timeout, malformed response, low confidence는 모두 review-needed로 남깁니다. +## Finding lifecycle / triage / quality loop + +이 섹션은 scanner 후보가 triage state, verifier verdict, report/gate, GHAS parity까지 어떻게 이동하는지 설명합니다. 공개 문서이므로 실제 repository 이름, 실제 alert, 실제 secret, private path는 쓰지 않습니다. + +### Lifecycle flow + +Secret Detection 기본 경로는 `Secret Finding`입니다. + +1. Scanner가 후보를 찾으면 runtime은 raw scanner output을 canonical `Finding`으로 바꿉니다. 기본 상태는 `status=OPEN`, `triage.verdict=NEEDS_REVIEW`, `disposition=unreviewed`입니다. +2. 저장소는 finding identity와 scan-run observation을 분리합니다. 같은 finding이 다시 보이면 observation은 새로 남기되, 기존 `FINDING_STATE`의 manual/verifier triage는 덮어쓰지 않습니다. +3. Verifier는 redacted metadata만 보고 `TRUE_POSITIVE`, `FALSE_POSITIVE`, `NEEDS_REVIEW` 중 하나를 제안합니다. Invalid JSON, timeout, low confidence, unknown label은 `NEEDS_REVIEW`로 남깁니다. +4. Terminal verifier verdict만 durable disposition으로 기록합니다. `FALSE_POSITIVE`는 `status=FALSE_POSITIVE`로 내려가고 gate에서 빠집니다. `TRUE_POSITIVE`는 `triage.verdict=TRUE_POSITIVE`로 남으며 `status=OPEN`이면 계속 blocking signal입니다. `NEEDS_REVIEW`는 terminal verdict가 아니므로 disposition row를 쓰지 않습니다. +5. Manual triage도 같은 disposition channel을 사용합니다. 사람은 `true_positive` 또는 `false_positive`를 기록할 수 있고, 기록은 append-only state event와 global `FINDING_STATE`에 반영됩니다. +6. 재스캔 때는 direct `finding_id`와 line-move tolerant `match_key`를 확인합니다. 이미 `FALSE_POSITIVE`, `RESOLVED`, `IGNORED` 같은 non-blocking terminal state가 있으면 재검증을 줄입니다. Lookup 실패는 fail-safe로 처리해 finding을 숨기지 않습니다. +7. Report는 저장된 finding snapshot에 lifecycle state를 overlay한 뒤 triage verdict와 status count를 보여줍니다. Gate는 `status=OPEN`이고 verdict가 `NEEDS_REVIEW` 또는 `TRUE_POSITIVE`인 항목을 blocking으로 계산합니다. +8. GHAS parity는 별도 품질 측정 loop입니다. GHAS Secret Scanning alert을 read-only reference로 보고, local `Finding`과 canonical secret type, file, tolerated line window로 비교해 precision/recall과 gap bucket을 계산합니다. GHAS alert을 mutation하거나 scanner lifecycle state로 직접 승격하지 않습니다. + +`VulnerabilityFinding`은 별도 제품 경로입니다. SARIF-native SAST output은 `Finding`이 아니라 `VulnerabilityFinding`으로 normalize됩니다. Secret hash, Gitleaks payload, `Finding.disposition`을 쓰지 않고, SARIF rule/location/trace metadata와 `triage_state`, `verifier_verdict`를 사용합니다. Code vulnerability gate는 severity와 precision threshold를 보고, `triage_state=FALSE_POSITIVE`만 blocking에서 제외합니다. + +### State 용어 사전 + +| 용어 | 적용 대상 | 의미 | Gate 영향 | +| --- | --- | --- | --- | +| `NEEDS_REVIEW` | `Finding.triage.verdict`, `VulnerabilityFinding.triage_state` | 아직 사람 또는 verifier가 확정하지 못한 상태 | Secret은 `OPEN`이면 blocking, code-vuln은 severity/precision 조건을 만족하면 blocking | +| `TRUE_POSITIVE` | `Finding.triage.verdict`, verifier verdict | 실제 signal로 보는 terminal verdict | Secret은 `OPEN`이면 blocking | +| `FALSE_POSITIVE` | Secret/Vulnerability triage | 노이즈로 확정한 terminal verdict | Secret과 code-vuln 모두 blocking에서 제외 | +| `disposition` | Secret `Finding` read/dashboard view | `triage.verdict`에서 파생되는 표시값: `verified`, `false_positive`, `unreviewed` | 독립 저장값이 아니며 gate는 canonical status/verdict로 계산 | +| `verified` | Secret dashboard disposition | `TRUE_POSITIVE`의 표시 이름 | 안전하다는 뜻이 아니라 확인된 signal이라는 뜻 | +| `OPEN` | Secret `Finding.status` | 아직 release/gate 관점에서 닫히지 않은 상태 | `NEEDS_REVIEW` 또는 `TRUE_POSITIVE`와 만나면 blocking | +| `RESOLVED`, `IGNORED` | Secret `Finding.status` | non-blocking terminal state | 재검증 억제와 gate 제외에 사용 | +| `triage_state` | `VulnerabilityFinding` | code-vuln 전용 triage 상태 | `FALSE_POSITIVE`가 아니면 severity/precision gate 대상 | +| `GHAS parity` | Secret quality loop | GHAS alert 대비 local scanner 결과의 precision/recall 비교 | Lifecycle state를 직접 바꾸지 않는 read-only 품질 신호 | +| `drift` | Quality monitoring | GHAS-calibrated 품질 기준과 non-GHAS sample 분포가 멀어지는 현상 | SLO가 아니라 조기 경보로 취급 | + +### Quality loop copy + +품질 루프의 목표는 "찾은 후보를 줄이는 것"이 아니라 "recall을 낮추지 않으면서 사람이 봐야 할 후보를 더 잘 분류하는 것"입니다. + +- Scanner는 후보 생성 책임을 가집니다. Secret Detection은 Gitleaks-first이고, code vulnerability는 opt-in SARIF-native 경로입니다. +- Normalizer는 도구별 output을 제품 모델로 바꿉니다. Secret은 `Finding`, SAST는 `VulnerabilityFinding`으로 분리합니다. +- Verifier는 삭제 권한이 없습니다. Redacted metadata만 보고 triage verdict를 보조하며, 애매하면 `NEEDS_REVIEW`로 남깁니다. +- Disposition은 terminal decision을 재사용하기 위한 layer입니다. Secret false positive는 같은 finding 또는 line-moved same-secret 후보에서 재검증을 줄일 수 있습니다. +- Report와 gate는 저장된 결과를 다시 읽어 재현 가능해야 합니다. Secret gate는 unresolved signal을 막고, code-vuln gate는 severity/precision 기준의 blocking count를 봅니다. +- Synthetic evaluation은 precision, recall, false negative를 고정합니다. False positive reduction은 verifier나 policy가 triage 비용을 줄였는지 보는 보조 지표입니다. +- GHAS parity는 Secret Detection의 외부 기준선입니다. `secret_type`과 `rule_id`를 canonical type으로 맞추고, line tolerance와 state-aware truth filter로 local-only, GHAS-only, type-unmatched gap을 나눕니다. +- Drift는 GHAS가 없는 대상에 품질 머신을 적용할 때의 안전장치입니다. GHAS parity SLO를 대신하지 않고, 분포 변화나 verifier와 무관한 품질 저하를 조기에 보여주는 신호입니다. + +### Gap + +현재 문서화된 gap은 다음과 같습니다. + +- `Finding.disposition`은 Secret 전용 파생 view입니다. `VulnerabilityFinding`에는 동일한 disposition vocabulary가 아직 없습니다. +- Secret manual disposition은 `true_positive`와 `false_positive`만 노출합니다. `IGNORED` 같은 상태는 모델에는 있지만 사용자 CLI verdict로는 열려 있지 않습니다. +- `TRUE_POSITIVE` verifier verdict는 terminal triage이지만 `status=OPEN`으로 남아 gate blocking signal입니다. 실제 remediation 완료를 나타내는 별도 resolution flow는 분리되어 있습니다. +- `NEEDS_REVIEW`는 durable disposition으로 쓰지 않습니다. Verify queue는 deterministic job id로 flood를 줄이지만, 사람이 review해야 하는 애매한 후보 자체를 자동으로 닫지는 않습니다. +- GHAS parity는 Secret Scanning 기준의 품질 측정입니다. Code vulnerability GHAS parity, GHAS lifecycle automation, alert mutation은 현재 공개 기본 경로가 아닙니다. +- Non-GHAS drift는 조기 경보입니다. GHAS-enabled repo의 read-only parity SLO와 같은 acceptance gate로 취급하지 않습니다. + ## 비공개 결과 처리 제한된 evidence는 `private/`, internal system, restricted knowledge base에 둡니다. 공개 view에는 정확한 저장 위치, host, credential injection path를 쓰지 않습니다. From 772e6dcaf8799c6cccfb41a852f888181a7d98dc Mon Sep 17 00:00:00 2001 From: pureliture Date: Sun, 21 Jun 2026 22:01:10 +0900 Subject: [PATCH 3/7] =?UTF-8?q?salvage(ghas-alt-matrix):=20codex=20worktre?= =?UTF-8?q?e=20=EB=AF=B8=EC=BB=A4=EB=B0=8B=20docs=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?=EB=B3=B4=EC=A1=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 --- docs/views/project-overview-and-strategy.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/docs/views/project-overview-and-strategy.md b/docs/views/project-overview-and-strategy.md index e0a6d0c..dc526c7 100644 --- a/docs/views/project-overview-and-strategy.md +++ b/docs/views/project-overview-and-strategy.md @@ -22,6 +22,7 @@ Secret scanner는 많은 후보를 찾아냅니다. 하지만 실제 운영에 - 스캔 실행, finding, lifecycle 상태를 로컬 저장소에 남깁니다. - Synthetic corpus로 precision, recall, false negative를 측정합니다. - Ollama-compatible verifier는 redacted metadata만 받아 triage 상태를 보조합니다. +- `code-vuln` category는 opt-in SARIF import와 Semgrep-compatible local adapter로 다룹니다. ## 현재 지원하지 않는 일 @@ -30,7 +31,7 @@ Secret scanner는 많은 후보를 찾아냅니다. 하지만 실제 운영에 - 관리형 클라우드 실행 환경 연동. - 내부 endpoint 또는 알림 시스템 연동. - Gitleaks를 대체하는 primary scanner 도입. -- SAST/SCA 확장. +- 기본 Secret Detection 경로를 대체하는 SAST/SCA 확장. ## 현재 상태 @@ -38,6 +39,23 @@ Secret scanner는 많은 후보를 찾아냅니다. 하지만 실제 운영에 따라서 지금의 핵심 메시지는 “로컬 Secret Detection 파이프라인을 안정화하는 중”입니다. 현재 공개 기본 경로는 local scan, local store, report, gate, evaluation입니다. +## 카탈로그용 요약 + +`security-scanner`는 GHAS 전체를 대체하는 managed SaaS가 아니라, local-first 환경에서 Secret Detection 결과를 재현 가능하게 만들고 GHAS Secret Scanning 품질에 가까워지도록 보정하는 제품입니다. + +PR #58 이후 자율층 M0~M5는 synthetic fixture 기준으로 GHAS parity harness, 인라인 FP 억제, LLM tier disposition 배선, non-GHAS drift monitor, report-only parity SLO gate까지 갖췄습니다. 다만 실 GHAS snapshot 취득, baseline 측정, threshold 커밋, enforce 전환은 human-gated H1~H3로 남아 있습니다. + +## GHAS 대체 매트릭스 + +| 영역 | 우리 제품 기능 | 현재 성숙도 | gap | +| --- | --- | --- | --- | +| GHAS Secret Scanning | Gitleaks-first local checkout scan, `Finding` 표준화, local store, report/gate/evaluate, read-only GHAS Secret Scanning alert/location fetch, redacted `GHAS_ALERT` 비교 input, incomplete metadata bucket | **부분 대체 + 보완.** Local secret detection과 redacted GHAS 비교는 가능하고, PR #58 이후 GHAS `secret_type` ↔ gitleaks `rule_id` 정규화, state-aware truth, line tolerance가 parity harness에 반영됨 | GitHub native push protection, secret validity/revocation, alert mutation, org-level managed rollout, GHAS scan trigger는 제공하지 않음. 실 GHAS snapshot 기반 enforce는 H1~H3 대기 | +| GHAS Code Scanning | `code-vuln` opt-in 경로: SARIF import, `VULN_FINDING` JSONL artifact, Semgrep-compatible local adapter, code-vuln report/gate/evaluate, vulnerability verifier/explainer | **보완.** SARIF-compatible local slice는 있으나 기본 경로가 아니며 GHAS Code Scanning alert lifecycle과 분리됨 | GHAS Code Scanning alert fetch/comparison, SARIF upload, code scanning alert mutation, security campaign, SCA/SBOM/dependency vulnerability는 future track | +| Alert lifecycle | Secret finding은 `FINDING_STATE`와 disposition ledger로 재검토 상태를 보존하고, verifier terminal verdict를 disposition으로 반영. `NEEDS_REVIEW`는 fail-closed review-needed 상태로 남김 | **부분 대체.** Local triage lifecycle은 갖췄고 PR #58 M3에서 `scan_all` + `scan_worker`의 LLM tier disposition/async verify queue가 배선됨 | GHAS alert open/resolved/dismissed 상태를 원격으로 변경하지 않음. GitHub UI와 양방향 lifecycle sync, assignment, dismissal comment, notification workflow는 미대체 | +| Dashboard / overview | Read API와 read-only dashboard/admin UI로 catalog, scan status, findings, repo health, backlog 상태를 보는 운영 화면 제공 | **보완.** M8 dashboard가 merged되어 local 운영 가시성은 강화됨 | GHAS organization security overview, enterprise dashboard, cross-org trend, native GitHub permission model, managed notification UX는 미대체 | +| Quality / parity | GHAS parity SLO 모델, per-repo 1:1 macro precision/recall, synthetic GHAS parity corpus, type coverage, GHAS-confirmed-FP bucket, report-only `governance.parity_slo --check`, stale-degraded 표시 | **보완에서 enforce 전 단계.** PR #58 M0~M5 완료로 측정/품질 자율층은 존재. Drift monitor는 GHAS-calibrated baseline을 쓰지만 non-GHAS에서는 early-warning일 뿐 SLO가 아님 | Threshold file 부재 시 parity gate는 report-only라 차단하지 않음. 실 baseline 측정과 목표 확정, enforce 전환, fixture-vs-real divergence 보고는 human-gated H-track 필요 | +| Managed SaaS | Local-first CLI/runtime, DynamoDB-compatible local storage pattern, systemd-oriented personal runtime, public-safe synthetic fixtures | **미대체.** 운영자가 소유하는 로컬/자체 실행 제품 | Hosted multi-tenant service, GitHub-native entitlement, managed scaling, SLA, billing, centralized secrets/revocation, enterprise policy management는 제공하지 않음 | + ## 성공 기준 - 같은 입력에서 같은 report와 gate 결과를 다시 만들 수 있습니다. From ebf65aa3b87adba87674a222d208c62adb69dac1 Mon Sep 17 00:00:00 2001 From: pureliture Date: Sun, 21 Jun 2026 22:01:11 +0900 Subject: [PATCH 4/7] =?UTF-8?q?salvage(product-catalog-db-schema):=20codex?= =?UTF-8?q?=20worktree=20=EB=AF=B8=EC=BB=A4=EB=B0=8B=20docs=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20=EB=B3=B4=EC=A1=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 --- .../views/source-scan-results-nosql-schema.md | 105 +++++++----------- 1 file changed, 43 insertions(+), 62 deletions(-) diff --git a/docs/views/source-scan-results-nosql-schema.md b/docs/views/source-scan-results-nosql-schema.md index d18b578..a7d423f 100644 --- a/docs/views/source-scan-results-nosql-schema.md +++ b/docs/views/source-scan-results-nosql-schema.md @@ -1,83 +1,64 @@ # 소스 스캔 결과 NoSQL Schema -이 schema는 스캔 결과를 저장하기 위한 것입니다. Source code 원문을 저장하지 않습니다. 목적은 로컬에서 필요한 조회 패턴을 먼저 검증하는 것입니다. +이 섹션은 제품 카탈로그의 **DB schema/storage architecture** 설명입니다. 목표는 single-table NoSQL schema를 구현 세부가 아니라 제품 동작 관점에서 이해할 수 있게 정리하는 것입니다. -![Schema access patterns](../assets/schema-access-patterns.svg) - -## 저장소가 답해야 하는 질문 - -Schema는 데이터 모양보다 질문에서 출발합니다. +`security-scanner`는 Gitleaks-first local scanner의 결과를 표준 `Finding` 모델로 바꾸고, 같은 저장소에서 target catalog, scan run, finding lifecycle, incremental queue, freshness, GHAS comparison metadata를 함께 다룹니다. 테이블은 물리적으로 하나이며, 각 row는 `entityType`, `PK`, `SK`와 필요한 GSI projection을 통해 access pattern별로 읽힙니다. -- 최근에 스캔한 대상은 무엇인가? -- 특정 대상의 스캔 이력은 어떻게 바뀌었는가? -- 특정 scan run에서 어떤 finding이 나왔는가? -- 같은 finding의 triage 상태는 무엇인가? -- Report와 gate 판단을 저장된 결과만으로 다시 만들 수 있는가? - -## 저장하는 단위 +![Schema access patterns](../assets/schema-access-patterns.svg) -| Entity | 저장하는 내용 | 주로 쓰는 곳 | -| --- | --- | --- | -| `REPO_META` | 대상 repository의 현재 정보와 최근 스캔 요약 | 대상 목록 | -| `SCAN_RUN` | 한 번의 스캔 실행 요약 | 실행 이력 | -| `FINDING` | dedup된 finding identity | identity lookup | -| `FINDING_OBSERVATION` | 특정 scan run에서 관측된 finding snapshot | 상세 검토 | -| `FINDING_STATE` | dedup된 finding의 lifecycle/triage 상태 | 재검토와 gate | +## Schema 설명문 -## CORE item shape +이 schema는 table을 entity별로 쪼개는 대신, 제품이 답해야 하는 질문을 기준으로 row를 배치합니다. -현재 구현 범위는 CORE row만 다룹니다. +- 어떤 repository가 스캔 대상인가? +- 특정 repository의 최신 스캔 상태와 실행 이력은 무엇인가? +- 특정 scan run에서 어떤 finding이 관측되었는가? +- 같은 finding의 현재 lifecycle/triage 상태는 무엇인가? +- incremental scan queue에서 다음에 처리할 commit은 무엇인가? +- dashboard/read API가 freshness, coverage, backlog를 full scan 없이 볼 수 있는가? +- GHAS Secret Scanning과 local finding을 raw secret 없이 비교할 수 있는가? -| Entity | PK | SK | 핵심 내용 | -| --- | --- | --- | --- | -| `REPO_META` | `REPO#` | `META` | repository metadata와 최근 스캔 요약 | -| `SCAN_RUN` | `REPO#` | `SCAN_RUN##` | scan run summary와 artifact pointer | -| `FINDING` | `FINDING#` | `META` | repo, rule, source tool, location, fingerprint 같은 identity field | -| `FINDING_OBSERVATION` | `RUN#` | `OBS##` | run-scoped finding snapshot | -| `FINDING_STATE` | `FINDING#` | `STATE#GLOBAL` | status와 triage lifecycle state | +핵심 분리는 `FINDING`, `FINDING_OBSERVATION`, `FINDING_STATE`입니다. `FINDING`은 dedup identity, `FINDING_OBSERVATION`은 scan-run별 관측 snapshot, `FINDING_STATE`는 사람이 바꿀 수 있는 lifecycle 상태입니다. Report와 gate는 observation을 읽은 뒤 state를 overlay해서 다시 계산합니다. -`FINDING` identity row에는 scanner evidence snapshot이나 triage state를 넣지 않습니다. -Scan run별 evidence snapshot은 `FINDING_OBSERVATION`에 두고, runtime read는 -observation snapshot에 `FINDING_STATE`를 overlay해서 `Finding`을 복원합니다. +스캔 대상에는 두 축이 있습니다. `SCAN_TARGET`은 CLI가 관리하는 scan-all 대상 catalog입니다. `CATALOG`는 org repository membership을 기록하는 coverage/freshness catalog입니다. 둘 다 repository 목록을 다루지만, 전자는 사용자가 켜고 끄는 스캔 대상이고 후자는 org N 대비 covered M을 계산하는 제품 상태입니다. -`occurrenceKey`는 redacted canonical observation identity의 deterministic hash입니다. -재료는 `repo`, `ruleId`, `sourceTool`, `file`, `startLine`, `fingerprint`를 기본으로 -하고, redacted fallback으로 `secretHash`, `matchHash`를 사용할 수 있습니다. Raw -secret이나 raw match 문자열은 occurrence key material에 넣지 않습니다. +Queue와 freshness는 반복 실행 경로를 위한 보조 entity입니다. `REF_STATE`가 ref cursor를 저장하고, `SCAN_JOB`이 commit scanner tuple을 대기열로 만들며, `SCAN_LEDGER`가 이미 처리한 tuple을 idempotency ledger로 남깁니다. 성공한 작업은 `REPO_HEALTH`를 전진시키고, evaluator는 `BREACH_COUNTER`를 materialize해서 dashboard가 O(1)에 freshness rollup을 읽게 합니다. -`FINDING_STATE`는 현재 `GLOBAL` scope만 사용합니다. Scan write는 state row가 없을 -때만 default state를 만들고, 이미 존재하는 manual triage verdict/verifier/reason을 -blind overwrite하지 않습니다. Observation write는 state와 분리되어 idempotent하게 -처리합니다. +GHAS 비교 경로는 local scanner를 대체하지 않습니다. `GHAS_ALERT`는 read-only GHAS Secret Scanning alert의 redacted comparison metadata이고, 필요한 raw local/GHAS evidence는 plaintext가 아니라 `SECRET_EVIDENCE`의 encrypted metadata로만 보관합니다. -## 조회 기준 +## Entity table data -| 알고 싶은 것 | 접근 방식 | -| --- | --- | -| 최근 대상 목록 | repo list index를 page 단위로 조회 | -| 대상별 스캔 이력 | 대상 partition에서 scan run만 조회 | -| 특정 실행의 finding | scan run partition에서 `OBS#` item 조회 | -| finding 상태 | finding별 `STATE#GLOBAL` item 조회 | -| report/gate 판단 | finding snapshot에 lifecycle state를 merge한 뒤 계산 | +| Entity | 목적 | PK/SK shape | 주요 access pattern | Raw secret 저장/노출 경계 | +| --- | --- | --- | --- | --- | +| `REPO_META` | local scan 대상 repository의 현재 metadata와 최신 scan summary를 저장합니다. | `PK=REPO#`, `SK=META` | 최근 대상 목록은 sharded `REPO_LIST#ALL` GSI1 axis를 최신순으로 page 조회합니다. 특정 repo의 run history와 같은 partition을 공유합니다. | Raw secret 없음. 실제 repo URL, owner, path는 운영 DB에서는 민감할 수 있으므로 public docs/tests에는 synthetic 값만 둡니다. | +| `SCAN_RUN` | 한 번의 repository scan 실행 요약과 artifact pointer를 저장합니다. | `PK=REPO#`, `SK=SCAN_RUN##` | repo별 실행 이력은 `PK=REPO#` + `SK begins_with SCAN_RUN#`로 읽습니다. 날짜별 실행 조회는 sharded `SCAN_DATE#` GSI1 axis를 사용합니다. | Raw secret 없음. `artifactUri`는 pointer일 뿐이며, 실제 scanner output이나 private artifact는 `private/` 또는 저장소 밖에 둡니다. | +| `FINDING` | dedup된 finding identity를 저장합니다. Rule, source tool, 위치, fingerprint 같은 identity field 중심입니다. | `PK=FINDING#`, `SK=META` | finding 단건 identity lookup, rule 기준 future/API 조회(`gsi2pk=RULE#`), repo-axis 조회에 사용합니다. | Raw secret을 넣지 않습니다. 파일/라인/fingerprint도 실제 repo에서는 민감할 수 있어 공개 예시는 synthetic만 사용합니다. | +| `FINDING_OBSERVATION` | 특정 scan run에서 관측된 finding snapshot을 저장합니다. 같은 finding이 run마다 다르게 보일 수 있는 부분을 여기에 둡니다. | `PK=RUN#`, `SK=OBS##` | scan-run 상세는 `PK=RUN#` + `SK begins_with OBS#`로 읽고, repo residual query는 sharded repo-axis GSI1에서 `RUN#` prefix로 읽습니다. | Runtime exact evidence review가 필요한 제한 경로에서는 scanner-native snapshot이 포함될 수 있습니다. Public report, gate, export, verifier prompt에는 raw secret/match를 내보내지 않고 hash/redacted field를 사용합니다. | +| `FINDING_STATE` | dedup finding의 현재 lifecycle/triage 상태를 저장합니다. Observation과 분리해 manual verdict를 blind overwrite하지 않습니다. | `PK=FINDING#`, `SK=STATE#GLOBAL` | finding state point read, scan-run/read-all 복원 시 observation에 state overlay, disposition update transaction에 사용합니다. | Raw secret 없음. Triage reason에는 secret, private path, raw match를 쓰지 않는 운영 규칙이 필요합니다. | +| `SCAN_TARGET` | CLI가 관리하는 scan-all 대상 catalog입니다. URL/name/enabled를 저장합니다. | `PK=SCAN_TARGET#`, `SK=META` | `add-target`, `list-targets`, `enable-target`, `disable-target`, `scan-all`이 사용합니다. 목록 조회는 sharded `TARGET_LIST#ALL` GSI1 axis입니다. | Raw secret 없음. 실제 private repo URL은 public fixture/docs에 쓰지 않습니다. | +| `CATALOG` | org repository membership catalog입니다. Coverage gap 계산과 future GHAS reconcile의 기준 목록입니다. | `PK=CATALOG#`, `SK=META` | reconcile timer가 upsert하고, coverage 계산은 bounded org-size enumeration으로 읽습니다. Read API hot path는 직접 scan하지 않고 `BREACH_COUNTER.coverageGap`을 읽습니다. | Raw secret 없음. Opt-out repo도 삭제하지 않고 `included=false`와 `excludedReason`으로 기록합니다. Public docs에는 실제 org/repo 값을 쓰지 않습니다. | +| `REF_STATE` | incremental discovery가 마지막으로 본 ref SHA를 저장합니다. | `PK=REPO#`, `SK=REF#` | ref point lookup과 `PK=REPO#` + `SK begins_with REF#` list로 cursor를 읽습니다. GSI list key는 제거되어 base-table query가 기준입니다. | Raw secret 없음. Ref name과 commit SHA는 공개 예시에 실제 private repo 값을 쓰지 않습니다. | +| `SCAN_JOB` | commit scanner tuple의 queue row입니다. Lease, retry, priority, status, fencing 정보를 담습니다. | `PK=SCAN_JOB#`, `SK=META` | pending/leased dequeue와 backlog count는 sharded `SCAN_JOB_STATUS#` GSI1 axis를 사용합니다. Repo별 job 조회는 `gsi2pk=REPO#` 축을 둡니다. | Raw secret 없음. `lastError`에는 scanner output snippet, raw match, credential-like 문자열을 넣지 않는 것이 경계입니다. | +| `SCAN_LEDGER` | 이미 처리한 `(repo, commit, scanner, rule pack, config)` tuple을 기록하는 idempotency ledger입니다. | `PK=SCAN_LEDGER##`, `SK=###` | enqueue 전 `has_scan_ledger` point lookup으로 중복 scan을 막고, repo-axis GSI1로 repo별 completion history를 볼 수 있습니다. | Raw secret 없음. Completion marker와 finding count만 저장합니다. | +| `REPO_HEALTH` | repo별 마지막 성공 incremental/baseline scan timestamp를 저장합니다. | `PK=REPO_HEALTH#`, `SK=META` | worker가 successful completion 후 advancing-only conditional update로 갱신합니다. Evaluator는 batch read 또는 evaluator-only enumeration으로 freshness breach를 계산합니다. | Raw secret 없음. Timestamp와 repo id만 다룹니다. | +| `BREACH_COUNTER` | freshness/coverage rollup을 materialized singleton으로 저장합니다. | `PK=BREACH_COUNTER`, `SK=META` | dashboard/read API가 O(1) point read로 incremental breach, baseline breach, total breach, coverage gap을 표시합니다. | Raw secret 없음. Aggregate count만 저장합니다. | +| `GHAS_ALERT` | GHAS Secret Scanning alert/location의 redacted comparison metadata를 저장합니다. | `PK=GHAS_ALERT#`, `SK=META` | `compare-ghas`가 redacted inputs를 저장하고, comparison read는 `entityType=GHAS_ALERT` scan을 사용합니다. Repo-axis GSI1 projection은 repo-scoped analysis를 위해 보존합니다. | Raw API response, alert URL, user object, resolution comment, raw secret, raw match context를 저장하지 않습니다. `hide_secret=true` fetch와 redacted comparison key가 기본 경계입니다. | +| `SECRET_EVIDENCE` | local 또는 GHAS raw evidence가 꼭 필요할 때 encrypted evidence metadata를 저장합니다. | `PK=SECRET_EVIDENCE#`, `SK=META` | linked finding이면 `gsi1pk=FINDING#`, linked GHAS alert이면 `gsi1pk=GHAS_ALERT#`, unlinked fallback은 `SECRET_EVIDENCE#ALL`입니다. Product list/read API는 raw evidence list를 제공하지 않습니다. | Plaintext secret/match를 저장하지 않습니다. `encryptedSecret`, `encryptedMatch`, `secretHash`, `keyId`, `nonce`, `authTag`, `algorithm`, retention metadata만 저장합니다. Public output에는 ciphertext도 노출하지 않습니다. | -## 안전 규칙 +## 핵심 design principle 5개 -- Exact evidence가 필요한 runtime review 경로에서만 scanner-native evidence를 제한적으로 둡니다. -- Report, gate, export, verifier prompt는 hash와 redacted evidence를 사용합니다. -- 공개 문서, tests, examples에는 synthetic 값만 둡니다. -- 실제 외부 export, 비공개 finding, DB dump는 이 저장소 밖에 둡니다. -- TTL, streams, transaction, 운영 DynamoDB behavior는 현재 기본 요구사항이 아닙니다. +1. **Access pattern first**: entity shape는 "무엇을 저장할까"보다 "어떤 질문에 bounded read로 답할까"에서 출발합니다. 최근 repo, 특정 run, queue head, freshness rollup은 각각 명시된 key path를 가집니다. +2. **Identity, observation, state 분리**: finding identity와 scan-run observation, lifecycle state를 분리해 rescan이 manual triage를 덮어쓰지 않게 합니다. Runtime read는 observation에 state를 overlay합니다. +3. **Raw evidence 최소화**: public docs, tests, reports, verifier prompt, dashboard는 raw secret을 다루지 않습니다. Exact evidence가 필요한 runtime review는 제한 경로로 두고, 장기 보관이 필요하면 `SECRET_EVIDENCE`에 encrypted metadata로만 둡니다. +4. **Hot partition 회피와 bounded reads**: `REPO_LIST`, `TARGET_LIST`, `SCAN_DATE`, `SCAN_JOB_STATUS`는 sharded list axis를 사용하고, per-repo finding/ledger/GHAS axis도 sharded repo-axis를 사용합니다. Dashboard hot path는 `BREACH_COUNTER`와 queue count처럼 bounded read를 우선합니다. +5. **Idempotent and fenced writes**: `SCAN_LEDGER`는 중복 scan을 막고, `SCAN_JOB` lease/fence는 stale worker completion을 막으며, `REPO_HEALTH`는 advancing-only update로 timestamp regression을 막습니다. Lifecycle decision은 latest state update와 append-only event 기록을 함께 남깁니다. ## 현재 non-goals -다음 row/table은 CORE schema split 범위가 아닙니다. - -- `FindingFingerprintMap` -- `ScanRunQueryRows` -- `PatternQueryRows` -- standalone Artifacts table/item -- TTL, streams, Lambda, production DynamoDB behavior +- Public repository에 실제 GHAS alert export, alert URL, private path, raw scanner output, raw secret을 커밋하지 않습니다. +- Managed production DynamoDB 운영, TTL, streams, notification adapter는 이 schema 설명의 필수 조건이 아닙니다. +- GHAS는 read-only comparison baseline입니다. 이 프로젝트가 GHAS scan을 trigger하거나 alert state를 mutate하지 않습니다. ## 로컬 실행 환경 -Dynalite는 로컬 검증 후보입니다. DynamoDB Local과 LocalStack은 parity 또는 adapter integration을 확인할 때 검토할 수 있지만, 현재 기본 운영 결정은 아닙니다. +로컬 검증은 DynamoDB-compatible adapter로 access pattern을 먼저 검증합니다. Table name, endpoint, region, credential placeholder는 CLI flag 또는 환경 변수로 주입하며, public docs에는 실제 운영 table name, endpoint, account, host identifier를 쓰지 않습니다. From 20e5b298aa98ec4084ef708ce73f2c93aff958a1 Mon Sep 17 00:00:00 2001 From: pureliture Date: Sun, 21 Jun 2026 22:01:11 +0900 Subject: [PATCH 5/7] =?UTF-8?q?salvage(product-catalog-scan-flow):=20codex?= =?UTF-8?q?=20worktree=20=EB=AF=B8=EC=BB=A4=EB=B0=8B=20docs=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20=EB=B3=B4=EC=A1=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 --- README.md | 1 + docs/README.md | 8 +- docs/_harness/doc-map.yml | 38 +-- docs/views/scan-behavior-and-mechanism.md | 268 ++++++++++++++++++++++ 4 files changed, 283 insertions(+), 32 deletions(-) create mode 100644 docs/views/scan-behavior-and-mechanism.md diff --git a/README.md b/README.md index 312dc13..8ad745d 100644 --- a/README.md +++ b/README.md @@ -210,6 +210,7 @@ core <- scanners/storage/llm/adapters <- cli/runtime - [프로젝트 개요와 추진 전략](docs/views/project-overview-and-strategy.md) - [시스템 구조와 실행 환경](docs/views/system-architecture-and-runtime.md) - [소스 스캔 결과 NoSQL Schema](docs/views/source-scan-results-nosql-schema.md) +- [스캔 동작 방식과 메커니즘](docs/views/scan-behavior-and-mechanism.md) - [탐지 결과와 지표](docs/views/secret-detection-results-and-metrics.md) - [Local Scan Orchestration 목표 구조](docs/views/local-scan-orchestration-target-architecture.md) - [확장 경로와 운영 기준](docs/views/operations-transition-architecture.md) diff --git a/docs/README.md b/docs/README.md index 8bd393f..eb5f58c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -24,9 +24,9 @@ 1. [프로젝트 개요와 추진 전략](views/project-overview-and-strategy.md) 2. [시스템 구조와 실행 환경](views/system-architecture-and-runtime.md) 3. [소스 스캔 결과 NoSQL Schema](views/source-scan-results-nosql-schema.md) -4. [탐지 결과와 지표](views/secret-detection-results-and-metrics.md) -5. [Local Scan Orchestration 구조](views/local-scan-orchestration-target-architecture.md) -6. [제품 카탈로그](views/product-catalog.md) +4. [스캔 동작 방식과 메커니즘](views/scan-behavior-and-mechanism.md) +5. [탐지 결과와 지표](views/secret-detection-results-and-metrics.md) +6. [Local Scan Orchestration 구조](views/local-scan-orchestration-target-architecture.md) 7. [시작하기](views/getting-started.md) 8. [progress dashboard](dashboards/progress.html) @@ -37,9 +37,9 @@ | [project-overview-and-strategy.md](views/project-overview-and-strategy.md) | 이 프로젝트가 해결하려는 문제, 현재 지원 범위, 성공 기준 | | [system-architecture-and-runtime.md](views/system-architecture-and-runtime.md) | 전체 구조, 실행 흐름, codebase dependency boundary | | [source-scan-results-nosql-schema.md](views/source-scan-results-nosql-schema.md) | 스캔 결과를 어떻게 저장하고 어떤 질문에 답하려는지 | +| [scan-behavior-and-mechanism.md](views/scan-behavior-and-mechanism.md) | local one-shot scan과 periodic queue/worker scan의 단계별 동작 방식 | | [secret-detection-results-and-metrics.md](views/secret-detection-results-and-metrics.md) | 어떤 결과를 공개할 수 있고, 지표를 어떻게 해석하는지 | | [local-scan-orchestration-target-architecture.md](views/local-scan-orchestration-target-architecture.md) | Local Scan Orchestration의 Python file 참조 관계와 runtime call chain | -| [product-catalog.md](views/product-catalog.md) | 관리자 UX와 운영자 시나리오 관점에서 현재 제품 표면과 미완성 gap | | [operations-transition-architecture.md](views/operations-transition-architecture.md) | 로컬 실행 경로에서 반복 실행과 확장 adapter로 넘어가는 기준 | | [research-and-technical-decisions.md](views/research-and-technical-decisions.md) | 왜 Gitleaks-first인지, 다른 도구는 어떤 위치인지 | | [getting-started.md](views/getting-started.md) | 이미 클론된 로컬 저장소로 첫 스캔을 끝까지 돌리는 절차 | diff --git a/docs/_harness/doc-map.yml b/docs/_harness/doc-map.yml index 790d9fb..83032b5 100644 --- a/docs/_harness/doc-map.yml +++ b/docs/_harness/doc-map.yml @@ -79,6 +79,16 @@ human_views: docs/workbench/context/legacy-source/02-local-db-and-dynamodb-compatible-schema.md: 57a1638a804eb21c4775f85d0657460cce25f5111635f28bfcf117d2f49eb006 assets: - docs/assets/schema-access-patterns.svg + - view_id: scan-behavior-and-mechanism + title: 스캔 동작 방식과 메커니즘 + path: docs/views/scan-behavior-and-mechanism.md + output_path: confluence://프로젝트-개요-및-추진-전략/스캔-동작-방식-및-메커니즘 + generated_by: human_doc_curator + generated_at: 2026-06-21 + safety_class: public + source_docs: [] + source_hashes: {} + assets: [] - view_id: secret-detection-results-and-metrics title: 탐지 결과와 지표 path: docs/views/secret-detection-results-and-metrics.md @@ -116,34 +126,6 @@ human_views: - docs/assets/local-scan-target-runtime-flow.drawio - docs/assets/local-scan-target-scenario-seams.svg - docs/assets/local-scan-target-scenario-seams.drawio - - view_id: product-catalog - title: 제품 카탈로그 - path: docs/views/product-catalog.md - output_path: confluence://프로젝트-개요-및-추진-전략/제품-카탈로그 - generated_by: human_doc_curator - generated_at: 2026-06-21 - safety_class: public - source_docs: - - src/security_scanner/runtime/read_api.py - - src/security_scanner/cli/commands/read_api.py - - src/security_scanner/cli/commands/scan.py - - src/security_scanner/cli/commands/scan_health.py - - src/security_scanner/cli/commands/disposition.py - - tests/test_read_api.py - - tests/test_queue_status.py - - tests/test_cli_disposition.py - - docs/workbench/context/legacy-source/08-milestones.md - source_hashes: - src/security_scanner/runtime/read_api.py: 3cfc5536fc34e2412506a09ef53a985a09e63e2debe1f34eccbce4353b5f0a2f - src/security_scanner/cli/commands/read_api.py: 83f77aa0223619f00de6ccc82ecc7de70dc94d0016f492ba2f8701cc6ea9aa68 - src/security_scanner/cli/commands/scan.py: 2d03278acbb08dd56a0c79c7e390cb2375a7b41dbefde777ee7c4e79bdc274cd - src/security_scanner/cli/commands/scan_health.py: 255b6243d2f05a4b6c59e0f5056b0b27f1db15e52ac0d77219a97586d3621b61 - src/security_scanner/cli/commands/disposition.py: 200a9198f8bcf8d7c80afc77e103bb22bdcc4d1169441d5451375ce0caf36bb5 - tests/test_read_api.py: ac327e19be8dec2f525701a54115d74d707e2c553f768b31ca4e0b6663a840eb - tests/test_queue_status.py: 448b89ce7d8aba6abfa9a71206aba827814087b3b2c3c0d5413c11104ce82fc3 - tests/test_cli_disposition.py: 7c9027b821521b3336e2c0da4ce0c2bb498a2e80e038d2b4e137070f0998225e - docs/workbench/context/legacy-source/08-milestones.md: 121170acde8ccdb4ca8ba50e22faf7ae5ecd79c33b76294ba852e1e7a1675c1b - assets: [] - view_id: operations-transition-architecture title: 확장 경로와 운영 기준 path: docs/views/operations-transition-architecture.md diff --git a/docs/views/scan-behavior-and-mechanism.md b/docs/views/scan-behavior-and-mechanism.md new file mode 100644 index 0000000..b01e038 --- /dev/null +++ b/docs/views/scan-behavior-and-mechanism.md @@ -0,0 +1,268 @@ +# 스캔 동작 방식과 메커니즘 + +이 문서는 제품 카탈로그에서 스캔이 실제로 어떤 단계를 거치는지 설명합니다. + +핵심은 두 실행 형태를 구분하는 것입니다. 첫째는 사용자가 CLI로 한 번 실행하는 +local one-shot scan입니다. 둘째는 저장소 catalog와 durable queue를 기반으로 +반복 실행되는 periodic queue/worker scan입니다. 두 흐름 모두 public-safe 결과 +모델을 만들고, 저장된 결과에서 report와 gate를 다시 계산하는 방향을 공유합니다. + +## 한 줄 설명 + +`security-scanner`는 대상 checkout을 준비한 뒤 Gitleaks 또는 opt-in +Semgrep-compatible scanner를 실행하고, scanner output을 canonical finding으로 +정규화합니다. 일반 결과 저장소에는 redacted evidence만 남기고, report와 gate는 +저장된 snapshot을 다시 읽어 계산합니다. 반복 실행 경로는 durable queue, ledger, +repo lease, freshness marker를 사용해 같은 commit을 중복 처리하지 않고 오래된 +coverage를 드러냅니다. + +## Flow 1. Local one-shot scan + +```text +manifest + -> enabled target 선택 + -> workspace/local checkout 확인 + -> Gitleaks 실행 + -> Finding 정규화 + -> optional encrypted raw evidence 기록 + -> JSONL 또는 DynamoDB-compatible store write + -> report / gate / evaluate에서 저장 결과 재조회 +``` + +### 1. Manifest + +Local one-shot scan은 명시적인 manifest 파일에서 시작합니다. + +- `version`은 현재 `1`만 지원합니다. +- `targets[]`는 `name`, `path`, `enabled`를 가집니다. +- `scan`은 `include_history`, `git_log_opts`, `exclude`, + `enable_noise_filter` 같은 scanner option을 담습니다. +- `gitleaks_config`가 있으면 Gitleaks 실행 시 `--config`로 전달됩니다. + +Manifest는 자동 발견되지 않습니다. 사용자가 넘긴 파일만 읽고, `enabled=true`인 +target만 스캔합니다. + +### 2. Workspace / clone + +현재 local one-shot 경로의 workspace 단계는 remote clone이 아닙니다. `Target.path` +를 절대 경로로 해석하고, 해당 directory가 local filesystem에 존재하는지만 +검증합니다. + +제품 관점에서 이 단계의 의미는 “scanner가 실행될 checkout을 보장한다”입니다. +Phase 1에서는 이미 준비된 local checkout만 대상으로 삼습니다. + +### 3. Gitleaks 실행 + +Secret Detection 기본 scanner는 Gitleaks입니다. + +- `include_history=true`이면 `gitleaks git`을 사용합니다. +- `include_history=false`이면 `gitleaks dir`을 사용합니다. +- `git_log_opts`가 있으면 git scan mode에서 `--log-opts`로 전달됩니다. +- report는 임시 JSON 파일로 받고, `--exit-code 0`을 사용해 “finding 있음”을 + process failure와 구분합니다. + +Gitleaks binary 없음, timeout, non-zero process exit은 scanner/runtime 실패로 +취급됩니다. Local scan summary에는 private path나 raw output 대신 public-safe +오류 형태로 축약됩니다. + +### 4. Finding normalization + +Gitleaks JSON item은 core `Finding`으로 변환됩니다. + +- file path는 repository root 밖으로 escape하지 못하도록 정규화됩니다. +- raw secret은 `secretHash`로 바뀌고, evidence summary는 항상 `redacted=true` + 입니다. +- Gitleaks fingerprint가 있으면 repo scope와 결합해 dedup 안정성을 높입니다. +- 기본 verdict는 `NEEDS_REVIEW`입니다. +- template placeholder, known dummy value, repeated character, low entropy 후보는 + parser 단계에서 scan-time noise로 제거됩니다. +- docs/example/test path-role이나 lockfile hash context 같은 deterministic + suppression도 scan-time에서 처리됩니다. + +이 단계에서 제거된 항목은 `Finding`으로 저장되지 않습니다. LLM verifier가 내리는 +판단은 scan-time suppression이 아니라 post-scan disposition입니다. + +### 5. Raw evidence / redaction + +일반 finding snapshot에는 raw `secret`과 raw `match`가 저장되지 않습니다. +`Finding.to_dict()`는 scanner-native Gitleaks payload에서도 `secret`과 `match`를 +`null`로 redaction합니다. + +정확한 runtime review가 필요한 경우에만 별도 raw evidence path를 지정할 수 +있습니다. 이때 `RawEvidenceJsonlStore`는 raw secret과 match를 암호화한 metadata +row로 append합니다. 이 artifact는 public docs, examples, committed fixtures에 +넣지 않습니다. + +### 6. Store write + +Local scan은 storage backend에 따라 결과를 기록합니다. + +- JSONL backend는 redacted finding snapshot을 append합니다. +- DynamoDB-compatible backend는 finding, repo metadata, scan run summary를 + 저장합니다. +- store가 repo freshness capability를 제공하면, 성공적으로 스캔된 target마다 + baseline freshness가 전진합니다. + +Store write는 scanner 실행과 report rendering을 분리합니다. 이후 report/gate는 +scanner를 다시 실행하지 않고 저장소를 읽습니다. + +### 7. Report / gate / evaluate + +`report`, `gate`, `evaluate`는 finding consumer입니다. + +- `--scan-run-id`가 있으면 특정 scan run만 읽습니다. +- 없으면 backend가 볼 수 있는 전체 finding을 읽습니다. +- Secret gate는 `OPEN` 상태이고 verdict가 `NEEDS_REVIEW` 또는 `TRUE_POSITIVE`인 + finding을 blocking으로 계산합니다. +- `FALSE_POSITIVE`, `RESOLVED`, `IGNORED` 상태는 blocking에서 제외됩니다. + +이 구조 때문에 scanner 실행, 저장, 검토, gate 판단을 각각 재현할 수 있습니다. + +## Flow 2. Periodic queue/worker scan + +```text +catalog / ref discovery / baseline enqueue + -> SCAN_JOB 생성 + -> scan-worker가 job lease + -> repo lease 획득 + -> workspace fetch hook으로 checkout 확보 + -> Gitleaks 실행 + -> Finding 정규화 + -> findings + ScanLedger write + -> job completed 또는 retry/dead-letter + -> repo freshness 전진 + -> ambiguous finding verifier job enqueue + -> 별도 verify drain이 disposition 기록 +``` + +### 1. Catalog와 job 생성 + +Periodic path는 local manifest를 매번 직접 읽는 구조가 아닙니다. catalog, +ref state, baseline enqueue 같은 runtime 입력이 durable `ScanJob`을 만듭니다. + +`ScanJob`은 repo, ref, commit, scanner tuple, priority, retry 정보, job type을 +가집니다. `job_type`은 freshness 의미를 나눕니다. + +- `incremental` completion은 incremental freshness를 전진합니다. +- `baseline` completion은 full scan freshness를 전진합니다. +- `verify` job은 scanner worker가 처리하지 않으며 freshness를 전진하지 않습니다. + +### 2. Worker lease와 repo lease + +`scan-worker`는 pending job을 lease하고, 별도로 repo lease를 획득한 뒤 스캔합니다. +이중 lease는 두 가지 문제를 줄입니다. + +- 여러 worker가 같은 job을 동시에 완료하는 문제 +- 같은 repo workspace를 동시에 조작하는 문제 + +Lease에는 fence token이 붙습니다. lease가 만료되어 reaper나 다른 worker가 소유권을 +가져가면, 오래된 worker의 completion/failure write는 조건부 write에서 거절될 수 +있습니다. + +### 3. Worker scan + +Worker의 기본 scanner도 Gitleaks입니다. + +- baseline job은 full-history scan으로 실행됩니다. +- incremental job은 commit 단위 `git_log_opts`로 실행됩니다. +- scanner 결과는 local one-shot과 같은 Gitleaks parser/mapper를 통과합니다. +- branch와 commit context는 finding occurrence에 덧붙습니다. + +이미 같은 scanner tuple의 `ScanLedger`가 있으면 worker는 scanner를 다시 실행하지 +않고 job을 completed로 처리합니다. + +### 4. Store write와 ledger + +Worker completion은 findings와 `ScanLedgerEntry`를 함께 기록하고 job을 completed로 +전환합니다. 실패는 retryable failure로 pending에 되돌아가거나, retry 한도를 넘으면 +dead-letter가 됩니다. + +Ledger의 의미는 “이 repo commit은 이 scanner/rule/config tuple로 이미 처리됐다”는 +내구성 있는 증거입니다. 이는 periodic scan의 idempotency 기준입니다. + +### 5. Verifier queue + +Periodic worker hot path는 LLM을 직접 호출하지 않습니다. 스캔이 성공한 뒤 ambiguous +finding에 대해 best-effort로 `job_type="verify"` job을 enqueue합니다. + +Verifier job은 같은 queue table을 사용하지만 논리적으로 별도 작업입니다. + +- code-scan worker가 `verify` job을 받으면 pending으로 돌려 dedicated drain이 + 처리하게 합니다. +- verify job id는 finding의 content-stable key에서 결정되어 중복 enqueue를 줄입니다. +- 별도 drain path가 verifier를 호출하고 terminal disposition을 기록합니다. +- `NEEDS_REVIEW`는 disposition을 쓰지 않고 job만 소비해 tight loop를 막습니다. + +Verifier는 detector가 아닙니다. Finding을 삭제하지 않고 review/gate에 쓰일 +disposition signal만 보강합니다. + +### 6. Freshness + +Periodic path는 freshness를 scan result와 별도로 다룹니다. + +- repo별 freshness row는 incremental과 baseline timestamp를 따로 가집니다. +- timestamp update는 advancing-only 조건부 write라 오래된 completion이 최신 + freshness를 되돌리지 못합니다. +- freshness evaluator는 repo health를 읽어 breach counter를 materialize합니다. +- read API나 dashboard는 materialized counter를 O(1)로 읽을 수 있습니다. + +Freshness는 “finding이 몇 개인가”가 아니라 “coverage가 최근에도 성공했는가”를 보는 +신호입니다. + +## Opt-in code vulnerability path + +Secret Detection 기본 경로와 별도로 `code-vuln` category가 있습니다. + +```text +local checkout + -> Semgrep-compatible CLI 실행 + -> SARIF 생성 + -> SARIF importer + -> VULN_FINDING 정규화 + -> vulnerability JSONL write + -> report/gate/evaluate --category code-vuln +``` + +이 경로는 `Finding` 모델을 재사용하지 않습니다. SARIF result는 +`VulnerabilityFinding`으로 변환되고, JSONL에는 `entityType=VULN_FINDING`으로 +기록됩니다. + +Code vulnerability path의 redaction 기준은 secret path와 다릅니다. + +- absolute path와 external URI는 hash 기반 redacted path로 바뀝니다. +- `scan-vuln`은 기본 `path_policy=redacted`를 사용합니다. +- message, rule name, help text, tags, partial fingerprint도 sanitizer를 거칩니다. +- gate는 severity와 precision threshold를 기준으로 blocking 여부를 계산합니다. + +현재 제품 설명에서는 이 경로를 opt-in 확장으로 다룹니다. Periodic queue/worker의 +기본 scanner path는 Gitleaks입니다. + +## 단계별 의미 요약 + +| 단계 | Local one-shot | Periodic queue/worker | 의미 | +| --- | --- | --- | --- | +| 대상 선택 | manifest의 enabled target | catalog/ref/baseline enqueue가 만든 `ScanJob` | 무엇을 스캔할지 고정 | +| workspace | local directory 존재 확인 | fetch hook과 repo lease로 checkout 확보 | scanner 입력 준비 | +| scanner | Gitleaks, opt-in `scan-vuln` | Gitleaks worker path | raw detector output 생성 | +| normalization | `Finding` 또는 `VULN_FINDING` | `Finding` | 저장 가능한 canonical model | +| raw evidence | 기본 redacted, optional encrypted artifact | 기본 redacted, verify용 metadata 중심 | 공개 output과 exact evidence 분리 | +| store write | JSONL 또는 DynamoDB-compatible | findings, job, ledger, health | 재조회와 재현성 확보 | +| report/gate | 저장 결과 재조회 | 저장 결과 재조회 | scanner 실행과 판단 분리 | +| verifier | post-scan artifact/CLI 경로 | async verify job drain | detector가 아닌 triage 보조 | +| freshness | successful target별 full scan freshness | incremental/baseline freshness 분리 | coverage staleness 감지 | + +## Gap / 주의 + +- Local one-shot의 `workspace/clone` 단계는 아직 remote clone/update가 아닙니다. + 이미 존재하는 local checkout을 확인하는 단계입니다. +- Periodic worker의 기본 scanner는 Gitleaks입니다. `code-vuln`의 Semgrep-compatible + path는 현재 opt-in local/SARIF flow이며 worker queue 기본 경로로 합쳐졌다고 + 설명하면 안 됩니다. +- Scanner-native raw `secret`과 `match`는 일반 finding snapshot에 남기지 않습니다. + exact evidence가 필요하면 암호화 raw evidence artifact를 별도로 관리해야 합니다. +- `NEEDS_REVIEW`는 안전한 최종 허용 상태가 아닙니다. Secret gate 기본값에서는 + `OPEN + NEEDS_REVIEW`가 blocking입니다. +- Freshness는 finding count가 아닙니다. 성공 timestamp가 오래되면 finding이 없어도 + coverage gap으로 봐야 합니다. +- Public repo에는 실제 target 목록, private path, raw findings, external alert data, + credential, internal endpoint를 넣지 않습니다. From 8f873e7d222c8e72d7539b72f8054bc3046224d4 Mon Sep 17 00:00:00 2001 From: pureliture Date: Sun, 21 Jun 2026 22:10:17 +0900 Subject: [PATCH 6/7] =?UTF-8?q?reconcile(docs):=20codex=20catalog=20doc=20?= =?UTF-8?q?salvage=20=EB=B3=91=ED=95=A9=20=E2=80=94=20doc-map.yml=C2=B7REA?= =?UTF-8?q?DME=20union?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 출처별 salvage 커밋에서 마지막 커밋(20e5b29 scan-flow)이 앞 커밋 (81554b7 admin-ux)의 product-catalog 등록을 덮어써, HEAD 트리에 한쪽만 남아 있던 문제를 union으로 복원한다. - docs/_harness/doc-map.yml: scan-behavior-and-mechanism(scan-flow)와 product-catalog(admin-ux) 두 view 등록을 모두 보존. view_id/path/ output_path 중복 없음, registry↔filesystem 정합 회복. - docs/README.md: scan-behavior와 product-catalog 줄을 번호 목록· 표 양쪽에 union으로 복원. 나머지 4개 view 문서(scan-behavior-and-mechanism, secret-detection, project-overview, source-scan-nosql, product-catalog)는 출처별 고유 파일이라 그대로 유지. Co-Authored-By: Claude Opus 4.8 --- docs/README.md | 6 ++++-- docs/_harness/doc-map.yml | 28 ++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/docs/README.md b/docs/README.md index eb5f58c..ebf3800 100644 --- a/docs/README.md +++ b/docs/README.md @@ -27,8 +27,9 @@ 4. [스캔 동작 방식과 메커니즘](views/scan-behavior-and-mechanism.md) 5. [탐지 결과와 지표](views/secret-detection-results-and-metrics.md) 6. [Local Scan Orchestration 구조](views/local-scan-orchestration-target-architecture.md) -7. [시작하기](views/getting-started.md) -8. [progress dashboard](dashboards/progress.html) +7. [제품 카탈로그](views/product-catalog.md) +8. [시작하기](views/getting-started.md) +9. [progress dashboard](dashboards/progress.html) ## 공개 후보 문서 @@ -40,6 +41,7 @@ | [scan-behavior-and-mechanism.md](views/scan-behavior-and-mechanism.md) | local one-shot scan과 periodic queue/worker scan의 단계별 동작 방식 | | [secret-detection-results-and-metrics.md](views/secret-detection-results-and-metrics.md) | 어떤 결과를 공개할 수 있고, 지표를 어떻게 해석하는지 | | [local-scan-orchestration-target-architecture.md](views/local-scan-orchestration-target-architecture.md) | Local Scan Orchestration의 Python file 참조 관계와 runtime call chain | +| [product-catalog.md](views/product-catalog.md) | 관리자 UX와 운영자 시나리오 관점에서 현재 제품 표면과 미완성 gap | | [operations-transition-architecture.md](views/operations-transition-architecture.md) | 로컬 실행 경로에서 반복 실행과 확장 adapter로 넘어가는 기준 | | [research-and-technical-decisions.md](views/research-and-technical-decisions.md) | 왜 Gitleaks-first인지, 다른 도구는 어떤 위치인지 | | [getting-started.md](views/getting-started.md) | 이미 클론된 로컬 저장소로 첫 스캔을 끝까지 돌리는 절차 | diff --git a/docs/_harness/doc-map.yml b/docs/_harness/doc-map.yml index 83032b5..d54ad97 100644 --- a/docs/_harness/doc-map.yml +++ b/docs/_harness/doc-map.yml @@ -126,6 +126,34 @@ human_views: - docs/assets/local-scan-target-runtime-flow.drawio - docs/assets/local-scan-target-scenario-seams.svg - docs/assets/local-scan-target-scenario-seams.drawio + - view_id: product-catalog + title: 제품 카탈로그 + path: docs/views/product-catalog.md + output_path: confluence://프로젝트-개요-및-추진-전략/제품-카탈로그 + generated_by: human_doc_curator + generated_at: 2026-06-21 + safety_class: public + source_docs: + - src/security_scanner/runtime/read_api.py + - src/security_scanner/cli/commands/read_api.py + - src/security_scanner/cli/commands/scan.py + - src/security_scanner/cli/commands/scan_health.py + - src/security_scanner/cli/commands/disposition.py + - tests/test_read_api.py + - tests/test_queue_status.py + - tests/test_cli_disposition.py + - docs/workbench/context/legacy-source/08-milestones.md + source_hashes: + src/security_scanner/runtime/read_api.py: 3cfc5536fc34e2412506a09ef53a985a09e63e2debe1f34eccbce4353b5f0a2f + src/security_scanner/cli/commands/read_api.py: 83f77aa0223619f00de6ccc82ecc7de70dc94d0016f492ba2f8701cc6ea9aa68 + src/security_scanner/cli/commands/scan.py: 2d03278acbb08dd56a0c79c7e390cb2375a7b41dbefde777ee7c4e79bdc274cd + src/security_scanner/cli/commands/scan_health.py: 255b6243d2f05a4b6c59e0f5056b0b27f1db15e52ac0d77219a97586d3621b61 + src/security_scanner/cli/commands/disposition.py: 200a9198f8bcf8d7c80afc77e103bb22bdcc4d1169441d5451375ce0caf36bb5 + tests/test_read_api.py: ac327e19be8dec2f525701a54115d74d707e2c553f768b31ca4e0b6663a840eb + tests/test_queue_status.py: 448b89ce7d8aba6abfa9a71206aba827814087b3b2c3c0d5413c11104ce82fc3 + tests/test_cli_disposition.py: 7c9027b821521b3336e2c0da4ce0c2bb498a2e80e038d2b4e137070f0998225e + docs/workbench/context/legacy-source/08-milestones.md: 121170acde8ccdb4ca8ba50e22faf7ae5ecd79c33b76294ba852e1e7a1675c1b + assets: [] - view_id: operations-transition-architecture title: 확장 경로와 운영 기준 path: docs/views/operations-transition-architecture.md From 6918360c16db39e8b881f9fc8e7b452207ea6647 Mon Sep 17 00:00:00 2001 From: pureliture Date: Mon, 22 Jun 2026 07:16:48 +0900 Subject: [PATCH 7/7] =?UTF-8?q?fix(docs):=20product-catalog=20source=20has?= =?UTF-8?q?h=20=EA=B0=B1=EC=8B=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #60 review follow-up으로 src/security_scanner/runtime/read_api.py의 현재 SHA-256을 doc-map source_hashes에 반영합니다. Co-Authored-By: Codex GPT-5 --- docs/_harness/doc-map.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_harness/doc-map.yml b/docs/_harness/doc-map.yml index d54ad97..dce39f3 100644 --- a/docs/_harness/doc-map.yml +++ b/docs/_harness/doc-map.yml @@ -144,7 +144,7 @@ human_views: - tests/test_cli_disposition.py - docs/workbench/context/legacy-source/08-milestones.md source_hashes: - src/security_scanner/runtime/read_api.py: 3cfc5536fc34e2412506a09ef53a985a09e63e2debe1f34eccbce4353b5f0a2f + src/security_scanner/runtime/read_api.py: f5af4a9840239078647c81a5bc3381316f81c12b54f3145a3d9e91fb8051fe9d src/security_scanner/cli/commands/read_api.py: 83f77aa0223619f00de6ccc82ecc7de70dc94d0016f492ba2f8701cc6ea9aa68 src/security_scanner/cli/commands/scan.py: 2d03278acbb08dd56a0c79c7e390cb2375a7b41dbefde777ee7c4e79bdc274cd src/security_scanner/cli/commands/scan_health.py: 255b6243d2f05a4b6c59e0f5056b0b27f1db15e52ac0d77219a97586d3621b61