From 2b11985c0caba11554d24e6689626055f4cb0a89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=98=A4=EC=A4=80=EC=84=9D=28Junseok=20Oh=29DevOps?= <52226147+Atom-oh@users.noreply.github.com> Date: Fri, 25 Sep 2026 21:02:27 +0000 Subject: [PATCH] docs: correct route counts and document network-path and SG rule APIs README, CLAUDE.md and web/app/CLAUDE.md quoted 99 API routes and 110 components; the tree has 104 routes and 118 components. The API reference was missing the network-path check routes and the SG rule inventory, refresh and flow-source routes. README also still described v1 as being decommissioned. A new vitest derives the counts from the tree and checks every route has an API reference row, so the numbers can't drift again. Co-Authored-By: Claude Opus 5.5 --- CLAUDE.md | 2 +- README.md | 16 +++++++------- docs/api-reference.md | 16 +++++++++++++- web/app/CLAUDE.md | 2 +- web/lib/docs-stats.test.ts | 43 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 68 insertions(+), 11 deletions(-) create mode 100644 web/lib/docs-stats.test.ts diff --git a/CLAUDE.md b/CLAUDE.md index f7aab8b0..a7c22a1d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -116,7 +116,7 @@ Architecture decision records (ADRs 001–021 + the BASELINE invariant register) ## Implementation References Per-layer implementation references live under `docs/reference/` (index: [README](docs/reference/README.md)) — [01 Edge Network](docs/reference/01-edge-network.md) · [02 Auth](docs/reference/02-auth.md) · [03 Aurora Data](docs/reference/03-data-aurora.md) · [04 Web BFF](docs/reference/04-web-bff.md) · [05 AgentCore](docs/reference/05-agentcore.md) · [06 Workers](docs/reference/06-workers.md) · [07 EKS](docs/reference/07-eks.md). -Full overview: [docs/architecture.md](docs/architecture.md) (bilingual + mermaid) · New joiners: [docs/onboarding.md](docs/onboarding.md) · Full API index (99 routes): [docs/api-reference.md](docs/api-reference.md) · Operations: [docs/runbooks/](docs/runbooks/). +Full overview: [docs/architecture.md](docs/architecture.md) (bilingual + mermaid) · New joiners: [docs/onboarding.md](docs/onboarding.md) · Full API index (104 routes): [docs/api-reference.md](docs/api-reference.md) · Operations: [docs/runbooks/](docs/runbooks/). Agent readiness changes require `cd agent && python3 -m pytest test_agent.py test_readiness.py -q`. diff --git a/README.md b/README.md index c1bf9c36..41d4e758 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ AWS + Kubernetes operations dashboard with real-time monitoring, a private Cloud ## Overview -AWSops v2 is a single-pane operations dashboard for AWS and Kubernetes, rebuilt as a Terraform-based MSA: a private edge (CloudFront VPC Origin → internal ALB → ECS Fargate), Cognito + Lambda@Edge auth, Aurora Serverless v2 persistent state, AgentCore section agents for live AWS queries, and an OOM-safe async worker tier. The previous v1 architecture (single EC2, CDK, embedded Steampipe) is being decommissioned per ADR-016 — per ADR-016 (decision records are maintained in the private upstream repository). +AWSops v2 is a single-pane operations dashboard for AWS and Kubernetes, rebuilt as a Terraform-based MSA: a private edge (CloudFront VPC Origin → internal ALB → ECS Fargate), Cognito + Lambda@Edge auth, Aurora Serverless v2 persistent state, AgentCore section agents for live AWS queries, and an OOM-safe async worker tier. The previous v1 architecture (single EC2, CDK, embedded Steampipe) was decommissioned per ADR-016 (2026-07-09); its code was removed from this tree on 2026-07-12 (decision records are maintained in the private upstream repository). ![AWSops v2 Architecture](images/awsops_arch_v2.png) @@ -32,7 +32,7 @@ Internet -> CloudFront (TLS, Lambda@Edge Cognito auth) -> VPC Origin (https-only -> async workers: POST /api/jobs -> SQS -> Step Functions -> Lambda or Fargate worker ``` -Stats: 41 pages, 99 API routes, 110 components (`web/`), 21 consolidated ADRs, Terraform-managed (`terraform/foundation`, no CDK). +Stats: 41 pages, 104 API routes, 118 components (`web/`), 21 consolidated ADRs, Terraform-managed (`terraform/foundation`, no CDK). > **No public ALB.** The edge is fully private — CloudFront reaches the ALB only through a VPC Origin, and the ALB only accepts traffic from CloudFront's managed security group. v2's posture is a **read-only ops dashboard + AI diagnosis**: AWS-resource mutation and autonomous remediation are FROZEN by design (ADR-005) — infra changes stay with the operator's own IaC/Change Manager, with one narrowly-scoped exception for self-healing service restarts (ADR-015). (ADR-019's SG-rules Athena role is a separate, ordinary GATED feature — ADR-019 concludes it sits inside the existing read-only invariant and is not an ADR-005 exception.) @@ -176,7 +176,7 @@ AgentCore's own config (runtime ARN, Memory ID, Code Interpreter ID) is written ``` awsops/ - web/ # Next.js 15 thin-BFF: 41 pages, 99 API routes, 110 components + web/ # Next.js 15 thin-BFF: 41 pages, 104 API routes, 118 components agent/ # Strands Agent (Runtime source) + MCP Lambda tool sources terraform/foundation/ # single Terraform root: network, edge, auth, data, workload, ai, workers, eks scripts/v2/ # configure/deploy/migrate/agentcore/workers tooling (all Node.js/Python) @@ -230,7 +230,7 @@ including the conditional documentation build and full presentation archive chec ## API Documentation -The 99 API routes live under `web/app/api/`. Key routes: `health` (public), `stream` (SSE chat), `db` (Aurora ping), `jobs` (+`/[id]`, async job submission/status), `security`, `compliance`, `auth/login`. See the docs site for user-facing guidance. +The 104 API routes live under `web/app/api/`. Key routes: `health` (public), `stream` (SSE chat), `db` (Aurora ping), `jobs` (+`/[id]`, async job submission/status), `security`, `compliance`, `auth/login`. See the docs site for user-facing guidance. ## Contributing @@ -261,7 +261,7 @@ Licensed under the MIT License. See [LICENSE](LICENSE) for details. ## 개요 -AWSops v2는 AWS와 Kubernetes를 위한 단일 화면 운영 대시보드로, Terraform 기반 MSA로 재구축되었습니다: 비공개 엣지(CloudFront VPC Origin → 내부 ALB → ECS Fargate), Cognito + Lambda@Edge 인증, Aurora Serverless v2 영속 상태, 라이브 AWS 조회를 수행하는 AgentCore 섹션 에이전트, OOM-안전 비동기 워커 계층으로 구성됩니다. 이전 v1 아키텍처(단일 EC2, CDK, 내장 Steampipe)는 ADR-016에 따라 폐기 진행 중입니다 — (결정 기록은 비공개 upstream 리포지토리에서 관리됩니다). +AWSops v2는 AWS와 Kubernetes를 위한 단일 화면 운영 대시보드로, Terraform 기반 MSA로 재구축되었습니다: 비공개 엣지(CloudFront VPC Origin → 내부 ALB → ECS Fargate), Cognito + Lambda@Edge 인증, Aurora Serverless v2 영속 상태, 라이브 AWS 조회를 수행하는 AgentCore 섹션 에이전트, OOM-안전 비동기 워커 계층으로 구성됩니다. 이전 v1 아키텍처(단일 EC2, CDK, 내장 Steampipe)는 ADR-016에 따라 폐기되었고(2026-07-09), 코드는 2026-07-12에 이 트리에서 제거되었습니다 (결정 기록은 비공개 upstream 리포지토리에서 관리됩니다). ![AWSops v2 Architecture](images/awsops_arch_v2.png) @@ -272,7 +272,7 @@ Internet -> CloudFront (TLS, Lambda@Edge Cognito 인증) -> VPC Origin (https-on -> 비동기 워커: POST /api/jobs -> SQS -> Step Functions -> Lambda 또는 Fargate 워커 ``` -현황: 41 페이지, 99 API 라우트, 110 컴포넌트(`web/`), 21개 통합 ADR, Terraform 관리(`terraform/foundation`, CDK 없음). +현황: 41 페이지, 104 API 라우트, 118 컴포넌트(`web/`), 21개 통합 ADR, Terraform 관리(`terraform/foundation`, CDK 없음). > **공개 ALB 없음.** 엣지는 완전히 비공개입니다 — CloudFront는 VPC Origin을 통해서만 ALB에 도달하고, ALB는 CloudFront 관리형 보안 그룹의 트래픽만 허용합니다. v2의 자세는 **read-only 운영 대시보드 + AI 진단**입니다: AWS 리소스 변경·자율 조치는 설계상 FROZEN(ADR-005) — 인프라 변경은 운영자 자신의 IaC/Change Manager가 담당하며, 자가치유 서비스 재시작 하나만 좁게 예외 허용됩니다(ADR-015). (ADR-019의 SG-rules Athena role은 별개의 일반 GATED 기능입니다 — ADR-019는 이것이 기존 read-only 불변식 내부에 있다고 결론 내리며, ADR-005 예외가 아닙니다.) @@ -411,7 +411,7 @@ AgentCore 자체 설정(runtime ARN, Memory ID, Code Interpreter ID)은 provisio ``` awsops/ - web/ # Next.js 15 thin-BFF: 41 페이지, 99 API 라우트, 110 컴포넌트 + web/ # Next.js 15 thin-BFF: 41 페이지, 104 API 라우트, 118 컴포넌트 agent/ # Strands Agent(Runtime 소스) + MCP Lambda 도구 소스 terraform/foundation/ # 단일 Terraform 루트: network, edge, auth, data, workload, ai, workers, eks scripts/v2/ # configure/deploy/migrate/agentcore/workers 도구(전부 Node.js/Python) @@ -465,7 +465,7 @@ controller/workflow 검사에는 Python 3·PyYAML·boto3/botocore (`pip install ## API 문서 -99개 API 라우트가 `web/app/api/`에 있습니다. 주요 라우트: `health`(공개), `stream`(SSE 채팅), `db`(Aurora ping), `jobs`(+`/[id]`, 비동기 작업 제출/상태), `security`, `compliance`, `auth/login`. 사용자 가이드는 docs site를 참고하세요. +104개 API 라우트가 `web/app/api/`에 있습니다. 주요 라우트: `health`(공개), `stream`(SSE 채팅), `db`(Aurora ping), `jobs`(+`/[id]`, 비동기 작업 제출/상태), `security`, `compliance`, `auth/login`. 사용자 가이드는 docs site를 참고하세요. ## 기여 방법 diff --git a/docs/api-reference.md b/docs/api-reference.md index b34e2215..574aee8f 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -270,10 +270,24 @@ The opt-in `/topology?view=e2e` view uses the pure `web/lib/e2e-topology.ts` mod | `/api/dns-logs` | GET | Resolver query-log 설정 상태(메뉴 게이트) — 미설정/무권한도 200 + 빈 configs | verifyUser | | `/api/dns-logs/analytics` | GET | Resolver 로그 집계 분석 (Logs Insights 병렬 폴링, `maxDuration` 60s, group은 라이브 allowlist 검증) | verifyUser | -## sg (1) +## sg (4) | 경로 | 메서드 | 역할 | 인증 | |------|--------|------|------| | `/api/sg` | GET | Security Group 사용 분석(ENI 부착+상호참조 미사용 감지, 룰 소스/목적지 식별). `?regions=`로 리전 스코핑(안 주면 인벤토리 전 리전). `?view=hits&id=sg-...` 트래픽 히트 매칭 — Flow Logs 우선(ACCEPT만 룰 귀속), NFM 폴백은 **상대 식별 전용**(양방향 집계라 룰 귀속 불가, hits=null) | verifyUser | +| `/api/sg/rules` | GET | Filtered, paginated SG-rule inventory and activity from Aurora. Filters are bound parameters (`web/lib/sg-rules.ts`), never SQL fragments | verifyUser | +| `/api/sg/rules/refresh` | POST | Manual refresh — enqueues the same `sg_rule_scan` job the daily dispatcher uses via `enqueueJob` (not the generic `/api/jobs`, ADR-009). 503 when workers are disabled | verifyUser + isAdmin | +| `/api/sg/flow-sources` | GET, PUT | Flow Log Athena source configuration (ADR-019). GET for any user; PUT validates workgroup/database/table against allowlist regexes, and the live existence check runs in the isolated Athena broker Lambda | verifyUser (+ isAdmin on PUT) | + + +## network-paths (4) +All routes return 503 unless `NETWORK_PATH_CHECK_ENABLED=true` (`web/lib/network-path-gate.ts`). + +| Path | Method | Role | Auth | +|------|--------|------|------| +| `/api/network-paths` | GET, POST | List saved network-path checks / create one (`created_by_sub` = caller) | verifyUser | +| `/api/network-paths/[id]` | GET, PATCH, DELETE | Read a check; edit or soft-delete it (creator or admin only, otherwise 403) | verifyUser | +| `/api/network-paths/[id]/runs` | GET, POST | Run history (most recent first, max 50; kept after soft delete) / start a run, which enqueues the `network_path` job directly (ADR-009). New runs are also refused by `networkPathLiveTopologyCapabilityGate` until live topology re-reads are available | verifyUser | +| `/api/network-path-runs/[runId]` | GET | Run status and phase; once concluded, `overall_status`, candidates, steps and the validation bundle | verifyUser | ## anfw (1) | 경로 | 메서드 | 역할 | 인증 | diff --git a/web/app/CLAUDE.md b/web/app/CLAUDE.md index 3ce33f8d..1427afc8 100644 --- a/web/app/CLAUDE.md +++ b/web/app/CLAUDE.md @@ -1,7 +1,7 @@ # App Routes Module ## Role -Next.js App Router — 41 pages + 99 API routes (`app/api/`). APIs are thin-BFF: Aurora reads, AWS SDK reads, and AgentCore calls only. Long/OOM-risk work is enqueued — but only through allowlisted (`noop`-family) types on the generic `POST /api/jobs`; domain jobs (`report`, `compliance`, etc.) go through their own ownership-checked dedicated routes (ADR-009), never the generic one. +Next.js App Router — 41 pages + 104 API routes (`app/api/`). APIs are thin-BFF: Aurora reads, AWS SDK reads, and AgentCore calls only. Long/OOM-risk work is enqueued — but only through allowlisted (`noop`-family) types on the generic `POST /api/jobs`; domain jobs (`report`, `compliance`, etc.) go through their own ownership-checked dedicated routes (ADR-009), never the generic one. ## Structure - Pages: overview `page.tsx`, `inventory/[type]` · `inventory/g/[group]` · `inventory/ecs` (unified ECS overview), `eks/` (overview · nodes · pods · deployments · services · explorer · cost · `[cluster]`), `topology/` (overview · infra · services · `resource/[id]`), `monitoring`, `network-flow`, `dns-query`, `ip-addresses`, `vpc-endpoints`, `direct-connect`, `network-firewall`, `sg/usage` · `sg/rules` (SG Rules & Usage, ADR-019), `network-paths` (+`[id]`, Network Path Check saved definitions/runs), `security`, `compliance`, `cost` (+FinOps baseline-recommendations card, ADR-020), `bedrock`, `agentcore`, `ai-diagnosis` (+`report` print view), `assistant`, `datasources`, `integrations` (+`datasources/[id]`), `accounts`, `customization`, `jobs`, `login`. diff --git a/web/lib/docs-stats.test.ts b/web/lib/docs-stats.test.ts new file mode 100644 index 00000000..6eb19acc --- /dev/null +++ b/web/lib/docs-stats.test.ts @@ -0,0 +1,43 @@ +import { readFileSync, readdirSync } from 'node:fs'; +import { join, relative } from 'node:path'; +import { describe, it, expect } from 'vitest'; + +// The route/page/component counts quoted in README and CLAUDE.md drifted repeatedly (99 vs 97 vs +// the real number). Derive them from the tree so a new route fails CI until the docs follow. +const web = new URL('..', import.meta.url).pathname; +const root = join(web, '..'); +const read = (p: string) => readFileSync(join(root, p), 'utf8'); + +function walk(dir: string, match: (name: string) => boolean): string[] { + return readdirSync(dir, { withFileTypes: true }).flatMap((e) => { + const p = join(dir, e.name); + if (e.isDirectory()) return e.name === 'node_modules' ? [] : walk(p, match); + return match(e.name) ? [p] : []; + }); +} + +const routes = walk(join(web, 'app/api'), (n) => n === 'route.ts') + .map((p) => '/' + relative(join(web, 'app'), p).replace(/\/route\.ts$/, '')); +const pages = walk(join(web, 'app'), (n) => n === 'page.tsx').length; +const components = walk(join(web, 'components'), (n) => n.endsWith('.tsx') && !n.endsWith('.test.tsx')).length; + +describe('documented web stats match the tree', () => { + it('README quotes the actual page, route and component counts (English and Korean)', () => { + const readme = read('README.md'); + expect(readme).toContain(`${pages} pages, ${routes.length} API routes, ${components} components`); + expect(readme).toContain(`${pages} 페이지, ${routes.length} API 라우트, ${components} 컴포넌트`); + expect(readme).toContain(`The ${routes.length} API routes live under`); + expect(readme).toContain(`${routes.length}개 API 라우트`); + }); + + it('CLAUDE.md files quote the actual route count', () => { + expect(read('CLAUDE.md')).toContain(`Full API index (${routes.length} routes)`); + expect(read('web/app/CLAUDE.md')).toContain(`${pages} pages + ${routes.length} API routes`); + }); + + it('docs/api-reference.md has a row for every route', () => { + const ref = read('docs/api-reference.md'); + const documented = new Set([...ref.matchAll(/^\| `(\/api[^`]*)`/gm)].map((m) => m[1])); + expect(routes.filter((r) => !documented.has(r))).toEqual([]); + }); +});